diff --git a/web/src/views/RiskRules.vue b/web/src/views/RiskRules.vue index 84829c3..7983e87 100644 --- a/web/src/views/RiskRules.vue +++ b/web/src/views/RiskRules.vue @@ -89,7 +89,8 @@ - {{ metricLabel(rule.metric) }} {{ condLabel(rule.condition) }} {{ rule.threshold }}{{ rule.unit || '' }} + 考核周期 {{ rule.threshold }}天 + {{ metricLabel(rule.metric) }} {{ condLabel(rule.condition) }} {{ rule.threshold }}{{ rule.unit || '' }} → 封号/删除账号 @@ -105,14 +106,32 @@
- -
- 阈值 - - {{ rule.unit || '' }} -
+ + - + + + +
适用条件 @@ -164,6 +183,7 @@ const typeOptions = [ { value: 'TRAFFIC', label: '流量' }, { value: 'HD', label: 'H&R' }, { value: 'RATIO', label: '分享率' }, + { value: 'TASK', label: '新人考核' }, ] const condOptions = [ { value: 'GT', label: '大于' }, @@ -186,6 +206,7 @@ const metricOptions = [ { value: 'download_gb', label: '下载量(GB)' }, { value: 'bonus', label: '魔力值' }, { value: 'seeding', label: '做种数' }, + { value: 'seeding_score', label: '做种积分' }, { value: 'hnr_count', label: 'H&R 违规次数' }, { value: 'avg_seed_time', label: '平均做种时间(小时)' }, ] @@ -201,9 +222,25 @@ const typeDefaults = { const metricLabel = (m) => metricOptions.find(x => x.value === m)?.label || m || '' const condLabel = (c) => condOptions.find(x => x.value === c)?.label || c || '' +const taskReqDefaults = [ + { metric:'upload_gb', label:'上传量', threshold:10, unit:'GB', condition:'GTE' }, + { metric:'download_gb', label:'下载量', threshold:10, unit:'GB', condition:'GTE' }, + { metric:'bonus', label:'魔力值', threshold:1000, unit:'', condition:'GTE' }, + { metric:'seeding_score', label:'做种积分', threshold:100, unit:'', condition:'GTE' }, + { metric:'avg_seed_time', label:'平均做种时间', threshold:24, unit:'小时', condition:'GTE' }, +] + const onTypeChange = (rule) => { - const d = typeDefaults[rule.type] - if (d) { rule.metric = d.metric; rule.condition = d.condition; rule.unit = d.unit } + if (rule.type === 'TASK') { + if (!rule.requirements || !rule.requirements.length) { + rule.requirements = JSON.parse(JSON.stringify(taskReqDefaults)) + } + rule.threshold = rule.threshold || 30 + rule.unit = '天' + } else { + const d = typeDefaults[rule.type] + if (d) { rule.metric = d.metric; rule.condition = d.condition; rule.unit = d.unit } + } sync() } @@ -220,6 +257,8 @@ const presets = [ { name:'连续无流量封号', type:'TRAFFIC', metric:'days_since_last_traffic', threshold:90, unit:'天', condition:'GT', description:'连续90天无上传/下载流量即封号' }, // H&R { name:'H&R 封号', type:'HD', metric:'hnr_count', threshold:5, unit:'次', condition:'GTE', description:'H&R违规次数达到5次即封号' }, + // 新人考核 + { name:'新人考核', type:'TASK', threshold:30, unit:'天', description:'新注册用户需在考核周期内完成各项要求' }, ] const presetCategories = [ @@ -227,9 +266,11 @@ 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 === 'TASK') }, ] const presetDesc = (p) => { + if (p.type === 'TASK') return `考核周期 ${p.threshold||30} 天,需满足上传/下载/魔力值/做种积分/做种时间要求` const condLabel = condOptions.find(c => c.value === p.condition)?.label || p.condition const appLabel = applicableOptions.find(a => a.value === p.applicable)?.label || '' return `${metricOptions.find(m=>m.value===p.metric)?.label||p.metric} ${condLabel} ${p.threshold}${p.unit||''}${appLabel?' ('+appLabel+')':''}` @@ -301,10 +342,24 @@ const confirmRemove = async (idx) => { const arr = [...parsed.value]; arr.splice(idx, 1); parsed.value = arr } -const addPreset = (p) => { parsed.value = [...parsed.value, { ...p, _fold: false }] } +const ensureTaskReqs = (rule) => { + if (rule.type === 'TASK' && !rule.requirements) { + rule.requirements = JSON.parse(JSON.stringify(taskReqDefaults)) + } +} + +const addPreset = (p) => { + const r = { ...p, _fold: false } + ensureTaskReqs(r) + parsed.value = [...parsed.value, r] +} const importPresets = () => { - const items = presets.filter(p => selectedPresets.value.includes(p.name)).map(p => ({ ...p, _fold: false })) + const items = presets.filter(p => selectedPresets.value.includes(p.name)).map(p => { + const r = { ...p, _fold: false } + ensureTaskReqs(r) + return r + }) parsed.value = [...parsed.value, ...items] selectedPresets.value = [] showPresets.value = false @@ -368,5 +423,7 @@ onMounted(fetchAll) .field-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 13px; } .field-label { font-weight: 500; color: #606266; font-size: 12px; min-width: 56px; } .field-hint { color: #909399; font-size: 12px; } +.condition-text { color: #909399; font-size: 12px; } +.req-divider { font-size: 12px; font-weight: 500; color: #303133; padding-top: 4px; border-top: 1px dashed #dcdfe6; } .then-arrow { color: #f56c6c; font-size: 14px; font-weight: 700; margin: 0 2px; }