Authored by 王水玲

Merge branch 'master' into feature/encryptionUid

Showing 47 changed files with 3816 additions and 490 deletions

Too many changes to show.

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

... ... @@ -2,5 +2,9 @@
"extends": "yoho",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"max-len": [0, 50, 4]
}
}
... ...
# Created by https://www.gitignore.io/api/node,webstorm,netbeans,sublimetext,vim
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
### WebStorm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/
# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### WebStorm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
nbactions.xml
.nb-gradle/
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
### Vim ###
# swap
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags
### VS Code ###
.vscode/
### YOHO ###
dist
public/css/*
public/bundle/*
.eslintcache
*.log.*
nbproject/*
.DS_Store
# Created by https://www.gitignore.io/api/node,webstorm,netbeans,sublimetext,vim
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
### WebStorm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/
# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### WebStorm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
nbactions.xml
.nb-gradle/
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
### Vim ###
# swap
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags
### VS Code ###
.vscode/
### YOHO ###
dist
public/css/*
public/bundle/*
.eslintcache
*.log.*
nbproject/*
.DS_Store
.devhost
... ...
**/css/**/*.css
**/dist/**/*.css
**/scss/activity/_aslider.css
... ...
{
"extends": "stylelint-config-yoho"
"extends": "stylelint-config-yoho",
"rules": {
"string-quotes": "double"
}
}
... ...
... ... @@ -86,13 +86,17 @@ try {
const setYohoData = require('./doraemon/middleware/set-yoho-data');
const errorHanlder = require('./doraemon/middleware/error-handler');
const setPageInfo = require('./doraemon/middleware/set-pageinfo');
const devtools = require('./doraemon/middleware/devtools');
// YOHO 前置中间件
app.use(setYohoData());
app.use(user());
app.use(setPageInfo());
if (app.locals.devEnv) {
app.use(devtools());
}
require('./dispatch')(app);
app.all('*', errorHanlder.notFound()); // 404
... ... @@ -100,7 +104,6 @@ try {
// YOHO 后置中间件
app.use(errorHanlder.serverError());
} catch (err) {
console.error(err);
logger.error(err);
}
... ...
... ... @@ -2,6 +2,7 @@
## 构建方法
* `npm i -g yo`
* `npm i -g generator-subapp`
* `cd apps`
* `yo subapp`
... ...
'use strict';
const mRoot = '../models';
const library = '../../../library';
const couponModel = require(`${mRoot}/coupon`); // 领取优惠券 model
const log = require(`${library}/logger`);
exports.index = (req, res, next) => {
var renderData = {
... ...
/**
* 邀请好友赢福利
* <xiaoxiao.hao@yoho.cn>
* 2016/07/13
*/
'use strict';
const inviteModel = require('../models/invite');
const inviteTitle = '有货 邀请好友赢福利';
const _ = require('lodash');
const md5 = require('md5');
const secretKey = '5466ee572bcbc75830d044e66ab429bc';// 秘钥
// 简介、好友领取列表页面
exports.index = (req, res, next) => {
let actId = req.query.act_id * 1 || 0;
let uid = req.query.uid || 0;
let renderPage = 'invite/list';
inviteModel.index({
uid: uid,
activityId: actId
}).then((result) => {
// 非法参数跳到首页
if (result.isGo) {
res.redirect('/');
return false;
}
if (result.isNil) {
renderPage = 'invite/intro';
}
res.render(renderPage, {
module: 'activity',
page: 'invite',
result: result,
title: inviteTitle
});
}).catch(next);
};
// 微信好友获取红包方法(即分享出去的地址)页面
exports.share = (req, res, next) => {
let shareUid = req.params[0];
let actId = req.params[1];
let nums = req.params[2];
let shareUrl = inviteModel.createShareUrl(shareUid, actId, nums);
let callback = 'http://m.yohobuy.com/activity/invite/getwxinfo?url=' + shareUrl;
let url = inviteModel.getWxOauthUrl(callback);
let wxUserInfo = req.cookies.wxUserInfo || {};
if (_.isEmpty(wxUserInfo) || _.isEmpty(wxUserInfo.unionid)) {
res.redirect(url);
return false;
}
inviteModel.shareModel({
uid: shareUid,
activityId: actId,
nums: nums,
openId: wxUserInfo.unionid,
nickName: wxUserInfo.nickname,
headImgUrl: wxUserInfo.headimgurl
}).then(result => {
if (result.code * 1 !== 200) {
res.redirect('/');
return false;
}
if (_.isEmpty(result.data.list)) {
res.render('invite/myshare', {
module: 'activity',
page: 'invite',
result: {
shareUid: shareUid,
nums: nums,
actId: actId,
openId: wxUserInfo.unionid
},
title: inviteTitle
});
} else {
// 是自己分享的连接
res.render('invite/list', {
module: 'activity',
page: 'invite',
result: result.data.list,
title: inviteTitle
});
}
}).catch(next);
};
// 发送短信验证码API
exports.sendRegCodeToMobile = (req, res, next) => {
let mobile = req.query.mobile || '';
inviteModel.sendRegCodeToMobile({
area: 86,
mobile: mobile
}).then((result) => {
res.json(result);
}).catch(next);
};
// 发送已注册用户参与活动的优惠券API
exports.checkOldUserCoupon = (req, res, next) => {
let mobile = req.query.mobile || '';
let actId = req.query.actId || '';
inviteModel.checkOldUserCoupon({
mobile: mobile,
activityId: actId
}).then((result) => {
res.json(result);
}).catch(next);
};
// 验证手机验证码是否正确API
exports.validRegCode = (req, res, next) => {
let mobile = req.query.mobile || '';
let code = req.query.code || '';
inviteModel.validRegCode({
area: 86,
mobile: mobile,
code: code
}).then((result) => {
res.json(result);
}).catch(next);
};
// 手机注册账号API
exports.register = (req, res, next) => {
let mobile = req.query.mobile || '';
let activityName = req.query.activityName || 'invite';
inviteModel.register({
mobile: mobile,
activityName: activityName
}).then((result) => {
res.json(result);
}).catch(next);
};
// 领福利-领取优惠券API
exports.receiveCoupons = (req, res, next) => {
let uid = req.query.uid;
let actId = req.query.actId;
let nums = req.query.nums;
let shareUid = req.query.shareUid;
let openId = req.query.openId;
inviteModel.receiveCoupons({
uid: uid,
activityId: actId,
nums: nums,
shareUid: shareUid,
openId: openId
}).then((result) => {
if (result.code === 200) {
result.data.goUrl = result.data.goUrl +
'?amount=' + result.data.couponAmount +
'&sign=' + md5(result.data.couponAmount + secretKey);
}
res.json(result);
}).catch(next);
};
// 好友领取优惠券成功页面
exports.myCoupons = (req, res, next) => {
let shareUid = req.params[0];
let amount = req.params[1];
let actId = req.params[2];
let nums = req.params[3];
let sign = req.query.sign;
let uid = req.cookies.inviteUid || '';
// 这个只是过滤一下非法的参数
if (md5(amount + secretKey) !== sign || _.isEmpty(uid)) {
res.redirect('/');
return false;
}
inviteModel.myCoupons({
uid: uid,
shareUid: shareUid,
nums: nums,
amount: amount,
activityId: actId
}).then((result) => {
// 非法参数跳到首页
if (result[0].isGo || result[0].isEmpty) {
res.redirect('/');
return false;
}
res.render('invite/mycoupons', {
module: 'activity',
page: 'invite',
result: result[0],
userInfo: result[1],
amount: amount,
title: inviteTitle
});
}).catch(next);
};
// 好友领取完页面
exports.shareover = (req, res) => {
let amount = req.query.amount * 1 || 5;
let sign = req.query.sign;
if (md5(amount + secretKey) !== sign) {
res.redirect('/');
return false;
}
res.render('invite/shareover', {
module: 'activity',
page: 'invite',
result: {
amount: amount
},
title: inviteTitle
});
};
// 接收微信返回后的信息
exports.getwxinfo = (req, res, next) => {
let url = req.query.url;
let code = req.query.code;
inviteModel.getWxUserInfo({
code: code
}).then((result) => {
if (result === false) {
res.redirect('/');
} else {
res.cookie('wxUserInfo', result, {
domain: 'yohobuy.com'
});
res.redirect(url);
}
}).catch(next);
};
// 活动结束页面
exports.over = (req, res) => {
res.render('invite/over', {
module: 'activity',
page: 'invite',
result: [],
title: inviteTitle
});
};
... ...
/* eslint no-unused-vars: ["error", {"args": "none"}] */
'use strict';
const model = require('../models/live');
const status = {
wait: 0,
living: 1,
end: 2
};
const liveModel = require('../models/live');
exports.index = (req, res, next) => {
liveModel.getAllList().then(result => {
res.render('live/entry', {
title: '直播列表',
module: 'activity',
page: 'entry',
best: result[0],
living: result[1],
pre: result[2],
record: result[3],
content: result[4],
isApp: req.yoho.isApp
});
}).catch(next);
};
exports.main = (req, res, next) => {
const isReplay = /^\/live\/replay\//.test(req.path);
const id = req.params.id;
res.locals.liveRoom = id;
res.locals.module = 'activity';
res.locals.page = 'live-play';
model.fetchInfo(id, isReplay)
.then(result => {
if (!result.data) {
return next();
}
res.render('live/play', result.data);
})
.catch(next);
};
exports.barrage = (req, res, next) => {
const type = req.query.type;
model.getBarrageHost(type)
.then(result => {
if (result.code !== 200) {
next(result.message);
return;
}
res.json(result);
})
.catch(next);
};
exports.replayBarrage = (req, res, next) => {
const id = req.query.id;
const startTime = req.query.startTime;
const timeInterval = req.query.timeInterval;
model.getReplyBarrage(id, startTime, timeInterval)
.then(result => {
res.json(result);
})
.catch(next);
};
... ...
'use strict';
const serviceApi = global.yoho.ServiceAPI;
const api = global.yoho.API;
const helpers = global.yoho.helpers;
const crypto = global.yoho.crypto;
const queryString = require('querystring');
const Promise = require('bluebird');
const co = Promise.coroutine;
const headerModel = require('../../../doraemon/models/header'); // 头部model
const _ = require('lodash');
const getResource = code => {
return serviceApi.get('operations/api/v5/resource/get', {
content_code: code,
platform: 'iphone'
});
};
const vip = (limit) => {
return api.get('', {
method: 'app.student.vip',
limit: limit || 60
});
};
const verifiedStudentTotal = () => {
return api.get('', {
method: 'app.student.verifiedStudentTotal'
});
};
const getProvince = () => {
return api.get('', {
method: 'app.studentMarket.getAddressList'
}, {
cache: true
});
};
const getSchool = code => {
return api.get('', {
method: 'app.studentMarket.getSchoolInfoList',
areaCode: code
});
};
const getEducationLevelList = () => {
return api.get('', {
method: 'app.studentMarket.getEducationLevelList'
});
};
const userAcquireStatus = (uid, couponIds) => {
return api.get('', {
method: 'app.coupons.userAcquireStatus',
uid: uid,
couponIds: couponIds
});
};
const verifyStudent = (uid, collegename, educationdegree, enrollmentyear) => {
return api.get('', {
method: 'app.student.verifyStudent',
uid: uid,
client_type: 'h5',
college_name: collegename,
enrollment_year: enrollmentyear,
education_degree: educationdegree
});
};
const verifyIdentity = (uid, certno, name, pageurl) => {
return api.get('', {
method: 'app.student.verifyIdentity',
uid: uid,
client_type: 'h5',
cert_no: certno,
name: name,
page_url: pageurl
});
};
const getUser = (uid) => {
if (!uid) {
return Promise.resolve({
code: 200,
data: {}
});
}
return api.get('', {
method: 'app.passport.profile',
uid: uid
}, {
cache: true
});
};
/* 获取用户或者环境相关数据*/
const getPlatForm = (req) => {
let userAgent = req.get('User-Agent');
let yoho = {};
let uids = req.get('User-Agent').match(/uid=([^;]+)/i);
let versions = req.get('User-Agent').match(/app_version=([^;]+)/i);
let arrs = [];
let isNewVersion = false;
const isProduction = process.env.NODE_ENV === 'production';
// console.log(req.get('User-Agent'));
// console.log(req.query.uid);
yoho.isiOS = /\(i[^;]+;( U;)? CPU.+Mac OS X/i.test(userAgent);
yoho.isAndroid = /Android/i.test(userAgent);
yoho.isApp = /YohoBuy/i.test(req.get('User-Agent')) || (req.query.app_version && req.query.client_type);
yoho.app_version = versions && versions.length === 2 ? versions[1] : '';
yoho.app_version = yoho.app_version || req.query.app_version || '';
if (yoho.app_version) {
yoho.app_version = _.toString(yoho.app_version);
arrs = yoho.app_version.split('.');
if (arrs.length > 2) {
if (arrs[0] && +arrs[0] < 4) {
isNewVersion = false;
} else if (arrs[1] && +arrs[1] < 9) {
isNewVersion = false;
} else if (arrs[2] && +arrs[2] <= 0) {
isNewVersion = false;
} else {
isNewVersion = true;
}
}
}
yoho.isSupportStudent = !yoho.isApp || isNewVersion;
yoho.http = 'http:';
if (isProduction) {
yoho.http = 'https:';
}
yoho.uid = uids && uids.length === 2 ? uids[1] : ''; // 8041246
yoho.uid = req.user.uid || yoho.uid || req.query.uid || '';
yoho.isLogin = yoho.uid ? true : false;
return co(function*() {
let data = yield getUser(yoho.uid);
yoho.isStudent = data.data && data.data.vip_info && data.data.vip_info.is_student ? true : false;
// yoho.isStudent = false;
// console.log(yoho);
return yoho;
})();
};
exports.index = (req, res, next) => {
let code = 'a83b7d55324fb65f96c1f85a3387ebd8';
let uid = req.__USER__.uid;
let options;
let noLoginUrl = helpers.urlFormat('/activity/student/register') + '?openby:yohobuy={"action":"go.weblogin","params":{"jumpurl":{"url":"' + req.__USER__.http + '//m.yohobuy.com/activity/student"}}}';
Promise.all([getResource(code), vip()]).then(datas => {
let coupons,
activities,
banner,
icons,
link,
// url,
// param,
couponids = [];
(datas[0].data || []).forEach((item) => {
switch (item.template_name) {
case 'getCoupon':
coupons = item;
break;
case 'image_list':
activities = item;
break;
case 'focus':
banner = item;
break;
case 'recommend_content_five':
icons = item;
break;
case 'link':
link = item;
break;
default:
// other = item;
break;
}
});
if (coupons && coupons.data) {
coupons.link = link && link.data ? link.data[0].url + (req.__USER__.isApp ? '&app_version=' + req.__USER__.app_version : '') : '';
coupons.data = (coupons.data || []).map((item) => {
// let url = item.image.url;
couponids.push(item.couponID);
// if (!req.__USER__.isLogin) {
// url = 'http://m.yohobuy.com/activity/student/register?openby:yohobuy={"action":"go.weblogin","params":{"jumpurl":{"url":"http://m.yohobuy.com/activity/student"}}}';
// }
//
// item.image.noLoginUrl=url;
return item;
});
}
return userAcquireStatus(uid, couponids.join(',')).then((cous) => {
coupons.data = (coupons.data || []).map((item)=>{
item.status = 1;
(cous.data || []).forEach((it) => {
if (+it.couponId === +item.couponID) {
item.hasNum = it.hasNum;
item.status = it.status;
}
});
item.couponID = crypto.encryption('yoho9646abcdefgh', item.couponID);
return item;
});
datas[1].data = datas[1].data || {};
datas[1].data.product_list = (datas[1].data.product_list || []).map(function(value) {
value.goodsId = value.goods_list[0].goods_id;
value.url = helpers.urlFormat(`/product/pro_${value.product_id}_${value.goodsId}/${value.cn_alphabet}.html`) + `?openby:yohobuy={"action":"go.productDetail","params":{"product_skn":${value.product_skn}}}`;
return value;
});
options = {
isApp: req.__USER__.isApp,
goods: datas[1].data.product_list,
banner: banner,
icons: icons,
coupons: coupons,
activities: activities,
isStudent: req.__USER__.isStudent,
isSupportStudent: req.__USER__.isSupportStudent,
isLogin: req.__USER__.isLogin,
title: '有货学生专享优惠',
http: req.__USER__.http,
uid: req.__USER__.uid,
app_version: req.__USER__.app_version,
isAppNoLogin: req.__USER__.isApp && !req.__USER__.isLogin,
noLoginUrl: noLoginUrl
};
if (!req.__USER__.isApp) {
options.pageHeader = headerModel.setNav({
navTitle: options.title,
navBtn: true
});
}
options.loginUrl = '//m.yohobuy.com/activity/student/register';
if (options.isApp) {
if (options.isLogin) {
if (options.isStudent) {
options.loginUrl = false;
} else {
options.loginUrl = options.loginUrl + '?openby:yohobuy={"action":"go.h5","params":{"islogin":"N","url":"' + req.__USER__.http + '//m.yohobuy.com/activity/student/register"}}';
}
} else {
// no login
options.loginUrl = options.loginUrl + '?openby:yohobuy={"action":"go.weblogin","params":{"jumpurl":{"url":"' + req.__USER__.http + '//m.yohobuy.com/activity/student/register","antarget":"1"}}}';
}
} else {
if (options.isLogin) {
if (options.isStudent) {
options.loginUrl = false;
}
}
}
// console.log(options);
res.render('student', options);
});
}).catch(next);
};
exports.province = (req, res, next) => {
getProvince().then((result) => {
res.json(result);
}).catch(next);
};
exports.register = (req, res, next) => {
let years = [],
refer;
for (let i = 0; i < 8; i++) {
years.push((new Date()).getFullYear() - i);
}
Promise.all([verifiedStudentTotal(), getEducationLevelList()]).then((arr) => {
if (req.__USER__.isStudent) {
refer = '/activity/student?uid=' + req.__USER__.uid;// 所有认证过的,都跳转学生首页
res.redirect(helpers.urlFormat(refer));
} else {
res.render('register', {
title: '认证信息填写',
isApp: req.__USER__.isApp,
count: arr[0].data,
educations: arr[1].data,
educationsStr: JSON.stringify(arr[1].data),
years: years,
yearsStr: JSON.stringify(years),
module: 'activity',
page: 'register',
http: req.__USER__.http
});
}
}).catch(next);
};
exports.school = (req, res, next) => {
let code = req.query.code;
getSchool(code).then((result) => {
res.json(result);
}).catch(next);
};
exports.verifyidentity = (req, res, next) => {
// let uid=req.user.id;
let params = req.body;
let url = 'http://m.yohobuy.com/activity/student/verify?' +
queryString.stringify({
college_name: params.college_name,
education_degree: params.education_degree,
enrollment_year: params.enrollment_year
}) + '&';
let uid = req.__USER__.uid;
verifyIdentity(uid, params.cert_no, params.name, url).then((result) => {
res.json(result);
}).catch(next);
};
exports.verifystudent = (req, res, next) => {
let params = req.query;
let uid = req.__USER__.uid;
Promise.all([verifiedStudentTotal(), vip(), verifyStudent(uid, params.college_name, params.education_degree, params.enrollment_year, params.token)])
.then((datas) => {
let isverify = false,
prompt = '您的学校信息未通过审核';
if (datas[2].code === 200) {
if (datas[2].data.isStudent === 1) {
isverify = true;
prompt = datas[2].data.prompt;
}
} else {
if (datas[2].code === 500) {
prompt = '请重新认证!';
} else {
prompt = datas[2].message;
}
}
datas[1].data.product_list = datas[1].data.product_list.map(function(value) {
value.goodsId = value.goods_list[0].goods_id;
value.url = helpers.urlFormat(`/product/pro_${value.product_id}_${value.goodsId}/${value.cn_alphabet}.html`) + `?openby:yohobuy={"action":"go.productDetail","params":{"product_skn":${value.product_skn}}}`;
return value;
});
return getUser(uid).then((user) => {
res.render('verify', {
isApp: req.__USER__.isApp,
count: datas[0].data,
goods: datas[1].data.product_list,
isverify: isverify,
prompt: prompt,
isLogin: user.data && user.data.vip_info && user.data.vip_info.is_student ? true : false,
title: '学生身份认证',
http: req.__USER__.http,
uid: req.__USER__.uid,
app_version: req.__USER__.app_version
});
}).catch(next);
}).catch(next);
};
exports.detail = (req, res) => {
let type = req.params.type,
options;
if (type === 'renzhen') {
options = {
title: '认证协议',
isRenZhen: true
};
} else {
options = {
title: '更多学生权益',
isQuanYi: true
};
}
options.isApp = req.__USER__.isApp;
res.render('detail', options);
};
exports.isLogin = (req, res, next) => {
// let refer = req.cookies.refer;
let url = req.get('referer') || '/activity/student/register';
getPlatForm(req).then((yoho)=>{
if (yoho.uid) {
req.__USER__ = yoho;
next();
return;
}
// refer = decodeURI(req.cookies.refer)||req.get("refer");
//
// if (refer) {
// refer = decodeURI(req.cookies.refer)||req.get("refer");
// } else {
// refer = '/activity/student/register';
// }
if (req.path === '/student/register' && !yoho.isStudent) {
url = '/activity/student/register';
}
res.redirect(helpers.urlFormat('/signin.html', {
refer: url
}));
}).catch(next);
};
exports.getUser = (req, res, next)=>{
getPlatForm(req).then((yoho)=>{
req.__USER__ = yoho;
next();
}).catch(next);
};
... ...
var API = require('../../../library/api').API;
var api = new API();
var api = global.yoho.API;
/**
* 分享页面基础参数
... ... @@ -9,31 +8,31 @@ var api = new API();
const getPageInfo = (pageInfo) => {
var dest = {};
dest.shareTitle = pageInfo.data.shareTitle;
dest.shareDesc = pageInfo.data.shareContent;
dest.shareImg = pageInfo.data.shareImgUrl;
dest.shareLink = pageInfo.data.shareUrl;
dest.code = pageInfo.code;
dest.activityID = pageInfo.id;
dest.title = pageInfo.data.h5Title;
dest.activityDesc = pageInfo.data.activityDesc;
dest.couponPic = pageInfo.data.couponPic;
dest.oldUserCouponPic = pageInfo.data.oldUserCouponPic;
dest.mobile = pageInfo.data.mobile;
dest.wechatShare = true;
// 强制活动开始,活动上线产品要求这样设置
pageInfo.data.flag = 1;
if (pageInfo.data.flag === 1) {
dest.bgImg = pageInfo.data.activityNormalPic;
} else {
dest.bgImg = pageInfo.data.activityEndPic;
dest.ended = true;
if (pageInfo && pageInfo.data) {
dest.shareTitle = pageInfo.data.shareTitle;
dest.shareDesc = pageInfo.data.shareContent;
dest.shareImg = pageInfo.data.shareImgUrl;
dest.shareLink = pageInfo.data.shareUrl;
dest.code = pageInfo.code;
dest.activityID = pageInfo.id;
dest.title = pageInfo.data.h5Title;
dest.activityDesc = pageInfo.data.activityDesc;
dest.couponPic = pageInfo.data.couponPic;
dest.oldUserCouponPic = pageInfo.data.oldUserCouponPic;
dest.mobile = pageInfo.data.mobile;
dest.wechatShare = true;
// 强制活动开始,活动上线产品要求这样设置
pageInfo.data.flag = 1;
if (pageInfo.data.flag === 1) {
dest.bgImg = pageInfo.data.activityNormalPic;
} else {
dest.bgImg = pageInfo.data.activityEndPic;
dest.ended = true;
}
dest.message = pageInfo.data.returnMsg;
}
dest.message = pageInfo.data.returnMsg;
// 清空变量,释放内存
pageInfo = {};
return dest;
};
... ...
'use strict';
const api = global.yoho.API;
const camelCase = global.yoho.camelCase;
const _ = require('lodash');
/* 微信相关 */
const wxCode = {
wxAppId: 'wx75e5a7c0c88e45c2',
wxAppSecret: 'ce21ae4a3f93852279175a167e54509b'
};
/**
* 获取微信授权地址
* @param callback
* @returns {string}
*/
const getWxOauthUrl = (callback) => {
return 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' +
wxCode.wxAppId + '&redirect_uri=' + callback +
'&response_type=code&scope=snsapi_userinfo#wechat_redirect';
};
/**
* 生成分享url
* @param shareUid
* @param actId
* @param nums
* @returns {string}
*/
const createShareUrl = (shareUid, actId, nums) => {
return 'http://m.yohobuy.com/activity/invite/share_' + shareUid + '_' + actId + '_' + nums + '.html';
};
/**
* 根据第三方id,查询绑定信息
* @param {[string || array]} openIds 第三方id数组
* @return {[array]}
*/
const getBindLogByOpenId = (openIds) => {
openIds = _.isArray(openIds) ? openIds : [openIds];
return api.get('', {
method: 'wap.invite.getBindLogByOpenId',
openIds: openIds.join(',')
});
};
/**
* 合并第三方头像和昵称
* @param {[array]} data 邀请的用户列表,最主要的是openId字段
* @return {[array]}
*/
const mergeBindLogDate = (data) => {
let openIds = [];
let photo = '//static.yohobuy.com/m/v1/activity/newyear/images/108.png';
// 取5条
data = _.slice(data, 0, 5);
_.forEach(data, (req) => {
openIds.push(req.openId);
});
return getBindLogByOpenId(openIds).then(result => {
if (!_.isEmpty(result.data)) {
_.forEach(data, (req, key) => {
data[key].img = photo;
_.forEach(result.data, (bind) => {
if (req.openId === bind.openId) {
data[key].img = _.isEmpty(bind.snsHeadimg) ?
photo : bind.snsHeadimg;
data[key].nick = bind.snsNick;
}
});
});
}
return data;
});
};
/**
* 获取分享页面列表数据
* @param {[int]} uid 用户id
* @param {[int]} activityId 活动id
* @return {[array]}
*/
const index = (params) => {
params = params || {};
return api.get('', Object.assign({
method: 'wap.invite.index'
}, params
)).then((result) => {
let firstData = {
isNil: false,
isEmpty: false,
isFive: false,
isGo: false,
remainData: ['', '', '', '', ''],
data: []
};
switch (result.code) {
case 401:
// 没有分享记录
firstData.isNil = true;
break;
case 200:
return mergeBindLogDate(result.data).then(data => {
let len = data.length;
// 判断是否否5条分享
result.data = data;
if (len === 0) {
firstData.isEmpty = true;
} else if (len < 5) {
firstData.remainData =
_.slice(firstData.remainData, 0, 5 - len);
} else {
firstData.isFive = true;
firstData.remainData = [];
}
return Object.assign(firstData, camelCase(result));
});
default:
// 活动状态不正确
firstData.isGo = true;
break;
}
return Object.assign(firstData, camelCase(result));
});
};
/**
* 通过手机号发送验证码
* @param {[int]} area 区域,中国:86
* @param {[string]} mobile 手机号
* @return {[array]}
*/
const sendRegCodeToMobile = (params) => {
return api.get('', Object.assign({
method: 'app.register.sendRegCodeToMobile'
}, params));
};
/**
* 发送已注册用户参与活动的优惠券
* @param {[string]} mobile 手机号
* @param {[int]} activityId 活动id
* @return {[array]}
*/
const checkOldUserCoupon = (params) => {
return api.get('', Object.assign({
method: 'wap.invite.checkOldUserCoupon'
}, params));
};
/**
* 验证手机验证码是否正确
* @param {[int]} area 区域,中国:86
* @param {[string]} mobile 手机号
* @param {[int]} code 验证码
* @return {[array]}
*/
const validRegCode = (params) => {
return api.get('', Object.assign({
method: 'app.register.validRegCode'
}, params));
};
/**
* 手机账号注册
* @param {[string]} mobile 手机号
* @param {[string]} activityName 活动名称
* @return {[array]}
*/
const register = (params) => {
return api.get('', Object.assign({
method: 'wap.invite.register'
}, params));
};
/**
* 微信好友获取红包方法(即分享出去的地址)
* @param {[int]} uid 分享用户id
* @param {[int]} activityId 活动id
* @param {[int]} nums 发送优惠券的数量
* @param {[String]} openId 微信的union_id
* @param {[String]} nickName 微信昵称
* @param {[String]} headImgUrl 微信头像
* @returns {[array]}
*/
const shareModel = (params) => {
let firstData = {
isEmpty: false,
isFive: false,
isGo: false,
remainData: ['', '', '', '', ''],
data: []
};
let listData = [];
// 这里面的逻辑就是获取第三方用户的头像和昵称,然后插入数据和更新数据
return api.get('', Object.assign({
method: 'wap.invite.share'
}, params))
.then((result) => {
// list为空,说明不是分享者本人
if (result.code !== 200 || _.isEmpty(result.data.list)) {
return result;
}
return mergeBindLogDate(result.data.list).then(data => {
let len = data.length;
// 判断是否满5条分享
listData = data;
if (len === 0) {
firstData.isEmpty = true;
} else if (len < 5) {
firstData.remainData =
_.slice(firstData.remainData, 0, 5 - len);
} else {
firstData.isFive = true;
firstData.remainData = [];
}
firstData.data = listData;
result.data.list = firstData;
// 释放内存
listData = [];
return camelCase(result);
});
});
};
/**
* 邀请好友赢福利之后领取优惠券
* @param {[int]} uid 用户id
* @param {[int]} activityId 活动id
* @param {[int]} nums 发送优惠券的数量
* @param {[int]} shareUid 分享者的uid
* @param {[string]} openId 微信的union_id
* @returns {[array]}
*/
const receiveCoupons = (params) => {
return api.get('', Object.assign({
method: 'wap.invite.receiveCoupons'
}, params));
};
/**
* 获取分享列表和用户信息
* @param {[int]} uid 用户id
* @param {[int]} shareUid 分享着uid
* @param {[int]} nums 发送优惠券的数量
* @param {[int]} amount 金额
* @param {[int]} activityId 活动id
* @return {[array]}
*/
const myCoupons = (params) => {
let mobile;
let firstData = {
isEmpty: false,
isFive: false,
isGo: false,
remainData: ['', '', '', '', ''],
data: []
};
let listData = [];
return api.get('', Object.assign({
method: 'wap.invite.myCoupons'
}, params)).then((result) => {
if (result.code !== 200) {
return result;
}
return mergeBindLogDate(result.data.list).then(data => {
listData = data;
if (listData.length < 5) {
firstData.remainData =
_.slice(firstData.remainData, 0, 5 - listData.length);
} else {
firstData.isFive = true;
firstData.remainData = [];
}
firstData.data = listData;
result.data.list = firstData;
mobile = result.data.myProfile.mobile;
result.data.myProfile.encMobile = mobile.replace(mobile.substring(3, 7), '****');
// 释放内存
listData = [];
return camelCase([result.data.list, result.data.myProfile]);
});
});
};
/**
* 授权后获取微信用户信息
* @return {[array]}
*/
const getWxUserInfo = (params) => {
let url1 = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' +
wxCode.wxAppId + '&secret=' + wxCode.wxAppSecret + '&code=' +
params.code + '&grant_type=authorization_code';
let url2 = 'https://api.weixin.qq.com/sns/userinfo?lang=zh_CN';
return api._requestFromAPI({
url: url1,
qs: {},
json: true,
gzip: true,
timeout: 3000
})
.then((result) => {
if (_.isEmpty(result.openid)) {
return false;
}
url2 = url2 + '&access_token=' + result.access_token +
'&openid=' + result.openid;
return api._requestFromAPI({
url: url2,
qs: {},
json: true,
gzip: true,
timeout: 3000
})
.then((result2) => {
return result2;
});
});
};
module.exports = {
index,
shareModel,
getBindLogByOpenId,
sendRegCodeToMobile,
checkOldUserCoupon,
validRegCode,
register,
receiveCoupons,
myCoupons,
createShareUrl,
getWxOauthUrl,
getWxUserInfo
};
... ...
'use strict';
const moment = require('moment');
const service = global.yoho.ServiceAPI;
const liveAPI = global.yoho.LiveAPI;
const contentCodeConfig = require('../../../config/content-code');
const resourcesProcess = require(`${global.utils}/resources-process`);
const helpers = global.yoho.helpers;
const _formatTime = (timestamp, b) => {
let date = b ? 'M.D ' : 'M月D日';
let time = 'HH:mm';
let startTime = moment(timestamp);
let now = moment();
let diff = moment.duration(startTime.clone().startOf('day') - now.startOf('day')).days();
switch (diff) {
case 0:
date = '[今天]';
break;
case 1:
date = '[明日]';
break;
default:
null;
}
return startTime.format(`${date}${time}`);
};
/**
* 根据 时长(秒) 返回 时长格式化后的 字符串 HH:mm:ss
*/
const _getHumanDuration = (duration) => {
duration = moment.duration(duration, 's');
let durationH = duration.hours();
let durationM = duration.minutes();
let durationS = duration.seconds();
duration = [durationH, durationM, durationS].map((item) => {
if (item < 10) {
return `0${item}`;
} else {
return String(item);
}
});
return `${duration[0]}:${duration[1]}:${duration[2]}`;
};
// 获取顶部bannel
let _getBannerData = () => {
return service.get('operations/api/v5/resource/get', {
content_code: contentCodeConfig.live.index,
platform: 'iphone'
}, {
code: 200,
cache: true
}).then((result) => {
return result && result.data ? resourcesProcess(result.data) : [];
});
};
// 获取精选视频
const _getBestList = () => {
return liveAPI.get('v1/living/best', {}, {
code: 200,
cache: false
}).then(result => {
let list = result && result.data || [];
if (result && result.data && result.data.length !== 2) {
result.data = [];
}
for (let item of list) {
switch (item.living) {
case 0:
item.pre_living = true;
break;
case 1:
default:
item.now_living = true;
break;
case 2:
// 直播结束不显示
result.data = [];
break;
}
// 格式化时间
item.starting_time = _formatTime(item.starting_time * 1000, true);
}
return result && result.data || [];
});
};
// 获取直播中所有视频
const _getLivingList = () => {
return liveAPI.get('v1/living/listing', {}, {
code: 200,
cache: false
}).then(result => {
return result && result.data || [];
});
};
// 获取直播预告列表
const _getPrelivingList = () => {
return liveAPI.get('v1/living/starting', {}, {
code: 200,
cache: false
}).then(result => {
let list = result && result.data || [];
for (let item of list) {
item.starting_time = _formatTime(item.starting_time * 1000);
}
return result && result.data || [];
});
};
// 获取回看列表
const _getRecordList = () => {
return liveAPI.get('v1/living/replaying', {}, {
code: 200,
cache: false
}).then(result => {
return result && result.data || [];
});
};
// 返回所有数据
const getAllList = () => {
return Promise.all([_getBestList(), _getLivingList(), _getPrelivingList(), _getRecordList(), _getBannerData()]);
};
// 获取 回放视屏 信息
const fetchReplayInfo = (videoID) => {
let url = 'v1/living/detail';
let data = { video_id: videoID };
let options = { cache: true };
return liveAPI.get(url, data, options)
.then(result => {
if (result && result.data) {
let d = result.data;
d.background = helpers.image(d.background, 640, 968);
d.pic = helpers.image(d.pic, 640, 968);
d.master_pic = helpers.image(d.master_pic, 180, 180);
d.humanTime = _formatTime(data.live_start_time * 1000);
d.video_src = d.url;
// 自定义数据
d.duration = '00:00:00'; // 回看时长 前端JS根据video获取
d.living = 3; // 重播 状态
d.canPlay = true;
d.atEnd = false;
d.isReplay = true;
}
return result || {};
});
};
// 获取 直播视屏 信息
const fetchLiveInfo = (roomID) => {
let url = 'v1/living/detail';
let data = { room_id: roomID };
return liveAPI.get(url, data)
.then(result => {
if (result && result.data) {
let d = result.data;
d.background_pic = helpers.image(d.background_pic, 640, 968);
d.pic = helpers.image(d.pic, 640, 968);
d.master_pic = helpers.image(d.master_pic, 180, 180);
d.humanTime = _formatTime(d.starting_time * 1000); // 预告 开始时间
d.video_src = d.hls_downstream_address;
// 自定义数据
d.duration = _getHumanDuration(d.live_last_time);
d.canPlay = d.living === 1;
d.notBegin = d.living === 0;
d.atEnd = d.living === 2;
}
return result || {};
});
};
const fetchInfo = (id, isReplay) => {
if (isReplay) {
return fetchReplayInfo(id);
} else {
return fetchLiveInfo(id);
}
};
// 获取 直播 弹幕 host
const getBarrageHost = (type) => {
return liveAPI.get('v1/system/gethosts', { type });
};
const getReplyBarrage = (videoID, startTime, timeInterval) => {
const url = 'v1/living/getreplaybarrage';
const data = {
startTime,
timeInterval,
video_id: videoID
};
const options = { cache: true };
return liveAPI.get(url, data, options);
};
// 处理直播时间
module.exports = {
getAllList,
fetchInfo,
getBarrageHost,
getReplyBarrage
};
... ...
... ... @@ -8,8 +8,8 @@
const request = require('request-promise');
const Promise = require('bluebird');
const crypto = require('crypto');
const logger = require('../../../library/logger');
const cache = require('../../../library/cache');
const logger = global.yoho.logger;
const cache = global.yoho.cache;
// 此处请勿使用有货公众号的 appId, 此处使用的是 女生志 的appId
const appId = 'wxb52ec6a352f0b090';
... ...
... ... @@ -11,6 +11,9 @@ const cRoot = './controllers';
const coupon = require(`${cRoot}/coupon`);
const wechat = require(`${cRoot}/wechat`);
const student = require(`${cRoot}/student`);
const live = require(`${cRoot}/live`);
const invite = require(`${cRoot}/invite`);
// routers
... ... @@ -22,4 +25,41 @@ router.get('/coupon/verify', coupon.verify);
router.get('/wechat/share', wechat.wechatShare);
router.get('/student', student.getUser, student.index);
router.get('/student/register', student.isLogin, student.register);
router.get('/student/province', student.province);
router.get('/student/school', student.school);
router.post('/student/join', student.isLogin, student.verifyidentity);
router.get('/student/verify', student.isLogin, student.verifystudent);
router.get('/student/detail/:type', student.getUser, student.detail);
// router.get('/student/getCoupons',student.getCoupons)
router.get('/live', live.index);
router.get('/live/barrage', live.barrage);
router.get('/live/replay/barrage', live.replayBarrage);
router.get('/live/replay/:id', live.main);
router.get('/live/:id', live.main);
router.get('/invite', invite.index);
router.get('/invite/index', invite.index);
router.get(/\/invite\/share_([\d]+)_([\d]+)_([\d]+).html/, invite.share);
router.get('/invite/sendRegCodeToMobile', invite.sendRegCodeToMobile);
router.get('/invite/checkOldUserCoupon', invite.checkOldUserCoupon);
router.get('/invite/validRegCode', invite.validRegCode);
router.get('/invite/register', invite.register);
router.get('/invite/receiveCoupons', invite.receiveCoupons);
router.get(/\/invite\/mycoupons_([\d]+)_([\d]+)_([\d]+)_([\d]+).html/, invite.myCoupons); // 好友领取完优惠券的页面
router.get('/invite/getwxinfo', invite.getwxinfo);
router.get('/invite/shareover', invite.shareover);
router.get('/invite/over', invite.over);
module.exports = router;
... ...
{{#unless isApp}}
<header class="yoho-header">
<a href="javascript:history.go(-1);" class="iconfont nav-back close"></a>
<p class="nav-title">{{title}}</p>
</header>
{{/unless}}
{{#isQuanYi}}
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
<section class='s-text'>
<h6>权益1:新品立享9折</h6>
<p>1、学生购买原价新品时,可立即享受9折优惠,与VIP折扣不可同时享受。</p>
<h6>权益2:每满100返100有货币</h6>
<p>1、学生购买商品时,商品金额每满100元即可获得100有货币;</p>
<p>2、有货币有效期:获得当日至次年12月31日,逾期自动作废;</p>
<p>3、查看有货币:登录后,点击“个人中心”在“我的有货币”中可以查看有货币余额及明细</p>
<h6>权益3:免单抽奖</h6>
<p>1、每月将在累计购物金额最高的学校中,抽取3名幸运学生用户,获得免单资格;</p>
<p>2、免单用户名单将在每月第1个工作日公布在有货微信公众号上,可关注【有货YOHOBUY】;</p>
<p>3、免单用户将在中奖次日获得与实付金额等额的现金券,使用时间:中奖当月。</p>
<h6>权益4:学生专享活动</h6>
<p>1、每月不定期的开展学生专享活动,可打开有货APP推送,及时查收学生专享活动通知。</p>
<h6></h6>
</section>
</div>
</div>
{{/isQuanYi}}
{{#isRenZhen}}
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
<section class='s-text'>
<h6>认证协议</h6>
<p>1、全日制大学及硕士博士研究生</p>
<p>2、学校在可选范围内,有部分学校可能暂未收录,后期会尽快添加</p>
<p>3、每个学号只能认证一个有货账户</p>
</section>
</div>
</div>
{{/isRenZhen}}
... ...
<div class="invite-page invite-page-bg">
<div class="invite-content-list">
<div>
<img src="//cdn.yoho.cn/m-yohobuy-node/assets/img/activity/invite/title_new.png" />
<br />
<p class="fz14">只需1位小伙伴领取,<br/>即得<strong class="fz17">10元现金券</strong><br/>全部领完,可继续发,上不封顶!<br/>奔跑吧,潮人们!</p>
<a href="javascript:void(0)" class="weal-btn fz16" id="send_gift">立刻发福利</a>
<h2 class="rule-tit fz15">活动细则</h2>
<ol class="rule-con hide">
<li>本次活动所获现金券仅限Yoho!Buy有货商城购买商品使用,不得转借他人,不可兑换现金;</li>
<li>一个订单只可使用一张优惠券,优惠券需在有效期内使用,过期则无法使用;</li>
<li>使用优惠券支付的订单,退款结算按照实际支付金额退款,优惠券返还账户,且有效期不变;</li>
<li>本活动仅限普通消费者参与,如有代购或批发行为Yoho!Buy有货有权取消订单并做相关处理;</li>
<li>每位会员有10次发福利机会,超过10次不再赠送现金券;</li>
<li>Yoho!Buy有货在法律允许范围内拥有本规则解释权。</li>
</ol>
</div>
<div class="share-tag"><img src="//static.yohobuy.com/m/v1/img/invite/yd_share.png"></div>
</div>
</div><!--/invite-page-->
\ No newline at end of file
... ...
<div class="invite-page invite-page-bg">
<div class="invite-content-list">
{{#if result.isEmpty}}
<div class="coupon-box coupon-box02 relative mar-top-a">
<p class="fz9 bold">YUAN</p>
<strong class="fz18">现金券</strong>
<p class="fz9 bold">CPOUPON</p>
<div class="pirbox absolute">
<em class="absolute"></em>0
</div>
</div>
<p class="draw-coupon fz19 bold t-shadow">至少1位小伙伴领取<br />才能获得现金券!</p>
<p class="goon fz11 t-shadow">~继续呼唤小伙伴吧~</p>
<p class="t-shadow">(点击右上角可以继续召集哦)</p>
<ul class="list-port">
<li><p class="pic"></p></li>
<li><p class="pic"></p></li>
<li><p class="pic"></p></li>
<li><p class="pic"></p></li>
<li><p class="pic"></p></li>
</ul>
<p><a href="javascript:void(0)" class="weal-btn fz16">再次分享</a></p>
{{else if result.isFive}}
<!-- 现金券 start -->
<div class="coupon-box coupon-box02 relative mar-top-a">
<p class="fz9 bold">YUAN</p>
<strong class="fz18">现金券</strong>
<p class="fz9 bold">CPOUPON</p>
<div class="pirbox absolute">
<em class="absolute"></em>10
</div>
<div class="count-not fz11 absolute"><hr class="fn-left"><hr class="fn-right">已送达您的账户</div>
</div>
<!-- 现金券 end -->
<p class="draw-coupon fz16 bold t-shadow">您的号召力爆棚!!!</p>
<p class="fz11">已有5位小伙伴领取红包</p>
<ul class="list-port">
{{# result.data}}
<li><img class="pic" src="{{img}}"><p class="name">{{nick}}</p><p class="pon">{{couponAmount}}</p></li>
{{/ result.data}}
</ul>
<br />
<p><a href="javascript:void(0)" class="weal-btn fz16">还要发福利</a></p>
<br />
<p class="fz11 t-shadow">优惠券有效期:优惠券到账以后7天内有效(自然天)</p>
{{else}}
<!-- 现金券 start -->
<div class="coupon-box coupon-box02 relative mar-top-a">
<p class="fz9 bold">YUAN</p>
<strong class="fz18">现金券</strong>
<p class="fz9 bold">CPOUPON</p>
<div class="pirbox absolute">
<em class="absolute"></em>10
</div>
<div class="count-not fz11 absolute"><hr class="fn-left"><hr class="fn-right">已送达您的账户</div>
</div>
<!-- 现金券 end -->
<p class="draw-coupon fz19 bold t-shadow">召集5位小伙伴领取,<br>即可再发福利,赶快召唤吧!</p>
<br /><br />
<ul class="list-port">
{{# result.data}}
<li><img class="pic" src="{{img}}"><p class="name">{{nick}}</p><p class="pon">{{couponAmount}}</p></li>
{{/ result.data}}
{{# result.remainData}}
<li><p class="pic"></p></li>
{{/ result.remainData}}
</ul>
<p><a href="javascript:void(0)" class="weal-btn fz16">继续发福利</a></p>
<br />
<p class="fz11 t-shadow">优惠券有效期:优惠券到账以后7天内有效(自然天)</p>
{{/if}}
<div class="share-tag"><img src="//static.yohobuy.com/m/v1/img/invite/yd_share.png"></div>
</div>
</div><!--/invite-page-->
... ...
<div class="invite-page invite-page-bg">
<div class="invite-content-list">
<!-- 已有小伙伴 start -->
<div class="coupon-box coupon-box02 relative mar-top-a">
<p class="fz9 bold">YUAN</p>
<strong class="fz18">现金券</strong>
<p class="fz9 bold">CPOUPON</p>
<div class="pirbox absolute">
<em class="absolute"></em>{{amount}}
</div>
<div class="count-not fz11 absolute"><hr class="fn-left"><hr class="fn-right">已送达您的账户</div>
</div>
<p class="congratu-coupon fz15 t-shadow">{{amount}}元现金券<br/>已自动存入YOHO!有货账户 {{userInfo.encMobile}}</p>
<p class="fz11">(首次下载客户端还能再次领取10元现金券)</p>
<a href="javascript:void(0)" onclick="downLoadApp()" class="download-btn fz16" style="margin-top: 30px;">下载客户端查看</a>
<p><a href="//m.yohobuy.com" class="now-login fz14" style="border-bottom: 1px solid #fff; text-decoration:none;color:#FFF;line-height: normal;">立即登录网站使用</a></p>
<br />
<p class="fz11 t-shadow">优惠券有效期:优惠券到账以后7天内有效(自然天)</p>
<ul class="list-port">
{{# result.data}}
<li><img class="pic" src="{{img}}"><p class="name">{{nick}}</p><p class="pon">{{couponAmount}}</p></li>
{{/ result.data}}
{{# result.remainData}}
<li><p class="pic"></p></li>
{{/ result.remainData}}
</ul>
<!-- 已有小伙伴 end -->
</div>
</div><!--/invite-page-->
\ No newline at end of file
... ...
<div class="invite-page invite-page-bg">
<div class="invite-content-page">
<img src="//cdn.yoho.cn/m-yohobuy-node/assets/img/activity/invite/title_new.png" />
<h2 class="bold fz16">YoHo!Buy有货福利来袭,即领即用!</h2>
<p>全球1000+潮流品牌每日上新!</p>
<div class='invite-group relative'>
<p class="fz13">&nbsp;&nbsp;&nbsp;&nbsp;<span class='bold'>填写您的手机号码,来YOHO潮流!</span>&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p class='fz11'>已注册YOHO!Buy有货的手机号不能领取哦</p><br />
<span class='send-validate-btn'>发送验证码</span>
<input type='text' placeholder='输入手机号' class='invite-mobile' />
<input type='text' placeholder='输入验证码' class='invite-code' />
<input type='hidden' name="actId" class='invite-actId' value='{{result.actId}}' />
<input type="hidden" name="nums" value="{{result.nums}}" />
<input type="hidden" name="shareUid" value="{{result.shareUid}}" />
<input type="hidden" name="openId" value="{{result.openId}}" />
<input type='button' value='领福利' class='invite-btn receive-btn'/>
</div>
<div class='invite-dialog oldget hide'>
<div class='invite-dialog-center'>
<p class="fz13 mtTop03">您已是Yoho!Buy有货的顾客,</p>
<p class="fz13">此福利仅限新用户领取,</p>
<p class="fz13">您只需下载Yoho!Buy有货手机客户端,</p>
<p class="fz13">在【我的】栏目中邀请好友领福利,</p>
<p class="fz13 mtTop04">即可赢取<strong class="fz18">10元现金券</strong></p>
<div class='btn-group'>
<input type='button' class='invite-btn cancelbtn' value='重新输入' />
<input type='button' class='invite-btn download-btn' value='立即查看' />
</div>
</div>
</div><!--/oldget-->
<div class="invite-dialog isreg hide">
<br />
<br />
<p class="fz13 mtTop03">您已经领取福利券!</p>
<p class="fz13">下载Yoho!Buy有货手机客户端,</p>
<p class="fz13">在【我的】栏目中邀请好友领福利,</p>
<p class="fz13 mtTop04">还可赢取<strong class="fz18">10元现金券</strong></p>
<br />
<div class='btn-group'>
<input type='button' class='invite-btn cancelbtn' value='重新输入' />
<input type='button' class='invite-btn download-btn' value='立即查看' />
</div>
</div><!--/isreg-->
<div class="invite-dialog ishint hide">
<div class="fz13 ishint-content">操作失败!</div>
<div class='btn-group'>
<input type='button' class='invite-btn closeBtn fw90' value='确定' />
</div>
</div><!--/ishint-->
</div>
</div><!--/invite-page-->
\ No newline at end of file
... ...
<div class="invite-page invite-page-bg">
<div class="invite-content-list">
<div>
<img src="//cdn.yoho.cn/m-yohobuy-node/assets/img/activity/invite/title_new.png" />
<br />
<p class="fz14">只需1位小伙伴领取,<br/>即得<strong class="fz17">10元现金券</strong><br/>全部领完,可继续发,上不封顶!<br/>奔跑吧,潮人们!</p>
<a href="javascript:void(0)" class="invite-btn fz16">立刻发福利</a>
<h2 class="rule-tit fz15">活动细则</h2>
</div>
<div class='invite-dialog-bg'></div>
<div class='invite-dialog over-color'>
<div class='bold invite-dialog-center'>
<br />
<br />
<p class="fz14 mtTop03">来晚了?!</p>
<p class="fz16">活动已结束!</p>
<br />
<a href='//m.yohobuy.com' class='invite-btn fw90'>继续逛潮牌</a>
</div>
</div>
</div>
</div><!--/invite-page-->
\ No newline at end of file
... ...
<div class="invite-page invite-page-bg">
<div class="invite-content-list">
<div class='mar-top-a'>
<h2 class="fz37 bold">太可惜了,</h2>
<p class="fz15 t-shadow">您与88元现金券擦肩而过!别伤心,</p>
<p class="fz22 bold">赠您{{result.amount}}元现金券</p>
<p class="t-shadow">(仅限Yobo!Buy有货客户端使用)</p>
<!-- 现金券 start -->
<div class="coupon-box coupon-box02 relative mar-top-a">
<p class="fz9 bold">YUAN</p>
<strong class="fz18">现金券</strong>
<p class="fz9 bold">CPOUPON</p>
<div class="pirbox absolute">
<em class="absolute"></em>{{result.amount}}
</div>
<div class="count-not fz11 absolute"><hr class="fn-left"><hr class="fn-right">已送达您的账户</div>
</div>
<!-- 现金券 end -->
<p class="hurry-size fz16 bold">赶紧来Yobo!Buy有货<br/>逛潮牌吧!</p>
<a href="javascript:void(0)" class="download-btn fz16">立即下载客户端</a>
<p class="fz11">优惠券有效期:优惠券到账以后7天内有效(自然天)</p>
</div>
</div>
</div><!--/invite-page-->
\ No newline at end of file
... ...
<div class="yoho-live yoho-page">
{{! 导航条}}
{{#unless isApp}}
<div class="home-header clearfix yoho-header">
<a href="javascript:history.go(-1);" class="iconfont nav-back buriedpoint" data-bp-id="page_header_back_0"></a>
<p class="nav-title">直播列表</p>
</div>
{{/unless}}
{{#content}}
{{! 头部banner}}
{{#if focus}}
{{> resources/banner-top}}
{{/if}}
{{/content}}
{{#if content}}
{{#if best}}
<div class="head_margin"></div>
{{/if}}
{{/if}}
{{! 精选房间}}
{{#if best}}
<div class="liverec">
{{#best}}
<div class="liverec_child">
<a href='http://m.yohobuy.com/activity/live/{{room_id}}?openby:yohobuy={"action":"go.videolive", "params":{"type":"{{living}}","room":"{{room_id}}","bgpic":"{{pic}}"}}'>
<img class="liverec_pic" src="{{image pic 320 320}}" alt="直播预览">
{{#if now_living}}
<p class="living">直播</p>
{{else if pre_living}}
<p class="pre-living">预告 {{starting_time}}</p>
{{/if}}
</a>
<div class="liverec_info">
<img class="liverec_head" src="{{image master_pic 120 120}}" alt="头像">
<div class="liverec_pannel">
<p class="liverec_name clearfix">
<span class="name-name">{{master_name}}</span>
<span class="name-tag">{{master_meta}}</span>
</p>
<p class="liverec_tag">{{title}}</p>
</div>
</div>
</div>
{{/best}}
</div>
{{/if}}
{{! 直播中房间}}
{{#if living}}
<h2 class="living_title">直播中</h2>
{{/if}}
{{#living}}
<div class="liveliving">
<header>
<img class="main-head" src="{{image master_pic 120 120}}" alt="头像">
<div class="header-info">
<p class="main-name">{{master_name}}</p>
<p class="main-tag">{{master_meta}}</p>
</div>
</header>
<section>
<a href='http://m.yohobuy.com/activity/live/{{room_id}}?openby:yohobuy={"action":"go.videolive", "params":{"type":"1","room":"{{room_id}}","bgpic":"{{pic}}"}}'>
<img class="main-bg" src="{{image pic 640 640}}" alt="正在直播">
<p class="main-living">直播</p>
<p class="main-intro">{{title}}</p>
<div class="main-people">
<span class="people-icon"></span>
<p class="people-sum">{{audience_num}}人观看</p>
</div>
</a>
</section>
</div>
{{/living}}
{{! 直播预告列表}}
{{#if pre}}
<div class="live-list">
<h2 class="title">直播预告</h2>
<ul class="list">
{{#pre}}
<li class="pre-list">
<a href='http://m.yohobuy.com/activity/live/{{room_id}}?openby:yohobuy={"action":"go.videolive", "params":{"type":"0","room":"{{room_id}}","bgpic":"{{pic}}"}}'>
<img class="pre-pic" src="{{image pic 150 150}}" alt="直播预览图">
<p class="pre-icon">预告</p>
<p class="pre-time">{{starting_time}}</p>
<div class="pre-pannel">
<p class="pre-title text-overflow">{{title}}</p>
<p class="pre-cast">主播:{{master_name}}</p>
</div>
</a>
</li>
{{/pre}}
</ul>
</div>
{{/if}}
{{! 精彩回看}}
{{#if record}}
<h2 class="living_title">精彩回看</h2>
{{/if}}
{{#record}}
<div class="liveliving">
<header>
<img class="main-head" src="{{image master_pic 120 120}}" alt="头像">
<div class="header-info">
<p class="main-name">{{master_name}}</p>
<p class="main-tag">{{master_meta}}</p>
</div>
</header>
<section>
<a href='http://m.yohobuy.com/activity/live/replay/{{video_id}}?openby:yohobuy={"action":"go.videoreplay", "params":{"videoid":"{{video_id}}","bgpic":"{{pic}}"}}'>
<div class="record-icon"></div>
<img class="main-bg" src="{{image pic 640 640}}" alt="精彩回放">
<p class="main-living">回放</p>
<p class="main-intro">{{title}}</p>
<div class="main-people">
<span class="eye-icon"></span>
<p class="people-sum">{{audience_num}}人看过</p>
</div>
</a>
</section>
</div>
{{/record}}
</div>
... ...
{{! 直播 播放页 }}
<div class="live-wrapper">
{{#canPlay}}
<div class="live-main">
<!-- 视频部分start-->
<!--http://live-hls-pili.1iptv.com/meipai-live/57651bb975b6255acc01444c.m3u8-->
<section id="live_container" class="live-video-main" style="background-image: url('{{pic}}');">
<div id="video_container" class="video_player" data-video="{{video_src}}">
</div>
<div class="live-loading-container">
<div class="live-video-loading">
<div class="img"></div>
<p>加载中</p>
</div>
<div class="live-loading-cover" style="background-image: url('{{pic}}');"></div>
</div>
<div id="live_touch_layer"></div>
<!--弹幕-->
<div class="live-chat-pannel">
<ul id="live_chat_ul">
</ul>
</div>
<!--点赞-->
<div class="live-like-pannel">
<div id="live_like_pannel" class="animate_pannel"></div>
<div class="like-main"></div>
<span id="like_num">0</span>
</div>
<!--播放按钮-->
<div class="live-video-play-button">
<a href="javascript:void(0)">
<div class="img"></div>
</a>
</div>
<!--直播状态-->
<div class="live-status">
<div class="overflow-hidden">
<div class="img"></div>
<div class="live-time">
{{#if isReplay}}
<span>Yoho!Buy回看</span>
{{else}}
<span>Yoho!Buy直播</span>&nbsp;<span id="live_time"></span>
{{/if}}
</div>
<div class="live-num">
{{#if isRelay}}
<span>{{audience_num}}人观看</span>
{{else}}
<span></span>
{{/if}}
</div>
</div>
<div class="title hide" id="live-watermark">
{{#if watemark }}
<span>{{watermark}}</span>
{{/if}}
</div>
</div>
<a href="javascript:;" class="live-btn-share">
<i class="iconfont">&#xe600</i>
</a>
<a href="javascript: history.back();" class="live-btn-close">
<i class="iconfont">&#xe623</i>
</a>
</section>
</div>
{{/canPlay}}
{{!直播已结束}}
<div id="live-state-end" class="live-state is-no-start {{#atEnd}}show{{/atEnd}}">
<div class="live-state-inner" style="background-image: url('{{background_pic}}');">
<div class="live-state__txt">直播已结束</div>
<ul class="live-state-info">
<li class="audience text-center">
<span class="val">{{audience_num}}</span>
<br>
<span class="label">总观看人数</span>
</li>
<li class="duration">
<div class="inner pull-right">
<span class="val">{{duration}}</span>
<br>
<span class="label">直播时长</span>
</div>
</li>
<li class="favorite">
<div class="inner pull-left">
<span class="val">{{like_num}}</span>
<br>
<span class="label">点赞数</span>
</div>
</li>
</ul>
<a href="javascript: history.back();" class="live-btn-close">
<i class="iconfont">&#xe623</i>
</a>
</div>
</div>
{{!直播未开始}}
{{#notBegin}}
<div class="live-state is-no-start show">
<div class="live-state-inner" style="background-image: url('{{background_pic}}');">
<div class="live-state__txt">直播未开始</div>
<div class="live-state-info text-center">
<img src="{{master_pic}}" alt="" class="avatar">
<span class="name text-overflow">{{master_name}}</span><br>
<h5 class="title">直播主题: {{title}}</h5>
<p class="begin-time">开始时间: {{humanTime}}</p>
</div>
<a href="javascript: history.back();" class="live-btn-close">
<i class="iconfont">&#xe623</i>
</a>
</div>
</div>
{{/notBegin}}
{{! footer}}
<div class="float-layer" id="float-layer-app">
<div class="float-layer-left">
<span class="yoho-icon iconfont">&#xe60d;</span>
<p>新用户送惊喜礼包</p>
</div>
<a href="javascript:void(0);" id="float-layer-close" >
<i class="close-icon iconfont">&#xe623;</i>
<div class="circle-rightbottom"></div>
</a>
<a href="http://a.app.qq.com/o/simple.jsp?pkgname=com.yoho" id="float-layer-btn">
立即下载
</a>
</div>
</div>
<script>
var live_start_time = {{live_start_time}};//该直播开始时间
var live_type = {{living}};//是否是直播 0直播未开始,1直播中,2直播结束,3重播
var live_room = {{liveRoom}};//房间id,资讯id
var replay_total_likes = {{like_num}};//重播总计点赞数,取的是直播最终点赞数
var replay_user_nums = {{audience_num}};//重播观看人数,取的是直播时最终观看人数
var site_url = '';
var site_domain = 'http://api.live.yoho.cn/';
// share data
var shareTitle = '{{share_title}}';
var shareContent = '{{share_content}}';
var sharePic = '{{pic}}'
</script>
\ No newline at end of file
... ...
{{! 直播 播放页: 状态页}}
\ No newline at end of file
... ...
{{#unless isApp}}
<header class="yoho-header">
<a href="javascript:history.go(-1);" class="iconfont nav-back"></a>
<p class="nav-title">认证信息填写</p>
</header>
{{/unless}}
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
<div style=" position: fixed;background: #f0f0f0;height: 100%;width: 100%;">
<section class="s-feild s-marbot">
全国已有<span class="s-red">{{count}}</span>位学生完成认证
</section>
<div class="s-form">
<section class="s-feild">
<label>真实姓名</label><input type="text" name="tb_name" id="tb_name" placeholder="请输入您的真实姓名" >
</section>
<section class="s-feild">
<label>身份证号</label><input type="text" id="tb_cert_no" name="tb_cert_no" placeholder="请输入您身份证号码" maxlength="18">
</section>
<section class="s-feild" data-aslider-in="province|fade">
<label>学校省份</label><input type="text" id="s-province-tb" name="s-province-tb" readonly="readonly" placeholder="请选择省份"><span class="s-select iconfont" >&#xe604;</span>
</section>
<section class="s-feild" data-aslider-in="school|fade" aslider-isShow="false">
<label>学校名称</label><input type="text" id="s-school-tb" name="s-school-tb" readonly="readonly" placeholder="请选择您的所在学校"><span id='s-toast-school' class="s-select iconfont">&#xe604;</span>
</section>
<section class="s-feild">
<a {{#if isApp}} href='//m.yohobuy.com/activity/student/register?openby:yohobuy={"action":"go.picker","type":"1","params":{"title":"选择学历","list":{{educationsStr}} }}'{{else}} data-aslider-in="education|fade" href="javascript:void(0)"{{/if}}><label>当前学历</label><input type="text" id="s-education-tb" name="s-education-tb" readonly="readonly" placeholder="请选择您的学历"><span class="s-select iconfont">&#xe604;</span></a>
</section>
<section class="s-feild">
<a {{#if isApp}} href='//m.yohobuy.com/activity/student/register?openby:yohobuy={"action":"go.picker","type":"2","params":{"title":"选择入学时间","list":{{yearsStr}} }}' {{else}} data-aslider-in="year|fade" href="javascript:void(0)" {{/if}}><label>入学年份</label><input type="text" id="s-year-tb" name="s-year-tb" readonly="readonly" placeholder="请选择您的入学年份"><span class="s-select iconfont">&#xe604;</span></a>
</section>
</div>
<div class="s-footer">
<section class="s-shenming">
<input type="checkbox" id="checkbox" class="regular-checkbox" style="visibility: hidden;">
<label class="checkbox icon-s-checked iconfont" for="checkbox">
</label>
同意<a class="s-blue" href='//m.yohobuy.com/activity/student/detail/renzhen?openby:yohobuy={"action":"go.h5","params":{"islogin":"N","url":"{{http}}//m.yohobuy.com/activity/student/detail/renzhen"}}'>Yoho!Buy有货学生认证协议</a>
</section>
<a class="s-submit" href="javascript:void(0);">去支付宝完成认证</a>
<p class="s-sub-tip">与蚂蚁金服旗下的芝麻信用(支付宝)合作认证</p>
</div>
</div>
</div>
</div>
<article class="aslider" data-aslider="province">
<section class="asilder_wrapper close">
<header class="s-layout-title">
<a href="javascript:void(0);" class="iconfont close">&#xe623;</a>
</header>
<div class="slider">
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
<section class='s-search'>
<span class="iconfont s-empty">&#xe60f;</span>
<div class='s-seach-tip'><span class="iconfont search-icon empty">&#xe60f;</span>搜索省份</div>
<input id='s-search-provinces' class='s-input-search'>
<span class="iconfont s-clear">&#xe623;</span>
</section>
<div class='s-items' id='s-provinces'>
</div>
</div>
</div>
</div>
<div class='s-group-zimu'></div>
</section>
</article>
<article class="aslider" data-aslider="school">
<section class="asilder_wrapper close">
<header class="s-layout-title">
<a href="javascript:void(0);" class="iconfont close">&#xe623;</a>
</header>
<div class="slider">
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
<section class='s-search'>
<span class="iconfont s-empty">&#xe60f;</span>
<div class='s-seach-tip'><span class="iconfont search-icon empty">&#xe60f;</span>搜索学校</div>
<input id='s-search-school' class='s-input-search'>
<span class="iconfont s-clear">&#xe623;</span>
</section>
<div class='s-items' id='s-school'>
</div>
</div>
</div>
</div>
<div class='s-group-zimu'></div>
</section>
</article>
<article class="aslider" data-aslider="education">
<section class="asilder_wrapper close">
<header class="s-layout-title">
<a href="javascript:void(0);" class="iconfont close">&#xe623;</a>
</header>
<div class="slider">
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
<div class='s-items' id='s-education'>
{{#each educations}}
<div class='s-item close'>
{{this}}
</div>
{{/each}}
</div>
</div>
</div>
</div>
</section>
</article>
<article class="aslider" data-aslider="year">
<section class="asilder_wrapper close">
<header class="s-layout-title">
<a href="javascript:void(0);" class="iconfont close">&#xe623;</a>
</header>
<div class="slider">
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
<div class='s-items' id='s-year'>
{{#each years}}
<div class='s-item close'>
{{this}}
</div>
{{/each}}
</div>
</div>
</div>
</div>
</section>
</article>
... ...
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
{{! 头部banner}}
{{#banner}}
{{> resources/banner-top}}
{{/banner}}
{{#icons}}
<section class='s-section clearfix' data-template-id="{{template_id}}">
<h1>学生权益<a class='more s-quan' href='//m.yohobuy.com/activity/student/detail/quanyi?openby:yohobuy={"action":"go.h5","params":{"islogin":"N","url":"{{../http}}//m.yohobuy.com/activity/student/detail/quanyi"}}'>更多权益</a></h1>
<div class='s-content'>
{{#each data.list}}
<div class='s-section'>
<a href="javascript:void(0)">
<img src='{{image src 320 149}}' title="{{title}}">
</a>
</div>
{{/each}}
</div>
</section>
{{/icons}}
{{#coupons}}
<section class='s-section clearfix' data-template-id="{{template_id}}">
<h1>领券中心
<a class='more iconfont' href="{{link}}">&#xe618;</a>
</h1>
<div class='s-coupon-contain'>
<style type="text/css">
.no-bg{
color: #fff!important;
background-image: none!important;
}
</style>
{{#each data}}
<div class="coupon-floor" coupon-id="{{couponID}}">
<div class="floor-main" style="background-image: url({{image image.src 0 0}});">
<a href='{{image.url}}' class="main-left"></a>
{{#isEqual status 1}}
{{#if @root.isAppNoLogin}}
<a href='{{@root.noLoginUrl}}' class="main-right-use" >
<span class="on-receive on-lingqu no-bg">
<p>点击</p>
<p>领取</p>
</span>
</a>
{{else}}
<div class="main-right-use" href='{{../image.url}}'>
<span class="on-receive on-lingqu no-bg">
<p>点击</p>
<p>领取</p>
</span>
</div>
{{/if}}
{{/isEqual}}
{{#isEqual status 2}}
<a href='{{../image.url}}' class="main-right-use" >
<span class="zero"></span>
</a>
{{/isEqual}}
{{#isEqual status 3}}
<a href='{{../image.url}}' class="main-right-use" >
<span class="received"></span>
</a>
{{/isEqual}}
{{#isEqual status 4}}
<a href='{{../image.url}}' class="main-right-use" >
<span class="on-receive no-bg" >
<p>已经</p>
<p>过期</p>
</span>
</a>
{{/isEqual}}
</div>
</div>
{{/each}}
</div>
</section>
{{/coupons}}
{{#activities}}
<section class='s-section clearfix' data-template-id="{{template_id}}">
<h1>学生专属活动</h1>
{{#each data.list}}
<a class='s-activity' href="{{url}}">
<img src='{{image src 750 234}}' title='{{title}}'>
</a>
{{/each}}
</section>
{{/activities}}
<section class='s-section clearfix'>
<h1>学生专享商品<a class="more iconfont" href="//search.m.yohobuy.com/?students=1&title=学生专享商品&uid={{uid}}{{#isApp}}&app_version={{@root.app_version}}{{/isApp}}?openby:yohobuy={'action':'go.h5','params':{'islogin':'N','url':'{{@root.http}}//search.m.yohobuy.com/','param':{'students':'1','title':'学生专享商品','uid':'{{uid}}'}}}">&#xe618;</a></h1>
<div class='goods-list clearfix'>
{{#each goods}}
<div class="good-info">
<div class="tag-container clearfix">
{{# tags}}
{{# isNew}}
<p class="good-tag new-tag">NEW</p>
{{/ isNew}}
{{# isAdvance}}
<p class="good-tag renew-tag">再到着</p>
{{/ isAdvance}}
{{# isDiscount}}
<p class="good-tag sale-tag">SALE</p>
{{/ isDiscount}}
{{# isYohoood}}
<p class="good-tag running-man-tag">跑男同款</p>
{{/ isYohoood}}
{{# isLimited}}
<p class="good-tag limit-tag">限量商品</p>
{{/ isLimited}}
{{/ tags}}
</div>
<div class="good-detail-img">
<a class="good-thumb" href="{{url}}">
<img class="lazy" data-original="{{image default_images 235 314}}">
</a>
</div>
<div class="good-detail-text">
<div class="name">
<a href="{{url}}">{{product_name}}</a>
</div>
<div class="price">
<span class="sale-price">¥{{round student_price}} <i class='s-biaoqian'></i></span>
<p class="s-price-block">
<span class="market-price">¥{{round sales_price}}</span>
</p>
</div>
</div>
</div>
{{/each}}
</div>
<a class='s-more' href="//search.m.yohobuy.com/?students=1&title=学生专享商品&uid={{uid}}{{#isApp}}&app_version={{@root.app_version}}{{/isApp}}?openby:yohobuy={'action':'go.h5','params':{'islogin':'N','url':'{{@root.http}}//search.m.yohobuy.com/','param':{'students':'1','title':'学生专享商品','uid':'{{uid}}'}}}">查看更多</a>
</section>
{{#loginUrl}}
<div class='s-layout'>
{{#if @root.isSupportStudent}}
快来认证吧,认证通过即可享受专属优惠!
{{else}}
请升级最新APP版本,完成认证
{{/if}}
{{#if @root.isSupportStudent}}
<a class='s-renzhen' href='{{.}}'>立即认证</a>
{{/if}}
</div>
<div class="s-replace"></div>
{{/loginUrl}}
</div>
</div>
... ...
{{#unless isApp}}
<header class="yoho-header">
<a href="javascript:history.go(-1);" class="iconfont nav-back"></a>
<p class="nav-title">学生身份认证</p>
</header>
{{/unless}}
<div class="mobile-container">
<div class="mobile-wrap yoho-page student">
{{#if isverify}}
<section class='s-section clearfix'>
<div class='s-verify-img s-verify-success'></div>
<p class='s-verify-title'>认证成功!{{./prompt}}</p>
<p class='s-verify-txt'>您是第<i class='red'>{{count}}</i>位认证的学生</p>
</section>
{{else}}
<section class='s-section clearfix'>
<div class='s-verify-img s-verify-fail'></div>
<p class='s-verify-title'>太遗憾了,{{./prompt}}</p>
<p class='s-verify-txt'>您可以<a class='red' href='//m.yohobuy.com/activity/student/register'>重新验证 ></a></p>
</section>
{{/if}}
<section class='s-section clearfix'>
<h1>学生专享商品<a class="more iconfont" href="//search.m.yohobuy.com/?students=1&title=学生专享商品&uid={{uid}}{{#isApp}}&app_version={{@root.app_version}}{{/isApp}}?openby:yohobuy={'action':'go.h5','params':{'islogin':'N','url':'{{@root.http}}//search.m.yohobuy.com/','param':{'students':'1','title':'学生专享商品','uid':'{{uid}}'}}}">&#xe618;</a></h1>
<div class='goods-list clearfix'>
{{#each goods}}
<div class="good-info">
<div class="tag-container clearfix">
{{# tags}}
{{# isNew}}
<p class="good-tag new-tag">NEW</p>
{{/ isNew}}
{{# isAdvance}}
<p class="good-tag renew-tag">再到着</p>
{{/ isAdvance}}
{{# isDiscount}}
<p class="good-tag sale-tag">SALE</p>
{{/ isDiscount}}
{{# isYohoood}}
<p class="good-tag running-man-tag">跑男同款</p>
{{/ isYohoood}}
{{# isLimited}}
<p class="good-tag limit-tag">限量商品</p>
{{/ isLimited}}
{{/ tags}}
</div>
<div class="good-detail-img">
<a class="good-thumb" href="{{url}}">
<img class="lazy" data-original="{{image default_images 235 314}}">
</a>
</div>
<div class="good-detail-text">
<div class="name">
<a href="{{url}}">{{product_name}}</a>
</div>
<div class="price">
<span class="sale-price">¥{{round student_price}} <i class='s-biaoqian'></i></span>
<p class="s-price-block">
<span class="market-price">¥{{round sales_price}}</span>
</p>
</div>
</div>
</div>
{{/each}}
</div>
<a class='s-more' href="//search.m.yohobuy.com/?students=1&title=学生专享商品{{#isApp}}&app_version={{@root.app_version}}{{/isApp}}">查看更多</a>
</section>
</div>
</div>
... ...
/**
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 16/8/22
*/
'use strict';
const http = require('http');
const logger = global.yoho.logger;
const env = process.env.NODE_ENV || 'development';
const hotfix = {
v1(req, res) {
let clientType = req.body.client_type || '';
let version = req.body.app_version || '';
let apiFile = `http://cdn.yoho.cn/app-hotfix2/${env}/yohobuy/`;
if (clientType.toLowerCase() === 'ios' && version) {
apiFile += `ios/${version}/api.json?_=` + (new Date()).getTime();
} else if (clientType.toLowerCase() === 'android' && version) {
apiFile += `android/${version}/api.json?_=` + (new Date()).getTime();
} else {
return res.json({
code: 400,
message: 'client_type or app_version error',
data: {}
});
}
http.get(apiFile, response => {
res.setHeader('Content-Type', 'application/json');
if (response.statusCode === 200) {
response.pipe(res);
} else {
return res.json({
code: 400,
message: 'client_type or app_version error',
data: {}
});
}
}).on('error', err => {
logger.error('hot fix v1 error:', err);
return res.json({
code: 400,
message: 'read api file fail',
data: {}
});
});
}
};
module.exports = hotfix;
... ...
/**
* some common api app
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/08/22
*/
var express = require('express');
var app = express();
// set view engin
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
// router
app.use(require('./router'));
module.exports = app;
... ...
/**
* router of sub app coupon
* @author: lixia.zhang<lixia.zhang@yoho.cn>
* @date: 2016/05/31
*/
'use strict';
const router = require('express').Router(); // eslint-disable-line
const cRoot = './controllers';
const hotfix = require(`${cRoot}/hotfix`);
// routers
router.post('/hf/v1', hotfix.v1);
module.exports = router;
... ...
... ... @@ -120,12 +120,12 @@ const _processSideBar = (list, choosed) => {
const _getChannelResource = (params) => {
params.gender = params.gender || 'boys';
params = Object.assign({
gender: genderData[params.gender],
params = Object.assign(params, {
gender: genderData[params.gender] || '1,2,3',
content_code: contentCode[params.gender], // eslint-disable-line
page: 1,
limit: 30
}, params);
});
if (!params.uid) {
params.new_device = true; // eslint-disable-line
}
... ... @@ -136,7 +136,7 @@ const _getChannelResource = (params) => {
if (result && result.code === 200) {
return resourcesProcess(result.data.list);
} else {
logger.error('首页资源位接口返回状态码 不是 200');
logger.error('index resouce is not 200');
return result;
}
});
... ... @@ -156,7 +156,7 @@ const _getLeftNav = (choosed) => {
if (result && result.code === 200) {
return _processSideBar(result.data, choosed);
} else {
logger.error('侧边栏数据接口返回状态码 不是 200');
logger.error('sidebar code is not 200');
return result;
}
});
... ... @@ -191,7 +191,7 @@ const _getChannelList = () => {
});
return Object.keys(list).length ? list : channelList;
} else {
logger.error('频道选择接口返回状态码 不是 200');
logger.error('channel select code is not 200');
return channelList;
}
});
... ... @@ -210,7 +210,7 @@ const _getChannelBg = () => {
if (result && result.code === 200) {
return result.data.length && result.data[0] && result.data[0].data && result.data[0].data.list[0];
} else {
logger.error('频道选择页背景图接口返回状态码 不是 200');
logger.error('channel select background code is not 200');
return {
src: ''
};
... ...
... ... @@ -55,4 +55,12 @@
{{#if newUserFloor}}
{{> resources/fresh-only}}
{{/if}}
{{! 标题楼层}}
{{#if titleFloor}}
{{> resources/title-floor}}
{{/if}}
{{! 直播楼层}}
{{#if livePicture}}
{{> resources/live-picture}}
{{/if}}
{{/content}}
... ...
# 代码规范说明文档
开发前请务必仔细阅读,遵守规范,保持团队代码风格统一
## 文件命名
* 中划线分隔小写单词
* Ex: `your-file`
## 缩进
* 统一4个Space
* 建议将编辑器Tab映射成4个Space
## 注释
* 为每个你创建的JS文件添加注释
```
/**
* 对文件实现功能的描述
* @date: 2016-11-11
* @author: name<emial@yoho.cn>
*/
```
* 为重要的函数添加注释
```
/**
* 对函数功能的说明
* @params name paramType 参数描述
* @return name returnType 返回值描述
*/
```
* 为重要的代码、逻辑复杂的代码或者有特殊处理的代码添加注释
```
// Your comments for the code
```
* 减少不必要的注释
类似于:**进入循环****循环结束**等垃圾话的注释请谨慎添加,大家都是程序员,不用你注释也能知道的
## html
* 见名知意,不要有1,2,3这种名字出现
* class、id等属性命名为中划线分隔小写单词
* html中请不要出现不必要的嵌套以及不要将标签滥用,比如使用`a`标签作为不跳转的按钮的标签
* 属性按顺序出现:`id -> class -> name -> data-* -> src,for,type,href -> title,alt -> aria-*,role`
## js
[Link](doc/code-norm/js.md)
## css
# 代码规范说明文档
开发前请务必仔细阅读,遵守规范,保持团队代码风格统一
## 文件命名
* 中划线分隔小写单词
* Ex: `your-file`
## 缩进
* 统一4个Space
* 建议将编辑器Tab映射成4个Space
## 注释
* 为每个你创建的JS文件添加注释
```
/**
* 对文件实现功能的描述
* @date: 2016-11-11
* @author: name<emial@yoho.cn>
*/
```
* 为重要的函数添加注释
```
/**
* 对函数功能的说明
* @params name paramType 参数描述
* @return name returnType 返回值描述
*/
```
* 为重要的代码、逻辑复杂的代码或者有特殊处理的代码添加注释
```
// Your comments for the code
```
* 减少不必要的注释
类似于:**进入循环****循环结束**等垃圾话的注释请谨慎添加,大家都是程序员,不用你注释也能知道的
## html
* 见名知意,不要有1,2,3这种名字出现
* class、id等属性命名为中划线分隔小写单词
* html中请不要出现不必要的嵌套以及不要将标签滥用,比如使用`a`标签作为不跳转的按钮的标签
* 属性按顺序出现:`id -> class -> name -> data-* -> src,for,type,href -> title,alt -> aria-*,role`
## js
[Link](doc/code-norm/js.md)
## css
[Link](doc/code-norm/css.md)
\ No newline at end of file
... ...
# css代码规范
## 选择器
* 使用class进行样式匹配,而不是id和标签
* 尽量避免使用属性选择器
* 尽可能精确的元素定位
## 规则细节
.ele-header, /*规则1:每个选择器声明总是使用新的一行*/
.ele-body,
.ele-footer { /*规则2:'{' 前需要添加1个空格*/
line-height: 1.2; /*规则3:样式声明以;结束,每个样式声明独占一行*/
font-weight: normal; /*规则4:属性声明的:后添加1个空格*/
margin: 0; /*规则5:属性值为0时不添加单位*/
background-color: #f3d; /*规则6:十六进制小写和缩写*/
}
/*规则7:没组选择器声明之间适用一空行间隔*/
.ele2 {
font-family: "open sans", arial, sans-serif; /*规则8:使用" ",而不是' '*/
padding: 0 1em 2em; /*规则9:适当缩写但不滥用*/
border-top: 1px;
background-color: rgba(0,0,0,.5); /*规则10:颜色值rgba等中不需要增加空格,并且去除浮点数前面不必要的0*/
}
## 声明顺序(不做强制要求,尽量实现)
这是一个选择器内书写CSS属性顺序的大致轮廓,作为最佳实践,我们应该遵循以下顺序:
* Position属性 (position,top,right,z-index...)
* Box Model属性 (display,float,width...)
* Typographic属性 (font,line-height,color,text-align...)
* Visual属性 (background,border,border-radius...)
因为Position属性可以是一个元素脱离正常的文本流并可以覆盖盒模型相关样式,所以Position排第一位。盒模型决定一个元素位置和大小紧跟其后。后面属性属于元素内部或不会对前两者产生影响的,排在后面。
完整属性顺序参考[Recsss](http://twitter.github.com/recess)
## Sass风格
* 控制嵌套层级,禁止超过5层,尽量控制在3层以内
# css代码规范
## 选择器
* 使用class进行样式匹配,而不是id和标签
* 尽量避免使用属性选择器
* 尽可能精确的元素定位
## 规则细节
.ele-header, /*规则1:每个选择器声明总是使用新的一行*/
.ele-body,
.ele-footer { /*规则2:'{' 前需要添加1个空格*/
line-height: 1.2; /*规则3:样式声明以;结束,每个样式声明独占一行*/
font-weight: normal; /*规则4:属性声明的:后添加1个空格*/
margin: 0; /*规则5:属性值为0时不添加单位*/
background-color: #f3d; /*规则6:十六进制小写和缩写*/
}
/*规则7:没组选择器声明之间适用一空行间隔*/
.ele2 {
font-family: "open sans", arial, sans-serif; /*规则8:使用" ",而不是' '*/
padding: 0 1em 2em; /*规则9:适当缩写但不滥用*/
border-top: 1px;
background-color: rgba(0,0,0,.5); /*规则10:颜色值rgba等中不需要增加空格,并且去除浮点数前面不必要的0*/
}
## 声明顺序(不做强制要求,尽量实现)
这是一个选择器内书写CSS属性顺序的大致轮廓,作为最佳实践,我们应该遵循以下顺序:
* Position属性 (position,top,right,z-index...)
* Box Model属性 (display,float,width...)
* Typographic属性 (font,line-height,color,text-align...)
* Visual属性 (background,border,border-radius...)
因为Position属性可以是一个元素脱离正常的文本流并可以覆盖盒模型相关样式,所以Position排第一位。盒模型决定一个元素位置和大小紧跟其后。后面属性属于元素内部或不会对前两者产生影响的,排在后面。
完整属性顺序参考[Recsss](http://twitter.github.com/recess)
## Sass风格
* 控制嵌套层级,禁止超过5层,尽量控制在3层以内
... ...
# JavaScript代码规范
## 行的长度
每行长度不应该超过**120**个字符,如果一行多余120个字符,应该在一个运算符后换行,下一行增加**2**级缩进,即8个空格)
```
doSomething(argument1, argument2, argument3, argument4,
argument5);
```
## 运算符间距
二元运算符前后必须使用一个空格保持表达式的整洁,操作符包括赋值运算符和逻辑运算符
```
var name = 'xuqi'; // GOOD
var name='xuqi'; // BAD
```
## 括号间距
当使用括号时,紧接左括号之后和紧接右括号之前不应该有空格。
```
doSomething(arg); // GODD
doSomething( arg ); // BAD
```
## 变量声明
* 所有变量在使用前应该先定义
* 变量定义应该放在函数开头
* 使用var,const,let表达式定义变量,每行定义一个
* 除了首行,所有行都应该多一层缩进使变量声明对齐
* 初始化的变量放在未初始化的变量之前
* 所有的变量命名必须使用英文单词
* 浮点变量必须指明实部(即便以0.开头)和小数点后一位
```
var name = 'xuqi',
age,
sex,
...;
const $ = require('yoho.jquery');
let i;
```
另外,晦涩的变量名最好给出注释,否则别人很难读懂接下来代码的意思。
## 函数声明
* 函数在使用前应该先定义
* 函数名和开始圆括号之间无空格(包括匿名函数的function关键字与圆括号之间)
* 开始圆括号和结束圆括号之间无空格
* 参数名之间应该在逗号之后保留一个空格
* 开始花括号应该同function关键字保持同一行,结束圆括号和开始花括号之间应该保留一个空格
* 函数体保持一级缩进
```
function doSomething(arg1, arg2) {
doThing1();
doThing2();
}
const method = function() {
doSomething();
};
```
另外,IIFE的标准格式也在这里指出:
```
(function(args) {
//
}(args)); //(args)位于外层括号内
```
## 对象直接量
* 起始左括号应该与表达式保持一行
* 每个属性的键名前保持一个缩进,第一个属性应该在左括号后另一起行
* 每个属性的键名不包含引号,其后跟一个冒号(前无空格,后有空格),然后是值
* 如果属性值为函数,函数体应该在属性名之下另起一行,并且其前后均应保留一个空行
* 一组相关属性的前后插入空行以提高代码的可读性
* 结束的右括号独占一行
```
var person = {
name: 'xuqi',
age: 25,
groupAttr1: xx1,
groupAttr2: xx2,
walk: function() {
//your walk fn
}
};
```
* 当对象字面量作为函数参数时,起始括号应该与函数名同行
```
doSomething({
//do something
});
```
## 命名
### 变量:
* 采用小驼峰命名格式
* 变量命名为名词(区别函数)
* 变量中不使用_
### 函数:
* 采用小驼峰命名格式
* 函数命名为动词(区别变量)
* 函数名中不使用_
### 构造函数:
* 采用大驼峰命名格式
* 命名应该是名词
### 私有成员:
* 一个对象中不希望外部访问的以下划线开头(约定)
## 等号运算符
使用`===`和`!==`,禁止使用`==`和`!=`
## undefined
禁止使用`name === undefined`判断一个变量是否定义。应该使用`typeof(name) === 'undefined'`;
## 常用语句规范
### if语句
```
if (condition) {
doSomething();
} else if (condition1) {
doSomething2();
} else {
soOtherThing();
}
```
### for语句
```
// GOOD
var i;
for (i = 0; i < len; i++) {
doSomething();
}
for (i in collection) {
if (collection.hasOwnProperty(i)) {
doSomething();
}
}
// BAD
for (var i = 0; i < len; i++) {
doSomething();
}
for (i in collection) {
doSomething();
}
```
### while,do语句
```
while (condition) {
doSomething();
}
do {
doSomething();
} while (condition)
```
### switch语句
* 每一个case保持一个缩进
* 每一组语句都应该以break,return等结尾,或者用一行注释表示跳过(falling through)
* 无default的情况也要注释特别说明
```
switch (val) {
case 1:
//nothing
case 2:
doSomething();
break;
default:
doDefault();
}
```
### try语句
```
try {
doSomething();
} catch (err) {
doSomething2();
} finally {
doSomething3();
}
```
## 模块化规范
* 模块开头require加载所有依赖模块
```
var $ = require('yoho.jquery'),
flip = require('../plugin/flip'); //普通文件
require('../plguin/login');
```
# JavaScript代码规范
## 行的长度
每行长度不应该超过**120**个字符,如果一行多余120个字符,应该在一个运算符后换行,下一行增加**2**级缩进,即8个空格)
```
doSomething(argument1, argument2, argument3, argument4,
argument5);
```
## 运算符间距
二元运算符前后必须使用一个空格保持表达式的整洁,操作符包括赋值运算符和逻辑运算符
```
var name = 'xuqi'; // GOOD
var name='xuqi'; // BAD
```
## 括号间距
当使用括号时,紧接左括号之后和紧接右括号之前不应该有空格。
```
doSomething(arg); // GODD
doSomething( arg ); // BAD
```
## 变量声明
* 所有变量在使用前应该先定义
* 变量定义应该放在函数开头
* 使用var,const,let表达式定义变量,每行定义一个
* 除了首行,所有行都应该多一层缩进使变量声明对齐
* 初始化的变量放在未初始化的变量之前
* 所有的变量命名必须使用英文单词
* 浮点变量必须指明实部(即便以0.开头)和小数点后一位
```
var name = 'xuqi',
age,
sex,
...;
const $ = require('yoho.jquery');
let i;
```
另外,晦涩的变量名最好给出注释,否则别人很难读懂接下来代码的意思。
## 函数声明
* 函数在使用前应该先定义
* 函数名和开始圆括号之间无空格(包括匿名函数的function关键字与圆括号之间)
* 开始圆括号和结束圆括号之间无空格
* 参数名之间应该在逗号之后保留一个空格
* 开始花括号应该同function关键字保持同一行,结束圆括号和开始花括号之间应该保留一个空格
* 函数体保持一级缩进
```
function doSomething(arg1, arg2) {
doThing1();
doThing2();
}
const method = function() {
doSomething();
};
```
另外,IIFE的标准格式也在这里指出:
```
(function(args) {
//
}(args)); //(args)位于外层括号内
```
## 对象直接量
* 起始左括号应该与表达式保持一行
* 每个属性的键名前保持一个缩进,第一个属性应该在左括号后另一起行
* 每个属性的键名不包含引号,其后跟一个冒号(前无空格,后有空格),然后是值
* 如果属性值为函数,函数体应该在属性名之下另起一行,并且其前后均应保留一个空行
* 一组相关属性的前后插入空行以提高代码的可读性
* 结束的右括号独占一行
```
var person = {
name: 'xuqi',
age: 25,
groupAttr1: xx1,
groupAttr2: xx2,
walk: function() {
//your walk fn
}
};
```
* 当对象字面量作为函数参数时,起始括号应该与函数名同行
```
doSomething({
//do something
});
```
## 命名
### 变量:
* 采用小驼峰命名格式
* 变量命名为名词(区别函数)
* 变量中不使用_
### 函数:
* 采用小驼峰命名格式
* 函数命名为动词(区别变量)
* 函数名中不使用_
### 构造函数:
* 采用大驼峰命名格式
* 命名应该是名词
### 私有成员:
* 一个对象中不希望外部访问的以下划线开头(约定)
## 等号运算符
使用`===`和`!==`,禁止使用`==`和`!=`
## undefined
禁止使用`name === undefined`判断一个变量是否定义。应该使用`typeof(name) === 'undefined'`;
## 常用语句规范
### if语句
```
if (condition) {
doSomething();
} else if (condition1) {
doSomething2();
} else {
soOtherThing();
}
```
### for语句
```
// GOOD
var i;
for (i = 0; i < len; i++) {
doSomething();
}
for (i in collection) {
if (collection.hasOwnProperty(i)) {
doSomething();
}
}
// BAD
for (var i = 0; i < len; i++) {
doSomething();
}
for (i in collection) {
doSomething();
}
```
### while,do语句
```
while (condition) {
doSomething();
}
do {
doSomething();
} while (condition)
```
### switch语句
* 每一个case保持一个缩进
* 每一组语句都应该以break,return等结尾,或者用一行注释表示跳过(falling through)
* 无default的情况也要注释特别说明
```
switch (val) {
case 1:
//nothing
case 2:
doSomething();
break;
default:
doDefault();
}
```
### try语句
```
try {
doSomething();
} catch (err) {
doSomething2();
} finally {
doSomething3();
}
```
## 模块化规范
* 模块开头require加载所有依赖模块
```
var $ = require('yoho.jquery'),
flip = require('../plugin/flip'); //普通文件
require('../plguin/login');
```
* 模块抛出接口应该给予注释说明功能和使用方法
\ No newline at end of file
... ...
/**
* 潮流优选
* @author: xiaoxiao<xiaoxiao.hao@yoho.cn>
* @date: 2016/08/04
*/
'use strict';
const mRoot = '../models';
const plusstarModel = require(`${mRoot}/plusstar`);
const headerModel = require('../../../doraemon/models/header'); // 头部model
let channels = {
boys: 1,
girl: 2,
kids: 3,
lifestyle: 4
};
/**
* 潮流优选首页
*/
exports.index = (req, res, next) => {
let isApp = req.query.app_version || req.query.appVersion || false;
let parameter = {};
let title = '潮流优选';
let gender = req.query.gender || req.cookies._Channel && channels[req.cookies._Channel] || 1;
if (isApp === false) {
parameter = {
pageHeader: headerModel.setNav({
navTitle: title
}),
pageFooter: true
};
}
plusstarModel.getAllChannels({gender: gender, app_type: 0}).then(result => {
res.render('plusstar/index', Object.assign({
page: 'plusstar-index',
result: result,
isApp: isApp,
title: title
}, parameter));
}).catch(next);
};
/**
* 潮流优选首页-资源位
*/
exports.resourcesTemplate = (req, res, next) => {
let code = req.query.code || '';
let isApp = req.query.app_version || req.query.appVersion || false;
plusstarModel.getResources({
content_code: code
}, {
isApp: isApp
}).then(result => {
res.render('plusstar/resources-template', {
layout: false,
result: result,
title: '潮流优选'
});
}).catch(next);
};
... ...
'use strict';
const api = global.yoho.API;
const serviceAPI = global.yoho.ServiceAPI;
const _ = require('lodash');
const productProcess = require('../../../utils/product-process');
/**
* 获取潮流优选tab
* @return {[array]}
*/
const getAllChannels = (params) => {
let gender = params.gender - 1 || 0;
return api.get('', {
method: 'app.blk.getAllChannels',
app_type: params.app_type
}).then(result => {
let data = {channel: []};
if (result.code !== 200) {
return data;
}
if (gender === 3 && result.data.length === 3) {
// 1:男,2:女,3:童装,4:创意生活
// 如果为3,说明童装没有配置,只配置了创意生活。所以要减一
gender = 2;
}
_.forEach(result.data, (res, index) => {
data.channel.push({
id: res.channel_id,
mame: res.channel_name,
code: res.content_code,
focus: index === gender ? true : false
});
});
return data;
});
};
/**
* 通过skn查询商品详情
* @param {[string || array]} productSkn 商品skn
* @return {[array]}
*/
const getProductBatch = (productSkn, options) => {
productSkn = _.isArray(productSkn) ? productSkn : [productSkn];
return api.get('', {
method: 'h5.product.batch',
productSkn: productSkn.join(',')
}).then(result => {
return result && result.data ? productProcess.processProductList(result.data.product_list, options) : [];
});
};
/**
* 获取资源位数据
* @param {[string]} content_code
* @return {[array]}
*/
const getResources = (params, options) => {
params = params || {};
if (options.isApp) {
params.platform = 'iphone';
}
return serviceAPI.get('operations/api/v5/resource/get', params).then(result => {
let data = {
goods: {},
recommend: {}
};
let list = {};
let productSkns = [];
if (result.code !== 200) {
return data;
}
_.forEach(result.data, (res) => {
list = {};
switch (res.template_name) {
case 'focus':
list = {
data: res.data
};
if (res.focus_type * 1 === 1) {
data.focus1 = [];
data.focus1.push(list);
} else {
data.focus2 = list;
}
break;
case 'title_image' :
if (typeof data[res.template_name] === 'undefined') {
data[res.template_name] = [];
}
list = {
title: res.data.title,
moreUrl: res.data.more_url,
moreName: res.data.more_name,
image: res.data.image
};
data.title_image.push(list);
break;
case 'titleFloor':
list = {
name: res.data.title.name,
moreUrl: res.data.title.more_url,
moreName: res.data.title.more_name
};
if (res.data.title.name === '热门商品') {
data.goods.title = list;
} else if (res.data.title.name === '热门品类') {
data.recommend.title = list;
}
break;
case 'recommend_content_five':
data.recommend.data = res.data.list;
break;
case 'goods':
_.forEach(res.data, (skn) => {
productSkns.push(skn.id);
});
data[res.template_name].productSkns = productSkns;
break;
default:
break;
}
});
if (_.isEmpty(data.goods.productSkns)) {
return data;
}
return getProductBatch(data.goods.productSkns, options).then(res => {
data.goods.data = res;
return data;
});
});
};
module.exports = {
getAllChannels,
getResources,
getProductBatch
};
... ...
... ... @@ -29,7 +29,7 @@ const _getResources = (page) => {
if (result && result.code === 200) {
return resourcesProcess(result.data);
} else {
logger.error('星潮教室页面资源位返回 code 不是 200');
logger.error('star class content resource return code no 200');
return [];
}
});
... ... @@ -57,7 +57,7 @@ const _processIndexData = (dataList) => {
if (list.ads) {
_.forEach(list.ads.data, (data) => {
formatData.ads.push({
src: data.src + '/q/80',
src: data.src.replace('imageView', 'imageView2'),
url: data.url
});
});
... ... @@ -65,7 +65,7 @@ const _processIndexData = (dataList) => {
// 首页明星文章数据处理
if (list.list) {
_.forEach(list.list, (data) => {
_.forEach(list.list, (data, index) => {
const avatar = {
tags: []
};
... ... @@ -74,7 +74,11 @@ const _processIndexData = (dataList) => {
avatar.isSwiper = true;
}
_.forEach(data.ext.tags, (tags) => {
_.forEach(data.ext.tags, (tags, i) => {
if (i >= 1) {
return;
}
avatar.tags.push({
avatarUrl: `/guang/star/detail?tag=${tags.tagName}&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"http://m.yohobuy.com/guang/star/detail","param":{"tag":"${tags.tagName}"}}}`, // eslint-disable-line
cover: tags.cover ? (tags.cover + '?imageView2/2/w/104/h/104/q/80') : tags.cover,
... ... @@ -82,7 +86,12 @@ const _processIndexData = (dataList) => {
});
});
if (formatData.articles.length > 30) {
return;
}
formatData.articles.push(_.merge({
noLazy: index < 2,
id: data.id,
url: data.url,
title: data.title,
... ... @@ -101,6 +110,7 @@ const _processIndexData = (dataList) => {
let url = `/guang/star/detail?tag=${data.tagName}&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"http://m.yohobuy.com/guang/star/detail","param":{"tag":"${data.tagName}"}}}`; // eslint-disable-line
formatData.starAvatar.push({
// noLazy: index < 5,
url: url,
cover: data.cover ? (data.cover + '?imageView2/2/w/180/h/180/q/80') : data.cover
});
... ... @@ -134,9 +144,9 @@ const _processGuangData = (list, flag) => {
data.isCollected = true;
}
data.src += '/q/80';
// data.src += '/q/80';
if (key < 4) {
if (key < 2) {
data.islazy = true;
}
... ... @@ -158,7 +168,7 @@ const getIndexData = () => {
if (result && result.code === 200) {
return _processIndexData(result);
} else {
logger.error('星潮教室首页数据返回 code 不是 200');
logger.error('star class content resource return code no 200');
return {};
}
});
... ... @@ -183,7 +193,7 @@ const getDetailData = (params, uid) => {
return _processGuangData(result.data.list, true);
}
} else {
logger.error('明星专题文章数据返回 code 不是 200');
logger.error('api app.starClass.lastTagArticle code no 200');
return [];
}
});
... ... @@ -227,7 +237,7 @@ const getCollocationListData = (params, uid) => {
if (result && result.code === 200) {
return _processGuangData(result.data.list.artList);
} else {
logger.error('获取星搭配文章列表返回 code 不是 200');
logger.error('getStarClassroomArticleList code no 200');
return [];
}
});
... ...
... ... @@ -9,6 +9,7 @@
const express = require('express');
const cRoot = './controllers';
const star = require(cRoot + '/star');
const plusstar = require(cRoot + '/plusstar');
const router = express.Router(); // eslint-disable-line
... ... @@ -22,4 +23,7 @@ router.get('/star/collocation/list', star.collocationList); // 星潮教室星
router.post('/star/setFavorite', star.setFavorite); // 收藏文章
router.get('/plusstar', plusstar.index); // 潮流优选
router.get('/plusstar/resources-template', plusstar.resourcesTemplate); // 潮流优选首页-资源位
module.exports = router;
... ...
<div class='plusstar-page'>
{{#if result.channel}}
<div class="tab-nav">
<ul>
{{#each result.channel}}
<li class='{{#if focus}} focus {{/if}}' data-code='{{code}}'>
<span>{{mame}}</span>
</li>
{{/each}}
</ul>
</div>
{{/if}}
<!--/tab-nav-->
{{#if isApp}}
<div class='empty-height'></div>
{{/if}}
<div class="plusstar-resources">
<!--资源位数据模板-->
</div><!--/plusstar-resources-->
</div><!--/plusstar-page-->
\ No newline at end of file
... ...
<div class="resources">
<!--banner-->
{{#each result.focus1}}
{{> resources/banner-top}}
{{/each}}
{{#each result.title_image}}
<div class="header-title">
{{title}}
<a class="more" href="{{moreUrl}}">{{moreName}}</a>
</div>
<div class="title-image">
<a class="image" href="{{image.url}}">
<img class="lazy" data-original="{{image image.src 750 364}}">
</a>
</div>
{{/each}}
{{#if result.focus2.data}}
<div class="focus-left-right">
{{#each result.focus2.data}}
<a href="{{url}}" title="{{title}}">
<img class="lazy" data-original="{{image src 250 250}}">
</a>
{{/each}}
</div>
{{/if}}
<!--/focus-left-right-->
{{#if result.recommend.title}}
<div class="header-title">
{{result.recommend.title.name}}
<a class="more" href="{{result.recommend.title.moreUrl}}">
{{result.recommend.title.moreName}}
</a>
</div>
{{/if}}
{{#if result.recommend.data}}
<div class="recommend-content-five">
{{#each result.recommend.data}}
<a href="{{url}}" title="{{title}}">
<img class="lazy" data-original="{{image src 375 375}}">
</a>
{{/each}}
</div>
{{/if}}
{{#if result.goods.title}}
<div class="header-title">
{{result.goods.title.name}}
<a class="more" href="{{result.goods.title.moreUrl}}">
{{result.goods.title.moreName}}
</a>
</div>
<div class="goods clearfix">
<!--商品--->
{{#each result.goods.data}}
{{> common/goods}}
{{/each}}
</div><!--/goods-->
{{/if}}
</div><!--/resources-->
... ...
... ... @@ -11,7 +11,13 @@
<ul class="clearfix swiper-wrapper">
{{# starAvatar}}
<li class="swiper-slide">
<a href='{{url}}' style="background-image: url({{image cover 180 180}})" class="star"></a>
{{#if noLazy}}
<a href='{{url}}' class="star" style="background: url('{{image cover 180 180}}')">
</a>
{{else}}
<a href='{{url}}' class="star swiper-lazy" data-background="{{image cover 180 180}}">
</a>
{{/if}}
</li>
{{/ starAvatar}}
</ul>
... ... @@ -22,7 +28,27 @@
{{#each articles}}
<li data-id="{{id}}">
<div class="star-avatar">
{{#if isSwiper}}
<div class="avatar-wrap avatar-num-{{tags.length}}">
{{# tags}}
<a href="{{avatarUrl}}">
{{#if ../noLazy}}
<img src="{{image cover 100 100}}" class="rank-avatar" ></img>
{{else}}
<img data-original="{{image cover 100 100}}" class="rank-avatar lazy" ></img>
{{/if}}
<p class="name">{{tagName}}</p>
</a>
{{/ tags}}
{{# tags}}
{{#if @first}}
<a href="{{avatarUrl}}">
<img data-original="{{image cover 100 100}}" class="rank-avatar lazy" ></img>
<p class="name">{{tagName}}</p>
</a>
{{/if}}
{{/ tags}}
</div>
{{!-- {{#if isSwiper}}
<div class="article-avatar-swiper">
<ul class="clearfix swiper-wrapper">
{{#each tags}}
... ... @@ -42,7 +68,7 @@
<p class="name">{{tagName}}</p>
</a>
{{/ tags}}
{{/if}}
{{/if}} --}}
</div>
<a class="star-article" href='{{url}}'>
<i class="article-arrow"></i>
... ... @@ -50,7 +76,11 @@
<div class="artice-cont">
<p>{{articeTxt}}</p>
<div class="artice-imgs-area">
{{#if noLazy}}
<img src="{{image src 266 266}}" />
{{else}}
<img data-original="{{image src 266 266}}" class="lazy" />
{{/if}}
{{!-- <ul class="artice-imgs">
{{#each articeImg}}
... ...
/**
* 分期付款
* @author: wsl<shuiling.wang@yoho.cn>
* @date: 2016/08/01
*/
'use strict';
const installmentModel = require('../models/installment');
const _ = require('lodash');
const helpers = global.yoho.helpers;
// 判断是否已经获取到了开通的状态值
const _reviewStatus = (uid, status, next) => {
let jumpUrl = helpers.appUrlFormat('/product/new', 'go.new');
if (status === '1') {
return {
review: {
url: jumpUrl
}
};
} else if (status === '2') {
return Promise.all([installmentModel.getSearchIntallment({
page: 1
}), installmentModel.getQueryCreditInfo(uid)]).then((result) => { //eslint-disable-line
return {
success: {
price: result[1].currCreditLimit,
installmentOnly: {
title: '分期专享',
goods: result[0]
},
url: jumpUrl
}
};
}).catch(next);
} else if (status === '3') {
return {
error: {
url: jumpUrl
}
};
}
};
// 还款列表公共处理块
const _repaymentList = (req, res, next, title, params) => {
params = _.assign({
uid: req.cookies.installmentUid || 1
}, params);
installmentModel.getQueryAmtList(params).then((result) => {
res.render('installment/repayment-list', {
module: 'home',
page: 'repayment-list',
isInstallmentPage: true,
title: title,
data: result
});
}).catch(next);
};
// 开通分期首页
const index = (req, res, next) => {
let uid = req.query.uid;
Promise.all([installmentModel.getStauts(uid), installmentModel.getSearchIntallment({
page: 1
})]).then((result) => {
// status:0 未申请 1审核中 2已开通 3 审核未通过
let openStatus = result[0];
let installmentOnly = {
title: '分期专享',
goods: result[1]
};
if (openStatus === '0') {
return installmentModel.getResources().then(data => {
if (data[0] && data[0].data[0]) {
data[0].data[0].url = 'javascript:void(0)'; //eslint-disable-line
}
return {
bannerTop: data,
notOpen: true,
installmentOnly: installmentOnly
};
});
} else if (openStatus === '2') {
return Promise.all([installmentModel.getQueryCreditInfo(uid), installmentModel.getQueryAmtInfo(uid)]).then((data) => { //eslint-disable-line
let params = _.assign({
isOverdue: false,
installmentOnly: installmentOnly
}, data[0], data[1]);
// status: 1 正常 2 逾期 3 不可用 4 未开通
if (data[0].status === '2') {
params.replayStatus = '逾期';
params.isOverdue = true;
} else if (data[0].status === '3') {
params.replayStatus = '不可用';
}
return params;
});
} else if (openStatus === '1' || openStatus === '3') {
res.redirect('/home/installment/review?status=' + openStatus);
}
}).then((result) => {
res.render('installment/open-index', _.assign({
module: 'home',
page: 'installment',
isInstallmentPage: true,
title: '有货分期'
}, result));
}).catch(next);
};
// ajax 请求分期专享商品
const getInstallmentGoods = (req, res, next) => {
let params = req.query || {};
installmentModel.getSearchIntallment(params).then((result) => {
if (result) {
res.render('installment/goods-template', {
layout: false,
goods: result
});
} else {
res.json();
}
}).catch(next);
};
// 开通结果显示
const review = (req, res, next) => {
let openStatus = req.query.status || false;
let uid = req.query.uid;
let data = {
module: 'home',
page: 'installment',
title: '有货分期'
};
if (openStatus !== '2') {
res.render('installment/open-result', _.assign(data, _reviewStatus(uid, openStatus)));
} else {
_reviewStatus(uid, openStatus, next).then((params) => {
res.render('installment/open-result', _.assign(data, params));
});
}
};
// 逾期未还款列表
const overdueList = (req, res, next) => {
_repaymentList(req, res, next, '逾期未还金额', {
queryDays: -1
});
};
// 7日待还款列表
const sevenDayList = (req, res, next) => {
_repaymentList(req, res, next, '近7日待还款', {
queryDays: 7
});
};
// 本月待还款列表
const monthRepayList = (req, res, next) => {
_repaymentList(req, res, next, '本月待还金额', {
queryDays: 30
});
};
// 待还总金额列表
const totalRepayList = (req, res, next) => {
_repaymentList(req, res, next, '待还总金额', {
queryDays: 0
});
};
// 还款记录页面渲染
const repayRecordPage = (req, res) => {
res.render('installment/repay-record', {
module: 'home',
page: 'repay-record',
isInstallmentPage: true,
title: '还款记录'
});
};
// ajax 请求还款记录
const getRepayRecord = (req, res, next) => {
let params = _.assign({
uid: req.cookies.installmentUid || 1,
pageNo: req.query.page || 1
});
installmentModel.getQueryRePayList(params).then((result) => {
if (result) {
res.render('installment/record-template', {
layout: false,
recordData: result
});
} else {
res.json();
}
}).catch(next);
};
// 账号管理
const account = (req, res, next) => {
let uid = req.cookies.installmentUid || 512579468;
installmentModel.getBankCards(uid).then((result) => {
res.render('installment/account', {
accountList: result,
title: '账号管理',
isInstallmentPage: true,
repaymentList: result
});
}).catch(next);
};
const startingService = (req, res) => {
res.render('installment/starting-service', {
module: 'home',
page: 'installment.starting-service',
title: '开通有货分期',
navTitle: '开通有货分期',
navBtn: false
});
};
/**
* 获取真实IP
*
* @param req
* @returns {*|string}
*/
function getRealIP(req) {
var realIP = req.headers['x-real-ip'];
var forwardedFor = req.headers['x-forwarded-for'] || '';
return realIP || forwardedFor.split(',')[0] || req.connection.remoteAddress;
}
const activateService = (req, res, next) => {
installmentModel.activateService({
uid: req.cookies.installmentUid || 532892,
userName: req.body.userName,
identityCardNo: req.body.identityCardNo,
cardNo: req.body.cardNo,
mobile: req.body.mobile,
snsCheckCode: req.body.snsCheckCode,
bankCode: req.body.bankCode,
bankName: req.body.bankName,
udid: req.cookies.udid || 0,
from_client_type: req.cookies.clientType,
ip: getRealIP(req)
}).then((result)=> {
res.json(result);
}).catch(next);
};
const getBankInfo = (req, res, next) => {
installmentModel.getBankInfo({
cardNo: req.query.cardNo,
uid: req.cookies.installmentUid || 512579468 // TODO: fix uid
}).then((result)=> {
res.json(result);
}).catch(next);
};
const verifyCode = (req, res, next) => {
installmentModel.sendVerifyCode(req.cookies.installmentUid || 1, req.query.mobile).then((result)=> {
res.json(result);
}).catch(next);
};
const orderIndex = (req, res) => {
res.render('installment/order', {
module: 'home',
page: 'installment.order',
title: '我的分期订单',
navBtn: false,
isInstallmentPage: true
});
};
const orderList = (req, res, next) => {
const params = {
uid: req.cookies.installmentUid || 8041876, // TODO: fix me
type: req.query.type || 1,
page: req.query.page || 1,
limit: req.query.limit || 10
};
installmentModel.getInstallmentOrders(params).then((result)=> {
res.render('installment/order-list', {
title: '我的分期订单',
orders: ((()=> {
// 处理数据,订单只包含第一条物品纪录
var list = result.data ? result.data.orderList : [];
if (list) {
list.forEach((item)=> {
item.orderGoods = [
item.orderGoods[0]
];
});
}
return list;
})()),
layout: false,
isInstallmentPage: true
});
}).catch(next);
};
const orderDetail = (req, res, next) => {
const params = {
uid: req.cookies.installmentUid || 8041876, // TODO: fix me
orderCode: req.params.id
};
installmentModel.getInstallmentOrderDetail(params).then((result)=> {
res.render('installment/order-detail', {
module: 'home',
page: 'installment.order-detail',
title: '分期详情',
order: (()=> {
let refundStatusCount = 0, completeStatusCount = 0, listCount = 0;
if (result && result.data && result.data.orderGoods) {
result.data.orderGoods = [
result.data.orderGoods[0]
];
}
if (result && result.data && result.data.packageList) {
listCount = result.data.packageList.length;
result.data.packageList.forEach((item)=> {
if (item.status === 2) {
completeStatusCount++;
} else if (item.status === 4 || item.status === 5) {
refundStatusCount++;
}
});
}
return Object.assign({
status: (()=> {
if (refundStatusCount === listCount) {
return 5;
} else if (completeStatusCount === listCount) {
return 2;
}
})()
}, result.data);
})(),
navBtn: false,
currAmtCount: 0,
currFeeCount: 0,
isCurrFee: true,
helpers: {
isPaymentComplete: function(status, options) {
if (status === 2 || status === 4 || status === 5) { // 已结清/已取消
return options.fn(this);
}
return options.inverse(this);
},
isPaymentIncomplete: function(status, options) {
if (status !== 2 && status !== 4 && status !== 5) { // 其他状态
return options.fn(this);
}
return options.inverse(this);
},
isRefundedAll: function(status, options) {
if (status === 5) { // 已退款
return options.fn(this);
}
return options.inverse(this);
},
isRepaymentAllCompleted: function(status, options) {
if (status === 2) { // 已结清
return options.fn(this);
}
return options.inverse(this);
},
greaterThanZero: function(value, options) {
if (value && parseFloat(value) > 0) {
return options.fn(this);
}
return options.inverse(this);
}
}
});
}).catch(next);
};
// 还款详情
const repayDetail = (req, res, next) => {
let params = {
uid: req.cookies.installmentUid || 512579468,
rePayNo: req.query.id || '',
pageNo: 1
};
installmentModel.getQueryRePayList(params).then((result) => {
res.render('installment/repay-detail', _.assign({
title: '还款详情',
isInstallmentPage: true,
isOne: true
}, result[0]));
}).catch(next);
};
// 帮助静态页面
const help = (req, res) => {
res.render('installment/help', {
title: '分期支付帮助中心'
});
};
// 协议静态页面
const agreement = (req, res) => {
res.render('installment/agreement', {
title: '用户服务协议'
});
};
// 计算金额
const totalAmount = (req, res, next) => {
installmentModel.totalAmount(req.query.prices).then((result) => {
res.json(result);
}).catch(next);
};
// 检查验证码
const checkVerifyCode = (req, res, next) => {
installmentModel.checkVerifyCode(req.cookies.installmentUid, req.query.mobile, req.query.code).then((result) => {
res.json(result);
}).catch(next);
};
module.exports = {
index,
review,
overdueList,
sevenDayList,
monthRepayList,
totalRepayList,
repayRecordPage,
getRepayRecord,
startingService,
activateService,
verifyCode,
getBankInfo,
account,
orderIndex,
orderList,
orderDetail,
repayDetail,
help,
agreement,
totalAmount,
checkVerifyCode,
getInstallmentGoods
};
... ...
/**
* 个人中心二维码 controller
* @author: weiqingting<qingting.wei@yoho.cn>
* @date: 2016/05/16
*/
'use strict';
const QRcodeModel = require('../models/qrcode');
exports.QRcode = (req, res, next) => {
let id = req.params.id || 0;
QRcodeModel.getQRcodeData(id, req.user.uid).then((result)=>{
if (result) {
result.ticks = result.ticks.map(item=>{
if (+item.ticket_type === 2) {
item.isgroup = true;
}
return item;
});
}
let vm = {
qrcodeData: result
};
res.render('QRcode', vm);
}).catch(next);
};
... ...
/**
* sub app home
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
var app = express();
// set view engin
var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
app.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
// router
app.use(require('./router'));
module.exports = app;
... ...