|
@@ -101,6 +101,14 @@
|
|
|
<el-descriptions-item label="金额">
|
|
<el-descriptions-item label="金额">
|
|
|
{{ fen2yuan(detail.total_amount) }} 元 / 实收 {{ fen2yuan(detail.receipt_amount) }} 元
|
|
{{ fen2yuan(detail.total_amount) }} 元 / 实收 {{ fen2yuan(detail.receipt_amount) }} 元
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
|
|
+ <!--
|
|
|
|
|
+ ★ 已退金额**本来就在详情响应里**(`LsyOrderEntity.refundedAmount`),只是此前没人渲染它:
|
|
|
|
|
+ 状态列写着「已全额退款」而详情里一个字都看不到退款,用户 2026-09-29 报的正是这个。
|
|
|
|
|
+ 这一行不需要后端改动 —— 缺的是显示。
|
|
|
|
|
+ -->
|
|
|
|
|
+ <el-descriptions-item label="已退金额">
|
|
|
|
|
+ {{ fen2yuan(detail.refunded_amount) }} 元
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
<el-descriptions-item label="支付结果">
|
|
<el-descriptions-item label="支付结果">
|
|
|
{{ detail.upstream_result_code }} {{ detail.upstream_result_msg }}
|
|
{{ detail.upstream_result_code }} {{ detail.upstream_result_msg }}
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
@@ -115,6 +123,51 @@
|
|
|
<el-descriptions-item label="备注">{{ detail.description || "-" }}</el-descriptions-item>
|
|
<el-descriptions-item label="备注">{{ detail.description || "-" }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
+ <!--
|
|
|
|
|
+ 退款单只在**有**的时候出现。空表会让每一笔正常支付都拖一节「没有退款」的空架子,
|
|
|
|
|
+ 而这一节存在的意义恰恰是回答「这笔到底退没退、退了哪几笔」。
|
|
|
|
|
+ -->
|
|
|
|
|
+ <template v-if="refunds.length">
|
|
|
|
|
+ <h4 style="margin-top: 20px">退款单</h4>
|
|
|
|
|
+ <el-table :data="refunds" border size="small">
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="out_refund_no"
|
|
|
|
|
+ label="商户退款单号"
|
|
|
|
|
+ min-width="150"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column label="金额(元)" width="90" align="right">
|
|
|
|
|
+ <template #default="{ row }">{{ fen2yuan(row.refund_fee) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="状态" width="90">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="refundTagType(row.status)" size="small">
|
|
|
|
|
+ {{ refundStatusLabel(row.status) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <!--
|
|
|
|
|
+ 上游退款单号可能是空的:它只在**同步应答**里才学到(预插入的那一行还没拿到)。
|
|
|
|
|
+ 空串在这里会看起来像「退款单号丢了」,所以显式渲染成 -。
|
|
|
|
|
+ -->
|
|
|
|
|
+ <el-table-column label="退款单号" min-width="160" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">{{ row.refund_no || "-" }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <!--
|
|
|
|
|
+ 宽度 180 与同抽屉「回调轨迹」的时间列、以及上方订单列表的「创建时间」一致 ——
|
|
|
|
|
+ 这三处显示的是同一种值,宽度不该各写各的。
|
|
|
|
|
+ ★ 值的形态是**实测**的,不是估的:HTTP 层实收
|
|
|
|
|
+ `2026-09-29T11:04:13.007374+08:00`(**微秒** 6 位,共 32 字符)。
|
|
|
|
|
+ 按「毫秒 3 位」估会少算 3 个字符、窄列里静默截断。
|
|
|
|
|
+ 所以这里挂 show-overflow-tooltip:截断至少能悬停看全,不是无声丢字。
|
|
|
|
|
+ 本页**不**引入 dayjs 格式化 —— 同一个抽屉里两节时间长得不一样,比原样显示更难读。
|
|
|
|
|
+ -->
|
|
|
|
|
+ <el-table-column label="完成时间" width="180" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">{{ row.finish_time || "-" }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
<h4 style="margin-top: 20px">回调轨迹</h4>
|
|
<h4 style="margin-top: 20px">回调轨迹</h4>
|
|
|
<el-table :data="notifyLogs" border size="small">
|
|
<el-table :data="notifyLogs" border size="small">
|
|
|
<el-table-column prop="created_time" label="时间" width="180" />
|
|
<el-table-column prop="created_time" label="时间" width="180" />
|
|
@@ -167,6 +220,7 @@ import {
|
|
|
getLsyOrderPage,
|
|
getLsyOrderPage,
|
|
|
getLsyOrderDetail,
|
|
getLsyOrderDetail,
|
|
|
getLsyNotifyLogs,
|
|
getLsyNotifyLogs,
|
|
|
|
|
+ getLsyOrderRefunds,
|
|
|
} from "@/api/module_payment/collection";
|
|
} from "@/api/module_payment/collection";
|
|
|
|
|
|
|
|
const STATUS_OPTIONS = [
|
|
const STATUS_OPTIONS = [
|
|
@@ -213,6 +267,18 @@ const VERIFY_LABEL: Record<string, string> = {
|
|
|
FAIL: "失败",
|
|
FAIL: "失败",
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * `LsyRefundStatus` 的四个取值 → 中文。**逐个取自枚举源码**(`CREATED / REFUNDING / SUCCESS / FAIL`,
|
|
|
|
|
+ * 见 `LsyRefundStatus.java` 的枚举体),措辞与退款列表页 `collection/refund/index.vue` 的下拉一致 ——
|
|
|
|
|
+ * 同一份状态在两个页面叫两个名字,排查时会对不上。
|
|
|
|
|
+ */
|
|
|
|
|
+const REFUND_STATUS_LABEL: Record<string, string> = {
|
|
|
|
|
+ CREATED: "已建单",
|
|
|
|
|
+ REFUNDING: "退款中",
|
|
|
|
|
+ SUCCESS: "退款成功",
|
|
|
|
|
+ FAIL: "退款失败",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
/** 查询参数用 camelCase:后端 `@RequestParam` 未写 name,线上名即 `pageNo` / `pageSize` */
|
|
/** 查询参数用 camelCase:后端 `@RequestParam` 未写 name,线上名即 `pageNo` / `pageSize` */
|
|
|
const query = reactive({
|
|
const query = reactive({
|
|
|
pageNo: 1,
|
|
pageNo: 1,
|
|
@@ -227,6 +293,7 @@ const loading = ref(false);
|
|
|
const detailVisible = ref(false);
|
|
const detailVisible = ref(false);
|
|
|
const detail = ref<any>(null);
|
|
const detail = ref<any>(null);
|
|
|
const notifyLogs = ref<any[]>([]);
|
|
const notifyLogs = ref<any[]>([]);
|
|
|
|
|
+const refunds = ref<any[]>([]);
|
|
|
|
|
|
|
|
function sceneLabel(v: string) {
|
|
function sceneLabel(v: string) {
|
|
|
return SCENE_LABEL[v] || v;
|
|
return SCENE_LABEL[v] || v;
|
|
@@ -253,6 +320,15 @@ function processLabel(v: string) {
|
|
|
function verifyLabel(v: string) {
|
|
function verifyLabel(v: string) {
|
|
|
return VERIFY_LABEL[v] || v || "-";
|
|
return VERIFY_LABEL[v] || v || "-";
|
|
|
}
|
|
}
|
|
|
|
|
+function refundStatusLabel(v: string) {
|
|
|
|
|
+ return REFUND_STATUS_LABEL[v] || v || "-";
|
|
|
|
|
+}
|
|
|
|
|
+/** 在途用 warning、失败用 danger —— 与退款列表页同一套配色,避免同一状态两种颜色。 */
|
|
|
|
|
+function refundTagType(v: string) {
|
|
|
|
|
+ if (v === "SUCCESS") return "success";
|
|
|
|
|
+ if (v === "FAIL") return "danger";
|
|
|
|
|
+ return "warning";
|
|
|
|
|
+}
|
|
|
/**
|
|
/**
|
|
|
* 「处理」列里**需要人看一眼**的四类,标红。
|
|
* 「处理」列里**需要人看一眼**的四类,标红。
|
|
|
*
|
|
*
|
|
@@ -298,6 +374,13 @@ function handleReset() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function openDetail(row: any) {
|
|
async function openDetail(row: any) {
|
|
|
|
|
+ // ★ 先清空、再拉取:抽屉实例是**被复用**的,三条请求里任何一条抛异常,
|
|
|
|
|
+ // 上一次的内容就会原样留在界面上 —— 而它看起来完全像「这一笔订单的数据」。
|
|
|
|
|
+ // (清空放在最前,所以下面三条赋值不再需要各自的兜底 || []。)
|
|
|
|
|
+ detail.value = null;
|
|
|
|
|
+ notifyLogs.value = [];
|
|
|
|
|
+ refunds.value = [];
|
|
|
|
|
+
|
|
|
const res: any = await getLsyOrderDetail(row.out_trade_no);
|
|
const res: any = await getLsyOrderDetail(row.out_trade_no);
|
|
|
// ★ 两层 data:`request.ts` 的响应拦截器 `return response`,**不做解包**,
|
|
// ★ 两层 data:`request.ts` 的响应拦截器 `return response`,**不做解包**,
|
|
|
// 所以 res.data 是整个 `{code,msg,data}` 信封,业务数据在 res.data.data。
|
|
// 所以 res.data 是整个 `{code,msg,data}` 信封,业务数据在 res.data.data。
|
|
@@ -306,6 +389,8 @@ async function openDetail(row: any) {
|
|
|
detail.value = res.data.data;
|
|
detail.value = res.data.data;
|
|
|
const logs: any = await getLsyNotifyLogs(row.out_trade_no);
|
|
const logs: any = await getLsyNotifyLogs(row.out_trade_no);
|
|
|
notifyLogs.value = logs.data.data || [];
|
|
notifyLogs.value = logs.data.data || [];
|
|
|
|
|
+ const rfs: any = await getLsyOrderRefunds(row.out_trade_no);
|
|
|
|
|
+ refunds.value = rfs.data.data || [];
|
|
|
detailVisible.value = true;
|
|
detailVisible.value = true;
|
|
|
}
|
|
}
|
|
|
|
|
|