Authored by yyq

Merge branch 'feature/1215' into develop

... ... @@ -86,7 +86,7 @@ exports.bottombar = function(req, res, next) {
*/
exports.couponSend = (req, res, next) => {
let token = req.query.token || '',
coupontype = req.query.coupontype,
coupontype = req.query.couponType,
uid = req.user.uid,
app = req.query.app || {};
... ... @@ -116,9 +116,20 @@ exports.couponSend = (req, res, next) => {
if (uid === '' || uid === 0 || token === '' || token === 0) {
return res.jsonp(resultData);
}
const promise = coupontype === 'ufo' ?
req.ctx(model).couponSendUFO(uid, token) :
req.ctx(model).couponSend(uid, token);
let promise;
switch (coupontype) {
case 'storeTrial':
promise = req.ctx(model).couponSendStoreTrial(uid, token);
break;
case 'ufo':
promise = req.ctx(model).couponSendUFO(uid, token);
break;
default:
promise = req.ctx(model).couponSend(uid, token);
break;
}
promise.then(result => {
res.set({
... ...
... ... @@ -9,7 +9,10 @@
const _ = require('lodash');
const api = global.yoho.API;
const UfoApi = global.yoho.UfoAPI;
const StoreApi = global.yoho.StoreAPI;
const helpers = global.yoho.helpers;
const crypto = global.yoho.crypto;
const cache = global.yoho.cache.master;
const yasProcess = require('../../../utils/yas-process');
const moment = require('moment');
... ... @@ -77,7 +80,8 @@ class featureModel extends global.yoho.BaseModel {
product_price_plan_list: _.sortBy(val.product_price_plan_list, o => {
return +o.show_begin_time;
}),
stock_number: val.stock_number
stock_number: val.stock_number,
totalSalesNum: val.total_sales_num
};
if (val.shop_id) {
... ... @@ -94,6 +98,7 @@ class featureModel extends global.yoho.BaseModel {
let defaultPros = [];
let baseConf = {
ufoProduct: value.ufoProduct,
searchCondition: value.searchCondition,
lefTopImg: value.lefTopImg,
rigTopImg: value.rigTopImg,
... ... @@ -113,7 +118,9 @@ class featureModel extends global.yoho.BaseModel {
priceBgImage: value.priceBgImage,
shopBgImage: value.shopBgImage,
enterShopBgImage: value.enterShopBgImage,
brandColor: value.brandColor
brandColor: value.brandColor,
buyBtnImg: value.buyBtnImg,
showLastSoldTpl: value.showLastSoldTpl
};
value._defaultSkns && value._defaultSkns.forEach(val => {
... ... @@ -139,7 +146,9 @@ class featureModel extends global.yoho.BaseModel {
}
if (pro) {
defaultPros.push(Object.assign({conf: baseConf}, pro));
defaultPros.push(Object.assign({conf: baseConf}, pro, {
lastSoldText: (value.showLastSoldTpl && pro.totalSalesNum) ? value.showLastSoldTpl.replace('{{soldNum}}', pro.totalSalesNum) : ''
}));
}
});
... ... @@ -285,7 +294,7 @@ class featureModel extends global.yoho.BaseModel {
*/
_formatSeckillCouponTime(featureData = {}) {
const {floors = []} = featureData;
const seckillCouponFloor = floors.find(floor=> {
const {component} = floor;
const isSeckillCoupon = !!component.find(c => c.seckillCoupon === '1');
... ... @@ -331,7 +340,7 @@ class featureModel extends global.yoho.BaseModel {
const { hour, year, month, day } = tabInfo;
const nextTab = array[index + 1];
const tabTime = new Date(`${year}-${month}-${day} ${hour}:00:00`).getTime();
const nextTabTime = nextTab ?
const nextTabTime = nextTab ?
new Date(`${nextTab.year}-${nextTab.month}-${nextTab.day} ${nextTab.hour}:00:00`).getTime() :
0;
... ... @@ -366,24 +375,28 @@ class featureModel extends global.yoho.BaseModel {
for(const floor of floors) {
const {component, tabname, tableComp = '{}'} = floor;
const colNum = +JSON.parse(tableComp).col || 1;
if(tabname) {
const tabInfo = tabList.find(tab=> tab.link === tabname);
if(tabInfo) {
let statusImgOnce = false;
const {startTime,status} = tabInfo;
floor.component = component.map(c=> {
if(c.type === 'coupon' && colNum === 1) {
return {...c, startTime, endOfDayTime, currentTime, status, statusImg: seckillCouponStatusInfo};
if(c.type === 'coupon') {
let statusImg = statusImgOnce ? '' : seckillCouponStatusInfo;
statusImgOnce = true;
return {...c, startTime, endOfDayTime, currentTime, status, statusImg};
}else {
return c;
}
});
});
}
}
}
}
... ... @@ -461,31 +474,49 @@ class featureModel extends global.yoho.BaseModel {
f.component[0].isStyle2 = _.get(f, 'component[0].newStyle') === '2' && _.get(f, 'component[0].numOfOneRow') === '2';// eslint-disable-line
}
if (f.component && f.component[0] &&
f.component[0].type === 'productGroup' && f.component[0].defaultSkns) {
if (componentType === 'productGroup') {
if (f.component[0].defaultSkns) {
componentArr.push(f.component[0]);
componentArr.push(f.component[0]);
f.component[0].newStyle = _.get(f, 'component[0].newStyle');
}
if (f.component && f.component[0] &&
f.component[0].type === 'productGroup' && f.component[0].favourite_prds_enable === '1') {
f.component[0].searchCondition = Object.assign({
maybeLike: '1',
limit: '60'
}, f.component[0].searchCondition || {});
f.component[0].newStyle = _.get(f, 'component[0].newStyle');
}
f.component[0].newStyle = _.get(f, 'component[0].newStyle');
if (f.component[0].favourite_prds_enable === '1') {
f.component[0].searchCondition = Object.assign({
maybeLike: '1',
limit: '60'
}, f.component[0].searchCondition || {});
}
f.component[0].newStyle = _.get(f, 'component[0].newStyle');
}
// 对背景图做特殊处理
if (f.component && f.component[0] && f.component[0].type === 'productGroup') {
if (f.param.bgimg) { // 对背景图做特殊处理
// 对背景图做特殊处理
if (f.param.bgimg) {
f.param._bgimgFill = f.param.bgimg;
f.param.bgimg = '';
}
// 默认为非ufo商品
f.component[0].ufoProduct = f.component[0].ufoProduct || '0';
// 一行一个商品或UFO商品展示销售数量
if (_.get(f, 'component[0].ufoProduct') === '1' || _.get(f, 'component[0].numOfOneRow') === '1') {
let showLastSoldTpl = '';
if (_.get(f, 'component[0].ufoProduct') === '1') {
showLastSoldTpl = '{{soldNum}}人付款';
} else {
showLastSoldTpl = '累积销售{{soldNum}}件';
}
f.component[0].showLastSoldTpl = showLastSoldTpl;
}
}
if (componentType === 'tab') {
let {num} = f.component[0] || {};
f.component[0].containerWidth = +num > 5 ? `${Math.round(100 / 5 * num)}%` : '100%';
}
// 新增店铺组
... ... @@ -581,7 +612,7 @@ class featureModel extends global.yoho.BaseModel {
let set = new Set();
_.forEach(f.component, component => {
_.forEach(f.component, (component, cIndex) => {
if (component.url && component.url.indexOf('go.productDetail') !== -1) {
component.url = yasProcess.addParamsToGoodsHref({
... ... @@ -599,6 +630,14 @@ class featureModel extends global.yoho.BaseModel {
componentArr.push(component);
}
if (component.type === 'coupon') { // 优惠券
// 门店体验券
if (+component.storeTrialCoupon > 0) {
component.tokenType = 'storeTrial';
component.token = encodeURIComponent(crypto.encryption('yoho9646__coupon', `${params.code}::${f.id}_${cIndex}::${component.token}`));
}
}
component.type && set.add(component.type);
});
... ... @@ -628,7 +667,7 @@ class featureModel extends global.yoho.BaseModel {
if (likeArr.length) {
yield self._getLikedFloorDataMulti(likeArr);
}
// 限时抢券时间转换
self._formatSeckillCouponTime(data);
... ... @@ -692,6 +731,72 @@ class featureModel extends global.yoho.BaseModel {
});
}
/**
* 线下门店体验券
* @param uid
* @param token
*/
async couponSendStoreTrial(uid, token) {
let code;
try {
code = crypto.decrypt('yoho9646__coupon', decodeURIComponent(token));
code = _.trim(code).split('::');
} catch (e) {}// eslint-disable-line
if (!code || !code[2]) {
return {
code: 400,
message: '获取优惠券信息失败,请稍后重试'
};
}
let key = `activityStoreTrialCoupon:${code[0]}:${uid}:${code[1]}`;
const getStatus = await cache.getAsync(key);
if (getStatus) {
return {
code: 400,
message: '您已领过该优惠券'
};
}
let apis = [];
code[2].split(',').forEach(id => {
apis.push(this.get({
url: 'coupon/sendCouponForMars.do',
data: {
uid,
couponId: id
},
api: StoreApi
}));
});
let res = await Promise.all(apis);
let pass = false;
let errMsg = '领取失败';
res.forEach(r => {
if (r.code === 200) {
pass = true;
} else {
r.message && (errMsg = r.message);
}
});
cache.setAsync(key, JSON.stringify({
time: Date.parse(new Date())
}), 30 * 12 * 60 * 60);
return {
code: pass ? 200 : 400,
message: pass ? '领取成功' : errMsg
};
}
getProductBySkns(skns) {
let obj = {defaultSkns: skns};
... ...
... ... @@ -21,6 +21,7 @@ let _getProduct = function(o) {
sales_price: o.sales_price,
cn_alphabet: o.cn_alphabet,
default_images: o.default_images,
total_sales_num: o.total_sales_num || 0,
goods_id: Array.isArray(o.goods_list) && o.goods_list.length ? o.goods_list[0].goods_id : void 0
};
... ...
... ... @@ -98,36 +98,39 @@
{{#isEqualOr type 'tab'}}
{{! tab}}
{{#isEqualOr seckillCoupon '1' }}
<div class="tab-container {{#isEqualOr topFloat '1'}} tab-fix {{/isEqualOr}}">
{{#list}}
<a class="anchor {{#isEqualOr ../chooseTab link}}active{{/isEqualOr}}"
tab= {{link}}
end-of-day-time={{endOfDayTime}}
current-time={{currentTime}}
start-time={{startTime}}
style="{{tabStyle @index ../num}}">
<div class="coupon-tab" style="color:{{../timeColor}};">
<div class="content">
<p class="time">{{hour}}:00</p>
<p class="status">{{statusText}}</p>
<div class="tab-scroll-wrap">
<div class="tab-scroll-container">
{{#isEqualOr seckillCoupon '1' }}
<div class="tab-container {{#isEqualOr topFloat '1'}} tab-fix {{/isEqualOr}}" style="width: {{containerWidth}};">
{{#list}}
<a class="anchor {{#isEqualOr ../chooseTab link}}active{{/isEqualOr}}"
tab= {{link}}
end-of-day-time={{endOfDayTime}}
current-time={{currentTime}}
start-time={{startTime}}
style="{{tabStyle @index ../num}}">
<div class="coupon-tab" style="color:{{../timeColor}};">
<div class="content">
<p class="time">{{hour}}:00</p>
<p class="status">{{statusText}}</p>
</div>
<img src="{{src}}" style="{{#isEqualOr ../chooseTab link}} {{^}}display: none;{{/isEqualOr}}" alt="">
</div>
</a>
{{/list}}
</div>
{{else}}
<div class="tab-container {{#isEqualOr topFloat '1'}} tab-fix {{/isEqualOr}}" style="width: {{containerWidth}};">
{{#list}}
<a class="anchor {{#isEqualOr ../chooseTab link}}active{{/isEqualOr}}"
tab= {{link}} style="{{tabStyle @index ../num}}">
<img src="{{src}}" style="{{#isEqualOr ../chooseTab link}} {{^}}display: none;{{/isEqualOr}}" alt="">
</div>
</a>
{{/list}}
</div>
{{else}}
<div class="tab-container {{#isEqualOr topFloat '1'}} tab-fix {{/isEqualOr}}">
{{#list}}
<a class="anchor {{#isEqualOr ../chooseTab link}}active{{/isEqualOr}}"
tab= {{link}} style="{{tabStyle @index ../num}}">
<img src="{{src}}" style="{{#isEqualOr ../chooseTab link}} {{^}}display: none;{{/isEqualOr}}" alt="">
</a>
{{/list}}
</div>
{{/isEqualOr}}
</a>
{{/list}}
</div>
{{/isEqualOr}}
</div>
</div>
{{/isEqualOr}}
{{#isEqualOr type 'coupon'}}
... ...
... ... @@ -22,9 +22,19 @@
{{/if}}
</div>
</div>
{{#isEqualOr conf.showPrdName '1'}}
<p class="product-name">{{productname}}</p>
{{/isEqualOr}}
<div class="product-name-wrap">
{{#isEqualOr conf.showPrdName '1'}}
<p class="product-name">{{productname}}</p>
{{/isEqualOr}}
{{#isEqualOr conf.ufoProduct '0'}}
{{#if lastSoldText}}
<p class="last-sold-num"><span class="sold-num-text">{{lastSoldText}}</span></p>
{{/if}}
{{/isEqualOr}}
</div>
<div class="product-detail-text">
{{#if collageprice}}
<div class="price collage-price" style="{{#if conf.salePriceBgColor}}background:{{conf.salePriceBgColor}};{{/if}}">
... ... @@ -49,6 +59,11 @@
</div>
{{/isEqualOr}}
{{/if}}
{{#isEqualOr conf.ufoProduct '1'}}
{{#if lastSoldText}}
<p class="last-sold-num"><span class="sold-num-text">{{lastSoldText}}</span></p>
{{/if}}
{{/isEqualOr}}
</div>
</a>
{{#if conf.brandImg}}
... ... @@ -59,4 +74,8 @@
<img class="brand-img" src="{{image2 conf.brandImg q=85}}">
</a>
{{/if}}
{{#if conf.buyBtnImg}}
<img class="buy-btn-icon" src="{{conf.buyBtnImg}}">
{{/if}}
</div>
... ...
<div class="product-container item{{numOfOneRow}} {{#if isStyle2}}is-style-2{{/if}}" {{#if proBgImg}} style="background:url({{image2 proBgImg q=85}}) repeat;background-size:100%;"
<div class="product-container item{{numOfOneRow}}{{#if isStyle2}} is-style-2{{/if}}{{#isEqualOr ufoProduct '1'}} is-ufo-style{{/isEqualOr}}" {{#if proBgImg}} style="background:url({{image2 proBgImg q=85}}) repeat;background-size:100%;"
{{/if}}>
<div class="product-source" condition='{{stringify searchCondition}}' fp="{{getAnalysis ../this @index}}" data-rownum="{{numOfOneRow}}" {{#unless defaultPros.length}}
<div class="product-source" condition='{{stringify searchCondition}}' fp="{{getAnalysis ../this @index}}" data-rownum="{{numOfOneRow}}"{{#if searchCondition}} data-lastsoldtpl="{{showLastSoldTpl}}"{{/if}} {{#unless defaultPros.length}}
{{#if searchCondition.item}} cloneitem="{{searchCondition.item}}" {{else}} cloneitem="{{searchCondition.limit}}" {{/if}}
{{/unless}}>
<input class="imgwh" type="hidden" value="386x514">
... ... @@ -31,9 +31,16 @@
{{/if}}
</div>
</div>
{{#isEqualOr showPrdName '1'}}
<p class="product-name"></p>
{{/isEqualOr}}
<div class="product-name-wrap">
{{#isEqualOr showPrdName '1'}}
<p class="product-name"></p>
{{/isEqualOr}}
{{#isEqualOr ufoProduct '0'}}
{{#if showLastSoldTpl}}
<p class="last-sold-num"><span class="sold-num-text"></span></p>
{{/if}}
{{/isEqualOr}}
</div>
<div class="product-detail-text">
{{#isEqualOr showSalePrice '1'}}
<div class="price" style="{{#if salePriceBgColor}}background:{{salePriceBgColor}};{{/if}}">
... ... @@ -47,6 +54,11 @@
<span class="vip-price-val"></span>
</div>
{{/isEqualOr}}
{{#isEqualOr ufoProduct '1'}}
{{#if showLastSoldTpl}}
<p class="last-sold-num"><span class="sold-num-text"></span></p>
{{/if}}
{{/isEqualOr}}
</div>
</a>
{{#if brandImg}}
... ... @@ -57,6 +69,10 @@
<img class="brand-img" src="{{image2 brandImg q=85}}">
</a>
{{/if}}
{{#if buyBtnImg}}
<img class="buy-btn-icon" src="{{buyBtnImg}}">
{{/if}}
</div>
{{/if}}
</div>
... ...
... ... @@ -37,7 +37,7 @@ const domains = {
imSocket: 'ws://socket.yohobuy.com:10240',
imCs: 'http://im.yohobuy.com/api',
global: 'http://api-global.yohobuy.com',
store: 'http://192.168.102.47:8080/portal-gateway/',
store: 'http://192.168.102.47:8080/portal-gateway/wechat/',
platformApi: 'http://192.168.102.48:8088/',
extstore: 'http://extstore.yohobuy.com',
... ... @@ -133,7 +133,7 @@ module.exports = {
notifyUrl: domains.service + 'payment/weixin_notify',
},
geetestJs: '//static.geetest.com/static/tools/gt.js',
jsSdk: 'https://cdn.yoho.cn/js-sdk/1.3.27-0/jssdk.js',
jsSdk: 'https://cdn.yoho.cn/js-sdk/1.3.30/jssdk.js',
redis: {
connect: {
host: '192.168.102.49',
... ...
... ... @@ -125,9 +125,27 @@ li {
z-index: 1000;
}
.tab-container {
a.active {
background: rgba(0, 0, 0, 0.2);
.tab-scroll-wrap {
width: 100%;
position: absolute;
top: 0;
bottom: 0;
overflow: hidden;
.tab-scroll-container {
width: 100%;
height: calc(100% + 40px);
padding-bottom: 40px;
overflow-x: scroll;
}
.tab-container {
height: 100%;
position: relative;
a.active {
background: rgba(0, 0, 0, 0.2);
}
}
}
}
... ...
... ... @@ -3,6 +3,177 @@
padding: 0 20px 10px;
font-size: 22px;
&.item1 .feature-product-info {
width: 100%;
margin-right: 0;
border-radius: 6px;
padding: 12px;
background-color: #fff;
position: relative;
&:before {
width: 80px;
height: 80px;
line-height: 74px;
text-align: center;
color: #fff;
font-size: 42px;
font-weight: 500;
padding-right: 20px;
background-image: url("img/activity/feature/top-icon.png");
background-size: 100% 100%;
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
z-index: 1;
transform: scale(0.6, 0.6);
transform-origin: 0 0 0;
}
&:nth-child(2):before {
content: "1";
}
&:nth-child(3):before {
content: "2";
}
&:nth-child(4):before {
content: "3";
}
&:nth-child(5):before {
content: "4";
}
&:nth-child(6):before {
content: "5";
}
&:nth-child(7):before {
content: "6";
}
&:nth-child(8):before {
content: "7";
}
&:nth-child(9):before {
content: "8";
}
&:nth-child(10):before {
content: "9";
}
&:nth-child(11):before {
content: "10";
}
.product-detail-imgbox {
width: 132px;
height: 176px;
.product-detail-img {
height: 100%;
}
}
.last-sold-num {
display: inline-block;
}
.product-name-wrap {
position: absolute;
top: 16px;
left: 160px;
right: 13px;
.last-sold-num {
color: #997509;
font-size: 24px;
height: 36px;
line-height: 34px;
background-color: #fff3c5;
border: 1px solid #fed931;
border-radius: 18px;
padding-left: 38px;
padding-right: 8px;
overflow: hidden;
position: relative;
top: 15px;
&:before {
content: "";
width: 36px;
height: 36px;
background-image: url("img/activity/feature/hot-icon.png");
background-size: 100% 100%;
position: absolute;
left: -1px;
top: -1px;
}
> span {
display: block;
transform: scale(0.9, 0.9);
}
}
}
.product-name {
height: auto;
max-height: 64px;
line-height: 32px;
font-size: 24px;
color: #222;
padding: 0;
}
.product-detail-text {
position: absolute;
left: 160px;
right: 13px;
bottom: 16px;
.price {
line-height: 1.1;
text-align: left;
display: flex;
align-items: center;
}
.sale-price {
font-size: 26px;
font-weight: 700;
}
.market-price {
font-size: 24px;
margin-left: 12px;
}
.last-sold-num {
color: #bebebe;
}
}
.second-part {
display: none;
}
.buy-btn-icon {
width: auto;
height: 36px;
position: absolute;
right: 12px;
bottom: 16px;
display: block;
z-index: 1;
}
}
&.single-item3,
&.item3 {
padding: 0 20px 10px;
... ... @@ -157,6 +328,14 @@
margin-top: 25px;
overflow: hidden;
.last-sold-num {
display: none;
}
.buy-btn-icon {
display: none;
}
&.novisible {
visibility: hidden;
}
... ... @@ -696,3 +875,153 @@
}
}
}
.is-ufo-style {
padding: 0 30px 10px !important;
.market-price {
display: none;
}
.feature-product-info {
width: 280px;
margin-right: 20px;
margin-top: 26px;
background-color: #fff;
.product-detail:after {
content: "";
display: block;
width: 100%;
}
&:nth-of-type(2n) {
margin-right: 0;
}
.product-detail-imgbox {
height: 280px;
overflow: hidden;
}
.product-name {
padding: 6px 16px;
margin-top: 0;
color: #7a7a7a;
}
.product-detail-text {
height: 40px;
margin: 14px 16px 28px;
display: flex;
justify-content: space-between;
align-items: flex-end;
flex-wrap: wrap;
overflow: hidden;
.price {
height: auto;
}
.sale-price {
font-size: 36px;
font-weight: 700;
font-style: italic;
color: #ff1925;
line-height: 1.1;
}
.last-sold-num {
display: block;
color: #aaa;
}
}
}
&.item1 .feature-product-info {
padding: 20px 16px;
border-radius: 0;
&:before {
content: none !important;
}
&:after {
content: "";
width: 218px;
height: 117px;
background-image: url("img/activity/feature/ufo-water-mark.png");
background-size: 100% 100%;
position: absolute;
right: 0;
bottom: 0;
}
.product-detail-imgbox {
width: 176px;
height: 176px;
}
.product-name-wrap {
top: 38px;
left: 210px;
}
.product-name {
font-size: 22px;
font-weight: 700;
}
.product-detail-text {
left: 210px;
bottom: 28px;
margin: 0;
display: block;
overflow: visible;
.sale-price {
font-size: 40px;
font-weight: 700;
font-style: italic;
}
}
.buy-btn-icon {
right: 20px;
bottom: 28px;
}
}
&.item3 .product-source {
width: 100%;
}
&.item3 .feature-product-info {
width: 188px;
margin-right: 8px;
&:nth-of-type(2n) {
margin-right: 8px;
}
.product-detail-imgbox {
height: 190px;
overflow: hidden;
}
.product-name {
padding: 4px;
margin-top: 0;
color: #7a7a7a;
}
.product-detail-text {
margin: 6px 6px 10px;
height: 34px;
.sale-price {
font-size: 32px;
}
}
}
}
... ...