| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806 |
- 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<OrderSearchForm>({
- orderNo: "",
- phone: "",
- deviceId: "",
- payStatus: "",
- status: "",
- startDate: "",
- endDate: ""
- });
- const formRef = ref();
- const ruleFormRef = ref();
- const dataList = ref([]);
- const loading = ref(true);
- const pagination = reactive<PaginationProps>(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 <el-tag type="success" size="small">-¥{amount}</el-tag>;
- }
- return <span class="text-gray-400">¥0.00</span>;
- }
- },
- {
- 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 <span class="text-red-600 font-bold">¥{amount}</span>;
- }
- },
- {
- label: "支付方式",
- prop: "payType",
- minWidth: 90,
- cellRenderer: ({ row }) => {
- const payTypeMap: Record<string, { text: string; type: string }> = {
- "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 <el-tag type={payType.type as any} size="small">{payType.text}</el-tag>;
- }
- },
- {
- label: "支付状态",
- prop: "payStatus",
- minWidth: 85,
- cellRenderer: ({ row }) => {
- const statusMap: Record<string, { label: string; type: string }> = {
- "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 <el-tag type={status.type as any}>{status.label}</el-tag>;
- }
- },
- {
- label: "订单状态",
- prop: "status",
- minWidth: 85,
- cellRenderer: ({ row }) => {
- const statusMap: Record<number, { text: string; type: string }> = {
- 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 <el-tag type={status.type as any}>{status.text}</el-tag>;
- }
- },
- {
- label: "用户标签",
- prop: "userTagLabel",
- minWidth: 90,
- cellRenderer: ({ row }) => {
- if (!row.userTagLabel) return <span>-</span>;
- const tagMap: Record<string, { type: string; label: string }> = {
- "new": { type: "success", label: "新用户" },
- "regular": { type: "", label: "老用户" },
- "frequent": { type: "warning", label: "常客" }
- };
- const tag = tagMap[row.userTag] || { type: "info", label: row.userTagLabel };
- return <el-tag type={tag.type as any} size="small">{tag.label}</el-tag>;
- }
- },
- {
- 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: () => (
- <div>
- <div style="display: flex; gap: 8px; margin-bottom: 12px;">
- <ElInput v-model={queryForm.orderId} placeholder="哈哈订单号" style="flex:1" />
- <ElInput v-model={queryForm.activityId} placeholder="活动ID" style="flex:1" />
- </div>
- </div>
- ),
- 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: () => (
- <pre style="max-height: 60vh; overflow-y: auto; padding: 12px; background: #f5f5f5; border-radius: 8px; font-size: 13px;">
- {JSON.stringify(data, null, 2)}
- </pre>
- ),
- 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<string, { text: string; type: string }> = {
- "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: () => (
- <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>
- <ElDescriptionsItem label="门店名称">{order.shopName || "-"}</ElDescriptionsItem>
- <ElDescriptionsItem label="支付订单号" span={2}>{order.outTradeNo || "-"}</ElDescriptionsItem>
- <ElDescriptionsItem label="设备 ID">{order.deviceId || "-"}</ElDescriptionsItem>
- <ElDescriptionsItem label="活动 ID" span={2}>{order.activityId || "-"}</ElDescriptionsItem>
- <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="订单金额">
- <span>¥{order.totalAmount || "0.00"}</span>
- </ElDescriptionsItem>
- <ElDescriptionsItem label="优惠金额">
- {order.discountAmount > 0 ? (
- <el-tag type="success" size="small">-¥{order.discountAmount}</el-tag>
- ) : (
- <span style="color: #999">¥0.00</span>
- )}
- </ElDescriptionsItem>
- <ElDescriptionsItem label="实付金额">
- <span style="color: #e6a23c; font-weight: 600; font-size: 15px;">¥{order.paidAmount || (order.totalAmount || 0) - (order.discountAmount || 0)}</span>
- </ElDescriptionsItem>
- <ElDescriptionsItem label="支付方式">
- <el-tag type={payType.type as any} size="small">{payType.text}</el-tag>
- </ElDescriptionsItem>
- <ElDescriptionsItem label="支付状态">
- <el-tag type={payStatusStyle as any} size="small">{payStatusText}</el-tag>
- </ElDescriptionsItem>
- <ElDescriptionsItem label="订单状态">
- <el-tag type={statusStyle as any} size="small">{statusText}</el-tag>
- </ElDescriptionsItem>
- <ElDescriptionsItem label="下单时间">{dayjs(order.createTime).format("YYYY-MM-DD HH:mm:ss")}</ElDescriptionsItem>
- <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>
- <ElDescriptions column={3} border size="small">
- {order.videoUrl && (
- <ElDescriptionsItem label="支付视频" span={3}>
- <video src={order.videoUrl} controls style="width: 100%; max-width: 360px; max-height: 200px;" />
- </ElDescriptionsItem>
- )}
- {order.confidence && (
- <ElDescriptionsItem label="置信度">{(order.confidence * 100).toFixed(2)}%</ElDescriptionsItem>
- )}
- </ElDescriptions>
- </>
- )}
-
- {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>
- <ElTable data={order.products} border size="small" style="width: 100%">
- <ElTableColumn prop="productName" label="商品名称" min-width={140} />
- <ElTableColumn
- prop="pic"
- label="图片"
- width={70}
- align="center"
- v-slots={{
- default: ({ row }: any) => row.pic ? (
- <el-image src={row.pic} style="width: 40px; height: 40px" fit="cover" preview-teleported={true} preview-src-list={[row.pic]} />
- ) : <span style="color: #999">-</span>
- }}
- />
- <ElTableColumn
- label="单价"
- width={90}
- align="right"
- v-slots={{
- default: ({ row }: any) => <span>¥{row.price || "0.00"}</span>
- }}
- />
- <ElTableColumn
- label="数量"
- width={60}
- align="center"
- v-slots={{
- default: ({ row }: any) => <span>×{row.productNum || 1}</span>
- }}
- />
- <ElTableColumn
- label="小计"
- width={100}
- align="right"
- v-slots={{
- default: ({ row }: any) => {
- const subtotal = row.price && row.productNum ? (row.price * row.productNum) : 0;
- return <span style="font-weight: 500">¥{subtotal.toFixed(2)}</span>;
- }
- }}
- />
- </ElTable>
- </>
- )}
- </div>
- ),
- 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<number, number>,
- 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: () => (
- <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, row: r }: any) => (
- <el-checkbox
- model-value={refundState.selectedProducts.includes($index)}
- disabled={(r.refundedQuantity || 0) >= (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);
- }
- }}
- />
- )
- }}
- />
- <ElTableColumn label="商品名称" min-width={160}>
- {{
- default: ({ row: r }: any) => (
- <div style="display: flex; align-items: center; gap: 8px;">
- {r.pic ? (
- <ElImage
- src={r.pic}
- style="width: 40px; height: 40px; border-radius: 4px; flex-shrink: 0;"
- fit="cover"
- >
- {{
- error: () => (
- <div style="width: 40px; height: 40px; background: #f5f5f5; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #999;">暂无</div>
- )
- }}
- </ElImage>
- ) : (
- <div style="width: 40px; height: 40px; background: #f5f5f5; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #999; flex-shrink: 0;">暂无</div>
- )}
- <div>
- <span>{r.productName}</span>
- {(r.refundedQuantity || 0) > 0 && (
- <span style="color: var(--el-color-warning); font-size: 12px; margin-left: 6px;">(已退{r.refundedQuantity}件)</span>
- )}
- </div>
- </div>
- )
- }}
- </ElTableColumn>
- <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) - (r.refundedQuantity || 0)}
- 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>
- <div style="margin-bottom: 16px; padding: 12px; background: #fafafa; border-radius: 6px;">
- {refundState.selectedProducts.length > 0 ? (
- <div style="color: #909399; font-size: 13px;">
- 已选择商品退款,退款金额根据所选商品自动计算。如需自定义金额,请先取消所有商品选择。
- </div>
- ) : (
- <div>
- <el-input-number
- model-value={refundState.customAmount}
- min={0.01}
- max={refundState.orderDetail?.paidAmount || refundState.orderDetail?.totalAmount || 0}
- precision={2}
- placeholder="输入自定义退款金额"
- style="width: 100%"
- controls-position="right"
- onUpdate:modelValue={(val: number | null) => { refundState.customAmount = val; }}
- />
- <div style="color: #909399; font-size: 12px; margin-top: 6px;">
- 退款金额上限:实付金额 ¥
- {((refundState.orderDetail?.paidAmount || refundState.orderDetail?.totalAmount || 0)).toFixed(2)}
- ,若不选择商品也未输入金额,则全额退款
- </div>
- </div>
- )}
- </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) => {
- // 校验:必须选择商品或输入自定义金额(都不选则全额退款)
- 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: () => (
- <div>
- <div style="margin-bottom: 12px;">
- <div style="margin-bottom: 4px; font-size: 13px; color: var(--el-text-color-regular);">
- 订单ID 或 订单编号 <span style="color: #f56c6c;">*</span>
- </div>
- <ElInput v-model={repairForm.orderId} placeholder="如 202607081727441260270668" />
- </div>
- <div>
- <div style="margin-bottom: 4px; font-size: 13px; color: var(--el-text-color-regular);">
- outOrderNo(选填)
- </div>
- <ElInput v-model={repairForm.outOrderNo} placeholder="如 PS1782351340B150534,本地丢失时从微信商户后台获取" />
- <div style="margin-top: 4px; font-size: 12px; color: #909399;">
- 仅在本地 payScoreOrderId 丢失时填写,从微信商户后台「支付分订单」中查找
- </div>
- </div>
- </div>
- ),
- 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
- };
- }
|