Authored by huangyCode

Merge branch 'feature/group-buy' of http://git.yoho.cn/fe/yohobuywap-node into feature/group-buy

... ... @@ -58,7 +58,17 @@ function groupList(req, res, next) {
});
}).catch(next);
}
function filter(req, res, next) {
let params = Object.assign({}, req.query);
req.ctx(GroupService).filterData(params).then((result) => {
res.render('group/filter', {
layout: false,
params: params,
filter: result
});
}).catch(next);
}
function progress(req, res, next) {
const uid = req.user.uid;
const groupNo = req.query.groupNo;
... ... @@ -152,5 +162,6 @@ module.exports = {
groupList,
progress,
goodsDetail,
order
order,
filter
};
... ...
... ... @@ -79,6 +79,24 @@ class GroupApi extends global.yoho.BaseModel {
}));
}
_getFilterData(params) {
let option = {
data: {
method: 'app.collage.filter',
...params
},
param: {
code: 200
}
};
return this.get(option).then((result => {
let filterData = result.data;
return filterData;
}));
}
/**
* 拼团详情
* @param uid
... ...
const GroupApi = require('./group-api');
const _ = require('lodash');
const filterProcess = require('../../../utils/group-filter-process');
class GroupService extends global.yoho.BaseModel {
constructor(ctx) {
... ... @@ -20,7 +21,11 @@ class GroupService extends global.yoho.BaseModel {
}
async groupResult({groupNo, activityId, uid} = {}) {
async groupResult({
groupNo,
activityId,
uid
} = {}) {
try {
const [detail, recommend] = await Promise.all([
this.api.groupDetail({
... ... @@ -66,7 +71,12 @@ class GroupService extends global.yoho.BaseModel {
}
}
async groupResultRec({activityId, uid, page, limit} = {}) {
async groupResultRec({
activityId,
uid,
page,
limit
} = {}) {
try {
const recommend = await this.api.activityRecommend({
uid,
... ... @@ -97,6 +107,11 @@ class GroupService extends global.yoho.BaseModel {
}
return tabsData;
}
async filterData(params) {
let result = await this.api._getFilterData(params);
return filterProcess.processFilter(result.filter || []);
}
async filterGroupList(params) {
const initParams = {
... ...
... ... @@ -369,6 +369,7 @@ router.post('/yoluck/detail/mylist.html', yoluck.getActivityCodeList);
router.get('/group', group.index); // 拼团首页
router.get('/group/list', group.groupListIndex); // 拼团列表页
router.get('/group/goods-list', group.groupList); // 拼团列表
router.get('/group/filter', group.filter); // 首页筛选结果
router.get('/group/progress', auth, group.progress); // 拼团状态详情页
router.get('/group/goodsDetail', group.goodsDetail);
router.get('/group/order', auth, group.order); // 我的拼团
... ...
{{> group/resources/filter-page}}
\ No newline at end of file
... ...
... ... @@ -23,7 +23,6 @@
{{/each}}
</div>
<div id='fixedTab' class="tab-filter">
{{!-- {{#if floatTab}} float{{/if}}{{#if !tabs.showTab}} only-filter{{/if}} --}}
{{#if tabs.showTab}}
<div class="tab group-tab">
<div class="tab-item">
... ... @@ -46,7 +45,7 @@
</div>
{{/if}}
{{> common/filter}}
{{!-- {{> group/resources/filter-page}} --}}
</div>
<a class="bottom" href="/activity/group/order">我的拼团</a>
... ...
... ... @@ -4,62 +4,69 @@
<div class="inner-card go-join" data-product-skn="{{yourJoinItem.productSkn}}"
data-activity-id="{{activityId}}" data-group-no="{{groupNo}}" data-page-go="{{pageGo}}">
<img src="{{image2 yourJoinItem.productIcon w=200 h=282}}" alt="" class="card-pre-img">
<div class="info" >
<div class="info">
<div class="name">{{yourJoinItem.productName}}</div>
<div class="group-price">{{yourJoinItem.productGroupPrice}}</div>
<div class="single-buy-price">单人购买:<span class="line-through">{{yourJoinItem
.productSalePrice}}</span></div>
</div>
</div>
<div class="members">
{{#membershipItems}}
{{#if empty}}
<div class="meb-item empty"></div>
{{else}}
<div class="meb-item">
<img class="avatar" src="{{image2 headUrl w=160 h=160}}" alt="">
{{#if @first}}
<span class="leader-badge"></span>
<div class="card-body">
<div class="members">
{{#membershipItems}}
{{#if empty}}
<div class="meb-item empty"></div>
{{else}}
<div class="meb-item">
<img class="avatar" src="{{image2 headUrl w=160 h=160}}" alt="">
{{#if @first}}
<span class="leader-badge"></span>
{{/if}}
</div>
{{/if}}
{{/membershipItems}}
</div>
{{/if}}
{{/membershipItems}}
</div>
{{#ifcond pageGo '===' 1}}
<div class="status-text">还差{{lackNum}}人拼团成功,剩余时间</div>
<div class="status-btn positive">邀请小伙伴拼团</div>
{{/ifcond}}
{{#ifcond pageGo '===' 1}}
<div class="status-tip">还差<span class="red">{{lackNum}}</span>拼团成功,剩余时间</div>
{{> group/progress-countdown}}
<div class="status-btn positive">邀请小伙伴拼团{{#ifcond joinLimit '==' 1}}(仅限新人){{/ifcond}}</div>
{{/ifcond}}
{{#ifcond pageGo '===' 2}}
<div class="status-text">还差{{lackNum}}人拼团成功,剩余时间</div>
{{/ifcond}}
{{#ifcond pageGo '===' 2}}
<div class="status-tip">还差<span class="red">{{lackNum}}</span>拼团成功,剩余时间</div>
{{> group/progress-countdown}}
<div class="status-btn positive">去参团</div>
{{/ifcond}}
{{#ifcond pageGo '===' 3}}
<div class="status-text">还差{{lackNum}}人拼团成功,剩余时间</div>
<div class="status-btn positive">邀请小伙伴拼团</div>
{{/ifcond}}
{{#ifcond pageGo '===' 3}}
<div class="status-tip">还差<span class="red">{{lackNum}}</span>拼团成功,剩余时间</div>
{{> group/progress-countdown}}
<div class="status-btn positive">邀请小伙伴拼团{{#ifcond joinLimit '==' 1}}(仅限新人){{/ifcond}}</div>
{{/ifcond}}
{{#ifcond pageGo '===' 4}}
<div class="status-text">拼团成功</div>
{{/ifcond}}
{{#ifcond pageGo '===' 4}}
<div class="status-text">拼团成功</div>
<div class="status-btn">查看更多拼团活动</div>
{{/ifcond}}
{{#ifcond pageGo '===' 5}}
<div class="status-text">你来晚了 你的好友拼团已经成功</div>
{{/ifcond}}
{{#ifcond pageGo '===' 5}}
<div class="status-text">你来晚了 你的好友拼团已经成功</div>
<div class="status-btn positive">我也要开团</div>
{{/ifcond}}
{{#ifcond pageGo '===' 6}}
<div class="status-text">拼团失败</div>
<div class="status-btn">查看更多拼团活动</div>
{{/ifcond}}
{{#ifcond pageGo '===' 6}}
<div class="status-text">拼团失败</div>
{{/ifcond}}
{{#ifcond pageGo '===' 7}}
<div class="status-text">拼团失败</div>
<div class="status-btn">查看更多拼团活动</div>
{{/ifcond}}
{{#ifcond pageGo '===' 7}}
<div class="status-text">拼团失败</div>
<div class="status-btn">查看更多拼团活动</div>
{{/ifcond}}
<a class="tip" href="http://m.yohobuy.com/activity/group">支付开团-支付参团-凑齐人数发货-凑不齐退款 玩法介绍》</a>
<a class="tip" href="http://m.yohobuy.com/activity/group">支付开团-支付参团-凑齐人数发货-凑不齐退款 玩法介绍》</a>
</div>
</div>
{{/data}}
<div class="divide"></div>
... ...
<div class="count-down">
<span class="digit-square">1</span>
<span class="digit-square">1</span> :
<span class="digit-square">1</span>
<span class="digit-square">1</span> :
<span class="digit-square">1</span>
<span class="digit-square">1</span>
</div>
... ...
{{# filter}}
<div class="filter-mask hide">
<div class="filter-body">
<ul class="classify">
{{#each classify}}
<li class="classify-item{{#if @first}} active{{/if}}" data-bp-id="filter_classify_{{name}}_true">
<p class="shower{{#if default}} default{{/if}}">
<span class="title">{{title}}</span>
{{name}}
</p>
<ul class="sub-classify" data-type={{dataType}}>
{{# subs}}
<li class="sub-item{{#if chosed}} chosed{{/if}}" data-id={{dataId}}
data-bp-id="filter_subclassify_{{name}}_true">
{{name}}
<i class="iconfont chosed-icon">&#xe617;</i>
</li>
{{/ subs}}
</ul>
</li>
{{/each}}
</ul>
</div>
<div class="cancelBtn">取消</div>
</div>
{{/ filter}}
\ No newline at end of file
... ...
... ... @@ -7,7 +7,7 @@
</li> --}}
<li class="new active" data-type="new" data-order="s_t_desc">
<a href="javascript:void(0);">
<span class="span-test">新品</span>
<span class="span-test">最新</span>
</a>
</li>
<li class="popularity" data-type="popularity" data-order="h_v_desc">
... ...
... ... @@ -2,10 +2,10 @@ import 'scss/activity/group/group.page.scss';
import Swiper from 'yoho-swiper';
import $ from 'yoho-jquery';
import Page from 'js/yoho-page';
import filter from 'js/plugin/filter';
// import lazyLoad from 'yoho-jquery-lazyload';
// import tip from 'js/plugin/tip';
// import filter from 'js/plugin/filter';
// import loading from 'js/plugin/loading';
class Group extends Page {
... ... @@ -21,33 +21,35 @@ class Group extends Page {
};
this.navInfo = {
new: {
order: 1,
reload: true,
page: 1,
end: false
},
popularity: {
order: 1,
reload: true,
page: 1,
page: 0,
end: false
},
price: {
order: 1,
order: 's_p_asc',
reload: true,
page: 0,
end: false
}
};
this.noResult = '<p class="no-result">未找到相关搜索结果</p>';
this.$pre = this.selector.filterTab.find('.active');
this.selectedChannel = 'newGroup';
this.filterTab = {
newGroup: {
$pre: this.selector.filterTab.find('.active'), // 记录新团筛选tab的激活项
joinLimit: 1,
order: 's_t_desc'
},
normalGroup: {
$pre: this.selector.filterTab.find('.active'), // 记录普通团筛选tab的激活项
joinLimit: 2,
order: 's_t_desc'
}
... ... @@ -58,7 +60,7 @@ class Group extends Page {
};
this.bindEvents();
this.swiperTop();
this.filterInit();
$(window).scroll(() => {
window.requestAnimationFrame(this.scrollHandler.bind(this));
});
... ... @@ -94,38 +96,82 @@ class Group extends Page {
}
filterTabChange(e) {
let $this = $(e.currentTarget);
let order = $this.data('order');
let navType = $this.data('type');
let currentChannel = this.selectedChannel;
let nav = this.navInfo[navType];
if ($this.hasClass('new') || $this.hasClass('popularity')) {
// 筛选状态设置
if ($this.hasClass('filter')) {
if ($this.hasClass('active')) {
$('.filter-mask').addClass('hide');
$this.removeClass('active');
} else {
$this.addClass('active');
filter.showFilter();
}
} else {
this.selector.filterTab.children('li').removeClass('active');
$this.addClass('active');
this.filterTab[currentChannel].order = order;
this.filterTab[currentChannel].$pre = $this;
}
this.search();
// 最新和人气
if ($this.hasClass('new') || $this.hasClass('popularity')) {
nav.reload = true;
nav.order = $this.data('order');
this.filterTab[currentChannel].order = nav.order;
}
if ($this.hasClass('price') || $this.hasClass('discount')) {
// 价格/折扣切换排序状态
// 价格切换排序状态
if ($this.hasClass('price')) {
$this.find('.icon > .iconfont').toggleClass('cur');
nav.reload = true; // 重置reload,HTML会被替换为逆序的HTML
nav.order = nav.order === 0 ? 1 : 0; // 切换排序
nav.reload = true;
nav.order = nav.order === 's_p_asc' ? 's_p_desc' : 's_p_asc'; // 切换排序
this.filterTab[currentChannel].order = nav.order;
}
}
this.search();
}
groupTabChange(e) {
let target = $(e.target);
let channel = target.data('channel');
let navInfo = this.navInfo;
let filterTab = this.filterTab[channel];
// 切换tab
if (!target.hasClass('active')) {
this.selector.groupTab.find('.tiptext').removeClass('active');
target.addClass('active');
}
this.selectedChannel = channel;
// 筛选tab状态重置
for (let i in navInfo) {
if (navInfo.hasOwnProperty(i)) {
navInfo[i].reload = true;
navInfo[i].end = false;
if (navInfo[i].hasOwnProperty('order')) {
navInfo[i].order = 's_p_asc';
}
}
}
this.selector.filterTab.children('li').removeClass('active');
filterTab.$pre.addClass('active');
// 价格筛选状态设置
if (filterTab.$pre.hasClass('price')) {
navInfo.price.order = filterTab.order;
filterTab.$pre.find('.icon > .iconfont').removeClass('cur');
if (navInfo.price.order === 's_p_asc') {
filterTab.$pre.find('.icon > .up').addClass('cur');
} else {
filterTab.$pre.find('.icon > .down').addClass('cur');
}
}
this.search();
}
... ... @@ -154,7 +200,7 @@ class Group extends Page {
search() {
let params = this.filterTab[this.selectedChannel];
console.log(params);
console.log(params, this.navInfo.price);
// let setting;
... ... @@ -176,35 +222,35 @@ class Group extends Page {
}
// 筛选初始化
// filterInit(option) {
// let $filterMask;
// $.ajax({
// type: 'GET',
// url: '/product/sale/filter',
// data: $.extend(defaultOpt, {
// saleType: '1'
// }, option),
// success: function(data) {
// $filterMask && $filterMask.remove();
// this.selector.groupListContent.append(data);
// $filterMask = $('.filter-mask');
// // 初始化filter&注册filter回调
// filter.initFilter({
// fCbFn: this.search,
// hCbFn: function() {
// // 切换active状态到$pre上
// this.$pre.addClass('active');
// this.$pre.siblings().removeClass('active');
// },
// missStatus: true
// });
// }
// });
// }
filterInit() {
let $filterMask;
let requestParams = this.filterTab[this.selectedChannel];
let filterTab = this.selector.filterTab;
delete requestParams.$pre;
console.log(requestParams);
$.ajax({
type: 'GET',
url: '/activity/group/filter',
data: requestParams,
success: function(data) {
$filterMask && $filterMask.remove();
$('.group').append(data);
$filterMask = $('.filter-mask');
// 初始化filter&注册filter回调
filter.initFilter({
fCbFn: this.search,
hCbFn: function() {
filterTab.children('.filter').removeClass('active');
},
missStatus: true
});
}
});
}
}
$(() => {
... ...
... ... @@ -4,47 +4,41 @@
.card {
position: relative;
width: 680px;
height: 690px;
margin: 0 auto;
background: #fff;
box-shadow: 0 0 20px rgba(205, 205, 205, 0.5);
}
.status-text {
position: absolute;
bottom: 250px;
left: 50%;
transform: translateX(-50%);
font-size: 36px;
color: #444;
letter-spacing: -1px;
text-align: center;
font-weight: bold;
}
.status-btn {
position: absolute;
bottom: 130px;
left: 50%;
display: inline-block;
height: 80px;
padding: 0 50px;
min-width: 380px;
margin-top: 40px;
font-size: 32px;
color: #fff;
text-align: center;
line-height: 80px;
border-radius: 40px;
background-color: #000;
transform: translateX(-50%);
&.positive {
background: #d0021b;
margin-top: 60px;
}
}
.inner-card {
position: absolute;
left: 44px;
top: -140px;
top: -128px;
width: 600px;
height: 282px;
background: #fff;
... ... @@ -80,44 +74,59 @@
}
}
.card-body {
position: relative;
width: 680px;
padding-top: 152px;
padding-bottom: 130px;
text-align: center;
}
.members {
position: absolute;
top: 194px;
left: 0;
right: 0;
margin-top: 40px;
font-size: 0;
text-align: center;
}
.meb-item {
position: relative;
display: inline-block;
width: 80px;
height: 80px;
margin-left: 40px;
border-radius: 40px;
.meb-item {
position: relative;
display: inline-block;
width: 80px;
height: 80px;
margin-left: 40px;
border-radius: 40px;
&:first-child {
margin-left: 0;
}
&:first-child {
margin-left: 0;
}
&.empty {
background-image: url("img/activity/group/member-empty.png");
background-size: 80px 80px;
}
&.empty {
background-image: url("img/activity/group/member-empty.png");
background-size: 80px 80px;
}
.avatar {
border-radius: 40px;
.avatar {
border-radius: 40px;
}
.leader-badge {
position: absolute;
top: 0;
left: 0;
width: 80px;
height: 80px;
background-image: url("img/activity/group/member-leader.png");
background-size: 80px 80px;
}
}
}
.leader-badge {
position: absolute;
top: 0;
left: 0;
width: 80px;
height: 80px;
background-image: url("img/activity/group/member-leader.png");
background-size: 80px 80px;
.status-tip {
font-size: 24px;
margin-top: 38px;
margin-bottom: 20px;
.red {
color: #d0021b;
}
}
... ... @@ -128,6 +137,7 @@
left: 28px;
font-size: 24px;
color: #b0b0b0;
text-align: left;
letter-spacing: -0.68px;
}
... ... @@ -253,4 +263,16 @@
width: 104px;
height: 110px;
}
.count-down {
.digit-square {
display: inline-block;
width: 36px;
height: 44px;
font-size: 28px;
color: #fff;
line-height: 44px;
background-color: #000;
}
}
}
... ...
... ... @@ -34,6 +34,10 @@
&.cur {
color: #000;
}
&.drop {
color: #000;
}
}
}
... ... @@ -70,3 +74,117 @@
}
}
}
.filter-mask {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 2;
height: 100%;
background: rgba(0, 0, 0, 0.1);
}
.cancel-btn {
position: absolute;
right: 0;
left: 0;
bottom: 0;
height: 80px;
line-height: 80px;
text-align: center;
z-index: 2;
background-color: #444;
color: #fff;
}
.filter-body {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 80px;
z-index: 2;
background: #fff;
color: #000;
font-size: 28px;
cursor: pointer;
.classify {
width: 50%;
height: 100%;
background: #f8f8f8;
> li {
height: 120px;
line-height: 120px;
> * {
box-sizing: border-box;
}
&.active {
background: #fff;
}
.shower {
overflow: hidden;
padding-left: 40px;
width: 100%;
color: #333;
text-overflow: ellipsis;
white-space: nowrap;
&.highlight {
background: #eee;
}
}
.default {
color: #999;
}
.title {
float: left;
color: #000;
}
}
}
.sub-classify {
position: absolute;
top: 0;
left: 50%;
display: none;
overflow: auto;
-webkit-overflow-scrolling: touch;
width: 50%;
height: 880px;
> li {
overflow: hidden;
padding-left: 30px;
height: 120px;
border-bottom: 1px solid #e6e6e6;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 120px;
&.highlight {
background: #eee;
}
}
.chosed-icon {
display: none;
}
.chosed .chosed-icon {
display: inline;
}
}
.active > .sub-classify {
display: block;
}
}
... ...
const _ = require('lodash');
const toArray = (obj) => {
if (_.isArray(obj)) {
return obj;
}
let arr = [];
_.forEach(obj, (v, k) => {
if (_.isObject(v)) {
v._key = k;
} else {
v = {
_key: k,
_value: v
};
}
arr.push(v);
});
return arr;
};
/**
* 按照数组中指定字段排序二维数组
*
* @param array list 需要排序的数组
* @param string key 字段名称
* @param boolean 有 desc 时候降序排列,默认为false
*/
const _sortListByField = (list, key, desc) => {
let array = toArray(list);
return array.sort(function(a, b) {
a = a._key.split(',')[0] * 1;
b = b._key.split(',')[0] * 1;
return desc ? a < b : a > b;
});
};
/**
* 处理筛选数据
* @param list
* @param string | options
* @return array 处理之后的筛选数据
*/
exports.processFilter = (list, options) => {
const filters = {
classify: []
};
const filtersType = {
brand: {
name: '所有品牌',
title: '品牌',
dataId: 'id',
subsName: 'brand_name',
firstSub: 0,
dataType: 'brand',
sortNum: '1'
},
color: {
name: '所有颜色',
title: '颜色',
dataId: 'color_id',
subsName: 'color_name',
firstSub: 0,
dataType: 'color',
sortNum: '4'
},
discount: {
name: '所有商品',
title: '折扣',
dataId: 'key',
subsName: 'name',
firstSub: '0,1',
dataType: 'p_d',
sortNum: '7'
},
gender: {
name: '所有性别',
title: '性别',
dataId: 'key',
subsName: 'flag',
firstSub: '1,2,3',
dataType: 'gender',
sortNum: '0'
},
group_sort: {
name: '所有品类',
title: '品类',
dataId: 'relation_parameter',
subsName: 'category_name',
firstSub: 0,
dataType: 'sort',
sortNum: '3'
},
priceRange: {
name: '所有价格',
title: '价格',
dataId: 'key',
subsName: 'flag',
firstSub: 0,
dataType: 'price',
sortNum: '6'
},
size: {
name: '所有尺码',
title: '尺码',
dataId: 'size_id',
subsName: 'size_name',
firstSub: 0,
dataType: 'size',
sortNum: '5'
},
ageLevel: {
name: '所有人群',
title: '人群',
dataId: 'id',
subsName: 'name',
firstSub: 0,
dataType: 'ageLevel',
sortNum: '2'
}
};
options = Object.assign({
gender: '1,2,3', // 默认选择的性别,默认1,2,3表示所有
exclude: null // 需要排除的字段
}, options);
_.forEach(list, (item) => {
let classify = {
subs: []
};
let key = item.filterId;
if (!filtersType[key]) {
return;
}
if ((options.hideSize && key === 'size') || (options.hideSort && key === 'group_sort')) {
return;
}
classify.dataType = filtersType[key].dataType;
classify.name = filtersType[key].name;
classify.title = filtersType[key].title;
classify.subs.push({
chosed: true,
dataId: filtersType[key].firstSub,
name: filtersType[key].name
});
// 折扣,价格区间,需要排序
if (key === 'discount' || key === 'priceRange') {
item = _sortListByField(item, 'name');
}
_.forEach(item.itemList, (sub) => {
let subs = {};
subs.dataId = sub.itemId;
subs.name = sub.itemName;
// if (key === 'discount') {
// subs.dataId = sub._key;
// } else if (key === 'priceRange') {
// subs.dataId = sub._key;
// } else if (filtersType[key].dataId === 'key') {
// subs.dataId = index;
// } else if (filtersType[key].dataId === 'relation_parameter') {
// subs.dataId = sub.relation_parameter['sort']; // eslint-disable-line
// } else {
// subs.dataId = sub[filtersType[key].dataId];
// }
// if (key === 'priceRange') {
// subs.name = sub._value.replace('¥', '¥');
// } else if (filtersType[key].subsName === 'flag') {
// subs.name = sub;
// } else {
// subs.name = sub[filtersType[key].subsName];
// if (key === 'discount') {
// subs.name = subs.name + '折商品';
// }
// }
if (options[classify.dataType] + '' === subs.dataId + '') {
subs.chosed = true;
classify.subs[0].chosed = false;
}
classify.subs.push(subs);
});
filters.classify[filtersType[key].sortNum] = classify;
});
return filters;
};
... ...