|
|
@@ -1,4 +1,4 @@
|
|
|
-import dayjs from "dayjs";
|
|
|
+import dayjs from "dayjs";
|
|
|
import { message } from "@/utils/message";
|
|
|
import { addDialog } from "@/components/ReDialog";
|
|
|
import type { PaginationProps } from "@pureadmin/table";
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
getOrderById,
|
|
|
refundOrder
|
|
|
} from "@/api/order";
|
|
|
-import { type Ref, ref, toRaw, reactive, onMounted } from "vue";
|
|
|
+import { type Ref, ref, computed, reactive, onMounted } from "vue";
|
|
|
import {
|
|
|
ElForm,
|
|
|
ElFormItem,
|
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
ElTableColumn,
|
|
|
ElMessageBox
|
|
|
} from "element-plus";
|
|
|
-import type { OrderItem, OrderSearchForm } from "./types";
|
|
|
+import type { OrderItem, OrderSearchForm, RefundProductItem } from "./types";
|
|
|
import {
|
|
|
initPagination,
|
|
|
handlePageSizeChange,
|
|
|
@@ -104,7 +104,6 @@ export function useOrder(tableRef: Ref) {
|
|
|
prop: "paidAmount",
|
|
|
minWidth: 100,
|
|
|
formatter: ({ paidAmount, totalAmount, discountAmount }) => {
|
|
|
- // 优先使用paidAmount,如果没有则计算
|
|
|
const amount = paidAmount || (totalAmount || 0) - (discountAmount || 0);
|
|
|
return `¥${amount}`;
|
|
|
},
|
|
|
@@ -194,7 +193,6 @@ export function useOrder(tableRef: Ref) {
|
|
|
}
|
|
|
];
|
|
|
|
|
|
- // 搜索
|
|
|
async function onSearch() {
|
|
|
loading.value = true;
|
|
|
try {
|
|
|
@@ -203,7 +201,6 @@ export function useOrder(tableRef: Ref) {
|
|
|
pageSize: pagination.pageSize
|
|
|
};
|
|
|
|
|
|
- // 只添加非空的搜索条件
|
|
|
if (form.orderNo) searchParams.orderNo = form.orderNo;
|
|
|
if (form.deviceId) searchParams.deviceId = form.deviceId;
|
|
|
if (form.payStatus) searchParams.payStatus = form.payStatus;
|
|
|
@@ -223,9 +220,7 @@ export function useOrder(tableRef: Ref) {
|
|
|
startDate?: string;
|
|
|
endDate?: string;
|
|
|
});
|
|
|
- // @ts-ignore - data 是 PageResult 结构
|
|
|
dataList.value = data.list || [];
|
|
|
- // @ts-ignore - data 是 PageResult 结构
|
|
|
pagination.total = Number(data.total) || 0;
|
|
|
} catch (error) {
|
|
|
console.error("获取订单列表失败:", error);
|
|
|
@@ -238,14 +233,12 @@ export function useOrder(tableRef: Ref) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 重置表单
|
|
|
const resetForm = formEl => {
|
|
|
if (!formEl) return;
|
|
|
formEl.resetFields();
|
|
|
resetPagination(pagination, onSearch);
|
|
|
};
|
|
|
|
|
|
- // 分页
|
|
|
function handleSizeChange(val: number) {
|
|
|
handlePageSizeChange(val, pagination, onSearch);
|
|
|
}
|
|
|
@@ -254,26 +247,22 @@ export function useOrder(tableRef: Ref) {
|
|
|
handleCurrentPageChange(val, pagination, onSearch);
|
|
|
}
|
|
|
|
|
|
- // 查看详情
|
|
|
async function handleDetail(row: OrderItem) {
|
|
|
try {
|
|
|
const { data } = await getOrderById(row.id);
|
|
|
const order = data;
|
|
|
|
|
|
- // 支付状态样式
|
|
|
const payStatusStyle = order.payStatus === "PAID"
|
|
|
? "success" : order.payStatus === "REFUND"
|
|
|
? "danger" : "warning";
|
|
|
const payStatusText = order.payStatusLabel || (order.payStatus === "PAID" ? "已支付" : order.payStatus === "REFUND" ? "已退款" : "待支付");
|
|
|
|
|
|
- // 订单状态样式
|
|
|
const statusStyle = order.status === 1
|
|
|
? "success" : order.status === 3
|
|
|
? "danger" : order.status === 2
|
|
|
? "info" : "warning";
|
|
|
const statusText = order.statusText || (order.status === 0 ? "待支付" : order.status === 1 ? "已完成" : order.status === 2 ? "已取消" : "已退款");
|
|
|
|
|
|
- // 支付方式样式
|
|
|
const payTypeMap: Record<string, { text: string; type: string }> = {
|
|
|
"wechat": { text: "微信支付", type: "success" },
|
|
|
"alipay": { text: "支付宝", type: "primary" },
|
|
|
@@ -289,7 +278,6 @@ export function useOrder(tableRef: Ref) {
|
|
|
closeOnClickModal: false,
|
|
|
contentRenderer: () => (
|
|
|
<div style="max-height: 70vh; overflow-y: auto; padding: 0 4px;">
|
|
|
- {/* 基本信息 */}
|
|
|
<div style="font-size: 15px; font-weight: 600; margin-bottom: 10px; color: var(--el-text-color-primary);">基本信息</div>
|
|
|
<ElDescriptions column={3} border size="small">
|
|
|
<ElDescriptionsItem label="订单编号" span={2}>{order.orderNo}</ElDescriptionsItem>
|
|
|
@@ -300,7 +288,6 @@ export function useOrder(tableRef: Ref) {
|
|
|
<ElDescriptionsItem label="用户 ID">{order.userId || "-"}</ElDescriptionsItem>
|
|
|
</ElDescriptions>
|
|
|
|
|
|
- {/* 金额与状态 */}
|
|
|
<div style="font-size: 15px; font-weight: 600; margin: 14px 0 10px; color: var(--el-text-color-primary);">金额与状态</div>
|
|
|
<ElDescriptions column={3} border size="small">
|
|
|
<ElDescriptionsItem label="订单金额">
|
|
|
@@ -329,7 +316,6 @@ export function useOrder(tableRef: Ref) {
|
|
|
<ElDescriptionsItem label="支付时间" span={2}>{order.payTime ? dayjs(order.payTime).format("YYYY-MM-DD HH:mm:ss") : "-"}</ElDescriptionsItem>
|
|
|
</ElDescriptions>
|
|
|
|
|
|
- {/* 视频与置信度 */}
|
|
|
{(order.videoUrl || order.confidence) && (
|
|
|
<>
|
|
|
<div style="font-size: 15px; font-weight: 600; margin: 14px 0 10px; color: var(--el-text-color-primary);">其他信息</div>
|
|
|
@@ -346,7 +332,6 @@ export function useOrder(tableRef: Ref) {
|
|
|
</>
|
|
|
)}
|
|
|
|
|
|
- {/* 订单商品 */}
|
|
|
{order.products && order.products.length > 0 && (
|
|
|
<>
|
|
|
<div style="font-size: 15px; font-weight: 600; margin: 14px 0 10px; color: var(--el-text-color-primary);">订单商品</div>
|
|
|
@@ -402,45 +387,159 @@ export function useOrder(tableRef: Ref) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 退款
|
|
|
- const refundForm = reactive({ reason: "" });
|
|
|
-
|
|
|
async function handleRefund(row: OrderItem) {
|
|
|
if (row.status !== 1) {
|
|
|
message("只有已完成的订单才能退款", { type: "warning" });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- refundForm.reason = "";
|
|
|
+ const refundState = reactive({
|
|
|
+ loadingDetail: true,
|
|
|
+ orderDetail: null as any,
|
|
|
+ selectedProducts: [] as number[],
|
|
|
+ refundQuantities: {} as Record<number, number>,
|
|
|
+ reason: ""
|
|
|
+ });
|
|
|
+
|
|
|
+ const refundFormRef = ref();
|
|
|
+
|
|
|
+ const refundTotalAmount = computed(() => {
|
|
|
+ if (!refundState.orderDetail?.products) return 0;
|
|
|
+ return refundState.selectedProducts.reduce((sum, i) => {
|
|
|
+ const p = refundState.orderDetail.products[i];
|
|
|
+ return sum + (p.price || 0) * (refundState.refundQuantities[i] || 1);
|
|
|
+ }, 0);
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ const { data } = await getOrderById(row.id);
|
|
|
+ refundState.orderDetail = data;
|
|
|
+ if (data.products && data.products.length > 0) {
|
|
|
+ data.products.forEach((p: any, i: number) => {
|
|
|
+ refundState.selectedProducts.push(i);
|
|
|
+ refundState.refundQuantities[i] = p.productNum || 1;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ message("加载订单商品失败", { type: "error" });
|
|
|
+ } finally {
|
|
|
+ refundState.loadingDetail = false;
|
|
|
+ }
|
|
|
|
|
|
addDialog({
|
|
|
title: `订单退款 - ${row.orderNo}`,
|
|
|
- width: "30%",
|
|
|
+ width: "56%",
|
|
|
draggable: true,
|
|
|
closeOnClickModal: false,
|
|
|
fullscreen: deviceDetection(),
|
|
|
contentRenderer: () => (
|
|
|
- <ElForm ref={ruleFormRef} model={refundForm} label-width="80px">
|
|
|
- <ElFormItem
|
|
|
- label="退款原因"
|
|
|
- prop="reason"
|
|
|
- rules={[{ required: true, message: "请输入退款原因", trigger: "blur" }]}
|
|
|
- >
|
|
|
- <ElInput
|
|
|
- v-model={refundForm.reason}
|
|
|
- type="textarea"
|
|
|
- placeholder="请输入退款原因"
|
|
|
- rows={3}
|
|
|
- />
|
|
|
- </ElFormItem>
|
|
|
- </ElForm>
|
|
|
+ <div style="max-height: 60vh; overflow-y: auto; padding: 0 4px;">
|
|
|
+ {refundState.loadingDetail ? (
|
|
|
+ <div style="text-align: center; padding: 40px; color: #999;">正在加载订单商品...</div>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <div style="font-size: 15px; font-weight: 600; margin-bottom: 10px; color: var(--el-text-color-primary);">退款商品</div>
|
|
|
+ <ElTable data={refundState.orderDetail?.products || []} border size="small" style="width: 100%">
|
|
|
+ <ElTableColumn width={55} align="center"
|
|
|
+ v-slots={{
|
|
|
+ default: ({ $index }: any) => (
|
|
|
+ <el-checkbox
|
|
|
+ model-value={refundState.selectedProducts.includes($index)}
|
|
|
+ onChange={(val: boolean) => {
|
|
|
+ if (val) {
|
|
|
+ refundState.selectedProducts.push($index);
|
|
|
+ if (!refundState.refundQuantities[$index]) {
|
|
|
+ refundState.refundQuantities[$index] = refundState.orderDetail.products[$index].productNum || 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const idx = refundState.selectedProducts.indexOf($index);
|
|
|
+ if (idx > -1) refundState.selectedProducts.splice(idx, 1);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ <ElTableColumn prop="productName" label="商品名称" min-width={140} />
|
|
|
+ <ElTableColumn label="单价" width={90} align="right"
|
|
|
+ v-slots={{ default: ({ row: r }: any) => <span>¥{(r.price || 0).toFixed(2)}</span> }}
|
|
|
+ />
|
|
|
+ <ElTableColumn label="购买数量" width={90} align="center"
|
|
|
+ v-slots={{ default: ({ row: r }: any) => <span>×{r.productNum || 1}</span> }}
|
|
|
+ />
|
|
|
+ <ElTableColumn label="退款数量" width={160} align="center"
|
|
|
+ v-slots={{
|
|
|
+ default: ({ $index, row: r }: any) => (
|
|
|
+ refundState.selectedProducts.includes($index) ? (
|
|
|
+ <el-input-number
|
|
|
+ model-value={refundState.refundQuantities[$index] || 1}
|
|
|
+ min={1}
|
|
|
+ max={r.productNum || 1}
|
|
|
+ size="small"
|
|
|
+ controls-position="right"
|
|
|
+ style="width: 120px"
|
|
|
+ onUpdate:modelValue={(val: number) => { refundState.refundQuantities[$index] = val; }}
|
|
|
+ />
|
|
|
+ ) : <span style="color: #999">-</span>
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ <ElTableColumn label="退款金额" width={110} align="right"
|
|
|
+ v-slots={{
|
|
|
+ default: ({ $index, row: r }: any) => (
|
|
|
+ refundState.selectedProducts.includes($index) ? (
|
|
|
+ <span style="color: #e6a23c; font-weight: 600; font-size: 14px;">
|
|
|
+ ¥{((r.price || 0) * (refundState.refundQuantities[$index] || 1)).toFixed(2)}
|
|
|
+ </span>
|
|
|
+ ) : <span style="color: #999">¥0.00</span>
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </ElTable>
|
|
|
+
|
|
|
+ <div style="text-align: right; padding: 14px 0; font-size: 15px; border-bottom: 1px solid #eee; margin-bottom: 12px;">
|
|
|
+ 退款总金额:
|
|
|
+ <span style="color: #f56c6c; font-weight: 700; font-size: 18px;">¥{refundTotalAmount.value.toFixed(2)}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style="font-size: 15px; font-weight: 600; margin-bottom: 10px; color: var(--el-text-color-primary);">退款原因</div>
|
|
|
+ <ElForm ref={refundFormRef} model={refundState}>
|
|
|
+ <ElFormItem
|
|
|
+ prop="reason"
|
|
|
+ rules={[{ required: true, message: "请输入退款原因", trigger: "blur" }]}
|
|
|
+ >
|
|
|
+ <ElInput
|
|
|
+ v-model={refundState.reason}
|
|
|
+ type="textarea"
|
|
|
+ placeholder="请输入退款原因"
|
|
|
+ rows={3}
|
|
|
+ />
|
|
|
+ </ElFormItem>
|
|
|
+ </ElForm>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
),
|
|
|
beforeSure: async (done) => {
|
|
|
- const valid = await ruleFormRef.value.validate().catch(() => false);
|
|
|
+ if (refundState.selectedProducts.length === 0) {
|
|
|
+ message("请选择至少一件退款商品", { type: "warning" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const valid = await refundFormRef.value?.validate().catch(() => false);
|
|
|
if (!valid) return;
|
|
|
|
|
|
+ const products: RefundProductItem[] = refundState.selectedProducts.map((i) => {
|
|
|
+ const p = refundState.orderDetail.products[i];
|
|
|
+ return {
|
|
|
+ productId: p.productId || p.id,
|
|
|
+ productName: p.productName,
|
|
|
+ quantity: refundState.refundQuantities[i] || 1,
|
|
|
+ price: p.price || 0
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
try {
|
|
|
- const res = await refundOrder(row.id, { reason: refundForm.reason });
|
|
|
+ const res = await refundOrder(row.id, { reason: refundState.reason, products });
|
|
|
if (res.code === 200) {
|
|
|
message(`订单 ${row.orderNo} 退款成功`, { type: "success" });
|
|
|
done();
|
|
|
@@ -472,4 +571,4 @@ export function useOrder(tableRef: Ref) {
|
|
|
handleSizeChange,
|
|
|
handleCurrentChange
|
|
|
};
|
|
|
-}
|
|
|
+}
|