refactor(risk): 黑名单宽抽屉对齐三态类型与平台微信应用

表单直接提交 ip/alipay_user/wechat_openid,微信必选平台支付应用;列表与命中筛选文案同步。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
DaxPay Dev
2026-07-24 14:32:45 +08:00
parent 06913f7198
commit ddaa6a14d0
14 changed files with 467 additions and 117 deletions

View File

@@ -36,23 +36,23 @@ export const PayBlacklistApi = {
/** 查询参数 */
export interface PayBlacklistQuery {
/** 类型 ip / open_id */
/** 类型 ip / alipay_user / wechat_openid */
type?: string;
/** 名单值 */
value?: string;
/** 状态 enable / disable */
status?: string;
/** 通道族 */
channel?: string;
/** 微信平台支付应用 AppId */
wxAppId?: string;
}
/** 表单参数 */
export interface PayBlacklistParam {
id?: string;
/** ip / alipay_user / wechat_openid */
type?: string;
value?: string;
channel?: string;
channelAppId?: string;
wxAppId?: string;
status?: string;
reason?: string;
expireTime?: string;
@@ -63,8 +63,7 @@ export interface PayBlacklistParam {
export interface PayBlacklistVo extends BaseEntity {
type?: string;
value?: string;
channel?: string;
channelAppId?: string;
wxAppId?: string;
status?: string;
reason?: string;
expireTime?: string;

View File

@@ -9,16 +9,21 @@
"type": "Type",
"value": "Value",
"channel": "Channel",
"channelAppId": "Channel AppId",
"channelAppId": "Channel app ID",
"status": "Status",
"reason": "Reason",
"expireTime": "Expire time",
"remark": "Remark",
"createTime": "Created at"
"createTime": "Created at",
"payApp": "Payment app",
"scope": "Scope",
"wxAppId": "WeChat app ID"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "Alipay user",
"wechat_openid": "WeChat OpenId"
},
"status": {
"enable": "Enabled",
@@ -27,7 +32,18 @@
"tip": {
"typeValueImmutable": "Type and value cannot be changed after creation",
"expireEmpty": "Leave empty for permanent",
"openIdBoundaryHint": "Barcode/APP/external browser scenarios fall back to post-pay hit recording only."
"openIdBoundaryHint": "Payment code / APP / external browser scenarios are logged after payment only.",
"alipayGlobalHint": "Alipay userId applies platform-wide and is not split by app or product.",
"wechatAppHint": "Only blocks openIds obtained under the selected platform payment app; pick the mini program app for cashier."
},
"scope": {
"global": "Global"
},
"placeholder": {
"ip": "Enter IP address",
"alipayUserId": "Enter Alipay userId",
"wechatOpenId": "Enter WeChat openId",
"payApp": "Select a platform payment app"
}
},
"hit": {

View File

@@ -8,17 +8,22 @@
"field": {
"type": "Jenis",
"value": "Nilai",
"channel": "Saluran",
"channelAppId": "AppId saluran",
"channel": "Kanal",
"channelAppId": "AppId kanal",
"status": "Status",
"reason": "Alasan",
"expireTime": "Waktu kedaluwarsa",
"remark": "Catatan",
"createTime": "Dibuat pada"
"createTime": "Dibuat pada",
"payApp": "Aplikasi pembayaran",
"scope": "Cakupan",
"wxAppId": "AppId WeChat"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "Pengguna Alipay",
"wechat_openid": "OpenId WeChat"
},
"status": {
"enable": "Aktif",
@@ -26,8 +31,19 @@
},
"tip": {
"typeValueImmutable": "Jenis dan nilai tidak dapat diubah setelah dibuat",
"expireEmpty": "Kosongkan untuk permanen",
"openIdBoundaryHint": "Barcode/APP/external browser scenarios fall back to post-pay hit recording only."
"expireEmpty": "Kosong berarti permanen",
"openIdBoundaryHint": "Kode bayar / APP / browser eksternal hanya dicatat setelah pembayaran.",
"alipayGlobalHint": "userId Alipay berlaku global di platform, tanpa membedakan aplikasi/produk.",
"wechatAppHint": "Hanya memblokir openId dari aplikasi pembayaran platform yang dipilih."
},
"scope": {
"global": "Global"
},
"placeholder": {
"ip": "Masukkan alamat IP",
"alipayUserId": "Masukkan userId Alipay",
"wechatOpenId": "Masukkan openId WeChat",
"payApp": "Pilih aplikasi pembayaran platform"
}
},
"hit": {

View File

@@ -8,26 +8,42 @@
"field": {
"type": "Type",
"value": "Value",
"channel": "Channel",
"channelAppId": "Channel AppId",
"channel": "チャネル",
"channelAppId": "チャネルアプリ ID",
"status": "Status",
"reason": "Reason",
"expireTime": "Expire time",
"remark": "Remark",
"createTime": "Created at"
"createTime": "Created at",
"payApp": "決済アプリ",
"scope": "適用範囲",
"wxAppId": "WeChat アプリ ID"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "Alipay ユーザー",
"wechat_openid": "WeChat OpenId"
},
"status": {
"enable": "Enabled",
"disable": "Disabled"
},
"tip": {
"typeValueImmutable": "Type and value cannot be changed after creation",
"expireEmpty": "Leave empty for permanent",
"openIdBoundaryHint": "バーコード/APP/外部ブラウザのシーンは事後記録のみ。"
"typeValueImmutable": "種別と値は作成後に変更できません",
"expireEmpty": "空欄は無期限",
"openIdBoundaryHint": "付款码 / APP / 外部ブラウザは事後記録のみです。",
"alipayGlobalHint": "Alipay userId はプラットフォーム全体に有効で、アプリ / 商品では区別しません。",
"wechatAppHint": "選択したプラットフォーム決済アプリで取得した openId のみ遮断します。"
},
"scope": {
"global": "グローバル"
},
"placeholder": {
"ip": "IP アドレスを入力",
"alipayUserId": "Alipay userId を入力",
"wechatOpenId": "WeChat openId を入力",
"payApp": "プラットフォーム決済アプリを選択"
}
},
"hit": {

View File

@@ -8,26 +8,42 @@
"field": {
"type": "Type",
"value": "Value",
"channel": "Channel",
"channelAppId": "Channel AppId",
"channel": "채널",
"channelAppId": "채널 앱 ID",
"status": "Status",
"reason": "Reason",
"expireTime": "Expire time",
"remark": "Remark",
"createTime": "Created at"
"createTime": "Created at",
"payApp": "결제 앱",
"scope": "적용 범위",
"wxAppId": "위챗 앱 ID"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "알리페이 사용자",
"wechat_openid": "위챗 OpenId"
},
"status": {
"enable": "Enabled",
"disable": "Disabled"
},
"tip": {
"typeValueImmutable": "Type and value cannot be changed after creation",
"expireEmpty": "Leave empty for permanent",
"openIdBoundaryHint": "바코드/APP/외부 브라우저 시나리오는 사후 기록만 합니다."
"typeValueImmutable": "유형과 값은 생성 후 변경할 수 없습니다",
"expireEmpty": "비워 두면 영구",
"openIdBoundaryHint": "결제 코드 / APP / 외부 브라우저는 사후 기록만 합니다.",
"alipayGlobalHint": "알리페이 userId는 플랫폼 전역에 적용되며 앱/상품으로 구분하지 않습니다.",
"wechatAppHint": "선택한 플랫폼 결제 앱에서 얻은 openId만 차단합니다."
},
"scope": {
"global": "전역"
},
"placeholder": {
"ip": "IP 주소를 입력하세요",
"alipayUserId": "알리페이 userId를 입력하세요",
"wechatOpenId": "위챗 openId를 입력하세요",
"payApp": "플랫폼 결제 앱을 선택하세요"
}
},
"hit": {

View File

@@ -14,11 +14,16 @@
"reason": "Sebab",
"expireTime": "Tamat tempoh",
"remark": "Catatan",
"createTime": "Dicipta pada"
"createTime": "Dicipta pada",
"payApp": "Aplikasi pembayaran",
"scope": "Skop",
"wxAppId": "AppId WeChat"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "Pengguna Alipay",
"wechat_openid": "OpenId WeChat"
},
"status": {
"enable": "Aktif",
@@ -26,8 +31,19 @@
},
"tip": {
"typeValueImmutable": "Jenis dan nilai tidak boleh diubah selepas dicipta",
"expireEmpty": "Biarkan kosong untuk kekal",
"openIdBoundaryHint": "Barcode/APP/external browser scenarios fall back to post-pay hit recording only."
"expireEmpty": "Kosong bermaksud kekal",
"openIdBoundaryHint": "Kod bayaran / APP / pelayar luaran hanya direkod selepas bayaran.",
"alipayGlobalHint": "userId Alipay berkuat kuasa seluruh platform, tidak dipecah mengikut aplikasi/produk.",
"wechatAppHint": "Hanya menyekat openId daripada aplikasi pembayaran platform yang dipilih."
},
"scope": {
"global": "Global"
},
"placeholder": {
"ip": "Masukkan alamat IP",
"alipayUserId": "Masukkan userId Alipay",
"wechatOpenId": "Masukkan openId WeChat",
"payApp": "Pilih aplikasi pembayaran platform"
}
},
"hit": {

View File

@@ -14,20 +14,36 @@
"reason": "เหตุผล",
"expireTime": "หมดอายุ",
"remark": "หมายเหตุ",
"createTime": "สร้างเมื่อ"
"createTime": "สร้างเมื่อ",
"payApp": "แอปชำระเงิน",
"scope": "ขอบเขต",
"wxAppId": "AppId WeChat"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "ผู้ใช้ Alipay",
"wechat_openid": "OpenId WeChat"
},
"status": {
"enable": "เปิดใช้",
"disable": "ปิดใช้"
},
"tip": {
"typeValueImmutable": "ประเภทและค่าไม่สามารถแก้ไขหลังสร้าง",
"expireEmpty": "เว้นว่างหากถาวร",
"openIdBoundaryHint": "Barcode/APP/external browser scenarios fall back to post-pay hit recording only."
"typeValueImmutable": "ประเภทและค่าแก้ไขไม่ได้หลังสร้าง",
"expireEmpty": "ว่าง = ถาวร",
"openIdBoundaryHint": "รหัสชำระเงิน / APP / เบราว์เซอร์ภายนอกจะบันทึกหลังชำระเงินเท่านั้น",
"alipayGlobalHint": "userId Alipay มีผลทั้งแพลตฟอร์ม ไม่แยกตามแอป/ผลิตภัณฑ์",
"wechatAppHint": "บล็อกเฉพาะ openId จากแอปชำระเงินแพลตฟอร์มที่เลือก"
},
"scope": {
"global": "ทั่วทั้งระบบ"
},
"placeholder": {
"ip": "กรอกที่อยู่ IP",
"alipayUserId": "กรอก userId Alipay",
"wechatOpenId": "กรอก openId WeChat",
"payApp": "เลือกแอปชำระเงินของแพลตฟอร์ม"
}
},
"hit": {

View File

@@ -14,11 +14,16 @@
"reason": "Lý do",
"expireTime": "Hết hạn",
"remark": "Ghi chú",
"createTime": "Thời gian tạo"
"createTime": "Thời gian tạo",
"payApp": "Ứng dụng thanh toán",
"scope": "Phạm vi",
"wxAppId": "AppId WeChat"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "Người dùng Alipay",
"wechat_openid": "OpenId WeChat"
},
"status": {
"enable": "Bật",
@@ -26,8 +31,19 @@
},
"tip": {
"typeValueImmutable": "Loại và giá trị không thể sửa sau khi tạo",
"expireEmpty": "Để trống nếu vĩnh viễn",
"openIdBoundaryHint": "Barcode/APP/external browser scenarios fall back to post-pay hit recording only."
"expireEmpty": "Để trống vĩnh viễn",
"openIdBoundaryHint": "Mã thanh toán / APP / trình duyệt ngoài chỉ ghi nhận sau thanh toán.",
"alipayGlobalHint": "userId Alipay áp dụng toàn nền tảng, không tách theo ứng dụng/sản phẩm.",
"wechatAppHint": "Chỉ chặn openId lấy từ ứng dụng thanh toán nền tảng đã chọn."
},
"scope": {
"global": "Toàn cục"
},
"placeholder": {
"ip": "Nhập địa chỉ IP",
"alipayUserId": "Nhập userId Alipay",
"wechatOpenId": "Nhập openId WeChat",
"payApp": "Chọn ứng dụng thanh toán nền tảng"
}
},
"hit": {

View File

@@ -8,26 +8,42 @@
"field": {
"type": "类型",
"value": "名单值",
"channel": "通道",
"channelAppId": "通道应用AppId",
"channel": "通道",
"channelAppId": "通道应用 AppId",
"payApp": "支付应用",
"scope": "作用范围",
"status": "状态",
"reason": "拉黑原因",
"expireTime": "过期时间",
"remark": "备注",
"createTime": "创建时间"
"createTime": "创建时间",
"wxAppId": "微信应用 AppId"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "支付宝用户",
"wechat_openid": "微信 OpenId"
},
"scope": {
"global": "全局"
},
"status": {
"enable": "启用",
"disable": "禁用"
},
"placeholder": {
"ip": "请输入 IP 地址",
"alipayUserId": "请输入支付宝 userId",
"wechatOpenId": "请输入微信 openId",
"payApp": "请选择平台支付应用"
},
"tip": {
"typeValueImmutable": "类型与名单值创建后不可修改",
"expireEmpty": "留空表示永久有效",
"openIdBoundaryHint": "付款码/APP/外部浏览器场景仅做事后补录。"
"openIdBoundaryHint": "付款码 / APP / 外部浏览器场景仅做事后补录。",
"alipayGlobalHint": "支付宝 userId 平台全局生效,不区分应用 / 支付产品。",
"wechatAppHint": "仅拦截在所选平台支付应用下取得的 openId收银台小程序请选对应小程序应用。"
}
},
"hit": {

View File

@@ -8,26 +8,42 @@
"field": {
"type": "类型",
"value": "名单值",
"channel": "通道",
"channelAppId": "通道应用AppId",
"channel": "通道",
"channelAppId": "通道應用 AppId",
"status": "状态",
"reason": "拉黑原因",
"expireTime": "过期时间",
"remark": "备注",
"createTime": "创建时间"
"createTime": "创建时间",
"payApp": "支付應用",
"scope": "作用範圍",
"wxAppId": "微信應用 AppId"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "支付寶用戶",
"wechat_openid": "微信 OpenId"
},
"status": {
"enable": "启用",
"disable": "禁用"
},
"tip": {
"typeValueImmutable": "类型与名单值创建后不可修改",
"typeValueImmutable": "類型與名單值建立後不可修改",
"expireEmpty": "留空表示永久有效",
"openIdBoundaryHint": "付款碼/APP/外部瀏覽器場景僅做事後補錄。"
"openIdBoundaryHint": "付款碼 / APP / 外部瀏覽器場景僅做事後補錄。",
"alipayGlobalHint": "支付寶 userId 平台全域生效,不區分應用 / 支付產品。",
"wechatAppHint": "僅攔截在所選平台支付應用下取得的 openId收銀台小程序請選對應小程序應用。"
},
"scope": {
"global": "全域"
},
"placeholder": {
"ip": "請輸入 IP 地址",
"alipayUserId": "請輸入支付寶 userId",
"wechatOpenId": "請輸入微信 openId",
"payApp": "請選擇平台支付應用"
}
},
"hit": {

View File

@@ -8,26 +8,42 @@
"field": {
"type": "类型",
"value": "名单值",
"channel": "通道",
"channelAppId": "通道应用AppId",
"channel": "通道",
"channelAppId": "通道應用 AppId",
"status": "状态",
"reason": "拉黑原因",
"expireTime": "过期时间",
"remark": "备注",
"createTime": "创建时间"
"createTime": "创建时间",
"payApp": "支付應用",
"scope": "作用範圍",
"wxAppId": "微信應用 AppId"
},
"type": {
"ip": "IP",
"open_id": "OpenId"
"open_id": "OpenId",
"alipay_user": "支付寶用戶",
"wechat_openid": "微信 OpenId"
},
"status": {
"enable": "启用",
"disable": "禁用"
},
"tip": {
"typeValueImmutable": "类型与名单值创建后不可修改",
"typeValueImmutable": "類型與名單值建立後不可修改",
"expireEmpty": "留空表示永久有效",
"openIdBoundaryHint": "付款碼/APP/外部瀏覽器場景僅做事後補錄。"
"openIdBoundaryHint": "付款碼 / APP / 外部瀏覽器場景僅做事後補錄。",
"alipayGlobalHint": "支付寶 userId 平台全域生效,不區分應用 / 支付產品。",
"wechatAppHint": "僅攔截在所選平台支付應用下取得的 openId收銀台小程序請選對應小程序應用。"
},
"scope": {
"global": "全域"
},
"placeholder": {
"ip": "請輸入 IP 地址",
"alipayUserId": "請輸入支付寶 userId",
"wechatOpenId": "請輸入微信 openId",
"payApp": "請選擇平台支付應用"
}
},
"hit": {

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import { $t } from '@vben/locales';
@@ -8,16 +8,23 @@
type PayBlacklistParam,
type PayBlacklistVo,
} from '#/api/payment/risk/blacklist.api';
import {
type WxPlatformApp,
WxPlatformAppApi,
} from '#/api/payment/wx/platform-app.api';
import { FormEditType } from '#/enums/formEditType';
import { useFormEdit } from '#/hooks/useFormEdit';
import { useMessage } from '#/hooks/useMessage';
/** 名单类型(与后端 type 一致) */
type BlacklistType = 'ip' | 'alipay_user' | 'wechat_openid';
const emit = defineEmits(['ok']);
const { message } = useMessage();
const formRef = ref();
const { visible, confirmLoading, title, initFormEditType, handleCancel, showable, formEditType } =
const { visible, confirmLoading, initFormEditType, handleCancel, showable, formEditType } =
useFormEdit();
const isAdd = computed(() => formEditType.value === FormEditType.Add);
@@ -27,22 +34,104 @@
id: '',
type: 'ip',
value: '',
channel: '',
channelAppId: '',
wxAppId: '',
status: 'enable',
reason: '',
expireTime: undefined,
remark: '',
});
const platformApps = ref<WxPlatformApp[]>([]);
const platformAppsLoading = ref(false);
const drawerTitle = computed(() => {
if (isAdd.value) {
// 新增黑名单
return $t('payment.risk.blacklist.add');
}
if (showable.value) {
// 查看黑名单
return $t('payment.risk.blacklist.viewTitle');
}
// 编辑黑名单
return $t('payment.risk.blacklist.editTitle');
});
const valuePlaceholder = computed(() => {
if (formState.value.type === 'ip') {
// IP 地址
return $t('payment.risk.blacklist.placeholder.ip');
}
if (formState.value.type === 'alipay_user') {
// 支付宝 userId
return $t('payment.risk.blacklist.placeholder.alipayUserId');
}
// 微信 openId
return $t('payment.risk.blacklist.placeholder.wechatOpenId');
});
const wxAppOptions = computed(() =>
platformApps.value.map((app) => ({
value: app.wxAppId || '',
label: formatWxAppLabel(app),
})),
);
/** 应用类型文案 */
function appTypeLabel(appType?: string) {
if (appType === 'mini_program') {
// 小程序
return $t('payment.wx.app.appTypeMiniProgram');
}
if (appType === 'official_account') {
// 公众号
return $t('payment.wx.app.appTypeOfficialAccount');
}
if (appType === 'mobile_app') {
// 移动应用
return $t('payment.wx.app.appTypeMobileApp');
}
return appType || '';
}
/** 下拉展示名称AppId· 类型 */
function formatWxAppLabel(app: WxPlatformApp) {
const name = app.appName || app.wxAppId || '';
const id = app.wxAppId || '';
const type = appTypeLabel(app.appType);
return type ? `${name}${id})· ${type}` : `${name}${id}`;
}
/** 加载平台微信应用 */
async function loadPlatformApps() {
if (platformApps.value.length > 0) {
return;
}
platformAppsLoading.value = true;
try {
const { data } = await WxPlatformAppApi.listAll();
platformApps.value = data || [];
} finally {
platformAppsLoading.value = false;
}
}
watch(
() => formState.value.type,
(val) => {
if (val === 'wechat_openid') {
void loadPlatformApps();
}
},
);
/** 重置表单 */
function resetForm() {
formState.value = {
id: '',
type: 'ip',
value: '',
channel: '',
channelAppId: '',
wxAppId: '',
status: 'enable',
reason: '',
expireTime: undefined,
@@ -57,12 +146,14 @@
try {
const { data } = await PayBlacklistApi.get(record.id!);
const row = data || record;
if (row.type === 'wechat_openid') {
await loadPlatformApps();
}
formState.value = {
id: row.id!,
type: row.type,
value: row.value,
channel: row.channel,
channelAppId: row.channelAppId,
wxAppId: row.wxAppId || '',
status: row.status,
reason: row.reason,
expireTime: row.expireTime,
@@ -93,6 +184,14 @@
await fillForm(record);
}
/** 切换类型时清理微信 AppId */
function handleTypeChange(val: string | BlacklistType) {
formState.value.type = val as BlacklistType;
if (val !== 'wechat_openid') {
formState.value.wxAppId = '';
}
}
/** 保存 */
async function handleOk() {
try {
@@ -102,10 +201,14 @@
}
confirmLoading.value = true;
try {
const payload: PayBlacklistParam = {
...formState.value,
wxAppId: formState.value.type === 'wechat_openid' ? formState.value.wxAppId || '' : '',
};
if (isAdd.value) {
await PayBlacklistApi.add(formState.value);
await PayBlacklistApi.add(payload);
} else {
await PayBlacklistApi.update(formState.value);
await PayBlacklistApi.update(payload);
}
message.success($t('common.saveSuccess'));
handleCancel();
@@ -119,26 +222,20 @@
</script>
<template>
<a-modal
<a-drawer
v-model:open="visible"
:title="
isAdd
? $t('payment.risk.blacklist.add')
: showable
? $t('payment.risk.blacklist.viewTitle')
: $t('payment.risk.blacklist.editTitle')
"
:width="560"
:title="drawerTitle"
:size="720"
:destroy-on-hidden="true"
:mask-closable="showable"
@cancel="handleCancel"
@close="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form
ref="formRef"
:model="formState"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
layout="vertical"
class="pt-2"
:disabled="showable"
>
<!-- 类型 -->
@@ -147,19 +244,50 @@
name="type"
:rules="[{ required: true, message: $t('common.pleaseSelect') }]"
>
<a-radio-group v-model:value="formState.type" button-style="solid" :disabled="!isAdd">
<a-radio-group
:value="formState.type"
button-style="solid"
:disabled="!isAdd"
@update:value="handleTypeChange"
>
<a-radio-button value="ip">{{ $t('payment.risk.blacklist.type.ip') }}</a-radio-button>
<a-radio-button value="open_id">{{ $t('payment.risk.blacklist.type.open_id') }}</a-radio-button>
<a-radio-button value="alipay_user">
{{ $t('payment.risk.blacklist.type.alipay_user') }}
</a-radio-button>
<a-radio-button value="wechat_openid">
{{ $t('payment.risk.blacklist.type.wechat_openid') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<!-- openId 类型场景边界提示 -->
<div v-if="formState.type === 'open_id'" class="mb-4 px-2">
<!-- 支付宝全局说明 -->
<div v-if="formState.type === 'alipay_user'" class="mb-4">
<a-alert
:message="$t('payment.risk.blacklist.tip.alipayGlobalHint')"
type="info"
show-icon
/>
</div>
<!-- 微信应用作用域说明 -->
<div v-if="formState.type === 'wechat_openid'" class="mb-4">
<a-alert
:message="$t('payment.risk.blacklist.tip.wechatAppHint')"
type="info"
show-icon
/>
</div>
<!-- openId 边界付款码等事后补录 -->
<div
v-if="formState.type === 'alipay_user' || formState.type === 'wechat_openid'"
class="mb-4"
>
<a-alert
:message="$t('payment.risk.blacklist.tip.openIdBoundaryHint')"
type="warning"
show-icon
/>
</div>
<!-- 名单值 -->
<a-form-item
:label="$t('payment.risk.blacklist.field.value')"
@@ -168,26 +296,33 @@
>
<a-input
v-model:value="formState.value"
:placeholder="$t('common.pleaseInput')"
:placeholder="valuePlaceholder"
:disabled="!isAdd"
:maxlength="128"
/>
</a-form-item>
<div v-if="!isAdd" class="mb-4 text-xs text-gray-400 text-center">
<div v-if="!isAdd" class="mb-4 text-center text-xs text-gray-400">
{{ $t('payment.risk.blacklist.tip.typeValueImmutable') }}
</div>
<!-- 通道族 -->
<a-form-item :label="$t('payment.risk.blacklist.field.channel')" name="channel">
<a-input v-model:value="formState.channel" :placeholder="$t('common.pleaseInput')" :maxlength="32" />
</a-form-item>
<!-- 通道 AppId -->
<a-form-item :label="$t('payment.risk.blacklist.field.channelAppId')" name="channelAppId">
<a-input
v-model:value="formState.channelAppId"
:placeholder="$t('common.pleaseInput')"
:maxlength="64"
<!-- 支付应用仅微信 -->
<a-form-item
v-if="formState.type === 'wechat_openid'"
:label="$t('payment.risk.blacklist.field.payApp')"
name="wxAppId"
:rules="[{ required: true, message: $t('common.pleaseSelect') }]"
>
<a-select
v-model:value="formState.wxAppId"
show-search
option-filter-prop="label"
:loading="platformAppsLoading"
:options="wxAppOptions"
:placeholder="$t('payment.risk.blacklist.placeholder.payApp')"
allow-clear
/>
</a-form-item>
<!-- 状态 -->
<a-form-item :label="$t('payment.risk.blacklist.field.status')" name="status">
<a-radio-group v-model:value="formState.status" button-style="solid">
@@ -197,7 +332,11 @@
</a-form-item>
<!-- 原因 -->
<a-form-item :label="$t('payment.risk.blacklist.field.reason')" name="reason">
<a-input v-model:value="formState.reason" :placeholder="$t('common.pleaseInput')" :maxlength="255" />
<a-input
v-model:value="formState.reason"
:placeholder="$t('common.pleaseInput')"
:maxlength="255"
/>
</a-form-item>
<!-- 过期 -->
<a-form-item :label="$t('payment.risk.blacklist.field.expireTime')" name="expireTime">
@@ -221,5 +360,5 @@
{{ $t('common.save') }}
</a-button>
</template>
</a-modal>
</a-drawer>
</template>

View File

@@ -12,6 +12,10 @@
type PayBlacklistQuery,
type PayBlacklistVo,
} from '#/api/payment/risk/blacklist.api';
import {
type WxPlatformApp,
WxPlatformAppApi,
} from '#/api/payment/wx/platform-app.api';
import { BQuery, type QueryField } from '#/components/query';
import { PermCodes } from '#/constants/perm-codes';
import { useMessage } from '#/hooks/useMessage';
@@ -31,6 +35,8 @@
const pageConfig = ref({ currentPage: 1, pageSize: 10, total: 0 });
const tableData = ref<PayBlacklistVo[]>([]);
const editRef = ref();
// 平台应用名缓存(列表作用范围展示)
const platformAppNameMap = ref<Record<string, string>>({});
const queryFields = computed<QueryField[]>(() => [
{
@@ -41,7 +47,8 @@
placeholder: $t('common.pleaseSelect'),
selectList: [
{ label: $t('payment.risk.blacklist.type.ip'), value: 'ip' },
{ label: $t('payment.risk.blacklist.type.open_id'), value: 'open_id' },
{ label: $t('payment.risk.blacklist.type.alipay_user'), value: 'alipay_user' },
{ label: $t('payment.risk.blacklist.type.wechat_openid'), value: 'wechat_openid' },
],
},
{
@@ -62,15 +69,24 @@
{ label: $t('payment.risk.blacklist.status.disable'), value: 'disable' },
],
},
{
type: 'string',
field: 'channel',
// 通道族
name: $t('payment.risk.blacklist.field.channel'),
placeholder: $t('common.pleaseInput'),
},
]);
/** 加载平台应用名称映射 */
async function loadPlatformAppNames() {
try {
const { data } = await WxPlatformAppApi.listAll();
const map: Record<string, string> = {};
(data || []).forEach((app: WxPlatformApp) => {
if (app.wxAppId) {
map[app.wxAppId] = app.appName || app.wxAppId;
}
});
platformAppNameMap.value = map;
} catch {
platformAppNameMap.value = {};
}
}
/** 分页查询 */
function queryPage() {
loading.value = true;
@@ -128,24 +144,45 @@
});
}
/** 类型列 */
function typeLabel(type?: string) {
if (type === 'open_id') return $t('payment.risk.blacklist.type.open_id');
if (type === 'ip') return $t('payment.risk.blacklist.type.ip');
if (type === 'ip') {
return $t('payment.risk.blacklist.type.ip');
}
if (type === 'alipay_user') {
return $t('payment.risk.blacklist.type.alipay_user');
}
if (type === 'wechat_openid') {
return $t('payment.risk.blacklist.type.wechat_openid');
}
return type || '';
}
/** 作用范围:全局 / 应用名或 AppId */
function scopeLabel(row: PayBlacklistVo) {
if (row.type === 'ip' || row.type === 'alipay_user') {
return $t('payment.risk.blacklist.scope.global');
}
if (row.type === 'wechat_openid' && row.wxAppId) {
const name = platformAppNameMap.value[row.wxAppId];
return name ? `${name}${row.wxAppId}` : row.wxAppId;
}
return '-';
}
function statusColor(status?: string) {
return status === 'enable' ? 'success' : 'default';
}
onMounted(() => {
xTable.value?.connectToolbar(xToolbar.value as VxeToolbarInstance);
void loadPlatformAppNames();
queryPage();
});
</script>
<template>
<div class="m-3 p-3 bg-background rounded-lg list-page-compact">
<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>
@@ -170,13 +207,15 @@
<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="type" :title="$t('payment.risk.blacklist.field.type')" width="100">
<vxe-column field="type" :title="$t('payment.risk.blacklist.field.type')" width="120">
<template #default="{ row }">{{ typeLabel(row.type) }}</template>
</vxe-column>
<!-- 名单值 -->
<vxe-column field="value" :title="$t('payment.risk.blacklist.field.value')" :min-width="180" />
<!-- 通道 -->
<vxe-column field="channel" :title="$t('payment.risk.blacklist.field.channel')" width="100" />
<!-- 作用范围 -->
<vxe-column field="wxAppId" :title="$t('payment.risk.blacklist.field.scope')" :min-width="200">
<template #default="{ row }">{{ scopeLabel(row) }}</template>
</vxe-column>
<!-- 状态 -->
<vxe-column field="status" :title="$t('payment.risk.blacklist.field.status')" width="100">
<template #default="{ row }">

View File

@@ -48,7 +48,8 @@
placeholder: $t('common.pleaseSelect'),
selectList: [
{ label: $t('payment.risk.blacklist.type.ip'), value: 'ip' },
{ label: $t('payment.risk.blacklist.type.open_id'), value: 'open_id' },
{ label: $t('payment.risk.blacklist.type.alipay_user'), value: 'alipay_user' },
{ label: $t('payment.risk.blacklist.type.wechat_openid'), value: 'wechat_openid' },
],
},
{
@@ -162,7 +163,19 @@
<template #default="{ row }">{{ phaseLabel(row.phase) }}</template>
</vxe-column>
<!-- 命中类型 -->
<vxe-column field="hitType" :title="$t('payment.risk.hit.field.hitType')" width="100" />
<vxe-column field="hitType" :title="$t('payment.risk.hit.field.hitType')" width="120">
<template #default="{ row }">
{{
row.hitType === 'ip'
? $t('payment.risk.blacklist.type.ip')
: row.hitType === 'alipay_user'
? $t('payment.risk.blacklist.type.alipay_user')
: row.hitType === 'wechat_openid'
? $t('payment.risk.blacklist.type.wechat_openid')
: row.hitType
}}
</template>
</vxe-column>
<!-- 命中值 -->
<vxe-column field="hitValue" :title="$t('payment.risk.hit.field.hitValue')" :min-width="160" />
<!-- 商户: 名称上 + 号下小字两排 -->