Authored by 陈峰

Merge branch 'feature/feedback' into 'release/3.1'

Feature/feedback

feedback


See merge request !43
<template>
<div class="cell-info">
<p v-for="item in items">
{{item.label}}:{{item.value}}
<span v-if="item.type === 'string' || !item.type">{{item.label}}:{{item.value}}</span>
<a v-if="item.type == 'link'" :href="item.value" target = '_blank' >{{item.label}}</a>
</p>
</div>
</template>
... ... @@ -15,11 +16,18 @@
}
}
};
</script>
<style lang="scss" scoped>
.cell-info {
text-align: left;
padding: 10px;
a {
display: block;
text-decoration: underline;
color: #999;
}
}
</style>
... ...
... ... @@ -38,5 +38,9 @@ export default {
.ivu-cascader {
max-width: 300px;
}
.ivu-date-picker {
width: 100%;
}
}
</style>
... ...
<template>
<Modal
class-name="vertical-center-modal"
width="500"
v-model="model">
<p slot="header">
咨询回复
</p>
<div style="text-align: center">
<filter-item>
<Input
type = "textarea"
:rows = "10"
placeholder="请输入文字内容"
v-model="answer"
@on-enter="submit"/>
</filter-item>
</div>
<div slot="footer" style="text-align: center">
<Button type="primary" size="large" :loading="modal_loading" @click="submit">回复</Button>
<Button type="default" size="large" @click="cancel">取消</Button>
</div>
</Modal>
</template>
<script>
import FeedbackService from 'services/product/feedback-service';
export default {
name: 'modal-answer-edit',
created() {
this.feedbackService = new FeedbackService();
},
data() {
return {
model: false,
modal_loading: false,
userId: '',
id: '',
answer: ''
};
},
methods: {
show(row) {
this.reset();
this.userId = row.userId;
this.id = row.id;
this.answer = row.answer;
this.model = true;
},
close() {
this.reset();
this.model = false;
},
reset() {
this.userId = null;
this.id = null;
this.answer = null;
},
submit() {
this.modal_loading = true;
this.submitData().then(() => {
this.modal_loading = false;
this.$emit('on-success');
this.close();
});
},
cancel() {
this.close();
},
submitData() {
return this.feedbackService.consultReply(this.userId, this.id, this.answer);
}
}
};
</script>
<style lang="scss">
</style>
... ...
<template>
<layout-body>
<layout-filter>
<filter-item :label="filters.productSKN.label">
<Input v-model.trim="filters.productSKN.model"
:placeholder="filters.productSKN.holder" :maxlength="9"></Input>
</filter-item>
<filter-item :label="filters.prodName.label">
<Input v-model.trim="filters.prodName.model"
:placeholder="filters.prodName.holder"></Input>
</filter-item>
<filter-item label="品牌">
<select-brand v-model="filters.brandId.model"></select-brand>
</filter-item>
<filter-item label="品类">
<select-category :value="categoryValue" @select-change="sortChange"></select-category>
</filter-item>
<filter-item :label="filters.isReply.label">
<Select v-model.trim="filters.isReply.model" clearable>
<Option v-for="option in filters.isReply.options"
:value="option.value"
:key="option.value">{{option.label}}</Option>
</Select>
</filter-item>
<filter-item :label="filters.answerUserName.label">
<Input v-model.trim="filters.answerUserName.model"
:placeholder="filters.answerUserName.holder"></Input>
</filter-item>
<filter-item :label="filters.askTimeStr.label">
<Date-picker type="datetimerange"
placeholder="选择日期和时间"
@on-change="askTimeChange"
v-model="filters.askTimeStr.model">
</Date-picker>
</filter-item>
<filter-item :label="filters.answerTimeStr.label">
<Date-picker v-model="filters.answerTimeStr.model"
type="datetimerange"
placeholder="选择日期和时间"
@on-change="answerTimeChange">
</Date-picker>
</filter-item>
<filter-item>
<Button type="primary" @click="list">筛选</Button>
<Button @click="clearFilters">清空条件</Button>
</filter-item>
</layout-filter>
<layout-list>
<Table border :columns="tableCols" :data="tableData" @on-selection-change="selectChange"></Table>
<Page :total="pageData.total"
:current="pageData.current"
@on-change="pageChange" show-total></Page>
</layout-list>
<modal-answer-edit ref="showAnswerEdit" @on-success="answerEdit"></modal-answer-edit>
</layout-body>
</template>
<script>
import _ from 'lodash';
import moment from 'moment';
import feedbackStore from './store';
import ModalAnswerEdit from './components/modal-answer-edit.vue';
import FeedbackService from 'services/product/feedback-service';
export default {
data() {
return feedbackStore.call(this);
},
created() {
this.feedbackService = new FeedbackService();
},
mounted() {
this.list();
},
methods: {
editAnswer(row) {
this.$refs.showAnswerEdit.show(row);
},
answerEdit() {
this.list();
},
clearFilters() {
this.filters.productSKN.model = null;
this.filters.prodName.model = null;
this.filters.answerUserName.model = null;
this.filters.isReply.model = null;
this.filters.brandId.model = null;
this.filters.askStartTime.model = '';
this.filters.askEndTime.model = '';
this.filters.answerStartTime.model = '';
this.filters.answerEndTime.model = '';
this.filters.askTimeStr.model = '';
this.filters.answerTimeStr.model = '';
this.pageData.current = 1;
this.list();
},
pageChange(page) {
this.pageData.current = page;
this.list();
},
filtersParams() {
let params = {};
let fts = this.filters;
let productSKN = fts.productSKN.model,
productName = fts.prodName.model,
answerUserName = fts.answerUserName.model,
isReply = fts.isReply.model,
maxSortId = fts.sort.first.model,
middleSortId = fts.sort.second.model,
smallSortId = fts.sort.third.model,
brandId = fts.brandId.model === '' || fts.brandId.model === null ?
null : fts.brandId.model,
askStartTime = fts.askStartTime.model,
askEndTime = fts.askEndTime.model,
answerStartTime = fts.answerStartTime.model,
answerEndTime = fts.answerEndTime.model;
let page = this.pageData.current;
let pageSize = this.pageData.pageSize;
if (this.filters.productSKN.model) {
if (this.isNumber(this.filters.productSKN.model)) {
params.productSKN = this.filters.productSKN.model;
} else {
return Promise.reject('skn必须是数字');
}
}
params.pageSize = this.pageData.pageSize;
params.page = this.pageData.current;
return Promise.resolve({
params,
productSKN,
productName,
answerUserName,
isReply,
maxSortId,
middleSortId,
smallSortId,
brandId,
askStartTime,
askEndTime,
answerStartTime,
answerEndTime,
page,
pageSize
});
},
list() {
this.$Loading.start();
return this.filtersParams().then((params) => {
function getLocalTime(ns) {
let date = new Date(ns * 1000);
let Y = date.getFullYear() + '-';
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
let D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' ';
let h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
let m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
let s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
return Y + M + D + h + m + s;
}
if (params) {
params.askStartTime = params.askStartTime ? getLocalTime(params.askStartTime) : null;
params.askEndTime = params.askEndTime ? getLocalTime(params.askEndTime) : null;
params.answerStartTime = params.answerStartTime ? getLocalTime(params.answerStartTime) : null;
params.answerEndTime = params.answerEndTime ? getLocalTime(params.answerEndTime) : null;
}
return this.feedbackService.list(params);
}).then((result) => {
if (result.code === 200) {
this.pageData.total = result.data.total;
this.pageData.current = result.data.page;
this.tableData = result.data.list;
}
this.$Loading.finish();
}).catch((err) => {
this.$Loading.finish();
this.$Message.error(err);
});
},
isNumber(numStr) {
const isNumber = /^[0-9]+$/;
return isNumber.test(numStr);
},
askTimeChange(time) {
// 兼容: https://github.com/iview/iview/issues/973
if (!_.isArray(time)) {
time = time.split(' - ');
}
if ((time[0] + '').length) {
this.filters.askStartTime.model = +moment(time[0]).format('X');
this.filters.askEndTime.model = +moment(time[1]).format('X');
} else {
this.filters.askStartTime.model = '';
this.filters.askEndTime.model = '';
}
},
answerTimeChange(time) {
// 兼容: https://github.com/iview/iview/issues/973
if (!_.isArray(time)) {
time = time.split(' - ');
}
if ((time[0] + '').length) {
this.filters.answerStartTime.model = +moment(time[0]).format('X');
this.filters.answerEndTime.model = +moment(time[1]).format('X');
}
},
sortChange(sort) {
this.filters.sort.first.model = sort.max;
this.filters.sort.second.model = sort.mid;
this.filters.sort.third.model = sort.min;
}
},
components: {
ModalAnswerEdit
}
};
</script>
... ...
export default {
path: '/feedback.html',
name: 'feedback',
component: () => import(/* webpackChunkName: "product.feedback" */'./feedback'),
meta: {
pageName: '商品咨询'
}
};
... ...
/**
* on feedback page store
* @author: Gexuhui
* @date: 2017/08/01
*/
import CellInfo from 'components/cell/cell-info';
export default function() {
return {
tableCols: [
{
title: 'SKN',
key: 'productSKN',
align: 'center',
},
{
title: '商品信息',
align: 'center',
render: (h, params) => {
const row = params.row;
const infoItems = [
{
type: 'link',
label: row.productName,
value: `//item.yohobuy.com/${row.productSKN}.html`
},
{
label: '品牌',
value: row.brandName
}, {
label: '品类',
value: `${row.maxSortName}/${row.middleSortName}/${row.smallSortName}`
}
];
return h(CellInfo, {
props: {
items: infoItems
}
});
}
},
{
title: '商品图片',
key: 'image',
width: 120,
align: 'center',
render: (h, params) => {
return (
<img v-prod-img={params.row.productSKN}/>
);
}
},
{
title: '咨询内容',
key: 'ask',
align: 'center'
},
{
title: '回复内容',
key: 'answer',
align: 'center'
},
{
title: '回复人',
key: 'answerUserName',
align: 'center',
},
{
title: '咨询及回复时间',
key: 'image',
align: 'center',
render: (h, params) => {
const row = params.row;
const infoItems = [
{
label: '咨询',
value: row.askTimeStr
}, {
label: '回复',
value: row.answerTimeStr
}
];
return h(CellInfo, {
props: {
items: infoItems
}
});
}
},
{
title: '操作',
key: 'action',
align: 'center',
render: (h, params) => {
const row = params.row;
let status = row.operateFlag;
if (status === 2) {
return (
<div class="cell-action-row">
<i-button type="error" size="small"
onClick={() => this.editAnswer(row)}>
修改
</i-button>
</div>
);
} else if (status === 0) {
return (
<div class="cell-action-row">
<i-button type="primary" size="small"
onClick={() => this.editAnswer(row)}>
回复
</i-button>
</div>
);
} else if (status === 1) {
return (
<div class="cell-action-row">
<i-button type="error" size="small" style="backgroundColor: #999;border-color: #999;cursor: not-allowed;">
修改
</i-button>
</div>
);
}
},
}
],
tableData: [],
pageData: {
total: 0,
current: 1,
pageSize: 20
},
filters: {
productSKN: {
label: 'SKN',
model: '',
holder: ''
},
prodName: {
label: '商品名称',
model: '',
holder: ''
},
brandId: {
label: '选择品牌',
model: ''
},
answerUserName: {
label: '回复人',
model: ''
},
sort: {
first: {
label: '选择类目',
holder: '选择一级类目',
model: ''
},
second: {
label: '二级类目',
holder: '选择二级类目',
model: ''
},
third: {
label: '三级类目',
holder: '选择三级类目',
model: ''
}
},
isReply: {
label: '是否回复',
model: '',
options: [
{
value: 1,
label: '已回复'
},
{
value: 2,
label: '未回复'
}
]
},
askTimeStr: {
label: '咨询时间',
model: ''
},
askStartTime: {
model: ''
},
askEndTime: {
model: ''
},
answerTimeStr: {
label: '回复时间',
model: ''
},
answerStartTime: {
model: ''
},
answerEndTime: {
model: ''
},
}
};
}
... ...
... ... @@ -4,12 +4,15 @@ import onsale from './onsale';
import offsale from './offsale';
import vips from './vips';
import output from './output';
import feedback from './feedback';
import share from './share';
export default {
create,
edit,
onsale,
offsale,
vips,
output
output,
feedback,
share
};
... ...
export default {
path: '/share.html',
name: 'share',
component: () => import(/* webpackChunkName: "product.share" */'./share'),
meta: {
pageName: '晒单评价'
}
};
... ...
<template>
<layout-body>
<layout-filter>
<filter-item :label="filters.erpSkuId.label">
<Input v-model.trim="filters.erpSkuId.model"
:placeholder="filters.erpSkuId.holder" :maxlength="9"></Input>
</filter-item>
<filter-item :label="filters.productSkn.label">
<Input v-model.trim="filters.productSkn.model"
:placeholder="filters.productSkn.holder" :maxlength="9"></Input>
</filter-item>
<filter-item :label="filters.productName.label">
<Input v-model.trim="filters.productName.model"
:placeholder="filters.productName.holder"></Input>
</filter-item>
<filter-item label="品牌">
<select-brand v-model="filters.brand.model"></select-brand>
</filter-item>
<filter-item :label="filters.hasImage.label">
<Select v-model.trim="filters.hasImage.model" clearable>
<Option v-for="option in filters.hasImage.options"
:value="option.value"
:key="option.value">{{option.label}}</Option>
</Select>
</filter-item>
<filter-item :label="filters.createTime.label">
<Date-picker type="datetimerange"
placeholder="选择日期和时间"
@on-change="createTimeChange"
v-model="filters.createTime.model">
</Date-picker>
</filter-item>
<filter-item>
<Button type="primary" @click="list">筛选</Button>
<Button @click="clearFilters">清空条件</Button>
</filter-item>
</layout-filter>
<layout-list>
<Table border :columns="tableCols" :data="tableData" @on-selection-change="selectChange"></Table>
<Page :total="pageData.total" :current="pageData.current"
@on-change="pageChange" show-total></Page>
</layout-list>
</layout-body>
</template>
<script>
import _ from 'lodash';
import moment from 'moment';
import shareStore from './store';
import ShareService from 'services/product/share-service';
export default {
data() {
return shareStore.call(this);
},
created() {
this.shareService = new ShareService();
},
mounted() {
this.list();
},
methods: {
clearFilters() {
this.filters.erpSkuId.model = null;
this.filters.productSkn.model = null;
this.filters.productName.model = null;
this.filters.hasImage.model = null;
this.filters.brand.model = null;
this.filters.beginTime = null;
this.filters.endTime = null;
this.pageData.current = 1;
this.list();
},
pageChange(page) {
this.pageData.current = page;
this.list();
},
filtersParams() {
let params = {};
let fts = this.filters;
let erpSkuId = fts.erpSkuId.model,
productSkn = fts.productSkn.model,
productName = fts.productName.model,
hasImage = fts.hasImage.model,
brand_id = fts.brand.model === '' || fts.brand.model === null ?
null : fts.brand.model,
beginTime = fts.beginTime.model,
endTime = fts.endTime.model;
let page = this.pageData.current;
let size = this.pageData.size;
if (this.filters.erpSkuId.model) {
if (this.isNumber(this.filters.erpSkuId.model)) {
params.erpSkuId = this.filters.erpSkuId.model;
} else {
return Promise.reject('sku必须是数字');
}
}
if (this.filters.productSkn.model) {
if (this.isNumber(this.filters.productSkn.model)) {
params.productSkn = this.filters.productSkn.model;
} else {
return Promise.reject('skn必须是数字');
}
}
params.size = this.pageData.size;
params.page = this.pageData.current;
return Promise.resolve({
params,
erpSkuId,
productSkn,
productName,
hasImage,
brand_id,
beginTime,
endTime,
page,
size
});
},
list() {
this.$Loading.start();
return this.filtersParams().then((params) => {
function getLocalTime(ns) {
let date = new Date(ns * 1000),
Y = date.getFullYear() + '-',
M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-',
D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' ',
h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':',
m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':',
s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
return Y + M + D + h + m + s;
}
if (params) {
params.beginTime = params.beginTime ? getLocalTime(params.beginTime) : null;
params.endTime = params.endTime ? getLocalTime(params.endTime) : null;
}
return this.shareService.list(params);
}).then((result) => {
if (result.code === 200) {
this.pageData.total = result.data.total;
this.pageData.current = result.data.page;
this.tableData = result.data.list;
}
this.$Loading.finish();
}).catch((err) => {
this.$Loading.finish();
this.$Message.error(err);
});
},
isNumber(numStr) {
const isNumber = /^[0-9]+$/;
return isNumber.test(numStr);
},
createTimeChange(time) {
// 兼容: https://github.com/iview/iview/issues/973
if (!_.isArray(time)) {
time = time.split(' - ');
}
if ((time[0] + '').length) {
this.filters.beginTime.model = +moment(time[0]).format('X');
this.filters.endTime.model = +moment(time[1]).format('X');
}
}
}
};
</script>
<style lang="scss">
.cell-img {
width: 80px;
background-size: 100%;
}
</style>
... ...
/**
* on share page store
* @author: Gexuhui
* @date: 2017/08/02
*/
import CellInfo from 'components/cell/cell-info';
import CellImage from 'components/cell/cell-image';
export default function() {
return {
tableCols: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: 'SKU',
key: 'erpSkuId',
width: 100,
align: 'center',
},
{
title: '商品信息',
align: 'center',
render: (h, params) => {
const row = params.row;
const infoItems = [
{
type: 'link',
label: row.productName,
value: `//item.yohobuy.com/${row.productSkn}.html`
},
{
label: 'SKN',
value: row.erpSkuId
},
{
label: '品牌',
value: row.brandName
},
{
label: '颜色/尺码',
value: `${row.colorName} / ${row.sizeName}`
}
];
return h(CellInfo, {
props: {
items: infoItems
}
});
}
},
{
title: '商品图片',
key: 'image',
width: 150,
align: 'center',
render: (h, params) => {
return (
<img v-prod-img={params.row.productSkn}/>
);
}
},
{
title: '评价内容',
key: 'content',
align: 'center',
},
{
title: '评价图片',
key: 'image',
width: 150,
align: 'center',
render: (h, params) => {
let url = params.row.url;
if (url.length > 0 && url.length !== 1) {
return h(CellImage, {
props: {
imageSrc: url,
productUrl: url
}
});
} else {
return '—';
}
}
},
{
title: '评价时间',
key: 'createTime',
align: 'center',
}
],
tableData: [],
pageData: {
total: 0,
current: 1
},
filters: {
erpSkuId: {
label: 'SKU',
model: '',
holder: ''
},
productSkn: {
label: 'SKN',
model: '',
holder: ''
},
productName: {
label: '商品名称',
model: '',
holder: ''
},
brand: {
label: '品牌',
model: ''
},
sort: {
first: {
label: '选择类目',
holder: '选择一级类目',
model: ''
},
second: {
label: '二级类目',
holder: '选择二级类目',
model: ''
},
third: {
label: '三级类目',
holder: '选择三级类目',
model: ''
}
},
hasImage: {
label: '图片筛选',
model: '',
options: [
{
value: 1,
label: '有图'
},
{
value: 0,
label: '无图'
}
]
},
createTime: {
label: '评价时间',
model: ''
},
beginTime: {
model: ''
},
endTime: {
model: ''
}
}
};
}
... ...
/**
* Created by GeXuHui on 2017/08/01.
*/
import Service from '../service';
const apiUrl = {
getConsultList: '/platform/getConsultList',
consultReply: '/platform/consultReply',
};
class FeedbackService extends Service {
list(params) {
return this.post(apiUrl.getConsultList, params);
}
consultReply(userId, id, answer) {
return this.post(apiUrl.consultReply, {userId, id, answer});
}
}
export default FeedbackService;
... ...
/**
* Created by GeXuHui on 2017/08/02.
*/
import Service from '../service';
const apiUrl = {
getShareOrderList: '/platform/getShareOrderList'
};
class FeedbackService extends Service {
list(params) {
return this.post(apiUrl.getShareOrderList, params);
}
}
export default FeedbackService;
... ...
... ... @@ -93,6 +93,9 @@ let domainApis = {
downloadFile: '/exceltemplate/download/CreateProductForShops',
getSellType: '/SellerProductController/getSellType',
queryProductPhotoList: '/sellerProductPhoto/queryProductPhotoList',
getConsultList: '/seller/consult/getConsultList',
consultReply: '/seller/consult/reply',
getShareOrderList: '/seller/shareOrder/shareOrderList',
},
shop: {
login: '/loginInter',
... ... @@ -103,7 +106,7 @@ let domainApis = {
// 域名列表
const domains = {
erp: 'http://192.168.103.82:9098',
platform: 'http://192.168.102.202:8088/platform',
platform: 'http://192.168.102.210:8088/platform',
shop: 'http://192.168.102.211:30016'
};
... ...