nerge feature/ssr
Showing
12 changed files
with
345 additions
and
12 deletions
@@ -22,6 +22,7 @@ const session = require('express-session'); | @@ -22,6 +22,7 @@ const session = require('express-session'); | ||
22 | const memcached = require('connect-memcached'); | 22 | const memcached = require('connect-memcached'); |
23 | const pkg = require('./package.json'); | 23 | const pkg = require('./package.json'); |
24 | const devtools = require('./doraemon/middleware/devtools'); | 24 | const devtools = require('./doraemon/middleware/devtools'); |
25 | +const _ = require('lodash'); | ||
25 | 26 | ||
26 | const uuid = require('uuid'); | 27 | const uuid = require('uuid'); |
27 | const app = express(); | 28 | const app = express(); |
@@ -48,10 +49,11 @@ if (app.locals.devEnv) { | @@ -48,10 +49,11 @@ if (app.locals.devEnv) { | ||
48 | app.use(global.yoho.hbs({ | 49 | app.use(global.yoho.hbs({ |
49 | extname: '.hbs', | 50 | extname: '.hbs', |
50 | defaultLayout: 'layout', | 51 | defaultLayout: 'layout', |
52 | + | ||
51 | layoutsDir: path.join(__dirname, 'doraemon/views'), | 53 | layoutsDir: path.join(__dirname, 'doraemon/views'), |
52 | partialsDir: path.join(__dirname, 'doraemon/views/partial'), | 54 | partialsDir: path.join(__dirname, 'doraemon/views/partial'), |
53 | views: path.join(__dirname, 'doraemon/views'), | 55 | views: path.join(__dirname, 'doraemon/views'), |
54 | - helpers: global.yoho.helpers | 56 | + helpers: _.assign(global.yoho.helpers, require('./utils/helpers')) |
55 | })); | 57 | })); |
56 | 58 | ||
57 | app.use(favicon(path.join(__dirname, '/public/favicon.ico'))); | 59 | app.use(favicon(path.join(__dirname, '/public/favicon.ico'))); |
@@ -5,19 +5,40 @@ | @@ -5,19 +5,40 @@ | ||
5 | */ | 5 | */ |
6 | 'use strict'; | 6 | 'use strict'; |
7 | const channelModel = require('../models/channel'); | 7 | const channelModel = require('../models/channel'); |
8 | +const channelMap = { | ||
9 | + men: '9ee58aadd9559d07207fe4a98843eaac', | ||
10 | + women: '3ad8826fc89fb0d023a4cd06a6991219', | ||
11 | + lifestyle: 'aa8d34c85934c2ccc16e2babd3eb5e47' | ||
12 | +}; | ||
13 | +const _ = require('lodash'); | ||
8 | 14 | ||
9 | /** | 15 | /** |
10 | * 频道选择页 | 16 | * 频道选择页 |
11 | */ | 17 | */ |
12 | module.exports = { | 18 | module.exports = { |
13 | - index(req, res) { | 19 | + index(req, res, next) { |
14 | let channel = req.path.split('/')[1] || req.yoho.channel; | 20 | let channel = req.path.split('/')[1] || req.yoho.channel; |
15 | 21 | ||
16 | - res.render('cindex', { | 22 | + channelModel.getResourcesData({ |
23 | + contentCode: channelMap[channel] | ||
24 | + }).then(result => { | ||
25 | + const resources = result.slice(0, 3); | ||
26 | + | ||
27 | + _.each(resources, (resource) => { | ||
28 | + // 只拿第一个数据 | ||
29 | + if (_.isArray(resource.data)) { | ||
30 | + resource.data = [resource.data[0]]; | ||
31 | + } | ||
32 | + }); | ||
33 | + | ||
34 | + res.locals.resources = resources; | ||
35 | + | ||
36 | + res.render('index', { | ||
17 | module: 'channel', | 37 | module: 'channel', |
18 | page: 'home', | 38 | page: 'home', |
19 | channel: channel | 39 | channel: channel |
20 | }); | 40 | }); |
41 | + }).catch(next); | ||
21 | }, | 42 | }, |
22 | channel(req, res, next) { | 43 | channel(req, res, next) { |
23 | channelModel.getChannelData().then(result => { | 44 | channelModel.getChannelData().then(result => { |
1 | <div id="channel"> | 1 | <div id="channel"> |
2 | - <channel></channel> | 2 | + <div id="ssr" class="resources" style="position: absolute; top: 2.25rem;"> |
3 | + {{#resources}} | ||
4 | + {{#if focus}} | ||
5 | + <div class="focus-floor" style="height: 9.1rem;"> | ||
6 | + {{#data}} | ||
7 | + <a href="{{this.url}}" title="{{this.title}}"> | ||
8 | + <img src="{{image2 this.src w=750 h=365 q=60}}" width="100%" alt=""> | ||
9 | + </a> | ||
10 | + {{/data}} | ||
11 | + </div> | ||
12 | + {{/if}} | ||
13 | + | ||
14 | + {{#if titleImage}} | ||
15 | + <div class="title-image"> | ||
16 | + {{#data}} | ||
17 | + <div class="floor-header"> | ||
18 | + {{title}} | ||
19 | + | ||
20 | + <a class="more" href="{{moreUrl}}"> | ||
21 | + {{#eq moreName '...'}} | ||
22 | + <span class="icon icon-more"></span> | ||
23 | + {{^}} | ||
24 | + <!--<span>{{{moreName}}}</span>--> | ||
25 | + {{/eq}} | ||
26 | + </a> | ||
27 | + </div> | ||
28 | + | ||
29 | + <a class="image" href="{{image.url}}"> | ||
30 | + <img src="{{image2 image.src w=750 h=365 q=60}}" width="100%" alt=""> | ||
31 | + </a> | ||
32 | + </div> | ||
33 | + {{/data}} | ||
34 | + | ||
35 | + {{/if}} | ||
36 | + | ||
37 | + {{/resources}} | ||
38 | + </div> | ||
39 | + | ||
40 | + <channel> | ||
41 | + </channel> | ||
3 | </div> | 42 | </div> |
@@ -35,18 +35,32 @@ const saveRecentGoodInCookies = (oldSkns, res, addSkns) => { | @@ -35,18 +35,32 @@ const saveRecentGoodInCookies = (oldSkns, res, addSkns) => { | ||
35 | * 商品详情 | 35 | * 商品详情 |
36 | */ | 36 | */ |
37 | const component = { | 37 | const component = { |
38 | - index(req, res) { | 38 | + index(req, res, next) { |
39 | const pid = req.params[0], | 39 | const pid = req.params[0], |
40 | goodsId = req.params[1], | 40 | goodsId = req.params[1], |
41 | cnAlphabet = req.params[2]; | 41 | cnAlphabet = req.params[2]; |
42 | 42 | ||
43 | - res.render('pdetail', { | 43 | + let params = { |
44 | + product_id: _.toString(pid), | ||
45 | + uid: req.user.uid || 0 | ||
46 | + }; | ||
47 | + | ||
48 | + model.product(params).then(product => { | ||
49 | + product = product.data || {}; | ||
50 | + | ||
51 | + product.formatPrice = product.formatSalesPrice !== '0' ? product.formatSalesPrice : product.formatMarketPrice; | ||
52 | + product.isDiscount = product.marketPrice > product.salesPrice; | ||
53 | + | ||
54 | + console.log(product); | ||
55 | + res.render('detail', { | ||
44 | module: 'product', | 56 | module: 'product', |
45 | page: 'detail', | 57 | page: 'detail', |
46 | pid: pid, | 58 | pid: pid, |
47 | goodsId: goodsId, | 59 | goodsId: goodsId, |
48 | - cnAlphabet: cnAlphabet | 60 | + cnAlphabet: cnAlphabet, |
61 | + product: product | ||
49 | }); | 62 | }); |
63 | + }).catch(next); | ||
50 | }, | 64 | }, |
51 | product(req, res, next) { | 65 | product(req, res, next) { |
52 | const pid = req.params[0]; // , goodsId = req.params[1]; | 66 | const pid = req.params[0]; // , goodsId = req.params[1]; |
@@ -8,11 +8,17 @@ const searchModel = require('../models/search'); | @@ -8,11 +8,17 @@ const searchModel = require('../models/search'); | ||
8 | 8 | ||
9 | 9 | ||
10 | /* 搜索 页面 */ | 10 | /* 搜索 页面 */ |
11 | -exports.index = (req, res) => { | 11 | +exports.index = (req, res, next) => { |
12 | + const params = req.query; | ||
13 | + | ||
14 | + searchModel.products(params).then(result => { | ||
12 | res.render('product-list', { | 15 | res.render('product-list', { |
16 | + navTitle: params.title || params.sort_name, | ||
13 | module: 'product', | 17 | module: 'product', |
14 | - page: 'list' | 18 | + page: 'list', |
19 | + list: result && result.data ? result.data.productList : [] | ||
15 | }); | 20 | }); |
21 | + }).catch(next); | ||
16 | }; | 22 | }; |
17 | 23 | ||
18 | /* 查询 产品列表 method:GET */ | 24 | /* 查询 产品列表 method:GET */ |
1 | <div id="app" class="product-page" data-pid="{{pid}}" data-goods-id="{{goodsId}}"> | 1 | <div id="app" class="product-page" data-pid="{{pid}}" data-goods-id="{{goodsId}}"> |
2 | + {{#product}} | ||
3 | + <div class="ssr show-box first-box"> | ||
4 | + <div class="image-carousel"> | ||
5 | + <div class="swipe"> | ||
6 | + <div class="swipe-items-wrap"> | ||
7 | + {{#each goodsList}} | ||
8 | + {{#if colorImage}} | ||
9 | + <div class="swipe-item {{#if @first}}active{{/if}}"> <img width="100%" alt="" src="{{image2 colorImage w=750 h=1000 q=80}}"></div> | ||
10 | + {{/if}} | ||
11 | + {{/each}} | ||
12 | + </div> | ||
13 | + </div> | ||
14 | + </div> | ||
15 | + <div class="title-box"> | ||
16 | + <h1 class="line-clamp-2">{{productName}}</h1> | ||
17 | + {{#if isDiscount}} | ||
18 | + <i class="price strike-through">{{formatMarketPrice}}</i> | ||
19 | + {{/if}} | ||
20 | + <i class="price {{#if isDiscount}}highlight{{/if}}">{{formatPrice}}</i> | ||
21 | + </div> | ||
22 | + </div> | ||
23 | + {{#brandInfo}} | ||
24 | + <div class="ssr show-box brand"> | ||
25 | + <img src="{{image2 brandIco w=110 h=68 q=80}}"> | ||
26 | + <h2>{{brandName}}</h2> | ||
27 | + <div class="brand-go"> | ||
28 | + <span>进入店铺</span> | ||
29 | + <span class="icon icon-right"></span> | ||
30 | + </div> | ||
31 | + <a href="/product/shop/{{brandDomain}}"></a> | ||
32 | + </div> | ||
33 | + {{/brandInfo}} | ||
34 | + {{/product}} | ||
2 | <app/> | 35 | <app/> |
3 | </div> | 36 | </div> |
1 | +<div id="ssr" style="position: absolute; top: 0;"> | ||
2 | + <div class="blk-header-wrap" class="is-fixed"> | ||
3 | + <div class="blk-header"> | ||
4 | + <div class="blk-header-left"> | ||
5 | + <i class="icon icon-left go-back-btn"></i> | ||
6 | + </div> | ||
7 | + <div class="blk-header-right"> | ||
8 | + </div> | ||
9 | + <div class="blk-header-main"> | ||
10 | + <span class="blk-header-title">{{navTitle}}</span> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <div class="blk-header-gap"></div> | ||
14 | + </div> | ||
15 | + | ||
16 | + <ul class="order-navs clearfix"> | ||
17 | + <li class="order-item active"> | ||
18 | + <span class="order-name">默认</span> | ||
19 | + </li> | ||
20 | + <li class="order-item"> | ||
21 | + <span class="order-name">最新</span> | ||
22 | + </li> | ||
23 | + | ||
24 | + <li class="order-item"> | ||
25 | + <span class="order-name">价格</span> | ||
26 | + <span class="order-icon"> | ||
27 | + <i class="icon icon-sort-up"></i> | ||
28 | + <i class="icon icon-sort-down"></i> | ||
29 | + </span> | ||
30 | + </li> | ||
31 | + <li class="order-item"> | ||
32 | + <span class="order-name">折扣</span> | ||
33 | + <span class="order-icon"> | ||
34 | + <i class="icon icon-sort-up"></i> | ||
35 | + <i class="icon icon-sort-down"></i> | ||
36 | + </span> | ||
37 | + </li> | ||
38 | + </ul> | ||
39 | + | ||
40 | + <div class="goods-box"> | ||
41 | + <ul class="cardlist card-large clearfix"> | ||
42 | + {{#list}} | ||
43 | + <li class="card"> | ||
44 | + <div class="card-pic"> | ||
45 | + <a href="{{goodsUrl this}}"> | ||
46 | + <img src="{{resizeImage defaultImages 372 499}}" alt="{{productName}}"> | ||
47 | + </a> | ||
48 | + </div> | ||
49 | + <div class="card-bd"> | ||
50 | + <h2 class="card-label"> | ||
51 | + <a href="{{goodsUrl this}}" class="line-clamp-2">{{productName}}</a> | ||
52 | + </h2> | ||
53 | + {{#if marketPrice}} | ||
54 | + <span class="good-price" class="old-price"> | ||
55 | + ¥ {{toFixed marketPrice}} | ||
56 | + </span> | ||
57 | + {{/if}} | ||
58 | + <span class="good-price {{#if marketPrice}}sale-price{{/if}}">¥ {{toFixed salesPrice}}</span> | ||
59 | + </div> | ||
60 | + </li> | ||
61 | + {{/list}} | ||
62 | + </ul> | ||
63 | + </div> | ||
64 | +</div> | ||
1 | <div id="product-list"></div> | 65 | <div id="product-list"></div> |
@@ -40,9 +40,9 @@ module.exports = { | @@ -40,9 +40,9 @@ module.exports = { | ||
40 | useOneapm: false, | 40 | useOneapm: false, |
41 | useCache: false, | 41 | useCache: false, |
42 | memcache: { | 42 | memcache: { |
43 | - master: ['192.168.102.205:12111'], | ||
44 | - slave: ['192.168.102.205:12111'], | ||
45 | - session: ['192.168.102.205:12111'], | 43 | + master: ['127.0.0.1:11211'], |
44 | + slave: ['127.0.0.1:11211'], | ||
45 | + session: ['127.0.0.1:11211'], | ||
46 | timeout: 1000, | 46 | timeout: 1000, |
47 | retries: 0 | 47 | retries: 0 |
48 | }, | 48 | }, |
@@ -15,6 +15,17 @@ yoho.ready(() => { | @@ -15,6 +15,17 @@ yoho.ready(() => { | ||
15 | el: '#app', | 15 | el: '#app', |
16 | components: { | 16 | components: { |
17 | app: app | 17 | app: app |
18 | + }, | ||
19 | + created() { | ||
20 | + setTimeout(() => { | ||
21 | + let ssrs = document.querySelectorAll('.ssr') || []; | ||
22 | + | ||
23 | + ssrs.forEach(i => { | ||
24 | + if (i) { | ||
25 | + i.remove(); | ||
26 | + } | ||
27 | + }); | ||
28 | + }, 500); | ||
18 | } | 29 | } |
19 | }); | 30 | }); |
20 | }); | 31 | }); |
@@ -93,6 +93,10 @@ | @@ -93,6 +93,10 @@ | ||
93 | if (result.length) { | 93 | if (result.length) { |
94 | dataCache[param] = result; | 94 | dataCache[param] = result; |
95 | } | 95 | } |
96 | + | ||
97 | + setTimeout(()=> { | ||
98 | + $('#ssr').remove(); | ||
99 | + }, 2000); | ||
96 | }).fail(() => { | 100 | }).fail(() => { |
97 | tip('网络错误'); | 101 | tip('网络错误'); |
98 | }); | 102 | }); |
utils/helpers.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const url = require('url'); | ||
4 | +const _ = require('lodash'); | ||
5 | +const config = require('../config/common'); | ||
6 | +const assetUrl = config.assetUrl; | ||
7 | + | ||
8 | +module.exports = { | ||
9 | + imgSrc: function(imgSrc) { | ||
10 | + return url.resolve(assetUrl, imgSrc); | ||
11 | + }, | ||
12 | + image2: function(imageUrl, opts) { | ||
13 | + if (imageUrl && _.isString(imageUrl)) { | ||
14 | + let params = opts.hash; | ||
15 | + let urls = imageUrl.split('?'); | ||
16 | + let query = urls[1] || ''; | ||
17 | + let uri = urls[0]; | ||
18 | + | ||
19 | + if (uri.indexOf('http:') === 0) { | ||
20 | + uri = uri.replace('http:', ''); | ||
21 | + } | ||
22 | + | ||
23 | + if (query) { | ||
24 | + query = query.replace(/{width}/g, params.w).replace(/{height}/g, params.h).replace(/{mode}/g, (params.mode || 2)); | ||
25 | + | ||
26 | + if (query.indexOf('imageView2') === 0) { | ||
27 | + if (params.q && query.indexOf('/q/') > 0) { | ||
28 | + query = query.replace(/\/q\/\d+/g, '/q/' + params.q); | ||
29 | + } else if (params.q) { | ||
30 | + query += '/q/' + params.q; | ||
31 | + } | ||
32 | + } else if (query.indexOf('imageMogr2') === 0) { | ||
33 | + if (params.q && query.indexOf('/quality/') > 0) { | ||
34 | + query = query.replace(/\/quality\/\d+/g, '/quality/' + params.q); | ||
35 | + } else if (params.q) { | ||
36 | + query += '/quality/' + params.q; | ||
37 | + } | ||
38 | + } else if (query.indexOf('imageView/') === 0) { | ||
39 | + if (params.q && query.indexOf('/q/') > 0) { | ||
40 | + query = query.replace(/\/q\/\d+/g, '/q/' + params.q); | ||
41 | + } else if (params.q) { | ||
42 | + query += '/q/' + params.q; | ||
43 | + } | ||
44 | + | ||
45 | + if (params.mode) { | ||
46 | + query = query.replace(/imageView\/\d{1}\//, 'imageView/' + params.mode + '/'); | ||
47 | + } | ||
48 | + } | ||
49 | + } else { | ||
50 | + query = 'imageView2/2/interlace/1/q/' + (params.q || 75); | ||
51 | + } | ||
52 | + return uri + '?' + query; | ||
53 | + } else { | ||
54 | + return ''; | ||
55 | + } | ||
56 | + }, | ||
57 | + ifor: function() { | ||
58 | + var args = Array.prototype.slice.call(arguments); | ||
59 | + var opt = args[args.length - 1]; | ||
60 | + var isTrue = false; | ||
61 | + | ||
62 | + for (var i = 0; i < args.length - 1; i++) { | ||
63 | + if (args[i]) { | ||
64 | + isTrue = true; | ||
65 | + break; | ||
66 | + } | ||
67 | + } | ||
68 | + | ||
69 | + if (isTrue) { | ||
70 | + return opt.fn(this); | ||
71 | + } else { | ||
72 | + return opt.inverse(this); | ||
73 | + } | ||
74 | + }, | ||
75 | + | ||
76 | + /** | ||
77 | + * 小于某zhi | ||
78 | + * | ||
79 | + * @param variable | ||
80 | + * @param number | ||
81 | + */ | ||
82 | + within: function(variable, number, opt) { | ||
83 | + if (variable < number) { | ||
84 | + return opt.fn(this); | ||
85 | + } else { | ||
86 | + return opt.inverse(this); | ||
87 | + } | ||
88 | + }, | ||
89 | + eq: function(a, b, options) { | ||
90 | + if (arguments.length === 2) { | ||
91 | + options = b; | ||
92 | + b = options.hash.compare; | ||
93 | + } | ||
94 | + if (a === b) { | ||
95 | + return options.fn(this); | ||
96 | + } | ||
97 | + return options.inverse(this); | ||
98 | + }, | ||
99 | + goodsUrl: (product, kind) => { | ||
100 | + let productId, goodsId, cnAlphabet; | ||
101 | + | ||
102 | + switch (kind) { | ||
103 | + case 'collection': | ||
104 | + productId = product.productId; | ||
105 | + goodsId = product.goodsId; | ||
106 | + cnAlphabet = product.cnAlphabet; | ||
107 | + break; | ||
108 | + default: | ||
109 | + productId = product.productId; | ||
110 | + goodsId = product.goodsList[0].goodsId; | ||
111 | + cnAlphabet = product.cnAlphabet; | ||
112 | + } | ||
113 | + | ||
114 | + return `/product/pro_${productId}_${goodsId}/${cnAlphabet}.html`; | ||
115 | + }, | ||
116 | + toFixed: (value, num) => { | ||
117 | + if (typeof value === 'undefined') { | ||
118 | + return; | ||
119 | + } | ||
120 | + return Number(value).toFixed(num || 2); | ||
121 | + }, | ||
122 | + resizeImage: (src, width, height, mode) => { | ||
123 | + return src ? src.replace(/(\{width}|\{height}|\{mode})/g, function($0) { | ||
124 | + const dict = { | ||
125 | + '{width}': width || 300, | ||
126 | + '{height}': height || 300, | ||
127 | + '{mode}': mode || 2 | ||
128 | + }; | ||
129 | + | ||
130 | + return dict[$0]; | ||
131 | + }).replace(/https?:/, '') + '/interlace/1' : ''; | ||
132 | + } | ||
133 | +}; |
-
Please register or login to post a comment