Skip to content

Commit 09d2163

Browse files
YunaiVgitee-org
authored andcommitted
!224 订单售后退款:完善列表、详情、售后相关操作
Merge pull request !224 from puhui999/dev-to-dev
2 parents 8d026f0 + eea285b commit 09d2163

File tree

7 files changed

+608
-12
lines changed

7 files changed

+608
-12
lines changed

src/api/mall/trade/afterSale/index.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import request from '@/config/axios'
2+
3+
export interface TradeAfterSaleVO {
4+
id?: number | null // 售后编号,主键自增
5+
no?: string // 售后单号
6+
status?: number | null // 退款状态
7+
way?: number | null // 售后方式
8+
type?: number | null // 售后类型
9+
userId?: number | null // 用户编号
10+
applyReason?: string // 申请原因
11+
applyDescription?: string // 补充描述
12+
applyPicUrls?: string[] // 补充凭证图片
13+
orderId?: number | null // 交易订单编号
14+
orderNo?: string // 订单流水号
15+
orderItemId?: number | null // 交易订单项编号
16+
spuId?: number | null // 商品 SPU 编号
17+
spuName?: string // 商品 SPU 名称
18+
skuId?: number | null // 商品 SKU 编号
19+
properties?: ProductPropertiesVO[] // 属性数组
20+
picUrl?: string // 商品图片
21+
count?: number | null // 退货商品数量
22+
auditTime?: Date // 审批时间
23+
auditUserId?: number | null // 审批人
24+
auditReason?: string // 审批备注
25+
refundPrice?: number | null // 退款金额,单位:分。
26+
payRefundId?: number | null // 支付退款编号
27+
refundTime?: Date // 退款时间
28+
logisticsId?: number | null // 退货物流公司编号
29+
logisticsNo?: string // 退货物流单号
30+
deliveryTime?: Date // 退货时间
31+
receiveTime?: Date // 收货时间
32+
receiveReason?: string // 收货备注
33+
}
34+
35+
export interface ProductPropertiesVO {
36+
propertyId?: number | null // 属性的编号
37+
propertyName?: string // 属性的名称
38+
valueId?: number | null //属性值的编号
39+
valueName?: string // 属性值的名称
40+
}
41+
42+
// 获得交易售后分页
43+
export const getAfterSalePage = async (params) => {
44+
return await request.get({ url: `/trade/after-sale/page`, params })
45+
}
46+
// 获得交易售后详情
47+
export const getAfterSale = async (id: any) => {
48+
return await request.get({ url: `/trade/after-sale/get-detail?id=${id}` })
49+
}
50+
// 同意售后
51+
export const agree = async (id: any) => {
52+
return await request.put({ url: `/trade/after-sale/agree?id=${id}` })
53+
}
54+
// 拒绝售后
55+
export const disagree = async (data: any) => {
56+
return await request.put({ url: `/trade/after-sale/disagree`, data })
57+
}
58+
// 确认收货
59+
export const receive = async (id: any) => {
60+
return await request.put({ url: `/trade/after-sale/receive?id=${id}` })
61+
}
62+
// 拒绝收货
63+
export const refuse = async (id: any) => {
64+
return await request.put({ url: `/trade/after-sale/refuse?id=${id}` })
65+
}
66+
// 确认退款
67+
export const refund = async (id: any) => {
68+
return await request.put({ url: `/trade/after-sale/refund?id=${id}` })
69+
}

src/router/modules/remaining.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,16 +395,22 @@ const remainingRouter: AppRouteRecordRaw[] = [
395395
{
396396
path: '/trade/order',
397397
component: Layout,
398-
name: 'Detail',
398+
name: 'Order',
399399
meta: {
400400
hidden: true
401401
},
402402
children: [
403403
{
404-
path: 'detail/:orderId(\\d+)',
404+
path: 'orderDetail/:orderId(\\d+)',
405405
component: () => import('@/views/mall/trade/order/detail/index.vue'),
406-
name: 'TradeOrderDetailForm',
406+
name: 'TradeOrderDetail',
407407
meta: { title: '订单详情', icon: '', activeMenu: '/trade/trade/order' }
408+
},
409+
{
410+
path: 'afterSaleDetail/:orderId(\\d+)',
411+
component: () => import('@/views/mall/trade/afterSale/detail/index.vue'),
412+
name: 'TradeAfterSaleDetail',
413+
meta: { title: '退款详情', icon: '', activeMenu: '/trade/trade/after-sale' }
408414
}
409415
]
410416
},
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<template>
2+
<Dialog v-model="dialogVisible" title="拒绝售后" width="45%">
3+
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
4+
<el-form-item label="审批备注">
5+
<el-input
6+
v-model="formData.auditReason"
7+
:rows="3"
8+
placeholder="请输入审批备注"
9+
type="textarea"
10+
/>
11+
</el-form-item>
12+
</el-form>
13+
<template #footer>
14+
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
15+
<el-button @click="dialogVisible = false">取 消</el-button>
16+
</template>
17+
</Dialog>
18+
</template>
19+
<script lang="ts" setup>
20+
import * as AfterSaleApi from '@/api/mall/trade/afterSale/index'
21+
22+
defineOptions({ name: 'UpdateAuditReasonForm' })
23+
24+
const { t } = useI18n() // 国际化
25+
const message = useMessage() // 消息弹窗
26+
27+
const dialogVisible = ref(false) // 弹窗的是否展示
28+
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
29+
const formData = ref({
30+
id: 0, // 售后订单编号
31+
auditReason: '' // 审批备注
32+
})
33+
const formRef = ref() // 表单 Ref
34+
35+
/** 打开弹窗 */
36+
const open = async (row: AfterSaleApi.TradeAfterSaleVO) => {
37+
resetForm()
38+
// 设置数据
39+
formData.value.id = row.id
40+
formData.value.auditReason = row.auditReason
41+
dialogVisible.value = true
42+
}
43+
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
44+
45+
/** 提交表单 */
46+
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
47+
const submitForm = async () => {
48+
// 提交请求
49+
formLoading.value = true
50+
try {
51+
const data = unref(formData)
52+
await AfterSaleApi.disagree(data)
53+
message.success(t('common.updateSuccess'))
54+
dialogVisible.value = false
55+
// 发送操作成功的事件
56+
emit('success', true)
57+
} finally {
58+
formLoading.value = false
59+
}
60+
}
61+
62+
/** 重置表单 */
63+
const resetForm = () => {
64+
formData.value = {
65+
id: 0, // 售后订单编号
66+
auditReason: '' // 审批备注
67+
}
68+
formRef.value?.resetFields()
69+
}
70+
</script>
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<template>
2+
<ContentWrap>
3+
<!-- 订单信息 -->
4+
<el-descriptions title="退款订单信息">
5+
<el-descriptions-item label="订单号: ">{{ formData.orderNo }}</el-descriptions-item>
6+
<el-descriptions-item label="配送方式: ">
7+
<dict-tag :type="DICT_TYPE.TRADE_DELIVERY_TYPE" :value="formData.order.deliveryType" />
8+
</el-descriptions-item>
9+
<el-descriptions-item label="订单类型: ">
10+
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="formData.order.type" />
11+
</el-descriptions-item>
12+
<el-descriptions-item label="收货人: ">
13+
{{ formData.order.receiverName }}
14+
</el-descriptions-item>
15+
<el-descriptions-item label="买家留言: ">
16+
{{ formData.order.userRemark }}
17+
</el-descriptions-item>
18+
<el-descriptions-item label="订单来源: ">
19+
<dict-tag :type="DICT_TYPE.TERMINAL" :value="formData.order.terminal" />
20+
</el-descriptions-item>
21+
<el-descriptions-item label="联系电话: ">
22+
{{ formData.order.receiverMobile }}
23+
</el-descriptions-item>
24+
<el-descriptions-item label="商家备注: ">{{ formData.order.remark }}</el-descriptions-item>
25+
<el-descriptions-item label="支付单号: ">
26+
{{ formData.order.payOrderId }}
27+
</el-descriptions-item>
28+
<el-descriptions-item label="付款方式: ">
29+
<dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="formData.order.payChannelCode" />
30+
</el-descriptions-item>
31+
<!-- TODO 芋艿:待实现:跳转会员 -->
32+
<!-- <el-descriptions-item label="买家: ">{{ formData.user.nickname }}</el-descriptions-item> -->
33+
</el-descriptions>
34+
35+
<!-- 售后信息 -->
36+
<el-descriptions title="售后信息">
37+
<el-descriptions-item label="退款编号: ">{{ formData.no }}</el-descriptions-item>
38+
<el-descriptions-item label="申请时间: ">
39+
{{ formatDate(formData.auditTime) }}
40+
</el-descriptions-item>
41+
<!-- TODO 营销活动待实现 -->
42+
<el-descriptions-item label="售后类型: ">
43+
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_TYPE" :value="formData.type" />
44+
</el-descriptions-item>
45+
<el-descriptions-item label="售后方式: ">
46+
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_WAY" :value="formData.way" />
47+
</el-descriptions-item>
48+
<el-descriptions-item label="退款金额: ">{{ formData.refundPrice }}</el-descriptions-item>
49+
<el-descriptions-item label="退款原因: ">{{ formData.applyReason }}</el-descriptions-item>
50+
<el-descriptions-item label="补充描述: ">
51+
{{ formData.applyDescription }}
52+
</el-descriptions-item>
53+
<el-descriptions-item label="凭证图片: "> {{ formData.applyPicUrls }}</el-descriptions-item>
54+
</el-descriptions>
55+
56+
<!-- 退款状态 -->
57+
<el-descriptions :column="1" title="退款状态">
58+
<el-descriptions-item label="退款状态: ">
59+
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_STATUS" :value="formData.status" />
60+
</el-descriptions-item>
61+
<el-descriptions-item label-class-name="no-colon">
62+
<el-button type="primary" @click="openForm('agree')">同意售后</el-button>
63+
<el-button type="primary" @click="openForm('disagree')">拒绝售后</el-button>
64+
<el-button type="primary" @click="openForm('receive')">确认收货</el-button>
65+
<el-button type="primary" @click="openForm('refuse')">拒绝收货</el-button>
66+
<el-button type="primary" @click="openForm('refund')">确认退款</el-button>
67+
</el-descriptions-item>
68+
<el-descriptions-item>
69+
<template #label><span style="color: red">提醒: </span></template>
70+
如果未发货,请点击同意退款给买家。<br />
71+
如果实际已发货,请主动与买家联系。<br />
72+
如果订单整体退款后,优惠券和余额会退还给买家.
73+
</el-descriptions-item>
74+
</el-descriptions>
75+
76+
<!-- 商品信息 -->
77+
<el-descriptions title="商品信息">
78+
<el-descriptions-item labelClassName="no-colon">
79+
<el-row :gutter="20">
80+
<el-col :span="15">
81+
<el-table :data="formData.items" border>
82+
<el-table-column label="商品" prop="spuName" width="auto">
83+
<template #default="{ row }">
84+
{{ row.spuName }}
85+
<el-tag v-for="property in row.properties" :key="property.propertyId">
86+
{{ property.propertyName }}: {{ property.valueName }}
87+
</el-tag>
88+
</template>
89+
</el-table-column>
90+
<el-table-column label="商品原价" prop="price" width="150">
91+
<template #default="{ row }">{{ floatToFixed2(row.price) }}元</template>
92+
</el-table-column>
93+
<el-table-column label="数量" prop="count" width="100" />
94+
<el-table-column label="合计" prop="payPrice" width="150">
95+
<template #default="{ row }">{{ floatToFixed2(row.payPrice) }}元</template>
96+
</el-table-column>
97+
<el-table-column label="售后状态" prop="afterSaleStatus" width="120">
98+
<template #default="{ row }">
99+
<dict-tag
100+
:type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS"
101+
:value="row.afterSaleStatus"
102+
/>
103+
</template>
104+
</el-table-column>
105+
</el-table>
106+
</el-col>
107+
<el-col :span="10" />
108+
</el-row>
109+
</el-descriptions-item>
110+
</el-descriptions>
111+
<!-- 售后信息 -->
112+
<el-descriptions title="售后日志" />
113+
</ContentWrap>
114+
115+
<!-- 各种操作的弹窗 -->
116+
<UpdateAuditReasonForm ref="updateAuditReasonFormRef" @success="getDetail" />
117+
</template>
118+
<script lang="ts" setup>
119+
import * as AfterSaleApi from '@/api/mall/trade/afterSale/index'
120+
import { floatToFixed2 } from '@/utils'
121+
import { DICT_TYPE } from '@/utils/dict'
122+
import { formatDate } from '@/utils/formatTime'
123+
import UpdateAuditReasonForm from '@/views/mall/trade/afterSale/components/UpdateAuditReasonForm.vue'
124+
125+
defineOptions({ name: 'TradeOrderDetailForm' })
126+
127+
const message = useMessage() // 消息弹窗
128+
const { params } = useRoute() // 查询参数
129+
const formData = ref({
130+
order: {}
131+
})
132+
const updateAuditReasonFormRef = ref() // 拒绝售后表单 Ref
133+
/** 获得详情 */
134+
const getDetail = async () => {
135+
const id = params.orderId as unknown as number
136+
if (id) {
137+
formData.value = await AfterSaleApi.getAfterSale(id)
138+
}
139+
}
140+
const openForm = (type: string) => {
141+
switch (type) {
142+
case 'agree':
143+
message.confirm('是否同意售后?').then(() => {
144+
AfterSaleApi.agree(formData.value.id)
145+
})
146+
break
147+
case 'disagree':
148+
updateAuditReasonFormRef.value?.open(formData.value)
149+
break
150+
case 'receive':
151+
message.confirm('是否确认收货?').then(() => {
152+
AfterSaleApi.receive(formData.value.id)
153+
})
154+
break
155+
case 'refuse':
156+
message.confirm('是否拒绝收货?').then(() => {
157+
AfterSaleApi.refuse(formData.value.id)
158+
})
159+
break
160+
case 'refund':
161+
message.confirm('是否确认退款?').then(() => {
162+
AfterSaleApi.refund(formData.value.id)
163+
})
164+
break
165+
}
166+
}
167+
onMounted(async () => {
168+
await getDetail()
169+
})
170+
</script>
171+
<style lang="scss" scoped>
172+
:deep(.el-descriptions) {
173+
&:not(:nth-child(1)) {
174+
margin-top: 20px;
175+
}
176+
177+
.el-descriptions__title {
178+
display: flex;
179+
align-items: center;
180+
181+
&::before {
182+
display: inline-block;
183+
width: 3px;
184+
height: 20px;
185+
margin-right: 10px;
186+
background-color: #409eff;
187+
content: '';
188+
}
189+
}
190+
191+
.el-descriptions-item__container {
192+
margin: 0 10px;
193+
194+
.no-colon {
195+
margin: 0;
196+
197+
&::after {
198+
content: '';
199+
}
200+
}
201+
}
202+
}
203+
</style>

0 commit comments

Comments
 (0)