Authored by zhangxiaoru

nerge feature/ssr

... ... @@ -22,6 +22,7 @@ const session = require('express-session');
const memcached = require('connect-memcached');
const pkg = require('./package.json');
const devtools = require('./doraemon/middleware/devtools');
const _ = require('lodash');
const uuid = require('uuid');
const app = express();
... ... @@ -48,10 +49,11 @@ if (app.locals.devEnv) {
app.use(global.yoho.hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: path.join(__dirname, 'doraemon/views'),
partialsDir: path.join(__dirname, 'doraemon/views/partial'),
views: path.join(__dirname, 'doraemon/views'),
helpers: global.yoho.helpers
helpers: _.assign(global.yoho.helpers, require('./utils/helpers'))
}));
app.use(favicon(path.join(__dirname, '/public/favicon.ico')));
... ...
... ... @@ -5,19 +5,40 @@
*/
'use strict';
const channelModel = require('../models/channel');
const channelMap = {
men: '9ee58aadd9559d07207fe4a98843eaac',
women: '3ad8826fc89fb0d023a4cd06a6991219',
lifestyle: 'aa8d34c85934c2ccc16e2babd3eb5e47'
};
const _ = require('lodash');
/**
* 频道选择页
*/
module.exports = {
index(req, res) {
index(req, res, next) {
let channel = req.path.split('/')[1] || req.yoho.channel;
res.render('cindex', {
channelModel.getResourcesData({
contentCode: channelMap[channel]
}).then(result => {
const resources = result.slice(0, 3);
_.each(resources, (resource) => {
// 只拿第一个数据
if (_.isArray(resource.data)) {
resource.data = [resource.data[0]];
}
});
res.locals.resources = resources;
res.render('index', {
module: 'channel',
page: 'home',
channel: channel
});
}).catch(next);
},
channel(req, res, next) {
channelModel.getChannelData().then(result => {
... ...
<div id="channel">
<channel></channel>
<div id="ssr" class="resources" style="position: absolute; top: 2.25rem;">
{{#resources}}
{{#if focus}}
<div class="focus-floor" style="height: 9.1rem;">
{{#data}}
<a href="{{this.url}}" title="{{this.title}}">
<img src="{{image2 this.src w=750 h=365 q=60}}" width="100%" alt="">
</a>
{{/data}}
</div>
{{/if}}
{{#if titleImage}}
<div class="title-image">
{{#data}}
<div class="floor-header">
{{title}}
<a class="more" href="{{moreUrl}}">
{{#eq moreName '...'}}
<span class="icon icon-more"></span>
{{^}}
<!--<span>{{{moreName}}}</span>-->
{{/eq}}
</a>
</div>
<a class="image" href="{{image.url}}">
<img src="{{image2 image.src w=750 h=365 q=60}}" width="100%" alt="">
</a>
</div>
{{/data}}
{{/if}}
{{/resources}}
</div>
<channel>
</channel>
</div>
... ...
... ... @@ -35,18 +35,32 @@ const saveRecentGoodInCookies = (oldSkns, res, addSkns) => {
* 商品详情
*/
const component = {
index(req, res) {
index(req, res, next) {
const pid = req.params[0],
goodsId = req.params[1],
cnAlphabet = req.params[2];
res.render('pdetail', {
let params = {
product_id: _.toString(pid),
uid: req.user.uid || 0
};
model.product(params).then(product => {
product = product.data || {};
product.formatPrice = product.formatSalesPrice !== '0' ? product.formatSalesPrice : product.formatMarketPrice;
product.isDiscount = product.marketPrice > product.salesPrice;
console.log(product);
res.render('detail', {
module: 'product',
page: 'detail',
pid: pid,
goodsId: goodsId,
cnAlphabet: cnAlphabet
cnAlphabet: cnAlphabet,
product: product
});
}).catch(next);
},
product(req, res, next) {
const pid = req.params[0]; // , goodsId = req.params[1];
... ...
... ... @@ -8,11 +8,17 @@ const searchModel = require('../models/search');
/* 搜索 页面 */
exports.index = (req, res) => {
exports.index = (req, res, next) => {
const params = req.query;
searchModel.products(params).then(result => {
res.render('product-list', {
navTitle: params.title || params.sort_name,
module: 'product',
page: 'list'
page: 'list',
list: result && result.data ? result.data.productList : []
});
}).catch(next);
};
/* 查询 产品列表 method:GET */
... ...
<div id="app" class="product-page" data-pid="{{pid}}" data-goods-id="{{goodsId}}">
{{#product}}
<div class="ssr show-box first-box">
<div class="image-carousel">
<div class="swipe">
<div class="swipe-items-wrap">
{{#each goodsList}}
{{#if colorImage}}
<div class="swipe-item {{#if @first}}active{{/if}}"> <img width="100%" alt="" src="{{image2 colorImage w=750 h=1000 q=80}}"></div>
{{/if}}
{{/each}}
</div>
</div>
</div>
<div class="title-box">
<h1 class="line-clamp-2">{{productName}}</h1>
{{#if isDiscount}}
<i class="price strike-through">{{formatMarketPrice}}</i>
{{/if}}
<i class="price {{#if isDiscount}}highlight{{/if}}">{{formatPrice}}</i>
</div>
</div>
{{#brandInfo}}
<div class="ssr show-box brand">
<img src="{{image2 brandIco w=110 h=68 q=80}}">
<h2>{{brandName}}</h2>
<div class="brand-go">
<span>进入店铺</span>
<span class="icon icon-right"></span>
</div>
<a href="/product/shop/{{brandDomain}}"></a>
</div>
{{/brandInfo}}
{{/product}}
<app/>
</div>
... ...
<div id="ssr" style="position: absolute; top: 0;">
<div class="blk-header-wrap" class="is-fixed">
<div class="blk-header">
<div class="blk-header-left">
<i class="icon icon-left go-back-btn"></i>
</div>
<div class="blk-header-right">
</div>
<div class="blk-header-main">
<span class="blk-header-title">{{navTitle}}</span>
</div>
</div>
<div class="blk-header-gap"></div>
</div>
<ul class="order-navs clearfix">
<li class="order-item active">
<span class="order-name">默认</span>
</li>
<li class="order-item">
<span class="order-name">最新</span>
</li>
<li class="order-item">
<span class="order-name">价格</span>
<span class="order-icon">
<i class="icon icon-sort-up"></i>
<i class="icon icon-sort-down"></i>
</span>
</li>
<li class="order-item">
<span class="order-name">折扣</span>
<span class="order-icon">
<i class="icon icon-sort-up"></i>
<i class="icon icon-sort-down"></i>
</span>
</li>
</ul>
<div class="goods-box">
<ul class="cardlist card-large clearfix">
{{#list}}
<li class="card">
<div class="card-pic">
<a href="{{goodsUrl this}}">
<img src="{{resizeImage defaultImages 372 499}}" alt="{{productName}}">
</a>
</div>
<div class="card-bd">
<h2 class="card-label">
<a href="{{goodsUrl this}}" class="line-clamp-2">{{productName}}</a>
</h2>
{{#if marketPrice}}
<span class="good-price" class="old-price">
¥ {{toFixed marketPrice}}
</span>
{{/if}}
<span class="good-price {{#if marketPrice}}sale-price{{/if}}">¥ {{toFixed salesPrice}}</span>
</div>
</li>
{{/list}}
</ul>
</div>
</div>
<div id="product-list"></div>
... ...
... ... @@ -40,9 +40,9 @@ module.exports = {
useOneapm: false,
useCache: false,
memcache: {
master: ['192.168.102.205:12111'],
slave: ['192.168.102.205:12111'],
session: ['192.168.102.205:12111'],
master: ['127.0.0.1:11211'],
slave: ['127.0.0.1:11211'],
session: ['127.0.0.1:11211'],
timeout: 1000,
retries: 0
},
... ...
... ... @@ -15,6 +15,17 @@ yoho.ready(() => {
el: '#app',
components: {
app: app
},
created() {
setTimeout(() => {
let ssrs = document.querySelectorAll('.ssr') || [];
ssrs.forEach(i => {
if (i) {
i.remove();
}
});
}, 500);
}
});
});
... ...
... ... @@ -93,6 +93,10 @@
if (result.length) {
dataCache[param] = result;
}
setTimeout(()=> {
$('#ssr').remove();
}, 2000);
}).fail(() => {
tip('网络错误');
});
... ...
... ... @@ -116,6 +116,12 @@
})
.always(() => {
self.inSearching = false;
// 完成后删除服务端渲染的元素
setTimeout(()=> {
$('#ssr').remove();
});
});
},
... ...
'use strict';
const url = require('url');
const _ = require('lodash');
const config = require('../config/common');
const assetUrl = config.assetUrl;
module.exports = {
imgSrc: function(imgSrc) {
return url.resolve(assetUrl, imgSrc);
},
image2: function(imageUrl, opts) {
if (imageUrl && _.isString(imageUrl)) {
let params = opts.hash;
let urls = imageUrl.split('?');
let query = urls[1] || '';
let uri = urls[0];
if (uri.indexOf('http:') === 0) {
uri = uri.replace('http:', '');
}
if (query) {
query = query.replace(/{width}/g, params.w).replace(/{height}/g, params.h).replace(/{mode}/g, (params.mode || 2));
if (query.indexOf('imageView2') === 0) {
if (params.q && query.indexOf('/q/') > 0) {
query = query.replace(/\/q\/\d+/g, '/q/' + params.q);
} else if (params.q) {
query += '/q/' + params.q;
}
} else if (query.indexOf('imageMogr2') === 0) {
if (params.q && query.indexOf('/quality/') > 0) {
query = query.replace(/\/quality\/\d+/g, '/quality/' + params.q);
} else if (params.q) {
query += '/quality/' + params.q;
}
} else if (query.indexOf('imageView/') === 0) {
if (params.q && query.indexOf('/q/') > 0) {
query = query.replace(/\/q\/\d+/g, '/q/' + params.q);
} else if (params.q) {
query += '/q/' + params.q;
}
if (params.mode) {
query = query.replace(/imageView\/\d{1}\//, 'imageView/' + params.mode + '/');
}
}
} else {
query = 'imageView2/2/interlace/1/q/' + (params.q || 75);
}
return uri + '?' + query;
} else {
return '';
}
},
ifor: function() {
var args = Array.prototype.slice.call(arguments);
var opt = args[args.length - 1];
var isTrue = false;
for (var i = 0; i < args.length - 1; i++) {
if (args[i]) {
isTrue = true;
break;
}
}
if (isTrue) {
return opt.fn(this);
} else {
return opt.inverse(this);
}
},
/**
* 小于某zhi
*
* @param variable
* @param number
*/
within: function(variable, number, opt) {
if (variable < number) {
return opt.fn(this);
} else {
return opt.inverse(this);
}
},
eq: function(a, b, options) {
if (arguments.length === 2) {
options = b;
b = options.hash.compare;
}
if (a === b) {
return options.fn(this);
}
return options.inverse(this);
},
goodsUrl: (product, kind) => {
let productId, goodsId, cnAlphabet;
switch (kind) {
case 'collection':
productId = product.productId;
goodsId = product.goodsId;
cnAlphabet = product.cnAlphabet;
break;
default:
productId = product.productId;
goodsId = product.goodsList[0].goodsId;
cnAlphabet = product.cnAlphabet;
}
return `/product/pro_${productId}_${goodsId}/${cnAlphabet}.html`;
},
toFixed: (value, num) => {
if (typeof value === 'undefined') {
return;
}
return Number(value).toFixed(num || 2);
},
resizeImage: (src, width, height, mode) => {
return src ? src.replace(/(\{width}|\{height}|\{mode})/g, function($0) {
const dict = {
'{width}': width || 300,
'{height}': height || 300,
'{mode}': mode || 2
};
return dict[$0];
}).replace(/https?:/, '') + '/interlace/1' : '';
}
};
... ...