|
|
@@ -1,9 +1,12 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, h } from 'vue';
|
|
|
-import { Avatar, Image, Radio, Button, Input } from 'ant-design-vue';
|
|
|
+import { ref, computed, h, onMounted } from 'vue';
|
|
|
+import { Avatar, Image, Radio, Button, Input, message } from 'ant-design-vue';
|
|
|
import { VxeUI } from 'vxe-pc-ui';
|
|
|
import Negotiations from './Negotiations.vue';
|
|
|
import LogisticsDetails from './LogisticsDetails.vue';
|
|
|
+import type { AfterSaleModel } from '@/model/order.model';
|
|
|
+import { getAfterSaleDetailMethod, getAftersaleReviewMethod } from '@/request/api/order.api';
|
|
|
+
|
|
|
const props = defineProps<{
|
|
|
data?: any;
|
|
|
}>();
|
|
|
@@ -13,6 +16,23 @@ const emit = defineEmits<{
|
|
|
'close': [];
|
|
|
}>();
|
|
|
|
|
|
+// 售后详情数据
|
|
|
+const detailData = ref<AfterSaleModel>();
|
|
|
+const loading = ref(false);
|
|
|
+
|
|
|
+// 获取售后详情
|
|
|
+onMounted(async () => {
|
|
|
+ if (props.data?.id) {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await getAfterSaleDetailMethod(props.data.id);
|
|
|
+ detailData.value = res as any;
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
// 是否收起协商历史
|
|
|
const isNegotiationCollapsed = ref(false);
|
|
|
|
|
|
@@ -20,22 +40,42 @@ const isNegotiationCollapsed = ref(false);
|
|
|
const reviewStatus = ref('agree');
|
|
|
// 理由
|
|
|
const reviewReason = ref('');
|
|
|
+const reasonValidated = ref(false);
|
|
|
|
|
|
-// 模拟顶部的商品数据
|
|
|
+// 商品信息
|
|
|
const productInfo = computed(() => {
|
|
|
+ const d = detailData.value;
|
|
|
return {
|
|
|
- name: props.data?.productName || '肝血虚穴位贴',
|
|
|
- price: 30,
|
|
|
- qty: 8,
|
|
|
- totalPrice: 240,
|
|
|
- specs: '10贴',
|
|
|
- imageUrl: 'https://img.yzcdn.cn/vant/cat.jpeg'
|
|
|
+ name: d?.conditioningProgramName || '',
|
|
|
+ price: d?.unitPrice || 0,
|
|
|
+ qty: d?.totalMeasure || 0,
|
|
|
+ totalPrice: d?.totalPrice || 0,
|
|
|
+ specs: d?.convertDose ? `${d.convertDose}${d.convertUnit || ''}` : '',
|
|
|
+ imageUrl: d?.conditioningProgramPhoto || '',
|
|
|
};
|
|
|
});
|
|
|
|
|
|
// 处理提交
|
|
|
-function handleConfirm() {
|
|
|
- emit('submit', reviewStatus.value, reviewReason.value);
|
|
|
+const submitting = ref(false);
|
|
|
+async function handleConfirm() {
|
|
|
+ if (submitting.value) return;
|
|
|
+ if (!detailData.value?.id) return;
|
|
|
+ if (reviewStatus.value === 'reject' && !reviewReason.value.trim()) {
|
|
|
+ reasonValidated.value = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ submitting.value = true;
|
|
|
+ try {
|
|
|
+ await getAftersaleReviewMethod({
|
|
|
+ id: detailData.value.id,
|
|
|
+ isAgree: reviewStatus.value === 'agree',
|
|
|
+ reason: reviewStatus.value === 'reject' ? reviewReason.value : '',
|
|
|
+ });
|
|
|
+ message.success('审核成功');
|
|
|
+ emit('submit', reviewStatus.value, reviewReason.value);
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 查看物流
|
|
|
@@ -50,7 +90,7 @@ function viewLogistics() {
|
|
|
slots: {
|
|
|
default() {
|
|
|
return h(LogisticsDetails, {
|
|
|
- data: props.data
|
|
|
+ data: detailData.value
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
@@ -64,11 +104,11 @@ function handleCancel() {
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
- <div class="review-page-container">
|
|
|
+ <div class="review-page-container" v-if="!loading">
|
|
|
<!-- 商品信息头部 -->
|
|
|
<div class="product-info-section">
|
|
|
<div class="product-card">
|
|
|
- <Image :src="productInfo.imageUrl" :width="70" :height="70" class="product-image" />
|
|
|
+ <Image v-if="productInfo.imageUrl" :src="productInfo.imageUrl" :width="70" :height="70" class="product-image" />
|
|
|
<div class="product-details">
|
|
|
<div class="detail-row header-row">
|
|
|
<span class="product-name">{{ productInfo.name }}</span>
|
|
|
@@ -95,42 +135,41 @@ function handleCancel() {
|
|
|
<div class="info-list">
|
|
|
<div class="info-item">
|
|
|
<span class="label">申请时间:</span>
|
|
|
- <span class="value">2025-02-19 14:30:27</span>
|
|
|
+ <span class="value">{{ detailData?.applyTime }}</span>
|
|
|
</div>
|
|
|
- <div class="info-item">
|
|
|
- <span class="label">商品状态:</span>
|
|
|
- <span class="value">未收货</span>
|
|
|
- </div>
|
|
|
- <div class="info-item methods-item">
|
|
|
- <span class="label">发货方式:</span>
|
|
|
- <span class="value">线下取货</span>
|
|
|
- <span class="label method-label">发货方式:</span>
|
|
|
- <span class="value">配送</span>
|
|
|
- <a href="javascript:;" class="shipping-link" @click="viewLogistics">运输中</a>
|
|
|
+ <div class="info-item" style="gap: 20px;">
|
|
|
+ <span>
|
|
|
+ <span class="label">商品状态:</span>
|
|
|
+ <span class="value">{{ detailData?.receiptStatusStr }}</span>
|
|
|
+ </span>
|
|
|
+ <span>
|
|
|
+ <span class="label">发货方式:</span>
|
|
|
+ <span class="value">{{ detailData?.receiptTypeStr }}</span>
|
|
|
+ </span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">退款类型:</span>
|
|
|
- <span class="value">退款</span>
|
|
|
+ <span class="value">{{ detailData?.typeStr }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">退款金额:</span>
|
|
|
- <span class="value amount-value">¥240.00</span>
|
|
|
+ <span class="value amount-value">¥{{ detailData?.applyAmount }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">退款原因:</span>
|
|
|
- <span class="value">少件(含缺少配件)</span>
|
|
|
+ <span class="value">{{ detailData?.reason }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">退款说明:</span>
|
|
|
- <span class="value">111</span>
|
|
|
+ <span class="value">{{ detailData?.remark }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 凭证图片 -->
|
|
|
- <div class="proof-images">
|
|
|
+ <div class="proof-images" v-if="detailData?.voucherImgs?.length">
|
|
|
<Image.PreviewGroup>
|
|
|
- <div v-for="i in 5" :key="i" class="image-wrapper">
|
|
|
- <Image :src="`https://img.yzcdn.cn/vant/cat.jpeg#${i}`" :width="40" :height="40" />
|
|
|
+ <div v-for="(img, index) in detailData.voucherImgs" :key="index" class="image-wrapper">
|
|
|
+ <Image :src="img" :width="40" :height="40" />
|
|
|
</div>
|
|
|
</Image.PreviewGroup>
|
|
|
</div>
|
|
|
@@ -148,7 +187,7 @@ function handleCancel() {
|
|
|
</a>
|
|
|
</div>
|
|
|
<div class="negotiation-content" v-show="!isNegotiationCollapsed">
|
|
|
- <Negotiations />
|
|
|
+ <Negotiations :data="detailData" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -166,16 +205,18 @@ function handleCancel() {
|
|
|
</Radio.Group>
|
|
|
</div>
|
|
|
<div class="reason-row" v-if="reviewStatus === 'reject'">
|
|
|
- <div class="reason-label">拒绝理由</div>
|
|
|
- <Input.TextArea
|
|
|
- v-model:value="reviewReason"
|
|
|
- placeholder="请输入拒绝退款的理由"
|
|
|
- :rows="4" />
|
|
|
+ <div class="reason-label">拒绝理由<span style="color: #ff4d4f;"> *</span></div>
|
|
|
+ <Input.TextArea
|
|
|
+ v-model:value="reviewReason"
|
|
|
+ placeholder="请输入拒绝退款的理由"
|
|
|
+ :rows="4"
|
|
|
+ :status="reviewReason.trim() === '' && reasonValidated ? 'error' : undefined" />
|
|
|
+ <div v-if="reviewReason.trim() === '' && reasonValidated" class="reason-error">请输入拒绝理由</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="action-buttons">
|
|
|
<Button @click="handleCancel">取消</Button>
|
|
|
- <Button type="primary" @click="handleConfirm" class="confirm-btn">确定</Button>
|
|
|
+ <Button type="primary" :loading="submitting" @click="handleConfirm" class="confirm-btn">确定</Button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -211,7 +252,7 @@ function handleCancel() {
|
|
|
justify-content: space-between;
|
|
|
margin-bottom: 4px;
|
|
|
width: 20%;
|
|
|
-
|
|
|
+
|
|
|
&.header-row {
|
|
|
.product-name {
|
|
|
font-size: 14px;
|
|
|
@@ -360,7 +401,7 @@ function handleCancel() {
|
|
|
flex: 1;
|
|
|
overflow-y: auto;
|
|
|
max-height: 340px;
|
|
|
-
|
|
|
+
|
|
|
:deep(.negotiations-container) {
|
|
|
padding: 5px 0;
|
|
|
}
|
|
|
@@ -390,6 +431,12 @@ function handleCancel() {
|
|
|
margin-bottom: 12px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
+
|
|
|
+ .reason-error {
|
|
|
+ color: #ff4d4f;
|
|
|
+ font-size: 12px;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|