Showing 63 changed files with 3224 additions and 703 deletions

Too many changes to show.

To preserve performance only 63 of 63+ files are displayed.

  1 +/**
  2 + * 领券中心 controller
  3 + * @author: shenzm<zhimin.shen@yoho.cn>
  4 + * @date: 2016/09/29
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const couponModel = require('../models/coupon');
  10 +
  11 +exports.index = (req, res, next) => {
  12 + const channel = req.cookies._Channel || 'boys';
  13 +
  14 + couponModel.getCouponData(channel, {
  15 + uid: req.user.uid,
  16 + contentCode: req.query.contentCode
  17 + }).then(result => {
  18 + res.render('coupon', Object.assign({
  19 + module: 'activity',
  20 + page: 'coupon'
  21 + }, result));
  22 + }).catch(next);
  23 +};
  24 +
  25 +exports.getCouponStatus = (req, res, next) => {
  26 + couponModel.getCouponStatus({
  27 + uid: req.user.uid,
  28 + contentCode: req.query.contentCode
  29 + }).then(result => {
  30 + res.json(result);
  31 + }).catch(next);
  32 +};
  33 +
  34 +exports.sendcoupon = (req, res, next) => {
  35 + couponModel.sendcoupon(req.query.id, req.user.uid).then(result => {
  36 + res.json(result);
  37 + }).catch(next);
  38 +};
  1 +/**
  2 + * activity model
  3 + * @author: shenzm<zhimin.shen@yoho.cn>
  4 + * @date: 2016/09/29
  5 + */
  6 +'use strict';
  7 +
  8 +const Promise = require('bluebird');
  9 +const api = global.yoho.API;
  10 +const crypto = global.yoho.crypto;
  11 +const helpers = global.yoho.helpers;
  12 +const HeaderModel = require('../../../doraemon/models/header');
  13 +const homeService = require('../../product/models/home-service');
  14 +
  15 +exports.getCouponData = (channel, params) => {
  16 + return Promise.coroutine(function*() {
  17 + const result = {
  18 + pathNav: [homeService.getHomeChannelNav(channel), {
  19 + name: '领券频道'
  20 + }],
  21 + footerTop: true,
  22 + topBanner: {
  23 + list: []
  24 + },
  25 + categories: []
  26 + };
  27 +
  28 + const requestData = yield Promise.all([
  29 + api.get('', Object.assign(params, {
  30 + method: 'app.promotion.queryCouponCenter'
  31 + })),
  32 + HeaderModel.requestHeaderData(channel)
  33 + ]);
  34 +
  35 + const coupon = requestData[0];
  36 +
  37 + result.headerData = requestData[1].headerData;
  38 +
  39 + do {
  40 + if (!coupon.data || !Array.isArray(coupon.data) || coupon.data.length === 0) {
  41 + break;
  42 + }
  43 +
  44 + let i = 0;
  45 +
  46 + coupon.data.forEach(function(val, index) {
  47 + // 头部banner
  48 + if (val.templateName === 'focus') {
  49 + val.data.forEach(function(item) {
  50 + result.topBanner.list.push({
  51 + href: item.url.replace('http:', ''), // banner跳转链接
  52 + img: item.src // banner图片
  53 + });
  54 + });
  55 + } else if (val.template_name === 'getCoupon' && val.data.length) {
  56 + // 优惠券楼层
  57 + if (!coupon.data[index - 1].data || !coupon.data[index - 1].data.text) {
  58 + return;
  59 + }
  60 +
  61 + const obj = {
  62 + title: coupon.data[index - 1].data.text, // 楼层标题
  63 + coupons: []
  64 + };
  65 +
  66 + val.data.forEach(function(item) {
  67 + obj.coupons.push({
  68 + id: crypto.encryption('yoho9646abcdefgh', item.couponID), // 加密优惠券号
  69 + img: helpers.image(item.image.src, 0, 0), // 优惠券图片
  70 + url: item.image.url.replace('http:', '') // 去逛逛链接
  71 + });
  72 + });
  73 +
  74 + result.categories.push(obj);
  75 + i++;
  76 + }
  77 + });
  78 + }
  79 + while (false);
  80 + return result;
  81 + })();
  82 +};
  83 +
  84 +exports.getCouponStatus = (params) => {
  85 + return Promise.coroutine(function*() {
  86 + const coupon = yield api.get('', Object.assign(params, {
  87 + method: 'app.promotion.queryCouponCenter'
  88 + }));
  89 + const result = {
  90 + code: coupon.code,
  91 + categories: []
  92 + };
  93 +
  94 + do {
  95 + if (!coupon.data || !Array.isArray(coupon.data) || coupon.data.length === 0) {
  96 + break;
  97 + }
  98 +
  99 + let i = 0;
  100 +
  101 + coupon.data.forEach(function(val, index) {
  102 + if (val.template_name === 'getCoupon' && val.data.length) {
  103 + // 优惠券楼层
  104 + if (!coupon.data[index - 1].data || !coupon.data[index - 1].data.text) {
  105 + return;
  106 + }
  107 +
  108 + val.data.forEach(function(item) {
  109 + const status = Number(item.status);
  110 +
  111 + if ([2, 3].indexOf(status) > -1) {
  112 + const cou = {
  113 + id: crypto.encryption('yoho9646abcdefgh', item.couponID) // 加密优惠券号
  114 + };
  115 +
  116 + if (status === 2) {
  117 + cou.empty = true; // 优惠券已抢光
  118 + } else if (status === 3) {
  119 + cou.got = true; // 优惠券已领取
  120 + }
  121 +
  122 + result.categories.push(cou);
  123 + }
  124 + });
  125 + i++;
  126 + }
  127 + });
  128 + }
  129 + while (false);
  130 + return result;
  131 + })();
  132 +};
  133 +
  134 +exports.sendcoupon = (couponId, uid) => {
  135 + let returnData = {};
  136 +
  137 + couponId = crypto.decrypt('yoho9646abcdefgh', couponId);
  138 +
  139 + // 领取优惠券
  140 + return api.get('', {
  141 + method: 'app.promotion.getCoupon',
  142 + couponId: couponId,
  143 + uid: uid
  144 + }).then(result => {
  145 + switch (result.code) {
  146 + case 200:
  147 + returnData = {
  148 + code: 200,
  149 + message: '恭喜您,成功领取优惠券',
  150 + url: helpers.urlFormat('/home/coupons')
  151 + };
  152 + break;
  153 + case 401:
  154 + returnData = {
  155 + code: 401,
  156 + message: '您已领取过优惠券'
  157 + };
  158 + break;
  159 + case 315:
  160 + returnData = {
  161 + code: 315,
  162 + message: '优惠券已过期'
  163 + };
  164 + break;
  165 + case 300:
  166 + returnData = {
  167 + code: 300,
  168 + message: '请求参数错误'
  169 + };
  170 + break;
  171 + default:
  172 + returnData = {
  173 + code: 500,
  174 + message: '领券失败!'
  175 + };
  176 + break;
  177 + }
  178 + return returnData;
  179 + });
  180 +};
@@ -9,10 +9,17 @@ @@ -9,10 +9,17 @@
9 const express = require('express'); 9 const express = require('express');
10 const router = express.Router(); // eslint-disable-line 10 const router = express.Router(); // eslint-disable-line
11 const cRoot = './controllers'; 11 const cRoot = './controllers';
  12 +const auth = require(`${global.middleware}/auth`);
12 13
13 const specialController = require(`${cRoot}/special`); 14 const specialController = require(`${cRoot}/special`);
  15 +const coupon = require(`${cRoot}/coupon`);
14 16
15 // 专题活动 17 // 专题活动
16 router.get(/^\/special\/(\d+)_(.*)\.html$/, specialController.special); 18 router.get(/^\/special\/(\d+)_(.*)\.html$/, specialController.special);
17 19
18 -module.exports = router; 20 +// 领券中心
  21 +router.get('/coupon/index', coupon.index);
  22 +router.get('/coupon/couponstatus', coupon.getCouponStatus);
  23 +router.get('/coupon/sendcoupon', auth, coupon.sendcoupon);
  24 +
  25 +module.exports = router;
  1 +<div class="coupon-page yoho-page">
  2 + {{> common/path-nav}}
  3 + {{> common/slide-banner}}
  4 +{{# categories}}
  5 + <div class="title clearfix">
  6 + <span>{{title}}</span>
  7 + </div>
  8 + {{# coupons}}
  9 + <div class="coupon">
  10 + <a href="{{url}}" target="_blank" href="javascript:void(0);" data-id="{{id}}">
  11 + <img src="{{img}}">
  12 + {{#if empty}}
  13 + <div class="coupon-mask"></div>
  14 + {{/if}}
  15 + <div class="enable info" id="{{id}}">
  16 + <div class="normal">
  17 + <p>点击</p>
  18 + <p>领取</p>
  19 + </div>
  20 + <div class="got hidden">
  21 + <p>已领取</p>
  22 + <p class="guang">去使用</p>
  23 + </div>
  24 + <div class="empty hidden">
  25 + <p>已抢光</p>
  26 + <p class="guang">去逛逛</p>
  27 + </div>
  28 + </div>
  29 + </a>
  30 + </div>
  31 + {{/ coupons}}
  32 +{{/ categories}}
  33 +
  34 +</div>
  1 +/**
  2 + * 品牌一览 controller
  3 + * @author: ghw<hongwei.gao@yoho.cn>
  4 + * @date: 2016/9/29
  5 + */
  6 +
  7 +'use strict';
  8 +const mRoot = '../models';
  9 +
  10 +const brandsService = require(`${mRoot}/brands-service`); // students model
  11 +
  12 +/**
  13 + * brands 首页
  14 + * @param {[type]} req [description]
  15 + * @param {[type]} res [description]
  16 + * @return {[type]} [description]
  17 + */
  18 +
  19 +exports.index = (req, res, next) => {
  20 + let channel = req.query.channel || req.cookies._Channel || 'boys';
  21 +
  22 + brandsService.getBrandViewList(channel, req).then(result => {
  23 +
  24 + res.render('brands/brands', result);
  25 +
  26 + }).catch(next);
  27 +};
  28 +
  29 +/**
  30 + * brandList-Ajax调用
  31 + */
  32 +exports.brandList = (req, res, next) => {
  33 + let channel = req.query.channel || req.cookies._Channel || 'boys';
  34 +
  35 + brandsService.getBrandList(channel, req.body.start).then(result => {
  36 +
  37 + res.render('brands/brand-list', Object.assign({layout: false}, result));
  38 +
  39 + }).catch(next);
  40 +};
  41 +
  42 +/**
  43 + * 品牌接口数据
  44 + *
  45 + * @param string brandId 获取品牌ID
  46 + * @return json
  47 + */
  48 +exports.brandInfo = (req, res, next) => {
  49 +
  50 + let brandId = req.query.brandId || 0;
  51 +
  52 + brandsService.brandInfo(brandId, req.user.uid).then(result => {
  53 + res.json(result);
  54 + }).catch(next);
  55 +};
  56 +
  57 +/**
  58 + * 品牌plusstar列表
  59 + */
  60 +exports.plusstarList = (req, res, next) => {
  61 + let channel = req.query.channel || req.cookies._Channel || 'boys';
  62 +
  63 + brandsService.plusstarList(channel, req).then(result => {
  64 + res.render('brands/plusstar', result);
  65 + }).catch(next);
  66 +};
  1 +/**
  2 + * router of sub app brands
  3 + * @author: ghw<hongwei.gao@yoho.cn>
  4 + * @date: 2016/09/29
  5 + */
  6 +
  7 +var express = require('express'),
  8 + path = require('path'),
  9 + hbs = require('express-handlebars');
  10 +
  11 +var app = express();
  12 +
  13 +// set view engin
  14 +var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
  15 +
  16 +app.on('mount', function(parent) {
  17 + delete parent.locals.settings; // 不继承父 App 的设置
  18 + Object.assign(app.locals, parent.locals);
  19 +});
  20 +
  21 +app.set('views', path.join(__dirname, 'views/action'));
  22 +app.engine('.hbs', hbs({
  23 + extname: '.hbs',
  24 + defaultLayout: 'layout',
  25 + layoutsDir: doraemon,
  26 + partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
  27 + helpers: global.yoho.helpers
  28 +}));
  29 +
  30 +// router
  31 +app.use(require('./router'));
  32 +
  33 +module.exports = app;
  1 +/**
  2 + * 品牌一览 api
  3 + * @author: ghw<hongwei.gao@yoho.cn>
  4 + * @date: 2016/9/29
  5 + */
  6 +'use strict';
  7 +const api = global.yoho.API;
  8 +const serviceAPI = global.yoho.ServiceAPI;
  9 +
  10 +/**
  11 + * 分开取数,品牌一览 顶部的轮翻广告及热门品牌数据-PC
  12 + * 顶部的轮翻广告及热门品牌数据
  13 + * @param string $contentCode 获取广告资源需要的位置码
  14 + */
  15 +const getBrandTopData = (contentCode) => {
  16 + return serviceAPI.get('operations/api/v5/resource/get', {
  17 + content_code: contentCode
  18 + }, {cache: 3600});
  19 +};
  20 +
  21 +/**
  22 + * 分开取数,获取品牌一览 "按字母'A-Z'分组的品牌列表数据"
  23 + * @param int $channel 频道标识 1:男,2:女,3:潮童,4:创意生活
  24 + */
  25 +const getBrandListData = channel => {
  26 + let params = {method: 'app.brand.brandlist'};
  27 +
  28 + if (!isNaN(channel)) {
  29 + params.yh_channel = channel;
  30 + }
  31 + return api.get('', params);
  32 +};
  33 +
  34 +/**
  35 + * 获取品牌简介
  36 + *
  37 + * @param integer $brandId 品牌ID
  38 + * @param int 用户ID
  39 + * @return array 品牌介绍信息
  40 + */
  41 +const getBrandIntro = (brandId, uid) => {
  42 + let param = {};
  43 +
  44 + if (!uid) {
  45 + param.cache = 3600;
  46 + }
  47 + return api.get('', {
  48 + method: 'app.brand.getBrandIntro',
  49 + brand_id: brandId,
  50 + uid: uid
  51 + }, param);
  52 +};
  53 +
  54 +/**
  55 + * 获取品牌中产品图片
  56 + * @param int 品牌ID
  57 + * @return array 品牌产品信息
  58 + */
  59 +const getProductByBrand = (brandId, limit) => {
  60 + return api.get('', {
  61 + method: 'web.search.search',
  62 + brand: brandId,
  63 + limit: limit
  64 + });
  65 +};
  66 +
  67 +/**
  68 + * 获取品牌信息
  69 + *
  70 + * @param array $ids
  71 + * @return array
  72 + */
  73 +const getBrandInfoByIds = (ids) => {
  74 + return api.get('', {
  75 + method: 'web.brand.info',
  76 + ids: ids instanceof Array ? ids.join(',') : parseInt(ids, 10)
  77 + }, {cache: 3600});
  78 +};
  79 +
  80 +/**
  81 + * 获取品牌列表
  82 + *
  83 + * @param int $brandType
  84 + * @param string $gender
  85 + * @param string $type
  86 + * @return array
  87 + */
  88 +const getPlusstarList = (brandType, gender) => {
  89 + return serviceAPI.get('guang/api/v3/plustar/getlist', {
  90 + gender: gender,
  91 + brand_type: brandType
  92 + }, {cache: 3600});
  93 +};
  94 +
  95 +module.exports = {
  96 + getBrandTopData,
  97 + getBrandListData,
  98 + getBrandIntro,
  99 + getProductByBrand,
  100 + getPlusstarList,
  101 + getBrandInfoByIds
  102 +};
  1 +
  2 +/**
  3 + * 品牌一览 model
  4 + * @author: ghw<hongwei.gao@yoho.cn>
  5 + * @date: 2016/9/29
  6 + */
  7 +'use strict';
  8 +
  9 +const Promise = require('bluebird');
  10 +const co = Promise.coroutine;
  11 +const _ = require('lodash');
  12 +const helpers = global.yoho.helpers;
  13 +const brandApi = require('./brands-api');
  14 +const serviceApi = global.yoho.ServiceAPI;
  15 +
  16 +// 品牌一览资源位CODE码
  17 +const channelCode = {
  18 + boys_brand: '8b16b7baf9a66fbe553a6caa97d2ce2a',
  19 + girls_brand: 'c95ae9e40f0add10549b819f821ad626',
  20 + kids_brand: 'c575c6bfdfa4125fae7d24bbec7119c8',
  21 + lifestyle_brand: '84b7926282fdef92f1039bdcf77c18ba',
  22 + brand_list: 'f0f72b1e8f30e6ad086dfc4401f3a856', // 品牌列表资源位CODE码
  23 + brand_plusstar_banner_boys: 'd0149783b8dd2adaf083fd10556c39a9',
  24 + brand_plusstar_banner_girls: 'aad7a43e9a04ac7c70ae7f0c1acf86ef',
  25 + brand_plusstarindex_boys: 'a833aed63d28457156310e97faa7fa37', // plusstarindex男首资源位
  26 + brand_plusstarindex_girls: '6e4f162be3b3ba44f3bfcf1c38bdb745' // plusstarindex女首资源位
  27 +};
  28 +
  29 +const BOYS = 'boys';
  30 +const GIRLS = 'girls';
  31 +const KIDS = 'kids';
  32 +const LIFESTYLE = 'lifestyle';
  33 +
  34 +/**
  35 + * 获取品牌一览资源位&channelType
  36 + *
  37 + * @param string $channelStr
  38 + * @return array
  39 + */
  40 +const switchBrandParams = channel => {
  41 + let req = {};
  42 +
  43 + switch (channel) {
  44 +
  45 + case BOYS:
  46 + req = {
  47 + channelType: 1,
  48 + brandCode: channelCode.brand_plusstar_banner_boys
  49 + };
  50 + break;
  51 + case GIRLS:
  52 + req = {
  53 + channelType: 2,
  54 + brandCode: channelCode.brand_plusstar_banner_girls
  55 + };
  56 + break;
  57 + case KIDS:
  58 + req = {
  59 + channelType: 3,
  60 + brandCode: channelCode.kids_brand
  61 + };
  62 + break;
  63 + case LIFESTYLE:
  64 + req = {
  65 + channelType: 4,
  66 + brandCode: channelCode.lifestyle_brand
  67 + };
  68 + break;
  69 + default:
  70 + req = {
  71 + channelType: 1,
  72 + brandCode: channelCode.boys_brand
  73 + };
  74 + break;
  75 + }
  76 + return req;
  77 +};
  78 +
  79 +/**
  80 + * 获取品牌一览页面,品牌top
  81 + * @param string $channel 频道名称
  82 + */
  83 +const getBrandViewTop = channel => {
  84 + return co(function*() {
  85 + let switchParams = switchBrandParams(channel);
  86 +
  87 + let res = yield brandApi.getBrandTopData(switchParams.brandCode);
  88 +
  89 + let result = {},
  90 + brandAds = [],
  91 + brandLogos = [];
  92 +
  93 + if (!res || res.code !== 200) {
  94 + return result;
  95 + }
  96 +
  97 + // 头部10个品牌小图块 url
  98 + if (res.data && res.data instanceof Array && res.data[1].data && res.data[1].data.list) {
  99 +
  100 + _.forEach(res.data[1].data.list, subValue => {
  101 + brandAds.push({
  102 + name: subValue.name,
  103 + src: helpers.image(subValue.src, 80, 50, 3),
  104 + url: subValue.url
  105 + });
  106 + });
  107 + }
  108 +
  109 + // 头部品牌图块,广告位
  110 + if (res.data && res.data instanceof Array && res.data[0].data) {
  111 + _.forEach(res.data[0].data, (subValue, k) => {
  112 + let srcUrl;
  113 +
  114 + // kids lifestyle 第一张图尺寸不同
  115 + if (switchParams.channelType === 1 || switchParams.channelType === 2) {
  116 + srcUrl = helpers.image(subValue.src, 222, 180, 3);
  117 + } else {
  118 + srcUrl = (k === 0) ? helpers.image(subValue.src, 570, 280, 3) :
  119 + helpers.image(subValue.src, 280, 280, 3);
  120 + }
  121 + let brandPlusstarItem = {
  122 + name: subValue.title,
  123 + src: srcUrl,
  124 + url: subValue.url
  125 + };
  126 +
  127 + if (channel === BOYS || channel === GIRLS) {
  128 + if (k === 0) {
  129 + brandPlusstarItem.url = helpers.urlFormat('/brands/plusstar', {channel: channel});
  130 + } else {
  131 + brandPlusstarItem.url = helpers.urlFormat('/brands/plusstar', {id: k, channel: channel});
  132 + }
  133 + }
  134 +
  135 + brandLogos.push(brandPlusstarItem);
  136 + });
  137 + }
  138 +
  139 + // 整合brandTop数据结构,boys、girls
  140 + if (switchParams.channelType === 1 || switchParams.channelType === 2) {
  141 + result.isTab = true;
  142 + }
  143 + result.tabHeader = brandLogos;
  144 + result.logos = brandAds;
  145 +
  146 + return result;
  147 + })();
  148 +};
  149 +
  150 +/**
  151 + * 获取品牌一览list
  152 + * @param string $channel 频道名称
  153 + * @param int start 开始位置 1 开始
  154 + * @param int length 取数长度 0 取到最后
  155 + */
  156 +const getBrandViewList = (channel, start, length) => {
  157 + return co(function*() {
  158 + let switchParams = switchBrandParams(channel);
  159 +
  160 + let res = yield brandApi.getBrandListData(switchParams.channelType);
  161 +
  162 + let result = [],
  163 + navigation = [];
  164 +
  165 + if (!res || res.code !== 200) {
  166 + return result;
  167 + }
  168 +
  169 + // 品牌list A-Z 0-9
  170 + if (res.data && res.data.brands) {
  171 +
  172 + _.forEach(res.data.brands, (subValue, key) => {
  173 + let listTmp = [];
  174 +
  175 + _.forEach(subValue, ssubValue => {
  176 + // 为品牌名称
  177 + let href;
  178 +
  179 + if (switchParams.channelType === 1) {
  180 + href = helpers.urlFormat('', {gender: '1,3'}, ssubValue.brand_domain);
  181 + } else if (switchParams.channelType === 2) {
  182 + href = helpers.urlFormat('', {gender: '2,3'}, ssubValue.brand_domain);
  183 + } else {
  184 + href = helpers.urlFormat('', '', ssubValue.brand_domain);
  185 + }
  186 + let brandItem = {
  187 + name: ssubValue.brand_name,
  188 + key: ssubValue.id,
  189 + href: href
  190 + };
  191 +
  192 + if (ssubValue.is_hot === 'Y') {
  193 + brandItem.hot = 'hot';
  194 + }
  195 + listTmp.push(brandItem);
  196 +
  197 + });
  198 + navigation.push(key);
  199 +
  200 + result.push({
  201 + key: key,
  202 + val: _.sortBy(listTmp, 'name')// 对name排序
  203 + });
  204 + });
  205 +
  206 + }
  207 +
  208 + // 只取部分数据
  209 + let begin;
  210 +
  211 +
  212 + if (start) {
  213 + begin = (start - 1) ? (start - 1) : 0;
  214 + begin = (begin > 0) ? begin : 0;
  215 + result = length ? result.slice(begin, length + begin) : result.slice(begin);
  216 + }
  217 +
  218 + result.navigation = navigation;
  219 +
  220 + return result;
  221 + })();
  222 +};
  223 +
  224 +/**
  225 + * 获取单个广告浮窗内容
  226 + *
  227 + * @param int $brandId
  228 + * @param int $uid
  229 + * @return array
  230 + */
  231 +const getBrandInfo = (brandId, uid) => {
  232 + return co(function*() {
  233 + let data = {},
  234 + imgs = [];
  235 +
  236 + // 获取品牌简介
  237 + let res = yield brandApi.getBrandIntro(brandId, uid);
  238 +
  239 + if (!res || res.code !== 200) {
  240 + return data;
  241 + }
  242 + if (res.data) {
  243 + // 获取品牌下的产品信息
  244 + let proInfo = yield brandApi.getProductByBrand(brandId, 3);
  245 +
  246 + if (!proInfo || proInfo.code !== 200) {
  247 + return data;
  248 + }
  249 + let proInfoTmp = proInfo.data.product_list ? proInfo.data.product_list : [];
  250 +
  251 + if (!_.isEmpty(proInfoTmp)) {
  252 + _.forEach(proInfoTmp, subValue => {
  253 + imgs.push({
  254 + src: helpers.image(subValue.default_images, 80, 100, 3)
  255 + });
  256 + });
  257 + }
  258 +
  259 + // 整合
  260 + data = {
  261 + key: res.data.brand_id,
  262 + icon: helpers.image(res.data.brand_ico, 80, 50, 3),
  263 + title: res.data.brand_name,
  264 + content: res.data.brand_intro,
  265 + subtitle: 'FEATURED ITEMS',
  266 + imgs: imgs
  267 + };
  268 + }
  269 + return data;
  270 + })();
  271 +};
  272 +
  273 +/**
  274 + * 多个品牌ID获取品牌信息
  275 + *
  276 + * @param array $brandIds
  277 + * @return array
  278 + */
  279 +const getBrandInfoByIds = (brandIds) => {
  280 + return co(function*() {
  281 + let res = yield brandApi.getBrandInfoByIds(brandIds);
  282 +
  283 + let brandsInfo = {};
  284 +
  285 + if (!res || res.code !== 200) {
  286 + return brandsInfo;
  287 + }
  288 + if (res.data && res.code === 200) {
  289 + _.forEach(res.data, (subValue, k) => {
  290 + subValue.desc = _.trim(subValue.brand_intro.replace(/(\t)|(\n)|(\r)|(&nbsp;)/g, '')
  291 + .replace(/<.*?>/ig, ''));
  292 + subValue.url = subValue.brand_domain;
  293 + delete subValue.brand_intro;
  294 + brandsInfo[k] = subValue;
  295 + });
  296 + }
  297 +
  298 + return brandsInfo;
  299 + })();
  300 +};
  301 +
  302 +/**
  303 + * 获取plusstar品牌列表项目
  304 + *
  305 + * @param string $channel
  306 + * @return array
  307 + */
  308 +const getPlusstarBrandListItem = (channel) => {
  309 + return co(function*() {
  310 + let code = channel === 'girls' ? channelCode.brand_plusstar_banner_girls :
  311 + channelCode.brand_plusstar_banner_boys;
  312 +
  313 + // 资源位数据
  314 + let resource = yield serviceApi.get('operations/api/v5/resource/get', {content_code: code}, {cache: 3600});
  315 +
  316 + let items = [];
  317 +
  318 + if (!resource || resource.code !== 200) {
  319 + return items;
  320 + }
  321 + if (resource.data && resource.code === 200) {
  322 + items[0] = {name: '所有品牌', src: '', url: helpers.urlFormat('/brands', {channel: channel}), brandType: ''};
  323 + items[1] = {name: '设计新潮', src: '', url: '', brandType: 4};
  324 + items[2] = {name: '潮流经典', src: '', url: '', brandType: 1};
  325 + items[3] = {name: '明星潮牌', src: '', url: '', brandType: 2};
  326 + items[4] = {name: '原创潮牌', src: '', url: '', brandType: 3};
  327 +
  328 + let resourceData = resource.data,
  329 + pos = 0;
  330 +
  331 + _.forEach(items, (subValue, k) => {
  332 + if (_.isEmpty(subValue.url)) {
  333 + subValue.url = helpers.urlFormat('/brands/plusstar', {id: k, channel: channel});
  334 + }
  335 +
  336 + if (pos in resourceData[0].data) {
  337 + subValue.src = helpers.image(resourceData[0].data[pos].src, 222, 180, 1);
  338 + subValue.name = resourceData[0].data[pos].title;
  339 + }
  340 + pos++;
  341 + });
  342 + }
  343 +
  344 + return items;
  345 + })();
  346 +};
  347 +
  348 +/**
  349 + * 获取Plustar列表
  350 + *
  351 + * @param string $brandType
  352 + * @param string $gender
  353 + * @return array
  354 + */
  355 +const getPlustarList = (brandType, gender) => {
  356 + return co(function*() {
  357 + let list = yield brandApi.getPlusstarList(brandType, gender);
  358 +
  359 + let brandList = {},
  360 + data = {},
  361 + brandsIds = [],
  362 + result = {brandsIds: [], data: {}};
  363 +
  364 + if (!list || list.code !== 200) {
  365 + return result;
  366 + }
  367 + if (list.data && list.data.data && list.data.data.list) {
  368 + brandList = list.data.data.list[0];
  369 + }
  370 + if (brandList.data) {
  371 + _.forEach(brandList.data, brand => {
  372 + let src = '';
  373 +
  374 + if (brand.data[0]) {
  375 + src = helpers.image(brand.data[0].src, 320, 160, 1);
  376 + }
  377 + data[brand.brand_id] = {
  378 + brand_id: brand.brand_id,
  379 + name: brand.brand_name,
  380 + sort_id: brand.sort_id,
  381 + src: src,
  382 + desc: '',
  383 + url: ''
  384 + };
  385 + brandsIds.push(brand.brand_id);
  386 + });
  387 +
  388 + result.brandsIds = brandsIds;
  389 + result.data = data;
  390 + }
  391 +
  392 + return result;
  393 + })();
  394 +};
  395 +
  396 +module.exports = {
  397 + getBrandViewTop,
  398 + getBrandViewList,
  399 + getBrandInfo,
  400 + getBrandInfoByIds,
  401 + getPlusstarBrandListItem,
  402 + getPlustarList
  403 +};
  1 +/**
  2 + * 品牌一览 controller
  3 + * @author: ghw<hongwei.gao@yoho.cn>
  4 + * @date: 2016/9/29
  5 + */
  6 +'use strict';
  7 +
  8 +const Promise = require('bluebird');
  9 +const co = Promise.coroutine;
  10 +const api = global.yoho.API;
  11 +const headerModel = require('../../../doraemon/models/header');
  12 +const brandsModel = require('./brands-model');
  13 +const _ = require('lodash');
  14 +const helpers = global.yoho.helpers;
  15 +const pager = require(`${global.utils}/pager`).setPager;
  16 +
  17 +const BOYS = 'boys';
  18 +const GIRLS = 'girls';
  19 +const KIDS = 'kids';
  20 +const LIFESTYLE = 'lifestyle';
  21 +
  22 +/**
  23 + * 获取品牌一览资源位&channelType
  24 + *
  25 + * @param string $channelStr
  26 + * @return array
  27 + */
  28 +const getGenderByChannel = channel => {
  29 + let gender = '';
  30 +
  31 + switch (channel) {
  32 +
  33 + case BOYS:
  34 + gender = '1,3';
  35 + break;
  36 + case GIRLS:
  37 + gender = '2,3';
  38 + break;
  39 + default:
  40 + gender = '1,2,3';
  41 + break;
  42 + }
  43 + return gender;
  44 +};
  45 +
  46 +const getHomeurlByChannel = channel => {
  47 + let home;
  48 +
  49 + switch (channel) {
  50 + case GIRLS:
  51 + home = helpers.urlFormat('/woman', {}, 'new');
  52 + break;
  53 + case LIFESTYLE:
  54 + home = helpers.urlFormat('/lifestyle', {}, 'new');
  55 + break;
  56 + case KIDS:
  57 + home = helpers.urlFormat('/kids', {}, 'new');
  58 + break;
  59 + default:
  60 + home = helpers.urlFormat('');
  61 + break;
  62 + }
  63 + return home;
  64 +};
  65 +
  66 +// 添加网站的SEO
  67 +const seoMap = {
  68 + boys: {
  69 + title: '品牌一览|男装品牌排行榜,男装品牌大全|YOHO!BUY 有货 100%正品保证',
  70 + keywords: '品牌一览,男装品牌,男装品牌排行榜,男装品牌大全,YOHO!BUY 有货',
  71 + description: 'YOHO!BUY 有货男装品牌一览汇集国内国际各大男装品牌大全,为广大爱好时尚的男士青年提供品牌男装、' +
  72 + '休闲男装、商务男装.YOHO!BUY 有货,100%正品保证'
  73 + },
  74 + girls: {
  75 + title: '品牌一览|女装品牌排行榜,女装品牌大全|YOHO!BUY 有货 100%正品保证',
  76 + keywords: '品牌一览,女装品牌,女装品牌排行榜,女装品牌大全,YOHO!BUY 有货',
  77 + description: 'YOHO!BUY 有货女装品牌一览汇集国内国际各大女装品牌,为广大爱美女生提供品牌女装、休闲女装、' +
  78 + '商务女装.买品牌女装就上YOHO!BUY 有货,100%正品保证'
  79 + },
  80 + kids: {
  81 + title: '品牌一览|童装童鞋品牌,儿童鞋包配饰排行榜,潮童品牌大全|YOHO!BUY 有货 100%正品保证',
  82 + keywords: '童装品牌,童装童鞋排行榜,儿童鞋包配饰排行榜,潮童品牌大全,品牌一览,YOHO!BUY 有货',
  83 + description: 'YOHO!BUY 有货童装品牌一览汇集国内国际各大童装品牌大全,为广大爱好潮流的儿童提供品牌童装、童鞋,' +
  84 + '儿童鞋包配饰.YOHO!BUY 有货,100%正品保证'
  85 + },
  86 + lifestyle: {
  87 + title: '品牌一览|数码3c,居家,玩具娱乐,文具,美妆品牌|YOHO!BUY 有货 100%正品保证',
  88 + keywords: '数码3c品牌,居家品牌,玩具娱乐品牌,文具品牌,美妆品牌',
  89 + description: 'YOHO!BUY 有货女装品牌一览汇集国内国际各大数码3c品牌,居家品牌,玩具娱乐品牌,文具品牌,' +
  90 + '美妆品牌.买创意生活家居就上YOHO!BUY 有货,100%正品保证'
  91 + }
  92 +};
  93 +
  94 +/**
  95 + * 获取品牌一览list
  96 + * @param string $channel 频道名称
  97 + * @param int start 开始位置 1 开始
  98 + * @param int length 取数长度 0 取到最后
  99 + */
  100 +exports.getBrandViewList = (channel) => {
  101 + let apiMethod = [
  102 + headerModel.requestHeaderData(channel),
  103 + brandsModel.getBrandViewTop(channel),
  104 + brandsModel.getBrandViewList(channel, 1, 5) // 分屏加载
  105 + ];
  106 +
  107 + return api.all(apiMethod).then(result => {
  108 + let responseData = {
  109 + module: 'brands',
  110 + page: 'brands'
  111 + };
  112 +
  113 + // 头部数据
  114 + Object.assign(responseData, result[0]);
  115 +
  116 + // 品牌一览列表
  117 + responseData.brands = result[1];
  118 + responseData.brands.navigation = result[2].navigation;
  119 + responseData.brands.category = result[2];
  120 +
  121 + // 导航pathNav
  122 + responseData.brands.pathNav = [
  123 + {
  124 + href: getHomeurlByChannel(channel),
  125 + name: `${_.toUpper(channel)}首页`,
  126 + pathTitle: 'YOHO!BUY 有货'
  127 + },
  128 + {
  129 + href: helpers.urlFormat('/brands'),
  130 + name: '品牌一览',
  131 + pathTitle: '品牌一览'
  132 + }
  133 + ];
  134 +
  135 + // SEO
  136 + Object.assign(responseData, seoMap[channel]);
  137 +
  138 + return responseData;
  139 + });
  140 +};
  141 +
  142 +/**
  143 + * brandList-Ajax调用
  144 + */
  145 +exports.getBrandList = (channel, start) => {
  146 + let apiMethod = [
  147 + brandsModel.getBrandViewList(channel, start)
  148 + ];
  149 +
  150 + return api.all(apiMethod).then(result => {
  151 + let responseData = {};
  152 +
  153 + // 品牌一览列表
  154 + responseData.category = result[0];
  155 + return responseData;
  156 + });
  157 +};
  158 +
  159 +/**
  160 + * 品牌接口数据
  161 + *
  162 + * @param string brandId 获取品牌ID
  163 + * @return json
  164 + */
  165 +exports.brandInfo = (brandId, uid) => {
  166 + let apiMethod = [
  167 + brandsModel.getBrandInfo(brandId, uid)
  168 + ];
  169 +
  170 + return api.all(apiMethod).then(result => {
  171 +
  172 + return {
  173 + code: _.isEmpty(result[0]) ? 400 : 200,
  174 + brand: _.isEmpty(result[0]) ? '' : result[0]
  175 + };
  176 + });
  177 +};
  178 +
  179 +/**
  180 + * 多个品牌ID获取品牌信息
  181 + *
  182 + * @param array $brandIds
  183 + * @return array
  184 + */
  185 +exports.plusstarList = (channel, req) => {
  186 + return co(function*() {
  187 + let headerData = yield headerModel.requestHeaderData(channel);
  188 +
  189 + let responseData = {
  190 + module: 'brands',
  191 + page: 'brands',
  192 + brandsHomePage: true
  193 + };
  194 +
  195 + let id = req.query.id || '',
  196 + gender = req.query.gender || getGenderByChannel(channel),
  197 + limit = 20,
  198 + page = parseInt(req.query.page, 10) || 1;
  199 +
  200 + let items = yield brandsModel.getPlusstarBrandListItem(channel);
  201 +
  202 + let brandType = 1;
  203 +
  204 + if (items[id]) {
  205 + brandType = items[id].brandType;
  206 + }
  207 + let plustarList = yield brandsModel.getPlustarList(brandType, gender);
  208 +
  209 + let list = plustarList.data;
  210 +
  211 + let brandIds = [],
  212 + brands = [],
  213 + pageList = {};
  214 +
  215 + brandIds = plustarList.brandsIds.slice((page - 1) * limit, page * limit);
  216 + if (plustarList.brandsIds.length > limit) {
  217 + pageList = pager(Math.ceil(plustarList.brandsIds.length / limit), {
  218 + channel: channel,
  219 + page: page,
  220 + id: id
  221 + });
  222 + }
  223 +
  224 + if (brandIds.length > 0) {
  225 + // 获取品牌信息
  226 + let brandsInfo = yield brandsModel.getBrandInfoByIds(brandIds);
  227 +
  228 + _.forEach(brandIds, brandId => {
  229 + if (brandsInfo[brandId]) {
  230 + list[brandId].desc = brandsInfo[brandId].desc;
  231 + list[brandId].url = helpers.urlFormat('', {gender: gender}, brandsInfo[brandId].url);
  232 + }
  233 + brands.push(list[brandId]);
  234 + });
  235 + }
  236 + let data = {
  237 + brandsHomePage: true,
  238 + brands: {
  239 + items: brands,
  240 + tabs: items
  241 +
  242 + }
  243 + };
  244 +
  245 + // 头部数据
  246 + Object.assign(responseData, headerData);
  247 +
  248 + // 产品信息
  249 + Object.assign(responseData, data);
  250 +
  251 + // 页码
  252 + Object.assign(responseData, pageList);
  253 +
  254 + // SEO
  255 + Object.assign(responseData, seoMap[channel]);
  256 + return responseData;
  257 + })();
  258 +
  259 +};
  1 +/**
  2 + * router of sub app brands
  3 + * @author: ghw<hongwei.gao@yoho.cn>
  4 + * @date: 2016/09/29
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const express = require('express');
  10 +const cRoot = './controllers';
  11 +
  12 +const router = express.Router(); // eslint-disable-line
  13 +const brandsController = require(`${cRoot}/brands`);
  14 +
  15 +// 品牌一览
  16 +router.get('', brandsController.index);
  17 +
  18 +// 悬浮出现品牌信息
  19 +router.get('/brandinfo', brandsController.brandInfo);
  20 +
  21 +// 品牌没有加载完全,继续加载
  22 +router.post('/brandList', brandsController.brandList);
  23 +
  24 +// brands/plusstar
  25 +router.get('/plusstar', brandsController.plusstarList);
  26 +
  27 +module.exports = router;
  1 +<div class="home-page yoho-page brands" data-page="brands">
  2 + {{# brands}}
  3 +
  4 + {{> common/path-nav}}
  5 +
  6 + {{#if isTab}}
  7 + <div class="brands-tabs">
  8 + <ul class="clearfix">
  9 + {{#each tabHeader}}
  10 + <li>
  11 + <a href="{{url}}">
  12 + <div class="g-mask"></div>
  13 + <p class="tips">{{name}}</p>
  14 + <img class="lazy" data-original="{{src}}"/>
  15 + </a>
  16 + </li>
  17 + {{/each}}
  18 + </ul>
  19 + <div class="hover-contain">
  20 + <div class="hoverarr">
  21 + <i></i>
  22 + </div>
  23 + </div>
  24 + </div>
  25 + {{^}}
  26 + <ul class="brands-ad clearfix">
  27 + {{#each tabHeader}}
  28 + <li>
  29 + <a href="{{url}}" target="_blank">
  30 + <img class="lazy" data-original="{{src}}">
  31 + </a>
  32 + </li>
  33 + {{/each}}
  34 + </ul>
  35 + {{/if}}
  36 + <div class="brands-logo clearfix">
  37 + {{#each logos}}
  38 + <a href="{{url}}" title="{{name}}" target="_blank">
  39 + <img class="lazy" data-original="{{src}}">
  40 + </a>
  41 + {{/each}}
  42 + </div>
  43 + <div class="brands-category">
  44 + <div class="category-nav">
  45 + <span>BRANDS A-Z:</span>
  46 + {{#each navigation}}
  47 + <a href="#{{this}}">{{this}}</a>
  48 + {{/each}}
  49 + </div>
  50 + </div>
  51 + <div class="brands-list" >
  52 + {{> brand-list}}
  53 + </div>
  54 + {{/ brands}}
  55 +</div>
  1 +{{> layout/header}}
  2 +<div class="home-page yoho-page brands" data-page="brands">
  3 +{{# brands}}
  4 +
  5 +
  6 + {{! 头部banner}}
  7 + {{# slide}}
  8 + {{>index/slide-banner}}
  9 + {{/ slide}}
  10 +
  11 + {{! 品牌 BRAND}}
  12 + {{# brand}}
  13 + {{> index/floor-header}}
  14 + <div class="brandfloor clearfix">
  15 + <ul class="g-list">
  16 + {{# list}}
  17 + <li>
  18 + <a href="{{url}}" target= "_blank">
  19 + <img class="lazy" data-original="{{src}}" alt="">
  20 +
  21 + </a>
  22 + </li>
  23 + {{/ list}}
  24 + </ul>
  25 + </div>
  26 + {{/ brand}}
  27 +
  28 + {{! 单品 SINGLE GOODS}}
  29 + {{# singlegoods}}
  30 + {{> index/floor-header}}
  31 + <div class="singlegoods clearfix">
  32 + <ul class="g-list">
  33 + {{# list}}
  34 + <li>
  35 + <a href="{{url}}" target= "_blank">
  36 + <img class="lazy" data-original="{{src}}" alt="">
  37 + <div class="singlegoods-title">
  38 + <div class="g-mask"></div>
  39 + <p>{{name}}</p>
  40 + </div>
  41 + </a>
  42 + </li>
  43 + {{/ list}}
  44 + </ul>
  45 + </div>
  46 + {{/ singlegoods}}
  47 +
  48 + {{!视频 VIDEO}}
  49 + {{# video}}
  50 + {{> index/floor-header}}
  51 + <div class="video clearfix">
  52 + <ul class="g-list">
  53 + {{# list}}
  54 + <li>
  55 + <a href="{{url}}" target= "_blank">
  56 + <img class="lazy" data-original="{{src}}" alt="" /><i class="video-play"></i>
  57 + <div class="video-title">
  58 + <div class="g-mask"></div>
  59 + <p>{{name}}</p>
  60 + </div>
  61 + </a>
  62 + </li>
  63 + {{/ list}}
  64 + </ul>
  65 + </div>
  66 + {{/ video}}
  67 +
  68 + {{!新闻 NEWS}}
  69 + {{# news}}
  70 + {{> index/floor-header}}
  71 + <div class="news clearfix">
  72 + <div class="news-pic">
  73 + {{# pics}}
  74 + {{>index/slide-banner}}
  75 + {{/ pics}}
  76 + </div>
  77 + <div class="news-txt">
  78 + {{# txts}}
  79 + <ul>
  80 + {{#each list}}
  81 + <li>
  82 + <i class="iconfont">&#xe619;</i><a href="{{url}}">{{name}}</a>
  83 + </li>
  84 + {{/each}}
  85 + </ul>
  86 + {{/ txts}}
  87 + </div>
  88 + </div>
  89 + {{/ news}}
  90 +
  91 + {{!推广 AD}}
  92 + {{# ads}}
  93 + <div class="ads clearfix">
  94 + <ul class="g-list">
  95 + {{# list}}
  96 + <li>
  97 + <a href="{{url}}" target= "_blank">
  98 + <img class="lazy" data-original="{{src}}" alt="">
  99 + <span class="name g-title">{{name}}</span>
  100 + <span class="des g-title">{{des}}</span>
  101 + </a>
  102 + </li>
  103 + {{/ list}}
  104 + </ul>
  105 + </div>
  106 + {{/ ads}}
  107 +{{/ brands}}
  108 +</div>
  109 +{{> layout/footer}}
  1 +{{> layout/header}}
  2 +<div class="home-page yoho-page brands" data-page="brands">
  3 +{{# brands}}
  4 + <div class="sit-nav">
  5 + <a href="#">BOYS首页</a><span class="sep">></span><a href="#">品牌一览</a>
  6 + </div>
  7 + <div class="brands-tabs height-initial">
  8 + <ul class="clearfix">
  9 + {{#each tabs}}
  10 + <li>
  11 + <a href="{{url}}" target="_blank">
  12 + <div class="g-mask"></div>
  13 + <p class="tips">{{name}}</p>
  14 + <img class="lazy" data-original="{{src}}"/>
  15 + </a>
  16 + </li>
  17 + {{/each}}
  18 + </ul>
  19 + <div class="hover-contain">
  20 + <div class="hoverarr">
  21 + <i></i>
  22 + </div>
  23 + </div>
  24 + </div>
  25 + <div class="brands-items clearfix">
  26 + {{#each items}}
  27 + <div class="brands-item clearfix">
  28 + <a class="brands-pic" title="{{name}}" href="{{url}}" target="_blank">
  29 + <img class="lazy" data-original="{{src}}"/>
  30 + </a>
  31 + <div class="brand-info">
  32 + <a title="{{name}}" href="{{url}}" target="_blank">
  33 + <h3>
  34 + {{name}}
  35 + </h3>
  36 + </a>
  37 + <div class="brand-desc">{{desc}}</div>
  38 + </div>
  39 + </div>
  40 + {{/each}}
  41 + </div>
  42 + <div class="pagination">
  43 + <a href="#" class="page_pre" title="上一页"><i class="iconfont">&#xe60f;</i>上一页</a>
  44 + <a href="#"><span>1</span></a>
  45 + <a href="#" class="cur"><span>2</span></a>
  46 + <a href="#"><span>3</span></a>
  47 + <a href="#"><span>4</span></a>
  48 + <a href="#"><span>5</span></a>
  49 + <a><span>...</span></a>
  50 + <a href="#"><span>215</span></a>
  51 + <a href="#" title="下一页">下一页<i class="iconfont">&#xe60e;</i></a>
  52 + </div>
  53 +{{/ brands}}
  54 +</div>
  55 +{{> layout/footer}}
  1 +<div class="home-page yoho-page brands" data-page="brands">
  2 +{{# brands}}
  3 +
  4 + {{> common/path-nav}}
  5 +
  6 + <div class="brands-tabs height-initial">
  7 + <ul class="clearfix">
  8 + {{#each tabs}}
  9 + <li>
  10 + <a href="{{url}}">
  11 + <div class="g-mask"></div>
  12 + <p class="tips">{{name}}</p>
  13 + <img class="lazy" data-original="{{src}}"/>
  14 + </a>
  15 + </li>
  16 + {{/each}}
  17 + </ul>
  18 + <div class="hover-contain">
  19 + <div class="hoverarr">
  20 + <i></i>
  21 + </div>
  22 + </div>
  23 + </div>
  24 + <div class="brands-items clearfix">
  25 + {{#each items}}
  26 + <div class="brands-item clearfix">
  27 + <a class="brands-pic" title="{{name}}" href="{{url}}" target="_blank">
  28 + <img class="lazy" data-original="{{src}}"/>
  29 + </a>
  30 + <div class="brand-info">
  31 + <a title="{{name}}" href="{{url}}" target="_blank">
  32 + <h3>
  33 + {{name}}
  34 + </h3>
  35 + </a>
  36 + <div class="brand-desc">{{{desc}}}</div>
  37 + </div>
  38 + </div>
  39 + {{/each}}
  40 + </div>
  41 +{{/ brands}}
  42 + <div class="pagination clearfix">
  43 + {{# prePage}}
  44 + <a href="{{url}}" title="上一页">上一页<span class="iconfont">&#xe60e;</span></a>
  45 + {{/ prePage}}
  46 +
  47 + {{# pages}}
  48 + <a{{#if url}} href="{{url}}"{{/if}}{{#if cur}} class="cur"{{/if}}>{{num}}</a>
  49 + {{/ pages}}
  50 +
  51 + {{# nextPage}}
  52 + <a href="{{url}}" title="下一页">下一页<span class="iconfont">&#xe60c;</span></a>
  53 + {{/ nextPage}}
  54 + </div>
  55 +</div>
  1 +{{> layout/header}}
  2 +<div class="home-page yoho-page brands" data-page="brands">
  3 +{{# brands}}
  4 +
  5 +
  6 + {{! 头部banner}}
  7 + {{# slide}}
  8 + {{>index/slide-banner}}
  9 + {{/ slide}}
  10 +
  11 + {{! 品牌 BRAND}}
  12 + {{# brand}}
  13 + {{> index/floor-header}}
  14 + <div class="brandfloor list-floor clearfix">
  15 + <ul class="g-list">
  16 + {{# list}}
  17 + <li>
  18 + <a href="{{href}}" target= "_blank">
  19 + <img class="lazy" data-original="{{img}}" alt="">
  20 +
  21 + </a>
  22 + </li>
  23 + {{/ list}}
  24 + </ul>
  25 + </div>
  26 + {{/ brand}}
  27 +
  28 + {{! 单品 SINGLE GOODS}}
  29 + {{# singlegoods}}
  30 + {{> index/floor-header}}
  31 + <div class="singlegoods list-floor clearfix">
  32 + <ul class="g-list">
  33 + {{# list}}
  34 + <li>
  35 + <a href="{{href}}" target= "_blank">
  36 + <img class="lazy" data-original="{{img}}" alt="">
  37 + <div class="singlegoods-title">
  38 + <div class="g-mask"></div>
  39 + <p>{{name}}</p>
  40 + </div>
  41 + </a>
  42 + </li>
  43 + {{/ list}}
  44 + </ul>
  45 + </div>
  46 + {{/ singlegoods}}
  47 +
  48 + {{!视频 VIDEO}}
  49 + {{# video}}
  50 + {{> index/floor-header}}
  51 + <div class="video list-floor clearfix">
  52 + <ul class="g-list">
  53 + {{# list}}
  54 + <li>
  55 + <a href="{{href}}" target= "_blank">
  56 + <img class="lazy" data-original="{{img}}" alt="" /><i class="video-play"></i>
  57 + <div class="video-title">
  58 + <div class="g-mask"></div>
  59 + <p>{{name}}</p>
  60 + </div>
  61 + </a>
  62 + </li>
  63 + {{/ list}}
  64 + </ul>
  65 + </div>
  66 + {{/ video}}
  67 +
  68 + {{!新闻 NEWS}}
  69 + {{# news}}
  70 + {{> index/floor-header}}
  71 + <div class="news clearfix">
  72 + <div class="news-pic">
  73 + {{# pics}}
  74 + {{>index/slide-banner}}
  75 + {{/ pics}}
  76 + </div>
  77 + <div class="news-txt">
  78 + {{# txts}}
  79 + <ul>
  80 + {{#each list}}
  81 + <li>
  82 + <i class="iconfont">&#xe619;</i><a href="{{href}}" target= "_blank">{{name}}</a>
  83 + </li>
  84 + {{/each}}
  85 + </ul>
  86 + {{/ txts}}
  87 + </div>
  88 + </div>
  89 + {{/ news}}
  90 +
  91 + {{!推广 AD}}
  92 + {{# ads}}
  93 + <div class="ads list-floor clearfix">
  94 + <ul class="g-list">
  95 + {{# list}}
  96 + <li>
  97 + <a href="{{href}}" target= "_blank">
  98 + <img class="lazy" data-original="{{img}}" alt="">
  99 + <span class="name g-title">{{name}}</span>
  100 + <span class="des g-title">{{des}}</span>
  101 + </a>
  102 + </li>
  103 + {{/ list}}
  104 + </ul>
  105 + </div>
  106 + {{/ ads}}
  107 +{{/ brands}}
  108 +</div>
  109 +{{> layout/footer}}
  1 +{{#each category}}
  2 + <dl class="clearfix" name="{{key}}">
  3 + <dt>{{key}}</dt>
  4 + <dd>
  5 + <ul class="clearfix">
  6 + {{#each val}}
  7 + <li>
  8 + <a class="{{hot}}" data-key="{{key}}" href="{{href}}" target="_blank">
  9 + <span>{{name}}</span>
  10 + </a>
  11 + {{# hot}}
  12 + <i class="iconfont">&#xe62c;</i>
  13 + {{/ hot}}
  14 + </li>
  15 + {{/each}}
  16 + </ul>
  17 + </dd>
  18 + </dl>
  19 +{{/each}}
  1 +/**
  2 + * Created by TaoHuang on 2016/10/19.
  3 + */
  4 +
  5 +'use strict';
  6 +
  7 +const service = require('../models/cart-service');
  8 +
  9 +const getProductInfo = (req, res, next) => {
  10 + let pid = req.query.productId || '';
  11 +
  12 + service.getProductInfoAsync(pid).then((result) => {
  13 + return res.render('goods-detail', Object.assign({
  14 + layout: false
  15 + }, result));
  16 + }).catch(next);
  17 +};
  18 +
  19 +
  20 +module.exports = {
  21 + getProductInfo
  22 +};
  1 +/**
  2 + * sub app cart
  3 + * @author: htoooth<ht.anglenx@gmail.com>
  4 + * @date: 2016/10/19
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +var express = require('express'),
  10 + path = require('path'),
  11 + hbs = require('express-handlebars');
  12 +
  13 +var app = express();
  14 +
  15 +// set view engin
  16 +var doraemon = path.join(__dirname, '../../doraemon/views'); //parent view root
  17 +
  18 +app.on('mount', function(parent) {
  19 + delete parent.locals.settings; // 不继承父 App 的设置
  20 + Object.assign(app.locals, parent.locals);
  21 +});
  22 +
  23 +app.set('views', path.join(__dirname, 'views/action'));
  24 +app.engine('.hbs', hbs({
  25 + extname: '.hbs',
  26 + defaultLayout: 'layout',
  27 + layoutsDir: doraemon,
  28 + partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
  29 + helpers: global.yoho.helpers
  30 +}));
  31 +
  32 +// router
  33 +app.use(require('./router'));
  34 +
  35 +module.exports = app;
  1 +/**
  2 + * 商品详情models
  3 + * @author: xuqi<qi.xu@yoho.cn>
  4 + * @date: 2016/5/6
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const Promise = require('bluebird');
  10 +const co = Promise.coroutine;
  11 +const _ = require('lodash');
  12 +const helpers = global.yoho.helpers;
  13 +
  14 +const productAPI = require('./product-api');
  15 +
  16 +const _getProductIntroAsync = (productSkn) => {
  17 + return co(function * () {
  18 + let result = yield Promise.props({
  19 + sizeInfo: productAPI.sizeInfoAsync(productSkn)
  20 + });
  21 +
  22 + return result;
  23 + })();
  24 +};
  25 +
  26 +/**
  27 + * 获得sku商品数据
  28 + */
  29 +const _getSkuDataByProductBaseInfo = (data) => {
  30 + let totalStorageNum = 0;
  31 + let skuGoods = null;// sku商品
  32 + let defaultImage = '';// 默认图
  33 + let chooseSkuFlag = false; // 选中状态
  34 +
  35 + if (_.isEmpty(_.get(data, 'goods_list', []))) {
  36 + return {
  37 + totalStorageNum,
  38 + skuGoods,
  39 + defaultImage
  40 + };
  41 + }
  42 +
  43 + skuGoods = _.get(data, 'goods_list', []).reduce((acc, cur, pos)=> {
  44 +
  45 + // 如果status为0,即skc下架时就跳过该商品$value['status'] === 0
  46 + let goodsGroup = {};
  47 +
  48 + if (_.isEmpty(cur.color_image)) {
  49 + return acc;
  50 + }
  51 +
  52 + if (cur.images_list) {
  53 + // 商品列表
  54 + goodsGroup.productSkc = cur.product_skc;
  55 + goodsGroup.src = helpers.image(cur.color_image, 40, 40);
  56 + goodsGroup.title = `${_.trim(data.product_name)} ${cur.color_name}`;
  57 + goodsGroup.name = cur.color_name;
  58 + goodsGroup.focus = false;
  59 + goodsGroup.total = 0;
  60 + goodsGroup.thumbs = [];
  61 + goodsGroup.size = [];
  62 + }
  63 +
  64 + _.get(cur, 'images_list', []).forEach((good) => {
  65 + if (good.image_url) {
  66 + goodsGroup.thumbs.push({
  67 + url: '',
  68 + shower: helpers.image(good.image_url, 420, 560),
  69 + img: helpers.image(good.image_url, 75, 100)
  70 + });
  71 + }
  72 + });
  73 +
  74 + // 缩略图空,不显示
  75 + if (_.isEmpty(goodsGroup.thumbs)) {
  76 + return acc;
  77 + }
  78 +
  79 + // 默认第一张图片
  80 + if (pos === 0) {
  81 + defaultImage = helpers.image(cur.color_image, 420, 560);
  82 + }
  83 +
  84 + // 商品的尺码列表
  85 + _.get(cur, 'size_list', []).forEach((size) => {
  86 + if (data.attribute === 3) {
  87 + // 虚拟商品,门票默认最大为4,
  88 + size.storage_number = size.storage_number > 4 ? 4 : size.storage_number;
  89 + }
  90 +
  91 + // 如果status为0,即skc下架时就跳过该商品
  92 + if (cur.status === 0) {
  93 + size.storage_number = 0;
  94 + }
  95 +
  96 + goodsGroup.size.push({
  97 + name: size.size_name,
  98 + sku: size.product_sku,
  99 + num: _.parseInt(size.storage_number),
  100 + goodsId: size.size_id
  101 + });
  102 +
  103 + // 单个sku商品的总数
  104 + goodsGroup.total += _.parseInt(size.storage_number);
  105 +
  106 + if (goodsGroup.total > 0 && !chooseSkuFlag) { // 默认选中该sku商品
  107 + goodsGroup.focus = true;
  108 + chooseSkuFlag = true;// 选中sku商品
  109 + }
  110 +
  111 + totalStorageNum += _.parseInt(size.storage_number);
  112 +
  113 + });
  114 +
  115 + acc.push(goodsGroup);
  116 + return acc;
  117 + }, []);
  118 +
  119 + if (!_.isEmpty(skuGoods) && !chooseSkuFlag) { // 没有选中一个sku商品,默认选中第一个sku商品
  120 + _.head(skuGoods).focus = true;
  121 + }
  122 +
  123 + return {
  124 + defaultImage: defaultImage,
  125 + skuGoods: skuGoods,
  126 + totalStorageNum: totalStorageNum
  127 + };
  128 +};
  129 +
  130 +/**
  131 + * 使sizeBoList id以 sizeAttributeBos id顺序一样
  132 + * @param sizeInfoBo
  133 + */
  134 +const _sizeInfoBoSort = (sizeInfoBo) => {
  135 + if (!sizeInfoBo.sizeBoList || !sizeInfoBo.sizeAttributeBos) {
  136 + return {};
  137 + }
  138 +
  139 + _.get(sizeInfoBo, 'sizeBoList', []).forEach((sizeBoList, sizek)=> {
  140 + let sortAttr = {};
  141 +
  142 + sizeBoList.sortAttributes.forEach(sortAttributes => {
  143 + sortAttr[sortAttributes.id] = sortAttributes;
  144 + });
  145 +
  146 + sizeInfoBo.sizeBoList[sizek].sortAttributes = sortAttr;
  147 + });
  148 +
  149 + _.get(sizeInfoBo, 'sizeBoList', []).forEach((sizeBoList, sizek)=> {
  150 + let sortAttr = [];
  151 +
  152 + sizeInfoBo.sizeAttributeBos.forEach(val => {
  153 + if (sizeBoList.sortAttributes[val.id]) {
  154 + sortAttr.push(sizeBoList.sortAttributes[val.id]);
  155 + }
  156 + });
  157 +
  158 + sizeInfoBo.sizeBoList[sizek].sortAttributes = sortAttr;
  159 +
  160 + });
  161 +
  162 + return sizeInfoBo;
  163 +};
  164 +
  165 +/**
  166 + * 获取尺寸信息
  167 + * @param sizeInfo
  168 + * @returns {{}}
  169 + */
  170 +const _getSizeData = (sizeInfo) => {
  171 +
  172 + // 尺码信息
  173 + if (!_.has(sizeInfo, 'sizeInfoBo')) {
  174 + return {};
  175 + }
  176 +
  177 + sizeInfo.sizeInfoBo = _sizeInfoBoSort(sizeInfo.sizeInfoBo);
  178 +
  179 + let boyReference = _.get(sizeInfo, 'productExtra.boyReference', false);
  180 + let girlReference = _.get(sizeInfo, 'productExtra.girlReference', false);
  181 + let gender = _.get(sizeInfo, 'productDescBo.gender', 3);
  182 + let referenceName = (function() {
  183 + if (gender === 3 && boyReference) {
  184 + return '参考尺码(男)';
  185 + } else if (gender === 3 && girlReference) {
  186 + return '参考尺码(女)';
  187 + } else {
  188 + return '参考尺码';
  189 + }
  190 + }());
  191 +
  192 + // 判断是否显示参考尺码
  193 + let showReference = (boyReference && _.get(sizeInfo, 'sizeInfoBo.sizeBoList[0].boyReferSize', false)) ||
  194 + (girlReference && _.get(sizeInfo, 'sizeInfoBo.sizeBoList[0].girlReferSize', false));
  195 +
  196 + if (!_.has(sizeInfo, 'sizeInfoBo.sizeAttributeBos')) {
  197 + return {};
  198 + }
  199 +
  200 + // 尺码信息头部
  201 + let size = {
  202 + thead: [{name: '吊牌尺码', id: ''}],
  203 + tbody: []
  204 + };
  205 +
  206 + // 显示参考尺码
  207 + if (showReference) {
  208 + size.thead[1] = {name: referenceName, id: ''};
  209 + }
  210 +
  211 + _.get(sizeInfo, 'sizeInfoBo.sizeAttributeBos', []).forEach((value) => {
  212 + size.thead.push({
  213 + name: value.attributeName || ' ',
  214 + id: value.id
  215 + });
  216 + });
  217 +
  218 + _.get(sizeInfo, 'sizeInfoBo.sizeBoList', []).forEach((value) => {
  219 + let sizes = [];
  220 +
  221 + // 吊牌尺码
  222 + sizes.push(value.sizeName);
  223 +
  224 + // 判断是否显示参考尺码
  225 + if (boyReference && (gender === 1 || gender === 3) && showReference) {
  226 + sizes.push(_.get(value, 'boyReferSize.referenceName', ' '));
  227 + } else if (girlReference && (gender === 2 || gender === 3) && showReference) {
  228 + sizes.push(_.get(value, 'girlReferSize.referenceName', ' '));
  229 + } else {
  230 + if (size.thead[1] && showReference) {
  231 + size.thead[1] = {};
  232 + }
  233 + }
  234 +
  235 + // 其他尺码信息
  236 + _.get(value, 'sortAttributes', []).forEach(attr => {
  237 + sizes.push(_.get(attr, 'sizeValue', ' '));
  238 + });
  239 +
  240 + // 尺码信息
  241 + size.tbody.push(sizes);
  242 + });
  243 +
  244 + // 参考尺码为空
  245 + if (_.isEmpty(size.thead[1]) && showReference) {
  246 + // 移除这个值
  247 + size.thead.splice(1, 1);
  248 + }
  249 +
  250 + // 测量方式
  251 + if (sizeInfo.sizeImage) {
  252 + size.sizeImg = sizeInfo.sizeImage;
  253 + }
  254 +
  255 + return size;
  256 +};
  257 +
  258 +/**
  259 + * 商品尺码信息
  260 + *
  261 + * @param productSkn
  262 + * @param maxSortId
  263 + * @return object
  264 + */
  265 +const _getIntroInfo = (productSkn, additionalData)=> {
  266 + if (!productSkn) {
  267 + return {};
  268 + }
  269 +
  270 + let sizeInfo = additionalData.sizeInfo;
  271 +
  272 + if (_.isEmpty(sizeInfo)) {
  273 + return {};
  274 + }
  275 +
  276 + let result = {};
  277 +
  278 + // 尺寸数据
  279 + result.size = _getSizeData(sizeInfo);
  280 +
  281 + return result;
  282 +};
  283 +
  284 +/**
  285 + * 详情页数据格式化
  286 + * @param origin Object 原始数据
  287 + * @return result Object 格式化数据
  288 + */
  289 +const _detailDataPkg = (origin) => {
  290 + return co(function*() {
  291 + if (_.isEmpty(origin) || _.isEmpty(origin)) {
  292 + return {};
  293 + }
  294 +
  295 + let result = {};
  296 +
  297 + let propOrigin = _.partial(_.get, origin);
  298 +
  299 + // 商品名称
  300 + if (!propOrigin('product_name')) {
  301 + return result;
  302 + }
  303 +
  304 + result.name = propOrigin('product_name');
  305 + result.skn = propOrigin('product_skn');
  306 + result.productId = propOrigin('product_id');
  307 +
  308 + // 商品价格
  309 + result.marketPrice = propOrigin('format_market_price');
  310 + result.salePrice = propOrigin('format_sales_price');
  311 + result.hasOtherPrice = true;
  312 +
  313 + if (result.salePrice === '0') {
  314 + delete result.salePrice;
  315 + result.hasOtherPrice = false;
  316 + }
  317 +
  318 + // 上市期
  319 + if (propOrigin('expect_arrival_time')) {
  320 + result.arrivalDate = `${propOrigin('expect_arrival_time')}月`;
  321 + result.presalePrice = propOrigin('format_sales_price');
  322 + delete result.salePrice;
  323 + result.hasOtherPrice = false;
  324 + }
  325 +
  326 + // sku商品信息
  327 + let skuData = _getSkuDataByProductBaseInfo(origin);
  328 +
  329 + // 商品购买状态
  330 + let soldOut = !!(propOrigin('status') === 0 || skuData.totalStorageNum === 0);
  331 + let notForSale = propOrigin('attribute') === 2; // 非卖品
  332 + let virtualGoods = propOrigin('attribute') === 3; // 虚拟商品
  333 +
  334 + if (!soldOut && !notForSale && !virtualGoods) {
  335 + result.addToCart = 1;
  336 + }
  337 +
  338 + result.colors = skuData.skuGoods;
  339 +
  340 + return result;
  341 + })();
  342 +};
  343 +
  344 +/**
  345 + * 获取某一个商品详情主页面
  346 + */
  347 +const getProductInfoAsync = (pid) => {
  348 + return co(function * () {
  349 + if (!pid) {
  350 + return {};
  351 + }
  352 +
  353 + // 获取商品基本信息
  354 + let productData = yield productAPI.getProductAsync(pid);
  355 +
  356 + if (_.isEmpty(productData.data)) {
  357 + return Promise.reject({
  358 + code: 404,
  359 + message: 'app.product.data api wrong'
  360 + });
  361 + }
  362 +
  363 + let productSkn = _.get(productData, 'data.product_skn');
  364 +
  365 + let requestData = yield Promise.all([
  366 + _getProductIntroAsync(productSkn), // 商品详细介绍
  367 + _detailDataPkg(productData.data) // 商品详细价格
  368 + ]);
  369 +
  370 + let productDescription = requestData[0];
  371 + let productInfo = requestData[1];
  372 +
  373 + let intro = _getIntroInfo(productSkn, productDescription);
  374 +
  375 + return Object.assign(productInfo, intro);
  376 + })();
  377 +};
  378 +
  379 +module.exports = {
  380 + getProductInfoAsync // 获取某一个商品详情主页面
  381 +};
  1 +/**
  2 + * Created by TaoHuang on 2016/10/19.
  3 + */
  4 +
  5 +const api = global.yoho.API;
  6 +
  7 +const sizeInfoAsync = skn => {
  8 + return api.get('', {
  9 + method: 'h5.product.intro',
  10 + productskn: skn
  11 + });
  12 +
  13 +};
  14 +
  15 +/**
  16 + * 获得产品信息
  17 + * @param pid
  18 + * @returns {Promise.<type>}
  19 + */
  20 +const getProductAsync = (pid) => {
  21 +
  22 + return api.get('', {
  23 + method: 'app.product.data',
  24 + product_id: pid
  25 + });
  26 +};
  27 +
  28 +module.exports = {
  29 + sizeInfoAsync,
  30 + getProductAsync
  31 +};
  1 +/**
  2 + * router of sub app cart
  3 + * @author: htoooth<ht.anglenx@gmail.com>
  4 + * @date: 2016/10/19
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const router = require('express').Router(); // eslint-disable-line
  10 +const cRoot = './controllers';
  11 +
  12 +const cart = require(`${cRoot}/cart`);
  13 +
  14 +router.get('/index/getProductInfo', cart.getProductInfo);
  15 +
  16 +// Your controller here
  17 +
  18 +module.exports = router;
  1 +<div class="detail-header">
  2 + <span class="colse">X关闭</span>
  3 +</div>
  4 +<div class="detail-body">
  5 + <span class="magnify"></span>
  6 + {{#colors}}
  7 + <div class="detail-bigpic {{#unless focus}}none{{/unless}}">
  8 + {{#thumbs}}
  9 + <div class="bigpic">
  10 + <img src="{{shower}}">
  11 + </div>
  12 + {{/thumbs}}
  13 + <div class="piclist">
  14 + <span class="pre"></span>
  15 + <div class="con">
  16 + <ul>
  17 + {{#thumbs}}
  18 + <li><img src="{{img}}"></li>
  19 + {{/thumbs}}
  20 + </ul>
  21 + </div>
  22 + <span class="next"></span>
  23 + </div>
  24 + </div>
  25 + {{/colors}}
  26 + <div class="detail-info">
  27 + <div class="title">
  28 + <h2>{{name}}</h2>
  29 + </div>
  30 + <div class="type">
  31 + <span class="type-s">新品</span>
  32 + </div>
  33 + <div class="price">
  34 +
  35 + {{#if salePrice}}
  36 + <span class="oldprice">原价:<del>¥{{marketPrice}}</del></span>
  37 + <span class="newprice">现价:<b class="promotion-price">¥{{salePrice}}</b></span>
  38 + {{^}}
  39 + <span class="newprice {{#presalePrice}}none{{/presalePrice}}">原价:<b class="promotion-price">¥{{marketPrice}}</b></span>
  40 + {{/if}}
  41 +
  42 + {{#if presalePrice}}
  43 + <span class="oldprice">原价:<del>¥{{marketPrice}}</del></span>
  44 + <span class="newprice">预售价:<b class="promotion-price">¥{{presalePrice}}</b></span>
  45 + {{/if}}
  46 + {{#arrivalDate}}
  47 + <span class="arrivalDate">上市期:{{arrivalDate}}</span>
  48 + {{/arrivalDate}}
  49 + </div>
  50 + <div class="order">
  51 + <dl>
  52 + <dd class="colorBox">选颜色:</dd>
  53 + <dt>
  54 + <div class="colorBox">
  55 + <ul>
  56 + {{#colors}}
  57 + <li class="color">
  58 + <p class="{{#if focus}}atcive{{/if}}"><span></span><img src="{{src}}"></p>
  59 + <span>{{name}}</span>
  60 + </li>
  61 + {{/colors}}
  62 + </ul>
  63 + </div>
  64 + </dt>
  65 + <dd class="">选尺码:</dd>
  66 + <dt>
  67 + {{#colors}}
  68 + <div class="showSizeBox {{#unless focus}}none{{/unless}}">
  69 + {{#size}}
  70 + <span data-sku="{{sku}}" data-num="{{num}}">{{name}}</span>
  71 + {{/size}}
  72 + </div>
  73 + {{/colors}}
  74 + </dt>
  75 + <dd>选件数:</dd>
  76 + <dt>
  77 + <div class="amount_wrapper">
  78 + <i class="amount cut"></i>
  79 + <input type="text" id="mnum" class="mnum" value="1" readonly="readonly">
  80 + <i class="amount add"></i>
  81 +
  82 + </div>
  83 + </dt>
  84 + </dl>
  85 + </div>
  86 + <div class="submit">
  87 + <input class="addcart" type="button">
  88 + <input class="btn_pre_sale none" type="button">
  89 + <input class="btn_sellout none" type="button">
  90 + <input class="fav_count" type="button">
  91 + </div>
  92 + </div>
  93 +
  94 + <div class="detail-size">
  95 + <h3>尺码信息<span>(单位:厘米)</span></h3>
  96 + {{# size}}
  97 + <table>
  98 + <thead>
  99 + <tr>
  100 + {{# thead}}
  101 + <td width="{{width}}">{{name}}</td>
  102 + {{/ thead}}
  103 + </tr>
  104 + </thead>
  105 + <tbody>
  106 + {{# tbody}}
  107 + <tr>
  108 + {{#each .}}
  109 + <td>{{.}}</td>
  110 + {{/each}}
  111 + </tr>
  112 + {{/ tbody}}
  113 + </tbody>
  114 + </table>
  115 + {{/ size}}
  116 + <div class="size-info">
  117 + ※ 以上尺寸为实物实际测量,因测量方式不同会有略微误差,相关数据仅作参考,以收到实物为准。
  118 + </div>
  119 + </div>
  120 +</div>
  121 +<input value="{{addToCart}}" id="addToCart" type="hidden" />
  1 +/**
  2 + * 最近浏览controller
  3 + * @author: xuqi<qi.xu@yoho.cn>
  4 + * @date: 2016/10/11
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const rvModel = require('../models/recent-view');
  10 +const _ = require('lodash');
  11 +
  12 +const index = (req, res, next) => {
  13 +
  14 + let limit = req.query.limit;
  15 +
  16 + let browserSkn = decodeURIComponent(req.cookies._browseskn);
  17 +
  18 + // 拆解skn
  19 + let skn = browserSkn ? browserSkn.replace(/\-(\d)+(\,){0,1}/g, ',') : '';
  20 +
  21 + // 去除skn字符串的最后一个多余的,
  22 + if (skn && skn.lastIndexOf(',') === skn.length - 1) {
  23 + skn = skn.slice(0, -1);
  24 + }
  25 +
  26 + if (!skn) {
  27 + res.jsonp({
  28 + code: 200,
  29 + data: [],
  30 + message: 'User info'
  31 + });
  32 + } else {
  33 + skn = _.slice(_.uniq(skn.split(',')), 0, limit).join(','); // 去重+截取
  34 + rvModel.index(skn, limit).then(data => {
  35 + res.jsonp(data);
  36 + }).catch(next);
  37 + }
  38 +
  39 +};
  40 +
  41 +module.exports = {
  42 + index
  43 +};
  1 +/**
  2 + * sub app common
  3 + * @author: xuqi<qi.xu@yoho.cn>
  4 + * @date: 2016/10/11
  5 + */
  6 +
  7 +var express = require('express'),
  8 + path = require('path'),
  9 + hbs = require('express-handlebars');
  10 +
  11 +var app = express();
  12 +
  13 +// set view engin
  14 +var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
  15 +
  16 +app.on('mount', function(parent) {
  17 + delete parent.locals.settings; // 不继承父 App 的设置
  18 + Object.assign(app.locals, parent.locals);
  19 +});
  20 +
  21 +app.set('views', path.join(__dirname, 'views/action'));
  22 +app.engine('.hbs', hbs({
  23 + extname: '.hbs',
  24 + defaultLayout: 'layout',
  25 + layoutsDir: doraemon,
  26 + partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
  27 + helpers: global.yoho.helpers
  28 +}));
  29 +
  30 +// router
  31 +app.use(require('./router'));
  32 +
  33 +module.exports = app;
  1 +/**
  2 + * recent view model
  3 + * @author: xuqi<qi.xu@yoho.cn>
  4 + * @date: 2016/10/11
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const _ = require('lodash');
  10 +const api = global.yoho.API;
  11 +const helper = global.yoho.helpers;
  12 +
  13 +const index = (skn, limit) => {
  14 +
  15 + return api.get('', {
  16 + method: 'h5.product.batch',
  17 + productSkn: skn,
  18 + limit: limit
  19 + }).then(result => {
  20 +
  21 + if (result.code === 200) {
  22 + let data = [];
  23 + let historyProduct = result.data.product_list;
  24 +
  25 + _.forEach(historyProduct, hp => {
  26 + if (!hp) {
  27 + return;
  28 + }
  29 +
  30 + let mp = hp.market_price;
  31 + let sp = hp.sales_price;
  32 +
  33 + let defaultGoods = _.find(hp.goods_list, {is_default: 'Y'});
  34 +
  35 + // 无默认商品取商品列表第一个
  36 + if (!defaultGoods) {
  37 + defaultGoods = hp.goods_list[0];
  38 + }
  39 +
  40 + data.push({
  41 + market_price: mp === sp ? '' : ${helper.round(mp, 2)}`,
  42 + price: ${helper.round(sp, 2)}`,
  43 + product_name: hp.product_name,
  44 + url: helper.urlFormat(
  45 + `/product/pro_${hp.product_id}_${defaultGoods.goods_id}/${hp.cn_alphabet}.html`, '', 'item'),
  46 + pic_url: helper.image(defaultGoods.images_url, 150, 200, 2, 70)
  47 + });
  48 + });
  49 +
  50 + return {
  51 + code: 200,
  52 + data: data,
  53 + message: result.message
  54 + };
  55 + } else {
  56 +
  57 + // get list error
  58 + return {
  59 + code: result.code,
  60 + message: result.message,
  61 + data: []
  62 + };
  63 + }
  64 + });
  65 +};
  66 +
  67 +module.exports = {
  68 + index
  69 +};
  1 +/**
  2 + * router of sub app common
  3 + * @author: xuqi<qi.xu@yoho.cn>
  4 + * @date: 2016/10/11
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const router = require('express').Router(); // eslint-disable-line
  10 +const cRoot = './controllers';
  11 +
  12 +const rvCtrl = require(`${cRoot}/recent-view`);
  13 +
  14 +router.get('/recentReview', rvCtrl.index); // 最近浏览
  15 +
  16 +module.exports = router;
@@ -15,6 +15,14 @@ const _ = require('lodash'); @@ -15,6 +15,14 @@ const _ = require('lodash');
15 * 找回密码主页面 15 * 找回密码主页面
16 */ 16 */
17 const index = (req, res, next) => { 17 const index = (req, res, next) => {
  18 + // 清除cookie
  19 + res.clearCookie('_UID', {
  20 + domain: 'yohobuy.com'
  21 + });
  22 + res.clearCookie('_TOKEN', {
  23 + domain: 'yohobuy.com'
  24 + });
  25 +
18 service.indexPageDataAsync() 26 service.indexPageDataAsync()
19 .then(result => { 27 .then(result => {
20 res.render('back/index', Object.assign({ 28 res.render('back/index', Object.assign({
@@ -33,38 +33,33 @@ function doPassportCallback(req, res, user) { @@ -33,38 +33,33 @@ function doPassportCallback(req, res, user) {
33 refer = config.siteUrl; 33 refer = config.siteUrl;
34 } 34 }
35 if (user.openId) { 35 if (user.openId) {
36 - let signinByOpenID;  
37 -  
38 user.nickname = _.trim(user.nickname); 36 user.nickname = _.trim(user.nickname);
39 37
40 if (user.sourceType === 'wechat') { 38 if (user.sourceType === 'wechat') {
41 -  
42 - // PC 的微信登录之前使用了 open_id, 所以需要特别的接口处理  
43 - signinByOpenID = AuthHelper.signinByWechat(  
44 - user.nickname, user.openId, user.unionId, user.sourceType, shoppingKey);  
45 - } else {  
46 - signinByOpenID = AuthHelper.signinByOpenID(  
47 - user.nickname, user.openId, user.sourceType, shoppingKey); 39 + // PC 的微信登录使用unionId
  40 + user.openId = user.unionId;
48 } 41 }
49 42
50 - return signinByOpenID.then((result) => {  
51 - if (result.code !== 200) {  
52 - return Promise.reject(result);  
53 - }  
54 - if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line  
55 - return helpers.urlFormat('/passport/thirdlogin/index', {  
56 - openId: user.unionId || user.openId,  
57 - sourceType: user.sourceType,  
58 - refer: refer  
59 - });  
60 - } else if (result.code === 200 && result.data.uid) {  
61 - return AuthHelper.syncUserSession(result.data.uid, req, res).then(() => {  
62 - return refer;  
63 - });  
64 - }  
65 - }).then((redirectTo) => {  
66 - return res.redirect(redirectTo);  
67 - }); 43 + return AuthHelper.signinByOpenID(user.nickname, user.openId, user.sourceType, shoppingKey)
  44 + .then((result) => {
  45 + if (result.code !== 200) {
  46 + return res.redirect(config.siteUrl);
  47 + }
  48 + if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line
  49 + return helpers.urlFormat('/passport/thirdlogin/index', {
  50 + openId: user.unionId || user.openId,
  51 + sourceType: user.sourceType,
  52 + refer: refer
  53 + });
  54 + } else if (result.code === 200 && result.data.uid) {
  55 + return AuthHelper.syncUserSession(result.data.uid, req, res).then(() => {
  56 + return refer;
  57 + });
  58 + }
  59 + })
  60 + .then((redirectTo) => {
  61 + return res.redirect(redirectTo);
  62 + });
68 } else { 63 } else {
69 return Promise.resolve(res.redirect(loginPage)); 64 return Promise.resolve(res.redirect(loginPage));
70 } 65 }
@@ -75,7 +70,7 @@ const common = { @@ -75,7 +70,7 @@ const common = {
75 let refer = req.query.refer || req.get('Referer'); 70 let refer = req.query.refer || req.get('Referer');
76 71
77 refer && res.cookie('refer', encodeURI(refer), { 72 refer && res.cookie('refer', encodeURI(refer), {
78 - domain: 'yohobuy.com' 73 + domain: '.yohobuy.com'
79 }); 74 });
80 next(); 75 next();
81 }, 76 },
@@ -122,11 +117,7 @@ const local = { @@ -122,11 +117,7 @@ const local = {
122 areaName = area ? area.name : ''; 117 areaName = area ? area.name : '';
123 } 118 }
124 119
125 - let refer = req.query.refer;  
126 -  
127 - refer && res.cookie('refer', encodeURI(refer), {  
128 - domain: 'yohobuy.com'  
129 - }); 120 + let refer = req.query.refer || req.cookies.refer || req.get('Referer');
130 121
131 PassportHelper.getLeftBannerAsync(SIGNIN_LEFT_BANNER_CODE).then(cover => { 122 PassportHelper.getLeftBannerAsync(SIGNIN_LEFT_BANNER_CODE).then(cover => {
132 res.render('login', { 123 res.render('login', {
@@ -176,7 +167,7 @@ const local = { @@ -176,7 +167,7 @@ const local = {
176 let refer = (function() { 167 let refer = (function() {
177 if (/sign|login|reg|passport/.test(_.get(req, 'cookies.refer', ''))) { 168 if (/sign|login|reg|passport/.test(_.get(req, 'cookies.refer', ''))) {
178 return `${config.siteUrl}/home`; 169 return `${config.siteUrl}/home`;
179 - } else if (_.has(req, 'cookies.refer')) { 170 + } else if (_.get(req, 'cookies.refer')) {
180 return decodeURI(req.cookies.refer); 171 return decodeURI(req.cookies.refer);
181 } else { 172 } else {
182 return `${config.siteUrl}/home`; 173 return `${config.siteUrl}/home`;
@@ -6,6 +6,11 @@ const aes = require('./aes-pwd'); @@ -6,6 +6,11 @@ const aes = require('./aes-pwd');
6 const cache = global.yoho.cache; 6 const cache = global.yoho.cache;
7 const sign = global.yoho.sign; 7 const sign = global.yoho.sign;
8 const api = global.yoho.API; 8 const api = global.yoho.API;
  9 +const cookie = global.yoho.cookie;
  10 +
  11 +const Promise = require('bluebird');
  12 +
  13 +const cartService = require('./cart-service');
9 14
10 const Auth = { 15 const Auth = {
11 signin(type, area, profile, password, shoppingKey) { 16 signin(type, area, profile, password, shoppingKey) {
@@ -137,12 +142,11 @@ const Auth = { @@ -137,12 +142,11 @@ const Auth = {
137 return api.get('', param); 142 return api.get('', param);
138 }, 143 },
139 syncUserSession(uid, req, res) { 144 syncUserSession(uid, req, res) {
140 - return Auth.profile(uid).then((userInfo) => { 145 + return Promise.all([Auth.profile(uid), cartService.goodsCount(uid)]).spread((userInfo, count) => {
141 let token = sign.makeToken(uid); 146 let token = sign.makeToken(uid);
142 let data = userInfo.data; 147 let data = userInfo.data;
143 let encryptionUid = aes.encryptionUid(data.uid); 148 let encryptionUid = aes.encryptionUid(data.uid);
144 149
145 -  
146 if (data) { 150 if (data) {
147 let uidCookie = `${data.profile_name}::${encryptionUid}::${data.vip_info.title}::${token}`; 151 let uidCookie = `${data.profile_name}::${encryptionUid}::${data.vip_info.title}::${token}`;
148 let isStudent = data.vip_info.is_student || 0; 152 let isStudent = data.vip_info.is_student || 0;
@@ -156,6 +160,16 @@ const Auth = { @@ -156,6 +160,16 @@ const Auth = {
156 res.cookie('isStudent', isStudent, { 160 res.cookie('isStudent', isStudent, {
157 domain: 'yohobuy.com' 161 domain: 'yohobuy.com'
158 }); 162 });
  163 +
  164 + // 购物车中商品的数量
  165 + res.cookie('_g', JSON.stringify({
  166 + _k: cookie.getShoppingKey(req),
  167 + _nac: count,
  168 + _ac: 0,
  169 + _c: 1
  170 + }), {
  171 + domain: 'yohobuy.com'
  172 + });
159 } 173 }
160 req.session._TOKEN = token; // esline-disable-line 174 req.session._TOKEN = token; // esline-disable-line
161 req.session._LOGIN_UID = uid; // esline-disable-line 175 req.session._LOGIN_UID = uid; // esline-disable-line
  1 +/**
  2 + * Created by TaoHuang on 2016/9/28.
  3 + */
  4 +
  5 +'use strict';
  6 +
  7 +const api = global.yoho.API;
  8 +
  9 +const goodsCount = (uid, shoppingKey) => {
  10 + let params = {
  11 + method: 'app.Shopping.count'
  12 + };
  13 +
  14 + if (uid) {
  15 + params.uid = uid;
  16 + }
  17 +
  18 + if (shoppingKey) {
  19 + params.shopping_key = shoppingKey;
  20 + }
  21 +
  22 + return api.get('', params);
  23 +};
  24 +
  25 +module.exports = {
  26 + goodsCount
  27 +};
  1 +/**
  2 + * Created by TaoHuang on 2016/9/28.
  3 + */
  4 +
  5 +'use strict';
  6 +
  7 +
  8 +const api = require('./cart-api');
  9 +
  10 +const _ = require('lodash');
  11 +
  12 +const goodsCount = (uid, shoppingKey) => {
  13 + return api.goodsCount(uid, shoppingKey).then(result => _.get(result, 'data.cart_goods_count', 0));
  14 +};
  15 +
  16 +module.exports = {
  17 + goodsCount
  18 +};
  19 +
@@ -11,189 +11,230 @@ const mRoot = '../models'; @@ -11,189 +11,230 @@ const mRoot = '../models';
11 const service = require(`${mRoot}/detail-service`); 11 const service = require(`${mRoot}/detail-service`);
12 const detailHelper = require(`${mRoot}/detail-helper`); 12 const detailHelper = require(`${mRoot}/detail-helper`);
13 13
14 -const Actions = require('./lib/actions');  
15 -const YohoAction = require('./lib/yoho-action');  
16 const moment = require('moment'); 14 const moment = require('moment');
17 const DEFAULT_AVATAR_ICO = 'http://static.yohobuy.com/images/v3/boy.jpg'; 15 const DEFAULT_AVATAR_ICO = 'http://static.yohobuy.com/images/v3/boy.jpg';
  16 +const SEO_SLOGAN = 'YOHO!BUY 有货';
18 const _ = require('lodash'); 17 const _ = require('lodash');
19 18
20 -class DetailAction extends YohoAction {  
21 - /**  
22 - * 渲染商品详情  
23 - */  
24 - render() {  
25 - const req = this.request;  
26 -  
27 - let pid = req.params[0];  
28 - let gid = req.params[1];  
29 - let saveCurrentGoodsInCookies = _.partial(service.saveRecentGoodInCookies,  
30 - req.cookies._browse,  
31 - req.cookies._browseskn,  
32 - this.response  
33 - );  
34 - let vipLevel = detailHelper.vipLevel(req.user.vip);  
35 - let uid = req.user.uid || 0;  
36 - let isStudent = req.user.isStudent || 0;  
37 -  
38 - return service.showMainAsync({  
39 - pid: pid,  
40 - gid: gid,  
41 - channel: this.getSessionChannel(),  
42 - gender: this.guessUserGender(),  
43 - uid: uid,  
44 - isStudent: isStudent,  
45 - vipLevel: vipLevel,  
46 - saveInCookies: saveCurrentGoodsInCookies  
47 - }).then(result=> {  
48 - const seo = result.seo;  
49 -  
50 - this.setTitle(seo.title, true, '|');  
51 - this.setKeywords(seo.keywords);  
52 - this.setDescription(seo.description, true);  
53 -  
54 - this.setEntry('product', 'detail');  
55 - this.renderTemplate('product/detail', result);  
56 - }).catch(err => {  
57 - if (err.code === 404) {  
58 - return this.next();  
59 - } 19 +const CHANNEL = {
  20 + boys: 'boys',
  21 + girls: 'girls',
  22 + kids: 'kids'
  23 +};
60 24
61 - return this.next(err); 25 +const _getChannel = (req, res) => {
  26 + let channel = req.cookies._Channel;
  27 +
  28 + if (!channel) {
  29 + res.cookie('_Channel', CHANNEL.boys, {
  30 + domain: '.yohobuy.com',
  31 + maxAge: moment.duration(300, 'days').seconds()
62 }); 32 });
  33 +
  34 + channel = 'boys'; // 设置默认值
  35 + }
  36 +
  37 + return channel;
  38 +};
  39 +
  40 +const _getGender = (channel) => {
  41 + switch (channel) {
  42 + case 'boys':
  43 + return '1,3';
  44 + case 'girls':
  45 + return '2,3';
  46 + default:
  47 + return '1,2,3';
  48 + }
  49 +};
  50 +
  51 +/**
  52 + * 首屏渲染商品详情
  53 + */
  54 +const showMain = (req, res, next) => {
  55 + let pid = req.params[0];
  56 + let channel = _getChannel(req, res);
  57 + let gender = _getGender(channel);
  58 +
  59 + const nullUserInfo = {
  60 + uid: null,
  61 + isStudent: null,
  62 + vipLevel: null,
  63 + saveInCookies: null
  64 + };
  65 +
  66 + return service.showMainAsync(Object.assign({
  67 + pid: pid,
  68 + channel: channel,
  69 + gender: gender
  70 + }, nullUserInfo)).then((result)=> {
  71 + return res.render('product/detail', Object.assign({
  72 + module: 'product',
  73 + page: 'detail',
  74 + title_more: true,
  75 + title: _.get(result, 'seo.title', '') + ' | ' + SEO_SLOGAN,
  76 + keywords: _.get(result, 'seo.keywords', '').replace(/~+/, ''),
  77 + description_more: true,
  78 + description: result.description
  79 + }, result));
  80 + }).catch(next);
  81 +
  82 +};
  83 +
  84 +/**
  85 + * 商品价格相关的数据
  86 + */
  87 +const detailHeader = (req, res, next) => {
  88 + let pid = req.query.productId || 0;
  89 + let uid = req.user.uid || 0;
  90 + let vipLevel = detailHelper.vipLevel(req.user.vip);
  91 + let dataMd5 = req.query.md5 || 0;
  92 +
  93 + let saveCurrentGoodsInCookies = _.partial(service.saveRecentGoodInCookies,
  94 + req.cookies._browseskn,
  95 + res
  96 + );
  97 +
  98 + return service.getDetailHeader(pid, uid, req.user.isStudent, vipLevel, dataMd5, saveCurrentGoodsInCookies)
  99 + .then((result) => {
  100 + if (result.code === 200) {
  101 + return res.render('product/detail-header', Object.assign({layout: false}, result.data));
  102 + } else {
  103 + return res.status(204).end();
  104 + }
  105 + }).catch(next);
  106 +};
  107 +
  108 +/**
  109 + * 特殊商品退换货
  110 + */
  111 +const detailReturn = (req, res, next) => {
  112 + let skn = req.query.skn || 0;
  113 +
  114 + if (!skn) {
  115 + return {
  116 + code: 400,
  117 + message: '商品数据出错'
  118 + };
63 } 119 }
64 -} 120 +
  121 + return service.saleReturn(skn).then(result => {
  122 + return res.json({
  123 + code: 200,
  124 + data: {
  125 + result: result
  126 + }
  127 + });
  128 + }).catch(next);
  129 +};
65 130
66 /** 131 /**
67 * 获取热区图 132 * 获取热区图
68 */ 133 */
69 -class HotAreaAction extends YohoAction {  
70 - render() {  
71 - let pid = this.request.query.productId || 0;  
72 -  
73 - return service.indexHotAreaAsync(pid).then(result => {  
74 - this.renderTemplate('product/hotarea', {  
75 - hotArea: result,  
76 - layout: false  
77 - }); 134 +const indexHotArea = (req, res, next) => {
  135 + let pid = req.query.productId || 0;
  136 +
  137 + return service.indexHotAreaAsync(pid).then((result) => {
  138 + res.render('product/hotarea', {
  139 + hotArea: result,
  140 + layout: false
78 }); 141 });
79 - }  
80 -} 142 + }).catch(next);
  143 +};
81 144
82 /** 145 /**
83 * 获得评论列表 146 * 获得评论列表
84 */ 147 */
85 -class CommentAction extends YohoAction {  
86 - render() {  
87 - const req = this.request;  
88 - let pid = req.query.productId || 0;  
89 - let page = req.query.page || 1;  
90 - let size = req.query.size || 10;  
91 -  
92 - return service.getShareOrderListAsync(pid, page, size).then((result) => {  
93 - let pageResponse = _.get(result, 'data.pageResponse', {});  
94 -  
95 - this.response.json({  
96 - code: result.code,  
97 - data: _.get(pageResponse, 'list', []).map((item)=> {  
98 -  
99 - return {  
100 - avatar: _.get(item, 'userInfo.headIco', '') ?  
101 - helpers.image(item.userInfo.headIco, 30, 30) :  
102 - DEFAULT_AVATAR_ICO,  
103 - userName: _.get(item, 'userInfo.nickName', ''),  
104 - date: moment(item.createTime, 'X').format('YYYY-MM-DD HH:mm:ss'),  
105 - color: _.get(item, 'goods.color_name', ''),  
106 - size: _.get(item, 'goods.size_name', ''),  
107 - comment: item.content,  
108 - total: pageResponse.totalCount  
109 - };  
110 - })  
111 - }); 148 +
  149 +const indexComment = (req, res, next) => {
  150 + let pid = req.query.productId || 0;
  151 + let page = req.query.page || 1;
  152 + let size = req.query.size || 10;
  153 +
  154 + return service.getShareOrderListAsync(pid, page, size).then((result) => {
  155 + let pageResponse = _.get(result, 'data.pageResponse', {});
  156 +
  157 + return res.json({
  158 + code: result.code,
  159 + data: _.get(pageResponse, 'list', []).map((item)=> {
  160 +
  161 + return {
  162 + avatar: _.get(item, 'userInfo.headIco', '') ?
  163 + helpers.image(item.userInfo.headIco, 30, 30) :
  164 + DEFAULT_AVATAR_ICO,
  165 + userName: _.get(item, 'userInfo.nickName', ''),
  166 + date: moment(item.createTime, 'X').format('YYYY-MM-DD HH:mm:ss'),
  167 + color: _.get(item, 'goods.color_name', ''),
  168 + size: _.get(item, 'goods.size_name', ''),
  169 + comment: item.content,
  170 + total: pageResponse.totalCount
  171 + };
  172 + })
112 }); 173 });
113 - }  
114 -} 174 + }).catch(next);
  175 +};
115 176
116 /** 177 /**
117 * 获得咨询列表 178 * 获得咨询列表
118 */ 179 */
119 -class ConsultAction extends YohoAction {  
120 - render() {  
121 - const req = this.request;  
122 - let uid = req.user.uid || '';  
123 - let pid = req.query.productId || 0;  
124 - let page = req.query.page || 1;  
125 - let size = req.query.size || 10;  
126 -  
127 - return service.indexConsultAsync(uid, pid, page, size).then(result => {  
128 - this.response.json({  
129 - code: 200,  
130 - data: result  
131 - }); 180 +const indexConsult = (req, res, next) => {
  181 + let uid = req.user.uid || '';
  182 + let pid = req.query.productId || 0;
  183 + let page = req.query.page || 1;
  184 + let size = req.query.size || 10;
  185 +
  186 + return service.indexConsultAsync(uid, pid, page, size).then(result => {
  187 + return res.json({
  188 + code: 200,
  189 + data: result
132 }); 190 });
133 - }  
134 -} 191 + }).catch(next);
  192 +};
135 193
136 /** 194 /**
137 * 新建咨询 195 * 新建咨询
138 */ 196 */
139 -class CreateConsultAction extends YohoAction {  
140 - render() {  
141 - const req = this.request, res = this.response;  
142 - let uid = req.user.uid || '';  
143 - let pid = req.body.productId || 0;  
144 - let content = req.body.content;  
145 -  
146 - if (content && uid) {  
147 - return service.createConsultAsync(uid, pid, content).then(result => {  
148 - res.json(result);  
149 - });  
150 - } else if (!content) {  
151 - res.json({  
152 - code: 400,  
153 - message: '请输入咨询内容'  
154 - });  
155 -  
156 - } else if (!uid) {  
157 - res.json({  
158 - code: 403,  
159 - message: '用户没有登录',  
160 - data: {  
161 - url: helpers.urlFormat('/signin.html')  
162 - }  
163 - });  
164 -  
165 - } else {  
166 - res.json({  
167 - code: 400,  
168 - message: '请输入咨询内容'  
169 - });  
170 - }  
171 - }  
172 -} 197 +const createConsult = (req, res, next) => {
  198 + let uid = req.user.uid || '';
  199 + let pid = req.body.productId || 0;
  200 + let content = req.body.content;
  201 +
  202 + if (content && uid) {
  203 + return service.createConsultAsync(uid, pid, content).then(result => {
  204 + res.json(result);
  205 + }).catch(next);
  206 + } else if (!content) {
  207 + res.json({
  208 + code: 400,
  209 + message: '请输入咨询内容'
  210 + });
173 211
174 -const detailHeader = (req, res, next) => {  
175 - let pid = req.query.productId || 0;  
176 - let uid = req.user.uid || 0;  
177 - let vipLevel = detailHelper.vipLevel(req.user.vip);  
178 - let dataMd5 = req.query.md5 || 0; 212 + } else if (!uid) {
  213 + res.json({
  214 + code: 403,
  215 + message: '用户没有登录',
  216 + data: {
  217 + url: helpers.urlFormat('/signin.html')
  218 + }
  219 + });
179 220
180 - service.getDetailHeader(pid, uid, req.user.isStudent, vipLevel, dataMd5).then((result) => {  
181 - if (result.code === 200) {  
182 - return res.render('product/detail-header', Object.assign({layout: false}, result.data));  
183 - } else {  
184 - return res.status(204).end();  
185 - }  
186 - }).catch(next); 221 + } else {
  222 + res.json({
  223 + code: 400,
  224 + message: '请输入咨询内容'
  225 + });
  226 + }
187 }; 227 };
188 228
189 -module.exports = {  
190 - showMain: Actions.createAction(DetailAction),  
191 - indexHotArea: Actions.createAction(HotAreaAction),  
192 - indexComment: Actions.createAction(CommentAction),  
193 - indexConsult: Actions.createAction(ConsultAction),  
194 - createConsult: Actions.createAction(CreateConsultAction),  
195 - productHeader: detailHeader  
196 229
  230 +module.exports = {
  231 + showMain,
  232 + indexHotArea,
  233 + indexComment,
  234 + indexConsult,
  235 + createConsult,
  236 + productHeader: detailHeader,
  237 + detailReturn
197 }; 238 };
198 239
199 240
@@ -17,10 +17,10 @@ const changeFavoriteBrand = (req, res, next) => { @@ -17,10 +17,10 @@ const changeFavoriteBrand = (req, res, next) => {
17 17
18 if (uid && brandId) { 18 if (uid && brandId) {
19 brandService.changeAsync(uid, brandId).then(result => { 19 brandService.changeAsync(uid, brandId).then(result => {
20 - res.json(result); 20 + return res.json(result);
21 }).catch(next); 21 }).catch(next);
22 } else if (!uid) { 22 } else if (!uid) {
23 - res.json({ 23 + return res.json({
24 code: 403, 24 code: 403,
25 message: '用户ID不存在', 25 message: '用户ID不存在',
26 data: { 26 data: {
@@ -28,13 +28,29 @@ const changeFavoriteBrand = (req, res, next) => { @@ -28,13 +28,29 @@ const changeFavoriteBrand = (req, res, next) => {
28 } 28 }
29 }); 29 });
30 } else { 30 } else {
31 - res.json({ 31 + return res.json({
32 code: 400, 32 code: 400,
33 message: '操作失败' 33 message: '操作失败'
34 }); 34 });
35 } 35 }
36 }; 36 };
37 37
  38 +const isFavoriteBrand = (req, res, next) => {
  39 + let uid = req.user.uid || '';
  40 + let brandId = req.query.brandId;
  41 +
  42 + if (uid && brandId) {
  43 + brandService.isFavoriteAsync(uid, brandId).then(result => {
  44 + return res.json(result);
  45 + }).catch(next);
  46 + } else {
  47 + return res.json({
  48 + code: 400,
  49 + message: '状态失败'
  50 + });
  51 + }
  52 +};
  53 +
38 const collectProduct = (req, res, next) => { 54 const collectProduct = (req, res, next) => {
39 let uid = req.user.uid || ''; 55 let uid = req.user.uid || '';
40 let pid = req.body.productId; 56 let pid = req.body.productId;
@@ -43,8 +59,7 @@ const collectProduct = (req, res, next) => { @@ -43,8 +59,7 @@ const collectProduct = (req, res, next) => {
43 if (uid && pid) { 59 if (uid && pid) {
44 switch (type) { 60 switch (type) {
45 case 'add': 61 case 'add':
46 - {  
47 - productService.createAsync(uid, pid) 62 + productService.createAsync(uid, pid)
48 .then(result => { 63 .then(result => {
49 if (result.code === 413) { 64 if (result.code === 413) {
50 result.message = '该商品已经收藏'; 65 result.message = '该商品已经收藏';
@@ -53,22 +68,17 @@ const collectProduct = (req, res, next) => { @@ -53,22 +68,17 @@ const collectProduct = (req, res, next) => {
53 res.json(result); 68 res.json(result);
54 }) 69 })
55 .catch(next); 70 .catch(next);
56 - break;  
57 - } 71 + break;
58 case 'cancel': 72 case 'cancel':
59 - {  
60 - productService.deleteAsync(uid, pid) 73 + productService.deleteAsync(uid, pid)
61 .then(result => res.json(result)) 74 .then(result => res.json(result))
62 .catch(next); 75 .catch(next);
63 - break;  
64 - } 76 + break;
65 default: 77 default:
66 - {  
67 - res.json({  
68 - code: 400,  
69 - message: '错误类型'  
70 - });  
71 - } 78 + res.json({
  79 + code: 400,
  80 + message: '错误类型'
  81 + });
72 } 82 }
73 } else if (!uid) { 83 } else if (!uid) {
74 res.json({ 84 res.json({
@@ -134,5 +144,6 @@ module.exports = { @@ -134,5 +144,6 @@ module.exports = {
134 changeFavoriteBrand, 144 changeFavoriteBrand,
135 collectProduct, 145 collectProduct,
136 collectShop, 146 collectShop,
137 - isFavShop 147 + isFavShop,
  148 + isFavoriteBrand
138 }; 149 };
1 -/**  
2 - * AbstractAction  
3 - *  
4 - * @author: Aiden Xu<aiden.xu@yoho.cn>  
5 - * @date: 2016/7/11  
6 - */  
7 -'use strict';  
8 -  
9 -const _ = require('lodash');  
10 -const Promise = require('bluebird');  
11 -  
12 -class AbstractAction {  
13 - constructor(req, res, next) {  
14 - if (!req || !res) {  
15 - throw new Error('Request and response object must be specified.');  
16 - }  
17 -  
18 - this.request = req;  
19 - this.response = res;  
20 - this.next = next;  
21 - this.renderContext = {};  
22 - }  
23 -  
24 - /**  
25 - * 判断是否是AJAX请求  
26 - *  
27 - * @return boolean 如果是AJAX请求返回 true  
28 - */  
29 -  
30 - /**  
31 - * 设置入口  
32 - *  
33 - * @param module 模块名称  
34 - * @param entry 入口名称  
35 - */  
36 - setEntry(module, entry) {  
37 - _.merge(this.renderContext, {  
38 - module: module,  
39 - page: entry  
40 - });  
41 - }  
42 -  
43 - /**  
44 - * 渲染视图  
45 - *  
46 - * @param template 模版名称  
47 - * @param context 上下文  
48 - */  
49 - renderTemplate(template, context) {  
50 - this.response.render(template, _.merge({}, this.renderContext, context));  
51 - }  
52 -  
53 - /**  
54 - * 内部渲染方法,该方法应该由 ActionCreator 来调用  
55 - *  
56 - * @returns Promise  
57 - */  
58 - _render() {  
59 - return this.render();  
60 - }  
61 -  
62 - /**  
63 - * 渲染回调方法  
64 - *  
65 - * @returns {Promise}  
66 - */  
67 - render() {  
68 - return new Promise(function(resolve) {  
69 - return resolve();  
70 - });  
71 - }  
72 -}  
73 -  
74 -module.exports = AbstractAction;  
1 -/**  
2 - * Actions  
3 - *  
4 - * @author: Aiden Xu<aiden.xu@yoho.cn>  
5 - * @date: 2016/7/11  
6 - */  
7 -'use strict';  
8 -  
9 -const AbstractAction = require('./abstract-action');  
10 -  
11 -/**  
12 - * 创建 Action  
13 - * @param Action  
14 - * @returns {Function}  
15 - */  
16 -const createAction = Action => {  
17 - return ((req, res, next) => {  
18 - const ret = new Action(req, res, next)._render();  
19 -  
20 - if (ret && typeof Promise.catch === 'function') {  
21 - ret.catch(next);  
22 - }  
23 - });  
24 -};  
25 -  
26 -module.exports = {  
27 - createAction,  
28 - AbstractAction  
29 -};  
1 -/**  
2 - * YohoAction  
3 - *  
4 - * @author: Aiden Xu<aiden.xu@yoho.cn>  
5 - * @date: 2016/7/11  
6 - */  
7 -'use strict';  
8 -  
9 -const CHANNEL_BOYS = 'boys';  
10 -const CHANNEL_GIRLS = 'girls';  
11 -const CHANNEL_KIDS = 'kids';  
12 -const CHANNEL_DEFAULT = CHANNEL_BOYS;  
13 -  
14 -module.exports = {  
15 - CHANNEL_DEFAULT,  
16 - CHANNEL_BOYS,  
17 - CHANNEL_GIRLS,  
18 - CHANNEL_KIDS  
19 -};  
1 -/**  
2 - * YohoAction  
3 - *  
4 - * @author: Aiden Xu<aiden.xu@yoho.cn>  
5 - * @date: 2016/7/11  
6 - */  
7 -'use strict';  
8 -  
9 -const AbstractAction = require('./abstract-action');  
10 -const Channel = require('./channel');  
11 -const moment = require('moment');  
12 -const COOKIE_DOMAIN = '.yohobuy.com';  
13 -const COOKIE_CHANEL_MAX_AGE = 300;  
14 -const _ = require('lodash');  
15 -  
16 -class YohoAction extends AbstractAction {  
17 -  
18 - /**  
19 - * 设置网站SEO的标题  
20 - *  
21 - * @param title 标题  
22 - * @param sign 连接的字符串  
23 - * @param showMore 是否显示更多内容  
24 - * @return void  
25 - */  
26 - setTitle(title, showMore, sign) {  
27 - showMore = showMore || true;  
28 - sign = sign || ' | ';  
29 -  
30 - _.merge(this.renderContext, {  
31 - title_more: showMore,  
32 - title: title + sign  
33 - });  
34 - }  
35 -  
36 - getUid() {  
37 - return this.request.user.uid;  
38 - }  
39 -  
40 - /**  
41 - * 获得当前用户所在的频道  
42 - *  
43 - * @returns {*}  
44 - */  
45 - getSessionChannel() {  
46 - const channel = this.request.cookies._Channel || Channel.CHANNEL_DEFAULT;  
47 -  
48 - if (!channel) {  
49 - // 设置默认频道  
50 - this.setSessionChannel(Channel.CHANNEL_BOYS);  
51 - }  
52 -  
53 - return channel;  
54 - }  
55 -  
56 - /**  
57 - * 设置当前用户的频道  
58 - */  
59 - setSessionChannel(channel) {  
60 - this.response.cookie('_Channel', channel || Channel.CHANNEL_DEFAULT, {  
61 - domain: COOKIE_DOMAIN,  
62 - maxAge: moment.duration(COOKIE_CHANEL_MAX_AGE, 'days').seconds()  
63 - });  
64 - }  
65 -  
66 - /**  
67 - * 根据用户访问的频道猜测用户性别  
68 - */  
69 - guessUserGender() {  
70 - switch (this.getSessionChannel()) {  
71 - case 'boys':  
72 - return '1,3';  
73 - case 'girls':  
74 - return '2,3';  
75 - default:  
76 - return '1,2,3';  
77 - }  
78 - }  
79 -  
80 - /**  
81 - * 设置网站SEO的关键词  
82 - *  
83 - * @param keywords 关键词,多个之间用","逗号分隔  
84 - */  
85 - setKeywords(keywords) {  
86 - // this->_view->assign('keywords', rtrim(keywords, ',') . ',');  
87 - _.merge(this.renderContext, {  
88 - keywords: keywords.replace(/~+/, '')  
89 - });  
90 - }  
91 -  
92 - /**  
93 - * 设置网站SEO的描述内容  
94 - *  
95 - * @param description 描述内容  
96 - * @param showMore 是否显示更多内容  
97 - * @param sign 连接的字符串  
98 - */  
99 - setDescription(description, showMore, sign) {  
100 - _.merge(this.renderContext, {  
101 - description_more: showMore,  
102 - description: description + sign  
103 - });  
104 - }  
105 -}  
106 -  
107 -module.exports = YohoAction;  
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
6 const Promise = require('bluebird'); 6 const Promise = require('bluebird');
7 const co = Promise.coroutine; 7 const co = Promise.coroutine;
8 const api = require('./brand-api'); 8 const api = require('./brand-api');
  9 +const _ = require('lodash');
9 10
10 const getBrandByDomainAsync = domain => { 11 const getBrandByDomainAsync = domain => {
11 return co(function*() { 12 return co(function*() {
@@ -24,7 +25,7 @@ const getBrandByDomainAsync = domain => { @@ -24,7 +25,7 @@ const getBrandByDomainAsync = domain => {
24 result.brandNameCn = brandInfo.data.brand_name_cn || ''; 25 result.brandNameCn = brandInfo.data.brand_name_cn || '';
25 result.brandAbout = brandInfo.data.brand_intro || ''; 26 result.brandAbout = brandInfo.data.brand_intro || '';
26 result.shopTemplateType = brandInfo.data.shop_template_type ? 27 result.shopTemplateType = brandInfo.data.shop_template_type ?
27 - parseInt(brandInfo.data.shop_template_type) : ''; 28 + _.parseInt(brandInfo.data.shop_template_type) : '';
28 result.type = brandInfo.data.type ? +brandInfo.data.type : 0; 29 result.type = brandInfo.data.type ? +brandInfo.data.type : 0;
29 result.shopId = brandInfo.data.shop_id || ''; 30 result.shopId = brandInfo.data.shop_id || '';
30 31
@@ -56,8 +56,8 @@ const indexAsync = pid => { @@ -56,8 +56,8 @@ const indexAsync = pid => {
56 marketPrice: cur.product.productPriceBo.formatMarketPrice, 56 marketPrice: cur.product.productPriceBo.formatMarketPrice,
57 productName: cur.product.productName, 57 productName: cur.product.productName,
58 href: helpers.getUrlBySkc( 58 href: helpers.getUrlBySkc(
59 - _.head(goods.goodsImagesList).productId,  
60 - _.head(goods.goodsImagesList).goodsId, 59 + _.get(goods, 'goodsImagesList[0].productId', ''),
  60 + _.get(goods, 'goodsImagesList[0].goodsId', ''),
61 cur.product.cnAlphabet 61 cur.product.cnAlphabet
62 ) 62 )
63 }; 63 };
@@ -86,6 +86,23 @@ const getPromotionAsync = (skn) => { @@ -86,6 +86,23 @@ const getPromotionAsync = (skn) => {
86 return api.get('', params); 86 return api.get('', params);
87 }; 87 };
88 88
  89 +const getLimitedProductStatusAsync = (code, uid, skn) => {
  90 + let params = {
  91 + method: 'app.limitProduct.productStatus',
  92 + limitProductCode: code
  93 + };
  94 +
  95 + if (uid) {
  96 + params.uid = uid;
  97 + }
  98 +
  99 + if (skn) {
  100 + params.product_skn = skn;
  101 + }
  102 +
  103 + return api.get('', params);
  104 +};
  105 +
89 module.exports = { 106 module.exports = {
90 getProductBannerAsync, 107 getProductBannerAsync,
91 sizeInfoAsync, 108 sizeInfoAsync,
@@ -94,5 +111,6 @@ module.exports = { @@ -94,5 +111,6 @@ module.exports = {
94 getProductModelTryAsync, 111 getProductModelTryAsync,
95 getProductAsync, 112 getProductAsync,
96 getPromotionAsync, 113 getPromotionAsync,
97 - isSupportReturnedSale 114 + isSupportReturnedSale,
  115 + getLimitedProductStatusAsync
98 }; 116 };
@@ -35,16 +35,11 @@ const EXHIBITION_TICKET = 51335912; @@ -35,16 +35,11 @@ const EXHIBITION_TICKET = 51335912;
35 const _getProductAdditionInfoAsync = (data) => { 35 const _getProductAdditionInfoAsync = (data) => {
36 return co(function * () { 36 return co(function * () {
37 let productId = _.get(data, 'product_id', 0); 37 let productId = _.get(data, 'product_id', 0);
38 - let productSkn = _.get(data, 'product_skn', 0);  
39 let brandId = _.get(data, 'brand_info.brand_id', 0); 38 let brandId = _.get(data, 'brand_info.brand_id', 0);
40 39
41 // 获取相关数据 40 // 获取相关数据
42 let promiseData = { 41 let promiseData = {
43 productBanner: productAPI.getProductBannerAsync(productId), 42 productBanner: productAPI.getProductBannerAsync(productId),
44 - sizeInfo: productAPI.sizeInfoAsync(productSkn),  
45 - productComfort: productAPI.getProductComfortAsync(productId),  
46 - productModelCard: productAPI.getProductModelCardAsync(productId),  
47 - productModelTry: productAPI.getProductModelTryAsync(productSkn),  
48 bannerInfo: brandService.getBannerInfoAsync(brandId) 43 bannerInfo: brandService.getBannerInfoAsync(brandId)
49 }; 44 };
50 45
@@ -54,12 +49,25 @@ const _getProductAdditionInfoAsync = (data) => { @@ -54,12 +49,25 @@ const _getProductAdditionInfoAsync = (data) => {
54 })(); 49 })();
55 }; 50 };
56 51
  52 +const _getProductIntroAsync = (productId, productSkn) => {
  53 + return co(function * () {
  54 + let result = yield Promise.props({
  55 + sizeInfo: productAPI.sizeInfoAsync(productSkn),
  56 + productComfort: productAPI.getProductComfortAsync(productId),
  57 + productModelCard: productAPI.getProductModelCardAsync(productId),
  58 + productModelTry: productAPI.getProductModelTryAsync(productSkn)
  59 + });
  60 +
  61 + return result;
  62 + })();
  63 +};
  64 +
57 /** 65 /**
58 * 获取商品的喜欢 66 * 获取商品的喜欢
59 * pid : product id 67 * pid : product id
60 * bid : brand id 68 * bid : brand id
61 */ 69 */
62 -const _getProductFavoriteDataAsync = (uid, pid, bid) => { 70 +const _getProductFavoriteDataAsync = (uid, pid) => {
63 return co(function*() { 71 return co(function*() {
64 let result = { 72 let result = {
65 product: false, 73 product: false,
@@ -70,16 +78,18 @@ const _getProductFavoriteDataAsync = (uid, pid, bid) => { @@ -70,16 +78,18 @@ const _getProductFavoriteDataAsync = (uid, pid, bid) => {
70 return result; 78 return result;
71 } 79 }
72 80
73 - if (pid) {  
74 - let productData = yield favoriteProductService.isFavoriteAsync(uid, pid); 81 + let requestApi = {};
75 82
76 - result.product = productData.code === 200 && productData.data ? true : false; 83 + if (pid) {
  84 + requestApi.product = favoriteProductService.isFavoriteAsync(uid, pid);
77 } 85 }
78 86
79 - if (bid) {  
80 - let brandData = yield favoriteBrandService.isFavoriteAsync(uid, bid); 87 + let requestData = yield Promise.props(requestApi);
  88 +
  89 + let productData = requestData.product;
81 90
82 - result.brand = brandData.code && brandData.code === 200 ? true : false; 91 + if (productData) {
  92 + result.product = productData.code === 200 && productData.data ? true : false;
83 } 93 }
84 94
85 return result; 95 return result;
@@ -162,7 +172,7 @@ const _getVipDataByProductBaseInfo = (data, vipLevel, uid) => { @@ -162,7 +172,7 @@ const _getVipDataByProductBaseInfo = (data, vipLevel, uid) => {
162 const _getProductActivityBanner = (additionalData) => { 172 const _getProductActivityBanner = (additionalData) => {
163 let data = additionalData.productBanner; 173 let data = additionalData.productBanner;
164 174
165 - if (_.isElement(data) || _.get(data, 'code', 400) !== 200 || !_.get(data, 'data.bannerImg')) { 175 + if (_.isEmpty(data) || _.get(data, 'code', 400) !== 200 || !_.get(data, 'data.bannerImg')) {
166 return {}; 176 return {};
167 } 177 }
168 178
@@ -191,20 +201,10 @@ const _getActivityDataByProductBaseInfo = (data, additionalData) => { @@ -191,20 +201,10 @@ const _getActivityDataByProductBaseInfo = (data, additionalData) => {
191 }; 201 };
192 202
193 /** 203 /**
194 - * 获取商品咨询和评论数据  
195 - * @param data  
196 - */  
197 -const _getConsultCommentDataByProductInfo = () => {  
198 - return {  
199 - commentUrl: helpers.urlFormat('/home/comment')  
200 - };  
201 -};  
202 -  
203 -/**  
204 * 获取品牌数据 204 * 获取品牌数据
205 */ 205 */
206 const _getBrandDataByProductBaseInfo = (data, additionalData) => { 206 const _getBrandDataByProductBaseInfo = (data, additionalData) => {
207 - if (!data.brand_info) { 207 + if (!_.get(data, 'brand_info')) {
208 return {}; 208 return {};
209 } 209 }
210 210
@@ -212,7 +212,7 @@ const _getBrandDataByProductBaseInfo = (data, additionalData) => { @@ -212,7 +212,7 @@ const _getBrandDataByProductBaseInfo = (data, additionalData) => {
212 let bgImg = ''; 212 let bgImg = '';
213 let logo = ''; 213 let logo = '';
214 let bannerInfo = null; 214 let bannerInfo = null;
215 - let result = additionalData.bannerInfo; 215 + let result = _.get(additionalData, 'bannerInfo', {});
216 216
217 if (_.isEmpty(result)) { 217 if (_.isEmpty(result)) {
218 return {}; 218 return {};
@@ -236,7 +236,7 @@ const _getBrandDataByProductBaseInfo = (data, additionalData) => { @@ -236,7 +236,7 @@ const _getBrandDataByProductBaseInfo = (data, additionalData) => {
236 236
237 let homeUrl = 'javascript:void(0)'; // eslint-disable-line no-script-url 237 let homeUrl = 'javascript:void(0)'; // eslint-disable-line no-script-url
238 238
239 - if (data.brand_info.brand_domain) { 239 + if (_.get(data, 'brand_info.brand_domain')) {
240 homeUrl = helpers.urlFormat('', null, data.brand_info.brand_domain); 240 homeUrl = helpers.urlFormat('', null, data.brand_info.brand_domain);
241 } 241 }
242 242
@@ -262,7 +262,7 @@ const _getSkuDataByProductBaseInfo = (data) => { @@ -262,7 +262,7 @@ const _getSkuDataByProductBaseInfo = (data) => {
262 let defaultImage = '';// 默认图 262 let defaultImage = '';// 默认图
263 let chooseSkuFlag = false; // 选中状态 263 let chooseSkuFlag = false; // 选中状态
264 264
265 - if (!data.goods_list) { 265 + if (_.isEmpty(_.get(data, 'goods_list', []))) {
266 return { 266 return {
267 totalStorageNum, 267 totalStorageNum,
268 skuGoods, 268 skuGoods,
@@ -475,25 +475,17 @@ function _getSortNavAsync(smallSortId, gender) { @@ -475,25 +475,17 @@ function _getSortNavAsync(smallSortId, gender) {
475 } 475 }
476 476
477 // 保存在 gids 和 skns ,最近流览功能 477 // 保存在 gids 和 skns ,最近流览功能
478 -const saveRecentGoodInCookies = (oldGids, oldSkns, res, addGids, addSkns) => {  
479 - oldGids = (oldGids || '').split(',');  
480 - oldSkns = (oldSkns || '').split(',');  
481 - addSkns = `${addSkns}-${addGids}`; 478 +const saveRecentGoodInCookies = (oldSkns, res, addSkns) => {
482 479
483 - _.remove(oldGids, addGids);  
484 - _.remove(oldSkns, addSkns); 480 + oldSkns = oldSkns ? oldSkns.split(',') : [];
485 481
486 - oldGids.unshift(addGids);  
487 - oldSkns.unshift(addSkns); 482 + oldSkns = _.reject(oldSkns, old => old === String(addSkns) ? true : false);
488 483
489 - res.cookie('_browse', oldGids.splice(0, 30).join(','), {  
490 - maxAge: 2000000000,  
491 - domain: 'yohobuy.com'  
492 - }); 484 + oldSkns.unshift(addSkns);
493 485
494 res.cookie('_browseskn', oldSkns.splice(0, 30).join(','), { 486 res.cookie('_browseskn', oldSkns.splice(0, 30).join(','), {
495 maxAge: 2000000000, 487 maxAge: 2000000000,
496 - domain: 'yohobuy.com' 488 + domain: '.yohobuy.com'
497 }); 489 });
498 }; 490 };
499 491
@@ -948,7 +940,7 @@ const _getSizeAttrByMaxSortId = (maxSortId, sizeList) => { @@ -948,7 +940,7 @@ const _getSizeAttrByMaxSortId = (maxSortId, sizeList) => {
948 * @param maxSortId 940 * @param maxSortId
949 * @return object 941 * @return object
950 */ 942 */
951 -const _getSizeInfo = (productSkn, maxSortId, additionalData)=> { 943 +const _getIntroInfo = (productSkn, maxSortId, additionalData)=> {
952 if (!productSkn) { 944 if (!productSkn) {
953 return {}; 945 return {};
954 } 946 }
@@ -1004,17 +996,15 @@ const _getSizeInfo = (productSkn, maxSortId, additionalData)=> { @@ -1004,17 +996,15 @@ const _getSizeInfo = (productSkn, maxSortId, additionalData)=> {
1004 * @param array $navs 996 * @param array $navs
1005 * @return array 997 * @return array
1006 */ 998 */
1007 -const _getSeoByGoodsInfo = function(goodsInfo, navs) { 999 +const _getSeoByGoodsInfo = (goodsInfo, navs) => {
1008 let title = ''; 1000 let title = '';
1009 - let keywords = '';  
1010 let brandName = ''; 1001 let brandName = '';
1011 let sortName = ''; 1002 let sortName = '';
1012 - let description = '';  
1013 1003
1014 goodsInfo = goodsInfo || {}; 1004 goodsInfo = goodsInfo || {};
1015 navs = navs || []; 1005 navs = navs || [];
1016 1006
1017 - if (!_.isEmpty(goodsInfo.brandName)) { 1007 + if (goodsInfo.brandName) {
1018 title = goodsInfo.brandName + ' '; 1008 title = goodsInfo.brandName + ' ';
1019 brandName = goodsInfo.brandName; 1009 brandName = goodsInfo.brandName;
1020 } 1010 }
@@ -1025,9 +1015,9 @@ const _getSeoByGoodsInfo = function(goodsInfo, navs) { @@ -1025,9 +1015,9 @@ const _getSeoByGoodsInfo = function(goodsInfo, navs) {
1025 } 1015 }
1026 1016
1027 title += goodsInfo.name + '正品 '; 1017 title += goodsInfo.name + '正品 ';
1028 - keywords = brandName + sortName + ',' + brandName + '官网专卖店,' + brandName + '官方授权店,' + 1018 + let keywords = brandName + sortName + ',' + brandName + '官网专卖店,' + brandName + '官方授权店,' +
1029 brandName + '正品,' + brandName + '打折,' + brandName + '折扣店,' + brandName + '真品,' + brandName + '代购'; 1019 brandName + '正品,' + brandName + '打折,' + brandName + '折扣店,' + brandName + '真品,' + brandName + '代购';
1030 - description = !goodsInfo.shareDesc ? goodsInfo.name : goodsInfo.shareDesc; 1020 + let description = !goodsInfo.shareDesc ? goodsInfo.name : goodsInfo.shareDesc;
1031 1021
1032 return { 1022 return {
1033 title: title, 1023 title: title,
@@ -1041,12 +1031,22 @@ const _getSeoByGoodsInfo = function(goodsInfo, navs) { @@ -1041,12 +1031,22 @@ const _getSeoByGoodsInfo = function(goodsInfo, navs) {
1041 * @param origin Object 原始数据 1031 * @param origin Object 原始数据
1042 * @return result Object 格式化数据 1032 * @return result Object 格式化数据
1043 */ 1033 */
1044 -const _detailDataPkg = (origin, uid, vipLevel) => { 1034 +const _detailDataPkg = (origin, uid, vipLevel, cookies) => {
1045 return co(function*() { 1035 return co(function*() {
1046 - let result = {}; // 结果输出  
1047 - let md5 = origin.md5; 1036 + if (_.isEmpty(origin) || _.isEmpty(origin.data)) {
  1037 + return {};
  1038 + }
  1039 +
  1040 + let result = {};
  1041 +
  1042 + result.md5 = origin.md5;// 用于前端数据变化的对比
1048 1043
1049 origin = origin.data; 1044 origin = origin.data;
  1045 +
  1046 + if (uid) {
  1047 + origin.uid = uid;
  1048 + }
  1049 +
1050 let propOrigin = _.partial(_.get, origin); 1050 let propOrigin = _.partial(_.get, origin);
1051 1051
1052 // 商品名称 1052 // 商品名称
@@ -1054,34 +1054,51 @@ const _detailDataPkg = (origin, uid, vipLevel) => { @@ -1054,34 +1054,51 @@ const _detailDataPkg = (origin, uid, vipLevel) => {
1054 return result; 1054 return result;
1055 } 1055 }
1056 1056
1057 - origin.uid = uid;  
1058 result.name = propOrigin('product_name'); 1057 result.name = propOrigin('product_name');
1059 result.skn = propOrigin('product_skn'); 1058 result.skn = propOrigin('product_skn');
1060 result.productId = propOrigin('product_id'); 1059 result.productId = propOrigin('product_id');
1061 - result.md5 = md5; // 用于前端数据变化的对比  
1062 1060
1063 result.maxSortId = propOrigin('maxSortId', ''); 1061 result.maxSortId = propOrigin('maxSortId', '');
1064 result.smallSortId = propOrigin('smallSortId', ''); 1062 result.smallSortId = propOrigin('smallSortId', '');
1065 1063
1066 result.goCartUrl = helpers.urlFormat('/shopping/cart'); 1064 result.goCartUrl = helpers.urlFormat('/shopping/cart');
1067 1065
1068 - let brandId = propOrigin('brand_info.brand_id', 0);  
1069 - let requestData = yield Promise.all([  
1070 - _getProductAdditionInfoAsync(origin), // 预处理所有的数据  
1071 - _getProductFavoriteDataAsync(uid, result.productId, brandId), // 处理收藏喜欢数据  
1072 - productAPI.getPromotionAsync(result.skn)  
1073 - ]); 1066 + let requestApi = {
  1067 + addition: _getProductAdditionInfoAsync(origin), // 预处理所有的数据
  1068 + fav: _getProductFavoriteDataAsync(uid, result.productId), // 处理收藏喜欢数据
  1069 + promotion: productAPI.getPromotionAsync(result.skn) // 打折信息
  1070 + };
  1071 +
  1072 + if (propOrigin('isLimitBuy', false) && propOrigin('limitProductCode', '')) {
  1073 + requestApi.limited = productAPI.getLimitedProductStatusAsync(
  1074 + propOrigin('limitProductCode'),
  1075 + uid,
  1076 + result.skn
  1077 + ); // 限购商品的状态
  1078 + }
  1079 +
  1080 + if (propOrigin('brand_info', '')) {
  1081 + requestApi.brand =
  1082 + brandService.getBrandByDomainAsync(propOrigin('brand_info.brand_domain')); // 品牌信息
  1083 + }
  1084 +
  1085 + let requestData = yield Promise.props(requestApi);
1074 1086
1075 - let additionalData = requestData[0];  
1076 - let favoriteData = requestData[1];  
1077 - let promotionData = requestData[2]; 1087 + let additionalData = requestData.addition;
  1088 + let favoriteData = requestData.fav;
  1089 + let promotionData = requestData.promotion;
  1090 + let limitedInfo = requestData.limited;
  1091 + let domainBrand = requestData.brand;
1078 1092
1079 // 商品标签 1093 // 商品标签
1080 result.tags = _getTagsDataByProductInfo(origin); 1094 result.tags = _getTagsDataByProductInfo(origin);
1081 1095
1082 - // 商品促销短语 1096 + // 商品促销短语
1083 result.saleTip = propOrigin('sales_phrase', ''); 1097 result.saleTip = propOrigin('sales_phrase', '');
1084 1098
  1099 + // 是否收藏
  1100 + result.isCollect = favoriteData.product;
  1101 +
1085 // 商品价格 1102 // 商品价格
1086 result.marketPrice = propOrigin('format_market_price'); 1103 result.marketPrice = propOrigin('format_market_price');
1087 result.salePrice = propOrigin('format_sales_price'); 1104 result.salePrice = propOrigin('format_sales_price');
@@ -1127,20 +1144,6 @@ const _detailDataPkg = (origin, uid, vipLevel) => { @@ -1127,20 +1144,6 @@ const _detailDataPkg = (origin, uid, vipLevel) => {
1127 result.hasOtherPrice = false; 1144 result.hasOtherPrice = false;
1128 } 1145 }
1129 1146
1130 - // 商品咨询和评论数据,当前为空  
1131 - let consultComment = _getConsultCommentDataByProductInfo();  
1132 -  
1133 - // 品牌信息  
1134 - let banner = {};  
1135 -  
1136 - if (propOrigin('brand_info', '')) {  
1137 - result.brandImg = helpers.image(propOrigin('brand_info.brand_ico', ''), 47, 47);  
1138 - result.brandName = propOrigin('brand_info.brand_name', '');  
1139 - result.brandUrl = helpers.urlFormat('', null, propOrigin('brand_info.brand_domain'));  
1140 - banner = _getBrandDataByProductBaseInfo(origin, additionalData);  
1141 - banner.isCollect = favoriteData.brand;  
1142 - }  
1143 -  
1144 // sku商品信息 1147 // sku商品信息
1145 let skuData = _getSkuDataByProductBaseInfo(origin); 1148 let skuData = _getSkuDataByProductBaseInfo(origin);
1146 1149
@@ -1148,15 +1151,13 @@ const _detailDataPkg = (origin, uid, vipLevel) => { @@ -1148,15 +1151,13 @@ const _detailDataPkg = (origin, uid, vipLevel) => {
1148 result.colors = skuData.skuGoods; 1151 result.colors = skuData.skuGoods;
1149 let totalStorageNum = skuData.totalStorageNum; 1152 let totalStorageNum = skuData.totalStorageNum;
1150 1153
1151 - // 是否收藏  
1152 - result.isCollect = favoriteData.product;  
1153 -  
1154 - if (propOrigin('isLimitBuy', false)) { 1154 + // 限购商品
  1155 + if (limitedInfo && limitedInfo.code === 200 && _.get(limitedInfo, 'data.isLimitBuy', false) === true) {
1155 // 是否开售 1156 // 是否开售
1156 - let isBeginSale = propOrigin('saleStatus', 0) === 1; 1157 + let isBeginSale = _.get(limitedInfo, 'data.saleStatus', 0) === 1;
1157 1158
1158 // 限购商品有关的展示状态 1159 // 限购商品有关的展示状态
1159 - let showStatus = propOrigin('showStatus', 1); 1160 + let showStatus = _.get(limitedInfo, 'data.showStatus', 1);
1160 1161
1161 let fashTopGoods = _getFashionTopGoodsStatus(uid, showStatus, isBeginSale); 1162 let fashTopGoods = _getFashionTopGoodsStatus(uid, showStatus, isBeginSale);
1162 1163
@@ -1177,6 +1178,7 @@ const _detailDataPkg = (origin, uid, vipLevel) => { @@ -1177,6 +1178,7 @@ const _detailDataPkg = (origin, uid, vipLevel) => {
1177 } 1178 }
1178 } 1179 }
1179 1180
  1181 + // 商品购买状态
1180 let soldOut = !!(propOrigin('status') === 0 || totalStorageNum === 0); 1182 let soldOut = !!(propOrigin('status') === 0 || totalStorageNum === 0);
1181 let notForSale = propOrigin('attribute') === 2; // 非卖品 1183 let notForSale = propOrigin('attribute') === 2; // 非卖品
1182 let virtualGoods = propOrigin('attribute') === 3; // 虚拟商品 1184 let virtualGoods = propOrigin('attribute') === 3; // 虚拟商品
@@ -1244,44 +1246,62 @@ const _detailDataPkg = (origin, uid, vipLevel) => { @@ -1244,44 +1246,62 @@ const _detailDataPkg = (origin, uid, vipLevel) => {
1244 statGoodsInfo.smallSortId = result.smallSortId; 1246 statGoodsInfo.smallSortId = result.smallSortId;
1245 statGoodsInfo.soldOut = soldOut ? 1 : 0; 1247 statGoodsInfo.soldOut = soldOut ? 1 : 0;
1246 1248
1247 - if (banner.brandId) {  
1248 - let domainBrand = yield brandService.getBrandByDomainAsync(banner.brandDomain); 1249 + // 商品的品牌信息
  1250 + let bandInfo = {};
  1251 +
  1252 + if (propOrigin('brand_info', '')) {
  1253 + result.brandImg = helpers.image(propOrigin('brand_info.brand_ico', ''), 47, 47);
  1254 + result.brandName = propOrigin('brand_info.brand_name', '');
  1255 + result.brandUrl = helpers.urlFormat('', null, propOrigin('brand_info.brand_domain'));
  1256 + bandInfo = _getBrandDataByProductBaseInfo(origin, additionalData);
  1257 + bandInfo.isCollect = favoriteData.brand;
1249 1258
1250 if (domainBrand.type && domainBrand.shopId) { 1259 if (domainBrand.type && domainBrand.shopId) {
1251 let type = _.parseInt(domainBrand.type); 1260 let type = _.parseInt(domainBrand.type);
1252 1261
1253 if (type === 1) { 1262 if (type === 1) {
1254 // 多品店不显示 1263 // 多品店不显示
1255 - banner = {}; 1264 + bandInfo = {};
1256 } else if (type === 2) { 1265 } else if (type === 2) {
1257 // 单品店显示新版的店铺banner 1266 // 单品店显示新版的店铺banner
1258 let basisData = yield shopService.basisTemplateAsync(domainBrand.shopId); 1267 let basisData = yield shopService.basisTemplateAsync(domainBrand.shopId);
1259 1268
1260 - banner.bgImg = basisData.shopTopBanner.banner || banner.bgImg; 1269 + bandInfo.bgImg = basisData.shopTopBanner.banner || bandInfo.bgImg;
1261 } 1270 }
1262 } 1271 }
1263 } 1272 }
1264 1273
  1274 + // 最近浏览功能 ,限量商品不加入到最近浏览
  1275 + if (!_.has(result, 'fashionTopGoods')) {
  1276 + cookies && cookies(_.get(result, 'skn', ''));
  1277 + }
1265 1278
1266 - // 获取商品尺寸相关  
1267 - let sizeInfo = _getSizeInfo(result, result.maxSortId, additionalData);  
1268 -  
1269 - return Object.assign({ 1279 + return {
1270 goodsInfo: result, 1280 goodsInfo: result,
1271 - consultComment: consultComment,  
1272 - banner: _.isEmpty(banner) ? null : banner, 1281 + banner: _.isEmpty(bandInfo) ? null : bandInfo,
1273 statGoodsInfo: statGoodsInfo 1282 statGoodsInfo: statGoodsInfo
1274 - }, sizeInfo); 1283 +
  1284 + };
1275 })(); 1285 })();
1276 }; 1286 };
1277 1287
1278 -const getDetailHeader = (pid, uid, isStudent, vipLevel, dataMd5) => {  
1279 - let currentUserProductInfo = _.partial(_detailDataPkg, _, uid, vipLevel); 1288 +/**
  1289 + * 获得商品价格,活动等数据
  1290 + */
  1291 +const getDetailHeader = (pid, uid, isStudent, vipLevel, dataMd5, cookie) => {
  1292 + let currentUserProductInfo = _.partial(_detailDataPkg, _, uid, vipLevel, cookie);
1280 1293
1281 return productAPI.getProductAsync(pid, uid, isStudent, vipLevel) 1294 return productAPI.getProductAsync(pid, uid, isStudent, vipLevel)
1282 - .then(result => currentUserProductInfo(result)) 1295 + .then(currentUserProductInfo)
1283 .then((result) => { 1296 .then((result) => {
1284 - if (result.goodsInfo.md5 !== dataMd5 || uid) { 1297 + if (_.isEmpty(result) || !_.get(result, 'goodsInfo.md5')) {
  1298 + return {
  1299 + code: 204, // 没有改变数据
  1300 + data: {}
  1301 + };
  1302 + }
  1303 +
  1304 + if (_.get(result, 'goodsInfo.md5') !== dataMd5 || uid) {
1285 return { 1305 return {
1286 code: 200, // 改变数据 1306 code: 200, // 改变数据
1287 data: result 1307 data: result
@@ -1296,60 +1316,81 @@ const getDetailHeader = (pid, uid, isStudent, vipLevel, dataMd5) => { @@ -1296,60 +1316,81 @@ const getDetailHeader = (pid, uid, isStudent, vipLevel, dataMd5) => {
1296 }; 1316 };
1297 1317
1298 /** 1318 /**
  1319 + * 是否支持退换货,true 支持,false 不支持
  1320 + */
  1321 +const saleReturn = (skn) => {
  1322 + return productAPI.isSupportReturnedSale(skn).then(result => _.get(result, `data.${skn}`, 'N') === 'N' ? 'Y' : 'N');
  1323 +};
  1324 +
  1325 +/**
1299 * 获取某一个商品详情主页面 1326 * 获取某一个商品详情主页面
1300 */ 1327 */
1301 const showMainAsync = (data) => { 1328 const showMainAsync = (data) => {
1302 return co(function * () { 1329 return co(function * () {
1303 - let result = {};  
1304 - let currentUserProductInfo = _.partial(_detailDataPkg, _, data.uid, data.vipLevel); 1330 + // 获取商品基本信息
  1331 + let productData = yield productAPI.getProductAsync(data.pid, data.uid, data.isStudent, data.vipLevel);
1305 1332
1306 - // 获取商品信息  
1307 - let productInfo = yield productAPI.getProductAsync(data.pid, data.uid, data.isStudent, data.vipLevel)  
1308 - .then(currentUserProductInfo);  
1309 -  
1310 - if (_.isEmpty(productInfo) || _.isEmpty(productInfo.goodsInfo)) { 1333 + if (_.isEmpty(productData.data)) {
1311 return Promise.reject({ 1334 return Promise.reject({
1312 - code: 404 1335 + code: 404,
  1336 + message: 'app.product.data api wrong'
1313 }); 1337 });
1314 } 1338 }
1315 1339
  1340 + let smallSortId = _.get(productData, 'data.smallSortId');
  1341 + let maxSortId = _.get(productData, 'data.maxSortId');
  1342 + let productId = _.get(productData, 'data.product_id');
  1343 + let productSkn = _.get(productData, 'data.product_skn');
  1344 + let curUserProduct = _.partial(_detailDataPkg, _, data.uid, data.vipLevel, data.gid, data.saveInCookies);
  1345 +
1316 let requestData = yield Promise.all([ 1346 let requestData = yield Promise.all([
1317 - _getSortNavAsync(productInfo.goodsInfo.smallSortId, data.gender),  
1318 - HeaderModel.requestHeaderData(data.channel),  
1319 - productAPI.isSupportReturnedSale(productInfo.goodsInfo.skn) 1347 + _getSortNavAsync(smallSortId, data.gender), // 面包屑导航
  1348 + HeaderModel.requestHeaderData(data.channel), // 通用头部数据
  1349 + _getProductIntroAsync(productId, productSkn), // 商品详细介绍
  1350 + curUserProduct(productData) // 商品详细价格
1320 ]); 1351 ]);
1321 1352
1322 - // 分类导航 ,seo  
1323 - let navs = requestData[0];  
1324 - let seo = _getSeoByGoodsInfo(productInfo.goodsInfo, navs);  
1325 -  
1326 - result.seo = seo;  
1327 -  
1328 - // 最近浏览功能 ,限量商品不能使用这个功能  
1329 - if (!_.has(productInfo, 'goodsInfo.fashionTopGoods')) {  
1330 - data.saveInCookies(data.gid, _.get(productInfo, 'goodsInfo.skn', ''));  
1331 - } 1353 + let smallSortNavigator = requestData[0];
  1354 + let navigatorHeader = requestData[1];
  1355 + let productDescription = requestData[2];
  1356 + let productInfo = requestData[3];
1332 1357
  1358 + // 拼装数据
  1359 + let result = {};
1333 1360
1334 - result.headerData = requestData[1].headerData; 1361 + // 商品价格
1335 result.productDetailPage = true; 1362 result.productDetailPage = true;
1336 result.detail = productInfo; 1363 result.detail = productInfo;
1337 - result.statGoodsInfo = Object.assign({fullSortName: navs.map(x => x.name).join('-')}, 1364 +
  1365 + // 商品介绍
  1366 + let intro = _getIntroInfo(productSkn, maxSortId, productDescription);
  1367 +
  1368 + result.deatil = Object.assign(result.detail, intro);
  1369 +
  1370 + // seo
  1371 + result.seo = _getSeoByGoodsInfo(productInfo.goodsInfo, smallSortNavigator);
  1372 +
  1373 + // 商品页面统计
  1374 + result.statGoodsInfo = Object.assign({fullSortName: smallSortNavigator.map(x => x.name).join('-')},
1338 productInfo.statGoodsInfo 1375 productInfo.statGoodsInfo
1339 ); 1376 );
1340 1377
1341 - // 是否支持退换货,true 支持,false 不支持  
1342 - result.detail.supportSaleReturnedService =  
1343 - _.get(requestData, `[2].data.${productInfo.goodsInfo.skn}`, 'N') === 'N';  
1344 -  
1345 - // 导航 1378 + // 面包屑导航
1346 result.detail.pathNav = _.concat( 1379 result.detail.pathNav = _.concat(
1347 homeService.getHomeChannelNav(data.channel), 1380 homeService.getHomeChannelNav(data.channel),
1348 - navs,  
1349 - [{name: productInfo.goodsInfo.name}] 1381 + smallSortNavigator,
  1382 + [{name: _.get(productInfo, 'goodsInfo.name')}]
1350 ); 1383 );
1351 1384
  1385 + // 头部数据
  1386 + result.headerData = navigatorHeader.headerData;
  1387 +
  1388 + // 咨询和评论
  1389 + result.detail.consultComment = true;
  1390 +
  1391 + // 最近浏览,最多5条记录
1352 result.detail.latestWalk = 5; 1392 result.detail.latestWalk = 5;
  1393 +
1353 return result; 1394 return result;
1354 })(); 1395 })();
1355 }; 1396 };
@@ -1362,5 +1403,6 @@ module.exports = { @@ -1362,5 +1403,6 @@ module.exports = {
1362 showMainAsync: showMainAsync, // 获取某一个商品详情主页面 1403 showMainAsync: showMainAsync, // 获取某一个商品详情主页面
1363 indexHotAreaAsync: hotAreaService.indexAsync, // 获取某一个商品的热区数据 1404 indexHotAreaAsync: hotAreaService.indexAsync, // 获取某一个商品的热区数据
1364 saveRecentGoodInCookies, // 保存最近的商品 1405 saveRecentGoodInCookies, // 保存最近的商品
1365 - getDetailHeader 1406 + getDetailHeader,
  1407 + saleReturn
1366 }; 1408 };
@@ -492,7 +492,7 @@ const getAdnav = (params) => { @@ -492,7 +492,7 @@ const getAdnav = (params) => {
492 492
493 if (result[1].code === 200 && result[1].data) { 493 if (result[1].code === 200 && result[1].data) {
494 dest.picTitle = brandFolderSeries; 494 dest.picTitle = brandFolderSeries;
495 - Object.assign(dest.list, searchHandler.handleFolderData(result[0].data)); 495 + Object.assign(dest.list, searchHandler.handleSeriesData(result[1].data));
496 } 496 }
497 497
498 498
@@ -516,6 +516,7 @@ const getShopInfo = (shopId, uid) => { @@ -516,6 +516,7 @@ const getShopInfo = (shopId, uid) => {
516 isFavorite: result.data.is_favorite === 'Y', 516 isFavorite: result.data.is_favorite === 'Y',
517 shopTemplateType: result.data.shop_template_type, 517 shopTemplateType: result.data.shop_template_type,
518 multBrandShopType: result.data.mult_brand_shop_type, 518 multBrandShopType: result.data.mult_brand_shop_type,
  519 + shopName: result.data.shop_name,
519 showShopName: result.data.is_show_shop_name === 'Y' 520 showShopName: result.data.is_show_shop_name === 'Y'
520 }; 521 };
521 } else { 522 } else {
@@ -549,7 +550,8 @@ const getShopData = (shopId, channel, params, shopInfo) => { @@ -549,7 +550,8 @@ const getShopData = (shopId, channel, params, shopInfo) => {
549 550
550 Object.assign(finalResult, 551 Object.assign(finalResult,
551 result[0], // 头部数据 552 result[0], // 头部数据
552 - searchHandler.handlePathNavData(shopInfo, params, 'shop', channel) // 面包屑导航 553 + searchHandler.handlePathNavData(shopInfo, params, 'shop', channel), // 面包屑导航
  554 + shopHandler.setShopSeo(shopInfo.shopName || shopInfo.brandName) // 店铺SEO
553 ); 555 );
554 556
555 _.set(finalResult, 'headerData.header', true); 557 _.set(finalResult, 'headerData.header', true);
@@ -786,6 +788,9 @@ const getShopListData = (channel, params, uid) => { @@ -786,6 +788,9 @@ const getShopListData = (channel, params, uid) => {
786 isFavorite: data.is_favorite === 'Y', 788 isFavorite: data.is_favorite === 'Y',
787 brandCont: data.shop_intro || '' 789 brandCont: data.shop_intro || ''
788 }); 790 });
  791 +
  792 + // 店铺SEO
  793 + Object.assign(finalResult, shopHandler.setShopSeo(data.shop_name));
789 } 794 }
790 } else { 795 } else {
791 return Promise.reject('No ShopDecorator data'); 796 return Promise.reject('No ShopDecorator data');
@@ -897,6 +902,7 @@ const getBaseShopData = (params, extra, channel, shopId) => { @@ -897,6 +902,7 @@ const getBaseShopData = (params, extra, channel, shopId) => {
897 shopIntro: `/about?shopId=${shopId}`, 902 shopIntro: `/about?shopId=${shopId}`,
898 coled: _.get(result[2], 'data.is_favorite', 'N') === 'Y' 903 coled: _.get(result[2], 'data.is_favorite', 'N') === 'Y'
899 }); 904 });
  905 +
900 _.set(resData, 'brand.shopBanner', decorator.shopTopBannerBase); 906 _.set(resData, 'brand.shopBanner', decorator.shopTopBannerBase);
901 _.unset(resData, 'brand.brandBanner'); 907 _.unset(resData, 'brand.brandBanner');
902 908
@@ -909,10 +915,14 @@ const getBaseShopData = (params, extra, channel, shopId) => { @@ -909,10 +915,14 @@ const getBaseShopData = (params, extra, channel, shopId) => {
909 if (result[2].code === 200) { 915 if (result[2].code === 200) {
910 let shopName = _.get(result[2], 'data.shop_name', ''); 916 let shopName = _.get(result[2], 'data.shop_name', '');
911 917
  918 + _.set(resData, 'brand.shopBanner.shopName', shopName);
912 _.set(resData, 'brand.pathNav[2]', { 919 _.set(resData, 'brand.pathNav[2]', {
913 name: shopName, 920 name: shopName,
914 pathTitle: shopName 921 pathTitle: shopName
915 }); 922 });
  923 +
  924 + // 店铺SEO
  925 + Object.assign(resData, shopHandler.setShopSeo(shopName));
916 } 926 }
917 927
918 // 临时删除seo信息 928 // 临时删除seo信息
@@ -731,9 +731,9 @@ exports.handlePagerData = (total, params) => { @@ -731,9 +731,9 @@ exports.handlePagerData = (total, params) => {
731 pages: [] 731 pages: []
732 }; 732 };
733 733
734 - let currentPage = parseInt((_.isEmpty(params.page) ? 1 : params.page), 10); // 当前页  
735 - let perPageCount = parseInt((_.isEmpty(params.limit) ? 60 : params.limit), 10); // 每页商品数  
736 - let totalPage = parseInt(total / perPageCount, 10) + 1; // 总页数 734 + let currentPage = parseInt(_.get(params, 'page', 1), 10); // 当前页
  735 + let perPageCount = parseInt(_.get(params, 'limit', 60), 10); // 每页商品数
  736 + let totalPage = Math.ceil(total / perPageCount); // 总页数
737 737
738 if (currentPage === 1) { 738 if (currentPage === 1) {
739 // 当前页为 1,一定没有上一页 739 // 当前页为 1,一定没有上一页
@@ -773,11 +773,14 @@ exports.handlePagerData = (total, params) => { @@ -773,11 +773,14 @@ exports.handlePagerData = (total, params) => {
773 } 773 }
774 } else if (currentPage > totalPage - 2) { 774 } else if (currentPage > totalPage - 2) {
775 for (let i = totalPage; i >= totalPage - 4; i--) { 775 for (let i = totalPage; i >= totalPage - 4; i--) {
776 - pages.push({  
777 - url: handleFilterUrl(params, {page: i}),  
778 - num: i,  
779 - cur: currentPage === i  
780 - }); 776 +
  777 + if (i > 0) {
  778 + pages.push({
  779 + url: handleFilterUrl(params, {page: i}),
  780 + num: i,
  781 + cur: currentPage === i
  782 + });
  783 + }
781 } 784 }
782 pages = _.sortBy(pages, ['num']); 785 pages = _.sortBy(pages, ['num']);
783 } 786 }
@@ -795,7 +798,7 @@ exports.handlePagerData = (total, params) => { @@ -795,7 +798,7 @@ exports.handlePagerData = (total, params) => {
795 num: '...' 798 num: '...'
796 }); 799 });
797 } 800 }
798 - if (currentPage < totalPage - 2) { 801 + if (currentPage < totalPage - 2 && totalPage > 5) {
799 nextPages.push({ 802 nextPages.push({
800 num: '...' 803 num: '...'
801 }); 804 });
1 /** 1 /**
2 * Created by TaoHuang on 2016/6/14. 2 * Created by TaoHuang on 2016/6/14.
3 */ 3 */
4 -  
5 -  
6 'use strict'; 4 'use strict';
  5 +const _ = require('lodash');
  6 +const md5 = require('md5');
7 7
8 -const api = global.yoho.SearchAPI;  
9 -  
10 -const yohoApi = global.yoho.API;  
11 - 8 +const api = global.yoho.API;
12 const serviceApi = global.yoho.ServiceAPI; 9 const serviceApi = global.yoho.ServiceAPI;
13 -const _ = require('lodash'); 10 +
14 const helpers = global.yoho.helpers; 11 const helpers = global.yoho.helpers;
15 -const images = require('../../../utils/images.js');  
16 const cache = global.yoho.cache; 12 const cache = global.yoho.cache;
17 const logger = global.yoho.logger; 13 const logger = global.yoho.logger;
18 -const md5 = require('md5');  
19 const config = require('../../../config/common'); 14 const config = require('../../../config/common');
  15 +const images = require('../../../utils/images.js');
20 16
21 const getSortByConditionAsync = (condition) => { 17 const getSortByConditionAsync = (condition) => {
22 - return api.get('sortgroup.json', condition); 18 + return api.get('', Object.assign({
  19 + method: 'web.regular.groupsort'
  20 + }, condition));
23 }; 21 };
24 22
25 // 判断用户是否收藏品牌 23 // 判断用户是否收藏品牌
@@ -40,12 +38,7 @@ const getSearchCackeKey = params => { @@ -40,12 +38,7 @@ const getSearchCackeKey = params => {
40 return md5(ks.join('_')); 38 return md5(ks.join('_'));
41 }; 39 };
42 40
43 -const getProductListOrig = (finalParams) => {  
44 -  
45 - return yohoApi.get('', finalParams).then(result => {  
46 - return result;  
47 - });  
48 -}; 41 +const getProductListOrig = finalParams => api.get('', finalParams);
49 42
50 /** 43 /**
51 * 获取商品列表 44 * 获取商品列表
@@ -104,11 +97,7 @@ const getProductList = (params) => { @@ -104,11 +97,7 @@ const getProductList = (params) => {
104 } 97 }
105 }; 98 };
106 99
107 -const getSortListOrig = (finalParams) => {  
108 - return yohoApi.get('', finalParams).then(ret => {  
109 - return ret;  
110 - });  
111 -}; 100 +const getSortListOrig = (finalParams) => api.get('', finalParams);
112 101
113 /** 102 /**
114 * 获取分类列表 103 * 获取分类列表
@@ -164,7 +153,7 @@ const getSortIntro = (params) => { @@ -164,7 +153,7 @@ const getSortIntro = (params) => {
164 }; 153 };
165 154
166 Object.assign(finalParams, params); 155 Object.assign(finalParams, params);
167 - return yohoApi.get('', finalParams); 156 + return api.get('', finalParams);
168 }; 157 };
169 158
170 /** 159 /**
@@ -177,7 +166,7 @@ const getSortAds = (params) => { @@ -177,7 +166,7 @@ const getSortAds = (params) => {
177 }; 166 };
178 167
179 Object.assign(finalParams, params); 168 Object.assign(finalParams, params);
180 - return yohoApi.get('', finalParams); 169 + return api.get('', finalParams);
181 }; 170 };
182 171
183 /** 172 /**
@@ -192,7 +181,7 @@ const getBrandSeries = (params) => { @@ -192,7 +181,7 @@ const getBrandSeries = (params) => {
192 status: params.status || 1 181 status: params.status || 1
193 }; 182 };
194 183
195 - return yohoApi.get('', finalParams); 184 + return api.get('', finalParams);
196 }; 185 };
197 186
198 /** 187 /**
@@ -207,7 +196,7 @@ const getBrandFolder = (params) => { @@ -207,7 +196,7 @@ const getBrandFolder = (params) => {
207 status: params.status || 1 196 status: params.status || 1
208 }; 197 };
209 198
210 - return yohoApi.get('', finalParams); 199 + return api.get('', finalParams);
211 }; 200 };
212 201
213 /** 202 /**
@@ -221,7 +210,7 @@ const getNodeContent = (params) => { @@ -221,7 +210,7 @@ const getNodeContent = (params) => {
221 node: params.node || '' 210 node: params.node || ''
222 }; 211 };
223 212
224 - return yohoApi.get('', finalParams); 213 + return api.get('', finalParams);
225 }; 214 };
226 215
227 /** 216 /**
@@ -235,7 +224,7 @@ const getWeekNew = (params) => { @@ -235,7 +224,7 @@ const getWeekNew = (params) => {
235 }; 224 };
236 225
237 Object.assign(finalParams, params); 226 Object.assign(finalParams, params);
238 - return yohoApi.get('', finalParams); 227 + return api.get('', finalParams);
239 }; 228 };
240 229
241 /** 230 /**
@@ -266,7 +255,7 @@ const getBrandShop = (query) => { @@ -266,7 +255,7 @@ const getBrandShop = (query) => {
266 if (retObj) { 255 if (retObj) {
267 return retObj; 256 return retObj;
268 } else { 257 } else {
269 - return yohoApi.get('', finalParams).then(ret => { 258 + return api.get('', finalParams).then(ret => {
270 if (ret && ret.code === 200) { 259 if (ret && ret.code === 200) {
271 260
272 cache.set(cKey, ret.data, 3600); 261 cache.set(cKey, ret.data, 3600);
@@ -304,7 +293,7 @@ const getShopsByBrandId = bid => { @@ -304,7 +293,7 @@ const getShopsByBrandId = bid => {
304 if (cdataObj) { 293 if (cdataObj) {
305 return cdataObj; 294 return cdataObj;
306 } else { 295 } else {
307 - return yohoApi.get('', finalParams).then(ret => { 296 + return api.get('', finalParams).then(ret => {
308 if (ret && ret.code === 200) { 297 if (ret && ret.code === 200) {
309 298
310 cache.set(cKey, ret.data, 3600); 299 cache.set(cKey, ret.data, 3600);
@@ -438,7 +427,7 @@ const getSuggest = (params) => { @@ -438,7 +427,7 @@ const getSuggest = (params) => {
438 keyword: params.keyword || '' 427 keyword: params.keyword || ''
439 }; 428 };
440 429
441 - return yohoApi.get('', finalParams); 430 + return api.get('', finalParams);
442 }; 431 };
443 432
444 433
@@ -452,7 +441,7 @@ const getBrandData = (params) => { @@ -452,7 +441,7 @@ const getBrandData = (params) => {
452 domain: params.domain || '' 441 domain: params.domain || ''
453 }; 442 };
454 443
455 - return yohoApi.get('', finalParams); 444 + return api.get('', finalParams);
456 }; 445 };
457 446
458 /** 447 /**
@@ -477,7 +466,7 @@ const getShopInfo = (shopId, uid) => { @@ -477,7 +466,7 @@ const getShopInfo = (shopId, uid) => {
477 uid: uid || 0 466 uid: uid || 0
478 }; 467 };
479 468
480 - return yohoApi.get('', finalParams); 469 + return api.get('', finalParams);
481 470
482 }; 471 };
483 472
@@ -485,15 +474,14 @@ const getShopInfo = (shopId, uid) => { @@ -485,15 +474,14 @@ const getShopInfo = (shopId, uid) => {
485 * 查询店铺下面的所有品牌 474 * 查询店铺下面的所有品牌
486 */ 475 */
487 const getShopBrands = (shopId) => { 476 const getShopBrands = (shopId) => {
488 -  
489 - return yohoApi.get('', {method: 'app.shops.getShopsBrands', shop_id: shopId || 0}); 477 + return api.get('', {method: 'app.shops.getShopsBrands', shop_id: shopId || 0});
490 }; 478 };
491 479
492 /** 480 /**
493 * 查询店铺装修 481 * 查询店铺装修
494 */ 482 */
495 const getShopDecorator = (shopId) => { 483 const getShopDecorator = (shopId) => {
496 - return yohoApi.get('', {method: 'app.shopsdecorator.getList', shop_id: shopId || 0}); 484 + return api.get('', {method: 'app.shopsdecorator.getList', shop_id: shopId || 0});
497 }; 485 };
498 486
499 /** 487 /**
@@ -515,7 +503,7 @@ const getBrands4Filter = (params) => { @@ -515,7 +503,7 @@ const getBrands4Filter = (params) => {
515 method: 'web.regular.aggBrand' 503 method: 'web.regular.aggBrand'
516 }; 504 };
517 505
518 - return yohoApi.get('', Object.assign({}, params, finalParams)); 506 + return api.get('', Object.assign(params, finalParams));
519 }; 507 };
520 508
521 module.exports = { 509 module.exports = {
@@ -1249,7 +1249,7 @@ exports.handleBrandBanner = (data) => { @@ -1249,7 +1249,7 @@ exports.handleBrandBanner = (data) => {
1249 }; 1249 };
1250 1250
1251 /** 1251 /**
1252 - * 处理品牌系列 1252 + * 处理品牌系列folder_id
1253 * @type {[type]} 1253 * @type {[type]}
1254 */ 1254 */
1255 exports.handleFolderData = (data) => { 1255 exports.handleFolderData = (data) => {
@@ -1266,6 +1266,23 @@ exports.handleFolderData = (data) => { @@ -1266,6 +1266,23 @@ exports.handleFolderData = (data) => {
1266 }; 1266 };
1267 1267
1268 /** 1268 /**
  1269 + * 处理品牌系列series
  1270 + * @type {[type]}
  1271 + */
  1272 +exports.handleSeriesData = (data) => {
  1273 + let dest = [];
  1274 +
  1275 + _.forEach(data, (value) => {
  1276 + dest.push({
  1277 + href: '?series=' + value.id,
  1278 + src: value.series_banner
  1279 + });
  1280 + });
  1281 +
  1282 + return dest;
  1283 +};
  1284 +
  1285 +/**
1269 * 筛选类链接处理的对外接口 1286 * 筛选类链接处理的对外接口
1270 * @type {[type]} 1287 * @type {[type]}
1271 */ 1288 */
@@ -16,6 +16,14 @@ const newProductsName = '新品上架 NEW'; @@ -16,6 +16,14 @@ const newProductsName = '新品上架 NEW';
16 const hotProductsName = '人气单品 HOT'; 16 const hotProductsName = '人气单品 HOT';
17 const shopListUrl = '/product/shoplist'; 17 const shopListUrl = '/product/shoplist';
18 18
  19 +exports.setShopSeo = (shopName) => {
  20 + return {
  21 + title: `${shopName} | ${shopName} 潮流服装服饰-Yoho!Buy有货`,
  22 + keywords: `${shopName},${shopName} 服装服饰,${shopName} 潮流服装服饰`,
  23 + description: `${shopName} | Yoho!Buy有货 ${shopName} 潮流服饰官方授权店!100%品牌正品保证,支持货到付款。`
  24 + };
  25 +};
  26 +
19 /** 27 /**
20 * 新品上架 28 * 新品上架
21 */ 29 */
@@ -72,7 +72,7 @@ const _signboard = data => { @@ -72,7 +72,7 @@ const _signboard = data => {
72 }); 72 });
73 73
74 return { 74 return {
75 - title: result[0].title || '', 75 + title: '',
76 list: result 76 list: result
77 }; 77 };
78 }; 78 };
@@ -59,6 +59,9 @@ router.post('/detail/consult', auth, detail.createConsult);// 创建咨询 @@ -59,6 +59,9 @@ router.post('/detail/consult', auth, detail.createConsult);// 创建咨询
59 router.get('/detail/hotarea', detail.indexHotArea);// 商品热区 59 router.get('/detail/hotarea', detail.indexHotArea);// 商品热区
60 router.post('/index/favoriteBrand', favorite.changeFavoriteBrand);// 收藏品牌 60 router.post('/index/favoriteBrand', favorite.changeFavoriteBrand);// 收藏品牌
61 router.post('/item/togglecollect', favorite.collectProduct); // 收藏商品 61 router.post('/item/togglecollect', favorite.collectProduct); // 收藏商品
  62 +router.get('/detail/header', detail.productHeader); // 价格数据重新获取接口
  63 +router.get('/detail/return', detail.detailReturn);// 特殊商品退换货
  64 +router.get('/index/isfav', favorite.isFavoriteBrand);// 品牌收藏状态
62 65
63 // 搜索 66 // 搜索
64 router.get('/search/index', search.index); 67 router.get('/search/index', search.index);
@@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
26 {{> product/standard-content}} 26 {{> product/standard-content}}
27 {{/if}} 27 {{/if}}
28 28
29 - {{> common/latest-walk}} 29 + {{> product/latest-walk}}
30 </div> 30 </div>
31 </div> 31 </div>
32 </div> 32 </div>
@@ -23,7 +23,8 @@ @@ -23,7 +23,8 @@
23 {{> common/path-nav}} 23 {{> common/path-nav}}
24 24
25 {{# goodsInfo}} 25 {{# goodsInfo}}
26 - <div class="main clearfix" data-skn="{{skn}}" data-id="{{productId}}"> 26 + <div class="main clearfix" data-skn="{{skn}}" data-id="{{productId}}" data-md5="{{md5}}"
  27 + data-skn="{{skn}}">
27 <div class="pull-left imgs clearfix"> 28 <div class="pull-left imgs clearfix">
28 <div class="pull-left img"> 29 <div class="pull-left img">
29 <div class="tags clearfix"> 30 <div class="tags clearfix">
@@ -540,12 +541,20 @@ @@ -540,12 +541,20 @@
540 <span class="title cur">商品详情 DETAILS</span> 541 <span class="title cur">商品详情 DETAILS</span>
541 </p> 542 </p>
542 <div id="details-html" class="details-html"> 543 <div id="details-html" class="details-html">
  544 + <div class="lazy-load-object">
  545 + <textarea class="datalazyload" style="visibility: hidden;">
  546 + <script>
  547 + fetchHotArea();
  548 + </script>
  549 + </textarea>
  550 + </div>
543 {{{details}}} 551 {{{details}}}
544 </div> 552 </div>
545 </div> 553 </div>
546 554
547 {{# consultComment}} 555 {{# consultComment}}
548 <div class="consult-comment info-block"> 556 <div class="consult-comment info-block">
  557 +
549 <p class="block-title"> 558 <p class="block-title">
550 <span class="title">顾客咨询(<em class="consult-num">0</em></span> 559 <span class="title">顾客咨询(<em class="consult-num">0</em></span>
551 <span class="sep">|</span> 560 <span class="sep">|</span>
@@ -561,7 +570,7 @@ @@ -561,7 +570,7 @@
561 </span> 570 </span>
562 </p> 571 </p>
563 <p class="btn-wrap"> 572 <p class="btn-wrap">
564 - <a class="btn" href="{{commentUrl}}" target="_blank"> 573 + <a class="btn" href="//www.yohobuy.com/home/comment" target="_blank">
565 <i class="iconfont">&#xe61e;</i> 574 <i class="iconfont">&#xe61e;</i>
566 我要评论 575 我要评论
567 </a> 576 </a>
@@ -599,6 +608,16 @@ @@ -599,6 +608,16 @@
599 </p> 608 </p>
600 </div> 609 </div>
601 </div> 610 </div>
  611 +
  612 + <div class="lazy-load-object">
  613 + <textarea class="datalazyload" style="visibility: hidden;">
  614 + <script>
  615 + fetchComment();
  616 + fetchReturn();
  617 + </script>
  618 + </textarea>
  619 + </div>
  620 +
602 </div> 621 </div>
603 {{/ consultComment}} 622 {{/ consultComment}}
604 623
@@ -635,11 +654,8 @@ @@ -635,11 +654,8 @@
635 </div> 654 </div>
636 </div> 655 </div>
637 656
638 - {{#if supportSaleReturnedService}}  
639 - <div class="support-saleReturned-service"></div>  
640 - {{^}}  
641 - <div class="not-support-saleReturned-service"></div>  
642 - {{/if}} 657 + <div id="saleReturn" class="support-saleReturned-service"></div>
  658 +
643 659
644 <div class="service"></div> 660 <div class="service"></div>
645 {{#if latestWalk}} 661 {{#if latestWalk}}
@@ -649,7 +665,7 @@ @@ -649,7 +665,7 @@
649 <span class="title cur">最近浏览 RECENT REVIEW</span> 665 <span class="title cur">最近浏览 RECENT REVIEW</span>
650 </p> 666 </p>
651 <div id="latest-walk-goods" class="goods clearfix"></div> 667 <div id="latest-walk-goods" class="goods clearfix"></div>
652 - {{> product/latest-walk}} 668 + {{> product/latest-walk-tpl}}
653 </div> 669 </div>
654 {{/if}} 670 {{/if}}
655 </div> 671 </div>
@@ -724,7 +740,8 @@ @@ -724,7 +740,8 @@
724 740
725 (function() { 741 (function() {
726 var mvl = document.createElement('script'); 742 var mvl = document.createElement('script');
727 - mvl.type = 'text/javascript'; mvl.async = true; 743 + mvl.type = 'text/javascript';
  744 + mvl.async = true;
728 mvl.src = ('https:' == document.location.protocol ? 'https://static-ssl.mediav.com/mvl.js' : 'http://static.mediav.com/mvl.js'); 745 mvl.src = ('https:' == document.location.protocol ? 'https://static-ssl.mediav.com/mvl.js' : 'http://static.mediav.com/mvl.js');
729 var s = document.getElementsByTagName('script')[0]; 746 var s = document.getElementsByTagName('script')[0];
730 s.parentNode.insertBefore(mvl, s); 747 s.parentNode.insertBefore(mvl, s);
@@ -732,13 +749,3 @@ @@ -732,13 +749,3 @@
732 </script> 749 </script>
733 {{/statGoodsInfo}} 750 {{/statGoodsInfo}}
734 751
735 -<script type="text/javascript">  
736 - (function() {  
737 - try {  
738 - var timestamp = (new Date()).valueOf();  
739 - var view = document.createElement('img');  
740 - view.src = 'http://shopping.yohobuy.com/1.jpg?t=' + timestamp;  
741 - } catch (e) {  
742 - }  
743 - })();  
744 -</script>  
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 <a name="stu-rights"> 18 <a name="stu-rights">
19 <h2 class="floor-title">学生权益</h2> 19 <h2 class="floor-title">学生权益</h2>
20 </a> 20 </a>
21 - <span id="rights-dia" class="floor-more">更多细则 ></span> 21 + <span id="rights-dia" class="floor-more">全部权益 ></span>
22 </div> 22 </div>
23 <div class="commodity-list clearfix"> 23 <div class="commodity-list clearfix">
24 {{> students/stu-rights}} 24 {{> students/stu-rights}}
1 -{{#if latestWalk}}  
2 - <input id="latest-walk-count" type="hidden" value="{{latestWalk}}">  
3 - <div class="latest-walk">  
4 - <h2>最近浏览的商品</h2>  
5 - <div id="latest-walk-goods" class="goods clearfix"></div>  
6 - </div>  
7 - {{> product/latest-walk-tpl}}  
8 -{{/if}}  
1 -<script id="latest-walk-tpl" type="text/html">  
2 - \{{# latestWalk}}  
3 - <div class="good">  
4 - <a href="\{{href}}" target="_blank">  
5 - <img class="lazy" data-original="\{{img}}">  
6 - </a>  
7 - <a class="name" href="\{{href}}" target="_blank">\{{name}}</a>  
8 - <p class="price">  
9 - <span class="market-price">\{{marketPrice}}</span>  
10 - <span class="sale-price">\{{salePrice}}</span>  
11 - </p>  
12 - </div>  
13 - \{{/ latestWalk}}  
14 -</script>  
1 -<div class="stu-rights clearfix"> 1 +<div id="stuRights" class="stu-rights clearfix">
2 {{#rightsItem}} 2 {{#rightsItem}}
3 <div class="stu-rights-item"> 3 <div class="stu-rights-item">
4 <img class="" src="{{image src 300 300}}"> 4 <img class="" src="{{image src 300 300}}">
5 </img> 5 </img>
6 </div> 6 </div>
7 {{/rightsItem}} 7 {{/rightsItem}}
8 -</div>  
  8 +</div>
@@ -36,8 +36,7 @@ module.exports = { @@ -36,8 +36,7 @@ module.exports = {
36 master: ['127.0.0.1:11211'], 36 master: ['127.0.0.1:11211'],
37 slave: ['127.0.0.1:11211'], 37 slave: ['127.0.0.1:11211'],
38 session: ['127.0.0.1:11211'], 38 session: ['127.0.0.1:11211'],
39 - reconnect: 5000,  
40 - timeout: 100, 39 + timeout: 1000,
41 retries: 0 40 retries: 0
42 }, 41 },
43 interfaceShunt: { 42 interfaceShunt: {
@@ -116,9 +115,8 @@ if (isProduction) { @@ -116,9 +115,8 @@ if (isProduction) {
116 master: ['127.0.0.1:12111'], 115 master: ['127.0.0.1:12111'],
117 slave: ['127.0.0.1:12112'], 116 slave: ['127.0.0.1:12112'],
118 session: ['127.0.0.1:12111'], 117 session: ['127.0.0.1:12111'],
119 - timeout: 100,  
120 - retries: 0,  
121 - reconnect: 5000 118 + timeout: 1000,
  119 + retries: 0
122 } 120 }
123 }); 121 });
124 } 122 }
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 module.exports = app => { 7 module.exports = app => {
8 8
9 // 公共服务 9 // 公共服务
  10 + app.use('/common', require('./apps/common'));
10 11
11 // 业务模块 12 // 业务模块
12 app.use(require('./apps/channel')); // 频道页 13 app.use(require('./apps/channel')); // 频道页
@@ -14,5 +15,7 @@ module.exports = app => { @@ -14,5 +15,7 @@ module.exports = app => {
14 app.use('/product', require('./apps/product')); // 商品相关页面 15 app.use('/product', require('./apps/product')); // 商品相关页面
15 app.use(require('./apps/passport')); // 登录注册 16 app.use(require('./apps/passport')); // 登录注册
16 app.use('/home', require('./apps/home')); // 会员中心 17 app.use('/home', require('./apps/home')); // 会员中心
  18 + app.use('/brands', require('./apps/brands'));
17 app.use('/guang', require('./apps/guang')); 19 app.use('/guang', require('./apps/guang'));
  20 + app.use('/cart', require('./apps/cart'));// 购物车
18 }; 21 };
@@ -52,10 +52,13 @@ const getNavBar = (data, type) => { @@ -52,10 +52,13 @@ const getNavBar = (data, type) => {
52 let obj = {}; 52 let obj = {};
53 let lowEn = _.camelCase(item.sort_name_en).toLowerCase(); 53 let lowEn = _.camelCase(item.sort_name_en).toLowerCase();
54 54
55 - obj.link = item.sort_url;  
56 - obj.cn = item.sort_name;  
57 - obj.en = item.sort_name_en;  
58 - obj.isNewPage = item.is_new_page === 'Y' ? true : false; 55 + Object.assign(obj, {
  56 + type: lowEn,
  57 + link: item.sort_url,
  58 + cn: item.sort_name,
  59 + en: item.sort_name_en,
  60 + isNewPage: item.is_new_page === 'Y'
  61 + });
59 62
60 if (type === lowEn) { 63 if (type === lowEn) {
61 obj.active = true; 64 obj.active = true;
@@ -135,30 +138,38 @@ const getThirdNav = (data) => { @@ -135,30 +138,38 @@ const getThirdNav = (data) => {
135 * @param {String} type 频道类型 138 * @param {String} type 频道类型
136 * @return {array} 子菜单数组 139 * @return {array} 子菜单数组
137 */ 140 */
138 -const getSubNav = (data, type) => {  
139 - let subNav = []; 141 +const getSubNavGroup = (data, type) => {
  142 + let subNavGroup = [];
140 143
141 _.forEach(data, it => { 144 _.forEach(data, it => {
142 - if (type === _.camelCase(it.sort_name_en).toLowerCase()) {  
143 - _.forEach(it.sub, item => {  
144 - let obj = {};  
145 -  
146 - obj.link = item.sort_url;  
147 - obj.name = item.sort_name;  
148 - obj.isHot = item.is_hot === 'Y' ? true : false;  
149 - obj.isNew = item.is_new === 'Y' ? true : false;  
150 -  
151 - if (item.sub) {  
152 - obj.thirdNav = getThirdNav(item.sub);  
153 - obj.imgCode = item.content_code;  
154 - }  
155 -  
156 - subNav.push(obj);  
157 - });  
158 - } 145 + let subNav = [];
  146 +
  147 + _.forEach(it.sub, item => {
  148 + let obj = {};
  149 +
  150 + obj.link = item.sort_url;
  151 + obj.name = item.sort_name;
  152 + obj.isHot = item.is_hot === 'Y' ? true : false;
  153 + obj.isNew = item.is_new === 'Y' ? true : false;
  154 +
  155 + if (item.sub) {
  156 + obj.thirdNav = getThirdNav(item.sub);
  157 + obj.imgCode = item.content_code;
  158 + }
  159 +
  160 + subNav.push(obj);
  161 + });
  162 +
  163 + let lowEn = _.camelCase(it.sort_name_en).toLowerCase();
  164 +
  165 + subNavGroup.push({
  166 + subType: lowEn,
  167 + subNav: subNav,
  168 + active: lowEn === type
  169 + });
159 }); 170 });
160 171
161 - return subNav; 172 + return subNavGroup;
162 }; 173 };
163 174
164 175
@@ -174,7 +185,7 @@ const setHeaderData = (resData, type) => ( @@ -174,7 +185,7 @@ const setHeaderData = (resData, type) => (
174 headType: type, 185 headType: type,
175 yohoGroup: getMenuData(), 186 yohoGroup: getMenuData(),
176 navbars: resData ? getNavBar(resData, type) : [], 187 navbars: resData ? getNavBar(resData, type) : [],
177 - subNav: resData ? getSubNav(resData, type) : [] 188 + subNavGroup: resData ? getSubNavGroup(resData, type) : []
178 } 189 }
179 ); 190 );
180 191
1 {{# headerData}} 1 {{# headerData}}
2 - <div class="yoho-header {{headType}}"> 2 + <div id="yoho-header" class="yoho-header" data-type="{{headType}}">
3 <div class="tool-wrapper clearfix"> 3 <div class="tool-wrapper clearfix">
4 <div class="center-content"> 4 <div class="center-content">
5 <div class="yoho-group-map left"> 5 <div class="yoho-group-map left">
@@ -19,8 +19,8 @@ @@ -19,8 +19,8 @@
19 <ul> 19 <ul>
20 <li id="loginBox"> 20 <li id="loginBox">
21 <span class="hi">Hi~</span> 21 <span class="hi">Hi~</span>
22 - [ <a href="//www.yohobuy.com/signin.html" class="loginbar">请登录</a> ]  
23 - [ <a href="//www.yohobuy.com/reg.html" class="registbar">免费注册</a> ] 22 + [ <a id="signin-url" href="//www.yohobuy.com/signin.html" class="loginbar">请登录</a> ]
  23 + [ <a id="reg-url" href="//www.yohobuy.com/reg.html" class="registbar">免费注册</a> ]
24 </li> 24 </li>
25 <li class="myyoho" id="myYohoBox"> 25 <li class="myyoho" id="myYohoBox">
26 <span class="tag-seprate"></span> 26 <span class="tag-seprate"></span>
@@ -66,7 +66,7 @@ @@ -66,7 +66,7 @@
66 <div class="main-logo"><a href="//www.yohobuy.com/" class="main-link"></a></div> 66 <div class="main-logo"><a href="//www.yohobuy.com/" class="main-link"></a></div>
67 <ul class="main-nav-list"> 67 <ul class="main-nav-list">
68 {{# navbars}} 68 {{# navbars}}
69 - <li {{#if active}} class="cure"{{/if}}{{#if ico}} style="background: url({{image ico 54 32}}) no-repeat center center"{{/if}}> 69 + <li class="{{type}}"{{#if ico}} style="background: url({{image ico 54 32}}) no-repeat center center"{{/if}}>
70 {{#if ico}} 70 {{#if ico}}
71 <a href="{{link}}"{{#if isNewPage}} target="_blank"{{/if}} class="menu-ico"></a> 71 <a href="{{link}}"{{#if isNewPage}} target="_blank"{{/if}} class="menu-ico"></a>
72 {{^}} 72 {{^}}
@@ -80,7 +80,7 @@ @@ -80,7 +80,7 @@
80 </li> 80 </li>
81 {{/ navbars}} 81 {{/ navbars}}
82 </ul> 82 </ul>
83 - <div class="func-area"> 83 + <div class="func-area hide">
84 <ul class="search-suggest"></ul> 84 <ul class="search-suggest"></ul>
85 <div class="search-2016"> 85 <div class="search-2016">
86 <form action="//search.yohobuy.com" method="get" id="search-form"> 86 <form action="//search.yohobuy.com" method="get" id="search-form">
@@ -108,7 +108,8 @@ @@ -108,7 +108,8 @@
108 </div> 108 </div>
109 <div class="nav-wrapper clearfix"> 109 <div class="nav-wrapper clearfix">
110 <div class="center-content"> 110 <div class="center-content">
111 - <ul class="sub-nav-list"> 111 + {{# subNavGroup}}
  112 + <ul class="sub-nav-list {{subType}}">
112 {{# subNav}} 113 {{# subNav}}
113 <li {{#if thirdNav}}class="contain-third"{{/if}}> 114 <li {{#if thirdNav}}class="contain-third"{{/if}}>
114 <a href="{{link}}">{{name}} 115 <a href="{{link}}">{{name}}
@@ -141,6 +142,7 @@ @@ -141,6 +142,7 @@
141 </li> 142 </li>
142 {{/ subNav}} 143 {{/ subNav}}
143 </ul> 144 </ul>
  145 + {{/ subNavGroup}}
144 </div> 146 </div>
145 </div> 147 </div>
146 </div> 148 </div>
@@ -272,4 +274,4 @@ @@ -272,4 +274,4 @@
272 </div> 274 </div>
273 275
274 <input id="api-domain" type="hidden" value="{{apiDomain}}"> 276 <input id="api-domain" type="hidden" value="{{apiDomain}}">
275 -{{/ headerData}}  
  277 +{{/ headerData}}
@@ -11,4 +11,9 @@ @@ -11,4 +11,9 @@
11 </p> 11 </p>
12 </div> 12 </div>
13 \{{/ latestWalk}} 13 \{{/ latestWalk}}
14 -</script>  
  14 +</script>
  15 +<div class="lazy-load-object">
  16 + <textarea class="latest-walk-datalazyload" style="visibility: hidden;">
  17 + <script> fetchLatestWalk(); </script>
  18 + </textarea>
  19 +</div>
@@ -4,5 +4,6 @@ @@ -4,5 +4,6 @@
4 <h2>最近浏览的商品</h2> 4 <h2>最近浏览的商品</h2>
5 <div id="latest-walk-goods" class="goods clearfix"></div> 5 <div id="latest-walk-goods" class="goods clearfix"></div>
6 </div> 6 </div>
  7 +
7 {{> product/latest-walk-tpl}} 8 {{> product/latest-walk-tpl}}
8 -{{/if}}  
  9 +{{/if}}
1 { 1 {
2 "name": "yohobuy-node", 2 "name": "yohobuy-node",
3 - "version": "5.0.4", 3 + "version": "5.0.10",
4 "private": true, 4 "private": true,
5 "description": "A New Yohobuy Project With Express", 5 "description": "A New Yohobuy Project With Express",
6 "repository": { 6 "repository": {
@@ -39,6 +39,7 @@ @@ -39,6 +39,7 @@
39 "express": "^4.13.1", 39 "express": "^4.13.1",
40 "handlebars": "^4.0.5", 40 "handlebars": "^4.0.5",
41 "express-handlebars": "^3.0.0", 41 "express-handlebars": "^3.0.0",
  42 + "express-session": "^1.13.0",
42 "influxdb-winston": "^1.0.1", 43 "influxdb-winston": "^1.0.1",
43 "lodash": "^4.13.1", 44 "lodash": "^4.13.1",
44 "md5": "^2.1.0", 45 "md5": "^2.1.0",
@@ -103,7 +104,6 @@ @@ -103,7 +104,6 @@
103 "yoho-handlebars": "^4.0.5", 104 "yoho-handlebars": "^4.0.5",
104 "yoho-jquery": "^1.12.4", 105 "yoho-jquery": "^1.12.4",
105 "yoho-jquery-lazyload": "^1.9.7", 106 "yoho-jquery-lazyload": "^1.9.7",
106 - "yoho-jquery-pjax": "0.0.1",  
107 "yoho-jquery-placeholder": "^2.3.1", 107 "yoho-jquery-placeholder": "^2.3.1",
108 "yoho-slider": "0.0.2", 108 "yoho-slider": "0.0.2",
109 "yoho-jquery-dotdotdot": "0.0.1", 109 "yoho-jquery-dotdotdot": "0.0.1",
  1 +/**
  2 + * 领券频道
  3 + * @author: 赵彪<bill.zhao@yoho.cn>
  4 + * @date: 2016/04/14
  5 + */
  6 +var $ = require('yoho-jquery'),
  7 + lazyLoad = require('yoho-jquery-lazyload'),
  8 + Dialog = require('../common/dialog').Dialog;
  9 +
  10 +
  11 +var alertConfig,
  12 + makeAlert;
  13 +
  14 +// j面跳转对象
  15 +var redirect = {
  16 +
  17 + // 去逛逛跳转链接
  18 + gunangSrc: null,
  19 +
  20 + // 查看优惠券跳转链接
  21 + checkCouponSrc: null,
  22 + goToGuang: function() {
  23 + window.location.href = this.gunangSrc;
  24 + },
  25 + goToCheck: function() {
  26 + window.location.href = this.checkCouponSrc;
  27 + },
  28 + 200: function(url) {
  29 + this.checkCouponSrc = url;
  30 + }
  31 +};
  32 +
  33 +require('../common');
  34 +require('../plugins/slider');
  35 +
  36 +// 加载底部图片
  37 +lazyLoad($('img.lazy'));
  38 +$('.slide-container').slider();
  39 +
  40 +// 根据配置执行展示弹窗
  41 +function couponAlert(opt) {
  42 + var newAlert = new Dialog(opt);
  43 +
  44 + newAlert.show();
  45 +}
  46 +
  47 +// 配置弹窗
  48 +alertConfig = {
  49 + success: {
  50 + content: '恭喜您,成功领取优惠券',
  51 + subContents: ['特殊情况下到账有延时', '请耐心等待'],
  52 + className: 'subcontent-dialog',
  53 + refreshOnClose: true,
  54 + btns: [{
  55 + id: 1,
  56 + name: '去购物啦',
  57 + btnClass: ['black', 'btn-close'],
  58 + cb: function() {
  59 + redirect.goToGuang();
  60 + }
  61 + }, {
  62 + id: 2,
  63 + name: '查看优惠券',
  64 + btnClass: ['btn-close'],
  65 + cb: function() {
  66 + redirect.goToCheck();
  67 + }
  68 + }]
  69 + },
  70 + alreadyGot: {
  71 + content: '您已领取过优惠券',
  72 + subContent: '快去选购心仪的潮品吧',
  73 + className: 'subcontent-dialog',
  74 + btns: [{
  75 + id: 1,
  76 + name: '去使用',
  77 + btnClass: ['btn-close'],
  78 + cb: function() {
  79 + redirect.goToGuang();
  80 + }
  81 + }]
  82 + },
  83 + expired: {
  84 + content: '优惠券已过期',
  85 + subContent: '去领最新的优惠券吧',
  86 + className: 'subcontent-dialog',
  87 + btns: [{
  88 + id: 1,
  89 + name: '关闭',
  90 + btnClass: ['btn-close']
  91 + }]
  92 + },
  93 + paramerror: {
  94 + content: '请求参数出错',
  95 + subContents: ['请检查参数后重试'],
  96 + className: 'subcontent-dialog',
  97 + btns: [{
  98 + id: 1,
  99 + name: '关闭',
  100 + btnClass: ['btn-close']
  101 + }]
  102 + },
  103 + failed: {
  104 + content: '领取失败',
  105 + subContents: ['请刷新重试,', '多次无效请联系客服'],
  106 + className: 'subcontent-dialog',
  107 + btns: [{
  108 + id: 1,
  109 + name: '刷新',
  110 + btnClass: ['btn-close'],
  111 + cb: function() {
  112 + window.location.reload();
  113 + }
  114 + }]
  115 + }
  116 +};
  117 +
  118 +// 对应不同的网络返回码展示不同的弹窗
  119 +makeAlert = {
  120 + 200: function() {
  121 + couponAlert(alertConfig.success);
  122 + },
  123 + 401: function() {
  124 + couponAlert(alertConfig.alreadyGot);
  125 + },
  126 + 315: function() {
  127 + couponAlert(alertConfig.expired);
  128 + },
  129 + 300: function() {
  130 + couponAlert(alertConfig.paramerror);
  131 + },
  132 + 500: function() {
  133 + couponAlert(alertConfig.failed);
  134 + }
  135 +};
  136 +
  137 +function requestCoupon(id) {
  138 + $.ajax({
  139 + type: 'GET',
  140 + url: '/coupon/sendcoupon',
  141 + data: {
  142 + id: id
  143 + },
  144 + success: function(res) {
  145 + var code = res.code;
  146 +
  147 + if (code === 400) {
  148 + window.location.href = res.data.refer; // 未登录情况下,跳转到登录页
  149 + return;
  150 + }
  151 +
  152 + // 如果返回的数据里有url,则执行redirect里的方法
  153 + res.url && res.url.length > 0 && redirect[code] && redirect[code](res.url + '?' + location.href.split('?')[1]);
  154 +
  155 + // 如果能找到对应code则执行相应方法,如果没有的对应code则执行error提示
  156 + makeAlert[code] ? makeAlert[code]() : makeAlert['500']();
  157 + },
  158 + error: function() {
  159 + var Alert = require('../common/dialog').Alert;
  160 +
  161 + new Alert('网络异常').show();
  162 + }
  163 + });
  164 +}
  165 +
  166 +function getCouponStatus() {
  167 + var hash,
  168 + data = {},
  169 + search = window.location.search,
  170 + hashes = search ? decodeURIComponent(search).slice(1).split('&') : [];
  171 +
  172 + for (i = 0; i < hashes.length; i++) {
  173 + hash = hashes[i].split('=');
  174 + data[hash[0]] = hash[1];
  175 + }
  176 +
  177 + if (!data.contentCode) {
  178 + return;
  179 + }
  180 +
  181 + $.ajax({
  182 + type: 'GET',
  183 + url: '/coupon/couponstatus',
  184 + data: data,
  185 + success: function(res) {
  186 + if (res.code === 200) {
  187 + var cates = res.categories || [];
  188 +
  189 + cates.forEach(function(obj) {
  190 + var e = document.getElementById(obj.id);
  191 + var child = e.children;
  192 +
  193 + if (!child.length) {
  194 + return;
  195 + }
  196 +
  197 + e.classList.remove('enable');
  198 +
  199 + for (var i = 0; i < child.length; i++) {
  200 + if (child[i].className === 'normal') {
  201 + child[i].classList.add('hidden');
  202 + }
  203 +
  204 + if ((obj.got && child[i].className.indexOf('got') > -1) || (obj.empty && child[i].className.indexOf('empty') > -1)) {
  205 + child[i].classList.remove('hidden');
  206 + }
  207 + }
  208 + })
  209 + }
  210 + }
  211 + });
  212 +}
  213 +
  214 +// 获取领券状态
  215 +getCouponStatus();
  216 +
  217 +$('.info').on('click', function(e) {
  218 + if (this.className.indexOf('enable') > -1) {
  219 + e.preventDefault();
  220 + requestCoupon($(this).closest('a').data('id'));
  221 + redirect.gunangSrc = $(this).closest('a').get(0).href;
  222 + }
  223 +});