refactor: 拆分两个独立菜单(页面配置+风险规则)+CRUD完整+删除接口

This commit is contained in:
mediabot-pt
2026-07-05 00:08:56 +08:00
parent a7734b6b98
commit 73c01b3573
11 changed files with 286 additions and 191 deletions

119
web/src/views/RiskRules.vue Normal file
View File

@@ -0,0 +1,119 @@
<template>
<div>
<h2>风险规则</h2>
<el-card style="margin-top:20px">
<div style="margin-bottom:12px;display:flex;gap:8px">
<el-button @click="fetchAll" :loading="loading">刷新</el-button>
<el-button type="primary" @click="showEditor('')">新建</el-button>
</div>
<el-table :data="rules" stripe size="small" v-loading="loading" empty-text="暂无规则">
<el-table-column prop="siteType" label="站点类型" width="120" />
<el-table-column prop="version" label="版本" width="80" />
<el-table-column label="规则数" width="80">
<template #default="s">
<el-tag size="small">{{ getRuleCount(s.row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="更新时间" width="160">
<template #default="s">{{ fmt(s.row.updatedAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="140">
<template #default="s">
<el-button size="small" @click="showEditor(s.row.siteType, s.row)">编辑</el-button>
<el-button size="small" type="danger" text @click="deleteRules(s.row.siteType)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="editorVisible" :title="editorTitle" width="900px" destroy-on-close>
<el-table :data="editingRules" stripe size="small" style="margin-bottom:12px">
<el-table-column prop="type" label="类型" width="90">
<template #default="s"><el-select v-model="s.row.type" size="small"><el-option v-for="x in ['KILL','TRAFFIC','RATIO','LOGIN','HD']" :key="x" :label="x" :value="x"/></el-select></template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="160">
<template #default="s"><el-input v-model="s.row.name" size="small" /></template>
</el-table-column>
<el-table-column prop="metric" label="指标" width="140">
<template #default="s"><el-input v-model="s.row.metric" size="small" /></template>
</el-table-column>
<el-table-column prop="threshold" label="阈值" width="80">
<template #default="s"><el-input v-model.number="s.row.threshold" size="small" type="number" /></template>
</el-table-column>
<el-table-column prop="unit" label="单位" width="70">
<template #default="s"><el-input v-model="s.row.unit" size="small" /></template>
</el-table-column>
<el-table-column prop="condition" label="条件" width="70">
<template #default="s"><el-select v-model="s.row.condition" size="small"><el-option v-for="x in ['GT','LT','GTE','LTE','EQ']" :key="x" :label="x" :value="x"/></el-select></template>
</el-table-column>
<el-table-column prop="level" label="级别" width="90">
<template #default="s"><el-select v-model="s.row.level" size="small"><el-option v-for="x in ['CRITICAL','WARN','INFO']" :key="x" :label="x" :value="x"/></el-select></template>
</el-table-column>
<el-table-column label="操作" width="60">
<template #default="s"><el-button size="small" type="danger" text @click="editingRules.splice(s.$index,1)">删除</el-button></template>
</el-table-column>
</el-table>
<div style="display:flex;gap:8px">
<el-button size="small" @click="editingRules.push({type:'TRAFFIC',name:'',metric:'',threshold:0,unit:'DAY',condition:'GT',level:'WARN'})">+ 添加规则</el-button>
<el-button size="small" type="primary" @click="saveRules" :loading="saving">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import api from '../api'
const loading = ref(false), saving = ref(false), rules = ref([])
const editorVisible = ref(false), editorType = ref(''), editorTitle = ref('')
const editingRules = ref([])
const fmt = (t) => t ? t.substring(0,19).replace('T',' ') : '-'
const getRuleCount = (row) => { try { return JSON.parse(row.rulesJson || '[]').length } catch { return 0 } }
const fetchAll = async () => {
loading.value = true
try { rules.value = (await api.get('/admin/risk-rules')).data || [] }
catch { ElMessage.error('加载失败') }
finally { loading.value = false }
}
const showEditor = (siteType, row) => {
editorType.value = siteType
editorTitle.value = siteType ? '编辑: ' + siteType : '新建风险规则'
if (row) {
try { editingRules.value = JSON.parse(JSON.stringify(JSON.parse(row.rulesJson || '[]'))) }
catch { editingRules.value = [] }
} else {
editingRules.value = [{type:'KILL',name:'',metric:'',threshold:0,unit:'DAY',condition:'GT',level:'CRITICAL'}]
}
editorVisible.value = true
}
const editTypeInput = ref('')
const saveRules = async () => {
const t = editorType.value || editTypeInput.value
if (!t) { ElMessage.warning('请输入站点类型'); return }
saving.value = true
try {
await api.post('/risk-rules/' + t, { rules: editingRules.value })
ElMessage.success('已保存')
editorVisible.value = false
fetchAll()
} catch { ElMessage.error('保存失败') }
finally { saving.value = false }
}
const deleteRules = async (siteType) => {
if (!await ElMessageBox.confirm('确认删除 ' + siteType + ' 的风险规则?', '删除', { type: 'warning', confirmButtonText: '确定' }).catch(() => null)) return
try {
await api.delete('/admin/risk-rules/' + siteType)
ElMessage.success('已删除')
fetchAll()
} catch { ElMessage.error('删除失败') }
}
onMounted(fetchAll)
</script>