mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-11 07:05:33 +08:00
feat(admin): 敏感词管理页与多语种补全
新增词库/命中列表、平台配置敏感词策略 Tab 与权限文案;补齐六语及东盟四语 risk/sensitiveWord 与菜单标题,并增加 locale key 完整性检查脚本。
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import type { Result } from '#/types/web';
|
||||
|
||||
import { defHttp } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 平台敏感词策略配置 API(平台配置 Tab)
|
||||
*/
|
||||
export const SensitiveWordConfigApi = {
|
||||
/** 获取策略 */
|
||||
get(): Promise<Result<SensitiveWordConfig>> {
|
||||
return defHttp.get({ url: '/platform/config/sensitive-word/get' });
|
||||
},
|
||||
|
||||
/** 更新策略 */
|
||||
update(data: SensitiveWordConfig): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/platform/config/sensitive-word/update', data });
|
||||
},
|
||||
};
|
||||
|
||||
/** 敏感词策略 */
|
||||
export interface SensitiveWordConfig {
|
||||
/** 是否启用过滤 */
|
||||
enabled?: boolean;
|
||||
/** 是否回显命中词 */
|
||||
revealWord?: boolean;
|
||||
/** 是否写命中审计 */
|
||||
recordHit?: boolean;
|
||||
/** 原文摘要最大长度 */
|
||||
contentPreviewMaxLen?: number;
|
||||
}
|
||||
43
apps/daxpay-admin/src/api/system/sensitive-word-hit.api.ts
Normal file
43
apps/daxpay-admin/src/api/system/sensitive-word-hit.api.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { BaseEntity, PageResult, Result } from '#/types/web';
|
||||
|
||||
import { defHttp } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 敏感词命中记录 API(运营端 /admin/system/sensitive-word-hit)
|
||||
*/
|
||||
export const SensitiveWordHitApi = {
|
||||
/** 分页查询 */
|
||||
page(
|
||||
params: SensitiveWordHitQuery & { current: number; size: number },
|
||||
): Promise<Result<PageResult<SensitiveWordHitVo>>> {
|
||||
return defHttp.get({ url: '/admin/system/sensitive-word-hit/page', params });
|
||||
},
|
||||
|
||||
/** 详情 */
|
||||
getById(id: string): Promise<Result<SensitiveWordHitVo>> {
|
||||
return defHttp.get({ url: '/admin/system/sensitive-word-hit/get-by-id', params: { id } });
|
||||
},
|
||||
};
|
||||
|
||||
/** 查询参数 */
|
||||
export interface SensitiveWordHitQuery {
|
||||
hitWord?: string;
|
||||
scene?: string;
|
||||
source?: string;
|
||||
mchNo?: string;
|
||||
}
|
||||
|
||||
/** 列表/详情 VO */
|
||||
export interface SensitiveWordHitVo extends BaseEntity {
|
||||
wordId?: string;
|
||||
hitWord?: string;
|
||||
contentPreview?: string;
|
||||
scene?: string;
|
||||
source?: string;
|
||||
mchNo?: string;
|
||||
appId?: string;
|
||||
operatorId?: string;
|
||||
clientIp?: string;
|
||||
requestPath?: string;
|
||||
remark?: string;
|
||||
}
|
||||
75
apps/daxpay-admin/src/api/system/sensitive-word.api.ts
Normal file
75
apps/daxpay-admin/src/api/system/sensitive-word.api.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { BaseEntity, PageResult, Result } from '#/types/web';
|
||||
|
||||
import { defHttp } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 敏感词词库 API(运营端 /admin/system/sensitive-word)
|
||||
*/
|
||||
export const SensitiveWordApi = {
|
||||
/** 分页查询 */
|
||||
page(
|
||||
params: SensitiveWordQuery & { current: number; size: number },
|
||||
): Promise<Result<PageResult<SensitiveWordVo>>> {
|
||||
return defHttp.get({ url: '/admin/system/sensitive-word/page', params });
|
||||
},
|
||||
|
||||
/** 详情 */
|
||||
getById(id: string): Promise<Result<SensitiveWordVo>> {
|
||||
return defHttp.get({ url: '/admin/system/sensitive-word/get-by-id', params: { id } });
|
||||
},
|
||||
|
||||
/** 新增 */
|
||||
add(data: SensitiveWordParam): Promise<Result<SensitiveWordVo>> {
|
||||
return defHttp.post({ url: '/admin/system/sensitive-word/add', data });
|
||||
},
|
||||
|
||||
/** 修改 */
|
||||
update(data: SensitiveWordParam): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/system/sensitive-word/update', data });
|
||||
},
|
||||
|
||||
/** 删除 */
|
||||
delete(id: string): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/system/sensitive-word/delete', params: { id } });
|
||||
},
|
||||
|
||||
/** 词面是否存在 */
|
||||
existsByWord(word: string, id?: string): Promise<Result<boolean>> {
|
||||
return defHttp.get({ url: '/admin/system/sensitive-word/exists-by-word', params: { word, id } });
|
||||
},
|
||||
|
||||
/** 试检文本 */
|
||||
checkText(data: { text: string; recordHit?: boolean }): Promise<
|
||||
Result<{ hits: string[]; hit: boolean }>
|
||||
> {
|
||||
return defHttp.post({ url: '/admin/system/sensitive-word/check-text', data });
|
||||
},
|
||||
};
|
||||
|
||||
/** 查询参数 */
|
||||
export interface SensitiveWordQuery {
|
||||
word?: string;
|
||||
category?: string;
|
||||
status?: string;
|
||||
matchMode?: string;
|
||||
}
|
||||
|
||||
/** 表单参数 */
|
||||
export interface SensitiveWordParam {
|
||||
id?: string;
|
||||
word?: string;
|
||||
category?: string;
|
||||
matchMode?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/** 列表/详情 VO */
|
||||
export interface SensitiveWordVo extends BaseEntity {
|
||||
word?: string;
|
||||
category?: string;
|
||||
matchMode?: string;
|
||||
level?: string;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
}
|
||||
@@ -248,5 +248,14 @@ export const PermCodes = {
|
||||
MANAGE: 'system:protocol:manage',
|
||||
PUBLISH: 'system:protocol:publish',
|
||||
},
|
||||
/** 敏感词词库 */
|
||||
SensitiveWord: {
|
||||
VIEW: 'system:sensitive-word:view',
|
||||
MANAGE: 'system:sensitive-word:manage',
|
||||
},
|
||||
/** 敏感词命中记录 */
|
||||
SensitiveWordHit: {
|
||||
VIEW: 'system:sensitive-word-hit:view',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "Prefer Simplified Chinese words. Matching applies traditional-to-simplified and full-width normalization.",
|
||||
"word": {
|
||||
"add": "Add word",
|
||||
"edit": "Edit word",
|
||||
"view": "View word",
|
||||
"confirmDelete": "Delete this sensitive word?",
|
||||
"field": {
|
||||
"word": "Word",
|
||||
"category": "Category",
|
||||
"matchMode": "Match mode",
|
||||
"status": "Status",
|
||||
"remark": "Remark",
|
||||
"createTime": "Created at"
|
||||
},
|
||||
"category": {
|
||||
"politic": "Political",
|
||||
"porn": "Pornography",
|
||||
"violence": "Violence",
|
||||
"ad": "Ads",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "Contains",
|
||||
"exact": "Exact"
|
||||
},
|
||||
"status": {
|
||||
"enable": "Enabled",
|
||||
"disable": "Disabled"
|
||||
},
|
||||
"check": {
|
||||
"title": "Check text",
|
||||
"placeholder": "Enter text to check",
|
||||
"button": "Check",
|
||||
"hit": "Hit: {0}",
|
||||
"clean": "No sensitive words",
|
||||
"recordHit": "Record hit"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "Hit word",
|
||||
"contentPreview": "Content preview",
|
||||
"scene": "Scene",
|
||||
"source": "Source",
|
||||
"mchNo": "Merchant no.",
|
||||
"clientIp": "Client IP",
|
||||
"createTime": "Hit time"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "Sensitive words",
|
||||
"description": "Global switches for sensitive-word filtering (CN-focused content)",
|
||||
"enabled": "Enable filtering",
|
||||
"enabledTip": "When off, all text checks pass (can disable for overseas)",
|
||||
"revealWord": "Reveal hit word in errors",
|
||||
"revealWordTip": "Shows the matched word in API errors (probing risk)",
|
||||
"recordHit": "Record hits",
|
||||
"recordHitTip": "Write hit audit rows on block",
|
||||
"confirmSaveContent": "Saving will affect online filtering immediately. Continue?"
|
||||
}
|
||||
}
|
||||
78
apps/daxpay-admin/src/locales/langs/id-ID/payment/risk.json
Normal file
78
apps/daxpay-admin/src/locales/langs/id-ID/payment/risk.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"blacklist": {
|
||||
"title": "Daftar hitam",
|
||||
"add": "Tambah daftar hitam",
|
||||
"editTitle": "Edit daftar hitam",
|
||||
"viewTitle": "Lihat daftar hitam",
|
||||
"confirmDelete": "Hapus entri daftar hitam ini?",
|
||||
"field": {
|
||||
"type": "Jenis",
|
||||
"value": "Nilai",
|
||||
"channel": "Saluran",
|
||||
"channelAppId": "AppId saluran",
|
||||
"status": "Status",
|
||||
"reason": "Alasan",
|
||||
"expireTime": "Waktu kedaluwarsa",
|
||||
"remark": "Catatan",
|
||||
"createTime": "Dibuat pada"
|
||||
},
|
||||
"type": {
|
||||
"ip": "IP",
|
||||
"open_id": "OpenId"
|
||||
},
|
||||
"status": {
|
||||
"enable": "Aktif",
|
||||
"disable": "Nonaktif"
|
||||
},
|
||||
"tip": {
|
||||
"typeValueImmutable": "Jenis dan nilai tidak dapat diubah setelah dibuat",
|
||||
"expireEmpty": "Kosongkan untuk permanen"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"title": "Hit risiko",
|
||||
"handleTitle": "Tangani hit",
|
||||
"viewTitle": "Lihat hit",
|
||||
"field": {
|
||||
"phase": "Fase",
|
||||
"hitType": "Jenis hit",
|
||||
"hitValue": "Nilai hit",
|
||||
"mchNo": "No. merchant",
|
||||
"appId": "App ID",
|
||||
"tradeNo": "No. transaksi",
|
||||
"orderNo": "No. order",
|
||||
"bizOrderNo": "No. order bisnis",
|
||||
"method": "Metode",
|
||||
"channel": "Saluran",
|
||||
"clientIp": "IP klien",
|
||||
"openid": "OpenId",
|
||||
"buyerId": "BuyerId",
|
||||
"scene": "Skenario",
|
||||
"handleStatus": "Status penanganan",
|
||||
"handleRemark": "Catatan penanganan",
|
||||
"handleTime": "Waktu penanganan",
|
||||
"createTime": "Dibuat pada"
|
||||
},
|
||||
"phase": {
|
||||
"before_pay": "Sebelum bayar",
|
||||
"after_pay": "Setelah bayar"
|
||||
},
|
||||
"handleStatus": {
|
||||
"pending": "Menunggu",
|
||||
"ignored": "Diabaikan",
|
||||
"added_blacklist": "Ditambah ke daftar hitam",
|
||||
"merchant_disabled": "Merchant dinonaktifkan",
|
||||
"other": "Lainnya"
|
||||
},
|
||||
"scene": {
|
||||
"api": "API",
|
||||
"gateway": "Gateway",
|
||||
"code": "Kode QR",
|
||||
"manual": "Manual",
|
||||
"unknown": "Tidak diketahui"
|
||||
},
|
||||
"action": {
|
||||
"handle": "Tangani"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "Disarankan mendaftarkan kata dalam bahasa Mandarin sederhana; sistem menormalisasi ke sederhana dan half-width sebelum pencocokan.",
|
||||
"word": {
|
||||
"add": "Tambah kata sensitif",
|
||||
"edit": "Edit kata sensitif",
|
||||
"view": "Lihat kata sensitif",
|
||||
"confirmDelete": "Hapus kata sensitif ini?",
|
||||
"field": {
|
||||
"word": "Kata",
|
||||
"category": "Kategori",
|
||||
"matchMode": "Mode pencocokan",
|
||||
"status": "Status",
|
||||
"remark": "Catatan",
|
||||
"createTime": "Dibuat pada"
|
||||
},
|
||||
"category": {
|
||||
"politic": "Politik",
|
||||
"porn": "Pornografi",
|
||||
"violence": "Kekerasan",
|
||||
"ad": "Iklan",
|
||||
"custom": "Kustom"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "Mengandung",
|
||||
"exact": "Persis"
|
||||
},
|
||||
"status": {
|
||||
"enable": "Aktif",
|
||||
"disable": "Nonaktif"
|
||||
},
|
||||
"check": {
|
||||
"title": "Uji teks",
|
||||
"placeholder": "Masukkan teks untuk diperiksa",
|
||||
"button": "Periksa",
|
||||
"hit": "Terdeteksi: {0}",
|
||||
"clean": "Tidak ada kata sensitif",
|
||||
"recordHit": "Catat hit"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "Kata terdeteksi",
|
||||
"contentPreview": "Ringkasan teks",
|
||||
"scene": "Skenario",
|
||||
"source": "Sumber",
|
||||
"mchNo": "No. merchant",
|
||||
"clientIp": "IP klien",
|
||||
"createTime": "Waktu hit"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "Kata sensitif",
|
||||
"description": "Saklar global dan kebijakan filter kata sensitif teks platform",
|
||||
"enabled": "Aktifkan filter",
|
||||
"enabledTip": "Jika dimatikan, semua teks lolos (dapat dimatikan untuk deployment luar negeri)",
|
||||
"revealWord": "Tampilkan kata terdeteksi pada error",
|
||||
"revealWordTip": "Jika aktif, pesan error API memuat kata konkret (risiko probing)",
|
||||
"recordHit": "Catat audit hit",
|
||||
"recordHitTip": "Menulis ke tabel hit saat diblokir",
|
||||
"confirmSaveContent": "Penyimpanan akan langsung memengaruhi filter online. Lanjutkan?"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "辞書は簡体字中国語での登録を推奨します。照合前に繁体字→簡体字変換と全角正規化を行います。",
|
||||
"word": {
|
||||
"add": "センシティブワード追加",
|
||||
"edit": "センシティブワード編集",
|
||||
"view": "センシティブワード詳細",
|
||||
"confirmDelete": "このセンシティブワードを削除しますか?",
|
||||
"field": {
|
||||
"word": "ワード",
|
||||
"category": "分類",
|
||||
"matchMode": "一致モード",
|
||||
"status": "状態",
|
||||
"remark": "備考",
|
||||
"createTime": "作成日時"
|
||||
},
|
||||
"category": {
|
||||
"politic": "政治",
|
||||
"porn": "ポルノ",
|
||||
"violence": "暴力",
|
||||
"ad": "広告",
|
||||
"custom": "カスタム"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "含む",
|
||||
"exact": "完全一致"
|
||||
},
|
||||
"status": {
|
||||
"enable": "有効",
|
||||
"disable": "無効"
|
||||
},
|
||||
"check": {
|
||||
"title": "テキスト検査",
|
||||
"placeholder": "検査するテキストを入力",
|
||||
"button": "検査",
|
||||
"hit": "ヒット:{0}",
|
||||
"clean": "センシティブワードなし",
|
||||
"recordHit": "ヒット記録を保存"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "ヒットワード",
|
||||
"contentPreview": "原文要約",
|
||||
"scene": "シーン",
|
||||
"source": "ソース",
|
||||
"mchNo": "加盟店番号",
|
||||
"clientIp": "クライアントIP",
|
||||
"createTime": "ヒット日時"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "センシティブワード",
|
||||
"description": "プラットフォームのテキスト用センシティブワードフィルタの全体スイッチと方針",
|
||||
"enabled": "フィルタを有効化",
|
||||
"enabledTip": "オフにするとすべてのテキスト検証を通過します(海外展開時は無効化可)",
|
||||
"revealWord": "エラーにヒットワードを表示",
|
||||
"revealWordTip": "有効にするとAPIエラー文に具体的なワードが含まれるため、探査リスクがあります",
|
||||
"recordHit": "ヒット監査を記録",
|
||||
"recordHitTip": "遮断時にヒット記録テーブルへ書き込みます",
|
||||
"confirmSaveContent": "保存後はオンラインの遮断方針に即時反映されます。保存しますか?"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "단어는 간체 중국어로 등록하는 것을 권장합니다. 매칭 전 번체→간체 변환 및 전각 정규화를 수행합니다.",
|
||||
"word": {
|
||||
"add": "민감어 추가",
|
||||
"edit": "민감어 편집",
|
||||
"view": "민감어 보기",
|
||||
"confirmDelete": "이 민감어를 삭제하시겠습니까?",
|
||||
"field": {
|
||||
"word": "민감어",
|
||||
"category": "분류",
|
||||
"matchMode": "일치 모드",
|
||||
"status": "상태",
|
||||
"remark": "비고",
|
||||
"createTime": "생성 시간"
|
||||
},
|
||||
"category": {
|
||||
"politic": "정치",
|
||||
"porn": "음란",
|
||||
"violence": "폭력",
|
||||
"ad": "광고",
|
||||
"custom": "사용자 정의"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "포함",
|
||||
"exact": "정확 일치"
|
||||
},
|
||||
"status": {
|
||||
"enable": "사용",
|
||||
"disable": "중지"
|
||||
},
|
||||
"check": {
|
||||
"title": "텍스트 검사",
|
||||
"placeholder": "검사할 텍스트를 입력하세요",
|
||||
"button": "검사",
|
||||
"hit": "적중: {0}",
|
||||
"clean": "민감어 없음",
|
||||
"recordHit": "적중 기록 저장"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "적중 단어",
|
||||
"contentPreview": "원문 요약",
|
||||
"scene": "장면",
|
||||
"source": "출처",
|
||||
"mchNo": "가맹점 번호",
|
||||
"clientIp": "클라이언트 IP",
|
||||
"createTime": "적중 시간"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "민감어",
|
||||
"description": "플랫폼 텍스트 민감어 필터의 전역 스위치 및 정책",
|
||||
"enabled": "필터 사용",
|
||||
"enabledTip": "끄면 모든 텍스트 검증이 통과됩니다(해외 배포 시 비활성화 가능)",
|
||||
"revealWord": "오류에 적중 단어 표시",
|
||||
"revealWordTip": "켜면 API 오류 메시지에 구체적 단어가 포함되어 탐색 위험이 있습니다",
|
||||
"recordHit": "적중 감사 기록",
|
||||
"recordHitTip": "차단 시 적중 기록 테이블에 기록합니다",
|
||||
"confirmSaveContent": "저장 후 온라인 차단 정책에 즉시 반영됩니다. 저장하시겠습니까?"
|
||||
}
|
||||
}
|
||||
78
apps/daxpay-admin/src/locales/langs/ms-MY/payment/risk.json
Normal file
78
apps/daxpay-admin/src/locales/langs/ms-MY/payment/risk.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"blacklist": {
|
||||
"title": "Senarai hitam",
|
||||
"add": "Tambah senarai hitam",
|
||||
"editTitle": "Edit senarai hitam",
|
||||
"viewTitle": "Lihat senarai hitam",
|
||||
"confirmDelete": "Padam entri senarai hitam ini?",
|
||||
"field": {
|
||||
"type": "Jenis",
|
||||
"value": "Nilai",
|
||||
"channel": "Saluran",
|
||||
"channelAppId": "AppId saluran",
|
||||
"status": "Status",
|
||||
"reason": "Sebab",
|
||||
"expireTime": "Tamat tempoh",
|
||||
"remark": "Catatan",
|
||||
"createTime": "Dicipta pada"
|
||||
},
|
||||
"type": {
|
||||
"ip": "IP",
|
||||
"open_id": "OpenId"
|
||||
},
|
||||
"status": {
|
||||
"enable": "Aktif",
|
||||
"disable": "Nyahaktif"
|
||||
},
|
||||
"tip": {
|
||||
"typeValueImmutable": "Jenis dan nilai tidak boleh diubah selepas dicipta",
|
||||
"expireEmpty": "Biarkan kosong untuk kekal"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"title": "Hit risiko",
|
||||
"handleTitle": "Urus hit",
|
||||
"viewTitle": "Lihat hit",
|
||||
"field": {
|
||||
"phase": "Fasa",
|
||||
"hitType": "Jenis hit",
|
||||
"hitValue": "Nilai hit",
|
||||
"mchNo": "No. peniaga",
|
||||
"appId": "App ID",
|
||||
"tradeNo": "No. dagangan",
|
||||
"orderNo": "No. pesanan",
|
||||
"bizOrderNo": "No. pesanan perniagaan",
|
||||
"method": "Kaedah",
|
||||
"channel": "Saluran",
|
||||
"clientIp": "IP klien",
|
||||
"openid": "OpenId",
|
||||
"buyerId": "BuyerId",
|
||||
"scene": "Senario",
|
||||
"handleStatus": "Status urusan",
|
||||
"handleRemark": "Catatan urusan",
|
||||
"handleTime": "Masa urusan",
|
||||
"createTime": "Dicipta pada"
|
||||
},
|
||||
"phase": {
|
||||
"before_pay": "Sebelum bayar",
|
||||
"after_pay": "Selepas bayar"
|
||||
},
|
||||
"handleStatus": {
|
||||
"pending": "Menunggu",
|
||||
"ignored": "Diabaikan",
|
||||
"added_blacklist": "Ditambah ke senarai hitam",
|
||||
"merchant_disabled": "Peniaga dinyahaktif",
|
||||
"other": "Lain-lain"
|
||||
},
|
||||
"scene": {
|
||||
"api": "API",
|
||||
"gateway": "Gateway",
|
||||
"code": "Kod QR",
|
||||
"manual": "Manual",
|
||||
"unknown": "Tidak diketahui"
|
||||
},
|
||||
"action": {
|
||||
"handle": "Urus"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "Disyorkan daftar perkataan dalam Cina ringkas; sistem menormalkan ke ringkas dan separuh lebar sebelum padanan.",
|
||||
"word": {
|
||||
"add": "Tambah perkataan sensitif",
|
||||
"edit": "Edit perkataan sensitif",
|
||||
"view": "Lihat perkataan sensitif",
|
||||
"confirmDelete": "Padam perkataan sensitif ini?",
|
||||
"field": {
|
||||
"word": "Perkataan",
|
||||
"category": "Kategori",
|
||||
"matchMode": "Mod padanan",
|
||||
"status": "Status",
|
||||
"remark": "Catatan",
|
||||
"createTime": "Dicipta pada"
|
||||
},
|
||||
"category": {
|
||||
"politic": "Politik",
|
||||
"porn": "Pornografi",
|
||||
"violence": "Keganasan",
|
||||
"ad": "Iklan",
|
||||
"custom": "Tersuai"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "Mengandungi",
|
||||
"exact": "Tepat"
|
||||
},
|
||||
"status": {
|
||||
"enable": "Aktif",
|
||||
"disable": "Nyahaktif"
|
||||
},
|
||||
"check": {
|
||||
"title": "Uji teks",
|
||||
"placeholder": "Masukkan teks untuk diperiksa",
|
||||
"button": "Periksa",
|
||||
"hit": "Dikesan: {0}",
|
||||
"clean": "Tiada perkataan sensitif",
|
||||
"recordHit": "Rekod hit"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "Perkataan dikesan",
|
||||
"contentPreview": "Ringkasan teks",
|
||||
"scene": "Senario",
|
||||
"source": "Sumber",
|
||||
"mchNo": "No. peniaga",
|
||||
"clientIp": "IP klien",
|
||||
"createTime": "Masa hit"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "Perkataan sensitif",
|
||||
"description": "Suis global dan polisi penapis perkataan sensitif platform",
|
||||
"enabled": "Dayakan penapis",
|
||||
"enabledTip": "Jika dimatikan, semua teks lulus (boleh dimatikan untuk luar negara)",
|
||||
"revealWord": "Papar perkataan dikesan dalam ralat",
|
||||
"revealWordTip": "Jika aktif, mesej ralat API mengandungi perkataan sebenar (risiko probing)",
|
||||
"recordHit": "Rekod audit hit",
|
||||
"recordHitTip": "Tulis ke jadual hit apabila disekat",
|
||||
"confirmSaveContent": "Simpan akan menjejaskan penapis dalam talian serta-merta. Teruskan?"
|
||||
}
|
||||
}
|
||||
78
apps/daxpay-admin/src/locales/langs/th-TH/payment/risk.json
Normal file
78
apps/daxpay-admin/src/locales/langs/th-TH/payment/risk.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"blacklist": {
|
||||
"title": "บัญชีดำ",
|
||||
"add": "เพิ่มบัญชีดำ",
|
||||
"editTitle": "แก้ไขบัญชีดำ",
|
||||
"viewTitle": "ดูบัญชีดำ",
|
||||
"confirmDelete": "ลบรายการบัญชีดำนี้?",
|
||||
"field": {
|
||||
"type": "ประเภท",
|
||||
"value": "ค่า",
|
||||
"channel": "ช่องทาง",
|
||||
"channelAppId": "AppId ช่องทาง",
|
||||
"status": "สถานะ",
|
||||
"reason": "เหตุผล",
|
||||
"expireTime": "หมดอายุ",
|
||||
"remark": "หมายเหตุ",
|
||||
"createTime": "สร้างเมื่อ"
|
||||
},
|
||||
"type": {
|
||||
"ip": "IP",
|
||||
"open_id": "OpenId"
|
||||
},
|
||||
"status": {
|
||||
"enable": "เปิดใช้",
|
||||
"disable": "ปิดใช้"
|
||||
},
|
||||
"tip": {
|
||||
"typeValueImmutable": "ประเภทและค่าไม่สามารถแก้ไขหลังสร้าง",
|
||||
"expireEmpty": "เว้นว่างหากถาวร"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"title": "การตรวจจับความเสี่ยง",
|
||||
"handleTitle": "จัดการรายการ",
|
||||
"viewTitle": "ดูรายการ",
|
||||
"field": {
|
||||
"phase": "ช่วง",
|
||||
"hitType": "ประเภทการตรวจจับ",
|
||||
"hitValue": "ค่าที่ตรวจจับ",
|
||||
"mchNo": "รหัสร้านค้า",
|
||||
"appId": "App ID",
|
||||
"tradeNo": "เลขธุรกรรม",
|
||||
"orderNo": "เลขออเดอร์",
|
||||
"bizOrderNo": "เลขออเดอร์ธุรกิจ",
|
||||
"method": "วิธีชำระ",
|
||||
"channel": "ช่องทาง",
|
||||
"clientIp": "IP ลูกค้า",
|
||||
"openid": "OpenId",
|
||||
"buyerId": "BuyerId",
|
||||
"scene": "ฉาก",
|
||||
"handleStatus": "สถานะจัดการ",
|
||||
"handleRemark": "หมายเหตุจัดการ",
|
||||
"handleTime": "เวลาจัดการ",
|
||||
"createTime": "สร้างเมื่อ"
|
||||
},
|
||||
"phase": {
|
||||
"before_pay": "ก่อนชำระ",
|
||||
"after_pay": "หลังชำระ"
|
||||
},
|
||||
"handleStatus": {
|
||||
"pending": "รอดำเนินการ",
|
||||
"ignored": "เพิกเฉย",
|
||||
"added_blacklist": "เพิ่มเข้าบัญชีดำแล้ว",
|
||||
"merchant_disabled": "ปิดร้านค้าแล้ว",
|
||||
"other": "อื่นๆ"
|
||||
},
|
||||
"scene": {
|
||||
"api": "API",
|
||||
"gateway": "Gateway",
|
||||
"code": "คิวอาร์โค้ด",
|
||||
"manual": "ด้วยตนเอง",
|
||||
"unknown": "ไม่ทราบ"
|
||||
},
|
||||
"action": {
|
||||
"handle": "จัดการ"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "แนะนำลงทะเบียนคำด้วยภาษาจีนตัวย่อ ระบบจะแปลงตัวเต็ม→ตัวย่อและทำให้ครึ่งความกว้างก่อนจับคู่",
|
||||
"word": {
|
||||
"add": "เพิ่มคำละเอียดอ่อน",
|
||||
"edit": "แก้ไขคำละเอียดอ่อน",
|
||||
"view": "ดูคำละเอียดอ่อน",
|
||||
"confirmDelete": "ลบคำละเอียดอ่อนนี้?",
|
||||
"field": {
|
||||
"word": "คำ",
|
||||
"category": "หมวดหมู่",
|
||||
"matchMode": "โหมดจับคู่",
|
||||
"status": "สถานะ",
|
||||
"remark": "หมายเหตุ",
|
||||
"createTime": "สร้างเมื่อ"
|
||||
},
|
||||
"category": {
|
||||
"politic": "การเมือง",
|
||||
"porn": "ลามก",
|
||||
"violence": "ความรุนแรง",
|
||||
"ad": "โฆษณา",
|
||||
"custom": "กำหนดเอง"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "มีคำ",
|
||||
"exact": "ตรงทั้งหมด"
|
||||
},
|
||||
"status": {
|
||||
"enable": "เปิดใช้",
|
||||
"disable": "ปิดใช้"
|
||||
},
|
||||
"check": {
|
||||
"title": "ตรวจข้อความ",
|
||||
"placeholder": "ป้อนข้อความเพื่อตรวจ",
|
||||
"button": "ตรวจ",
|
||||
"hit": "พบ: {0}",
|
||||
"clean": "ไม่พบคำละเอียดอ่อน",
|
||||
"recordHit": "บันทึกการพบ"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "คำที่พบ",
|
||||
"contentPreview": "สรุปข้อความ",
|
||||
"scene": "ฉาก",
|
||||
"source": "แหล่งที่มา",
|
||||
"mchNo": "รหัสร้านค้า",
|
||||
"clientIp": "IP ลูกค้า",
|
||||
"createTime": "เวลาที่พบ"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "คำละเอียดอ่อน",
|
||||
"description": "สวิตช์และนโยบายตัวกรองคำละเอียดอ่อนทั้งระบบ",
|
||||
"enabled": "เปิดตัวกรอง",
|
||||
"enabledTip": "ปิดแล้วข้อความทั้งหมดจะผ่าน (ปิดได้เมื่อใช้งานต่างประเทศ)",
|
||||
"revealWord": "แสดงคำที่พบในข้อผิดพลาด",
|
||||
"revealWordTip": "เปิดแล้วข้อความ error API จะมีคำจริง (มีความเสี่ยงถูกสำรวจ)",
|
||||
"recordHit": "บันทึกการตรวจจับ",
|
||||
"recordHitTip": "เขียนตาราง hit เมื่อถูกบล็อก",
|
||||
"confirmSaveContent": "การบันทึกจะมีผลกับตัวกรองออนไลน์ทันที ดำเนินการต่อ?"
|
||||
}
|
||||
}
|
||||
78
apps/daxpay-admin/src/locales/langs/vi-VN/payment/risk.json
Normal file
78
apps/daxpay-admin/src/locales/langs/vi-VN/payment/risk.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"blacklist": {
|
||||
"title": "Danh sách đen",
|
||||
"add": "Thêm danh sách đen",
|
||||
"editTitle": "Sửa danh sách đen",
|
||||
"viewTitle": "Xem danh sách đen",
|
||||
"confirmDelete": "Xóa mục danh sách đen này?",
|
||||
"field": {
|
||||
"type": "Loại",
|
||||
"value": "Giá trị",
|
||||
"channel": "Kênh",
|
||||
"channelAppId": "AppId kênh",
|
||||
"status": "Trạng thái",
|
||||
"reason": "Lý do",
|
||||
"expireTime": "Hết hạn",
|
||||
"remark": "Ghi chú",
|
||||
"createTime": "Thời gian tạo"
|
||||
},
|
||||
"type": {
|
||||
"ip": "IP",
|
||||
"open_id": "OpenId"
|
||||
},
|
||||
"status": {
|
||||
"enable": "Bật",
|
||||
"disable": "Tắt"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"title": "Lượt chặn rủi ro",
|
||||
"handleTitle": "Xử lý lượt chặn",
|
||||
"viewTitle": "Xem lượt chặn",
|
||||
"field": {
|
||||
"phase": "Giai đoạn",
|
||||
"hitType": "Loại chặn",
|
||||
"hitValue": "Giá trị chặn",
|
||||
"mchNo": "Mã merchant",
|
||||
"appId": "App ID",
|
||||
"tradeNo": "Mã giao dịch",
|
||||
"orderNo": "Mã đơn",
|
||||
"bizOrderNo": "Mã đơn nghiệp vụ",
|
||||
"method": "Phương thức",
|
||||
"channel": "Kênh",
|
||||
"clientIp": "IP client",
|
||||
"openid": "OpenId",
|
||||
"buyerId": "BuyerId",
|
||||
"scene": "Kịch bản",
|
||||
"handleStatus": "Trạng thái xử lý",
|
||||
"handleRemark": "Ghi chú xử lý",
|
||||
"handleTime": "Thời gian xử lý",
|
||||
"createTime": "Thời gian tạo"
|
||||
},
|
||||
"phase": {
|
||||
"before_pay": "Trước thanh toán",
|
||||
"after_pay": "Sau thanh toán"
|
||||
},
|
||||
"handleStatus": {
|
||||
"pending": "Chờ xử lý",
|
||||
"ignored": "Bỏ qua",
|
||||
"added_blacklist": "Đã thêm vào danh sách đen",
|
||||
"merchant_disabled": "Merchant đã khóa",
|
||||
"other": "Khác"
|
||||
},
|
||||
"scene": {
|
||||
"api": "API",
|
||||
"gateway": "Gateway",
|
||||
"code": "Mã QR",
|
||||
"manual": "Thủ công",
|
||||
"unknown": "Không rõ"
|
||||
},
|
||||
"action": {
|
||||
"handle": "Xử lý"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "Nên đăng ký từ bằng tiếng Trung giản thể; hệ thống chuẩn hóa phồn thể→giản thể và full-width trước khi khớp.",
|
||||
"word": {
|
||||
"add": "Thêm từ nhạy cảm",
|
||||
"edit": "Sửa từ nhạy cảm",
|
||||
"view": "Xem từ nhạy cảm",
|
||||
"confirmDelete": "Xóa từ nhạy cảm này?",
|
||||
"field": {
|
||||
"word": "Từ",
|
||||
"category": "Phân loại",
|
||||
"matchMode": "Chế độ khớp",
|
||||
"status": "Trạng thái",
|
||||
"remark": "Ghi chú",
|
||||
"createTime": "Thời gian tạo"
|
||||
},
|
||||
"category": {
|
||||
"politic": "Chính trị",
|
||||
"porn": "Khiêu dâm",
|
||||
"violence": "Bạo lực",
|
||||
"ad": "Quảng cáo",
|
||||
"custom": "Tùy chỉnh"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "Chứa",
|
||||
"exact": "Chính xác"
|
||||
},
|
||||
"status": {
|
||||
"enable": "Bật",
|
||||
"disable": "Tắt"
|
||||
},
|
||||
"check": {
|
||||
"title": "Kiểm tra văn bản",
|
||||
"placeholder": "Nhập văn bản để kiểm tra",
|
||||
"button": "Kiểm tra",
|
||||
"hit": "Trúng: {0}",
|
||||
"clean": "Không có từ nhạy cảm",
|
||||
"recordHit": "Ghi nhận lượt trúng"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "Từ trúng",
|
||||
"contentPreview": "Tóm tắt nội dung",
|
||||
"scene": "Kịch bản",
|
||||
"source": "Nguồn",
|
||||
"mchNo": "Mã merchant",
|
||||
"clientIp": "IP client",
|
||||
"createTime": "Thời gian trúng"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "Từ nhạy cảm",
|
||||
"description": "Công tắc và chính sách lọc từ nhạy cảm toàn cục cho văn bản nền tảng",
|
||||
"enabled": "Bật bộ lọc",
|
||||
"enabledTip": "Tắt thì mọi kiểm tra văn bản đều qua (có thể tắt khi triển khai nước ngoài)",
|
||||
"revealWord": "Hiện từ trúng trong lỗi",
|
||||
"revealWordTip": "Bật thì thông báo lỗi API có từ cụ thể (rủi ro dò quét)",
|
||||
"recordHit": "Ghi nhật ký trúng",
|
||||
"recordHitTip": "Ghi vào bảng hit khi chặn",
|
||||
"confirmSaveContent": "Lưu sẽ ảnh hưởng ngay bộ lọc online. Tiếp tục?"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "词库建议录入简体中文;系统匹配前会做繁转简与全角规范化。",
|
||||
"word": {
|
||||
"add": "新增敏感词",
|
||||
"edit": "编辑敏感词",
|
||||
"view": "查看敏感词",
|
||||
"confirmDelete": "确认删除该敏感词?",
|
||||
"field": {
|
||||
"word": "敏感词",
|
||||
"category": "分类",
|
||||
"matchMode": "匹配模式",
|
||||
"status": "状态",
|
||||
"remark": "备注",
|
||||
"createTime": "创建时间"
|
||||
},
|
||||
"category": {
|
||||
"politic": "政治",
|
||||
"porn": "色情",
|
||||
"violence": "暴力",
|
||||
"ad": "广告",
|
||||
"custom": "自定义"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "包含",
|
||||
"exact": "精确"
|
||||
},
|
||||
"status": {
|
||||
"enable": "启用",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"check": {
|
||||
"title": "试检文本",
|
||||
"placeholder": "输入文本检测是否命中敏感词",
|
||||
"button": "检测",
|
||||
"hit": "命中:{0}",
|
||||
"clean": "未命中敏感词",
|
||||
"recordHit": "写入命中记录"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "命中词",
|
||||
"contentPreview": "原文摘要",
|
||||
"scene": "场景",
|
||||
"source": "来源",
|
||||
"mchNo": "商户号",
|
||||
"clientIp": "客户端IP",
|
||||
"createTime": "命中时间"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "敏感词",
|
||||
"description": "控制平台文本敏感词过滤的总开关与策略(国内主场景)",
|
||||
"enabled": "启用敏感词过滤",
|
||||
"enabledTip": "关闭后所有文本校验放行(海外部署可关)",
|
||||
"revealWord": "错误回显命中词",
|
||||
"revealWordTip": "开启后对外错误文案会带出具体敏感词,有探测风险",
|
||||
"recordHit": "记录命中审计",
|
||||
"recordHitTip": "拦截时写入命中记录表",
|
||||
"confirmSaveContent": "保存后将立即影响线上敏感词拦截策略,确认保存?"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "詞庫建議錄入簡體中文;系統匹配前會做繁轉簡與全角規範化。",
|
||||
"word": {
|
||||
"add": "新增敏感詞",
|
||||
"edit": "編輯敏感詞",
|
||||
"view": "查看敏感詞",
|
||||
"confirmDelete": "確認刪除該敏感詞?",
|
||||
"field": {
|
||||
"word": "敏感詞",
|
||||
"category": "分類",
|
||||
"matchMode": "匹配模式",
|
||||
"status": "狀態",
|
||||
"remark": "備註",
|
||||
"createTime": "建立時間"
|
||||
},
|
||||
"category": {
|
||||
"politic": "政治",
|
||||
"porn": "色情",
|
||||
"violence": "暴力",
|
||||
"ad": "廣告",
|
||||
"custom": "自訂"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "包含",
|
||||
"exact": "精確"
|
||||
},
|
||||
"status": {
|
||||
"enable": "啟用",
|
||||
"disable": "停用"
|
||||
},
|
||||
"check": {
|
||||
"title": "試檢文字",
|
||||
"placeholder": "輸入文字檢測是否命中敏感詞",
|
||||
"button": "檢測",
|
||||
"hit": "命中:{0}",
|
||||
"clean": "未命中敏感詞",
|
||||
"recordHit": "寫入命中記錄"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "命中詞",
|
||||
"contentPreview": "原文摘要",
|
||||
"scene": "場景",
|
||||
"source": "來源",
|
||||
"mchNo": "商戶號",
|
||||
"clientIp": "用戶端IP",
|
||||
"createTime": "命中時間"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "敏感詞",
|
||||
"description": "控制平台文字敏感詞過濾的總開關與策略(國內主場景)",
|
||||
"enabled": "啟用敏感詞過濾",
|
||||
"enabledTip": "關閉後所有文字校驗放行(海外部署可關)",
|
||||
"revealWord": "錯誤回顯命中詞",
|
||||
"revealWordTip": "開啟後對外錯誤文案會帶出具體敏感詞,有探測風險",
|
||||
"recordHit": "記錄命中審計",
|
||||
"recordHitTip": "攔截時寫入命中記錄表",
|
||||
"confirmSaveContent": "儲存後將立即影響線上敏感詞攔截策略,確認儲存?"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"tip": "詞庫建議錄入簡體中文;系統匹配前會做繁轉簡與全角規範化。",
|
||||
"word": {
|
||||
"add": "新增敏感詞",
|
||||
"edit": "編輯敏感詞",
|
||||
"view": "查看敏感詞",
|
||||
"confirmDelete": "確認刪除該敏感詞?",
|
||||
"field": {
|
||||
"word": "敏感詞",
|
||||
"category": "分類",
|
||||
"matchMode": "匹配模式",
|
||||
"status": "狀態",
|
||||
"remark": "備註",
|
||||
"createTime": "建立時間"
|
||||
},
|
||||
"category": {
|
||||
"politic": "政治",
|
||||
"porn": "色情",
|
||||
"violence": "暴力",
|
||||
"ad": "廣告",
|
||||
"custom": "自訂"
|
||||
},
|
||||
"matchMode": {
|
||||
"contains": "包含",
|
||||
"exact": "精確"
|
||||
},
|
||||
"status": {
|
||||
"enable": "啟用",
|
||||
"disable": "停用"
|
||||
},
|
||||
"check": {
|
||||
"title": "試檢文字",
|
||||
"placeholder": "輸入文字檢測是否命中敏感詞",
|
||||
"button": "檢測",
|
||||
"hit": "命中:{0}",
|
||||
"clean": "未命中敏感詞",
|
||||
"recordHit": "寫入命中記錄"
|
||||
}
|
||||
},
|
||||
"hit": {
|
||||
"field": {
|
||||
"hitWord": "命中詞",
|
||||
"contentPreview": "原文摘要",
|
||||
"scene": "場景",
|
||||
"source": "來源",
|
||||
"mchNo": "商戶號",
|
||||
"clientIp": "用戶端IP",
|
||||
"createTime": "命中時間"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"title": "敏感詞",
|
||||
"description": "控制平台文字敏感詞過濾的總開關與策略(國內主場景)",
|
||||
"enabled": "啟用敏感詞過濾",
|
||||
"enabledTip": "關閉後所有文字校驗放行(海外部署可關)",
|
||||
"revealWord": "錯誤回顯命中詞",
|
||||
"revealWordTip": "開啟後對外錯誤文案會帶出具體敏感詞,有探測風險",
|
||||
"recordHit": "記錄命中審計",
|
||||
"recordHitTip": "攔截時寫入命中記錄表",
|
||||
"confirmSaveContent": "儲存後將立即影響線上敏感詞攔截策略,確認儲存?"
|
||||
}
|
||||
}
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "QR Code Pay Config",
|
||||
"menu.payment.risk": "Payment Risk",
|
||||
"menu.payment.risk.blacklist": "Blacklist",
|
||||
"menu.payment.risk.hit": "Risk Hits"
|
||||
"menu.payment.risk.hit": "Risk Hits",
|
||||
"menu.system.sensitive": "Sensitive Words",
|
||||
"menu.system.sensitive.word": "Word List",
|
||||
"menu.system.sensitive.hit": "Hit Records"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "Konfigurasi Bayar Kode QR",
|
||||
"menu.payment.risk": "Payment Risk",
|
||||
"menu.payment.risk.blacklist": "Blacklist",
|
||||
"menu.payment.risk.hit": "Risk Hits"
|
||||
"menu.payment.risk.hit": "Risk Hits",
|
||||
"menu.system.sensitive": "Kata sensitif",
|
||||
"menu.system.sensitive.word": "Daftar kata",
|
||||
"menu.system.sensitive.hit": "Catatan hit"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "コード支払設定",
|
||||
"menu.payment.risk": "支払リスク",
|
||||
"menu.payment.risk.blacklist": "ブラックリスト",
|
||||
"menu.payment.risk.hit": "リスクヒット"
|
||||
"menu.payment.risk.hit": "リスクヒット",
|
||||
"menu.system.sensitive": "センシティブワード",
|
||||
"menu.system.sensitive.word": "ワード一覧",
|
||||
"menu.system.sensitive.hit": "ヒット記録"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "코드 결제 구성",
|
||||
"menu.payment.risk": "결제 리스크",
|
||||
"menu.payment.risk.blacklist": "블랙리스트",
|
||||
"menu.payment.risk.hit": "리스크 적중"
|
||||
"menu.payment.risk.hit": "리스크 적중",
|
||||
"menu.system.sensitive": "민감어",
|
||||
"menu.system.sensitive.word": "단어 관리",
|
||||
"menu.system.sensitive.hit": "적중 기록"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "Konfigurasi Bayaran Kod QR",
|
||||
"menu.payment.risk": "Payment Risk",
|
||||
"menu.payment.risk.blacklist": "Blacklist",
|
||||
"menu.payment.risk.hit": "Risk Hits"
|
||||
"menu.payment.risk.hit": "Risk Hits",
|
||||
"menu.system.sensitive": "Perkataan sensitif",
|
||||
"menu.system.sensitive.word": "Senarai kata",
|
||||
"menu.system.sensitive.hit": "Rekod hit"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "การกำหนดค่าชำระเงินรหัส",
|
||||
"menu.payment.risk": "Payment Risk",
|
||||
"menu.payment.risk.blacklist": "Blacklist",
|
||||
"menu.payment.risk.hit": "Risk Hits"
|
||||
"menu.payment.risk.hit": "Risk Hits",
|
||||
"menu.system.sensitive": "คำละเอียดอ่อน",
|
||||
"menu.system.sensitive.word": "รายการคำ",
|
||||
"menu.system.sensitive.hit": "ประวัติการพบ"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "Cấu hình thanh toán mã",
|
||||
"menu.payment.risk": "Payment Risk",
|
||||
"menu.payment.risk.blacklist": "Blacklist",
|
||||
"menu.payment.risk.hit": "Risk Hits"
|
||||
"menu.payment.risk.hit": "Risk Hits",
|
||||
"menu.system.sensitive": "Từ nhạy cảm",
|
||||
"menu.system.sensitive.word": "Danh sách từ",
|
||||
"menu.system.sensitive.hit": "Lịch sử trúng"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.easypay": "易支付配置",
|
||||
"menu.payment.risk": "支付风控",
|
||||
"menu.payment.risk.blacklist": "黑名单管理",
|
||||
"menu.payment.risk.hit": "风险命中记录"
|
||||
"menu.payment.risk.hit": "风险命中记录",
|
||||
"menu.system.sensitive": "敏感词",
|
||||
"menu.system.sensitive.word": "词库管理",
|
||||
"menu.system.sensitive.hit": "命中记录"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "碼牌支付配置",
|
||||
"menu.payment.risk": "支付風控",
|
||||
"menu.payment.risk.blacklist": "黑名單管理",
|
||||
"menu.payment.risk.hit": "風險命中記錄"
|
||||
"menu.payment.risk.hit": "風險命中記錄",
|
||||
"menu.system.sensitive": "敏感詞",
|
||||
"menu.system.sensitive.word": "詞庫管理",
|
||||
"menu.system.sensitive.hit": "命中記錄"
|
||||
}
|
||||
|
||||
@@ -83,5 +83,8 @@
|
||||
"menu.payment.merchant.codePayConfig": "碼牌支付配置",
|
||||
"menu.payment.risk": "支付風控",
|
||||
"menu.payment.risk.blacklist": "黑名單管理",
|
||||
"menu.payment.risk.hit": "風險命中記錄"
|
||||
"menu.payment.risk.hit": "風險命中記錄",
|
||||
"menu.system.sensitive": "敏感詞",
|
||||
"menu.system.sensitive.word": "詞庫管理",
|
||||
"menu.system.sensitive.hit": "命中記錄"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import OssConfigForm from './oss/OssConfigForm.vue';
|
||||
import SensitiveWordConfigForm from './sensitive-word/SensitiveWordConfigForm.vue';
|
||||
import UrlConfigForm from './url/UrlConfigForm.vue';
|
||||
import WebsiteConfigForm from './website/WebsiteConfigForm.vue';
|
||||
|
||||
@@ -34,6 +35,13 @@
|
||||
// OSS配置描述
|
||||
description: $t('system.platform.oss.description'),
|
||||
},
|
||||
{
|
||||
key: 'sensitive-word',
|
||||
// 敏感词策略标题
|
||||
label: $t('system.sensitiveWord.config.title'),
|
||||
// 敏感词策略描述
|
||||
description: $t('system.sensitiveWord.config.description'),
|
||||
},
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
@@ -66,6 +74,7 @@
|
||||
<UrlConfigForm v-if="activeKey === 'url'" />
|
||||
<WebsiteConfigForm v-if="activeKey === 'website'" />
|
||||
<OssConfigForm v-if="activeKey === 'oss'" />
|
||||
<SensitiveWordConfigForm v-if="activeKey === 'sensitive-word'" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SensitiveWordConfig } from '#/api/system/sensitive-word-config.api';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { SensitiveWordConfigApi } from '#/api/system/sensitive-word-config.api';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
|
||||
const { confirm, message } = useMessage();
|
||||
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const isEditing = ref(false);
|
||||
const formState = ref<SensitiveWordConfig>({
|
||||
enabled: true,
|
||||
revealWord: false,
|
||||
recordHit: true,
|
||||
contentPreviewMaxLen: 200,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig();
|
||||
});
|
||||
|
||||
async function loadConfig() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await SensitiveWordConfigApi.get();
|
||||
if (data) {
|
||||
formState.value = {
|
||||
enabled: data.enabled ?? true,
|
||||
revealWord: data.revealWord ?? false,
|
||||
recordHit: data.recordHit ?? true,
|
||||
contentPreviewMaxLen: data.contentPreviewMaxLen ?? 200,
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
isEditing.value = true;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
confirm({
|
||||
title: $t('common.confirm'),
|
||||
content: $t('common.confirmCancelContent'),
|
||||
okText: $t('common.okText'),
|
||||
cancelText: $t('common.cancelText'),
|
||||
onOk: async () => {
|
||||
isEditing.value = false;
|
||||
await loadConfig();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
confirm({
|
||||
title: $t('common.confirm'),
|
||||
content: $t('system.sensitiveWord.config.confirmSaveContent'),
|
||||
okText: $t('common.okText'),
|
||||
cancelText: $t('common.cancelText'),
|
||||
onOk: async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
await SensitiveWordConfigApi.update(formState.value);
|
||||
message.success($t('common.saveSuccess'));
|
||||
isEditing.value = false;
|
||||
await loadConfig();
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="sensitive-word-config">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-base font-medium">{{ $t('system.sensitiveWord.config.title') }}</div>
|
||||
<div class="text-muted-foreground text-sm">{{ $t('system.sensitiveWord.config.description') }}</div>
|
||||
</div>
|
||||
<a-space>
|
||||
<a-button v-if="!isEditing" type="primary" @click="handleEdit">{{ $t('common.edit') }}</a-button>
|
||||
<template v-else>
|
||||
<a-button @click="handleCancel">{{ $t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" :loading="saving" @click="handleSave">{{ $t('common.save') }}</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-form ref="formRef" :model="formState" layout="vertical" :disabled="!isEditing">
|
||||
<a-form-item :label="$t('system.sensitiveWord.config.enabled')">
|
||||
<a-switch v-model:checked="formState.enabled" />
|
||||
<div class="text-muted-foreground mt-1 text-xs">{{ $t('system.sensitiveWord.config.enabledTip') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('system.sensitiveWord.config.revealWord')">
|
||||
<a-switch v-model:checked="formState.revealWord" />
|
||||
<div class="text-muted-foreground mt-1 text-xs">{{ $t('system.sensitiveWord.config.revealWordTip') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('system.sensitiveWord.config.recordHit')">
|
||||
<a-switch v-model:checked="formState.recordHit" />
|
||||
<div class="text-muted-foreground mt-1 text-xs">{{ $t('system.sensitiveWord.config.recordHitTip') }}</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
@@ -0,0 +1,155 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
SensitiveWordApi,
|
||||
type SensitiveWordParam,
|
||||
type SensitiveWordVo,
|
||||
} from '#/api/system/sensitive-word.api';
|
||||
import { FormEditType } from '#/enums/formEditType';
|
||||
import { useFormEdit } from '#/hooks/useFormEdit';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
|
||||
const emit = defineEmits(['ok']);
|
||||
|
||||
const { message } = useMessage();
|
||||
const formRef = ref();
|
||||
|
||||
const { visible, confirmLoading, title, initFormEditType, handleCancel, showable, formEditType } =
|
||||
useFormEdit();
|
||||
|
||||
const isAdd = computed(() => formEditType.value === FormEditType.Add);
|
||||
|
||||
const formState = ref<SensitiveWordParam>({
|
||||
id: '',
|
||||
word: '',
|
||||
category: 'custom',
|
||||
matchMode: 'contains',
|
||||
status: 'enable',
|
||||
remark: '',
|
||||
});
|
||||
|
||||
function resetForm() {
|
||||
formState.value = {
|
||||
id: '',
|
||||
word: '',
|
||||
category: 'custom',
|
||||
matchMode: 'contains',
|
||||
status: 'enable',
|
||||
remark: '',
|
||||
};
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
|
||||
async function fillForm(record: SensitiveWordVo) {
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
const { data } = await SensitiveWordApi.getById(record.id!);
|
||||
const row = data || record;
|
||||
formState.value = {
|
||||
id: row.id!,
|
||||
word: row.word,
|
||||
category: row.category || 'custom',
|
||||
matchMode: row.matchMode || 'contains',
|
||||
status: row.status || 'enable',
|
||||
remark: row.remark,
|
||||
};
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showAdd() {
|
||||
initFormEditType(FormEditType.Add);
|
||||
resetForm();
|
||||
}
|
||||
|
||||
async function showEdit(record: SensitiveWordVo) {
|
||||
initFormEditType(FormEditType.Edit);
|
||||
resetForm();
|
||||
await fillForm(record);
|
||||
}
|
||||
|
||||
async function showView(record: SensitiveWordVo) {
|
||||
initFormEditType(FormEditType.Show);
|
||||
resetForm();
|
||||
await fillForm(record);
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
if (isAdd.value) {
|
||||
await SensitiveWordApi.add(formState.value);
|
||||
} else {
|
||||
await SensitiveWordApi.update(formState.value);
|
||||
}
|
||||
message.success($t('common.saveSuccess'));
|
||||
handleCancel();
|
||||
emit('ok');
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ showAdd, showEdit, showView });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:open="visible"
|
||||
:title="title"
|
||||
:width="480"
|
||||
:destroy-on-close="true"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" layout="vertical" :disabled="showable">
|
||||
<a-form-item
|
||||
name="word"
|
||||
:label="$t('system.sensitiveWord.word.field.word')"
|
||||
:rules="[{ required: true, message: $t('common.pleaseInput') }]"
|
||||
>
|
||||
<a-input v-model:value="formState.word" :maxlength="64" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item name="category" :label="$t('system.sensitiveWord.word.field.category')">
|
||||
<a-select v-model:value="formState.category">
|
||||
<a-select-option value="politic">{{ $t('system.sensitiveWord.word.category.politic') }}</a-select-option>
|
||||
<a-select-option value="porn">{{ $t('system.sensitiveWord.word.category.porn') }}</a-select-option>
|
||||
<a-select-option value="violence">{{ $t('system.sensitiveWord.word.category.violence') }}</a-select-option>
|
||||
<a-select-option value="ad">{{ $t('system.sensitiveWord.word.category.ad') }}</a-select-option>
|
||||
<a-select-option value="custom">{{ $t('system.sensitiveWord.word.category.custom') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="matchMode" :label="$t('system.sensitiveWord.word.field.matchMode')">
|
||||
<a-radio-group v-model:value="formState.matchMode" button-style="solid">
|
||||
<a-radio-button value="contains">{{ $t('system.sensitiveWord.word.matchMode.contains') }}</a-radio-button>
|
||||
<a-radio-button value="exact">{{ $t('system.sensitiveWord.word.matchMode.exact') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="status" :label="$t('system.sensitiveWord.word.field.status')">
|
||||
<a-radio-group v-model:value="formState.status" button-style="solid">
|
||||
<a-radio-button value="enable">{{ $t('system.sensitiveWord.word.status.enable') }}</a-radio-button>
|
||||
<a-radio-button value="disable">{{ $t('system.sensitiveWord.word.status.disable') }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item name="remark" :label="$t('system.sensitiveWord.word.field.remark')">
|
||||
<a-textarea v-model:value="formState.remark" :rows="3" :maxlength="255" allow-clear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">{{ $t('common.cancel') }}</a-button>
|
||||
<a-button v-if="!showable" type="primary" :loading="confirmLoading" @click="handleOk">
|
||||
{{ $t('common.save') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
@@ -0,0 +1,131 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableInstance, VxeToolbarInstance } from 'vxe-table';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
SensitiveWordHitApi,
|
||||
type SensitiveWordHitQuery,
|
||||
type SensitiveWordHitVo,
|
||||
} from '#/api/system/sensitive-word-hit.api';
|
||||
import { BQuery, type QueryField } from '#/components/query';
|
||||
import { PermCodes } from '#/constants/perm-codes';
|
||||
import { usePermission } from '#/hooks/usePermission';
|
||||
|
||||
defineOptions({ name: 'SensitiveWordHitList' });
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const loading = ref(false);
|
||||
const xTable = ref<VxeTableInstance>();
|
||||
const xToolbar = ref<VxeToolbarInstance>();
|
||||
const queryForm = ref<SensitiveWordHitQuery>({});
|
||||
const pageConfig = ref({ currentPage: 1, pageSize: 10, total: 0 });
|
||||
const tableData = ref<SensitiveWordHitVo[]>([]);
|
||||
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
type: 'string',
|
||||
field: 'hitWord',
|
||||
// 命中词
|
||||
name: $t('system.sensitiveWord.hit.field.hitWord'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
field: 'scene',
|
||||
// 场景
|
||||
name: $t('system.sensitiveWord.hit.field.scene'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
field: 'source',
|
||||
// 来源
|
||||
name: $t('system.sensitiveWord.hit.field.source'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
field: 'mchNo',
|
||||
// 商户号
|
||||
name: $t('system.sensitiveWord.hit.field.mchNo'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
]);
|
||||
|
||||
function queryPage() {
|
||||
if (!hasPermission(PermCodes.System.SensitiveWordHit.VIEW)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
loading.value = true;
|
||||
return SensitiveWordHitApi.page({
|
||||
current: pageConfig.value.currentPage,
|
||||
size: pageConfig.value.pageSize,
|
||||
...queryForm.value,
|
||||
})
|
||||
.then((res) => {
|
||||
tableData.value = res.data?.records || [];
|
||||
pageConfig.value.total = Number(res.data?.total) || 0;
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
queryForm.value = {};
|
||||
pageConfig.value.currentPage = 1;
|
||||
queryPage();
|
||||
}
|
||||
|
||||
function handlePageChange({ currentPage, pageSize }: { currentPage: number; pageSize: number }) {
|
||||
pageConfig.value.currentPage = currentPage;
|
||||
pageConfig.value.pageSize = pageSize;
|
||||
queryPage();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
xTable.value?.connectToolbar(xToolbar.value as VxeToolbarInstance);
|
||||
queryPage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="m-3 p-3 bg-background rounded-lg 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 }" />
|
||||
<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="hitWord" :title="$t('system.sensitiveWord.hit.field.hitWord')" width="120" />
|
||||
<vxe-column
|
||||
field="contentPreview"
|
||||
:title="$t('system.sensitiveWord.hit.field.contentPreview')"
|
||||
:min-width="180"
|
||||
/>
|
||||
<vxe-column field="scene" :title="$t('system.sensitiveWord.hit.field.scene')" width="120" />
|
||||
<vxe-column field="source" :title="$t('system.sensitiveWord.hit.field.source')" width="100" />
|
||||
<vxe-column field="mchNo" :title="$t('system.sensitiveWord.hit.field.mchNo')" width="120" />
|
||||
<vxe-column field="clientIp" :title="$t('system.sensitiveWord.hit.field.clientIp')" width="130" />
|
||||
<vxe-column field="createTime" :title="$t('system.sensitiveWord.hit.field.createTime')" width="170" />
|
||||
</vxe-table>
|
||||
<div class="mt-3 flex justify-end">
|
||||
<vxe-pager
|
||||
v-model:current-page="pageConfig.currentPage"
|
||||
v-model:page-size="pageConfig.pageSize"
|
||||
:total="pageConfig.total"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,290 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableInstance, VxeToolbarInstance } from 'vxe-table';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { IconifyIcon } from '@vben-core/icons';
|
||||
|
||||
import {
|
||||
SensitiveWordApi,
|
||||
type SensitiveWordQuery,
|
||||
type SensitiveWordVo,
|
||||
} from '#/api/system/sensitive-word.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 SensitiveWordEdit from './SensitiveWordEdit.vue';
|
||||
|
||||
defineOptions({ name: 'SensitiveWordList' });
|
||||
|
||||
const { confirm, message } = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const loading = ref(false);
|
||||
const xTable = ref<VxeTableInstance>();
|
||||
const xToolbar = ref<VxeToolbarInstance>();
|
||||
const queryForm = ref<SensitiveWordQuery>({});
|
||||
const pageConfig = ref({ currentPage: 1, pageSize: 10, total: 0 });
|
||||
const tableData = ref<SensitiveWordVo[]>([]);
|
||||
const editRef = ref();
|
||||
const checkText = ref('');
|
||||
const checkRecordHit = ref(false);
|
||||
const checkLoading = ref(false);
|
||||
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
type: 'string',
|
||||
field: 'word',
|
||||
// 敏感词
|
||||
name: $t('system.sensitiveWord.word.field.word'),
|
||||
placeholder: $t('common.pleaseInput'),
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
field: 'category',
|
||||
// 分类
|
||||
name: $t('system.sensitiveWord.word.field.category'),
|
||||
placeholder: $t('common.pleaseSelect'),
|
||||
selectList: [
|
||||
{ label: $t('system.sensitiveWord.word.category.politic'), value: 'politic' },
|
||||
{ label: $t('system.sensitiveWord.word.category.porn'), value: 'porn' },
|
||||
{ label: $t('system.sensitiveWord.word.category.violence'), value: 'violence' },
|
||||
{ label: $t('system.sensitiveWord.word.category.ad'), value: 'ad' },
|
||||
{ label: $t('system.sensitiveWord.word.category.custom'), value: 'custom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
field: 'status',
|
||||
// 状态
|
||||
name: $t('system.sensitiveWord.word.field.status'),
|
||||
placeholder: $t('common.pleaseSelect'),
|
||||
selectList: [
|
||||
{ label: $t('system.sensitiveWord.word.status.enable'), value: 'enable' },
|
||||
{ label: $t('system.sensitiveWord.word.status.disable'), value: 'disable' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
/** 分页查询 */
|
||||
function queryPage() {
|
||||
loading.value = true;
|
||||
return SensitiveWordApi.page({
|
||||
current: pageConfig.value.currentPage,
|
||||
size: pageConfig.value.pageSize,
|
||||
...queryForm.value,
|
||||
})
|
||||
.then((res) => {
|
||||
tableData.value = res.data?.records || [];
|
||||
pageConfig.value.total = Number(res.data?.total) || 0;
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
queryForm.value = {};
|
||||
pageConfig.value.currentPage = 1;
|
||||
queryPage();
|
||||
}
|
||||
|
||||
function handlePageChange({ currentPage, pageSize }: { currentPage: number; pageSize: number }) {
|
||||
pageConfig.value.currentPage = currentPage;
|
||||
pageConfig.value.pageSize = pageSize;
|
||||
queryPage();
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
editRef.value?.showAdd();
|
||||
}
|
||||
|
||||
function handleEdit(row: SensitiveWordVo) {
|
||||
editRef.value?.showEdit(row);
|
||||
}
|
||||
|
||||
function handleView(row: SensitiveWordVo) {
|
||||
editRef.value?.showView(row);
|
||||
}
|
||||
|
||||
function handleDelete(row: SensitiveWordVo) {
|
||||
confirm({
|
||||
title: $t('common.confirm'),
|
||||
content: $t('system.sensitiveWord.word.confirmDelete'),
|
||||
okText: $t('common.delete'),
|
||||
cancelText: $t('common.cancel'),
|
||||
onOk() {
|
||||
return SensitiveWordApi.delete(row.id!).then(() => {
|
||||
message.success($t('common.deleteSuccess'));
|
||||
queryPage();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 试检 */
|
||||
async function handleCheck() {
|
||||
if (!checkText.value?.trim()) {
|
||||
return;
|
||||
}
|
||||
checkLoading.value = true;
|
||||
try {
|
||||
const { data } = await SensitiveWordApi.checkText({
|
||||
text: checkText.value,
|
||||
recordHit: checkRecordHit.value,
|
||||
});
|
||||
if (data?.hit) {
|
||||
message.warning($t('system.sensitiveWord.word.check.hit', [data.hits?.join(', ')]));
|
||||
} else {
|
||||
message.success($t('system.sensitiveWord.word.check.clean'));
|
||||
}
|
||||
} finally {
|
||||
checkLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function categoryLabel(c?: string) {
|
||||
if (!c) return '';
|
||||
return $t(`system.sensitiveWord.word.category.${c}` as 'system.sensitiveWord.word.category.custom');
|
||||
}
|
||||
|
||||
function statusColor(status?: string) {
|
||||
return status === 'enable' ? 'success' : 'default';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
xTable.value?.connectToolbar(xToolbar.value as VxeToolbarInstance);
|
||||
queryPage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="m-3 p-3 bg-background rounded-lg list-page-compact">
|
||||
<div class="mb-3">
|
||||
<!-- 间距放 wrapper,避免 antd cssinjs 覆盖 Tailwind margin -->
|
||||
<a-alert type="info" show-icon :message="$t('system.sensitiveWord.tip')" />
|
||||
</div>
|
||||
|
||||
<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-button
|
||||
v-if="hasPermission(PermCodes.System.SensitiveWord.MANAGE)"
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:plus-outlined" />
|
||||
</template>
|
||||
<!-- 新增敏感词 -->
|
||||
{{ $t('system.sensitiveWord.word.add') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</vxe-toolbar>
|
||||
|
||||
<div class="mb-3 flex flex-wrap items-center gap-2">
|
||||
<a-input
|
||||
v-model:value="checkText"
|
||||
class="!w-80"
|
||||
:placeholder="$t('system.sensitiveWord.word.check.placeholder')"
|
||||
allow-clear
|
||||
/>
|
||||
<a-checkbox v-model:checked="checkRecordHit">
|
||||
{{ $t('system.sensitiveWord.word.check.recordHit') }}
|
||||
</a-checkbox>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.System.SensitiveWord.VIEW)"
|
||||
:loading="checkLoading"
|
||||
@click="handleCheck"
|
||||
>
|
||||
{{ $t('system.sensitiveWord.word.check.button') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<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="word" :title="$t('system.sensitiveWord.word.field.word')" :min-width="140" />
|
||||
<vxe-column field="category" :title="$t('system.sensitiveWord.word.field.category')" width="100">
|
||||
<template #default="{ row }">{{ categoryLabel(row.category) }}</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="matchMode" :title="$t('system.sensitiveWord.word.field.matchMode')" width="100">
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
row.matchMode === 'exact'
|
||||
? $t('system.sensitiveWord.word.matchMode.exact')
|
||||
: $t('system.sensitiveWord.word.matchMode.contains')
|
||||
}}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="status" :title="$t('system.sensitiveWord.word.field.status')" width="100">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="statusColor(row.status)">
|
||||
{{
|
||||
row.status === 'enable'
|
||||
? $t('system.sensitiveWord.word.status.enable')
|
||||
: $t('system.sensitiveWord.word.status.disable')
|
||||
}}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="remark" :title="$t('system.sensitiveWord.word.field.remark')" :min-width="120" />
|
||||
<vxe-column field="createTime" :title="$t('system.sensitiveWord.word.field.createTime')" width="170" />
|
||||
<vxe-column fixed="right" :width="200" :show-overflow="false" :title="$t('common.operation')">
|
||||
<template #default="{ row }">
|
||||
<a-space :size="2">
|
||||
<template #separator>
|
||||
<a-divider type="vertical" />
|
||||
</template>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.System.SensitiveWord.VIEW)"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleView(row)"
|
||||
>
|
||||
{{ $t('common.view') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.System.SensitiveWord.MANAGE)"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
{{ $t('common.edit') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="hasPermission(PermCodes.System.SensitiveWord.MANAGE)"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleDelete(row)"
|
||||
>
|
||||
{{ $t('common.delete') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<div class="mt-3 flex justify-end">
|
||||
<vxe-pager
|
||||
v-model:current-page="pageConfig.currentPage"
|
||||
v-model:page-size="pageConfig.pageSize"
|
||||
:total="pageConfig.total"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<SensitiveWordEdit ref="editRef" @ok="queryPage" />
|
||||
</div>
|
||||
</template>
|
||||
144
scripts/check-locale-keys.mjs
Normal file
144
scripts/check-locale-keys.mjs
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 运营端业务语言包 key 完整性检查(相对 zh-CN)
|
||||
*
|
||||
* 用法:
|
||||
* node scripts/check-locale-keys.mjs
|
||||
* node scripts/check-locale-keys.mjs --langs=ja-JP,ko-KR
|
||||
*
|
||||
* 退出码:存在 missing key 时为 1
|
||||
*/
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const adminLocales = path.resolve(__dirname, '../apps/daxpay-admin/src/locales')
|
||||
const langsRoot = path.join(adminLocales, 'langs')
|
||||
const menuRoot = path.join(adminLocales, 'menu-titles')
|
||||
const BASELINE = 'zh-CN'
|
||||
|
||||
function flatten(obj, prefix = '', out = {}) {
|
||||
for (const [k, v] of Object.entries(obj || {})) {
|
||||
const key = prefix ? `${prefix}.${k}` : k
|
||||
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
||||
flatten(v, key, out)
|
||||
} else {
|
||||
out[key] = String(v ?? '')
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function loadLangDir(dir) {
|
||||
const out = {}
|
||||
function walk(d, prefix = '') {
|
||||
if (!fs.existsSync(d)) return
|
||||
for (const ent of fs.readdirSync(d, { withFileTypes: true })) {
|
||||
const fp = path.join(d, ent.name)
|
||||
if (ent.isDirectory()) {
|
||||
walk(fp, prefix ? `${prefix}.${ent.name}` : ent.name)
|
||||
} else if (ent.name.endsWith('.json')) {
|
||||
const fileKey = ent.name.replace(/\.json$/, '')
|
||||
const ns = prefix ? `${prefix}.${fileKey}` : fileKey
|
||||
const json = JSON.parse(fs.readFileSync(fp, 'utf8'))
|
||||
const flat = flatten(json)
|
||||
for (const [k, v] of Object.entries(flat)) {
|
||||
out[`${ns}.${k}`] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(dir)
|
||||
return out
|
||||
}
|
||||
|
||||
function parseArgs() {
|
||||
const arg = process.argv.find((a) => a.startsWith('--langs='))
|
||||
if (!arg) return null
|
||||
return arg
|
||||
.slice('--langs='.length)
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
const allLocales = fs
|
||||
.readdirSync(langsRoot)
|
||||
.filter((d) => fs.statSync(path.join(langsRoot, d)).isDirectory())
|
||||
.sort()
|
||||
|
||||
const filter = parseArgs()
|
||||
const locales = filter?.length
|
||||
? allLocales.filter((l) => filter.includes(l))
|
||||
: allLocales
|
||||
|
||||
const maps = {}
|
||||
for (const locale of allLocales) {
|
||||
maps[locale] = loadLangDir(path.join(langsRoot, locale))
|
||||
}
|
||||
|
||||
const baselineKeys = Object.keys(maps[BASELINE] || {}).sort()
|
||||
if (!baselineKeys.length) {
|
||||
console.error(`[check-locale-keys] baseline ${BASELINE} has no keys`)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
console.log(`[check-locale-keys] baseline=${BASELINE} keys=${baselineKeys.length}`)
|
||||
console.log(`[check-locale-keys] locales=${locales.join(', ')}`)
|
||||
console.log('')
|
||||
|
||||
let hasMissing = false
|
||||
const report = []
|
||||
|
||||
for (const locale of locales) {
|
||||
if (locale === BASELINE) continue
|
||||
const keys = new Set(Object.keys(maps[locale] || {}))
|
||||
const missing = baselineKeys.filter((k) => !keys.has(k))
|
||||
const extra = [...keys].filter((k) => !baselineKeys.includes(k)).sort()
|
||||
if (missing.length) hasMissing = true
|
||||
report.push({ locale, missing, extra })
|
||||
console.log(
|
||||
`[${locale}] missing=${missing.length} extra=${extra.length} total=${keys.size}`,
|
||||
)
|
||||
if (missing.length) {
|
||||
const sample = missing.slice(0, 20)
|
||||
console.log(` missing sample (${sample.length}/${missing.length}):`)
|
||||
for (const k of sample) console.log(` - ${k}`)
|
||||
if (missing.length > sample.length) {
|
||||
console.log(` ... +${missing.length - sample.length} more`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// menu-titles
|
||||
console.log('')
|
||||
console.log('[menu-titles]')
|
||||
const menuFiles = fs
|
||||
.readdirSync(menuRoot)
|
||||
.filter((f) => f.endsWith('.json'))
|
||||
.map((f) => f.replace(/\.json$/, ''))
|
||||
.sort()
|
||||
const menuMaps = {}
|
||||
for (const locale of menuFiles) {
|
||||
menuMaps[locale] = JSON.parse(
|
||||
fs.readFileSync(path.join(menuRoot, `${locale}.json`), 'utf8'),
|
||||
)
|
||||
}
|
||||
const menuBaseline = Object.keys(menuMaps[BASELINE] || {}).sort()
|
||||
console.log(`baseline keys=${menuBaseline.length}`)
|
||||
for (const locale of menuFiles) {
|
||||
if (locale === BASELINE) continue
|
||||
if (filter?.length && !filter.includes(locale)) continue
|
||||
const missing = menuBaseline.filter((k) => !(k in (menuMaps[locale] || {})))
|
||||
if (missing.length) hasMissing = true
|
||||
console.log(`[${locale}] missing=${missing.length}`)
|
||||
for (const k of missing) console.log(` - ${k}`)
|
||||
}
|
||||
|
||||
console.log('')
|
||||
if (hasMissing) {
|
||||
console.error('[check-locale-keys] FAILED: missing keys detected')
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('[check-locale-keys] OK: no missing keys vs zh-CN')
|
||||
process.exit(0)
|
||||
Reference in New Issue
Block a user