Authored by 李奇

Merge branch 'release/1.0' of git.yoho.cn:fe/yoho-shop-manage into release/1.0

... ... @@ -6,4 +6,5 @@ node_modules/
*.log
.eslintcache
app/bundle
public/
\ No newline at end of file
public/
.vscode/
\ No newline at end of file
... ...
<template>
<Row class="layout-header">
<Col :span="12" class="brand-title">
<i class="fa fa-bars" aria-hidden="true" @click="$emit('menu-trigger')"></i>
<i class="iconfont icon-alignjustify" aria-hidden="true" @click="$emit('menu-trigger')"></i>
{{userInfo.name}}
</Col>
<Col :span="12" class="shop-info">
<span class="name">{{userInfo.currentShop.shopName}}</span>
<span v-if="!showLoading" class="name">{{userInfo.currentShop.shopName}}</span>
<span v-if="showLoading" class="loading">切换中...</span>
<span>|</span>
<Dropdown @on-click="switchShop" trigger="click">
<a class="swtich-shop" href="javascript:void(0)">
... ... @@ -22,12 +23,14 @@
<script>
import Vue from 'vue';
import userService from 'user-service';
export default {
name: 'UserInfo',
data() {
return {
userInfo: this.$user
userInfo: this.$user,
showLoading: false
};
},
methods: {
... ... @@ -36,9 +39,18 @@ export default {
},
switchShop(id) {
if (this.userInfo.currentShop.shopsId !== id) {
this.userInfo.currentShop = this.userInfo.shops.find(shop => shop.shopsId === id);
Vue.switchShop(id);
this.$emit('shop-change', this.userInfo.currentShop);
this.showLoading = true;
userService.switchShop(id).then(res => {
this.showLoading = false;
if (res.code === 200) {
this.userInfo.currentShop = this.userInfo.shops.find(shop => shop.shopsId === id);
Vue.switchShop(id);
this.$emit('shop-change', this.userInfo.currentShop);
this.$Message.success(`当前店铺切换为:${this.userInfo.currentShop.shopName}`);
} else {
this.$Message.error(`切换失败:${res.message}`);
}
});
}
}
}
... ... @@ -49,15 +61,15 @@ export default {
.layout-header {
height: 50px !important;
background: #fff;
box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
font-size: 14px;
line-height: 20px;
padding: 15px;
position: relative;
&.user-collapse {
height: 0px !important;
padding: 0px !important;
height: 0 !important;
padding: 0 !important;
overflow: hidden;
.fa-bars {
... ... @@ -67,7 +79,8 @@ export default {
}
&.print-hide {
display: none; }
display: none;
}
.fa {
font-size: 20px;
... ... @@ -75,7 +88,7 @@ export default {
margin-right: 10px;
cursor: pointer;
}
.shop-info {
text-align: right;
padding-right: 20px;
... ... @@ -84,6 +97,10 @@ export default {
margin-right: 5px;
}
.loading {
color: #ccc;
}
.swtich-shop {
margin-left: 5px;
font-size: 12px;
... ... @@ -91,7 +108,7 @@ export default {
}
.logout {
color: #F44545;
color: #f44545;
font-size: 12px;
margin-left: 5px;
}
... ...
<template>
<Select :value="handleValue" @on-change="updateValue" :disabled="disable" placeholder="请选择" style="width: 350px;">
<Select :value="handleValue" @on-change="updateValue" :disabled="disable" placeholder="请选择" style="width: 400px;">
<Option :value="season.id" v-for="season in seasonList" :key="season.id">{{season.label}}</Option>
</Select>
</template>
... ...
import TableGoodSize from './table-good-size';
import TableCreateGoodSize from './table-create-good-size';
export {
TableGoodSize
TableGoodSize,
TableCreateGoodSize
};
... ...
<template>
<Table ref="sellerGoods" :context="self" :data="table.data" :columns="table.columns" stripe border></Table>
</template>
<script>
import {ExamplePop} from 'components/pop';
export default {
name: 'TableCreateGoodSize',
props: {
value: {
type: Array,
default() {
return [];
}
}
},
data() {
return {
self: this,
table: {
columns: [
{
title: '色系名称',
key: 'goodsName',
render(row, col, index) {
return `<div v-if="isExist(${index})">
<p>{{table.data[${index}].goodsName.name}}</p>
<Radio :value="table.data[${index}].goodsName.isDefault"
@on-change="clickDefault(${index})">主推</Radio>
</div>`;
}
},
{
title: '颜色展示名称',
key: 'factoryGoodsName',
render(row, col, index) {
return `<div v-if="isExist(${index})">
<i-input
v-model="row.factoryGoodsName"
:placeholder="row.goodsName.name">
</i-input>
</div>`;
}
},
{
title: '色卡图片',
key: 'goodsColorImage',
width: 170,
render(row, col, index) {
return `<div
:class="{'table-upload-item': true ,
'table-upload-item-error':
row.goodsColorImage.showValidate && row.goodsColorImage.validate}"
>
<div class="table-upload-item-img">
<img v-if="row.goodsColorImage.value"
:src="row.goodsColorImage.value" alt="" width="84px" height="112px">
</div>
<div>
<file-upload :id="{index: ${index}}"
@on-success="uploadSuccess"
@on-error="uploadError"></file-upload>
<example-pop></example-pop>
</div>
<div v-if="row.goodsColorImage.showValidate && row.goodsColorImage.validate"
class="table-upload-item-tip">
必须上传图片
</div>
</div>`;
},
renderHeader(col, index) {
return `<span class="table-header">${col.title}</span>`;
}
},
{
title: '款型编码',
key: 'factoryCode',
render(row, col, index) {
return `<div v-if="isExist(${index})">
<i-input
v-model="row.factoryCode"
placeholder="请输入..." >
</i-input>
</div>`;
}
},
{
title: '尺码',
key: 'sizeId',
width: 80,
render(row, col, index) {
return `<div class="size-id">
<div v-for="size in row.sizeId" class="row-span">
{{size.name}}
</div>
</div>`;
}
},
{
title: '商品条码',
key: 'sizeCode',
render(row, col, index) {
return `<div class='size-code'>
<div v-for="size,i in row.sizeCode" class="row-span">
<div style="position: relative">
<div :class="{'size-code-error': size.validate && !size.name}">
<i-input
v-model="size.name"
:disabled="!row.operator[i].value"
placeholder="请输入..."
/>
</div>
<div class="size-code-tip" v-if="size.validate && !size.name">
不能为空
</div>
</div>
</div>
</div>`;
},
renderHeader(col, index) {
return `<span class="table-header">${col.title}</span>`;
}
},
{
title: '操作',
key: 'operator',
width: 100,
render(row, col, index) {
return `<template v-if="isExist(${index})">
<div class="size-operator">
<div v-for="op,i in row.operator" class="row-span">
<i-button v-if="row.operator[i].value"
type="warning"
@click="clickOperator(row, i)">禁用</i-button>
<i-button v-else type="primary"
@click="clickOperator(row, i)">启用</i-button>
</div>
</div>
</template>`;
}
}
],
data: this.value
}
};
},
methods: {
isExist(index) {
let row = this.table.data[index];
if (row) {
return true;
}
return false;
},
refreshTable() {
this.table.data = this.$refs.sellerGoods.rebuildData;
},
syncData() {
this.$emit('input', this.$refs.sellerGoods.rebuildData);
},
clickDefault(index) {
this.refreshTable();
let color = this.table.data[index];
color.goodsName.isDefault = true;
this.table.data.forEach((c) => {
if (c.colorId !== color.colorId) {
c.goodsName.isDefault = false;
}
});
},
clickOperator(row, itemIndex) {
this.refreshTable();
row.operator[itemIndex].value = row.operator[itemIndex].value ? false : true;
if (row.operator[itemIndex].value) {
row.sizeCode[itemIndex].name = '';
row.sizeCode[itemIndex].validate = true;
} else {
row.sizeCode[itemIndex].name = '';
row.sizeCode[itemIndex].validate = false;
}
},
uploadSuccess: function(attach, files) {
this.refreshTable();
this.table.data[attach.index].goodsColorImage.value = files[0];
this.table.data[attach.index].goodsColorImage.validate = true;
if (this.table.data[attach.index].goodsColorImage.value) {
this.table.data[attach.index].goodsColorImage.showValidate = false;
} else {
this.table.data[attach.index].goodsColorImage.showValidate = true;
}
},
uploadError: function(attach, err) {
},
},
watch: {
value(newValue) {
this.table.data = newValue;
}
},
components: {
ExamplePop
}
};
</script>
<style lang="scss">
@mixin row-span{
min-height: 30px;
.row-span {
min-height: 30px;
border-bottom: 1px solid #e3e8ee;
padding-top: 20px ;
padding-bottom: 20px ;
margin-left: -18px;
margin-right: -18px;
padding-left: 18px ;
padding-right: 18px ;
&:last-child {
border-bottom: none;
}
}
}
.size-code {
@include row-span;
&-error {
border: 1px solid #f30;
}
&-tip {
position: absolute;
line-height: 1;
padding-top: 6px;
color: #f30;
}
}
.size-id {
@include row-span;
text-align: center;
}
.size-operator {
@include row-span;
text-align: center;
}
.table-upload-item {
display: inline-block;
height: 220px;
width: 130px;
text-align: center;
margin: 30px 0;
}
.table-upload-item-error {
border: 1px solid #f30;
}
.table-upload-item-tip {
padding-top: 6px;
color: #f30;
top: 100%;
}
.table-upload-item-img {
display: inline-block;
height: 116px;
width: 88px;
border: 2px solid #e8e8e8;
box-sizing: border-box;
}
.table-header {
&:after {
content: '*';
display: inline-block;
margin-left: 4px;
line-height: 1;
font-family: SimSun;
font-size: 12px;
color: #f30;
}
}
</style>
\ No newline at end of file
... ...
... ... @@ -50,16 +50,16 @@ export default {
{
title: '色卡图片',
key: 'goodsColorImage',
width: 250,
width: 180,
render(row, col, index) {
return `<div
:class="{'upload-item': true ,
'upload-item-error':
:class="{'table-upload-item': true ,
'table-upload-item-error':
row.goodsColorImage.showValidate && row.goodsColorImage.validate}">
<div class="upload-item-img">
<div class="table-upload-item-img">
<img v-if="row.goodsColorImage.value"
:src="row.goodsColorImage.value"
alt="" width="120px" height="122px">
alt="" width="84px" height="112px">
</div>
<div>
... ... @@ -69,7 +69,7 @@ export default {
<example-pop></example-pop>
</div>
<div v-if="row.goodsColorImage.showValidate && row.goodsColorImage.validate"
class="upload-item-tip">
class="table-upload-item-tip">
必须上传图片
</div>
</div>`;
... ... @@ -297,11 +297,11 @@ export default {
}
}
.upload-item-error {
.table-upload-item-error {
border: 1px solid #f30;
}
.upload-item-tip {
.table-upload-item-tip {
padding-top: 6px;
color: #f30;
top: 100%;
... ...
... ... @@ -50,7 +50,7 @@ export default {
let mid = value[1];
let min = value[2];
if (!max.value) {
if (!max || !max.value) {
return callback(new Error('一级类目不能为空'));
} else if (!mid.value) {
... ... @@ -84,6 +84,8 @@ export default {
this.$refs.formData.validate((valid) => {
if (valid) {
this.goNext();
} else {
this.$Message.error('请填写必填项!');
}
});
},
... ...
<template>
<div>
<Form ref="product" :model="product" :label-width="70" :rules="ruleValidate">
<Row>
<div class="create-group">
<span class="create-group-indicator"></span>
... ... @@ -10,78 +9,36 @@
</div>
</Row>
<Row>
<Col>
<div class="create-item-title">商品基本信息</div>
</Col>
</Row>
<Row> <div class="create-item-title">商品基本信息</div> </Row>
<Row>
<div class="ivu-form-item ivu-form-item-required">
<label class="ivu-form-item-label" style="width: 100px;">品牌</label>
<div style="float: left; line-height: 1; padding: 10px 12px 10px 0; box-sizing: border-box;">
<span>{{product.brandName}}</span>
</div>
</div>
</Row>
<Form-item label="品牌"> <span>{{product.brandName}}</span> </Form-item>
<Row>
<div class="ivu-form-item ivu-form-item-required">
<label class="ivu-form-item-label" style="width: 100px;">类目</label>
<span style="float: left; line-height: 1; padding: 10px 12px 10px 0; box-sizing: border-box;">
<span>{{sortName}}</span>
</span>
</div>
</Row>
<Form-item label="类目"> <span>{{sortName}}</span> </Form-item>
<Row>
<Col span="8">
<Form-item label="商品名称" prop="productName">
<Input v-model="product.productName" placeholder="请输入..."/>
</Form-item>
</Col>
</Row>
<Form-item label="商品名称" prop="productName">
<Input v-model="product.productName" placeholder="请输入..." style="width: 400px;"/>
</Form-item>
<Row>
<Col span="8">
<Form-item label="商品卖点">
<Input v-model="product.phrase" type="textarea" :rows="4" placeholder="请输入..."/>
</Form-item>
</Col>
</Row>
<Form-item label="商品卖点">
<Input v-model="product.phrase" type="textarea" :rows="4" placeholder="请输入..." style="width: 400px;"/>
</Form-item>
<Row>
<Col span="8">
<Form-item label="商家商品编码" prop="factoryCode">
<Input v-model="product.factoryCode" placeholder="请输入..."/>
</Form-item>
</Col>
</Row>
<Form-item label="商家商品编码" prop="factoryCode">
<Input v-model="product.factoryCode" placeholder="请输入..." style="width: 400px;"/>
</Form-item>
<Row>
<Col span="8">
<Form-item label="货品年" prop="goodsYears">
<Date-picker :value="product.goodsYears" @on-change="clickGoodsYear" type="year" placeholder="选择年" style="width: 200px">
</Date-picker>
</Form-item>
</Col>
</Row>
<Form-item label="货品年" prop="goodsYears">
<Date-picker :value="product.goodsYears" @on-change="clickGoodsYear" type="year" placeholder="选择年" style="width: 400px">
</Date-picker>
</Form-item>
<Row>
<Col span="8">
<Form-item label="货品季" prop="goodsSeason">
<SelectSeason v-model="product.goodsSeason"></SelectSeason>
</Form-item>
</Col>
</Row>
<Form-item label="货品季" prop="goodsSeason">
<SelectSeason v-model="product.goodsSeason"></SelectSeason>
</Form-item>
<Row>
<Col span="8">
<Form-item label="上市日期" prop="expectSaleTimeStr">
<Date-picker :value="product.expectSaleTimeStr" @on-change="clickSaleDate" type="date" placeholder="选择日期" style="width: 200px"></Date-picker>
</Form-item>
</Col>
</Row>
<Form-item label="上市日期" prop="expectSaleTimeStr">
<Date-picker :value="product.expectSaleTimeStr" @on-change="clickSaleDate" type="date" placeholder="选择日期" style="width: 400px"></Date-picker>
</Form-item>
<Form-item label="性别" prop="gender">
<RadioGender v-model="product.gender"></RadioGender>
... ... @@ -91,20 +48,16 @@
<RadioSeason v-model="product.seasons"></RadioSeason>
</Form-item>
<Form-item label="年龄层" prop="ageLevel">
<CheckboxAge v-model="product.ageLevel"></CheckboxAge>
</Form-item>
<Row>
<Col>
<div class="create-item-title">商品规格
<span class="create-group-sub-title">(颜色名称只能填写中文,最多5个汉字。款型编码和条码只能填写英文和数字,不区分大小写)</span>
</div>
</Col>
<div class="create-item-title">商品规格
<span class="create-group-sub-title">(颜色名称只能填写中文,最多5个汉字。款型编码和条码只能填写英文和数字,不区分大小写)</span>
</div>
</Row>
<Form-item label="颜色" prop="selectColor">
<CheckboxColor v-model="product.selectColor" @on-add="addColor" @on-remove="removeColor"></CheckboxColor>
</Form-item>
... ... @@ -113,45 +66,25 @@
<CheckboxSize v-model="product.selectSize" :sort-id="product.smallSortId" @on-add="addSize" @on-remove="removeSize"></CheckboxSize>
</Form-item>
<Row>
<Form-item >
<Table ref="sellerGoods" :context="self" :data="table.data" :columns="table.columns" stripe border></Table>
</Form-item>
</Row>
<Row>
<Col>
<div class="create-item-title">商品价格</div>
</Col>
</Row>
<Row>
<Col span="8">
<Form-item label="吊牌价" prop="retailPrice">
<Input v-model="product.retailPrice" :number="true" placeholder="请输入..."/>
</Form-item>
</Col>
</Row>
<Form-item >
<TableCreateGoodSize ref="sellerGoods" v-model="table.data"></TableCreateGoodSize>
</Form-item>
<Row> <div class="create-item-title">商品价格</div> </Row>
<Row>
<Col span="8">
<Form-item label="销售价" prop="salesPrice">
<Input v-model="product.salesPrice" :number="true" placeholder="请输入..."/>
</Form-item>
</Col>
</Row>
<Form-item label="吊牌价" prop="retailPrice">
<Input v-model="product.retailPrice" :number="true" placeholder="请输入..." style="width: 400px;"/>
</Form-item>
<Form-item label="销售价" prop="salesPrice">
<Input v-model="product.salesPrice" :number="true" placeholder="请输入..." style="width: 400px;"/>
</Form-item>
</Form>
<Row>
<Col>
<div style="text-align: center">
<Button type="primary" @click="nextStep(1)" size="large">下一步</Button>
<Button type="primary" @click="nextStep(0)" size="large">保存并退出</Button>
</div>
</Col>
</Row>
<div style="text-align: center">
<Button type="primary" @click="nextStep(1)" size="large">下一步</Button>
<Button type="primary" @click="nextStep(0)" size="large">保存并退出</Button>
</div>
</div>
</template>
... ... @@ -163,6 +96,7 @@ import {CheckboxAge, CheckboxColor, CheckboxSize} from 'components/checkbox';
import {RadioSeason, RadioGender} from 'components/radio';
import {SelectSeason} from 'components/select';
import {ExamplePop} from 'components/pop';
import {TableCreateGoodSize} from 'components/table';
const makeColor = () => {
return {
... ... @@ -238,128 +172,6 @@ export default {
self: this,
colors: [],
table: {
columns: [
{
title: '色系名称',
key: 'goodsName',
render(row, col, index) {
return `<div v-if="isExist(${index})">
<p>{{table.data[${index}].goodsName.name}}</p>
<Radio :value="table.data[${index}].goodsName.isDefault"
@on-change="clickDefault(${index})">主推</Radio>
</div>`;
}
},
{
title: '颜色展示名称',
key: 'factoryGoodsName',
render(row, col, index) {
return `<div v-if="isExist(${index})">
<i-input
v-model="row.factoryGoodsName"
:placeholder="row.goodsName.name">
</i-input>
</div>`;
}
},
{
title: '色卡图片',
key: 'goodsColorImage',
width: 170,
render(row, col, index) {
return `<div
:class="{'upload-item': true ,
'upload-item-error':
row.goodsColorImage.showValidate && row.goodsColorImage.validate}"
>
<div class="upload-item-img">
<img v-if="row.goodsColorImage.value"
:src="row.goodsColorImage.value" alt="" width="120px" height="122px">
</div>
<div>
<file-upload :id="{index: ${index}}"
@on-success="uploadSuccess"
@on-error="uploadError"></file-upload>
<example-pop></example-pop>
</div>
<div v-if="row.goodsColorImage.showValidate && row.goodsColorImage.validate"
class="upload-item-tip">
必须上传图片
</div>
</div>`;
},
renderHeader(col, index) {
return `<span class="table-header">${col.title}</span>`;
}
},
{
title: '款型编码',
key: 'factoryCode',
render(row, col, index) {
return `<div v-if="isExist(${index})">
<i-input
v-model="row.factoryCode"
placeholder="请输入..." >
</i-input>
</div>`;
}
},
{
title: '尺码',
key: 'sizeId',
width: 80,
render(row, col, index) {
return `<div class="size-id">
<div v-for="size in row.sizeId" class="row-span">
{{size.name}}
</div>
</div>`;
}
},
{
title: '商品条码',
key: 'sizeCode',
render(row, col, index) {
return `<div class='size-code'>
<div v-for="size,i in row.sizeCode" class="row-span">
<div style="position: relative">
<div :class="{'size-code-error': size.validate && !size.name}">
<i-input
v-model="size.name"
:disabled="!row.operator[i].value"
placeholder="请输入..."
/>
</div>
<div class="size-code-tip" v-if="size.validate && !size.name">
不能为空
</div>
</div>
</div>
</div>`;
},
renderHeader(col, index) {
return `<span class="table-header">${col.title}</span>`;
}
},
{
title: '操作',
key: 'operator',
width: 100,
render(row, col, index) {
return `<template v-if="isExist(${index})">
<div class="size-operator">
<div v-for="op,i in row.operator" class="row-span">
<i-button v-if="row.operator[i].value"
type="warning"
@click="clickOperator(row, i)">禁用</i-button>
<i-button v-else type="primary"
@click="clickOperator(row, i)">启用</i-button>
</div>
</div>
</template>`;
}
}
],
data: [],
selectedSizes: [],
selectedColors: [],
... ... @@ -449,7 +261,7 @@ export default {
this.beforeSubmit();
return api.saveBaseProductInfo(this.product);
}, () => {
this.$Message.error('表单验证失败!');
this.$Message.error('请填写必填项!');
});
},
nextStep: function(go) {
... ... @@ -489,31 +301,6 @@ export default {
saveAndQuit: function() {
this.$router.push({name: 'product.offsale'});
},
clickDefault: function(index) {
this.refreshTable();
let color = this.table.data[index];
color.goodsName.isDefault = true;
this.table.data.forEach((c) => {
if (c.colorId !== color.colorId) {
c.goodsName.isDefault = false;
}
});
},
clickOperator: function(row, itemIndex) {
this.refreshTable();
row.operator[itemIndex].value = row.operator[itemIndex].value ? false : true;
if (row.operator[itemIndex].value) {
row.sizeCode[itemIndex].name = '';
row.sizeCode[itemIndex].validate = true;
} else {
row.sizeCode[itemIndex].name = '';
row.sizeCode[itemIndex].validate = false;
}
},
clickGoodsYear: function(value) {
this.product.goodsYears = value;
},
... ... @@ -535,22 +322,6 @@ export default {
_.first(this.table.data).goodsName.isDefault = true;
},
changeFactoryGoodsName: function(row, index) {
this.table.data[index].factoryGoodsName = row.factoryGoodsName;
},
changeFactoryCode: function(row, index) {
this.table.data[index].factoryCode = row.factoryCode;
},
changeSizeCode: function(row, rowIndex, sizeIndex) {
this.table.data[rowIndex].sizeCode[sizeIndex].name = row.sizeCode[sizeIndex].name;
this.table.data[rowIndex].sizeCode[sizeIndex].validate = true;
if (this.table.data[rowIndex].sizeCode[sizeIndex].name) {
this.table.data[rowIndex].sizeCode[sizeIndex].showValidate = false;
} else {
this.table.data[rowIndex].sizeCode[sizeIndex].showValidate = true;
}
},
addColor: function(color) {
this.refreshTable();
this.addColorData(color);
... ... @@ -646,33 +417,11 @@ export default {
});
color.operator.push({
value: true
value: false
});
},
isExist: function(index) {
let row = this.table.data[index];
if (row) {
return true;
}
return false;
},
uploadSuccess: function(attach, files) {
this.refreshTable();
this.table.data[attach.index].goodsColorImage.value = files[0];
this.table.data[attach.index].goodsColorImage.validate = true;
if (this.table.data[attach.index].goodsColorImage.value) {
this.table.data[attach.index].goodsColorImage.showValidate = false;
} else {
this.table.data[attach.index].goodsColorImage.showValidate = true;
}
},
refreshTable() {
this.table.data = this.$refs.sellerGoods.rebuildData;
},
uploadError: function(attach, err) {
this.$refs.sellerGoods.syncData();
},
validateOtherProps: function() {
return new Promise((resolve, reject) => {
... ... @@ -782,124 +531,12 @@ export default {
SelectSeason,
CheckboxColor,
CheckboxSize,
ExamplePop
ExamplePop,
TableCreateGoodSize
}
};
</script>
<style lang="scss">
@mixin row-span{
min-height: 30px;
.row-span {
min-height: 30px;
border-bottom: 1px solid #e3e8ee;
padding-top: 20px ;
padding-bottom: 20px ;
margin-left: -18px;
margin-right: -18px;
padding-left: 18px ;
padding-right: 18px ;
&:last-child {
border-bottom: none;
}
}
}
.squre {
display: inline-block;
height: 30px;
margin-right: 10px;
cursor: pointer;
border: 1px solid white;
}
.squre-selected {
border: 1px solid black;
}
.squre-disabled {
color: gray;
opacity: 0.5;
cursor: not-allowed;
border: 1px solid gray;
}
.squre-color {
display: inline-block;
height: 28px;
width: 28px;
}
.squre-name {
display: inline-block;
height: 30px;
line-height: 30px;
vertical-align: top;
}
.size-code {
@include row-span;
&-error {
border: 1px solid #f30;
}
&-tip {
position: absolute;
line-height: 1;
padding-top: 6px;
color: #f30;
}
}
.size-id {
@include row-span;
text-align: center;
}
.size-operator {
@include row-span;
text-align: center;
}
.upload-item {
display: inline-block;
height: 220px;
width: 130px;
text-align: center;
margin: 30px 0;
}
.upload-item-error {
border: 1px solid #f30;
}
.upload-item-tip {
padding-top: 6px;
color: #f30;
top: 100%;
}
.upload-item-img {
display: inline-block;
height: 126px;
width: 124px;
border: 2px solid #e8e8e8;
box-sizing: border-box;
}
.table-header {
&:after {
content: '*';
display: inline-block;
margin-left: 4px;
line-height: 1;
font-family: SimSun;
font-size: 12px;
color: #f30;
}
}
</style>
... ...
... ... @@ -129,6 +129,14 @@ import api from 'product-create/api';
import serial from 'product-create/serialize';
export default {
props: {
from: {
type: String,
default() {
return 'product.offsale';
}
}
},
data() {
return {
showLoading: true,
... ... @@ -159,6 +167,8 @@ export default {
};
},
created() {
this.from = this.$route.query.from || this.from;
service.getProduct(this.$route.params.id).then((result) => {
_.update(result, 'goodsSeason', (s) => `${s}`);
if (!_.has(result, 'materialList')) {
... ... @@ -493,7 +503,7 @@ export default {
desc: '该商品保存成功!'
});
this.$router.push({ name: 'product.offsale' });
this.go(this.from);
} else {
this.$Notice.error({
title: '保存错误',
... ... @@ -503,13 +513,16 @@ export default {
}
});
},
go(from) {
this.$router.push({ name: from });
},
submit() {
this.validate()
.then(([r1, r2]) => {
if (r1 & r2) {
return Promise.resolve();
} else {
this.$Message.error('验证未通过');
this.$Message.error('请填写必填项');
return Promise.reject();
}
})
... ...
... ... @@ -295,7 +295,10 @@
this.$router.push({
name: 'product.edit',
params: {
id: skn
id: skn,
},
query: {
from: 'product.offsale'
}
});
},
... ...
... ... @@ -273,6 +273,9 @@
name: 'product.edit',
params: {
id: skn
},
query: {
from: 'product.onsale'
}
});
},
... ...
... ... @@ -103,7 +103,7 @@
return service.getCategoryList(this.pageData.current, this.pageData.pageSize).then((result) => {
if (result.code === 200) {
this.pageData.total = result.data.total;
this.pageData.current = result.data.currentPage;
this.pageData.current = result.data.currentPage || 1;
this.tableData = result.data.rows;
}
});
... ...
... ... @@ -19,9 +19,7 @@ const plugin = {
checkPurview() {
return Promise.resolve();
// let pUrl = `/${_.split(to.name, '.').join('/')}`;
// let pur = _.find(purviews, p => p.menu_url === pUrl);
// let pur = _.find(Vue.$purviews, p => p.menu_url === to.path);
// if (pur) {
// return Promise.resolve();
... ... @@ -42,6 +40,8 @@ const plugin = {
user = Rsa.decrypt(user, Object);
return this.initPurview(Vue, user).then(() => {
next();
}, () => {
next();
});
}
next();
... ... @@ -65,7 +65,7 @@ const plugin = {
return next('/login.html');
}
return this.checkPurview(Vue.$purviews, to).then(() => {
return this.checkPurview(Vue, to).then(() => {
return next();
}, () => {
return next('/401.html');
... ...
... ... @@ -31,6 +31,13 @@ const userService = {
}
});
return purs;
},
switchShop(shopId) {
return axios.post('/switchShop', {
shopId
}).then(res => {
return res.data;
});
}
};
... ...
{
"name": "yoho-shop-manage",
"version": "1.0.5",
"version": "1.0.7",
"description": "",
"main": "app.js",
"scripts": {
... ...
... ... @@ -55,7 +55,8 @@ let domainApis = {
getRemoteImageUrlBySku: '/product/getRemoteImageUrlBySku'
},
shop: {
login: '/loginInter'
login: '/loginInter',
switchShop: '/index/ajaxshop'
}
};
... ...
... ... @@ -83,7 +83,8 @@ class Api extends Context {
logger.error(`api call ${err.statusCode} [${err.options.method}]
${err.options.url} ${err.response.body || ''}`);
} else {
logger.error(`api call ${err}`);
logger.error(`api call [${options.method || 'get'}] ${options.url}
${JSON.stringify(options.qs || options.body)} ${err}`);
}
return Promise.reject(API_INTERNAL_ERROR);
});
... ...
... ... @@ -10,6 +10,8 @@ const Express = require('express');
const UserController = require('./user-controller');
const FileController = require('./file-controller');
const middleware = require('../framework/middleware');
const before = require('../middleware/before');
const auth = require('../middleware/auth');
const multipart = require('connect-multiparty');
const multipartMiddleware = multipart();
... ... @@ -17,7 +19,8 @@ let router = Express.Router(); // eslint-disable-line
router.post('/login', middleware(UserController, 'login'));
router.post('/logout', middleware(UserController, 'logout'));
router.post('/upload/image', multipartMiddleware, middleware(FileController, 'uploadImage'));
router.post('/upload/xlsx', multipartMiddleware, middleware(FileController, 'uploadXlsx'));
router.post('/switchShop', before, auth, middleware(UserController, 'switchShop'));
router.post('/upload/image', before, auth, multipartMiddleware, middleware(FileController, 'uploadImage'));
router.post('/upload/xlsx', before, auth, multipartMiddleware, middleware(FileController, 'uploadXlsx'));
module.exports = router;
... ...
... ... @@ -26,9 +26,8 @@ class UserController extends Context {
let currentShop = _.first(result.data);
this.syncSession({req, res}, Object.assign(user, {
shops: result.data,
currentShop: currentShop
}), sess);
shops: result.data
}), sess, currentShop);
return res.json({
code: 200,
... ... @@ -68,26 +67,62 @@ class UserController extends Context {
data: '登出成功'
});
}
switchShop(req, res) {
let shopId = req.body.shopId;
syncSession(context, user, sess) {
if (!shopId) {
return res.json({
code: 400,
message: '参数错误'
});
}
let shop = _.find(req.session.USER.shops, s => s.shopsId === shopId);
if (!shop) {
return res.json({
code: 400,
message: '不存在的店铺'
});
}
this.userService.switchShop({
shopId,
cookies: {
PHPSESSID: encodeURIComponent(req.cookies.PHPSESSID),
'connect.sid': encodeURIComponent(req.cookies['connect.sid'])
}
}).then(response => {
this.syncShopSession({
req,
res
}, response);
return res.json({
code: 200
});
});
}
syncSession(context, user, sess, currentShop) {
context.req.session.USER = user;
context.req.session.LOGIN_UID = user.pid; // pid 为用户名
this.syncShopSession(context, sess);
context.res.cookie('_isLogin', true, {
path: '/'
});
context.res.cookie('_sign', currentShop.shopsId, {
path: '/'
});
}
syncShopSession(context, sess) {
_.each(sess, (v, k) => {
context.res.cookie(k, v, {
path: '/',
domain: '.yohobuy.com',
httpOnly: true,
overwrite: false,
encode: val => val
});
});
context.res.cookie('_isLogin', true, {
path: '/'
});
context.res.cookie('_sign', user.currentShop.shopsId, {
path: '/'
});
}
}
... ...
... ... @@ -39,24 +39,28 @@ class UserService extends Context {
password: password
}
}).then(response => {
let sessId = '', sid = '';
return this._getShopsSession(response.rawHeaders);
});
}
_getShopsSession(rawHeaders) {
let sessId = '', sid = '';
_.each(response.rawHeaders, header => {
let sessIdMatched = header.match(new RegExp(regSession.replace('${0}', 'PHPSESSID')));
let sidMatched = header.match(new RegExp(regSession.replace('${0}', 'connect\.sid')));
_.each(rawHeaders, header => {
let sessIdMatched = header.match(new RegExp(regSession.replace('${0}', 'PHPSESSID')));
let sidMatched = header.match(new RegExp(regSession.replace('${0}', 'connect\.sid')));
if (sessIdMatched) {
sessId = sessIdMatched[1];
}
if (sidMatched) {
sid = sidMatched[1];
}
});
return {
PHPSESSID: sessId,
'connect.sid': sid
};
if (sessIdMatched) {
sessId = sessIdMatched[1];
}
if (sidMatched) {
sid = sidMatched[1];
}
});
return {
PHPSESSID: sessId,
'connect.sid': sid
};
}
getShops(pid) {
... ... @@ -68,6 +72,22 @@ class UserService extends Context {
});
}
switchShop(opts) {
let options = {
url: `${config.apiDomain.shop.switchShop}?shops_id=${opts.shopId}`,
resolveWithFullResponse: true,
};
let jar = rp.jar();
_.each(opts.cookies, (val, key) => {
jar.setCookie(rp.cookie(`${key}=${val}`), options.url);
});
options.jar = jar;
return rp.get(options).then(response => {
return this._getShopsSession(response.rawHeaders);
});
}
profile(pid) {
return this.instance(Api).get(config.apiDomain.shop.profile.url, {userId: pid});
}
... ...