import dayjs from "dayjs"; import { message } from "@/utils/message"; import { addDialog } from "@/components/ReDialog"; import type { PaginationProps } from "@pureadmin/table"; import { deviceDetection } from "@pureadmin/utils"; import { getOrderList, getOrderById, refundOrder, cancelPayScoreOrder, queryOrderFromHaha, repairPayScorePayment } from "@/api/order"; import { type Ref, ref, computed, reactive, onMounted } from "vue"; import { ElForm, ElFormItem, ElInput, ElInputNumber, ElSelect, ElOption, ElDatePicker, ElDescriptions, ElDescriptionsItem, ElImage, ElTable, ElTableColumn, ElMessageBox } from "element-plus"; import type { OrderItem, OrderSearchForm, RefundProductItem } from "./types"; import { initPagination, handlePageSizeChange, handleCurrentPageChange, resetPagination, updatePaginationData } from "@/utils/paginationHelper"; export function useOrder(tableRef: Ref) { const form = reactive({ orderNo: "", phone: "", deviceId: "", payStatus: "", status: "", startDate: "", endDate: "" }); const formRef = ref(); const ruleFormRef = ref(); const dataList = ref([]); const loading = ref(true); const pagination = reactive(initPagination()); const columns: TableColumnList = [ { label: "订单编号", prop: "orderNo", minWidth: 160, formatter: ({ orderNo }) => orderNo || "-" }, { label: "活动ID", prop: "activityId", minWidth: 120, formatter: ({ activityId }) => activityId || "-" }, { label: "支付订单号", prop: "outTradeNo", minWidth: 150, formatter: ({ outTradeNo }) => outTradeNo || "-" }, { label: "用户 ID", prop: "userId", minWidth: 80, formatter: ({ userId }) => userId || "-" }, { label: "手机号", prop: "phone", minWidth: 120, formatter: ({ phone }) => phone || "-" }, { label: "门店名称", prop: "storeName", minWidth: 120, formatter: ({ storeName }) => storeName || "-" }, { label: "设备 ID", prop: "deviceId", minWidth: 120, formatter: ({ deviceId }) => deviceId || "-" }, { label: "订单金额", prop: "totalAmount", minWidth: 100, formatter: ({ totalAmount }) => totalAmount ? `¥${totalAmount}` : "¥0.00" }, { label: "优惠金额", prop: "discountAmount", minWidth: 100, formatter: ({ discountAmount }) => { const amount = discountAmount || 0; return amount > 0 ? `¥${amount}` : "¥0.00"; }, cellRenderer: ({ row }) => { const amount = row.discountAmount || 0; if (amount > 0) { return -¥{amount}; } return ¥0.00; } }, { label: "实付金额", prop: "paidAmount", minWidth: 100, formatter: ({ paidAmount, totalAmount, discountAmount }) => { const amount = paidAmount || (totalAmount || 0) - (discountAmount || 0); return `¥${amount}`; }, cellRenderer: ({ row }) => { const amount = row.paidAmount || (row.totalAmount || 0) - (row.discountAmount || 0); return ¥{amount}; } }, { label: "支付方式", prop: "payType", minWidth: 90, cellRenderer: ({ row }) => { const payTypeMap: Record = { "wechat": { text: "微信支付", type: "success" }, "alipay": { text: "支付宝", type: "primary" }, "cash": { text: "现金", type: "warning" }, "free": { text: "免费", type: "info" } }; const payType = payTypeMap[row.payType] || { text: row.payType || "-", type: "info" }; return {payType.text}; } }, { label: "支付状态", prop: "payStatus", minWidth: 85, cellRenderer: ({ row }) => { const statusMap: Record = { "UNPAID": { label: "未支付", type: "warning" }, "PAID": { label: "已支付", type: "success" }, "PARTIAL_REFUND": { label: "部分退款", type: "warning" }, "REFUND": { label: "全额退款", type: "danger" } }; const status = statusMap[row.payStatus] || { label: "未知", type: "info" }; return {status.label}; } }, { label: "订单状态", prop: "status", minWidth: 85, cellRenderer: ({ row }) => { const statusMap: Record = { 0: { text: "待支付", type: "warning" }, 1: { text: "已完成", type: "success" }, 2: { text: "已取消", type: "info" }, 3: { text: "已关闭", type: "info" } }; const status = statusMap[row.status] || { text: "未知", type: "info" }; return {status.text}; } }, { label: "用户标签", prop: "userTagLabel", minWidth: 90, cellRenderer: ({ row }) => { if (!row.userTagLabel) return -; const tagMap: Record = { "new": { type: "success", label: "新用户" }, "regular": { type: "", label: "老用户" }, "frequent": { type: "warning", label: "常客" } }; const tag = tagMap[row.userTag] || { type: "info", label: row.userTagLabel }; return {tag.label}; } }, { label: "下单时间", prop: "createTime", minWidth: 160, formatter: ({ createTime }) => createTime ? dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") : "-" }, { label: "支付时间", prop: "payTime", minWidth: 160, formatter: ({ payTime }) => payTime ? dayjs(payTime).format("YYYY-MM-DD HH:mm:ss") : "-" }, { label: "操作", fixed: "right", width: 150, slot: "operation" } ]; async function onSearch() { loading.value = true; try { const searchParams: any = { page: pagination.currentPage, pageSize: pagination.pageSize }; if (form.orderNo) searchParams.orderNo = form.orderNo; if (form.phone) searchParams.phone = form.phone; if (form.deviceId) searchParams.deviceId = form.deviceId; if (form.payStatus) searchParams.payStatus = form.payStatus; if (form.status !== "") { searchParams.status = parseInt(form.status as string, 10); } if (form.startDate) searchParams.startDate = form.startDate; if (form.endDate) searchParams.endDate = form.endDate; const { data } = await getOrderList(searchParams as { page?: number; pageSize?: number; orderNo?: string; deviceId?: string; payStatus?: string; status?: number; startDate?: string; endDate?: string; }); dataList.value = data.list || []; pagination.total = Number(data.total) || 0; } catch (error) { console.error("获取订单列表失败:", error); dataList.value = []; pagination.total = 0; } finally { setTimeout(() => { loading.value = false; }, 300); } } const resetForm = formEl => { if (!formEl) return; formEl.resetFields(); resetPagination(pagination, onSearch); }; function handleSizeChange(val: number) { handlePageSizeChange(val, pagination, onSearch); } function handleCurrentChange(val: number) { handleCurrentPageChange(val, pagination, onSearch); } // 从哈哈平台同步查询订单 async function handleQueryFromHaha(orderNo?: string, activityId?: string) { const queryForm = reactive({ orderId: orderNo || "", activityId: activityId || "" }); addDialog({ title: "从哈哈平台查询订单", width: "40%", draggable: true, closeOnClickModal: false, contentRenderer: () => (
), beforeSure: async (done) => { try { const { data } = await queryOrderFromHaha({ orderId: queryForm.orderId || undefined, activityId: queryForm.activityId || undefined }); if (data) { message("查询成功,订单信息已返回", { type: "success" }); // 展示查询结果 addDialog({ title: "哈哈平台订单详情", width: "60%", draggable: true, closeOnClickModal: false, contentRenderer: () => (
                  {JSON.stringify(data, null, 2)}
                
), hideFooter: true }); } done(); } catch { message("查询失败", { type: "error" }); } } }); } async function handleDetail(row: OrderItem) { try { const { data } = await getOrderById(row.id); const order = data; const payStatusStyle = order.payStatus === "PAID" ? "success" : order.payStatus === "PARTIAL_REFUND" ? "warning" : order.payStatus === "REFUND" ? "danger" : "warning"; const payStatusText = order.payStatusLabel || ( order.payStatus === "PAID" ? "已支付" : order.payStatus === "PARTIAL_REFUND" ? "部分退款" : order.payStatus === "REFUND" ? "全额退款" : "未支付"); const statusStyle = order.status === 1 ? "success" : order.status === 0 ? "warning" : "info"; const statusText = order.statusText || (order.status === 0 ? "待支付" : order.status === 1 ? "已完成" : order.status === 2 ? "已取消" : "已关闭"); const payTypeMap: Record = { "wechat": { text: "微信支付", type: "success" }, "alipay": { text: "支付宝", type: "primary" }, "cash": { text: "现金", type: "warning" }, "free": { text: "免费", type: "info" } }; const payType = payTypeMap[order.payType] || { text: order.payType || "-", type: "info" }; addDialog({ title: `订单详情`, width: "720px", draggable: true, closeOnClickModal: false, contentRenderer: () => (
基本信息
{order.orderNo} {order.shopName || "-"} {order.outTradeNo || "-"} {order.deviceId || "-"} {order.activityId || "-"} {order.userId || "-"}
金额与状态
¥{order.totalAmount || "0.00"} {order.discountAmount > 0 ? ( -¥{order.discountAmount} ) : ( ¥0.00 )} ¥{order.paidAmount || (order.totalAmount || 0) - (order.discountAmount || 0)} {payType.text} {payStatusText} {statusText} {dayjs(order.createTime).format("YYYY-MM-DD HH:mm:ss")} {order.payTime ? dayjs(order.payTime).format("YYYY-MM-DD HH:mm:ss") : "-"} {(order.videoUrl || order.confidence) && ( <>
其他信息
{order.videoUrl && ( )} {order.confidence && ( {(order.confidence * 100).toFixed(2)}% )} )} {order.products && order.products.length > 0 && ( <>
订单商品
row.pic ? ( ) : - }} /> ¥{row.price || "0.00"} }} /> ×{row.productNum || 1} }} /> { const subtotal = row.price && row.productNum ? (row.price * row.productNum) : 0; return ¥{subtotal.toFixed(2)}; } }} /> )}
), hideFooter: true }); } catch (error) { message("获取订单详情失败", { type: "error" }); } } async function handleRefund(row: OrderItem) { if (row.status !== 1) { message("只有已完成的订单才能退款", { type: "warning" }); return; } // 累计退款已达实付金额则不允许继续退款 const paidAmount = row.paidAmount || row.totalAmount || 0; const refundedAmount = (row as any).refundAmount || 0; if (refundedAmount >= paidAmount) { message("该订单已全额退款", { type: "warning" }); return; } const refundState = reactive({ loadingDetail: true, orderDetail: null as any, selectedProducts: [] as number[], refundQuantities: {} as Record, reason: "", customAmount: null as number | null }); 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) => { // 已全额退款的商品不预选 const refundedQty = p.refundedQuantity || 0; if (refundedQty < (p.productNum || 1)) { refundState.selectedProducts.push(i); refundState.refundQuantities[i] = (p.productNum || 1) - refundedQty; } }); } } catch { message("加载订单商品失败", { type: "error" }); } finally { refundState.loadingDetail = false; } addDialog({ title: `订单退款 - ${row.orderNo}`, width: "40%", draggable: true, closeOnClickModal: false, fullscreen: deviceDetection(), contentRenderer: () => (
{refundState.loadingDetail ? (
正在加载订单商品...
) : ( <>
退款商品
( = (r.productNum || 1)} onChange={(val: boolean) => { if (val) { refundState.selectedProducts.push($index); if (!refundState.refundQuantities[$index]) { const remaining = (r.productNum || 1) - (r.refundedQuantity || 0); refundState.refundQuantities[$index] = Math.max(1, remaining); } } else { const idx = refundState.selectedProducts.indexOf($index); if (idx > -1) refundState.selectedProducts.splice(idx, 1); } }} /> ) }} /> {{ default: ({ row: r }: any) => (
{r.pic ? ( {{ error: () => (
暂无
) }}
) : (
暂无
)}
{r.productName} {(r.refundedQuantity || 0) > 0 && ( (已退{r.refundedQuantity}件) )}
) }}
¥{(r.price || 0).toFixed(2)} }} /> ×{r.productNum || 1} }} /> ( refundState.selectedProducts.includes($index) ? ( { refundState.refundQuantities[$index] = val; }} /> ) : - ) }} /> ( refundState.selectedProducts.includes($index) ? ( ¥{((r.price || 0) * (refundState.refundQuantities[$index] || 1)).toFixed(2)} ) : ¥0.00 ) }} />
退款总金额: ¥{refundTotalAmount.value.toFixed(2)}
自定义退款金额
{refundState.selectedProducts.length > 0 ? (
已选择商品退款,退款金额根据所选商品自动计算。如需自定义金额,请先取消所有商品选择。
) : (
{ refundState.customAmount = val; }} />
退款金额上限:实付金额 ¥ {((refundState.orderDetail?.paidAmount || refundState.orderDetail?.totalAmount || 0)).toFixed(2)} ,若不选择商品也未输入金额,则全额退款
)}
退款原因
)}
), beforeSure: async (done) => { // 校验:必须选择商品或输入自定义金额(都不选则全额退款) if (refundState.selectedProducts.length === 0 && (!refundState.customAmount || refundState.customAmount <= 0)) { // 未选择商品也未输入金额,走全额退款(确认提示) try { await ElMessageBox.confirm( "未选择商品也未输入自定义金额,将对整单实付金额进行全额退款,是否继续?", "确认全额退款", { confirmButtonText: "确认退款", cancelButtonText: "取消", type: "warning" } ); } catch { return; } } const valid = await refundFormRef.value?.validate().catch(() => false); if (!valid) return; // 构建请求体 const refundData: any = { reason: refundState.reason }; if (refundState.selectedProducts.length > 0) { refundData.products = 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 }; }); } else if (refundState.customAmount && refundState.customAmount > 0) { refundData.refundAmount = refundState.customAmount; } // 两者都没有 → 全额退款,不传 products 和 refundAmount try { const res = await refundOrder(row.id, refundData); if (res.code === 200 || res.code === 0) { message(`订单 ${row.orderNo} 退款成功`, { type: "success" }); done(); onSearch(); } else { message(res.message || "退款失败", { type: "error" }); } } catch (error) { message("退款失败", { type: "error" }); } } }); } async function handleRepairPayScore() { const repairForm = reactive({ orderId: "", outOrderNo: "" }); addDialog({ title: "修复支付分扣费", width: "460px", draggable: true, closeOnClickModal: false, contentRenderer: () => (
订单ID 或 订单编号 *
outOrderNo(选填)
仅在本地 payScoreOrderId 丢失时填写,从微信商户后台「支付分订单」中查找
), beforeSure: async (done) => { const orderId = repairForm.orderId.trim(); if (!orderId) { message("请输入订单ID或订单编号", { type: "warning" }); return; } const outOrderNo = repairForm.outOrderNo.trim() || undefined; try { const res = await repairPayScorePayment(orderId, outOrderNo); if (res.code === 200) { message(res.message || "修复完成", { type: "success" }); onSearch(); } else { message(res.message || "修复失败", { type: "error" }); } done(); } catch { message("修复请求失败", { type: "error" }); } } }); } async function handlePayScoreCancel(cancelForm: { outOrderNo: string; reason?: string }) { const outOrderNo = cancelForm.outOrderNo?.trim(); if (!outOrderNo) { message("请输入 outOrderNo", { type: "warning" }); return; } try { const res = await cancelPayScoreOrder({ outOrderNo, reason: cancelForm.reason || undefined }); if (res.code === 200) { message(`支付分订单 ${outOrderNo} 取消成功`, { type: "success" }); } else { message(res.message || "取消失败", { type: "error" }); } } catch (error: any) { message(error?.message || "取消失败", { type: "error" }); } } onMounted(() => { onSearch(); }); return { form, loading, columns, dataList, pagination, onSearch, resetForm, handleDetail, handleRefund, handleRepairPayScore, handlePayScoreCancel, handleQueryFromHaha, handleSizeChange, handleCurrentChange }; }