From 7ee928aa42d7e3f2ea3a5f9da8b1b75987034d7f Mon Sep 17 00:00:00 2001
From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com>
Date: Sun, 5 Jul 2026 19:57:04 +0800
Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=A3=8E=E9=99=A9=E8=A7=84?=
=?UTF-8?q?=E5=88=99=E6=9C=8D=E5=8A=A1=E7=AB=AF=E9=BB=98=E8=AE=A4=E6=A8=A1?=
=?UTF-8?q?=E6=9D=BF+=E7=89=88=E6=9C=AC=E5=8E=86=E5=8F=B2+UI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web/src/views/RiskRules.vue | 59 ++++++++++++++++++++++++++-----------
1 file changed, 42 insertions(+), 17 deletions(-)
diff --git a/web/src/views/RiskRules.vue b/web/src/views/RiskRules.vue
index 2f06c4f..4651a6b 100644
--- a/web/src/views/RiskRules.vue
+++ b/web/src/views/RiskRules.vue
@@ -38,8 +38,12 @@
{{ fmt(s.row.updatedAt) }}
-
+
+ loadRuleVersion(s.row,v)" @click.stop>
+
+
{{ editSiteId===s.row.siteId ? '收起' : '编辑' }}
@@ -75,25 +79,16 @@
import api from '../api'
const loading = ref(false), saving = ref(false), viewVisible = ref(false), viewType = ref(''), viewJson = ref('')
-const sites = ref([]), rulesMap = ref({})
+const sites = ref([]), rulesMap = ref({}), defaultRulesMap = ref({}), ruleVer = ref('')
const editSiteId = ref(''), editSiteType = ref(''), editorJson = ref('')
-const defaultRules = {
- nexusphp: [
- {type:'KILL', name:'登录间隔超过30天', metric:'daysSinceLastLogin', threshold:30, unit:'DAY', condition:'GT', level:'CRITICAL'},
- {type:'RATIO', name:'分享率过低', metric:'shareRatio', threshold:0.3, condition:'LT', level:'CRITICAL', applicable:{daysSinceRegistration:30}},
- {type:'TRAFFIC',name:'超过5天无流量', metric:'daysSinceLastTraffic', threshold:5, unit:'DAY', condition:'GT', level:'WARN', applicable:{daysSinceRegistration:30}},
- ],
- mtorrent: [
- {type:'KILL', name:'登录过期', metric:'daysSinceLastLogin', threshold:30, unit:'DAY', condition:'GT', level:'CRITICAL'},
- ],
-}
+const fallbackRules = { nexusphp: [{type:'KILL',name:'',metric:'',threshold:0,unit:'DAY',condition:'GT',level:'CRITICAL'}] }
const defaults = computed(() => {
const types = [...new Set((Array.isArray(sites.value)?sites.value:[]).map(s=>s.siteType).filter(Boolean))]
const base = ['nexusphp','mtorrent','gazelle','unit3d']
return [...new Set([...types,...base])].map(t => {
- const rules = defaultRules[t] || defaultRules['nexusphp'] || []
+ const rules = defaultRulesMap.value[t] || fallbackRules[t] || []
return {type:t, ruleCount:rules.length, rules}
})
})
@@ -109,6 +104,7 @@ const merged = computed(() => {
version: rulesMap.value[s.siteType]?.version || null,
updatedAt: rulesMap.value[s.siteType]?.updatedAt || null,
rulesJson: rulesMap.value[s.siteType]?.rulesJson || null,
+ versions: rulesMap.value[s.siteType]?.versions || [],
}))
})
@@ -120,13 +116,31 @@ const fetchAll = async () => {
api.raw.get('/admin/risk-rules'),
])
sites.value = sr.status==='fulfilled' ? (Array.isArray(sr.value?.data)?sr.value.data:sr.value||[]) : []
+ const typeSet = new Set(sites.value.map(s=>s.siteType).filter(Boolean))
+ siteTypes.value = [...typeSet]
+ if (siteTypes.value.length===0) siteTypes.value = ['nexusphp','mtorrent','gazelle','unit3d']
+
const rulesData = rr.status==='fulfilled' ? (Array.isArray(rr.value?.data)?rr.value.data:rr.value||[]) : []
- const map = {}
+ const map = {}, verMap = {}
for (const r of rulesData) {
const cnt = (() => { try { return JSON.parse(r.rulesJson||'[]').length } catch { return 0 } })()
- map[r.siteType] = { ruleCount: cnt, version: r.version, updatedAt: r.updatedAt, rulesJson: r.rulesJson }
+ const v = r.version
+ if (!map[r.siteType] || v > map[r.siteType].version) {
+ map[r.siteType] = { ruleCount: cnt, version: v, updatedAt: r.updatedAt, rulesJson: r.rulesJson }
+ }
+ if (!verMap[r.siteType]) verMap[r.siteType] = new Set()
+ verMap[r.siteType].add(v)
}
+ for (const k in verMap) map[k].versions = [...verMap[k]].sort((a,b)=>b-a)
rulesMap.value = map
+ // 加载默认模板
+ for (const t of siteTypes.value) {
+ try {
+ const r = await api.raw.get('/admin/default-rules/'+t)
+ const data = r?.data || r
+ if (data?.rules) defaultRulesMap.value[t] = data.rules
+ } catch {}
+ }
} finally { loading.value = false }
}
@@ -143,10 +157,18 @@ const editDefault = (dt) => {
window.scrollTo({top: document.body.scrollHeight, behavior:'smooth'})
}
+const loadRuleVersion = async (row, ver) => {
+ if (!ver) return
+ try {
+ const r = await api.raw.get('/admin/risk-rules/'+row.siteType+'/version/'+ver)
+ editorJson.value = JSON.stringify(JSON.parse((r?.data||r)?.content||'[]'), null, 2)
+ } catch { ElMessage.error('加载版本失败') }
+}
+
const loadDefaultRules = (row) => {
editSiteId.value = row.siteId
editSiteType.value = row.siteType
- const rules = defaultRules[row.siteType] || defaultRules['nexusphp'] || []
+ const rules = defaultRulesMap.value[row.siteType] || fallbackRules[row.siteType] || []
editorJson.value = JSON.stringify(rules, null, 2)
window.scrollTo({top: document.body.scrollHeight, behavior:'smooth'})
}
@@ -162,7 +184,10 @@ const startEdit = (row) => {
const saveRules = async () => {
saving.value = true
try {
- await api.raw.post('/risk-rules/'+editSiteType.value, { rules: JSON.parse(editorJson.value) })
+ const url = editSiteType.value.startsWith('default:')
+ ? '/admin/default-rules/'+editSiteType.value.replace('default:','')
+ : '/risk-rules/'+editSiteType.value
+ await api.raw.post(url, { rules: JSON.parse(editorJson.value) })
ElMessage.success('已保存'); editSiteId.value = ''; fetchAll()
} catch { ElMessage.error('保存失败') }
finally { saving.value = false }