diff --git a/apps/daxpay-admin/src/api/system/sensitive-word-config.api.ts b/apps/daxpay-admin/src/api/system/sensitive-word-config.api.ts new file mode 100644 index 000000000..84174f52d --- /dev/null +++ b/apps/daxpay-admin/src/api/system/sensitive-word-config.api.ts @@ -0,0 +1,30 @@ +import type { Result } from '#/types/web'; + +import { defHttp } from '#/api/request'; + +/** + * 平台敏感词策略配置 API(平台配置 Tab) + */ +export const SensitiveWordConfigApi = { + /** 获取策略 */ + get(): Promise> { + return defHttp.get({ url: '/platform/config/sensitive-word/get' }); + }, + + /** 更新策略 */ + update(data: SensitiveWordConfig): Promise> { + return defHttp.post({ url: '/platform/config/sensitive-word/update', data }); + }, +}; + +/** 敏感词策略 */ +export interface SensitiveWordConfig { + /** 是否启用过滤 */ + enabled?: boolean; + /** 是否回显命中词 */ + revealWord?: boolean; + /** 是否写命中审计 */ + recordHit?: boolean; + /** 原文摘要最大长度 */ + contentPreviewMaxLen?: number; +} diff --git a/apps/daxpay-admin/src/api/system/sensitive-word-hit.api.ts b/apps/daxpay-admin/src/api/system/sensitive-word-hit.api.ts new file mode 100644 index 000000000..30d2d5bbe --- /dev/null +++ b/apps/daxpay-admin/src/api/system/sensitive-word-hit.api.ts @@ -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>> { + return defHttp.get({ url: '/admin/system/sensitive-word-hit/page', params }); + }, + + /** 详情 */ + getById(id: string): Promise> { + 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; +} diff --git a/apps/daxpay-admin/src/api/system/sensitive-word.api.ts b/apps/daxpay-admin/src/api/system/sensitive-word.api.ts new file mode 100644 index 000000000..1e6126e37 --- /dev/null +++ b/apps/daxpay-admin/src/api/system/sensitive-word.api.ts @@ -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>> { + return defHttp.get({ url: '/admin/system/sensitive-word/page', params }); + }, + + /** 详情 */ + getById(id: string): Promise> { + return defHttp.get({ url: '/admin/system/sensitive-word/get-by-id', params: { id } }); + }, + + /** 新增 */ + add(data: SensitiveWordParam): Promise> { + return defHttp.post({ url: '/admin/system/sensitive-word/add', data }); + }, + + /** 修改 */ + update(data: SensitiveWordParam): Promise> { + return defHttp.post({ url: '/admin/system/sensitive-word/update', data }); + }, + + /** 删除 */ + delete(id: string): Promise> { + return defHttp.post({ url: '/admin/system/sensitive-word/delete', params: { id } }); + }, + + /** 词面是否存在 */ + existsByWord(word: string, id?: string): Promise> { + 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; +} diff --git a/apps/daxpay-admin/src/constants/perm-codes.ts b/apps/daxpay-admin/src/constants/perm-codes.ts index 8af6b1ce6..87330bbeb 100644 --- a/apps/daxpay-admin/src/constants/perm-codes.ts +++ b/apps/daxpay-admin/src/constants/perm-codes.ts @@ -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; diff --git a/apps/daxpay-admin/src/locales/langs/en-US/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/en-US/system/sensitiveWord.json new file mode 100644 index 000000000..65d14a74b --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/en-US/system/sensitiveWord.json @@ -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?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/id-ID/payment/risk.json b/apps/daxpay-admin/src/locales/langs/id-ID/payment/risk.json new file mode 100644 index 000000000..001a78b77 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/id-ID/payment/risk.json @@ -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" + } + } +} diff --git a/apps/daxpay-admin/src/locales/langs/id-ID/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/id-ID/system/sensitiveWord.json new file mode 100644 index 000000000..6a2204b19 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/id-ID/system/sensitiveWord.json @@ -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?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/ja-JP/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/ja-JP/system/sensitiveWord.json new file mode 100644 index 000000000..733248913 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/ja-JP/system/sensitiveWord.json @@ -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": "保存後はオンラインの遮断方針に即時反映されます。保存しますか?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/ko-KR/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/ko-KR/system/sensitiveWord.json new file mode 100644 index 000000000..992d1c8ca --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/ko-KR/system/sensitiveWord.json @@ -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": "저장 후 온라인 차단 정책에 즉시 반영됩니다. 저장하시겠습니까?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/ms-MY/payment/risk.json b/apps/daxpay-admin/src/locales/langs/ms-MY/payment/risk.json new file mode 100644 index 000000000..5c097923a --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/ms-MY/payment/risk.json @@ -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" + } + } +} diff --git a/apps/daxpay-admin/src/locales/langs/ms-MY/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/ms-MY/system/sensitiveWord.json new file mode 100644 index 000000000..a77e7427b --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/ms-MY/system/sensitiveWord.json @@ -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?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/th-TH/payment/risk.json b/apps/daxpay-admin/src/locales/langs/th-TH/payment/risk.json new file mode 100644 index 000000000..ab4e96416 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/th-TH/payment/risk.json @@ -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": "จัดการ" + } + } +} diff --git a/apps/daxpay-admin/src/locales/langs/th-TH/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/th-TH/system/sensitiveWord.json new file mode 100644 index 000000000..5e74cf8b8 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/th-TH/system/sensitiveWord.json @@ -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": "การบันทึกจะมีผลกับตัวกรองออนไลน์ทันที ดำเนินการต่อ?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/vi-VN/payment/risk.json b/apps/daxpay-admin/src/locales/langs/vi-VN/payment/risk.json new file mode 100644 index 000000000..06686c4be --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/vi-VN/payment/risk.json @@ -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ý" + } + } +} diff --git a/apps/daxpay-admin/src/locales/langs/vi-VN/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/vi-VN/system/sensitiveWord.json new file mode 100644 index 000000000..2f5338915 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/vi-VN/system/sensitiveWord.json @@ -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?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/zh-CN/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/zh-CN/system/sensitiveWord.json new file mode 100644 index 000000000..02e8746f6 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/zh-CN/system/sensitiveWord.json @@ -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": "保存后将立即影响线上敏感词拦截策略,确认保存?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/zh-HK/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/zh-HK/system/sensitiveWord.json new file mode 100644 index 000000000..5c26704a7 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/zh-HK/system/sensitiveWord.json @@ -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": "儲存後將立即影響線上敏感詞攔截策略,確認儲存?" + } +} diff --git a/apps/daxpay-admin/src/locales/langs/zh-TW/system/sensitiveWord.json b/apps/daxpay-admin/src/locales/langs/zh-TW/system/sensitiveWord.json new file mode 100644 index 000000000..5c26704a7 --- /dev/null +++ b/apps/daxpay-admin/src/locales/langs/zh-TW/system/sensitiveWord.json @@ -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": "儲存後將立即影響線上敏感詞攔截策略,確認儲存?" + } +} diff --git a/apps/daxpay-admin/src/locales/menu-titles/en-US.json b/apps/daxpay-admin/src/locales/menu-titles/en-US.json index ae45396b1..77b9dc62d 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/en-US.json +++ b/apps/daxpay-admin/src/locales/menu-titles/en-US.json @@ -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" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/id-ID.json b/apps/daxpay-admin/src/locales/menu-titles/id-ID.json index bc121dfac..0515ea1fe 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/id-ID.json +++ b/apps/daxpay-admin/src/locales/menu-titles/id-ID.json @@ -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" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/ja-JP.json b/apps/daxpay-admin/src/locales/menu-titles/ja-JP.json index 02a8f2e8c..de46168cc 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/ja-JP.json +++ b/apps/daxpay-admin/src/locales/menu-titles/ja-JP.json @@ -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": "ヒット記録" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/ko-KR.json b/apps/daxpay-admin/src/locales/menu-titles/ko-KR.json index 4f952f5b8..fce0a8c1b 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/ko-KR.json +++ b/apps/daxpay-admin/src/locales/menu-titles/ko-KR.json @@ -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": "적중 기록" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/ms-MY.json b/apps/daxpay-admin/src/locales/menu-titles/ms-MY.json index 3ff3e4c6e..19acb16cb 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/ms-MY.json +++ b/apps/daxpay-admin/src/locales/menu-titles/ms-MY.json @@ -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" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/th-TH.json b/apps/daxpay-admin/src/locales/menu-titles/th-TH.json index 8f2bebb47..9049fcc36 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/th-TH.json +++ b/apps/daxpay-admin/src/locales/menu-titles/th-TH.json @@ -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": "ประวัติการพบ" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/vi-VN.json b/apps/daxpay-admin/src/locales/menu-titles/vi-VN.json index b77bc8154..1a98940e6 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/vi-VN.json +++ b/apps/daxpay-admin/src/locales/menu-titles/vi-VN.json @@ -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" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/zh-CN.json b/apps/daxpay-admin/src/locales/menu-titles/zh-CN.json index 81046a16b..50a542fa5 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/zh-CN.json +++ b/apps/daxpay-admin/src/locales/menu-titles/zh-CN.json @@ -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": "命中记录" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/zh-HK.json b/apps/daxpay-admin/src/locales/menu-titles/zh-HK.json index 2e1c1ca94..a55fc53dc 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/zh-HK.json +++ b/apps/daxpay-admin/src/locales/menu-titles/zh-HK.json @@ -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": "命中記錄" } diff --git a/apps/daxpay-admin/src/locales/menu-titles/zh-TW.json b/apps/daxpay-admin/src/locales/menu-titles/zh-TW.json index 3cec8d30e..5a61fcf8f 100644 --- a/apps/daxpay-admin/src/locales/menu-titles/zh-TW.json +++ b/apps/daxpay-admin/src/locales/menu-titles/zh-TW.json @@ -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": "命中記錄" } diff --git a/apps/daxpay-admin/src/views/system/config/platform/PlatformConfig.vue b/apps/daxpay-admin/src/views/system/config/platform/PlatformConfig.vue index 54d842f02..230e5b2ad 100644 --- a/apps/daxpay-admin/src/views/system/config/platform/PlatformConfig.vue +++ b/apps/daxpay-admin/src/views/system/config/platform/PlatformConfig.vue @@ -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; @@ -66,6 +74,7 @@ + diff --git a/apps/daxpay-admin/src/views/system/config/platform/sensitive-word/SensitiveWordConfigForm.vue b/apps/daxpay-admin/src/views/system/config/platform/sensitive-word/SensitiveWordConfigForm.vue new file mode 100644 index 000000000..0fc59492f --- /dev/null +++ b/apps/daxpay-admin/src/views/system/config/platform/sensitive-word/SensitiveWordConfigForm.vue @@ -0,0 +1,116 @@ + + + diff --git a/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordEdit.vue b/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordEdit.vue new file mode 100644 index 000000000..9683705e4 --- /dev/null +++ b/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordEdit.vue @@ -0,0 +1,155 @@ + + + diff --git a/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordHitList.vue b/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordHitList.vue new file mode 100644 index 000000000..8a3cea79d --- /dev/null +++ b/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordHitList.vue @@ -0,0 +1,131 @@ + + + diff --git a/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordList.vue b/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordList.vue new file mode 100644 index 000000000..a3c1d3015 --- /dev/null +++ b/apps/daxpay-admin/src/views/system/sensitive-word/SensitiveWordList.vue @@ -0,0 +1,290 @@ + + + diff --git a/scripts/check-locale-keys.mjs b/scripts/check-locale-keys.mjs new file mode 100644 index 000000000..855d27727 --- /dev/null +++ b/scripts/check-locale-keys.mjs @@ -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)