Authored by htoooth

Merge branch 'feature/ufo' of http://git.yoho.cn/fe/yoho-app-web into feature/ufo

# Conflicts:
#	config/api-map.js
#	config/common.js
... ... @@ -31,7 +31,7 @@ if (cluster.isMaster) {
if (msg.action === 'ssr_request') {
realyPromise.then(() => {
renderer.renderToString(msg.context, (err, html) => {
worker.send({action: 'ssr_request', html, err: err && (err.stack || JSON.stringify(err))});
worker.send({action: 'ssr_request', html, err: err && JSON.stringify(err)});
});
});
}
... ...
const api = global.yoho.API;
const service = global.yoho.ServiceAPI;
const yohoApi = global.yoho.API;
const ufoAPI = global.yoho.UfoAPI;
const serviceApi = global.yoho.ServiceAPI;
const checkParams = require('../../utils/check-params');
const apiMaps = require('../../config/api-map');
const NOT_FOUND_API_MAP = {
code: 400,
message: '未找到对应的接口'
};
const checkApiMap = url => {
return apiMaps[url] ? apiMaps[url] : void 0;
};
... ... @@ -15,7 +12,7 @@ const request = async({url, method, reqParams, context}) => {
const {env, user} = context;
if (!apiInfo) {
return Promise.reject(NOT_FOUND_API_MAP);
return Promise.reject(new Error(`未找到对应的接口:${url}`));
}
if (!apiInfo.service) {
Object.assign(reqParams, {
... ... @@ -32,26 +29,29 @@ const request = async({url, method, reqParams, context}) => {
const params = checkParams.getParams(reqParams, apiInfo);
const cache = method.toLowerCase() !== 'get' ? false : apiInfo.cache;
const headers = {
'X-YOHO-IP': env.clientIp,
'X-Forwarded-For': env.clientIp,
'User-Agent': 'yoho/nodejs'
};
if (apiInfo.service) {
return await service.get(apiInfo.api, params, {
return await serviceApi.get(apiInfo.api, params, {
cache: cache,
code: 200,
headers: {
'X-YOHO-IP': env.clientIp,
'X-Forwarded-For': env.clientIp,
'User-Agent': 'yoho/nodejs'
}
headers
});
} else if (apiInfo.ufo) {
return await ufoAPI[method]('', params, {
cache: cache,
code: 200,
headers
});
} else {
return await api[method]('', params, {
return await yohoApi[method]('', params, {
code: 200,
cache: cache,
headers: {
'X-YOHO-IP': env.clientIp,
'X-Forwarded-For': env.clientIp,
'User-Agent': 'yoho/nodejs'
}
headers
});
}
};
... ...
<template>
<img :src="currentSrc" :alt="alt">
</template>
<script>
export default {
name: 'ImgSize',
props: {
src: String,
width: Number,
height: Number,
alt: String
},
computed: {
currentSrc() {
return (this.src || '')
.replace('{width}', this.width)
.replace('{height}', this.height);
}
}
}
</script>
<style>
</style>
... ...
... ... @@ -8,7 +8,7 @@ const sender = global.yoho.apmSender;
const logger = global.yoho.logger;
const catchError = (err, context) => {
logger.error(`[catchError], ${err}`);
logger.error(err);
setImmediate(() => {
try {
sender.addMessage({
... ...
... ... @@ -20,5 +20,6 @@ export default {
right: 0;
bottom: 0;
overflow: hidden;
font-size: 24px;
}
</style>
... ...
<template>
<div v-show="value" v-transfer-dom :data-transfer="transfer">
<div class="modal-box" v-show="value" v-transfer-dom :data-transfer="transfer">
<div class="modal-mask"></div>
<div class="modal-wrap" @touchmove.prevent.stop="onTouchmove">
<div class="modal modal-content">
... ... @@ -44,6 +44,10 @@ export default {
</script>
<style lang="scss">
.modal-box {
font-size: 24px;
}
.modal-mask {
position: fixed;
top: 0;
... ...
<template>
<Modal class="ufo-font" :value="value" @input="onInput" ref="modal" :transfer="true" @on-sure="onSure">
<Modal class="ufo-font" :value="visiable" @input="onInput" ref="modal" :transfer="true" @on-sure="onSure" @on-cancel="onCancel">
<div class="change-price-modal">
<p class="modal-title">选择你要下架的数量</p>
<Inputx v-model="stockNum" :maxlength="8" :readonly="true" class="input-number">
<Inputx v-model="unStockNum" :maxlength="8" :readonly="true" class="input-number">
<i slot="prefix" class="iconfont icon-plus-minus" @touchend="onChangeNum(-1)"></i>
<i slot="suffix" class="iconfont icon-i-add" @touchend="onChangeNum(1)"></i>
</Inputx>
<p class="stock-txt">
目前还有 6 个库存
目前还有 {{storageNum}} 个库存
</p>
<p class="tips">
下架商品的保证金将会被释放
... ... @@ -20,31 +20,38 @@
import Inputx from '../../components/inputx.vue';
import Modal from '../../components/modal.vue';
export default {
name: 'ModalPrice',
props: {
value: Boolean,
},
data() {
return {
stockNum: 1,
maxNum: 6,
visiable: false,
unStockNum: 1,
storageNum: 6,
};
},
methods: {
show({storageNum}) {
this.storageNum = storageNum;
this.visiable = true;
},
onSure() {
this.$emit('on-sure');
this.$emit('on-sure', this.unStockNum);
this.visiable = false;
},
onCancel() {
this.visiable = false;
},
onInput(val) {
this.$emit('input', val);
},
onChangeNum(num) {
const value = this.stockNum + num;
const value = this.unStockNum + num;
if (value <= 0 || value > this.maxNum) {
if (value <= 0 || value > this.storageNum) {
return;
}
this.stockNum = value;
this.unStockNum = value;
}
},
components: {Modal, Inputx}
... ...
... ... @@ -4,13 +4,13 @@
<div class="tip" v-if="value.tip">超出建议售价将被限制超出建议售价将被限制展示</div>
<div class="info">
<div class="left">
<span class="size">{{value.size}}</span>
<span class="size">{{value.goodsInfo.sizeName}}</span>
<span class="l-size">1/3</span>
<span class="unit">码</span>
</div>
<div class="middle">
<p class="size-store">¥{{value.price}},12个库存</p>
<p class="low-price">当前最低价¥{{value.price}}</p>
<p class="size-store">¥{{value.goodsInfo.goodPrice}},{{value.goodsInfo.storageNum}}个库存</p>
<p class="low-price">当前最低价¥{{value.goodsInfo.leastPrice}}</p>
</div>
<div class="right">
<Button class="chg-price" @click="onChgPrice">调 价</Button>
... ... @@ -64,10 +64,10 @@ export default {
},
methods: {
onNoSale() {
this.$emit('on-no-sale');
this.$emit('on-no-sale', this.value.goodsInfo);
},
onChgPrice() {
this.$emit('on-chg-price');
this.$emit('on-chg-price', this.value.goodsInfo);
},
onTouchStart(evt) {
const {clientX, clientY} = evt.touches[0];
... ... @@ -146,7 +146,7 @@ export default {
bottom: 0;
z-index: 1;
.cube-btn {
.btn-no-sale {
height: 100%;
line-height: 1;
background-color: #eee;
... ...
... ... @@ -27,6 +27,12 @@
import ModalPrice from './modal-price';
import ModalUnstock from './modal-unstock';
import ProductItem from './product-item';
import {createNamespacedHelpers} from 'vuex';
import Vue from 'vue';
import {Toast} from 'cube-ui';
Vue.use(Toast);
const {mapActions} = createNamespacedHelpers('ufo/order');
export default {
name: 'ProductList',
... ... @@ -46,20 +52,34 @@ export default {
this.modalLoad = true;
},
methods: {
...mapActions(['postNoSale']),
onPriceSure() {
this.showTips = !this.showTips;
},
onUnstockSure() {
this.showTips = !this.showTips;
async onUnstockSure(product) {
const result = await this.postNoSale(product);
if (result.code === 200) {
this.$createToast({
txt: '下架成功',
time: 5000,
type: 'correct'
}).show();
} else {
this.$createToast({
txt: result.message || '下架失败',
type: 'warn'
}).show();
}
this.slideSkc = {};
},
onChgPrice() {
this.showModalPrice = true;
},
onNoSale() {
this.showModalUnstock = true;
onNoSale(productInfo) {
this.$refs.modalUnstock.show(productInfo);
},
onSlide(val) {
console.log('onSlide')
this.slideSkc = val;
}
},
... ...
... ... @@ -2,31 +2,37 @@
<LayoutApp class="ufo-font" :class="classes">
<ScrollView ref="scroll" :observe-dom="false" :pull-up-load="true" :pull-down-refresh="true" @pullingUp="onPullingUp" @pullingDown="onPullingDown">
<div class="order-page">
<div class="title">出售中</div>
<div class="title">{{productInfo.statuStr}}</div>
<div class="product">
<img class="pro-img" src="//img11.static.yhbimg.com/goodsimg/2018/11/23/12/017e70f47d95b3e2f93f7946e0574a291a.jpg?imageMogr2/thumbnail/200x200/background/d2hpdGU=/position/center/quality/80" alt="">
<ImgSize class="pro-img" :src="productInfo.goodsImg"></ImgSize>
<div class="pro-info">
<p class="pro-name">Air Jordan 11 Con版康扣Con版康扣 2018年版2018年版</p>
<p class="stock-info">5个尺码,39个商品库存</p>
<p class="pro-name">{{productInfo.productName}}</p>
<p class="stock-info">{{productInfo.sizeNum}}个尺码,{{productInfo.storageNum}}个商品库存</p>
</div>
</div>
<div class="arrival">
<p class="arrival-time"><i class="iconfont icon-info"></i><span>尺码列表左滑选择 不卖了 ,下架当前尺码商品</span></p>
</div>
<ProductList :skcs="orderDetail.skcs"></ProductList>
<ProductList :skcs="sizes"></ProductList>
</div>
</ScrollView>
</LayoutApp>
</template>
<script>
import ImgSize from 'components/img-size';
import {Button} from 'cube-ui';
import ScrollView from 'components/scroll-view';
import ProductList from './components/product-list';
import Vue from 'vue';
import {Toast} from 'cube-ui';
import {createNamespacedHelpers} from 'vuex';
import {Style} from 'cube-ui';
const {mapState, mapActions} = createNamespacedHelpers('ufo/order');
Vue.use(Toast);
const {mapState} = createNamespacedHelpers('ufo/order');
export default {
name: 'OrderPage',
... ... @@ -40,16 +46,18 @@ export default {
};
},
computed: {
...mapState(['orderProduct', 'orderDetail'])
...mapState(['orderInfo']),
productInfo() {
return this.orderInfo.productInfo || {};
},
sizes() {
return this.orderInfo.data || [];
}
},
asyncData({store, router}) {
return store.dispatch('ufo/order/fetchProduct', {orderId: router.params.orderId});
},
mounted() {
this.fetchOrderDetail({orderId: this.$route.params.orderId});
},
methods: {
...mapActions(['fetchOrderDetail']),
onPullingUp() {
setTimeout(() => {
this.$refs.scroll.forceUpdate();
... ... @@ -64,7 +72,7 @@ export default {
this.showModalUnstock = true;
},
},
components: {Button, ScrollView, ProductList}
components: {Button, ScrollView, ProductList, ImgSize}
};
</script>
... ...
... ... @@ -19,6 +19,8 @@ html {
-webkit-text-size-adjust: 100%;
/* 2 */
user-select:none;
font-size: initial;
}
/**
... ...
import * as Types from './types';
export default {
async fetchProduct({commit}, {orderId}) {
async fetchProduct({commit}, {productId}) {
commit(Types.FETCH_ORDER_PRODUCT_REQUEST);
// const result = await this.$api.get('/getproductxxx', {orderId});
const result = await Promise.resolve({
code: 200,
data: {
name: '测试产品',
image: 'xxx'
// const result = await this.$api.get('/api/ufo/seller/entryGoodsSizeList', {
// productId,
// });
const result = {
"alg": "SALT_MD5",
"code": 200,
"data": {
productInfo:{
"productId": 10000078,
"productName": "Air Jordan XX9 Low “Chicago Bulls”",
"colorName":"红色",
"goodsImg" : "http://img11.static.yhbimg.com/goodsimg/2018/10/18/17/01699014e8981a532f27abc74730e40bbd.jpg?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80",
"sizeNum":5,
"storageNum":100,
"statuStr": "出售中",
"status": 1,
},
"data": [
{
"buttons": [
{
"code": "batch_cancel_sold",
"name": "BATCH_CANCEL_SOLD",
"text": "不卖了"
},
{
"code": "change_price",
"name": "CHANGE_PRICE",
"text": "调价"
}
],
"createTime": "2018-11-05 15:03:27",
"goodsInfo": {
"colorName": "",
"goodImg": "",
"goodPrice": "9.00",
"storageNum":100,
"sizeName": "L",
"storageId": 10000162,
"leastPrice": 1119
},
"secendLevelCreateTime": 1541401407,
"uid": 500031152
}
],
"page": 1,
"pageSize": 20,
"pagetotal": 1,
"total": 2
},
"md5": "42a616d78633c19d80eb410da1ae4644",
"message": "列表"
}
});
if (result && result.code === 200) {
commit(Types.FETCH_ORDER_PRODUCT_SUCCESS, {
product: result.data
order: result.data
});
} else {
commit(Types.FETCH_ORDER_PRODUCT_FAILD);
}
},
async fetchOrderDetail({commit}, {orderId}) {
commit(Types.FETCH_ORDERDETAIL_REQUEST);
// const result = await this.$api.get('/getproductxxx', {orderId});
const skcs = [];
for (let i = 0; i < 40; i++) {
skcs.push({
id: 1,
size: '39',
num: 10,
price: 1089,
tip: !(i % 3)
})
}
const result = await Promise.resolve({
code: 200,
data: {
name: '测试订单',
image: 'xxx',
skcs: skcs
}
});
async postNoSale({commit}, {product}) {
commit(Types.POST_NOSALE_REQUEST);
// const result = await this.$api.get('/api/ufo/sellerOrder/batchDownShelf', product);
const result = {
"alg": "SALT_MD5",
"code": 200,
"data": true,
"md5": "40f3824dd1e55b340dc5357dd7ccab72",
"message": "批量取消成功"
};
if (result && result.code === 200) {
commit(Types.FETCH_ORDERDETAIL_SUCCESS, {
commit(Types.POST_NOSALE_SUCCESS, {
order: result.data
});
} else {
commit(Types.FETCH_ORDERDETAIL_FAILD);
commit(Types.POST_NOSALE_FAILD);
}
return result || {};
}
};
... ...
... ... @@ -6,11 +6,8 @@ export default function() {
namespaced: true,
state: {
fetchingPro: false,
orderProduct: {},
fetchingOrder: false,
orderDetail: {
skcs: []
}
fetchingNoSale: false,
orderInfo: {},
},
actions,
mutations
... ...
... ... @@ -2,25 +2,23 @@ import * as Types from './types';
export default {
[Types.FETCH_ORDER_PRODUCT_REQUEST](state) {
state.orderProduct = {};
state.orderInfo = {};
state.fetchingPro = true;
},
[Types.FETCH_ORDER_PRODUCT_FAILD](state) {
state.fetchingPro = false;
},
[Types.FETCH_ORDER_PRODUCT_SUCCESS](state, {product}) {
[Types.FETCH_ORDER_PRODUCT_SUCCESS](state, {order}) {
state.fetchingPro = false;
state.orderProduct = product;
state.orderInfo = order;
},
[Types.FETCH_ORDERDETAIL_REQUEST](state) {
state.orderDetail = {};
state.fetchingOrder = true;
[Types.POST_NOSALE_REQUEST](state) {
state.fetchingNoSale = true;
},
[Types.FETCH_ORDERDETAIL_FAILD](state) {
state.fetchingOrder = false;
[Types.POST_NOSALE_FAILD](state) {
state.fetchingNoSale = false;
},
[Types.FETCH_ORDERDETAIL_SUCCESS](state, {order}) {
state.fetchingOrder = false;
state.orderDetail = order;
[Types.POST_NOSALE_SUCCESS](state) {
state.fetchingNoSale = false;
},
};
... ...
... ... @@ -2,6 +2,10 @@ export const FETCH_ORDER_PRODUCT_REQUEST = 'FETCH_ORDER_PRODUCT_REQUEST';
export const FETCH_ORDER_PRODUCT_FAILD = 'FETCH_ORDER_PRODUCT_FAILD';
export const FETCH_ORDER_PRODUCT_SUCCESS = 'FETCH_ORDER_PRODUCT_SUCCESS';
export const FETCH_ORDERDETAIL_REQUEST = 'FETCH_ORDERDETAIL_REQUEST';
export const FETCH_ORDERDETAIL_FAILD = 'FETCH_ORDERDETAIL_FAILD';
export const FETCH_ORDERDETAIL_SUCCESS = 'FETCH_ORDERDETAIL_SUCCESS';
export const POST_NOSALE_REQUEST = 'POST_NOSALE_REQUEST';
export const POST_NOSALE_FAILD = 'POST_NOSALE_FAILD';
export const POST_NOSALE_SUCCESS = 'POST_NOSALE_SUCCESS';
export const POST_CHANGEPRICE_REQUEST = 'POST_CHANGEPRICE_REQUEST';
export const POST_CHANGEPRICE_FAILD = 'POST_CHANGEPRICE_FAILD';
export const POST_CHANGEPRICE_SUCCESS = 'POST_CHANGEPRICE_SUCCESS';
... ...
... ... @@ -24,5 +24,46 @@ module.exports = {
api: 'ufo.coupons.get',
ufo: true,
params: {}
},
'/api/ufo/seller/entryGoodsSizeList': {
ufo: true,
api: 'ufo.seller.entryGoodsSizeList',
params: {
productId: {type: Number},
limit: {type: Number},
page: {type: Number}
}
},
'/api/ufo/sellerOrder/computeAdjustPrice': {
ufo: true,
api: 'ufo.sellerOrder.computeAdjustPrice',
params: {
product_id: {type: Number},
storage_id: {type: Number},
new_price: {type: Number},
old_price: {type: Number},
num: {type: Number}
}
},
'/api/ufo/sellerOrder/batchAdjustPrice': {
ufo: true,
api: 'ufo.sellerOrder.batchAdjustPrice',
params: {
product_id: {type: Number},
storage_id: {type: Number},
new_price: {type: Number},
old_price: {type: Number},
num: {type: Number}
}
},
'/api/ufo/sellerOrder/batchDownShelf': {
ufo: true,
api: 'ufo.sellerOrder.batchDownShelf',
params: {
product_id: {type: Number},
storage_id: {type: Number},
old_price: {type: Number},
num: {type: Number}
}
}
};
... ...
... ... @@ -13,6 +13,7 @@ const domains = {
// api: 'http://api.yoho.cn/',
// service: 'http://service.yoho.cn/',
// ufo: 'http://java-yohoufo-fore.test3.ingress.dev.yohocorp.com/ufo-gateway/',
// liveApi: 'http://testapi.live.yohops.com:9999/',
// singleApi: 'http://api-test3.yohops.com:9999/',
... ... @@ -25,7 +26,7 @@ const domains = {
api: 'http://api-test3.dev.yohocorp.com/',
service: 'http://api-test3.dev.yohocorp.com/',
// ufo: 'http://java-yohoufo-fore.test3.ingress.dev.yohocorp.com/ufo-gateway/',
ufo: 'http://java-yohoufo-fore.test3.ingress.dev.yohocorp.com/ufo-gateway/',
};
module.exports = {
... ... @@ -36,7 +37,7 @@ module.exports = {
siteUrl: '//m.yohobuy.com',
assetUrl: '//localhost:5001/yohoapp-node/',
testCode: 'yoho4946abcdef#$%&!@',
domains: domains,
domains,
yohoVerifyUdid: 'ca5c462a-e28b-407d-8061-5e204398e3cc',
signExtend: {
business_line: 'yohobuy'
... ...
const service = global.yoho.ServiceAPI;
const serviceApi = global.yoho.ServiceAPI;
const ufoAPI = global.yoho.UfoAPI;
const logger = global.yoho.logger;
const checkParams = require('../../utils/check-params');
const apiMaps = require('../../config/api-map');
... ... @@ -36,7 +37,7 @@ module.exports = async(req, res, next) => {
if (apiInfo.service) {
result = await apiCtx.get({
api: service,
api: serviceApi,
url: apiInfo.api,
data: params,
param: {
... ... @@ -44,6 +45,15 @@ module.exports = async(req, res, next) => {
code: 200
}
});
} else if (apiInfo.ufo) {
result = await apiCtx[method]({
api: ufoAPI,
data: params,
param: {
code: 200,
cache: cache
}
});
} else {
result = await apiCtx[method]({
data: params,
... ...
... ... @@ -6,7 +6,7 @@ module.exports = {
rootValue: 40,
unitPrecision: 5, // 保留5位小数字
minPixelValue: 2, // 小于 2 时,不转换
selectorBlackList: ['.fix-ios-top'], // 选择器黑名单,可以使用正则
selectorBlackList: ['.fix-ios-top', /cube-/], // 选择器黑名单,可以使用正则
propWhiteList: [] // 属性名称为空,表示替换所有属性的值
}),
require('autoprefixer')({
... ...