feat(payment): 完成阶段7订单+支付管理开发(订单支付/汇总订单/原始订单)
parent
a9243039b5
commit
0565ac8dc1
|
|
@ -0,0 +1,17 @@
|
|||
import request from './request'
|
||||
|
||||
// ---------------- 订单管理(/api/original-order/*、/api/summary-order/*) ----------------
|
||||
// 说明:
|
||||
// - `original-order/page` 仅支持 {page,pageSize,originalOrderNo} 过滤,不支持 docx 描述的
|
||||
// 渠道/下单日期范围/订单状态/结算状态/汇总订单编号/店铺ID,已登记于《缺失的后端接口.md》。
|
||||
// - `summary-order/page` 仅支持 {page,pageSize,orderNo} 过滤,不支持商品类型/清算状态/是否已
|
||||
// 推送/订单日期范围/计划清算日/实际清算日范围/贷款人,已登记于《缺失的后端接口.md》。
|
||||
// - `original-order/export` 入参 {summaryOrderId,originalOrderNo} 均可选,响应 data 结构
|
||||
// 未在 swagger 中定义,前端按项目既有约定(参考钱包对账单/回单下载)假定为 {fileData: base64},
|
||||
// 已登记于《缺失的后端接口.md》。
|
||||
// - `summary-order/detail` 响应即 SummaryOrderVo 本身,不含关联原始订单明细列表,已登记于
|
||||
// 《缺失的后端接口.md》。
|
||||
export const originalOrderPageApi = (data) => request.post('/api/original-order/page', data)
|
||||
export const originalOrderExportApi = (data) => request.post('/api/original-order/export', data)
|
||||
export const summaryOrderPageApi = (data) => request.post('/api/summary-order/page', data)
|
||||
export const summaryOrderDetailApi = (data) => request.post('/api/summary-order/detail', data)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import request from './request'
|
||||
|
||||
// ---------------- 支付管理(/api/payment/*、/api/payment-management/*) ----------------
|
||||
// 说明:
|
||||
// - `/api/payment/query-credit-quota`:查询授信额度,入参仅 {accountNo}(钱包账号字符串),
|
||||
// 响应 data 字段在 swagger 中完全未定义具体结构(无 $ref、无 properties),属于未文档化接口,
|
||||
// 前端按"任意存在字段"做防御性展示,已登记于《缺失的后端接口.md》。
|
||||
// - `/api/payment/order-pay`:订单支付,入参 CreatePaymentBto 不含短信验证码字段,短验无法
|
||||
// 真正传递给后端校验,前端仅做本地格式校验(非空+6位数字),已登记于《缺失的后端接口.md》。
|
||||
// - 查询支付记录/详情走 payment-management 组,与创建支付的 payment 组为同一 tag 下的不同前缀。
|
||||
export const queryCreditQuotaApi = (data) => request.post('/api/payment/query-credit-quota', data)
|
||||
export const orderPayApi = (data) => request.post('/api/payment/order-pay', data)
|
||||
export const queryPaymentRecordApi = (data) => request.post('/api/payment-management/query-payment-record', data)
|
||||
export const queryPaymentDetailApi = (data) => request.post('/api/payment-management/query-payment-detail', data)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// src/constants/orderEnums.js
|
||||
// 订单管理相关枚举字典,取值与中文标签均从 swagger 组件 schema 的 description 字段程序化
|
||||
// 提取,未做任何人工编造。
|
||||
|
||||
// OrderTypeEnum 出现在 OriginalOrderVo.orderType 字段上,语义为"该原始订单记录本身的类型标
|
||||
// 记"(与"汇总订单"模块概念同名但用途不同,详见《缺失的后端接口.md》)。
|
||||
export const ORDER_TYPE_OPTIONS = [
|
||||
{ value: 'SUMMARY', label: '汇总订单' },
|
||||
{ value: 'ORIGINAL', label: '原始订单' }
|
||||
]
|
||||
export const ORDER_TYPE_MAP = {
|
||||
SUMMARY: { text: '汇总订单', color: 'blue' },
|
||||
ORIGINAL: { text: '原始订单', color: 'default' }
|
||||
}
|
||||
|
||||
export const SUMMARY_ORDER_STATUS_OPTIONS = [
|
||||
{ value: 'REGISTERED', label: '已登记' },
|
||||
{ value: 'SETTLED', label: '已完结' }
|
||||
]
|
||||
export const SUMMARY_ORDER_STATUS_MAP = {
|
||||
REGISTERED: { text: '已登记', color: 'blue' },
|
||||
SETTLED: { text: '已完结', color: 'green' }
|
||||
}
|
||||
|
||||
export const SETTLEMENT_STATUS_OPTIONS = [
|
||||
{ value: 'UNSCHEDULED', label: '未清算' },
|
||||
{ value: 'SETTLED_REPAID', label: '已清算并还款' },
|
||||
{ value: 'FAILED', label: '清算失败' }
|
||||
]
|
||||
export const SETTLEMENT_STATUS_MAP = {
|
||||
UNSCHEDULED: { text: '未清算', color: 'default' },
|
||||
SETTLED_REPAID: { text: '已清算并还款', color: 'green' },
|
||||
FAILED: { text: '清算失败', color: 'red' }
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// src/constants/paymentEnums.js
|
||||
// 支付管理相关枚举字典,取值与中文标签均从 swagger 组件 schema 的 description 字段程序化
|
||||
// 提取,未做任何人工编造。
|
||||
|
||||
export const PAYMENT_METHOD_OPTIONS = [
|
||||
{ value: 'BALANCE', label: '余额支付' },
|
||||
{ value: 'FINANCE', label: '融资支付' },
|
||||
{ value: 'COMBINED', label: '组合支付' }
|
||||
]
|
||||
export const PAYMENT_METHOD_MAP = {
|
||||
BALANCE: { text: '余额支付', color: 'blue' },
|
||||
FINANCE: { text: '融资支付', color: 'purple' },
|
||||
COMBINED: { text: '组合支付', color: 'cyan' }
|
||||
}
|
||||
|
||||
export const PAYMENT_STATUS_OPTIONS = [
|
||||
{ value: 'PENDING_VERIFICATION', label: '待验证' },
|
||||
{ value: 'VERIFYING', label: '验证中' },
|
||||
{ value: 'PROCESSING', label: '支付中' },
|
||||
{ value: 'SUCCESS', label: '支付成功' },
|
||||
{ value: 'FAILED', label: '支付失败' },
|
||||
{ value: 'CANCELLED', label: '已取消' }
|
||||
]
|
||||
export const PAYMENT_STATUS_MAP = {
|
||||
PENDING_VERIFICATION: { text: '待验证', color: 'default' },
|
||||
VERIFYING: { text: '验证中', color: 'processing' },
|
||||
PROCESSING: { text: '支付中', color: 'processing' },
|
||||
SUCCESS: { text: '支付成功', color: 'green' },
|
||||
FAILED: { text: '支付失败', color: 'red' },
|
||||
CANCELLED: { text: '已取消', color: 'default' }
|
||||
}
|
||||
|
||||
export const FUND_SOURCE_OPTIONS = [
|
||||
{ value: 'BALANCE', label: '余额' },
|
||||
{ value: 'FINANCE', label: '融资' }
|
||||
]
|
||||
export const FUND_SOURCE_MAP = {
|
||||
BALANCE: { text: '余额', color: 'blue' },
|
||||
FINANCE: { text: '融资', color: 'purple' }
|
||||
}
|
||||
|
|
@ -13,7 +13,10 @@ export const routePathComponentMap = {
|
|||
'/wallet/enterprise-open': 'wallet/enterprise-open/EnterpriseOpenList',
|
||||
'/credit/loan-application': 'credit/loan-application/LoanApplicationList',
|
||||
'/merchant/management': 'merchant/management/MerchantList',
|
||||
'/merchant/invoice': 'merchant/invoice/InvoiceList'
|
||||
'/merchant/invoice': 'merchant/invoice/InvoiceList',
|
||||
'/payment/order-payment': 'payment/order-payment/PaymentRecordList',
|
||||
'/order/summary': 'order/summary/SummaryOrderList',
|
||||
'/order/original': 'order/original/OriginalOrderList'
|
||||
}
|
||||
|
||||
// 新增/编辑/查看等表单页不是后端菜单节点(不出现在侧边栏),但仍需要注册为
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
<!-- src/views/order/original/OriginalOrderList.vue -->
|
||||
<!-- 4.2 原始订单:查询按 /api/original-order/page 实际支持的入参(仅 originalOrderNo)实现,
|
||||
不支持 docx 描述的渠道/下单日期范围/订单状态/结算状态/汇总订单编号/店铺ID过滤条件,原型图
|
||||
"需优先选择电商渠道才能加载数据"的前置步骤后端不需要,未实现该门槛;原型多出的"新增/修改"
|
||||
按钮 docx 未提及(原始订单数据来源为电商平台爬虫/文件导入,不在本前端范围新建导入功能),未
|
||||
实现,已登记于《缺失的后端接口.md》。"订单导出"调用 /api/original-order/export,响应结构
|
||||
未在 swagger 中定义,前端按项目既有约定(对账单/回单下载模式)假定返回 {fileData: base64},
|
||||
导出文件按 xlsx 处理,若实际结构不同将导致导出失败,已登记于《缺失的后端接口.md》。 -->
|
||||
<template>
|
||||
<div class="original-order-list-page">
|
||||
<ProTable
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:fetch-data="loadList"
|
||||
:row-selection="false"
|
||||
:initial-search="{ originalOrderNo: '' }"
|
||||
>
|
||||
<template #search="{ form }">
|
||||
<a-form-item label="订单编号">
|
||||
<a-input v-model:value="form.originalOrderNo" placeholder="请输入原始订单编号" allow-clear style="width: 200px" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<a-button :loading="exporting" @click="handleExport">订单导出</a-button>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.dataIndex === 'orderType'">
|
||||
{{ ORDER_TYPE_OPTIONS.find((opt) => opt.value === record.orderType)?.label || record.orderType || '-' }}
|
||||
</template>
|
||||
</template>
|
||||
</ProTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import ProTable from '@/components/ProTable.vue'
|
||||
import { originalOrderPageApi, originalOrderExportApi } from '@/api/order'
|
||||
import { ORDER_TYPE_OPTIONS } from '@/constants/orderEnums'
|
||||
import { downloadBase64File } from '@/utils/download'
|
||||
|
||||
const tableRef = ref(null)
|
||||
const exporting = ref(false)
|
||||
|
||||
const columns = [
|
||||
{ title: '序号', dataIndex: 'index', width: 60 },
|
||||
{ title: '订单编号', dataIndex: 'originalOrderNo' },
|
||||
{ title: '所属汇总订单ID', dataIndex: 'summaryOrderId' },
|
||||
{ title: '商户ID', dataIndex: 'merchantId' },
|
||||
{ title: '订单类型', dataIndex: 'orderType' },
|
||||
{ title: '订单日期', dataIndex: 'orderDate' },
|
||||
{ title: '商品名称', dataIndex: 'productName' },
|
||||
{ title: '商品数量', dataIndex: 'productQuantity' },
|
||||
{ title: '单价', dataIndex: 'unitPrice' },
|
||||
{ title: '订单金额', dataIndex: 'totalAmount' },
|
||||
{ title: '买家名称', dataIndex: 'buyerName' },
|
||||
{ title: '卖家名称', dataIndex: 'sellerName' },
|
||||
{ title: '订单状态', dataIndex: 'orderStatus' },
|
||||
{ title: '备注', dataIndex: 'remark' }
|
||||
]
|
||||
|
||||
async function loadList(params) {
|
||||
const res = await originalOrderPageApi(params)
|
||||
if (res.data.code === 200) {
|
||||
return { list: res.data.data.records || [], total: res.data.data.total || 0 }
|
||||
}
|
||||
return { list: [], total: 0 }
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
const originalOrderNo = tableRef.value?.searchForm?.originalOrderNo
|
||||
exporting.value = true
|
||||
try {
|
||||
const res = await originalOrderExportApi({ originalOrderNo: originalOrderNo || undefined })
|
||||
if (res.data.code === 200) {
|
||||
const fileData = res.data.data?.fileData
|
||||
if (fileData) {
|
||||
downloadBase64File(
|
||||
fileData,
|
||||
`原始订单_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`,
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
} else {
|
||||
message.warning('导出接口未返回可识别的文件内容,响应结构与前端约定不一致,请联系后端确认')
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<!-- src/views/order/summary/SummaryOrderList.vue -->
|
||||
<!-- 4.1 汇总订单:查询按 /api/summary-order/page 实际支持的入参(仅 orderNo)实现,不支持 docx
|
||||
描述的商品类型/清算状态/是否已推送/订单日期范围/计划清算日/实际清算日范围/贷款人过滤条件,
|
||||
已登记于《缺失的后端接口.md》。列表列按 SummaryOrderVo 实际返回字段展示,docx 描述的"预计
|
||||
结算额/可融资金额/已使用额度/剩余额度/超额退款/累计已补差额/推送状态/计划清算日"等字段在
|
||||
真实模型中部分缺失、部分字段名不同(如 financeQuota≈可融资金额、accumulatedLoanAmount≈已
|
||||
使用额度),前端已按最接近的语义映射展示,"剩余额度"由前端用 financeQuota-accumulatedLoanAmount
|
||||
本地计算得出(非后端返回字段)。"查看"操作弹出详情弹窗展示同一 Vo 的完整字段;后端无"关联
|
||||
原始订单明细"查询能力(original-order/page 不支持按 summaryOrderId 过滤),详情弹窗不展示
|
||||
关联原始订单列表,已登记于《缺失的后端接口.md》。 -->
|
||||
<template>
|
||||
<div class="summary-order-list-page">
|
||||
<ProTable ref="tableRef" :columns="columns" :fetch-data="loadList" :row-selection="false" :initial-search="{ orderNo: '' }">
|
||||
<template #search="{ form }">
|
||||
<a-form-item label="订单编号">
|
||||
<a-input v-model:value="form.orderNo" placeholder="请输入汇总订单编号" allow-clear style="width: 200px" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.dataIndex === 'orderStatus'">
|
||||
<StatusTag :value="record.orderStatus" :map="SUMMARY_ORDER_STATUS_MAP" />
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'settlementStatus'">
|
||||
<StatusTag :value="record.settlementStatus" :map="SETTLEMENT_STATUS_MAP" />
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'remainingQuota'">
|
||||
{{ computeRemainingQuota(record) }}
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'action'">
|
||||
<a @click="detailRef.show(record)">查看</a>
|
||||
</template>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
<a-modal v-model:open="detailOpen" title="汇总订单详情" width="800px" :footer="null">
|
||||
<a-descriptions v-if="detailRecord" bordered :column="2" size="small">
|
||||
<a-descriptions-item label="订单编号">{{ detailRecord.orderNo }}</a-descriptions-item>
|
||||
<a-descriptions-item label="商户编号">{{ detailRecord.merchantNo }}</a-descriptions-item>
|
||||
<a-descriptions-item label="订单日期">{{ detailRecord.orderDate }}</a-descriptions-item>
|
||||
<a-descriptions-item label="订单总额">{{ detailRecord.totalAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="已发货未收款总额">{{ detailRecord.shippedUncollectedAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="预计退货总额">{{ detailRecord.estimatedReturnAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="预计应收款总额">{{ detailRecord.estimatedReceivableAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="实际退货总额">{{ detailRecord.actualReturnAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="融资额度(可融资金额)">{{ detailRecord.financeQuota }}</a-descriptions-item>
|
||||
<a-descriptions-item label="累计融资金额(已使用额度)">{{ detailRecord.accumulatedLoanAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="剩余额度(本地计算)">{{ computeRemainingQuota(detailRecord) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="累计补足差额">{{ detailRecord.accumulatedSupplementAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="订单状态">
|
||||
<StatusTag :value="detailRecord.orderStatus" :map="SUMMARY_ORDER_STATUS_MAP" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="清算状态">
|
||||
<StatusTag :value="detailRecord.settlementStatus" :map="SETTLEMENT_STATUS_MAP" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="摘要" :span="2">{{ detailRecord.remark || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间" :span="2">{{ detailRecord.createdAt }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<a-alert
|
||||
style="margin-top: 12px"
|
||||
type="warning"
|
||||
show-icon
|
||||
message="后端暂不支持按汇总订单查询关联的原始订单明细,此处未展示关联原始订单列表,详见《缺失的后端接口.md》"
|
||||
/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import ProTable from '@/components/ProTable.vue'
|
||||
import StatusTag from '@/components/StatusTag.vue'
|
||||
import { summaryOrderPageApi, summaryOrderDetailApi } from '@/api/order'
|
||||
import { SUMMARY_ORDER_STATUS_MAP, SETTLEMENT_STATUS_MAP } from '@/constants/orderEnums'
|
||||
|
||||
const tableRef = ref(null)
|
||||
const detailOpen = ref(false)
|
||||
const detailRecord = ref(null)
|
||||
|
||||
const columns = [
|
||||
{ title: '序号', dataIndex: 'index', width: 60 },
|
||||
{ title: '订单编号', dataIndex: 'orderNo' },
|
||||
{ title: '商户编号', dataIndex: 'merchantNo' },
|
||||
{ title: '订单日期', dataIndex: 'orderDate' },
|
||||
{ title: '订单总额', dataIndex: 'totalAmount' },
|
||||
{ title: '预计应收款总额', dataIndex: 'estimatedReceivableAmount' },
|
||||
{ title: '融资额度', dataIndex: 'financeQuota' },
|
||||
{ title: '累计融资金额', dataIndex: 'accumulatedLoanAmount' },
|
||||
{ title: '剩余额度', dataIndex: 'remainingQuota' },
|
||||
{ title: '累计补足差额', dataIndex: 'accumulatedSupplementAmount' },
|
||||
{ title: '订单状态', dataIndex: 'orderStatus' },
|
||||
{ title: '清算状态', dataIndex: 'settlementStatus' },
|
||||
{ title: '操作', dataIndex: 'action', width: 80, fixed: 'right' }
|
||||
]
|
||||
|
||||
async function loadList(params) {
|
||||
const res = await summaryOrderPageApi(params)
|
||||
if (res.data.code === 200) {
|
||||
return { list: res.data.data.records || [], total: res.data.data.total || 0 }
|
||||
}
|
||||
return { list: [], total: 0 }
|
||||
}
|
||||
|
||||
function computeRemainingQuota(record) {
|
||||
const quota = Number(record.financeQuota)
|
||||
const used = Number(record.accumulatedLoanAmount)
|
||||
if (Number.isNaN(quota) || Number.isNaN(used)) return '-'
|
||||
return (quota - used).toFixed(2)
|
||||
}
|
||||
|
||||
const detailRef = {
|
||||
show: async (record) => {
|
||||
const res = await summaryOrderDetailApi({ id: record.id })
|
||||
detailRecord.value = res.data.code === 200 ? res.data.data : record
|
||||
detailOpen.value = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<!-- src/views/payment/order-payment/PaymentDetailModal.vue -->
|
||||
<!-- 支付详情弹窗,对应 /api/payment-management/query-payment-detail(入参仅 {id}),
|
||||
响应 PaymentRecordDetailVo 在列表字段基础上多一个 detailList(各资金渠道明细)。 -->
|
||||
<template>
|
||||
<a-modal v-model:open="open" title="支付详情" width="640px" :footer="null">
|
||||
<a-descriptions v-if="detail" bordered :column="2" size="small">
|
||||
<a-descriptions-item label="支付方式">
|
||||
<StatusTag :value="detail.paymentMethod" :map="PAYMENT_METHOD_MAP" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="支付状态">
|
||||
<StatusTag :value="detail.status" :map="PAYMENT_STATUS_MAP" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="支付总金额">{{ detail.totalAmount }}</a-descriptions-item>
|
||||
<a-descriptions-item label="凡荣e链流水号">{{ detail.frchainSerialNo || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="余额支付金额">{{ detail.balanceAmount || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="融资支付金额">{{ detail.financeAmount || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="支付完成时间">{{ detail.paidAt || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="失败原因">{{ detail.failedReason || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="收款方户名">{{ detail.payeeInfo?.payeeName || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="收款方账号">{{ detail.payeeInfo?.payeeAccountNo || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="收款方开户行" :span="2">{{ detail.payeeInfo?.payeeBankName || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-divider>支付明细</a-divider>
|
||||
<a-table
|
||||
:columns="detailColumns"
|
||||
:data-source="detail?.detailList || []"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'fundChannel'">
|
||||
<StatusTag :value="record.fundChannel" :map="FUND_SOURCE_MAP" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import StatusTag from '@/components/StatusTag.vue'
|
||||
import { queryPaymentDetailApi } from '@/api/payment'
|
||||
import { PAYMENT_METHOD_MAP, PAYMENT_STATUS_MAP, FUND_SOURCE_MAP } from '@/constants/paymentEnums'
|
||||
|
||||
const open = ref(false)
|
||||
const detail = ref(null)
|
||||
|
||||
const detailColumns = [
|
||||
{ title: '资金渠道', dataIndex: 'fundChannel' },
|
||||
{ title: '渠道金额', dataIndex: 'channelAmount' },
|
||||
{ title: '备注', dataIndex: 'remark' }
|
||||
]
|
||||
|
||||
async function show(record) {
|
||||
detail.value = null
|
||||
open.value = true
|
||||
const res = await queryPaymentDetailApi({ id: record.id })
|
||||
if (res.data.code === 200) {
|
||||
detail.value = res.data.data
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
<!-- src/views/payment/order-payment/PaymentFormModal.vue -->
|
||||
<!-- 3.1 订单支付 —— 发起支付弹窗,对应 /api/payment/order-pay(CreatePaymentBto)。
|
||||
重要设计假设(因 swagger 未明确说明,已登记于《缺失的后端接口.md》):
|
||||
- CreatePaymentBto 仅有一个 walletAccountId 字段,无法同时指定"A1余额账户"+"A3融资账户"两个
|
||||
不同账户ID。结合 wallet-account/detail 以一个账户id返回其所属客户整套 A1/A2/A6/A7 余额的
|
||||
既有模式,推断 walletAccountId 应始终填"客户主账户(A1)"的 id,融资额度查询
|
||||
(query-credit-quota)则用该账户的 accountNo 反查,不需要单独选择 A3 账户。
|
||||
- query-credit-quota 响应 data 结构完全未文档化,前端只做防御性字段探测展示,无法保证
|
||||
"已签约且剩余额度>0"的判断完全准确。
|
||||
- CreatePaymentBto 无短信验证码字段,短验流程仅为前端 UI 展示,不会被后端真正校验。
|
||||
- loanApplyId 取自 credit-apply/loan-application/list 返回记录的数字主键 id(非
|
||||
loanApplicationId 业务单号),仅列出 applicationStatus=SUBMITTED(已申请/流程终态)的记录
|
||||
供选择,因该枚举无"审批通过/放款"等更精确状态。 -->
|
||||
<template>
|
||||
<a-modal v-model:open="open" title="发起支付" width="760px" :confirm-loading="submitting" @ok="handleSubmit" @cancel="handleCancel">
|
||||
<a-form layout="vertical" :model="form">
|
||||
<a-form-item label="付款钱包账户" required>
|
||||
<a-input-group compact style="display: flex">
|
||||
<a-input :value="payerDisplay" placeholder="请选择付款钱包账户" readonly />
|
||||
<a-button @click="pickerRef.show()">选择</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-descriptions v-if="balanceInfo" bordered size="small" :column="2" style="margin-bottom: 16px">
|
||||
<a-descriptions-item label="A1主账户可用余额">{{ balanceInfo.mainBalance ?? '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="A2电商账户可用余额">{{ balanceInfo.a2Balance ?? '-' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-form-item label="支付方式" required>
|
||||
<a-radio-group v-model:value="form.paymentMethod">
|
||||
<a-radio v-for="opt in PAYMENT_METHOD_OPTIONS" :key="opt.value" :value="opt.value">{{ opt.label }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="form.paymentMethod === 'BALANCE' || form.paymentMethod === 'COMBINED'">
|
||||
<a-form-item label="余额支付金额(元)" required>
|
||||
<a-input-number v-model:value="form.balanceAmount" :min="0" :precision="2" style="width: 240px" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-if="form.paymentMethod === 'FINANCE' || form.paymentMethod === 'COMBINED'">
|
||||
<a-form-item label="融资支付金额(元)" required>
|
||||
<a-input-number v-model:value="form.financeAmount" :min="0" :precision="2" style="width: 240px" />
|
||||
</a-form-item>
|
||||
|
||||
<a-alert
|
||||
v-if="quotaInfo"
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
message="授信额度查询结果(接口响应结构未文档化,以下为原始字段展示)"
|
||||
>
|
||||
<template #description>
|
||||
<pre style="margin: 0; white-space: pre-wrap">{{ JSON.stringify(quotaInfo, null, 2) }}</pre>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-alert v-else-if="form.walletAccountId" type="warning" show-icon style="margin-bottom: 16px" message="尚未查询到授信额度信息,融资支付功能将被禁用" />
|
||||
|
||||
<a-form-item label="关联贷款申请" required>
|
||||
<a-select
|
||||
v-model:value="form.loanApplyId"
|
||||
placeholder="请输入客户名称搜索已申请的贷款记录"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
:options="loanOptions"
|
||||
:loading="loadingLoan"
|
||||
@search="handleSearchLoan"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item label="支付总金额(元,自动计算)">
|
||||
<a-input :value="totalAmountDisplay" readonly />
|
||||
</a-form-item>
|
||||
|
||||
<a-divider>收款人信息</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="收款方户名" required>
|
||||
<a-input v-model:value="form.payeeName" placeholder="请输入收款方户名" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="收款方账号" required>
|
||||
<a-input v-model:value="form.payeeAccountNo" placeholder="请输入收款方账号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="收款方开户行">
|
||||
<a-input v-model:value="form.payeeBankName" placeholder="请输入收款方开户行" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider>短信验证</a-divider>
|
||||
<a-alert
|
||||
type="warning"
|
||||
show-icon
|
||||
style="margin-bottom: 12px"
|
||||
message="后端支付接口未提供短信验证码校验字段,以下验证流程仅为前端交互展示,不会被后端真正核验,详见《缺失的后端接口.md》"
|
||||
/>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="10">
|
||||
<a-form-item label="验证手机号" required>
|
||||
<a-input v-model:value="form.smsPhone" placeholder="请输入11位手机号" maxlength="11" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<a-form-item label="短信验证码" required>
|
||||
<a-input v-model:value="form.smsCode" placeholder="请输入验证码" maxlength="6" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-form-item label=" ">
|
||||
<a-button :disabled="smsCountdown > 0" :loading="sendingSms" @click="handleSendSms">
|
||||
{{ smsCountdown > 0 ? `${smsCountdown}s后重发` : '发送验证码' }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="备注">
|
||||
<a-textarea v-model:value="form.remark" :rows="2" placeholder="请输入备注" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<WalletAccountPicker ref="pickerRef" @select="handlePickPayer" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import WalletAccountPicker from '@/components/WalletAccountPicker.vue'
|
||||
import { fetchWalletAccountDetailApi } from '@/api/wallet'
|
||||
import { fetchLoanApplicationListApi } from '@/api/credit'
|
||||
import { queryCreditQuotaApi, orderPayApi } from '@/api/payment'
|
||||
import { sendSmsCodeApi } from '@/api/auth'
|
||||
import { PAYMENT_METHOD_OPTIONS } from '@/constants/paymentEnums'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const open = ref(false)
|
||||
const submitting = ref(false)
|
||||
const pickerRef = ref(null)
|
||||
const balanceInfo = ref(null)
|
||||
const quotaInfo = ref(null)
|
||||
const loadingLoan = ref(false)
|
||||
const loanOptions = ref([])
|
||||
const sendingSms = ref(false)
|
||||
const smsCountdown = ref(0)
|
||||
let countdownTimer = null
|
||||
|
||||
const payerDisplay = ref('')
|
||||
const payerAccountNo = ref('')
|
||||
|
||||
const defaultForm = () => ({
|
||||
walletAccountId: undefined,
|
||||
paymentMethod: 'BALANCE',
|
||||
balanceAmount: undefined,
|
||||
financeAmount: undefined,
|
||||
loanApplyId: undefined,
|
||||
payeeName: '',
|
||||
payeeAccountNo: '',
|
||||
payeeBankName: '',
|
||||
smsPhone: '',
|
||||
smsCode: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const form = reactive(defaultForm())
|
||||
|
||||
const totalAmountDisplay = computed(() => {
|
||||
const balance = form.paymentMethod !== 'FINANCE' ? Number(form.balanceAmount) || 0 : 0
|
||||
const finance = form.paymentMethod !== 'BALANCE' ? Number(form.financeAmount) || 0 : 0
|
||||
return (balance + finance).toFixed(2)
|
||||
})
|
||||
|
||||
async function handlePickPayer(record) {
|
||||
form.walletAccountId = record.id
|
||||
payerDisplay.value = `${record.accountName}(${record.accountNo})`
|
||||
payerAccountNo.value = record.accountNo
|
||||
balanceInfo.value = null
|
||||
quotaInfo.value = null
|
||||
const res = await fetchWalletAccountDetailApi({ id: record.id })
|
||||
if (res.data.code === 200) {
|
||||
balanceInfo.value = res.data.data
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => form.paymentMethod,
|
||||
async (method) => {
|
||||
if ((method === 'FINANCE' || method === 'COMBINED') && payerAccountNo.value && !quotaInfo.value) {
|
||||
const res = await queryCreditQuotaApi({ accountNo: payerAccountNo.value })
|
||||
if (res.data.code === 200) {
|
||||
quotaInfo.value = res.data.data
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async function handleSearchLoan(keyword) {
|
||||
if (!keyword) {
|
||||
loanOptions.value = []
|
||||
return
|
||||
}
|
||||
loadingLoan.value = true
|
||||
try {
|
||||
const res = await fetchLoanApplicationListApi({ page: 1, pageSize: 50, customerName: keyword, applicationStatus: 'SUBMITTED' })
|
||||
if (res.data.code === 200) {
|
||||
loanOptions.value = (res.data.data.records || []).map((item) => ({
|
||||
label: `${item.loanApplicationId} - ${item.customerName} - ¥${item.loanAmount}`,
|
||||
value: item.id
|
||||
}))
|
||||
}
|
||||
} finally {
|
||||
loadingLoan.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSendSms() {
|
||||
if (!/^1\d{10}$/.test(form.smsPhone)) {
|
||||
message.warning('请输入正确的11位手机号')
|
||||
return
|
||||
}
|
||||
sendingSms.value = true
|
||||
try {
|
||||
const res = await sendSmsCodeApi({ phone: form.smsPhone, businessType: 'PAYMENT' })
|
||||
if (res.data.code === 200) {
|
||||
message.success('验证码已发送')
|
||||
smsCountdown.value = 60
|
||||
countdownTimer = setInterval(() => {
|
||||
smsCountdown.value -= 1
|
||||
if (smsCountdown.value <= 0) clearInterval(countdownTimer)
|
||||
}, 1000)
|
||||
}
|
||||
} finally {
|
||||
sendingSms.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
if (!form.walletAccountId) {
|
||||
message.warning('请选择付款钱包账户')
|
||||
return false
|
||||
}
|
||||
if ((form.paymentMethod === 'BALANCE' || form.paymentMethod === 'COMBINED') && !(Number(form.balanceAmount) > 0)) {
|
||||
message.warning('请输入余额支付金额')
|
||||
return false
|
||||
}
|
||||
if ((form.paymentMethod === 'FINANCE' || form.paymentMethod === 'COMBINED') && !(Number(form.financeAmount) > 0)) {
|
||||
message.warning('请输入融资支付金额')
|
||||
return false
|
||||
}
|
||||
if ((form.paymentMethod === 'FINANCE' || form.paymentMethod === 'COMBINED') && !form.loanApplyId) {
|
||||
message.warning('请选择关联贷款申请')
|
||||
return false
|
||||
}
|
||||
if (form.paymentMethod === 'BALANCE' || form.paymentMethod === 'COMBINED') {
|
||||
const avail = Number(balanceInfo.value?.mainBalance)
|
||||
if (!Number.isNaN(avail) && Number(form.balanceAmount) > avail) {
|
||||
message.error('余额支付金额超出可用余额')
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!form.payeeName || !form.payeeAccountNo) {
|
||||
message.warning('请完整填写收款方户名与账号')
|
||||
return false
|
||||
}
|
||||
if (!/^1\d{10}$/.test(form.smsPhone)) {
|
||||
message.warning('请输入正确的验证手机号')
|
||||
return false
|
||||
}
|
||||
if (!/^\d{6}$/.test(form.smsCode)) {
|
||||
message.warning('请输入6位数字验证码')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!validateForm()) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const detailList = []
|
||||
if (form.paymentMethod === 'BALANCE' || form.paymentMethod === 'COMBINED') {
|
||||
detailList.push({ fundChannel: 'BALANCE', channelAmount: String(form.balanceAmount) })
|
||||
}
|
||||
if (form.paymentMethod === 'FINANCE' || form.paymentMethod === 'COMBINED') {
|
||||
detailList.push({ fundChannel: 'FINANCE', channelAmount: String(form.financeAmount) })
|
||||
}
|
||||
const payload = {
|
||||
walletAccountId: form.walletAccountId,
|
||||
loanApplyId: form.paymentMethod === 'BALANCE' ? undefined : form.loanApplyId,
|
||||
paymentMethod: form.paymentMethod,
|
||||
payeeInfo: {
|
||||
payeeName: form.payeeName,
|
||||
payeeAccountNo: form.payeeAccountNo,
|
||||
payeeBankName: form.payeeBankName
|
||||
},
|
||||
totalAmount: totalAmountDisplay.value,
|
||||
balanceAmount: form.paymentMethod === 'FINANCE' ? undefined : String(form.balanceAmount ?? ''),
|
||||
financeAmount: form.paymentMethod === 'BALANCE' ? undefined : String(form.financeAmount ?? ''),
|
||||
remark: form.remark,
|
||||
paymentDetailBtoList: detailList
|
||||
}
|
||||
const res = await orderPayApi(payload)
|
||||
if (res.data.code === 200) {
|
||||
message.success('支付请求已提交')
|
||||
open.value = false
|
||||
emit('success')
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
open.value = false
|
||||
}
|
||||
|
||||
function show() {
|
||||
Object.assign(form, defaultForm())
|
||||
payerDisplay.value = ''
|
||||
payerAccountNo.value = ''
|
||||
balanceInfo.value = null
|
||||
quotaInfo.value = null
|
||||
loanOptions.value = []
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
smsCountdown.value = 0
|
||||
open.value = true
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
<!-- src/views/payment/order-payment/PaymentRecordList.vue -->
|
||||
<!-- 3.1 订单支付:列表查询字段严格按 /api/payment-management/query-payment-record
|
||||
(FindPaymentRecordListQto)实际支持的入参实现(钱包账户ID/支付方式/支付状态/支付完成时间范围)。
|
||||
原型图要求的"机构/商户/订单编号/付款人/收款人"等查询条件及列,后端 PaymentRecordListVo
|
||||
均未提供对应字段,已在《缺失的后端接口.md》登记为缺口,未在此臆造。 -->
|
||||
<template>
|
||||
<div class="payment-record-list-page">
|
||||
<ProTable
|
||||
ref="proTableRef"
|
||||
:columns="columns"
|
||||
:fetch-data="loadList"
|
||||
:row-selection="false"
|
||||
:initial-search="{ walletAccountIdIs: undefined, paymentMethodList: [], statusList: [], startDate: undefined, endDate: undefined }"
|
||||
>
|
||||
<template #search="{ form }">
|
||||
<a-form-item label="付款钱包账户">
|
||||
<a-input-group compact style="display: flex">
|
||||
<a-input :value="payerDisplayMap[form.walletAccountIdIs] || ''" placeholder="请选择付款钱包账户" readonly style="width: 200px" />
|
||||
<a-button @click="openPickerFor(form)">选择</a-button>
|
||||
<a-button v-if="form.walletAccountIdIs" @click="form.walletAccountIdIs = undefined">清除</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付方式">
|
||||
<a-select v-model:value="form.paymentMethodList" mode="multiple" placeholder="请选择支付方式" allow-clear style="width: 200px">
|
||||
<a-select-option v-for="opt in PAYMENT_METHOD_OPTIONS" :key="opt.value" :value="opt.value">{{ opt.label }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付状态">
|
||||
<a-select v-model:value="form.statusList" mode="multiple" placeholder="请选择支付状态" allow-clear style="width: 220px">
|
||||
<a-select-option v-for="opt in PAYMENT_STATUS_OPTIONS" :key="opt.value" :value="opt.value">{{ opt.label }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付完成时间">
|
||||
<a-range-picker
|
||||
:value="form.startDate && form.endDate ? [form.startDate, form.endDate] : []"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="(vals) => { form.startDate = vals?.[0]; form.endDate = vals?.[1] }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<a-button type="primary" @click="formModalRef.show()">发起支付</a-button>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.dataIndex === 'paymentMethod'">
|
||||
<StatusTag :value="record.paymentMethod" :map="PAYMENT_METHOD_MAP" />
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'status'">
|
||||
<StatusTag :value="record.status" :map="PAYMENT_STATUS_MAP" />
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'payeeName'">{{ record.payeeInfo?.payeeName || '-' }}</template>
|
||||
<template v-else-if="column.dataIndex === 'payeeAccountNo'">{{ record.payeeInfo?.payeeAccountNo || '-' }}</template>
|
||||
<template v-else-if="column.dataIndex === 'action'">
|
||||
<a @click="detailModalRef.show(record)">详情</a>
|
||||
</template>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
<PaymentFormModal ref="formModalRef" @success="reloadList" />
|
||||
<PaymentDetailModal ref="detailModalRef" />
|
||||
<WalletAccountPicker ref="pickerRef" @select="handlePickPayer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import ProTable from '@/components/ProTable.vue'
|
||||
import StatusTag from '@/components/StatusTag.vue'
|
||||
import WalletAccountPicker from '@/components/WalletAccountPicker.vue'
|
||||
import PaymentFormModal from './PaymentFormModal.vue'
|
||||
import PaymentDetailModal from './PaymentDetailModal.vue'
|
||||
import { queryPaymentRecordApi } from '@/api/payment'
|
||||
import { PAYMENT_METHOD_OPTIONS, PAYMENT_METHOD_MAP, PAYMENT_STATUS_OPTIONS, PAYMENT_STATUS_MAP } from '@/constants/paymentEnums'
|
||||
|
||||
const proTableRef = ref(null)
|
||||
const formModalRef = ref(null)
|
||||
const detailModalRef = ref(null)
|
||||
const pickerRef = ref(null)
|
||||
const payerDisplayMap = ref({})
|
||||
let activeForm = null
|
||||
|
||||
const columns = [
|
||||
{ title: '序号', dataIndex: 'index', width: 60 },
|
||||
{ title: '支付方式', dataIndex: 'paymentMethod' },
|
||||
{ title: '支付总金额', dataIndex: 'totalAmount' },
|
||||
{ title: '余额支付金额', dataIndex: 'balanceAmount' },
|
||||
{ title: '融资支付金额', dataIndex: 'financeAmount' },
|
||||
{ title: '收款方户名', dataIndex: 'payeeName' },
|
||||
{ title: '收款方账号', dataIndex: 'payeeAccountNo' },
|
||||
{ title: '支付状态', dataIndex: 'status' },
|
||||
{ title: '凡荣e链流水号', dataIndex: 'frchainSerialNo' },
|
||||
{ title: '支付完成时间', dataIndex: 'paidAt' },
|
||||
{ title: '失败原因', dataIndex: 'failedReason' },
|
||||
{ title: '操作', dataIndex: 'action', width: 80 }
|
||||
]
|
||||
|
||||
function openPickerFor(form) {
|
||||
activeForm = form
|
||||
pickerRef.value.show()
|
||||
}
|
||||
|
||||
function handlePickPayer(record) {
|
||||
if (activeForm) {
|
||||
activeForm.walletAccountIdIs = record.id
|
||||
payerDisplayMap.value[record.id] = `${record.accountName}(${record.accountNo})`
|
||||
}
|
||||
}
|
||||
|
||||
async function loadList(params) {
|
||||
const payload = { ...params }
|
||||
if (payload.startDate) payload.paidAtStart = `${payload.startDate} 00:00:00`
|
||||
if (payload.endDate) payload.paidAtEnd = `${payload.endDate} 23:59:59`
|
||||
delete payload.startDate
|
||||
delete payload.endDate
|
||||
if (!payload.paymentMethodList?.length) delete payload.paymentMethodList
|
||||
if (!payload.statusList?.length) delete payload.statusList
|
||||
const res = await queryPaymentRecordApi(payload)
|
||||
if (res.data.code === 200) {
|
||||
return { list: res.data.data.records || [], total: res.data.data.total || 0 }
|
||||
}
|
||||
return { list: [], total: 0 }
|
||||
}
|
||||
|
||||
function reloadList() {
|
||||
proTableRef.value?.reload?.()
|
||||
}
|
||||
</script>
|
||||
58
缺失的后端接口.md
58
缺失的后端接口.md
|
|
@ -187,6 +187,26 @@
|
|||
| `POST invoice-management/repayment/list` | 查询可匹配的本地回款流水,`FindRepaymentListQto`(merchantIdIs/accountNoLike/oppAccountNoLike/timeStart-End) |
|
||||
| `POST invoice-management/offline-recharge-unmatched-detail` | 查询线下来账明细,**透传中台30908接口**,需 `channelNo`+交易日期范围,用于"自动匹配"高级场景,本次未在 UI 中实现对应入口 |
|
||||
|
||||
### 1.15 支付管理 —— 订单支付(`/api/payment/*`、`/api/payment-management/*`)
|
||||
|
||||
| 接口 | 说明 |
|
||||
| --- | --- |
|
||||
| `POST payment/query-credit-quota` | 查询授信额度,入参仅 `{accountNo}`(钱包账号字符串),响应 `data` **在 swagger 中完全未定义结构**(无 `$ref`/无 `properties`),真正的未文档化接口,前端只能做防御性字段展示 |
|
||||
| `POST payment/order-pay` | 创建支付,`CreatePaymentBto`:`walletAccountId/loanApplyId(可空)/paymentMethod/payeeInfo/totalAmount/balanceAmount/financeAmount/status/remark/paymentDetailBtoList`,**无短信验证码字段** |
|
||||
| `POST payment-management/query-payment-record` | 分页查询支付记录,`FindPaymentRecordListQto`,仅支持 `walletAccountIdIs/paymentMethodList/statusList/paidAtStart/paidAtEnd`,响应 `PaymentRecordListVo[]` |
|
||||
| `POST payment-management/query-payment-detail` | `{id}`,响应 `PaymentRecordDetailVo`(列表字段 + `detailList: PaymentDetailVo[]`) |
|
||||
| — | `PaymentMethodEnum`:`BALANCE`余额支付/`FINANCE`融资支付/`COMBINED`组合支付;`PaymentStatusEnum`:`PENDING_VERIFICATION/VERIFYING/PROCESSING/SUCCESS/FAILED/CANCELLED`;`FundSourceEnum`:`BALANCE/FINANCE` |
|
||||
|
||||
### 1.16 订单管理 —— 汇总订单/原始订单(`/api/summary-order/*`、`/api/original-order/*`)
|
||||
|
||||
| 接口 | 说明 |
|
||||
| --- | --- |
|
||||
| `POST summary-order/page` | 请求仅支持 `{page,pageSize,orderNo}`,响应 `SummaryOrderVo`(无关联原始订单明细列表) |
|
||||
| `POST summary-order/detail` | `{id}`,响应即 `SummaryOrderVo` 本身,**不含关联原始订单明细** |
|
||||
| `POST original-order/page` | 请求仅支持 `{page,pageSize,originalOrderNo}`,**不支持按 `summaryOrderId` 过滤** |
|
||||
| `POST original-order/export` | 入参 `{summaryOrderId,originalOrderNo}` 均可选,响应 `data` **未定义结构**,前端沿用 `downloadStatement` 已建立的 `{fileData: base64}` 假设处理 |
|
||||
| — | `SettlementStatusEnum`:`UNSCHEDULED/SETTLED_REPAID/FAILED`;`SummaryOrderVo` 含 `financeQuota/accumulatedLoanAmount` 等字段但**无剩余额度字段**,前端本地计算 `剩余额度 = financeQuota - accumulatedLoanAmount` |
|
||||
|
||||
---
|
||||
|
||||
## Part 2:待后端确认 / 建议补充事项
|
||||
|
|
@ -313,3 +333,41 @@
|
|||
43. **`/api/invoice/page`(透传中台31909查询)与 `/api/invoice-management/invoice/list`(本地查询)为
|
||||
功能重叠的两组平行接口**:前者需 `channelNo` 且字段命名与本地 `InvoiceVo` 不同,本项目统一采用
|
||||
后者用于列表展示,前者未使用,建议后端确认二者分工或说明是否为废弃接口。
|
||||
44. **`payment/query-credit-quota` 响应 `data` 字段完全未定义结构**:swagger 中该字段无 `$ref`、无
|
||||
`properties`,前端在支付表单中改为原样展示返回的 JSON 供操作人员肉眼判断,无法做强类型的"剩余
|
||||
额度"校验与展示,若融资金额超出实际额度,只能依赖后端 `order-pay` 接口调用失败后的错误提示兜底,
|
||||
建议后端补充该接口的响应字段定义。
|
||||
45. **`order-pay` 的 `CreatePaymentBto` 无短信验证码字段**:docx 描述的"发起支付前需短信验证"流程在
|
||||
后端契约层面无法真正落地,前端仍按 docx 实现了"发送验证码(复用 `send-sms-code`)+ 输入验证码"的
|
||||
交互与本地格式校验(非空+6位数字),但验证码本身不会被传给 `order-pay` 也不会被后端比对校验,
|
||||
该环节目前形同虚设,需后端评估是否要在 `CreatePaymentBto` 中补充 `smsCode` 字段并在服务端真正
|
||||
校验。
|
||||
46. **`payment-management/query-payment-record`(及其 `PaymentRecordListVo`/`PaymentRecordDetailVo`)
|
||||
均无机构/商户/订单编号/付款人姓名等字段**:原型图查询区与列表列包含"机构/商户/订单编号/付款人/
|
||||
付款账号/服务费"等信息,后端当前的支付记录模型仅关联到 `walletAccountId`,不关联商户或订单,
|
||||
前端已按接口实际返回字段精简查询表单与列表列,未臆造上述字段,建议后端评估是否需要在支付记录
|
||||
落库时补充商户/订单关联字段。
|
||||
47. **`CreatePaymentBto` 只有一个 `walletAccountId` 字段,无法同时指定"余额账户"与"融资账户"两个不同
|
||||
账户 ID**:结合 `wallet-account/detail` 以一个账户 id 返回其所属客户整套 `mainBalance/a2Balance/
|
||||
a6Balance/a7Balance` 余额的既有设计模式,前端推断该字段应始终填客户主账户(A1)的 id,融资额度
|
||||
查询(`query-credit-quota`)则用该账户的 `accountNo` 反查,组合支付时的"融资部分"通过
|
||||
`loanApplyId` 关联的贷款申请单据来承载客户与金额信息,不再单独指定 A3 账户 ID。此为前端基于现有
|
||||
接口设计模式的**推断性假设**,未在 swagger 中被明确证实,建议后端确认该理解是否准确。
|
||||
48. **`loanApplyId` 取自 `credit-apply/loan-application/list` 返回记录的数字主键 `id`**,且该模块使用的
|
||||
`ApplicationStatusEnum`(`DRAFT/CONFIRMED/SUBMITTED/APPLY_FAILED`)**无"已批准/生效/放款"等更精确的
|
||||
终态**,前端支付表单在选择"关联贷款申请"时只能按 `SUBMITTED`(已申请)状态筛选可选记录,无法准确
|
||||
判断该笔贷款是否已实际放款、是否仍有可用余额支持本次融资支付,建议后端补充更精确的贷款状态枚举
|
||||
或提供专门的"可用于支付的贷款额度"查询接口。
|
||||
49. **`summary-order/page`/`original-order/page` 查询入参严重少于 docx/原型描述**:`summary-order/page`
|
||||
仅支持 `orderNo`,`original-order/page` 仅支持 `originalOrderNo`,原型图要求的渠道/日期范围/订单
|
||||
状态/结算状态/店铺 ID 等查询条件均无对应参数,前端已按接口实际支持字段精简查询表单,建议后端
|
||||
评估是否需要补充查询参数。
|
||||
50. **`original-order/export` 响应 `data` 字段未定义结构**:与 `download-statement`/`download-receipt`
|
||||
等已文档化 `fileData` 字段的导出接口不同,该接口响应结构在 swagger 中完全空白,前端沿用既有
|
||||
"假设为 `{fileData: base64}`"的处理模式实现下载,若实际结构不同将导致导出功能报错或下载内容
|
||||
异常,建议后端补充响应字段定义。
|
||||
51. **`summary-order/detail` 不返回关联原始订单明细,且 `original-order/page` 不支持按
|
||||
`summaryOrderId` 过滤查询**:两个缺口叠加导致"查看汇总订单关联的原始订单明细列表"功能在当前
|
||||
接口能力下无法实现,前端在汇总订单详情弹窗中用 `a-alert` 明确提示该缺口,未展示虚假数据,建议
|
||||
后端为 `original-order/page` 补充 `summaryOrderId` 过滤参数,或在 `summary-order/detail` 响应中
|
||||
直接内嵌关联的原始订单列表。
|
||||
|
|
|
|||
Loading…
Reference in New Issue