feat(admin): 支付接口审计日志页与调试 reqId

运营端日志管理增加支付接口日志列表/详情;调试交易与网关组参自动生成 reqId;菜单与权限码对齐 system:log:unipay。
This commit is contained in:
DaxPay Dev
2026-07-17 17:40:00 +08:00
parent 622bcc0b04
commit 51f35ff153
30 changed files with 755 additions and 4 deletions

View File

@@ -60,6 +60,8 @@ export interface GatewayPrePayParam {
expiredTime?: string;
/** 门店号 */
storeNo?: string;
/** 请求ID(商户生成, 审计索引, 必填) */
reqId?: string;
/** 随机串 */
nonceStr?: string;
/** 请求时间(北京时间 yyyy-MM-dd HH:mm:ss) */

View File

@@ -86,6 +86,8 @@ export interface PayParam {
expiredTime?: string;
/** 客户端 IP(可选, 未传时由 unipay 从 HTTP 请求提取) */
clientIp?: string;
/** 请求ID(商户生成, 审计索引, 必填) */
reqId?: string;
/** 随机串 */
nonceStr?: string;
/** 请求时间(北京时间 yyyy-MM-dd HH:mm:ss) */

View File

@@ -21,6 +21,8 @@ export interface DaxResult<T = unknown> {
data?: T;
sign?: string;
resTime?: string;
/** 请求ID(回显) */
reqId?: string;
traceId?: string;
}

View File

@@ -16,7 +16,7 @@ import { unipayPost } from './unipay-request';
* 统一支付下单
*
* POST /unipay/pay
* 请求体须含完整商户签名字段(reqTime/nonceStr/sign 等)
* 请求体须含完整商户签名字段(reqId/reqTime/nonceStr/sign 等)
*/
export function uniPay(data: PayParam): Promise<DaxResult<PayResult>> {
return unipayPost<PayResult>('/unipay/pay', data);
@@ -26,7 +26,7 @@ export function uniPay(data: PayParam): Promise<DaxResult<PayResult>> {
* 网关预下单
*
* POST /unipay/gateway/pre-pay
* 请求体须含完整商户签名字段(reqTime/nonceStr/sign 等)
* 请求体须含完整商户签名字段(reqId/reqTime/nonceStr/sign 等)
*/
export function uniGatewayPrePay(data: GatewayPrePayParam): Promise<DaxResult<GatewayPrePayResult>> {
return unipayPost<GatewayPrePayResult>('/unipay/gateway/pre-pay', data);

View File

@@ -0,0 +1,65 @@
import type { BaseEntity, PageResult, Result } from '#/types/web';
import { defHttp } from '#/api/request';
/**
* 支付接口日志 API
*/
export const UnipayApiLogApi = {
/**
* 分页查询支付接口日志
*/
page(params: any): Promise<Result<PageResult<UnipayApiLog>>> {
return defHttp.get({ url: '/log/unipay/page', params });
},
/**
* 获取支付接口日志详情
*/
findById(id: string): Promise<Result<UnipayApiLog>> {
return defHttp.get({ url: '/log/unipay/get', params: { id } });
},
/**
* 清除指定天数之前的支付接口日志
*/
deleteByDay(deleteDay: number): Promise<Result<void>> {
return defHttp.post({ url: '/log/unipay/delete-by-day', params: { deleteDay } });
},
};
/**
* 支付接口日志
*/
export interface UnipayApiLog extends BaseEntity {
/** 商户号 */
mchNo?: string;
/** 请求ID */
reqId?: string;
/** 接口路径 */
apiPath?: string;
/** 接口标题 */
apiTitle?: string;
/** HTTP 方法 */
requestMethod?: string;
/** 商户声明客户端 IP */
clientIp?: string;
/** 真实接入 IP */
requestIp?: string;
/** 接入 IP 归属地 */
requestLocation?: string;
/** 是否成功 */
success?: boolean;
/** 业务错误码 */
errorCode?: number;
/** 错误信息 */
errorMsg?: string;
/** 耗时毫秒 */
durationMs?: number;
/** 链路追踪 ID */
traceId?: string;
/** 请求参数 */
reqParam?: string;
/** 响应体 */
resBody?: string;
/** 操作时间 */
operateTime?: string;
}

View File

@@ -220,6 +220,10 @@ export const PermCodes = {
VIEW: 'system:log:operate:view',
MANAGE: 'system:log:operate:manage',
},
Unipay: {
VIEW: 'system:log:unipay:view',
MANAGE: 'system:log:unipay:manage',
},
},
Notify: {
VIEW: 'system:notify:notice:view',

View File

@@ -0,0 +1,27 @@
{
"mchNo": "Merchant No.",
"reqId": "Request ID",
"apiPath": "API Path",
"apiTitle": "API Name",
"requestMethod": "HTTP Method",
"clientIp": "Client IP (declared)",
"requestIp": "Request IP",
"requestLocation": "Location",
"status": "Status",
"errorCode": "Error Code",
"errorMsg": "Error Message",
"durationMs": "Duration (ms)",
"traceId": "Trace ID",
"reqParam": "Request Body",
"resBody": "Response Body",
"operateTime": "Time",
"inputMchNo": "Enter merchant no.",
"inputReqId": "Enter request ID",
"inputApiPath": "Enter API path",
"inputTraceId": "Enter trace ID",
"detailTitle": "Payment API Log Detail",
"selectCleanDay": "Clear logs older than",
"cleanWarning": "Warning",
"cleanConfirm": "Clear payment API logs older than the selected days? This cannot be undone.",
"cleanSuccess": "Logs cleared successfully"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "Merchant No.",
"reqId": "Request ID",
"apiPath": "API Path",
"apiTitle": "API Name",
"requestMethod": "HTTP Method",
"clientIp": "Client IP (declared)",
"requestIp": "Request IP",
"requestLocation": "Location",
"status": "Status",
"errorCode": "Error Code",
"errorMsg": "Error Message",
"durationMs": "Duration (ms)",
"traceId": "Trace ID",
"reqParam": "Request Body",
"resBody": "Response Body",
"operateTime": "Time",
"inputMchNo": "Enter merchant no.",
"inputReqId": "Masukkan request ID",
"inputApiPath": "Enter API path",
"inputTraceId": "Enter trace ID",
"detailTitle": "Payment API Log Detail",
"selectCleanDay": "Clear logs older than",
"cleanWarning": "Warning",
"cleanConfirm": "Clear payment API logs older than the selected days? This cannot be undone.",
"cleanSuccess": "Logs cleared successfully"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "加盟店番号",
"reqId": "リクエストID",
"apiPath": "APIパス",
"apiTitle": "API名",
"requestMethod": "HTTPメソッド",
"clientIp": "申告クライアントIP",
"requestIp": "接続IP",
"requestLocation": "接続地域",
"status": "状態",
"errorCode": "エラーコード",
"errorMsg": "エラーメッセージ",
"durationMs": "所要時間(ms)",
"traceId": "トレースID",
"reqParam": "リクエスト",
"resBody": "レスポンス",
"operateTime": "呼び出し時刻",
"inputMchNo": "加盟店番号を入力",
"inputReqId": "リクエストIDを入力",
"inputApiPath": "APIパスを入力",
"inputTraceId": "トレースIDを入力",
"detailTitle": "決済APIログ詳細",
"selectCleanDay": "削除する期間",
"cleanWarning": "警告",
"cleanConfirm": "指定日数より前の決済APIログを削除します。元に戻せません。",
"cleanSuccess": "ログを削除しました"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "가맹점 번호",
"reqId": "요청 ID",
"apiPath": "API 경로",
"apiTitle": "API 이름",
"requestMethod": "요청 방식",
"clientIp": "가맹점 선언 IP",
"requestIp": "접속 IP",
"requestLocation": "접속 지역",
"status": "호출 상태",
"errorCode": "오류 코드",
"errorMsg": "오류 메시지",
"durationMs": "소요 시간(ms)",
"traceId": "추적 ID",
"reqParam": "요청 파라미터",
"resBody": "응답 본문",
"operateTime": "호출 시간",
"inputMchNo": "가맹점 번호 입력",
"inputReqId": "요청 ID 입력",
"inputApiPath": "API 경로 입력",
"inputTraceId": "추적 ID 입력",
"detailTitle": "결제 API 로그 상세",
"selectCleanDay": "삭제 기간 선택",
"cleanWarning": "경고",
"cleanConfirm": "지정 일수 이전의 결제 API 로그를 삭제합니다. 되돌릴 수 없습니다.",
"cleanSuccess": "로그 삭제 완료"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "Merchant No.",
"reqId": "Request ID",
"apiPath": "API Path",
"apiTitle": "API Name",
"requestMethod": "HTTP Method",
"clientIp": "Client IP (declared)",
"requestIp": "Request IP",
"requestLocation": "Location",
"status": "Status",
"errorCode": "Error Code",
"errorMsg": "Error Message",
"durationMs": "Duration (ms)",
"traceId": "Trace ID",
"reqParam": "Request Body",
"resBody": "Response Body",
"operateTime": "Time",
"inputMchNo": "Enter merchant no.",
"inputReqId": "Masukkan request ID",
"inputApiPath": "Enter API path",
"inputTraceId": "Enter trace ID",
"detailTitle": "Payment API Log Detail",
"selectCleanDay": "Clear logs older than",
"cleanWarning": "Warning",
"cleanConfirm": "Clear payment API logs older than the selected days? This cannot be undone.",
"cleanSuccess": "Logs cleared successfully"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "Merchant No.",
"reqId": "Request ID",
"apiPath": "API Path",
"apiTitle": "API Name",
"requestMethod": "HTTP Method",
"clientIp": "Client IP (declared)",
"requestIp": "Request IP",
"requestLocation": "Location",
"status": "Status",
"errorCode": "Error Code",
"errorMsg": "Error Message",
"durationMs": "Duration (ms)",
"traceId": "Trace ID",
"reqParam": "Request Body",
"resBody": "Response Body",
"operateTime": "Time",
"inputMchNo": "Enter merchant no.",
"inputReqId": "กรอก Request ID",
"inputApiPath": "Enter API path",
"inputTraceId": "Enter trace ID",
"detailTitle": "Payment API Log Detail",
"selectCleanDay": "Clear logs older than",
"cleanWarning": "Warning",
"cleanConfirm": "Clear payment API logs older than the selected days? This cannot be undone.",
"cleanSuccess": "Logs cleared successfully"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "Mã thương nhân",
"reqId": "Request ID",
"apiPath": "Đường dẫn API",
"apiTitle": "Tên API",
"requestMethod": "HTTP Method",
"clientIp": "Client IP (declared)",
"requestIp": "Request IP",
"requestLocation": "Location",
"status": "Trạng thái",
"errorCode": "Error Code",
"errorMsg": "Error Message",
"durationMs": "Duration (ms)",
"traceId": "Trace ID",
"reqParam": "Request Body",
"resBody": "Response Body",
"operateTime": "Time",
"inputMchNo": "Enter merchant no.",
"inputReqId": "Nhập request ID",
"inputApiPath": "Enter API path",
"inputTraceId": "Enter trace ID",
"detailTitle": "Chi tiết nhật ký API thanh toán",
"selectCleanDay": "Clear logs older than",
"cleanWarning": "Warning",
"cleanConfirm": "Clear payment API logs older than the selected days? This cannot be undone.",
"cleanSuccess": "Logs cleared successfully"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "商户号",
"reqId": "请求ID",
"apiPath": "接口路径",
"apiTitle": "接口名称",
"requestMethod": "请求方式",
"clientIp": "商户声明IP",
"requestIp": "接入IP",
"requestLocation": "接入地点",
"status": "调用状态",
"errorCode": "错误码",
"errorMsg": "错误提示",
"durationMs": "耗时(ms)",
"traceId": "追踪ID",
"reqParam": "请求参数",
"resBody": "响应内容",
"operateTime": "调用时间",
"inputMchNo": "请输入商户号",
"inputReqId": "请输入请求ID",
"inputApiPath": "请输入接口路径",
"inputTraceId": "请输入追踪ID",
"detailTitle": "支付接口日志详情",
"selectCleanDay": "清除多久前的日志",
"cleanWarning": "警告",
"cleanConfirm": "是否清除指定日期前的支付接口日志,该操作不可撤回",
"cleanSuccess": "清理日志成功"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "商户号",
"reqId": "請求ID",
"apiPath": "接口路径",
"apiTitle": "接口名称",
"requestMethod": "请求方式",
"clientIp": "商户声明IP",
"requestIp": "接入IP",
"requestLocation": "接入地点",
"status": "调用状态",
"errorCode": "错误码",
"errorMsg": "错误提示",
"durationMs": "耗时(ms)",
"traceId": "追踪ID",
"reqParam": "请求参数",
"resBody": "响应内容",
"operateTime": "调用时间",
"inputMchNo": "请输入商户号",
"inputReqId": "請輸入請求ID",
"inputApiPath": "请输入接口路径",
"inputTraceId": "请输入追踪ID",
"detailTitle": "支付接口日志详情",
"selectCleanDay": "清除多久前的日志",
"cleanWarning": "警告",
"cleanConfirm": "是否清除指定日期前的支付接口日志,该操作不可撤回",
"cleanSuccess": "清理日志成功"
}

View File

@@ -0,0 +1,27 @@
{
"mchNo": "商戶號",
"reqId": "請求ID",
"apiPath": "介面路徑",
"apiTitle": "介面名稱",
"requestMethod": "請求方式",
"clientIp": "商戶聲明IP",
"requestIp": "接入IP",
"requestLocation": "接入地點",
"status": "呼叫狀態",
"errorCode": "錯誤碼",
"errorMsg": "錯誤提示",
"durationMs": "耗時(ms)",
"traceId": "追蹤ID",
"reqParam": "請求參數",
"resBody": "回應內容",
"operateTime": "呼叫時間",
"inputMchNo": "請輸入商戶號",
"inputReqId": "請輸入請求ID",
"inputApiPath": "請輸入介面路徑",
"inputTraceId": "請輸入追蹤ID",
"detailTitle": "支付介面日誌詳情",
"selectCleanDay": "清除多久前的日誌",
"cleanWarning": "警告",
"cleanConfirm": "是否清除指定日期前的支付介面日誌,該操作不可撤回",
"cleanSuccess": "清理日誌成功"
}

View File

@@ -16,6 +16,7 @@
"menu.demos": "Demos",
"menu.dashboard": "Dashboard",
"menu.system.log.operate": "Operate Log",
"menu.system.log.unipay": "Payment API Log",
"menu.platform": "Payment Management",
"menu.system.log.login": "Login Log",
"menu.dashboard.workspace": "Workspace",

View File

@@ -16,6 +16,7 @@
"menu.demos": "Demo",
"menu.dashboard": "Dasbor",
"menu.system.log.operate": "Operasikan Log",
"menu.system.log.unipay": "Log API Pembayaran",
"menu.platform": "Manajemen Pembayaran",
"menu.system.log.login": "Catatan Masuk",
"menu.dashboard.workspace": "Ruang kerja",

View File

@@ -16,6 +16,7 @@
"menu.demos": "デモ",
"menu.dashboard": "ダッシュボード",
"menu.system.log.operate": "操作ログ",
"menu.system.log.unipay": "決済APIログ",
"menu.platform": "支払い管理",
"menu.system.log.login": "ログインログ",
"menu.dashboard.workspace": "ワークスペース",

View File

@@ -16,6 +16,7 @@
"menu.demos": "시민",
"menu.dashboard": "계기반",
"menu.system.log.operate": "운영 로그",
"menu.system.log.unipay": "결제 API 로그",
"menu.platform": "결제관리",
"menu.system.log.login": "로그인 로그",
"menu.dashboard.workspace": "작업공간",

View File

@@ -16,6 +16,7 @@
"menu.demos": "tunjuk cara",
"menu.dashboard": "Papan pemuka",
"menu.system.log.operate": "Kendalikan Log",
"menu.system.log.unipay": "Log API Pembayaran",
"menu.platform": "Pengurusan Pembayaran",
"menu.system.log.login": "Log Masuk",
"menu.dashboard.workspace": "Ruang kerja",

View File

@@ -16,6 +16,7 @@
"menu.demos": "การสาธิต",
"menu.dashboard": "แดชบอร์ด",
"menu.system.log.operate": "ดำเนินการบันทึก",
"menu.system.log.unipay": "ล็อก API ชำระเงิน",
"menu.platform": "การจัดการการชำระเงิน",
"menu.system.log.login": "เข้าสู่ระบบ",
"menu.dashboard.workspace": "พื้นที่ทำงาน",

View File

@@ -16,6 +16,7 @@
"menu.demos": "Bản demo",
"menu.dashboard": "Trang tổng quan",
"menu.system.log.operate": "Nhật ký vận hành",
"menu.system.log.unipay": "Nhật ký API thanh toán",
"menu.platform": "Quản lý thanh toán",
"menu.system.log.login": "Đăng nhập Nhật ký",
"menu.dashboard.workspace": "Không gian làm việc",

View File

@@ -16,6 +16,7 @@
"menu.demos": "演示",
"menu.dashboard": "仪表板",
"menu.system.log.operate": "操作日志",
"menu.system.log.unipay": "支付接口日志",
"menu.platform": "支付管理",
"menu.system.log.login": "登录日志",
"menu.dashboard.workspace": "工作台",

View File

@@ -16,6 +16,7 @@
"menu.demos": "演示",
"menu.dashboard": "儀表板",
"menu.system.log.operate": "操作日誌",
"menu.system.log.unipay": "支付接口日誌",
"menu.platform": "支付管理",
"menu.system.log.login": "登錄日誌",
"menu.dashboard.workspace": "工作台",

View File

@@ -16,6 +16,7 @@
"menu.demos": "演示",
"menu.dashboard": "儀表板",
"menu.system.log.operate": "操作日誌",
"menu.system.log.unipay": "支付介面日誌",
"menu.platform": "支付管理",
"menu.system.log.login": "登入日誌",
"menu.dashboard.workspace": "工作臺",

View File

@@ -192,6 +192,16 @@
return s;
}
/** 生成请求 ID(审计索引用, 32 位) */
function genReqId(): string {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let s = '';
for (let i = 0; i < 32; i++) {
s += chars[Math.floor(Math.random() * chars.length)];
}
return s;
}
/** 剔除空串/null/undefined, 避免参与签名或污染请求体 */
function cleanPayload(raw: GatewayPrePayParam): GatewayPrePayParam {
const cleaned: Record<string, any> = {};
@@ -204,12 +214,13 @@
}
/**
* 组装提交参数(含公共字段 reqTime/nonceStr)
* 组装提交参数(含公共字段 reqId/reqTime/nonceStr)
*/
function buildPayload(): GatewayPrePayParam {
const payload: GatewayPrePayParam = {
...form,
// 每次组参刷新公共字段, 贴近真实商户 SDK
reqId: genReqId(),
reqTime: formatReqTimeCst(),
nonceStr: genNonceStr(),
// 签名由后续步骤写入, 预览阶段不带旧 sign

View File

@@ -256,6 +256,16 @@
return s;
}
/** 生成请求 ID(审计索引用, 32 位) */
function genReqId(): string {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let s = '';
for (let i = 0; i < 32; i++) {
s += chars[Math.floor(Math.random() * chars.length)];
}
return s;
}
/** 剔除空串/null/undefined, 避免参与签名或污染请求体 */
function cleanPayload(raw: PayParam): PayParam {
const cleaned: Record<string, any> = {};
@@ -268,13 +278,14 @@
}
/**
* 按当前模式组装提交参数(含公共字段 reqTime/nonceStr)
* 按当前模式组装提交参数(含公共字段 reqId/reqTime/nonceStr)
* 各模式只透传自身字段; 直接指定 method 由 unipay 路由反推
*/
function buildPayload(): PayParam {
const payload: PayParam = {
...form,
// 每次组参刷新公共字段, 贴近真实商户 SDK
reqId: genReqId(),
reqTime: formatReqTimeCst(),
nonceStr: genNonceStr(),
// 签名由后续步骤写入, 预览阶段不带旧 sign

View File

@@ -0,0 +1,110 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { $t } from '@vben/locales';
import { formatDateTime } from '@vben/utils';
import { type UnipayApiLog, UnipayApiLogApi } from '#/api/system/log/unipay-api-log.api';
const data = ref<Partial<UnipayApiLog>>({});
const visible = ref(false);
const confirmLoading = ref(false);
/**
* 显示详情
*/
function show(id: string) {
visible.value = true;
confirmLoading.value = true;
UnipayApiLogApi.findById(id).then(({ data: res }) => {
data.value = res || {};
confirmLoading.value = false;
});
}
defineExpose({ show });
</script>
<template>
<!-- 国际化支付接口日志详情 -->
<a-modal
v-bind="$attrs"
:loading="confirmLoading"
:width="960"
:title="$t('system.log.unipay-api-log.detailTitle')"
:open="visible"
:footer="null"
@cancel="visible = false"
>
<a-spin :spinning="confirmLoading">
<a-descriptions :column="2" size="small" bordered>
<!-- 商户号 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.mchNo')">
{{ data.mchNo }}
</a-descriptions-item>
<!-- 请求ID -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.reqId')">
{{ data.reqId }}
</a-descriptions-item>
<!-- 状态 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.status')">
<a-tag :color="data.success ? 'green' : 'red'">
{{ data.success ? $t('common.success') : $t('common.fail') }}
</a-tag>
</a-descriptions-item>
<!-- 耗时 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.durationMs')">
{{ data.durationMs != null ? `${data.durationMs} ms` : '' }}
</a-descriptions-item>
<!-- 接口标题 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.apiTitle')">
{{ data.apiTitle }}
</a-descriptions-item>
<!-- 请求方式 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.requestMethod')">
{{ data.requestMethod }}
</a-descriptions-item>
<!-- 接口路径 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.apiPath')" :span="2">
{{ data.apiPath }}
</a-descriptions-item>
<!-- 真实接入 IP -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.requestIp')">
{{ data.requestIp }}
</a-descriptions-item>
<!-- 归属地 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.requestLocation')">
{{ data.requestLocation }}
</a-descriptions-item>
<!-- 商户声明 IP -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.clientIp')">
{{ data.clientIp }}
</a-descriptions-item>
<!-- 追踪 ID -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.traceId')">
{{ data.traceId }}
</a-descriptions-item>
<!-- 错误码 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.errorCode')">
{{ data.errorCode }}
</a-descriptions-item>
<!-- 错误信息 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.errorMsg')" :span="2">
{{ data.errorMsg }}
</a-descriptions-item>
<!-- 请求参数 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.reqParam')" :span="2">
<pre class="whitespace-pre-wrap break-all text-xs">{{ data.reqParam }}</pre>
</a-descriptions-item>
<!-- 响应体 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.resBody')" :span="2">
<pre class="whitespace-pre-wrap break-all text-xs">{{ data.resBody }}</pre>
</a-descriptions-item>
<!-- 时间 -->
<a-descriptions-item :label="$t('system.log.unipay-api-log.operateTime')" :span="2">
{{ formatDateTime(data.operateTime) }}
</a-descriptions-item>
</a-descriptions>
</a-spin>
</a-modal>
</template>

View File

@@ -0,0 +1,264 @@
<script lang="ts" setup>
import type { VxeTableInstance, VxeToolbarInstance } from 'vxe-table';
import { computed, onMounted, ref } from 'vue';
import { $t } from '@vben/locales';
import { UnipayApiLogApi } from '#/api/system/log/unipay-api-log.api';
import { BQuery, type QueryField } from '#/components/query';
import { PermCodes } from '#/constants/perm-codes';
import { useMessage } from '#/hooks/useMessage';
import { usePermission } from '#/hooks/usePermission';
import UnipayApiLogInfo from './UnipayApiLogInfo.vue';
const { confirm, message } = useMessage();
const { hasPermission } = usePermission();
const loading = ref(false);
const xTable = ref<VxeTableInstance>();
const xToolbar = ref<VxeToolbarInstance>();
const unipayApiLogInfoRef = ref();
// 查询条件
const queryForm = ref<Record<string, any>>({});
// 分页配置
const pageConfig = ref({
currentPage: 1,
pageSize: 10,
total: 0,
});
// 表格数据
const tableData = ref<any[]>([]);
// 清理天数
const deleteDay = ref<number | undefined>(undefined);
// 清理条件
const deleteDays = computed(() => [
// 3天
{ label: $t('system.log.common.cleanDays.d3'), value: 3 },
// 7天
{ label: $t('system.log.common.cleanDays.d7'), value: 7 },
// 30天
{ label: $t('system.log.common.cleanDays.d30'), value: 30 },
// 60天
{ label: $t('system.log.common.cleanDays.d60'), value: 60 },
// 90天
{ label: $t('system.log.common.cleanDays.d90'), value: 90 },
// 180天
{ label: $t('system.log.common.cleanDays.d180'), value: 180 },
// 365天
{ label: $t('system.log.common.cleanDays.d365'), value: 365 },
]);
// 状态选项
const successStatusOptions = computed(() => [
// 成功
{ label: $t('common.success'), value: true },
// 失败
{ label: $t('common.fail'), value: false },
]);
// 查询字段配置
const queryFields: QueryField[] = [
// 商户号
{
field: 'mchNo',
name: $t('system.log.unipay-api-log.mchNo'),
placeholder: $t('system.log.unipay-api-log.inputMchNo'),
},
// 请求ID
{
field: 'reqId',
name: $t('system.log.unipay-api-log.reqId'),
placeholder: $t('system.log.unipay-api-log.inputReqId'),
},
// 接口路径
{
field: 'apiPath',
name: $t('system.log.unipay-api-log.apiPath'),
placeholder: $t('system.log.unipay-api-log.inputApiPath'),
},
// 追踪 ID
{
field: 'traceId',
name: $t('system.log.unipay-api-log.traceId'),
placeholder: $t('system.log.unipay-api-log.inputTraceId'),
},
{
field: 'success',
type: 'list',
// 状态
name: $t('system.log.unipay-api-log.status'),
selectList: successStatusOptions.value,
},
];
onMounted(() => {
xTable.value?.connectToolbar(xToolbar.value as VxeToolbarInstance);
queryPage();
});
/**
* 查询分页数据
*/
function queryPage() {
loading.value = true;
UnipayApiLogApi.page({
current: pageConfig.value.currentPage,
size: pageConfig.value.pageSize,
...queryForm.value,
}).then((res: any) => {
tableData.value = res.data.records || [];
pageConfig.value.total = Number(res.data.total) || 0;
loading.value = false;
});
return Promise.resolve();
}
/**
* 重置查询
*/
function resetQuery() {
queryForm.value = {};
pageConfig.value.currentPage = 1;
queryPage();
}
/**
* 查看详情
*/
function handleShow(row: any) {
unipayApiLogInfoRef.value.show(row.id);
}
/**
* 清理日志
*/
function handleDeleteLogs() {
if (!deleteDay.value) {
return;
}
confirm({
// 清理确认
title: $t('system.log.unipay-api-log.cleanWarning'),
// 确认清理内容
content: $t('system.log.unipay-api-log.cleanConfirm'),
okText: $t('common.okText'),
cancelText: $t('common.cancelText'),
onOk() {
return UnipayApiLogApi.deleteByDay(deleteDay.value!).then(() => {
// 清理成功
message.success($t('system.log.unipay-api-log.cleanSuccess'));
deleteDay.value = undefined;
queryPage();
});
},
});
}
/**
* 分页变化
*/
function handlePageChange({ currentPage, pageSize }: any) {
pageConfig.value.currentPage = currentPage;
pageConfig.value.pageSize = pageSize;
queryPage();
}
</script>
<template>
<div class="m-3 rounded-lg bg-background p-3 list-page-compact">
<a-card>
<!-- 查询表单 -->
<BQuery :fields="queryFields" :query-params="queryForm" @query="queryPage" @reset="resetQuery" />
</a-card>
<div class="mt-4">
<a-card>
<vxe-toolbar ref="xToolbar" custom refresh :refresh-options="{ queryMethod: queryPage }">
<template #buttons>
<a-space v-if="hasPermission(PermCodes.System.Log.Unipay.MANAGE)">
<!-- 国际化清除多久前的日志 -->
<a-select
v-model:value="deleteDay"
:options="deleteDays"
:placeholder="$t('system.log.unipay-api-log.selectCleanDay')"
allow-clear
style="width: 180px"
/>
<a-button v-if="deleteDay" type="primary" @click="handleDeleteLogs">
<!-- 国际化清理 -->
{{ $t('system.log.common.clean') }}
</a-button>
</a-space>
</template>
</vxe-toolbar>
<!-- 数据表格 -->
<vxe-table ref="xTable" :row-config="{ keyField: 'id' }" :data="tableData" :loading="loading">
<!-- 序号 -->
<vxe-column type="seq" :title="$t('common.seq')" width="60" align="center" />
<!-- 商户号 -->
<vxe-column field="mchNo" :title="$t('system.log.unipay-api-log.mchNo')" :min-width="140" />
<!-- 请求ID -->
<vxe-column field="reqId" :title="$t('system.log.unipay-api-log.reqId')" :min-width="180" />
<!-- 接口标题 -->
<vxe-column field="apiTitle" :title="$t('system.log.unipay-api-log.apiTitle')" :min-width="120" />
<!-- 状态 -->
<vxe-column field="success" :title="$t('system.log.unipay-api-log.status')" :min-width="90" align="center">
<template #default="{ row }">
<a-tag :color="row.success ? 'green' : 'red'">
{{ row.success ? $t('common.success') : $t('common.fail') }}
</a-tag>
</template>
</vxe-column>
<!-- 耗时 -->
<vxe-column field="durationMs" :title="$t('system.log.unipay-api-log.durationMs')" :min-width="90" align="right">
<template #default="{ row }">
{{ row.durationMs != null ? `${row.durationMs}` : '' }}
</template>
</vxe-column>
<!-- 接入 IP -->
<vxe-column field="requestIp" :title="$t('system.log.unipay-api-log.requestIp')" :min-width="130" />
<!-- 错误提示 -->
<vxe-column field="errorMsg" :title="$t('system.log.unipay-api-log.errorMsg')" :min-width="150" />
<!-- 时间 -->
<vxe-column
field="operateTime"
:title="$t('system.log.unipay-api-log.operateTime')"
:min-width="170"
formatter="formatDateTime"
/>
<!-- 操作 -->
<vxe-column fixed="right" width="100" :show-overflow="false" :title="$t('common.operation')">
<template #default="{ row }">
<a-button
v-if="hasPermission(PermCodes.System.Log.Unipay.VIEW)"
type="link"
size="small"
@click="handleShow(row)"
>
{{ $t('common.view') }}
</a-button>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pageConfig.currentPage"
:page-size="pageConfig.pageSize"
:total="Number(pageConfig.total)"
@page-change="handlePageChange"
/>
</a-card>
</div>
<UnipayApiLogInfo ref="unipayApiLogInfoRef" />
</div>
</template>