refactor: remove action field, rules define kill conditions only

- WARN is auto-detected by RiskCheckEngine (riskPct > 80), not configured in rules

- Each rule simply defines: type + metric + condition + threshold + unit -> 封号

- Card always shows red left border and red '封号' arrow, no action selector

- Presets reduced to 7 kill-only templates, no WARN presets
This commit is contained in:
mediabot-pt
2026-07-06 21:12:47 +08:00
parent 040b97914e
commit 842c01d79a

View File

@@ -62,7 +62,7 @@
<el-dropdown-menu>
<el-dropdown-item v-for="p in presets" :key="p.name" :command="p">
<div style="font-size:12px">
<span :style="{color:actionColor(p.action),fontWeight:600}">{{ p.name }}</span>
<span style="font-weight:600">{{ p.name }}</span>
<span style="color:#909399;margin-left:4px">{{ typeLabel(p.type) }}</span>
</div>
</el-dropdown-item>
@@ -75,7 +75,7 @@
<!-- 规则卡片列表 -->
<div v-if="parsed.length" style="display:flex;flex-direction:column;gap:10px">
<div v-for="(rule, idx) in parsed" :key="idx"
:class="['rule-card', rule.action.toLowerCase()]"
class="rule-card"
:draggable="true"
@dragstart="onDragStart($event, idx)"
@dragover.prevent="onDragOver($event, idx)"
@@ -86,18 +86,12 @@
<div class="card-header" @dblclick="rule._fold = !rule._fold">
<div class="card-header-left">
<el-icon class="drag-handle"><Rank /></el-icon>
<!-- 操作:封号 / 告警 -->
<el-select v-model="rule.action" size="small" @change="sync" style="width:80px">
<el-option v-for="x in actionOptions" :key="x.value" :label="x.label" :value="x.value">
<span :style="{color:actionColor(x.value),fontWeight:600}">{{ x.label }}</span>
</el-option>
</el-select>
<!-- 类型 -->
<el-select v-model="rule.type" size="small" @change="sync" style="width:90px">
<el-select v-model="rule.type" size="small" @change="sync" style="width:100px">
<el-option v-for="x in typeOptions" :key="x.value" :label="x.label" :value="x.value" />
</el-select>
<!-- 规则名称 -->
<el-input v-model="rule.name" size="small" placeholder="规则名称" @input="sync" style="width:200px" />
<el-input v-model="rule.name" size="small" placeholder="规则名称" @input="sync" style="width:220px" />
</div>
<div class="card-header-right">
<span class="rule-index">#{{ idx + 1 }}</span>
@@ -120,7 +114,7 @@
<span class="field-hint">{{ applicableHint(rule.applicable) }}</span>
</div>
<!-- 触发条件 -->
<!-- 触发条件:当 [指标] [条件] [阈值] [单位] → 封号 -->
<div class="field-row">
<span class="field-label">触发条件</span>
<span class="condition-text">当</span>
@@ -133,7 +127,7 @@
<el-input v-model.number="rule.threshold" size="small" type="number" placeholder="阈值" @input="sync" style="width:90px" />
<el-input v-model="rule.unit" size="small" placeholder="单位" @input="sync" style="width:70px" />
<span class="then-arrow">→</span>
<span :style="{color:actionColor(rule.action),fontWeight:600}">{{ actionText(rule.action) }}</span>
<span style="color:#f56c6c;font-weight:600">封号</span>
</div>
<!-- 规则描述 -->
@@ -151,7 +145,7 @@
<el-dialog v-model="showPresets" title="PT 站点管理规则模板" width="700px" :close-on-click-modal="false">
<div style="margin-bottom:12px;padding:10px 14px;background:#f0f9ff;border-radius:6px;font-size:13px;color:#606266">
<p style="margin:0 0 4px;font-weight:500">规则体系:等级豁免 → 账号存活 → 流量监控 → H&R 惩罚 → 分享率检查</p>
<p style="margin:0;font-size:12px;color:#909399">满足豁免条件的用户不受任何规则影响。导入后可调整参数和操作类型(封号/告警)。</p>
<p style="margin:0;font-size:12px;color:#909399">满足豁免条件的用户不受任何规则影响。系统会自动根据风险百分比触发告警(riskPct &gt; 80%)。</p>
</div>
<div v-for="cat in presetCategories" :key="cat.title" style="margin-bottom:12px">
<div style="font-weight:600;font-size:13px;margin-bottom:6px;color:#303133">{{ cat.title }}</div>
@@ -161,7 +155,7 @@
<el-checkbox :value="p.name" :label="p.name" style="margin-right:0;height:auto">
<span style="font-weight:500;font-size:13px">{{ p.name }}</span>
</el-checkbox>
<span :style="{color:actionColor(p.action),fontSize:'11px',fontWeight:500}">{{ actionText(p.action) }}</span>
<span style="color:#f56c6c;font-size:11px;font-weight:500">封号</span>
<span style="color:#909399;font-size:11px;flex:1">{{ presetDesc(p) }}</span>
</div>
</el-checkbox-group>
@@ -180,10 +174,6 @@ import { Plus, ArrowDown, ArrowUp, Rank, Delete } from '@element-plus/icons-vue'
import api from '../api'
// ============ 选项配置 ============
const actionOptions = [
{ value: 'KILL', label: '封号' },
{ value: 'WARN', label: '告警' },
]
const typeOptions = [
{ value: 'KILL', label: '封号规则' },
{ value: 'TRAFFIC', label: '流量监控' },
@@ -217,34 +207,24 @@ const metricOptions = [
]
const typeLabel = (t) => typeOptions.find(x => x.value === t)?.label || t
const actionColor = (a) => ({ KILL: '#f56c6c', WARN: '#e6a23c' }[a] || '#909399')
const actionText = (a) => ({ KILL: '封号', WARN: '告警' }[a] || a)
const applicableHint = (a) => {
const m = { '!sealed':'未封存用户', 'sealed':'已封存用户', 'zero_traffic':'上传/下载均为 0', 'new_account_30d':'从未完成种子且无流量增量' }
return a ? `— ${m[a] || ''}` : ''
}
// ============ 预设规则模板 ============
// 每条模板包含 type(监控类型)+ action(KILL/WARN 操作)+ 触发条件
const presets = [
// 四、账号存活策略
{ name:'未封存不登录封号', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:150, unit:'天', condition:'GT', applicable:'!sealed', description:'未封存账号连续150天不登录即封号' },
{ name:'封存账号不登录封号', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:365, unit:'天', condition:'GT', applicable:'sealed', description:'封存账号连续365天不登录即封号' },
{ name:'零流量账号清理', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:30, unit:'天', condition:'GT', applicable:'zero_traffic', description:'零流量用户连续30天不登录即封号' },
{ name:'未封存不登录封号', type:'KILL', metric:'days_since_last_login', threshold:150, unit:'天', condition:'GT', applicable:'!sealed', description:'未封存账号连续150天不登录即封号' },
{ name:'封存账号不登录封号', type:'KILL', metric:'days_since_last_login', threshold:365, unit:'天', condition:'GT', applicable:'sealed', description:'封存账号连续365天不登录即封号' },
{ name:'零流量账号清理', type:'KILL', metric:'days_since_last_login', threshold:30, unit:'天', condition:'GT', applicable:'zero_traffic', description:'零流量用户连续30天不登录即封号' },
// 五、零活跃新用户清理
{ name:'零活跃不登录封号', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:7, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户连续7天不登录即封号' },
{ name:'零活跃注册天数封号', type:'KILL', action:'KILL', metric:'days_since_registration', threshold:30, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户注册满30天即封号' },
{ name:'零活跃不登录封号', type:'KILL', metric:'days_since_last_login', threshold:7, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户连续7天不登录即封号' },
{ name:'零活跃注册天数封号', type:'KILL', metric:'days_since_registration', threshold:30, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户注册满30天即封号' },
// 流量监控
{ name:'连续无流量封号', type:'TRAFFIC', action:'KILL', metric:'days_since_last_traffic', threshold:90, unit:'天', condition:'GT', description:'连续90天无上传/下载流量即封号' },
{ name:'无流量告警', type:'TRAFFIC', action:'WARN', metric:'days_since_last_traffic', threshold:60, unit:'天', condition:'GT', description:'连续60天无流量时发出告警提醒' },
// 登录监控
{ name:'长期未登录告警', type:'LOGIN', action:'WARN', metric:'days_since_last_login', threshold:30, unit:'天', condition:'GT', description:'超过30天未登录时发出告警提醒' },
{ name:'即将触发封号提醒', type:'LOGIN', action:'WARN', metric:'days_since_last_login', threshold:140, unit:'天', condition:'GT', applicable:'!sealed', description:'未封存用户即将达到150天封号线时提前告警' },
{ name:'连续无流量封号', type:'TRAFFIC', metric:'days_since_last_traffic', threshold:90, unit:'天', condition:'GT', description:'连续90天无上传/下载流量即封号' },
// H&R
{ name:'H&R 封号', type:'HD', action:'KILL', metric:'hnr_count', threshold:5, unit:'次', condition:'GTE', description:'H&R违规次数达到5次即封号' },
{ name:'H&R 告警', type:'HD', action:'WARN', metric:'hnr_count', threshold:3, unit:'次', condition:'GTE', description:'H&R违规次数达到3次发出告警' },
// 分享率
{ name:'分享率过低告警', type:'RATIO', action:'WARN', metric:'ratio', threshold:1.0, unit:'倍', condition:'LT', description:'分享率低于1.0时发出告警提醒' },
{ name:'H&R 封号', type:'HD', metric:'hnr_count', threshold:5, unit:'次', condition:'GTE', description:'H&R违规次数达到5次即封号' },
]
const presetCategories = [
@@ -252,8 +232,6 @@ const presetCategories = [
{ title:'五、零活跃新用户清理', items: presets.filter(p => p.name.includes('零活跃')) },
{ title:'流量监控', items: presets.filter(p => p.type === 'TRAFFIC') },
{ title:'H&R 惩罚', items: presets.filter(p => p.type === 'HD') },
{ title:'登录提醒', items: presets.filter(p => p.type === 'LOGIN') },
{ title:'分享率监控', items: presets.filter(p => p.type === 'RATIO') },
]
const presetDesc = (p) => {
@@ -312,7 +290,7 @@ const sync = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) }
const addRule = () => {
parsed.value = [...parsed.value, {
type:'KILL', action:'KILL', name:'', metric:'days_since_last_login', threshold:0, unit:'天', condition:'GT',
type:'KILL', name:'', metric:'days_since_last_login', threshold:0, unit:'天', condition:'GT',
applicable:null, description:'', _fold:false
}]
}
@@ -365,14 +343,13 @@ onMounted(fetchAll)
<style scoped>
.rule-card {
border: 1px solid #e4e7ed;
border-left: 4px solid #f56c6c;
border-radius: 8px;
background: #fff;
transition: box-shadow .2s;
cursor: default;
}
.rule-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,.08); }
.rule-card.kill { border-left: 4px solid #f56c6c; }
.rule-card.warn { border-left: 4px solid #e6a23c; }
.card-header {
display: flex; align-items: center; justify-content: space-between;
@@ -390,5 +367,5 @@ onMounted(fetchAll)
.field-label { font-weight: 500; color: #606266; font-size: 12px; min-width: 56px; }
.field-hint { color: #909399; font-size: 11px; }
.condition-text { color: #909399; font-size: 12px; }
.then-arrow { color: #409eff; font-size: 16px; font-weight: 700; margin: 0 2px; }
.then-arrow { color: #f56c6c; font-size: 16px; font-weight: 700; margin: 0 2px; }
</style>