feat: 售后服务

feature/task1.0.0__0514__ch
向文可 2 years ago
parent 734afe1ed9
commit 6c3e9c5863

@ -1,2 +1,2 @@
VITE_BASE_URL=https://gateway-test.mashibing.cn
VITE_BASE_URL=http://k8s-horse-gateway.mashibing.cn/
VITE_REQUEST_TIMEOUT=20000

@ -40,5 +40,11 @@
"<style lang=\"less\" scoped></style>"
],
"description": "快速二次封装ElementPlus组件"
},
"try catch": {
"scope": "javascript,typescript",
"prefix": "trycatch",
"body": ["try {", "\t$0", "} catch (e) {", "\tconsole.info('取消$1', e);", "}"],
"description": "TryCatch代码块"
}
}

@ -0,0 +1,75 @@
import request from '@/utils/request.js';
export const search = (params) => {
return request({
url: '/mall/trade/admin/refundOrder/page',
method: 'get',
params,
});
};
export const detail = (id) => {
return request({
url: '/mall/trade/admin/refundOrder/refundInfo/' + id,
method: 'get',
});
};
export const returnDetail = (id) => {
return request({
url: '/mall/trade/admin/refundOrder/returnInfo/' + id,
method: 'get',
});
};
export const logistics = (id) => {
return request({
url: '/mall/trade/admin/refundOrder/logistics/' + id,
method: 'get',
});
};
export const summary = () => {
return request({
url: '/mall/trade/admin/tradeOrder/statistics',
method: 'get',
});
};
export const resolveReceive = (data) => {
return request({
url: '/mall/trade/admin/refundOrder/agreeReceiving',
method: 'put',
data,
});
};
export const rejectReceive = (data) => {
return request({
url: '/mall/trade/admin/refundOrder/disagreeReceiving',
method: 'put',
data,
});
};
export const resolveRefund = (data) => {
return request({
url: '/mall/trade/admin/refundOrder/agreeRefund',
method: 'put',
data,
});
};
export const rejectRefund = (data) => {
return request({
url: '/mall/trade/admin/refundOrder/disagreeRefund',
method: 'put',
data,
});
};
export const resolveReturn = (data) => {
return request({
url: '/mall/trade/admin/refundOrder/agreeReturn',
method: 'put',
data,
});
};
export const rejectReturn = (data) => {
return request({
url: '/mall/trade/admin/refundOrder/disagreeReturn',
method: 'put',
data,
});
};

@ -8,11 +8,11 @@
<el-icon :name="props.menuItem.meta.icon" size="20" />
<p>{{ props.menuItem.meta.title }}</p>
</template>
<MenuItem v-for="(item, index) in menuItem.children" :key="index" :menu-item="item" />
<menu-item v-for="(item, index) in menuItem.children" :key="index" :menu-item="item" />
</el-sub-menu>
</template>
<script setup>
<script setup lang="jsx">
const props = defineProps({
menuItem: {
type: Object,
@ -40,14 +40,10 @@
background-color: @layout-menu-active-bgc;
color: @layout-menu-active-fc;
}
+ .el-menu-item {
margin-top: @layout-space-small;
}
}
.el-sub-menu {
border-radius: @layout-border-radius;
&.is-opened {
background-color: @color-ghost-black;
> :deep(.el-sub-menu__title) {
background-color: @layout-menu-hover-bgc;
}
@ -56,13 +52,16 @@
border-radius: @layout-border-radius;
margin-bottom: @layout-space-small;
}
+ .el-menu-item {
margin-top: @layout-space-small;
}
:deep(.el-menu) {
padding: 0 !important;
border-radius: @layout-border-radius;
color: @color-white3;
}
}
.el-menu-item + .el-menu-item,
.el-menu-item + .el-sub-menu,
.el-sub-menu + .el-menu-item,
.el-sub-menu + .el-sub-menu {
margin-top: @layout-space-small;
}
</style>

@ -1,15 +1,15 @@
<template>
<div class="layout-menu" :class="{ collapse: collapseMenu }">
<LayoutTitle />
<layout-title />
<el-scrollbar>
<el-menu :default-active="activeMenu" unique-opened @select="handleSelect">
<MenuItem v-for="(item, index) in menuList" :key="index" :menu-item="item" />
<menu-item v-for="(item, index) in menuList" :key="index" :menu-item="item" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup>
<script setup lang="jsx">
import MenuItem from './menu-item.vue';
import LayoutTitle from './title.vue';
const router = useRouter();

@ -22,20 +22,6 @@ export const globalRoutes = [
global: true,
},
},
];
export const demoRoutes = import.meta.env.MODE === 'development' ? demoModule : [];
// 动态模块
export const dynamicRoutes = [];
const modules = import.meta.globEager('./modules/*.js');
Object.values(modules).forEach((mod) => {
dynamicRoutes.push(...mod.default);
});
// 本地路由
export const routes = [
...globalRoutes,
{
path: '/',
name: 'App',
@ -58,6 +44,20 @@ export const routes = [
},
],
},
];
export const demoRoutes = import.meta.env.MODE === 'development' ? demoModule : [];
// 动态模块
export const dynamicRoutes = [];
const modules = import.meta.globEager('./modules/*.js');
Object.values(modules).forEach((mod) => {
dynamicRoutes.push(...(mod.default instanceof Array ? mod.default : [mod.default]));
});
// 本地路由
export const routes = [
...globalRoutes,
...dynamicRoutes,
...demoRoutes,
{
@ -130,7 +130,6 @@ const flatRoutes = (routes, parent = { path: '/', meta: {} }) =>
}
route.path = parent.path + route.path;
}
route.meta = route.meta || {};
route.meta.menu = route.path.replaceAll(/\/:[^?]+\?/g, '');
let activeMenu = route.meta.activeMenu;
if (!activeMenu && route.meta.hidden) {
@ -138,8 +137,8 @@ const flatRoutes = (routes, parent = { path: '/', meta: {} }) =>
}
route.meta.activeMenu = activeMenu || route.meta.menu;
const children = flatRoutes(route.children || [], route);
route.redirect = children.find((item) => !item.meta.hidden)?.meta.menu;
if (route.meta.layout) {
route.redirect = route.redirect || children.find((item) => !item.meta.hidden)?.meta.menu;
if (route.meta.layout || route.meta.view) {
route.children = children;
res = [route];
} else {
@ -151,21 +150,6 @@ export const reset = (routes) => {
router.getRoutes().forEach((item) => {
router.removeRoute(item.name);
});
if (!config.useLocalRouter) {
routes = [
...globalRoutes,
...routes,
...demoRoutes,
{
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'NotFound',
meta: {
global: true,
},
},
];
}
flatRoutes(routes).forEach(router.addRoute);
store.commit(
'layout/setNotKeepAliveList',

@ -40,6 +40,27 @@ export default [
},
],
},
{
path: 'service',
name: 'ServiceApplication',
component: () => import('@/views/sales/service/index.vue'),
meta: {
title: '售后申请',
icon: 'barcode-box-fill',
},
children: [
{
path: 'detail/:id',
name: 'ServiceDetail',
component: () => import('@/views/sales/service/detail.vue'),
meta: {
title: '订单详情',
icon: 'barcode-box-fill',
hidden: true,
},
},
],
},
{
path: 'category',
name: 'CategoryManagement',

@ -1,8 +1,8 @@
import { getPermission, getUserInfo, login, sendSmsCode } from '@/api/auth';
import config from '@/configs';
import { ElMessage } from '@/plugins/element-plus';
import router, { reset as resetRoutes } from '@/router';
const viewComponents = import.meta.glob('../../views/**/*.vue');
import router, { demoRoutes, globalRoutes, reset as resetRoutes } from '@/router';
const viewComponents = import.meta.glob('../../../views/**/*.vue');
const state = () => ({
userInfo: null,
permission: [],
@ -22,6 +22,8 @@ const mutations = {
icon: item.icon,
hidden: item.type === 2,
activeMenu: item.activeMenu,
layout: item.pageUrl?.toLowerCase() === 'layout',
view: item.pageUrl?.toLowerCase() === 'view',
},
};
if (!route.path.startsWith('/')) {
@ -36,27 +38,42 @@ const mutations = {
activeMenu = parent.meta.menu;
}
route.meta.activeMenu = activeMenu || route.meta.menu;
if (route.component) {
if (route.component === 'Layout') {
route.component = () => import('@/layouts/default.vue');
route.meta.layout = true;
} else {
let path = `../../views/${route.component}`;
if (!path.endsWith('.vue')) {
path += '.vue';
}
let alias = path.replace(/\.vue$/, '/index.vue');
route.component = viewComponents[path] || viewComponents[alias];
if (!route.component) {
route.component = () => import('@/views/global/404.vue');
}
if (route.meta.layout) {
route.component = () => import('@/layouts/default.vue');
} else if (route.meta.view) {
route.component = () => import('@/layouts/components/view.vue');
} else if (route.component) {
let path = `../../../views/${route.component}`;
if (!path.endsWith('.vue')) {
path += '.vue';
}
let alias = path.replace(/\.vue$/, '/index.vue');
route.component = viewComponents[path] || viewComponents[alias];
if (!route.component) {
route.component = () => import('@/views/global/404.vue');
}
} else {
throw new Error('菜单没有配置前端页面');
}
route.children = convert(item.children || [], route);
return route;
});
};
data = config.useLocalRouter ? data : convert(data);
data = config.useLocalRouter
? data
: [
...globalRoutes,
...convert(data),
...demoRoutes,
{
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'NotFound',
meta: {
global: true,
},
},
];
state.permission = data;
resetRoutes(state.permission);
},

@ -106,6 +106,15 @@ const actions = {
return res;
},
address: async (context, data) => {
data = _.cloneDeep(data);
data.province = data.addressInfo[0];
data.city = data.addressInfo[1];
data.area = data.addressInfo[2];
delete data.addressInfo;
data.provinceCode = data.address[0];
data.cityCode = data.address[1];
data.areaCode = data.address[2];
delete data.address;
let res = await api.updateAddress(data);
if (res) {
ElMessage.success('修改收货人信息成功');

@ -0,0 +1,143 @@
import * as api from '@/api/sales/service.js';
import { ElMessage } from '@/plugins/element-plus';
const state = () => ({
code: 'ServiceOrderManagement',
condition: {},
list: [],
total: 0,
summary: [],
opts: {
init: false,
type: [
{ label: '仅退款', value: 1 },
{ label: '退货退款', value: 2 },
],
handle: [
{ label: '待处理', value: 1 },
{ label: '同意退款', value: 2, count: 0 },
{ label: '拒绝退款', value: 3, count: 0 },
{ label: '同意退货', value: 4, count: 0 },
{ label: '拒绝退货', value: 5, count: 0 },
{ label: '确认收货', value: 6, count: 0 },
{ label: '拒绝收货', value: 7, count: 0 },
],
status: [
{ label: '全部', value: 0, count: 0 },
{ label: '已申请', value: 1, count: 0 },
{ label: '已关闭', value: 2, count: 0 },
{ label: '待退货', value: 3, count: 0 },
{ label: '退货中', value: 4, count: 0 },
{ label: '退款中', value: 5, count: 0 },
{ label: '退款成功', value: 6, count: 0 },
{ label: '退款失败', value: 7, count: 0 },
],
shipFees: [
{
label: '不退运费',
value: 0,
},
{
label: '退运费',
value: 1,
},
],
receivePoint: [],
},
});
const getters = {};
const mutations = {
setCode: (state, data) => (state.code = data),
setCondition: (state, data) => (state.condition = data),
setList: (state, data) => (state.list = data),
setTotal: (state, data) => (state.total = data),
setSummary: (state, data) => (state.summary = data),
setOpts: (state, data) => (state.opts = data),
};
const actions = {
search: async ({ state, commit, rootGetters }) => {
let data = { ...state.condition };
if (data.dateRange?.length) {
data.applyStartTime = data.dateRange[0];
data.applyEndTime = data.dateRange[1];
}
delete data.dateRange;
data.refundStatus = (data.refundStatus || []).join(',');
if (data.refundStatus === '0') {
delete data.refundStatus;
}
let res = await api.search({ ...rootGetters['local/page'](state.code), ...data });
commit('setList', res?.records || []);
commit('setTotal', res?.total || 0);
if (!res) {
ElMessage.error('查询订单列表失败');
}
res = await api.summary();
if (res) {
commit(
'setSummary',
['allCount', 'unpaidCount', 'closeCount', 'waitDeliveryCount', 'deliveredCount', '', 'finishCount'].map(
(prop) => res[prop]
)
);
} else {
ElMessage.error('查询订单统计失败');
commit('setSummary', []);
}
return res;
},
load: async ({ commit, state }) => {
commit('setOpts', {
...state.opts,
init: true,
});
},
detail: async (context, id) => {
let res = await api.detail(id);
if (!res) {
ElMessage.error('加载详情失败');
}
return res;
},
resolveRefund: async (context, data) => {
let res = await api.resolveRefund(data);
if (res) {
ElMessage.success('同意退款成功');
} else {
ElMessage.error('同意退款失败');
}
return res;
},
rejectRefund: async (context, data) => {
let res = await api.rejectRefund(data);
if (res) {
ElMessage.success('拒绝退款成功');
} else {
ElMessage.error('拒绝退款失败');
}
return res;
},
resolveReturn: async (context, data) => {
data = _.cloneDeep(data);
data.province = data.addressInfo[0];
data.city = data.addressInfo[1];
data.area = data.addressInfo[2];
delete data.addressInfo;
data.provinceCode = data.address[0];
data.cityCode = data.address[1];
data.areaCode = data.address[2];
delete data.address;
let res = await api.resolveReturn(data);
if (res) {
ElMessage.success('同意退货成功');
} else {
ElMessage.error('同意退货失败');
}
return res;
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -49,7 +49,7 @@
@layout-menu-bgc: @color-white;
@layout-menu-fc: @color-white4;
@layout-menu-ic: @color-white3;
@layout-menu-hover-bgc: lighten(@color-black, 25%);
@layout-menu-hover-bgc: @color-primary-white;
@layout-menu-active-fc: @color-primary;
@layout-menu-active-bgc: @color-primary-white;
@ -69,12 +69,10 @@
@layout-footer-bgc: @color-white;
@layout-footer-fc: @color-black;
@layout-main-height: calc(
100vh - @layout-header-height - @layout-tabs-height - @layout-space-large * 2 - @layout-footer-height
);
@layout-main-height: calc(100vh - @layout-header-height - @layout-tabs-height - @layout-space-large * 2 - @layout-footer-height);
:export {
layoutAsideWidth: @layout-aside-width;
layoutLogoSize: @layout-logo-size;
layoutLogoColor: @layout-logo-color;
}
}

@ -68,16 +68,7 @@
state.submitting = true;
try {
await unref(refsForm).validate();
let data = _.cloneDeep(state.form);
data.province = data.addressInfo[0];
data.city = data.addressInfo[1];
data.area = data.addressInfo[2];
delete data.addressInfo;
data.provinceCode = data.address[0];
data.cityCode = data.address[1];
data.areaCode = data.address[2];
delete data.address;
let res = await store.dispatch('order/address', data);
let res = await store.dispatch('order/address', state.form);
if (res) {
Object.assign(state.order, state.form);
state.visible = false;

@ -0,0 +1,406 @@
<template>
<div v-loading="state.loading" class="detail-container">
<el-steps :active="state.currentStep" align-center>
<el-step
v-for="(item, index) in [[], state.steps, state.steps2][state.detail.refundType || 0]"
:key="index"
:description="item.desc"
:title="item.title"
/>
</el-steps>
<div class="card">
<div class="header">
<h3 class="left red">当前服务单状态{{ state.detail.refundStatusDesc }}</h3>
<div class="right">
<template v-if="state.detail.refundStatus === 1">
<el-button type="primary" @click="handleResolve">
同意{{ state.detail.refundType === 1 ? '退款' : '退货' }}
</el-button>
<el-button type="danger" @click="handleReject">
拒绝{{ state.detail.refundType === 1 ? '退款' : '退货' }}
</el-button>
</template>
</div>
</div>
<el-scrollbar class="body">
<h3>售后商品</h3>
<el-table border :data="[state.detail.refundProduct]">
<el-table-column align="center" header-align="center" label="商品图片" prop="productImageUrl">
<template #default="{ row }">
<el-image :alt="row.productName || '商品图片'" height="64px" :src="row.productImageUrl" />
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="商品名称" prop="productName" />
<el-table-column align="center" header-align="center" label="价格" prop="realPrice">
<template #default="{ row }">{{ new Number(row.realPrice).toFixed(2) }}</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="属性" prop="skuDescribe" />
<el-table-column align="center" header-align="center" label="数量" prop="quantity" />
<el-table-column align="center" header-align="center" label="小计" prop="realAmount">
<template #default="{ row }">{{ new Number(row.realAmount).toFixed(2) }}</template>
</el-table-column>
</el-table>
<h3 class="summary">
<span>合计</span>
<span class="red">{{ new Number(state.sum).toFixed(2) }}</span>
</h3>
<br />
<h3>服务单信息</h3>
<el-form class="half-form" inline label-width="120px">
<el-form-item class="half" label="服务单号">
{{ state.detail.refundNo }}
</el-form-item>
<el-form-item class="half" label="处理状态">
{{ state.detail.refundStatusDesc }}
</el-form-item>
<el-form-item class="half" label="订单编号">
{{ state.detail.refundTradeOrderInfo?.orderNo }}
</el-form-item>
<el-form-item class="half" label="订单状态">
{{ state.detail.refundTradeOrderInfo?.orderStatusDesc }}
</el-form-item>
<el-form-item label="申请时间">
{{ state.detail.applyTime }}
</el-form-item>
<el-form-item label="用户账号">
{{ state.detail.userId }}
</el-form-item>
<el-form-item label="联系人">
{{ state.detail.userId }}
</el-form-item>
<el-form-item label="联系电话">
{{ state.detail.userPhone }}
</el-form-item>
<el-form-item label="退货原因">
{{ state.detail.refundReason }}
</el-form-item>
<el-form-item label="申请退款金额">
{{ state.detail.applyAmount }}
</el-form-item>
<el-form-item label="问题描述">
{{ state.detail.problemDescribe }}
</el-form-item>
<el-form-item label="凭证照片">
<el-image
v-for="(item, index) in state.detail.refundEvidences"
:key="index"
alt="凭证照片"
:src="item.fileUrl"
/>
</el-form-item>
</el-form>
<template v-if="state.detail.refundType === 1">
<h3>退款处理</h3>
<el-form v-if="[2, 6].includes(state.detail.refundStatus)" label-width="120px">
<el-form-item label="订单金额">
{{ state.detail.productAmount }}
</el-form-item>
<el-form-item label="退运费">
{{ state.detail.isBackShippingAmount ? '退运费' : '不退运费' }}
</el-form-item>
<el-form-item label="确认退款金额" prop="refundAmount">
{{ state.detail.refundAmount }}
</el-form-item>
<el-form-item label="处理人员">
{{ state.detail.handleRefundLog?.createUserNickName }}
</el-form-item>
<el-form-item label="处理操作">
{{ state.detail.handleRefundLog?.operationTypeDesc }}
</el-form-item>
<el-form-item label="处理时间">
{{ state.detail.handleRefundLog?.createTime }}
</el-form-item>
<el-form-item label="处理备注">
{{ state.detail.remark || state.detail.closeReason || '无' }}
</el-form-item>
</el-form>
<el-form v-else ref="refsForm" label-width="120px" :model="state.form" :rules="state.rules">
<el-form-item label="订单金额">
{{ state.detail.refundAmount }}
</el-form-item>
<el-form-item label="确认退款金额" prop="refundAmount">
<el-input-number v-model="state.form.refundAmount" />
</el-form-item>
<el-form-item label="处理备注" prop="remark">
<el-input v-model="state.form.remark" />
<p class="tips">展示给用户的说明</p>
</el-form-item>
</el-form>
</template>
<template v-else>
<h3>退货处理</h3>
<el-form
v-if="state.detail.refundStatus === 1"
ref="refsForm"
label-width="120px"
:model="state.form"
:rules="state.rules"
>
<el-form-item label="订单金额">
{{ state.detail.productAmount }}
</el-form-item>
<el-form-item label="退运费" prop="isBackShippingAmount">
<el-radio-group v-model="state.form.isBackShippingAmount" :opts="opts.shipFees" />
</el-form-item>
<el-form-item label="确认退款金额" prop="refundAmount">
<el-input-number v-model="state.form.refundAmount" />
</el-form-item>
<el-form-item label="选择收货点" prop="receivePoint">
<el-select v-model="state.form.receivePoint" :opts="opts.receivePoint" />
</el-form-item>
<el-form-item label="收货人姓名" prop="recipientName">
<el-input v-model="state.form.recipientName" />
</el-form-item>
<el-form-item label="所在区域" prop="address">
<el-area v-model="state.form.address" v-model:info="state.form.addressInfo" />
</el-form-item>
<el-form-item label="详细地址" prop="detailAddress">
<el-input v-model="state.form.detailAddress" />
</el-form-item>
<el-form-item label="联系电话" prop="recipientPhone">
<el-input v-model="state.form.recipientPhone" />
</el-form-item>
<el-form-item label="退货说明" prop="remark">
<el-input v-model="state.form.remark" />
<p class="tips">展示给用户的说明</p>
</el-form-item>
</el-form>
<el-form v-else label-width="120px">
<el-form-item label="订单金额">
{{ state.detail.productAmount }}
</el-form-item>
<el-form-item label="退运费">
{{ state.detail.isBackShippingAmount ? '退运费' : '不退运费' }}
</el-form-item>
<el-form-item label="确认退款金额">
{{ state.detail.refundAmount }}
</el-form-item>
<el-form-item label="收货人姓名">
{{ state.detail.refundAddress?.recipientName }}
</el-form-item>
<el-form-item label="收货地址">
{{ state.detail.refundAddress?.detailAddress }}
</el-form-item>
<el-form-item label="联系电话">
{{ state.detail.refundAddress?.recipientPhone }}
</el-form-item>
<el-form-item label="退货说明">
{{ state.detail.handleReturnLog?.remark }}
</el-form-item>
<el-form-item label="处理人员">
{{ state.detail.handleReturnLog?.createUserNickName }}
</el-form-item>
<el-form-item label="处理操作">
{{ state.detail.handleReturnLog?.operationTypeDesc }}
</el-form-item>
<el-form-item label="处理时间">
{{ state.detail.handleReturnLog?.createTime }}
</el-form-item>
</el-form>
</template>
</el-scrollbar>
</div>
</div>
</template>
<script setup lang="jsx">
const store = useStore();
const route = useRoute();
const opts = computed(() => store.state.service.opts);
if (!unref(opts).init) {
store.dispatch('service/load');
}
const refsForm = ref(null);
const state = reactive({
steps: [
{
title: '买家申请退款',
desc: '',
status: [1, 2],
},
{
title: '商家处理售后申请',
desc: '未处理',
status: [5, 6],
},
{
title: '售后完毕',
desc: '未完毕',
status: [3, 4, 5, 6, 12],
},
],
steps2: [
{
title: '买家申请退货退款',
desc: '',
status: [1, 2],
},
{
title: '商家处理售后申请',
desc: '未处理',
status: [8, 9],
},
{
title: '买家寄回退货商品',
desc: '未寄回',
status: [11],
},
{
title: '商家确认收货',
desc: '未收货',
status: [13, 14],
},
{
title: '售后完毕',
desc: '未完毕',
status: [3, 4, 7, 10],
},
],
currentStep: 1,
loading: false,
detail: {
refundProduct: [],
},
form: {
reject: false,
address: [],
addressInfo: [],
receivePoint: null,
isBackShippingAmount: 0,
area: '',
areaCode: '',
city: '',
cityCode: '',
detailAddress: '',
province: '',
provinceCode: '',
recipientName: '',
recipientPhone: '',
refundAmount: 0,
refundId: 0,
remark: '',
},
rules: {
refundAmount: [{ required: true, message: '退款金额不能为空' }],
recipientName: [{ required: true, message: '收件人姓名不能为空' }],
address: [{ required: true, message: '所在区域不能为空' }],
detailAddress: [{ required: true, message: '详细地址不能为空' }],
recipientPhone: [{ required: true, message: '联系电话不能为空' }],
remark: [
{
validator: (rule, value, cb) => {
if (state.form.reject && !value) {
cb('拒绝退货/退款时处理备注不能为空');
} else {
cb();
}
},
},
],
},
sum: computed(() => [state.detail.refundProduct].reduce((sum, current) => sum + current.realAmount || 0, 0)),
});
const handleLoad = async () => {
state.loading = true;
let res = await store.dispatch('service/detail', route.params.id);
Object.assign(
state.detail,
res || {
refundProduct: [],
}
);
state.form.refundId = res.refundId;
state.form.refundAmount = res.refundAmount;
[null, state.steps, state.steps2][res.refundType].forEach((step, index) => {
let date = res.refundLogs.find((item) => step.status.includes(item.operationType));
step.desc = date?.createTime || step.desc;
if (date) {
if (index === 4) {
step.desc = date?.operationTypeDesc;
}
state.currentStep = index + 1;
}
});
state.loading = false;
};
onActivated(handleLoad);
/* 同意退货 */
const handleResolve = async () => {
state.loading = true;
state.form.reject = false;
try {
await unref(refsForm).validate();
await store.dispatch(
state.detail.refundType === 1 ? 'service/resolveRefund' : 'service/resolveReturn',
state.form
);
handleLoad();
} catch (e) {
console.info('取消同意', e);
}
state.loading = false;
};
/* 拒绝退货 */
const handleReject = async () => {
state.loading = true;
state.form.reject = true;
try {
await unref(refsForm).validate();
await store.dispatch(
state.detail.refundType === 1 ? 'service/rejectRefund' : 'service/rejectReturn',
state.form
);
handleLoad();
} catch (e) {
console.info('取消拒绝', e);
}
state.loading = false;
};
</script>
<style lang="less" scoped>
.detail-container {
display: flex;
flex-direction: column;
.card {
height: 100%;
flex-shrink: 1;
overflow: hidden;
margin-top: @layout-space;
display: flex;
flex-direction: column;
.header {
display: flex;
justify-content: space-between;
padding: @layout-space;
}
.body {
height: 100%;
flex-shrink: 1;
display: flex;
flex-direction: column;
padding: @layout-space;
h3 {
margin-bottom: @layout-space;
}
.half-form {
.el-form-item {
width: 100%;
&.half {
width: 50%;
margin-right: 0;
}
}
}
.summary {
text-align: right;
margin-top: @layout-space;
}
}
.red {
color: var(--el-color-danger);
}
}
}
</style>

@ -0,0 +1,233 @@
<template>
<div class="list-container">
<ul class="order-status">
<li v-for="(item, index) in opts.status" :key="index">
<el-button
:type="state.condition.refundStatus.includes(item.value) ? 'primary' : 'default'"
@click="handleStatus(item.value)"
>
<span>
{{ item.label }}
</span>
<span>(</span>
<span class="num">{{ summary[index] || 0 }}</span>
<span>)</span>
</el-button>
</li>
</ul>
<TableList
v-loading="loading"
:code="code"
:config="config"
:data="list"
:operation="['search', 'export']"
:reset="handleReset"
title="订单"
:total="total"
@export="handleExport"
@search="handleSearch"
>
<template #search>
<el-form inline>
<el-form-item label="服务单号" prop="orderNo">
<el-input v-model="state.condition.orderNo" />
</el-form-item>
<el-form-item label="售后类型" prop="refundType">
<el-select v-model="state.condition.refundType" :opts="opts.type" />
</el-form-item>
<el-form-item label="处理状态" prop="handleStatus">
<el-select v-model="state.condition.handleStatus" :opts="opts.handle" />
</el-form-item>
<el-form-item label="用户账号" prop="userPhone">
<el-input v-model="state.condition.userPhone" />
</el-form-item>
<el-form-item label="申请时间" prop="dateRange">
<el-date-picker
v-model="state.condition.dateRange"
:default-time="[new Date(0, 0, 0, 0, 0, 0), new Date(0, 0, 0, 23, 59, 59)]"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
</el-form>
</template>
</TableList>
</div>
</template>
<script setup lang="jsx">
import ElButton from '@/components/extra/ElButton.vue';
const router = useRouter();
const store = useStore();
const loading = ref(false);
const code = computed(() => store.state.service.code);
const list = computed(() => store.state.service.list);
const total = computed(() => store.state.service.total);
const summary = computed(() => store.state.service.summary);
const opts = computed(() => store.state.service.opts);
if (!unref(opts).init) {
store.dispatch('service/load');
}
/* 查询订单 */
const state = reactive({
condition: {
orderNo: null,
userPhone: null,
orderSource: null,
refundStatus: [0],
dateRange: [],
startTime: null,
endTime: null,
},
});
watch(
() => state.condition,
(value) => {
store.commit('service/setCondition', _.cloneDeep(value));
},
{ immediate: true, deep: true }
);
watch(
() => state.condition.refundStatus,
() => {
handleSearch();
},
{ deep: true }
);
const handleReset = () => {
state.condition = {
orderNo: null,
userPhone: null,
orderSource: null,
refundStatus: [0],
dateRange: [],
startTime: null,
endTime: null,
};
};
const handleStatus = (status) => {
let index = state.condition.refundStatus.indexOf(status);
if (index === -1) {
if (status === 0) {
state.condition.refundStatus = [0];
} else {
state.condition.refundStatus.push(status);
state.condition.refundStatus = state.condition.refundStatus.filter((item) => item > 0);
}
} else {
if (status !== 0) {
state.condition.refundStatus.splice(index, 1);
if (state.condition.refundStatus.length) {
state.condition.refundStatus = state.condition.refundStatus.filter((item) => item > 0);
} else {
state.condition.refundStatus = [0];
}
}
}
};
const handleSearch = async () => {
loading.value = true;
await store.dispatch('service/search');
loading.value = false;
};
onActivated(handleSearch);
/* 导出订单 */
const handleExport = async () => {
console.info('export');
};
/* 查看详情 */
const handleDetail = (row) => {
router.push({
name: 'ServiceDetail',
params: {
id: row.refundId,
},
});
};
/* 列表配置 */
const config = reactive({
columns: [
{
type: 'selection',
fixed: 'left',
width: 60,
},
{
label: '服务单号',
prop: 'refundNo',
minWidth: 160,
fixed: 'left',
},
{
label: '申请时间',
prop: 'applyTime',
width: 180,
},
{
label: '用户账号',
prop: 'userPhone',
width: 140,
},
{
label: '退款金额',
prop: 'applyAmount',
minWidth: 120,
},
{
label: '售后类型',
prop: 'refundTypeDesc',
width: 120,
},
{
label: '服务单状态',
width: 120,
prop: 'refundStatusDesc',
},
{
label: '处理状态',
width: 120,
prop: 'handleStatusDesc',
},
{
label: '处理时间',
width: 180,
prop: 'handleTime',
},
{
label: '操作',
fixed: 'right',
width: 160,
slots: {
default: ({ row }) => (
<ElButton type="text" onClick={() => handleDetail(row)}>
查看详情
</ElButton>
),
},
},
],
});
</script>
<style lang="less" scoped>
.list-container {
display: flex;
flex-direction: column;
.common-list {
flex-shrink: 1;
}
.order-status {
display: flex;
margin-bottom: @layout-space;
li {
+ li {
margin-left: @layout-space;
}
}
}
}
</style>
Loading…
Cancel
Save