Authored by 沈志敏

全球购列表

'use strict';
const headerModel = require('../../../doraemon/models/header');
const model = require('../models/global');
const list = (req, res, next) => {
let brand = req.query.brand;
let sort = req.query.sort;
let title = req.query.title;
if (!brand && !sort) {
return res.redirect('//m.yohobuy.com');
}
let param = {
limit: 12,
order: 's_t_desc',
page: 1,
uid: req.user.uid || 0
};
if (brand) {
param.brand = brand;
} else if (sort) {
param.sort = sort;
}
let arrs = [model.list(param)];
if (brand) {
arrs.push(model.getBrand({
brandId: brand,
uid: req.user.uid || 0,
client_type: 'iphone' // todo
}));
}
Promise.all(arrs).then((result) => {
res.render('global/list', {
module: 'product',
page: 'global-list',
pageHeader: headerModel.setNav({
navTitle: (result[1] || {}).brand_name || title
}),
list: result[0].list,
filter: result[0].filter,
pageFooter: true,
});
}).catch(next);
};
const search = (req, res, next) => {
let query = req.query;
if (query.type === 'new') {
query.order = 's_t_desc';
} else if (query.type === 'price') {
query.order = query.order === '1' ? 's_p_asc' : 's_p_desc';
}
model.list(query).then((result) => {
res.render('global/search', {
list: result.list,
layout: false
});
}).catch(next);
};
module.exports = {
list,
search,
detail: function() {
},
};
... ...
'use strict';
const utils = '../../../utils';
const productProcess = require(`${utils}/product-process`);
const globalapi = global.yoho.GlobalAPI;
exports.list = (param) => {
return globalapi.get('product/api/v2/detail/getlist', param).then((result) => {
if (!result || !result.data) {
return {};
}
return {
filter: productProcess.processFilter(result.data.filter || []),
list: result.data.product_list.map((data) => {
return {
product_url: `//m.yohobuy.com/product/global/${data.product_skn}.html`,
product_img: data.default_images,
price: data.formart_final_price,
product_name: data.product_name,
country_name: data.country_name,
is_stock: data.is_stock,
is_limited: data.is_limited,
is_plane: data.is_plane
};
})
};
});
};
exports.getBrand = (param) => {
return globalapi.get('editor/api/v1/brand/get', param).then((data) => {
return data.data || {};
});
};
... ...
... ... @@ -53,6 +53,9 @@ const newShop = require(`${cRoot}/new-shop`);
// 套装
const bundle = require(`${cRoot}/bundle`);
// 全球购
const globalPro = require(`${cRoot}/global`);
// routers
// /pro_136349_455445/HEARTSOFARMianMaShuJiaoXiuXianKuPS1684.html
... ... @@ -200,4 +203,8 @@ router.get('/detail/limitHelp', newDetail.limitHelp);
router.get('/index/allBrand', newShop.allBrand); // 店铺全部品牌
router.get('/global/list', globalPro.list); // 全球购列表页
router.get('/global/search', globalPro.search); // 全球购列表页搜索数据
router.get(/^\/global\/(\d+)\.html/, globalPro.detail); // 全球购店铺详情页
module.exports = router;
... ...
<div class="global-list-page yoho-page">
<div class="filter-tab">
<ul id="list-nav" class="list-nav clearfix">
<li class="active new buriedpoint" data-bp-id="shop_listnav_new_1">
<a href="javascript:void(0);">
<span class="nav-txt">最新</span>
</a>
</li>
<li class="price buriedpoint" data-bp-id="shop_listnav_price_1">
<a href="javascript:void(0);">
<span class="nav-txt">价格</span>
<span class="icon">
<i class="iconfont up cur">&#xe615;</i>
<i class="iconfont down">&#xe616;</i>
</span>
</a>
</li>
<li class="filter buriedpoint" data-bp-id="shop_listnav_filter_1">
<a href="javascript:void(0);">
<span class="nav-txt">筛选</span>
<span class="iconfont cur">&#xe613;</span>
</a>
</li>
</ul>
</div>
<div id="global-container" class="global-container">
<div class="new-goods container clearfix">
<div class="global-list clearfix">
{{# list}}
<div class="good-info">
<div class="global-tag-container clearfix">
<div class="global-country">
{{#isEqualOr is_plane 'Y'}}
<span class="global-plane"></span>
{{/isEqualOr}}
<span>{{country_name}}</span>
</div>
{{#isEqualOr is_limited 'Y'}}
<div class="global-limited">
<span>限量</span>
</div>
{{/isEqualOr}}
</div>
<div class="good-detail-img">
{{#isEqualOr is_stock 'N'}}
<div class="global-saleout">
<div class="bg"></div>
<span class="text">售罄</span>
</div>
{{/isEqualOr}}
<a class="good-thumb" href="{{product_url}}">
<img src="{{image2 product_img w=235 h=314 q=60}}">
</a>
</div>
<div class="good-detail-text">
<div class="name">
<a href="{{product_url}}">{{product_name}}</a>
</div>
<div class="price">
<span class="sale-price">{{price}}</span>
</div>
</div>
</div>
{{/ list}}
</div>
</div>
<div class="price-goods container clearfix hide"></div>
{{> common/filter}}
</div>
</div>
\ No newline at end of file
... ...
{{# list}}
<div class="good-info">
<div class="global-tag-container clearfix">
<div class="global-country">
{{#isEqualOr is_plane 'Y'}}
<span class="global-plane"></span>
{{/isEqualOr}}
<span>{{country_name}}</span>
</div>
{{#isEqualOr is_limited 'Y'}}
<div class="global-limited">
<span>限量</span>
</div>
{{/isEqualOr}}
</div>
<div class="good-detail-img">
{{#isEqualOr is_stock 'N'}}
<div class="global-saleout">
<div class="bg"></div>
<span class="text">售罄</span>
</div>
{{/isEqualOr}}
<a class="good-thumb" href="{{product_url}}">
<img src="{{image2 product_img w=235 h=314 q=60}}">
</a>
</div>
<div class="good-detail-text">
<div class="name">
<a href="{{product_url}}">{{product_name}}</a>
</div>
<div class="price">
<span class="sale-price">{{price}}</span>
</div>
</div>
</div>
{{/ list}}
\ No newline at end of file
... ...
... ... @@ -13,8 +13,11 @@ const domains = {
liveApi: 'http://testapi.live.yohops.com:9999/',
singleApi: 'http://api-test3.yohops.com:9999/',
api: 'http://api-test3.yohops.com:9999/',
service: 'http://service-test3.yohops.com:9999/',
// api: 'http://api-test3.yohops.com:9999/',
// service: 'http://service-test3.yohops.com:9999/',
api: 'http://api.yoho.cn/',
service: 'http://service.yoho.cn/',
global: 'http://api-global.yohobuy.com',
// liveApi: 'http://api.live.yoho.cn/',
// singleApi: 'http://single.yoho.cn/',
... ... @@ -116,6 +119,7 @@ if (isProduction) {
domains: {
api: 'http://api.yoho.yohoops.org/',
service: 'http://service.yoho.yohoops.org/',
global: 'http://api-global.yohobuy.com',
liveApi: 'http://api.live.yoho.cn/',
singleApi: 'http://single.yoho.cn/',
imSocket: 'wss://imsocket.yohobuy.com:443',
... ... @@ -164,6 +168,7 @@ if (isProduction) {
domains: {
api: process.env.TEST_API || 'http://api-test1.yohops.com:9999/',
service: process.env.TEST_SERVICE || 'http://service-test1.yohops.com:9999/',
global: process.env.TEST_GLOBAL || 'http://api-global.yohobuy.com',
liveApi: process.env.TEST_LIVE || 'http://testapi.live.yohops.com:9999/',
singleApi: process.env.TEST_SINGLE || 'http://api-test1.yohops.com:9999/',
imSocket: process.env.TEST_IM_SOCKET || 'ws://socket.yohobuy.com:10240',
... ...
'use strict';
import {
Controller
} from 'yoho-mvc';
import {
NavView,
ContentView
} from './view';
import {
search
} from '../models';
let filter = require('plugin/filter');
let noResultHbs = require('product/search/no-result-new.hbs');
class ListController extends Controller {
constructor() {
super();
this.navView = new NavView();
this.content = new ContentView();
this.page = 1;
this.searching = false;
this.nav = this.navView.getDefaultNav();
this.nav.reload = false;
this.query = $.extend(this.queryString(), {
type: 'new',
order: this.nav.order
});
this.navView.on('nav-change', this.doNavChange.bind(this));
this.content.on('search', this.doSearch.bind(this));
this.created();
}
queryString() {
let vars = {},
hash,
i;
let hashes = window.location.search.slice(1).split('&');
for (i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars[hash[0]] = hash[1];
}
return vars;
}
doNavChange(e, nav, navType) {
this.content.doContentChange(e, navType);
this.nav = nav;
if (nav.reload) {
this.nav.page = 0;
} else if (nav.end) {
return;
}
this.query = $.extend(this.query, {
type: navType,
order: nav.order
});
if (this.nav.page === 0) {
this.search();
}
}
doSearch() {
this.search();
}
search(opt) {
if (this.searching || this.nav.end) {
return;
}
let ext = {};
if (opt) {
switch (opt.type) {
case 'ageLevel':
ext = {
age_level: opt.id
};
break;
default:
ext[opt.type] = opt.id;
break;
}
this.query = $.extend({}, this.query, ext);
this.nav.reload = true;
this.nav.page = 0;
}
let page = this.nav.page + 1;
let params = $.extend({}, this.query, {
page: page
});
this.searching = true;
search(params).then(data => {
this.nav.page = page;
if (!data) {
this.nav.end = true;
if (this.nav.reload) {
this.content.setList(noResultHbs(), {
reload: true
});
}
} else {
if (this.nav.reload) {
this.content.setList(data, {
reload: true
});
} else {
this.content.setList(data, {});
}
}
}).catch(() => {
}).finally(() => {
this.nav.reload = false;
this.searching = false;
});
}
created() {
this.navView.setFilter(filter);
filter.initFilter({
fCbFn: this.search.bind(this),
hCbFn: () => {
this.navView.preActive();
}
});
}
}
module.exports = ListController;
... ...
require('product/global/list.page.css');
const ListController = require('./controller');
require('common/footer');
new ListController();
... ...
import {
View
} from 'yoho-mvc';
/**
* 排序 nav view
*/
class NavView extends View {
constructor() {
super('#list-nav');
// 初始化变量
this.navType = '';
this.navInfo = {
new: {
order: 0,
reload: true,
page: 1,
end: false
},
price: {
order: 1,
reload: true,
page: 0,
end: false
}
};
this.$pre = this.$base.find('.active');
// 事件委托
this.on('touchend touchcancel', 'li', this.onTabClick.bind(this));
}
/**
* nav tab click 事件处理
* @param {*} e
*/
onTabClick(e) {
let $this = $(e.currentTarget);
let $active;
if ($this.hasClass('filter')) {
// 筛选面板切换状态
if ($this.hasClass('active')) {
this.filter.hideFilter();
} else {
this.$pre = $this.siblings('.active');
this.filter.showFilter();
}
this.$pre.toggleClass('active');
$this.toggleClass('active');
return;
} else {
if ($this.hasClass('new')) {
this.navType = 'new';
} else if ($this.hasClass('price')) {
this.navType = 'price';
}
}
let nav = this.navInfo[this.navType];
if ($this.hasClass('active')) {
// 最新无排序切换
if ($this.hasClass('new')) {
return;
}
if ($this.hasClass('price')) {
// 价格切换排序状态
$this.find('.icon > .iconfont').toggleClass('cur');
this.$pre = $this; // 更新pre为当前项
nav.reload = true; // 重置reload,HTML会被替换为逆序的HTML
nav.order = nav.order === 0 ? 1 : 0; // 切换排序
}
} else {
$active = $this.siblings('.active');
this.$pre = $this; // $pre为除筛选导航的其他导航项,若当前active的为筛选,则把$pre置为当前点击项
if ($active.hasClass('filter')) {
// 若之前active项为筛选,则隐藏筛选面板
this.filter.hideFilter();
}
$active.removeClass('active');
$this.addClass('active');
}
this.emit('nav-change', nav, this.navType);
}
// 获取默认排序
getDefaultNav() {
return this.navInfo.new;
}
// 设置前一个tab为选中状态
preActive() {
this.$pre.addClass('active');
this.$pre.siblings('.filter').removeClass('active');
}
setFilter(filter) {
this.filter = filter;
}
}
/**
* 商品列表 view
*/
class ContentView extends View {
constructor() {
super('#global-container');
this.$ngc = this.$base.children('.new-goods');
this.$pgc = this.$base.children('.price-goods');
this.$current = this.$ngc;
this.winH = $(window).height();
// srcoll to load more
$(window).scroll(() => {
window.requestAnimationFrame(this.scrollHandler.bind(this));
});
}
/**
* 滚动事件处理
*/
scrollHandler() {
// 当scroll到1/2$goodsContainer高度后继续请求下一页数据
if ($(window).scrollTop() + this.winH >
$(document).height() - 0.5 * this.$current.height()) {
this.emit('search');
}
}
/**
* 商品列表切换
* @param {*} e
* @param {*} navType
*/
doContentChange(e, navType) {
// 切换container显示
this.$base.children('.container:not(.hide)').addClass('hide');
switch (navType) {
case 'new':
this.$ngc.removeClass('hide');
this.$current = this.$ngc;
break;
case 'price':
this.$pgc.removeClass('hide');
this.$current = this.$pgc;
break;
default:
break;
}
}
setList(data, opts) {
if (opts.reload) {
this.$current.html(data);
} else {
this.$current.append(data);
}
}
}
export {
NavView,
ContentView
};
... ...
'use strict';
import {
http
} from 'yoho-mvc';
function search(data) {
return http({
type: 'GET',
url: location.protocol + '//m.yohobuy.com/product/global/search',
data: data,
xhrFields: {
withCredentials: true
}
});
}
export {
search
};
... ...
'use strict';
const $ = require('yoho-jquery');
class View {
constructor(ele) {
this.$base = $(ele);
}
on(event, target, fn) {
if (typeof target === 'function') {
fn = target;
target = null;
}
if (this.$base) {
if (target) {
this.$base.on(event, target, fn);
} else {
this.$base.on(event, fn);
}
}
}
emit(event, ...data) {
this.$base.trigger(event, data);
}
}
class Controller {
constructor() {
}
}
function http(options) {
let ajax = $.ajax(options);
ajax.then = ajax.done.bind(ajax);
ajax.catch = ajax.fail.bind(ajax);
ajax.finally = ajax.always.bind(ajax);
return ajax;
}
export {
View, Controller, http
};
... ...
.global-list-page {
.list-nav {
border-top: 2px solid #fff;
border-bottom: 1px solid #e6e6e6;
> li {
float: left;
width: 33%;
height: 33PX;
line-height: 33PX;
text-align: center;
font-size: 14PX;
}
a {
display: block;
box-sizing: border-box;
width: 100%;
height: 100%;
color: #999;
}
.nav-txt {
display: inline-block;
height: 100%;
box-sizing: border-box;
}
.active > a {
color: #000;
.iconfont {
color: #999;
&.cur {
color: #000;
}
}
}
.new .iconfont {
transform: scale(0.8);
font-weight: bold;
font-size: 12PX;
}
.filter .iconfont {
font-size: 12PX;
transition: transform 0.1 ease-in;
}
.filter.active .iconfont {
transform: rotate(-180deg);
}
.icon {
position: relative;
i {
position: absolute;
transform: scale(0.8);
font-weight: bold;
}
.up {
top: -11PX;
}
.down {
top: -4PX;
}
}
}
.global-container {
position: relative;
min-height: auto !important;
padding-left: 15px;
padding-top: 20px;
.global-country {
float: left;
background: rgb(70, 46, 61);
color: #fff;
padding: 2px 8px;
font-size: 16px;
span {
float: left;
}
}
.global-limited {
float: left;
border: 1px solid rgb(70, 46, 61);
margin-left: 2px;
padding: 0 5px;
font-size: 16px;
}
.global-saleout {
width: 50px;
height: 50px;
position: absolute;
top: 0;
right: 5px;
.text {
width: 100%;
text-align: center;
font-size: 16px;
line-height: 24px;
position: absolute;
color: #fff;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.bg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: #999;
border-radius: 30%;
transform: rotate(45deg);
}
}
.global-plane {
width: 21px;
height: 21px;
background-image: resolve("product/airplane.png");
background-repeat: no-repeat;
background-size: cover;
margin-right: 10px;
margin-top: 2px;
}
.sale-price {
color: #000;
}
}
.no-result-new {
text-align: center;
padding-top: 90px;
padding-bottom: 110px;
p {
color: #ccc;
margin-bottom: 25px;
font-size: 26px;
&:first-child {
color: #444;
font-size: 32px;
}
}
}
}
... ...