Merge remote-tracking branch 'remotes/origin/develop' into feature/brand
Showing
25 changed files
with
1123 additions
and
27 deletions
apps/product/controllers/detail.js
0 → 100644
1 | +/** | ||
2 | + * | ||
3 | + * @author: Aiden Xu<aiden.xu@yoho.cn> | ||
4 | + * @date: 2016/07/19 | ||
5 | + */ | ||
6 | +'use strict'; | ||
7 | + | ||
8 | +// const _ = require('lodash'); | ||
9 | + | ||
10 | +// const helpers = global.yoho.helpers; | ||
11 | +const api = global.yoho.API; | ||
12 | +const _ = require('lodash'); | ||
13 | + | ||
14 | +/** | ||
15 | + * 商品详情 | ||
16 | + */ | ||
17 | +const component = { | ||
18 | + index(req, res) { | ||
19 | + const pid = req.params[0], goodsId = req.params[1]; | ||
20 | + | ||
21 | + res.render('detail', { | ||
22 | + module: 'product', | ||
23 | + page: 'detail', | ||
24 | + pid: pid, | ||
25 | + goodsId: goodsId | ||
26 | + }); | ||
27 | + }, | ||
28 | + product(req, res, next) { | ||
29 | + const pid = req.params[0];// , goodsId = req.params[1]; | ||
30 | + | ||
31 | + let params = { | ||
32 | + productId: _.toString(pid), | ||
33 | + method: 'h5.product.data' // TODO replace this to 'app.product.data' | ||
34 | + | ||
35 | + }; | ||
36 | + | ||
37 | + api.get('', params).then(result => { | ||
38 | + res.json(result); | ||
39 | + }).catch(next); | ||
40 | + }, | ||
41 | + intro(req, res, next) { | ||
42 | + let params = { | ||
43 | + method: 'h5.product.intro', // TODO replace this to 'app.product.intro' | ||
44 | + productskn: req.query.skn, | ||
45 | + udid: 'f528764d624db129b32c21fbca0cb8d6' | ||
46 | + }; | ||
47 | + | ||
48 | + api.get('', params).then(result => { | ||
49 | + res.json(result); | ||
50 | + }).catch(next); | ||
51 | + }, | ||
52 | + | ||
53 | + /** | ||
54 | + * 加入购物车接口 | ||
55 | + * | ||
56 | + */ | ||
57 | + addToCart(req, res, next) { | ||
58 | + let params = { | ||
59 | + method: 'app.Shopping.add', | ||
60 | + product_sku: req.body.productSku, // 商品SKU | ||
61 | + buy_number: req.body.buyNumber, // 购买数量 | ||
62 | + goods_type: req.body.goodsType || 0, // 商品类型,0表示普通商品,1表示加价购商品 | ||
63 | + edit_product_sku: req.body.isEdit || 0, // 是否是编辑商品SKU,0表示不是编辑 | ||
64 | + selected: 'Y', | ||
65 | + promotion_id: req.body.promotionId || null, // 促销id,默认null(加价购有关) | ||
66 | + uid: req.user.uid || null, // TODO: fix uid | ||
67 | + shopping_key: global.yoho.cookie.getShoppingKey(req) | ||
68 | + }; | ||
69 | + | ||
70 | + api.get('', params).then(result => { | ||
71 | + res.json(result); | ||
72 | + }).catch(next); | ||
73 | + }, | ||
74 | + | ||
75 | + getFavorite(req, res, next) { | ||
76 | + api.get('', {}).then(result => { | ||
77 | + res.json(result); | ||
78 | + }).catch(next); | ||
79 | + }, | ||
80 | + | ||
81 | + /** | ||
82 | + * 收藏 | ||
83 | + * | ||
84 | + * @param req | ||
85 | + * @param res | ||
86 | + * @param next | ||
87 | + */ | ||
88 | + addFavorite(req, res, next) { | ||
89 | + let params = { | ||
90 | + method: 'app.Shopping.addfavorite', | ||
91 | + product_sku_list: req.body.sku, | ||
92 | + uid: req.user.uid || 8050378 // TODO: fix this hard coded uid | ||
93 | + }; | ||
94 | + | ||
95 | + api.get('', params).then(result => { | ||
96 | + res.json(result); | ||
97 | + }).catch(next); | ||
98 | + }, | ||
99 | + | ||
100 | + /** | ||
101 | + * 获取购物车数量 | ||
102 | + * | ||
103 | + * @param req | ||
104 | + * @param res | ||
105 | + * @param next | ||
106 | + */ | ||
107 | + getCartCount: (req, res, next) => { | ||
108 | + let params = { | ||
109 | + method: 'app.Shopping.count', | ||
110 | + shopping_key: global.yoho.cookie.getShoppingKey(req), | ||
111 | + uid: req.user.uid || 0 // TODO fix uid | ||
112 | + }; | ||
113 | + | ||
114 | + api.get('', params).then(result => { | ||
115 | + res.json(result); | ||
116 | + }).catch(next); | ||
117 | + } | ||
118 | +}; | ||
119 | + | ||
120 | +module.exports = component; |
1 | /** | 1 | /** |
2 | - * sub app product | ||
3 | - * @author: chen xuan<xuan.chen@yoho.cn> | ||
4 | - * @date: 2016/07/19 | 2 | + * sub app |
3 | + * @author: Bi Kai<kai.bi@yoho.cn> | ||
4 | + * @date: 2016/05/09 | ||
5 | */ | 5 | */ |
6 | 6 | ||
7 | -var express = require('express'), | ||
8 | - path = require('path'), | ||
9 | - hbs = require('express-handlebars'); | 7 | +const express = require('express'); |
8 | +const path = require('path'); | ||
9 | +const hbs = require('express-handlebars'); | ||
10 | 10 | ||
11 | -var app = express(); | 11 | +const app = express(); |
12 | 12 | ||
13 | -// set view engin | ||
14 | -var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root | 13 | +// set view engine |
14 | +const doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root | ||
15 | 15 | ||
16 | app.on('mount', function(parent) { | 16 | app.on('mount', function(parent) { |
17 | delete parent.locals.settings; // 不继承父 App 的设置 | 17 | delete parent.locals.settings; // 不继承父 App 的设置 |
1 | /** | 1 | /** |
2 | * router of sub app product | 2 | * router of sub app product |
3 | + * @author: Aiden Xu<aiden.xu@yoho.cn> | ||
3 | * @author: chen xuan<xuan.chen@yoho.cn> | 4 | * @author: chen xuan<xuan.chen@yoho.cn> |
4 | * @date: 2016/07/19 | 5 | * @date: 2016/07/19 |
5 | */ | 6 | */ |
6 | 7 | ||
7 | 'use strict'; | 8 | 'use strict'; |
8 | 9 | ||
9 | -const Router = require('express').Router; | 10 | +const expressRouter = require('express').Router; |
10 | const cRoot = './controllers'; | 11 | const cRoot = './controllers'; |
11 | const productList = require(`${cRoot}/list`); | 12 | const productList = require(`${cRoot}/list`); |
12 | 13 | ||
13 | -const router = Router(); | 14 | +const router = expressRouter(); |
14 | 15 | ||
15 | // 商品列表 | 16 | // 商品列表 |
16 | router.use('/list', (req, res, next) => { | 17 | router.use('/list', (req, res, next) => { |
@@ -21,4 +22,14 @@ router.use('/list', (req, res, next) => { | @@ -21,4 +22,14 @@ router.use('/list', (req, res, next) => { | ||
21 | router.get('/list', productList.index); | 22 | router.get('/list', productList.index); |
22 | router.post('/list', productList.getProducts); | 23 | router.post('/list', productList.getProducts); |
23 | 24 | ||
25 | +// 商品详情controller | ||
26 | +const detail = require(`${cRoot}/detail`); | ||
27 | + | ||
28 | +router.get(/\/pro_([\d]+)_([\d]+)\/(.*)/, detail.index); // 商品详情routers | ||
29 | +router.get(/\/product_([\d]+)_([\d]+)\.json/, detail.product); | ||
30 | +router.get(/\/intro\.json/, detail.intro); | ||
31 | +router.post(/cart.json/, detail.addToCart); | ||
32 | +router.get(/favorite.json/, detail.getFavorite); | ||
33 | +router.post(/favorite.json/, detail.addFavorite); | ||
34 | +router.get(/cart-count.json/, detail.getCartCount); | ||
24 | module.exports = router; | 35 | module.exports = router; |
apps/product/views/action/detail.hbs
0 → 100644
@@ -13,20 +13,20 @@ module.exports = { | @@ -13,20 +13,20 @@ module.exports = { | ||
13 | app: 'h5', | 13 | app: 'h5', |
14 | appVersion: '4.6.0', // 调用api的版本 | 14 | appVersion: '4.6.0', // 调用api的版本 |
15 | port: 6004, | 15 | port: 6004, |
16 | - siteUrl: '//m.yohobuy.com', | 16 | + siteUrl: '//m.yohoblk.com', |
17 | domains: { | 17 | domains: { |
18 | api: 'http://devapi.yoho.cn:58078/', | 18 | api: 'http://devapi.yoho.cn:58078/', |
19 | - service: 'http://192.168.102.202:8080/gateway/' | 19 | + service: 'http://devservice.yoho.cn:58077/' |
20 | }, | 20 | }, |
21 | subDomains: { | 21 | subDomains: { |
22 | - host: '.m.yohobuy.com', | ||
23 | - default: '//m.yohobuy.com', | ||
24 | - guang: '//guang.m.yohobuy.com', | ||
25 | - list: '//list.m.yohobuy.com', | ||
26 | - search: '//search.m.yohobuy.com', | ||
27 | - huodong: '//huodong.m.yohobuy.com', | ||
28 | - activity: '//activity.yohobuy.com', | ||
29 | - index: '//m.yohobuy.com' | 22 | + host: '.m.yohoblk.com', |
23 | + default: '//m.yohoblk.com', | ||
24 | + guang: '//guang.m.yohoblk.com', | ||
25 | + list: '//list.m.yohoblk.com', | ||
26 | + search: '//search.m.yohoblk.com', | ||
27 | + huodong: '//huodong.m.yohoblk.com', | ||
28 | + activity: '//activity.yohoblk.com', | ||
29 | + index: '//m.yohoblk.com' | ||
30 | }, | 30 | }, |
31 | useOneapm: false, | 31 | useOneapm: false, |
32 | useCache: false, | 32 | useCache: false, |
1 | /** | 1 | /** |
2 | * 路由分发 | 2 | * 路由分发 |
3 | - * @author: xuqi<qi.xu@yoho.cn> | 3 | + * |
4 | + * @author: Aiden Xu<aiden.xu@yoho.cn> | ||
4 | * @date: 2016/4/27 | 5 | * @date: 2016/4/27 |
5 | */ | 6 | */ |
6 | 7 | ||
@@ -13,4 +14,6 @@ module.exports = app => { | @@ -13,4 +14,6 @@ module.exports = app => { | ||
13 | if (app.locals.devEnv) { | 14 | if (app.locals.devEnv) { |
14 | app.use('/example', require('./apps/example')); | 15 | app.use('/example', require('./apps/example')); |
15 | } | 16 | } |
17 | + | ||
18 | + app.use('/product', require('./apps/product')); | ||
16 | }; | 19 | }; |
@@ -5,18 +5,42 @@ let Vue = require('yoho-vue'); | @@ -5,18 +5,42 @@ let Vue = require('yoho-vue'); | ||
5 | * 替换参数 | 5 | * 替换参数 |
6 | * | 6 | * |
7 | * @example | 7 | * @example |
8 | - * value = /{width}/{height}/{model} | 8 | + * value = /{width}/{height}/{mode} |
9 | * | 9 | * |
10 | * {value | resize 100 200 2} ==> /100/200/2 | 10 | * {value | resize 100 200 2} ==> /100/200/2 |
11 | */ | 11 | */ |
12 | -Vue.filter('resize', (value, width, height, model)=>{ | ||
13 | - return value.replace(/({width}|{height}|{mode})/g, function($0) { | 12 | +Vue.filter('resize', (value, width, height, mode)=> { |
13 | + return value ? value.replace(/(\{width}|\{height}|\{mode})/g, function($0) { | ||
14 | const dict = { | 14 | const dict = { |
15 | '{width}': width, | 15 | '{width}': width, |
16 | '{height}': height, | 16 | '{height}': height, |
17 | - '{mode}': model || 2 | 17 | + '{mode}': mode || 2 |
18 | }; | 18 | }; |
19 | 19 | ||
20 | return dict[$0]; | 20 | return dict[$0]; |
21 | - }); | 21 | + }) : ''; |
22 | +}); | ||
23 | + | ||
24 | +/** | ||
25 | + * 性别款式 | ||
26 | + * | ||
27 | + * @example | ||
28 | + * | ||
29 | + * {value | gender} | ||
30 | + */ | ||
31 | +Vue.filter('clothingGenderIdentity', (value)=> { | ||
32 | + let ret = null; | ||
33 | + | ||
34 | + switch (value) { | ||
35 | + case 1: | ||
36 | + ret = '男款'; | ||
37 | + break; | ||
38 | + case 2: | ||
39 | + ret = '女款'; | ||
40 | + break; | ||
41 | + default: | ||
42 | + ret = '通用'; | ||
43 | + } | ||
44 | + | ||
45 | + return ret; | ||
22 | }); | 46 | }); |
public/js/product/detail.page.js
0 → 100644
1 | @charset "utf-8"; | 1 | @charset "utf-8"; |
2 | @import "common/index"; | 2 | @import "common/index"; |
3 | @import "example/index"; | 3 | @import "example/index"; |
4 | +@import "product/index"; | ||
4 | @import "channel/index"; | 5 | @import "channel/index"; |
5 | @import "home/index"; | 6 | @import "home/index"; |
6 | @import "brand/index"; | 7 | @import "brand/index"; |
public/scss/product/_index.css
0 → 100644
1 | +.feature-selector { | ||
2 | + background: #FFFFFF; | ||
3 | + width: 100%; | ||
4 | + height: 608px; | ||
5 | + bottom: 0; | ||
6 | + position: fixed; | ||
7 | + padding: 20px 30px 30px 30px; | ||
8 | + z-index: 1001; | ||
9 | + transform: translate3d(0, 100%, 0); | ||
10 | + transition: all 0.1s ease-in-out; | ||
11 | + | ||
12 | + .header { | ||
13 | + height: 120px; | ||
14 | + | ||
15 | + h3 { | ||
16 | + margin: 0; | ||
17 | + max-height: 60px; | ||
18 | + font-weight: 300; | ||
19 | + } | ||
20 | + h4 { | ||
21 | + color: #b0b0b0; | ||
22 | + font-weight: 200; | ||
23 | + font-size: 30px; | ||
24 | + margin-top: 32px; | ||
25 | + margin-bottom: 0; | ||
26 | + } | ||
27 | + .image-box { | ||
28 | + width: 90px; | ||
29 | + height: 120px; | ||
30 | + display: inline-block; | ||
31 | + } | ||
32 | + .text-box { | ||
33 | + display: inline-block; | ||
34 | + margin-left: 24px; | ||
35 | + max-width: 512px; | ||
36 | + } | ||
37 | + } | ||
38 | + | ||
39 | + hr { | ||
40 | + border: none; | ||
41 | + border-top: 1px solid #F0F0F0; | ||
42 | + margin-top: 30px; | ||
43 | + margin-bottom: 20px; | ||
44 | + } | ||
45 | + | ||
46 | + ul { | ||
47 | + list-style: none; | ||
48 | + border: none; | ||
49 | + margin-left: 18px; | ||
50 | + margin-top: 30px; | ||
51 | + margin-bottom: 0; | ||
52 | + padding: 0; | ||
53 | + } | ||
54 | + | ||
55 | + li { | ||
56 | + display: inline-block; | ||
57 | + } | ||
58 | + | ||
59 | + section { | ||
60 | + h4 { | ||
61 | + margin: 0; | ||
62 | + font-size: 25px; | ||
63 | + line-height: 80px; | ||
64 | + display: inline-block; | ||
65 | + } | ||
66 | + } | ||
67 | + | ||
68 | + .add-to-cart { | ||
69 | + width: 100%; | ||
70 | + margin-top: 50px; | ||
71 | + font-size: 27px; | ||
72 | + } | ||
73 | + | ||
74 | + &.slide-in { | ||
75 | + transform: translate3d(0, 0, 0); | ||
76 | + } | ||
77 | +} |
1 | +<template> | ||
2 | + <ul class="feature-options"> | ||
3 | + <li v-for="item in options"> | ||
4 | + <button :class="{ 'button-solid': value && value === item.value}" | ||
5 | + :disabled="item.disabled" | ||
6 | + @click="selectOption(item.value)" | ||
7 | + class="button feature-button"> | ||
8 | + {{item.text}} | ||
9 | + </button> | ||
10 | + </li> | ||
11 | + </ul> | ||
12 | +</template> | ||
13 | +<style src="./css/feature-options.css"></style> | ||
14 | +<script src="./js/feature-options.js"></script> |
1 | +<template> | ||
2 | + <div class="feature-selector" :class="{ 'slide-in': isVisible }"> | ||
3 | + <div class="header"> | ||
4 | + <div class="image-box"> | ||
5 | + <img :src="selection.thumbnail | resize 45 60"/> | ||
6 | + </div> | ||
7 | + <div class="text-box"> | ||
8 | + <h3>{{entity.productName}}</h3> | ||
9 | + <h4>{{entity.productPriceBo.formatSalesPrice}}</h4> | ||
10 | + </div> | ||
11 | + </div> | ||
12 | + | ||
13 | + <hr> | ||
14 | + | ||
15 | + <div> | ||
16 | + <section> | ||
17 | + <h4>颜色</h4> | ||
18 | + <feature-options name="color" :options="colors" :selection="selection.color"></feature-options> | ||
19 | + </section> | ||
20 | + <section> | ||
21 | + <h4>尺码</h4> | ||
22 | + <feature-options name="size" :options="sizes" :selection="selection.size"></feature-options> | ||
23 | + </section> | ||
24 | + <button @click="addToCart()" | ||
25 | + class="button button-solid add-to-cart">加入购物袋 | ||
26 | + </button> | ||
27 | + </div> | ||
28 | + </div> | ||
29 | +</template> | ||
30 | +<style src="./css/feature-selector.css"></style> | ||
31 | +<script src="./js/feature-selector.js"></script> |
1 | +module.exports = { | ||
2 | + props: { | ||
3 | + options: Array, | ||
4 | + name: String, | ||
5 | + selection: null | ||
6 | + }, | ||
7 | + data() { | ||
8 | + return { | ||
9 | + value: '' | ||
10 | + }; | ||
11 | + }, | ||
12 | + watch: { | ||
13 | + selection() { | ||
14 | + this.value = this.selection; | ||
15 | + } | ||
16 | + }, | ||
17 | + methods: { | ||
18 | + selectOption: function(opt) { | ||
19 | + this.value = opt; | ||
20 | + this.$parent.$emit(`feature:${this.name}.select`, opt); | ||
21 | + } | ||
22 | + } | ||
23 | +}; |
1 | +module.exports = { | ||
2 | + init() { | ||
3 | + }, | ||
4 | + props: { | ||
5 | + isVisible: Boolean, | ||
6 | + entity: Object, | ||
7 | + onAddToCart: Function | ||
8 | + }, | ||
9 | + watch: { | ||
10 | + isVisible() { | ||
11 | + const self = this; | ||
12 | + | ||
13 | + if (this.isVisible) { | ||
14 | + this.overlay = $.overlay({ | ||
15 | + onClose: function() { | ||
16 | + self.isVisible = false; | ||
17 | + } | ||
18 | + }); | ||
19 | + | ||
20 | + this.overlay.show(); | ||
21 | + } else { | ||
22 | + this.overlay.hide(); | ||
23 | + this.$parent.$emit('featureselector.close'); | ||
24 | + } | ||
25 | + }, | ||
26 | + entity() { | ||
27 | + const thumbnails = {}; | ||
28 | + const selection = {}; | ||
29 | + const colorSizes = {}; | ||
30 | + const stocks = {}; | ||
31 | + | ||
32 | + // 更新颜色 | ||
33 | + this.colors = this.entity.goodsList.filter((goods)=> { | ||
34 | + // 确保商品启用 | ||
35 | + return goods.status !== 0; | ||
36 | + }).map((goods)=> { | ||
37 | + // 缩略图 | ||
38 | + thumbnails[goods.colorId] = goods.colorImage; | ||
39 | + | ||
40 | + // 更新颜色对应尺码 | ||
41 | + colorSizes[goods.colorId] = goods.goodsSizeBoList.map((size)=> { | ||
42 | + if (!stocks[goods.colorId]) { | ||
43 | + stocks[goods.colorId] = 0; | ||
44 | + } | ||
45 | + | ||
46 | + // 默认选中有库存的第一个颜色尺码 | ||
47 | + if (size.goodsSizeStorageNum > 0) { | ||
48 | + if (!selection.color) { | ||
49 | + selection.color = goods.colorId; | ||
50 | + } | ||
51 | + | ||
52 | + if (!selection.size && size.goodsSizeStorageNum > 0) { | ||
53 | + selection.size = size.goodsSizeSkuId; | ||
54 | + } | ||
55 | + | ||
56 | + // 计算所有尺码的库存 | ||
57 | + stocks[goods.colorId] += size.goodsSizeStorageNum; | ||
58 | + } | ||
59 | + | ||
60 | + return { | ||
61 | + text: size.sizeName, | ||
62 | + value: size.goodsSizeSkuId, | ||
63 | + disabled: size.goodsSizeStorageNum === 0 | ||
64 | + }; | ||
65 | + }); | ||
66 | + | ||
67 | + return { | ||
68 | + text: goods.colorName, | ||
69 | + value: goods.colorId, | ||
70 | + disabled: stocks[goods.colorId] === 0 // 是否售完 | ||
71 | + }; | ||
72 | + }); | ||
73 | + | ||
74 | + this.sizes = colorSizes[selection.color]; | ||
75 | + this.colorSizes = colorSizes; | ||
76 | + this.thumbnails = thumbnails; | ||
77 | + | ||
78 | + // 选择默认值 | ||
79 | + this.$emit('feature:color.select', selection.color); | ||
80 | + this.$emit('feature:size.select', selection.size); | ||
81 | + } | ||
82 | + }, | ||
83 | + data() { | ||
84 | + return { | ||
85 | + colors: [], | ||
86 | + sizes: [], | ||
87 | + colorSizes: {}, | ||
88 | + thumbnails: {}, | ||
89 | + selection: { | ||
90 | + color: null, | ||
91 | + size: null, | ||
92 | + thumbnail: '' | ||
93 | + } | ||
94 | + }; | ||
95 | + }, | ||
96 | + components: { | ||
97 | + featureOptions: require('../feature-options.vue') | ||
98 | + }, | ||
99 | + created() { | ||
100 | + // 选择颜色 | ||
101 | + this.$on('feature:color.select', (opt)=> { | ||
102 | + const selection = { | ||
103 | + color: opt, | ||
104 | + size: ((color, size)=> { | ||
105 | + // 切换颜色后选择匹配的尺码 | ||
106 | + const sizes = this.colorSizes[color]; | ||
107 | + | ||
108 | + if (sizes && sizes.length > 0) { | ||
109 | + const oldSizes = sizes.filter((item) => { | ||
110 | + return item.value === size; | ||
111 | + }); | ||
112 | + | ||
113 | + if (oldSizes && oldSizes.length > 0) { | ||
114 | + const newSizes = this.colorSizes[opt]; | ||
115 | + | ||
116 | + const matchedSize = newSizes.filter((item)=> { | ||
117 | + return !item.disabled && item.text === oldSizes[0].text; | ||
118 | + }); | ||
119 | + | ||
120 | + if (matchedSize && matchedSize.length > 0) { | ||
121 | + return matchedSize[0].value; | ||
122 | + } | ||
123 | + } | ||
124 | + } | ||
125 | + | ||
126 | + return null; | ||
127 | + })(this.selection.color, this.selection.size), | ||
128 | + thumbnail: this.thumbnails[opt] | ||
129 | + }; | ||
130 | + | ||
131 | + this.sizes = this.colorSizes[opt]; | ||
132 | + Object.assign(this.selection, selection); | ||
133 | + }); | ||
134 | + | ||
135 | + // 选择尺码 | ||
136 | + this.$on('feature:size.select', (opt)=> { | ||
137 | + const selection = { | ||
138 | + size: opt | ||
139 | + }; | ||
140 | + | ||
141 | + Object.assign(this.selection, selection); | ||
142 | + }); | ||
143 | + }, | ||
144 | + methods: { | ||
145 | + /** | ||
146 | + * 将当前选择添加到购物车 | ||
147 | + */ | ||
148 | + addToCart() { | ||
149 | + // console.log(`${this.selection.color}:${this.selection.size}`); | ||
150 | + const sku = this.selection.size; | ||
151 | + | ||
152 | + $.post('/product/cart.json', { | ||
153 | + productSku: sku, | ||
154 | + buyNumber: 1 | ||
155 | + }).then((result)=> { | ||
156 | + this.onAddToCart(result); | ||
157 | + }); | ||
158 | + } | ||
159 | + } | ||
160 | +}; |
public/vue/product/css/detail.css
0 → 100644
1 | +.show-box .brand { | ||
2 | + max-height: 108px; | ||
3 | + line-height: 48px; | ||
4 | + overflow: hidden; | ||
5 | + | ||
6 | + img { | ||
7 | + vertical-align: middle; | ||
8 | + } | ||
9 | + | ||
10 | + h2 { | ||
11 | + font-size: 28px; | ||
12 | + vertical-align: middle; | ||
13 | + margin-left: 30px; | ||
14 | + } | ||
15 | + | ||
16 | + a { | ||
17 | + float: right; | ||
18 | + margin-top: 12px; | ||
19 | + font-size: 28px; | ||
20 | + color: #b0b0b0; | ||
21 | + display: inline-block; | ||
22 | + vertical-align: middle; | ||
23 | + } | ||
24 | +} | ||
25 | + | ||
26 | +.separator { | ||
27 | + text-align: center; | ||
28 | + color: #c4c4c4; | ||
29 | + height: 110px; | ||
30 | + line-height: 110px; | ||
31 | + margin-bottom: -20px; | ||
32 | + | ||
33 | + span { | ||
34 | + background: #f6f6f6; | ||
35 | + padding: 0 15px; | ||
36 | + } | ||
37 | + | ||
38 | + hr { | ||
39 | + max-width: 512px; | ||
40 | + margin-top: -55px; | ||
41 | + border: none; | ||
42 | + border-top: 1px solid #eeeeee; | ||
43 | + } | ||
44 | +} | ||
45 | + | ||
46 | +i.info { | ||
47 | + font-style: normal; | ||
48 | + color: #b0b0b0; | ||
49 | + margin-top: 24px; | ||
50 | + display: block; | ||
51 | + font-size: 18px; | ||
52 | +} | ||
53 | + | ||
54 | +.image-box { | ||
55 | + background: #ffffff; | ||
56 | +} | ||
57 | + | ||
58 | +.title-box { | ||
59 | + text-align: center; | ||
60 | + margin-bottom: 50px; | ||
61 | + max-height: 195px; | ||
62 | + | ||
63 | + h1 { | ||
64 | + text-align: center; | ||
65 | + font-size: 30px; | ||
66 | + line-height: 48px; | ||
67 | + font-weight: normal; | ||
68 | + max-width: 580px; | ||
69 | + margin: 30px auto 30px auto; | ||
70 | + } | ||
71 | + | ||
72 | + i.price { | ||
73 | + color: #b0b0b0; | ||
74 | + font-size: 32px; | ||
75 | + font-weight: lighter; | ||
76 | + font-style: normal; | ||
77 | + | ||
78 | + &.strike-through { | ||
79 | + text-decoration: line-through; | ||
80 | + } | ||
81 | + | ||
82 | + &.highlight { | ||
83 | + color: #d0021b; | ||
84 | + } | ||
85 | + } | ||
86 | +} | ||
87 | + | ||
88 | +.control-box { | ||
89 | + display: flex; | ||
90 | + flex-direction: row; | ||
91 | + justify-content: space-around; | ||
92 | + align-items: stretch; | ||
93 | + position: fixed; | ||
94 | + width: 100%; | ||
95 | + height: 99px; | ||
96 | + bottom: 0; | ||
97 | + | ||
98 | + .control-button { | ||
99 | + min-width: 100px; | ||
100 | + border: none; | ||
101 | + border-top: 1px solid #CCC; | ||
102 | + | ||
103 | + .icon { | ||
104 | + font-size: 40px; | ||
105 | + } | ||
106 | + | ||
107 | + } | ||
108 | + | ||
109 | + .control-button:first-child { | ||
110 | + border-right: 1px solid #CCC; | ||
111 | + } | ||
112 | + | ||
113 | + .button-solid { | ||
114 | + font-size: 26px; | ||
115 | + } | ||
116 | + | ||
117 | +} | ||
118 | + | ||
119 | +.horizon-wrapper { | ||
120 | + overflow-x: scroll; | ||
121 | +} | ||
122 | + | ||
123 | +.table { | ||
124 | + border-collapse: collapse; | ||
125 | + | ||
126 | + th { | ||
127 | + background: #f6f6f6; | ||
128 | + } | ||
129 | + | ||
130 | + th, td { | ||
131 | + border: 1px solid #eeeeee; | ||
132 | + min-width: 170px; | ||
133 | + line-height: 66px; | ||
134 | + text-align: center; | ||
135 | + vertical-align: middle; | ||
136 | + } | ||
137 | +} | ||
138 | + | ||
139 | +.wash-condition { | ||
140 | + display: flex; | ||
141 | + justify-content: space-around; | ||
142 | +} | ||
143 | + | ||
144 | +.wash-condition-item { | ||
145 | + flex: 1; | ||
146 | + text-align: center; | ||
147 | +} | ||
148 | + | ||
149 | +.description { | ||
150 | + font-size: 0; | ||
151 | + | ||
152 | + li { | ||
153 | + font-size: 24px; | ||
154 | + width: 325px; | ||
155 | + line-height: 40px; | ||
156 | + display: inline-block; | ||
157 | + } | ||
158 | + | ||
159 | + .desc-caption { | ||
160 | + color: #c7c7c7; | ||
161 | + min-width: 100px; | ||
162 | + } | ||
163 | +} | ||
164 | + | ||
165 | +.model-avatar { | ||
166 | + vertical-align: middle; | ||
167 | + border-radius: 100%; | ||
168 | +} | ||
169 | + | ||
170 | +.model-name { | ||
171 | + width: 100px; | ||
172 | + display: inline-block; | ||
173 | +} | ||
174 | + | ||
175 | +.badge-tr { | ||
176 | + margin-top: -10px; | ||
177 | +} |
public/vue/product/css/image-carousel.css
0 → 100644
1 | +.image-carousel { | ||
2 | + width: 100%; | ||
3 | + height: 1000px; | ||
4 | + | ||
5 | + .swipe { | ||
6 | + height: 100%; | ||
7 | + } | ||
8 | + | ||
9 | + .swipe-indicators { | ||
10 | + left: auto; | ||
11 | + right: 32px; | ||
12 | + } | ||
13 | + | ||
14 | + .swipe-indicator { | ||
15 | + width: 8px; | ||
16 | + height: 8px; | ||
17 | + line-height: 12px; | ||
18 | + display: inline-block; | ||
19 | + | ||
20 | + &.active { | ||
21 | + width: 12px; | ||
22 | + height: 12px; | ||
23 | + background: #000; | ||
24 | + opacity: 0.6; | ||
25 | + margin: -2px 5px; | ||
26 | + } | ||
27 | + } | ||
28 | +} |
public/vue/product/css/show-box.css
0 → 100644
1 | +.show-box { | ||
2 | + margin-top: 20px; | ||
3 | + background: #ffffff; | ||
4 | + border-top: 1px solid #eeeeee; | ||
5 | + border-bottom: 1px solid #eeeeee; | ||
6 | + padding: 30px; | ||
7 | + | ||
8 | + img { | ||
9 | + max-width: 100%; | ||
10 | + height: auto !important; | ||
11 | + } | ||
12 | + | ||
13 | + p { | ||
14 | + color: #808080; | ||
15 | + font-size: 24px; | ||
16 | + line-height: 48px; | ||
17 | + } | ||
18 | + | ||
19 | + hr { | ||
20 | + border: none; | ||
21 | + border-bottom: 1px solid #eeeeee; | ||
22 | + margin: 32px 0 20px 0; | ||
23 | + } | ||
24 | + | ||
25 | + h2 { | ||
26 | + margin: 0; | ||
27 | + font-size: 32px; | ||
28 | + text-align: left; | ||
29 | + color: #000000; | ||
30 | + font-weight: normal; | ||
31 | + display: inline-block; | ||
32 | + | ||
33 | + + i { | ||
34 | + font-size: 14px; | ||
35 | + font-style: normal; | ||
36 | + color: #b0b0b0; | ||
37 | + font-weight: normal; | ||
38 | + } | ||
39 | + } | ||
40 | + | ||
41 | + ul { | ||
42 | + list-style: none; | ||
43 | + padding: 0; | ||
44 | + } | ||
45 | + | ||
46 | + .image-box { | ||
47 | + float: left; | ||
48 | + } | ||
49 | + | ||
50 | + .text-box { | ||
51 | + float: left; | ||
52 | + margin-left: 20px; | ||
53 | + text-align: center; | ||
54 | + line-height: 36px; | ||
55 | + } | ||
56 | + | ||
57 | + .clear-fix { | ||
58 | + clear: both; | ||
59 | + } | ||
60 | + | ||
61 | + &.first-box { | ||
62 | + margin-top: 0; | ||
63 | + padding: 0; | ||
64 | + } | ||
65 | + | ||
66 | + &.last-box { | ||
67 | + margin-bottom: 99px; | ||
68 | + } | ||
69 | +} |
public/vue/product/detail.vue
0 → 100644
1 | +<template> | ||
2 | + <show-box :is-first="true"> | ||
3 | + <image-carousel :goods="entity.goodsList"></image-carousel> | ||
4 | + <div class="title-box"> | ||
5 | + <h1>{{entity.productName}}</h1> | ||
6 | + | ||
7 | + <i class="price" :class="{'strike-through': entity.productPriceBo.salesPrice > 0}">{{entity.productPriceBo.formatMarketPrice}}</i> | ||
8 | + | ||
9 | + <i v-if="entity.productPriceBo.salesPrice > 0" class="price highlight"> | ||
10 | + {{entity.productPriceBo.formatSalesPrice}} | ||
11 | + </i> | ||
12 | + </div> | ||
13 | + </show-box> | ||
14 | + | ||
15 | + <show-box> | ||
16 | + <div class="brand"> | ||
17 | + <img :src="entity.brand.brandIco | resize 110 68" width="55" height="34"/> | ||
18 | + | ||
19 | + <h2>{{entity.brand.brandName}}</h2> | ||
20 | + <a href="#"> | ||
21 | + 进入店铺 | ||
22 | + <span class="icon icon-right"></span> | ||
23 | + </a> | ||
24 | + </div> | ||
25 | + </show-box> | ||
26 | + | ||
27 | + <div class="separator"><span>继续拖动,查看商品信息</span> | ||
28 | + <hr/> | ||
29 | + </div> | ||
30 | + | ||
31 | + <show-box v-if="intro.productDescBo"> | ||
32 | + <h2>商品信息</h2> | ||
33 | + <i>DESCRIPTION</i> | ||
34 | + <hr> | ||
35 | + | ||
36 | + <ul class="description"> | ||
37 | + <li> | ||
38 | + <span class="desc-caption">编号:</span> | ||
39 | + <span>{{intro.productDescBo.erpProductId}}</span> | ||
40 | + </li> | ||
41 | + <li> | ||
42 | + <span class="desc-caption">颜色:</span> | ||
43 | + <span>{{intro.productDescBo.colorName}}</span> | ||
44 | + </li> | ||
45 | + <li> | ||
46 | + <span class="desc-caption">性别:</span> | ||
47 | + <span>{{intro.productDescBo.gender | clothingGenderIdentity}}</span> | ||
48 | + </li> | ||
49 | + <li v-for="item in intro.productDescBo.standardBos"> | ||
50 | + <span class="desc-caption">{{item.standardName}}:</span> <span>{{item.standardVal}}</span> | ||
51 | + </li> | ||
52 | + </ul> | ||
53 | + </show-box> | ||
54 | + | ||
55 | + | ||
56 | + <show-box v-if="intro.sizeInfoBo"> | ||
57 | + <h2>尺码信息</h2> | ||
58 | + <i>SIZE INFO</i> | ||
59 | + <hr> | ||
60 | + <div class="horizon-wrapper"> | ||
61 | + <table class="table"> | ||
62 | + <thead> | ||
63 | + <th>吊牌尺码</th> | ||
64 | + <th v-for="header in intro.sizeInfoBo.sizeAttributeBos">{{header.attributeName}}</th> | ||
65 | + </thead> | ||
66 | + <tbody> | ||
67 | + | ||
68 | + <tr v-for="size in intro.sizeInfoBo.sizeBoList"> | ||
69 | + <td>{{size.sizeName}}</td> | ||
70 | + <td v-for="item in size.sortAttributes">{{item.sizeValue}}</td> | ||
71 | + </tr> | ||
72 | + </tbody> | ||
73 | + </table> | ||
74 | + </div> | ||
75 | + | ||
76 | + <i class="info">提示:左滑查看完整表格信息</i> | ||
77 | + </show-box> | ||
78 | + | ||
79 | + <show-box v-if="intro.sizeImage"> | ||
80 | + <h2>测量方式</h2> | ||
81 | + <i>MEASUREMENT METHOD</i> | ||
82 | + <hr> | ||
83 | + <img v-if="intro.sizeImage" :src="intro.sizeImage"/> | ||
84 | + </show-box> | ||
85 | + | ||
86 | + <show-box v-if="intro.modelBos && intro.modelBos.length > 0"> | ||
87 | + <h2>模特试穿</h2> | ||
88 | + <i>REFERENCE</i> | ||
89 | + <hr> | ||
90 | + <div class="horizon-wrapper"> | ||
91 | + <table class="table"> | ||
92 | + <thead> | ||
93 | + <tr> | ||
94 | + <th>模特</th> | ||
95 | + <th>身高</th> | ||
96 | + <th>体重</th> | ||
97 | + <th>三围</th> | ||
98 | + <th>吊牌尺码</th> | ||
99 | + <th>试穿描述</th> | ||
100 | + </tr> | ||
101 | + </thead> | ||
102 | + <tbody> | ||
103 | + <tr v-for="item in intro.modelBos"> | ||
104 | + <td> | ||
105 | + <img class="model-avatar" :src="item.avatar"/> | ||
106 | + <span class="model-name">{{item.modelName}}</span> | ||
107 | + </td> | ||
108 | + <td>{{item.height}}</td> | ||
109 | + <td>{{item.weight}}</td> | ||
110 | + <td>{{item.vitalStatistics}}</td> | ||
111 | + <td>{{item.fitModelBo.fit_size}}</td> | ||
112 | + <td>{{item.fitModelBo.feel}}</td> | ||
113 | + </tr> | ||
114 | + </tbody> | ||
115 | + </table> | ||
116 | + </div> | ||
117 | + <i class="info">提示:左滑查看完整表格信息</i> | ||
118 | + </show-box> | ||
119 | + | ||
120 | + <show-box> | ||
121 | + <div v-if="intro.productMaterialList && intro.productMaterialList.length > 0"> | ||
122 | + <h2>商品材质</h2> | ||
123 | + <i>MATERIALS</i> | ||
124 | + <hr> | ||
125 | + </div> | ||
126 | + | ||
127 | + <div v-if="intro.productMaterialList"> | ||
128 | + <ul v-for="item in intro.productMaterialList"> | ||
129 | + <div> | ||
130 | + <div class="image-box"> | ||
131 | + <img :src="item.imageUrl" width="86" height="35"/> | ||
132 | + </div> | ||
133 | + <div class="text-box"> | ||
134 | + <div>{{item.caption}}</div> | ||
135 | + <div>{{item.encaption}}</div> | ||
136 | + </div> | ||
137 | + <div class="clear-fix"></div> | ||
138 | + </div> | ||
139 | + <p> | ||
140 | + {{item.remark}} | ||
141 | + </p> | ||
142 | + | ||
143 | + <hr/> | ||
144 | + </ul> | ||
145 | + </div> | ||
146 | + | ||
147 | + <ul class="wash-condition"> | ||
148 | + <li class="wash-condition-item" v-for="item in intro.washTipsBoList"> | ||
149 | + <img :src="item.img" width="25" height="25"/> | ||
150 | + <div>{{item.caption}}</div> | ||
151 | + </li> | ||
152 | + </ul> | ||
153 | + </show-box> | ||
154 | + | ||
155 | + <show-box :is-last="true"> | ||
156 | + <h2>商品详情</h2> | ||
157 | + <i>DETAILS</i> | ||
158 | + <p> | ||
159 | + {{{entity.brand.brandIntro}}} | ||
160 | + </p> | ||
161 | + | ||
162 | + <p v-if="intro.productIntroBo"> | ||
163 | + {{{intro.productIntroBo.productIntro}}} | ||
164 | + </p> | ||
165 | + </show-box> | ||
166 | + | ||
167 | + <div class="control-box"> | ||
168 | + <button class="button control-button" style="flex: 1"> | ||
169 | + <a style="position:relative"> | ||
170 | + <i class="icon icon-bag"></i> | ||
171 | + <span v-if="cartCount > 0" class="badge badge-tr">{{cartCount}}</span> | ||
172 | + </a> | ||
173 | + </button> | ||
174 | + <button class="button control-button" style="flex: 1"> | ||
175 | + <span class="icon icon-love"></span> | ||
176 | + </button> | ||
177 | + <button class="button button-solid" style="flex: 2" | ||
178 | + @click="showAddToCart()" | ||
179 | + :disabled="isSoldOut"> | ||
180 | + <span v-if="isSoldOut"> | ||
181 | + 已售完 | ||
182 | + </span> | ||
183 | + <span v-else=""> | ||
184 | + 加入购物袋 | ||
185 | + </span> | ||
186 | + </button> | ||
187 | + </div> | ||
188 | + | ||
189 | + <feature-selector :is-visible="showFeatureSelector" :entity="entity" | ||
190 | + :on-add-to-cart="onAddToCart"></feature-selector> | ||
191 | +</template> | ||
192 | +<style src="./css/detail.css"></style> | ||
193 | +<script src="./js/detail.js"></script> |
public/vue/product/image-carousel.vue
0 → 100644
1 | +<style src="./css/image-carousel.css"></style> | ||
2 | +<template> | ||
3 | + <div class="image-carousel"> | ||
4 | + <swipe> | ||
5 | + <swipe-item v-for="item in goods"> | ||
6 | + <a href="#" title="{{item.title}}"> | ||
7 | + <img :src="item.colorImage | resize 750 1000" width="375" height="500" alt=""> | ||
8 | + </a> | ||
9 | + </swipe-item> | ||
10 | + </swipe> | ||
11 | + </div> | ||
12 | +</template> | ||
13 | +<script> | ||
14 | + const swipe = require('vue-swipe'); | ||
15 | + | ||
16 | + require('common/vue-filter'); | ||
17 | + | ||
18 | + module.exports = { | ||
19 | + props: { | ||
20 | + goods: [Object] | ||
21 | + }, | ||
22 | + data() { | ||
23 | + return {}; | ||
24 | + }, | ||
25 | + components: { | ||
26 | + swipe: swipe.Swipe, | ||
27 | + swipeItem: swipe.SwipeItem | ||
28 | + } | ||
29 | + }; | ||
30 | +</script> |
public/vue/product/js/detail.js
0 → 100644
1 | +const app = $('#app'); | ||
2 | +const tip = require('common/tip'); | ||
3 | + | ||
4 | +require('vue-swipe/dist/vue-swipe.css'); | ||
5 | + | ||
6 | +module.exports = { | ||
7 | + data() { | ||
8 | + return { | ||
9 | + intro: {}, | ||
10 | + entity: { | ||
11 | + brand: { | ||
12 | + brandName: '', | ||
13 | + brandIco: '' | ||
14 | + }, | ||
15 | + productPriceBo: { | ||
16 | + formatMarketPrice: '' | ||
17 | + } | ||
18 | + }, | ||
19 | + showFeatureSelector: false, | ||
20 | + cartCount: 0, | ||
21 | + | ||
22 | + /** | ||
23 | + * 加入购物车回调 | ||
24 | + * | ||
25 | + * @param result | ||
26 | + */ | ||
27 | + onAddToCart: (result)=> { | ||
28 | + // TODO: 库存不足 | ||
29 | + // TODO: 商品已下架 | ||
30 | + if (result.code === 200) { | ||
31 | + this.cartCount = result.data.goods_count; | ||
32 | + this.showFeatureSelector = false; | ||
33 | + } else { | ||
34 | + this.showFeatureSelector = false; | ||
35 | + tip('系统异常,请稍后重试'); | ||
36 | + } | ||
37 | + } | ||
38 | + }; | ||
39 | + }, | ||
40 | + computed: { | ||
41 | + isSoldOut: function() { | ||
42 | + return this.entity.storage === 0; | ||
43 | + } | ||
44 | + }, | ||
45 | + components: { | ||
46 | + imageCarousel: require('../image-carousel.vue'), | ||
47 | + featureSelector: require('component/product/feature-selector.vue'), | ||
48 | + showBox: require('../show-box.vue') | ||
49 | + }, | ||
50 | + methods: { | ||
51 | + showAddToCart: function() { | ||
52 | + this.showFeatureSelector = true; | ||
53 | + } | ||
54 | + }, | ||
55 | + created() { | ||
56 | + const self = this; | ||
57 | + | ||
58 | + // 显示商品特征选择组件 | ||
59 | + this.$on('featureselector.close', function() { | ||
60 | + self.showFeatureSelector = false; | ||
61 | + }); | ||
62 | + | ||
63 | + // 读取基础数据 | ||
64 | + $.get(`/product/product_${app.data('pid')}_${app.data('goodsId')}.json`).then(result=> { | ||
65 | + this.entity = result; | ||
66 | + return result; | ||
67 | + }).then((result)=> { | ||
68 | + // 读取商品详情 | ||
69 | + return $.get('/product/product/intro.json', {skn: result.productPriceBo.productSkn}); | ||
70 | + }).then(result => { | ||
71 | + this.intro = result; | ||
72 | + }); | ||
73 | + | ||
74 | + // 读取购物车数量 | ||
75 | + $.get('/product/cart-count.json', {}).then(result=> { | ||
76 | + if (result.code === 200) { | ||
77 | + this.cartCount = result.data.cart_goods_count; | ||
78 | + } | ||
79 | + }); | ||
80 | + } | ||
81 | +}; |
public/vue/product/show-box.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="show-box" :class="{ 'first-box': isFirst, 'last-box': isLast }"> | ||
3 | + <slot></slot> | ||
4 | + </div> | ||
5 | +</template> | ||
6 | +<style src="./css/show-box.css"></style> | ||
7 | +<script> | ||
8 | + module.exports = { | ||
9 | + props: { | ||
10 | + isFirst: [Boolean], | ||
11 | + isLast: [Boolean] | ||
12 | + }, | ||
13 | + data() { | ||
14 | + return {}; | ||
15 | + } | ||
16 | + }; | ||
17 | +</script> |
-
Please register or login to post a comment