feat: prevent duplicate rules by type+applicable key

- Uniqueness key: type + applicable condition combination

- '添加规则' button warns if KILL(null) already exists

- Dropdown and template dialog disabled+greyed for already-added presets

- Import skips duplicates with warning message

- Applicable dropdown change guarded against duplicates
This commit is contained in:
mediabot-pt
2026-07-06 22:44:15 +08:00
parent accb38986a
commit 110af4a79c

View File

@@ -60,10 +60,11 @@
<el-button size="small">快速添加<el-icon style="margin-left:2px"><ArrowDown /></el-icon></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="p in presets" :key="p.name" :command="p">
<el-dropdown-item v-for="p in presets" :key="p.name" :command="p" :disabled="existsRule(p)">
<div style="font-size:12px">
<span style="font-weight:600">{{ p.name }}</span>
<span style="color:#909399;margin-left:4px">{{ typeLabel(p.type) }}</span>
<span :style="{fontWeight:600,color:existsRule(p)?'#c0c4cc':''}">{{ p.name }}</span>
<span :style="{color:existsRule(p)?'#c0c4cc':'#909399',marginLeft:'4px'}">{{ typeLabel(p.type) }}</span>
<span v-if="existsRule(p)" style="color:#c0c4cc;margin-left:4px;font-size:11px">(已添加)</span>
</div>
</el-dropdown-item>
</el-dropdown-menu>
@@ -170,7 +171,7 @@
<!-- 适用条件(通用) -->
<div class="field-row">
<span class="field-label">适用条件</span>
<el-select v-model="rule.applicable" size="small" @change="sync" placeholder="无条件(始终适用)" style="width:200px">
<el-select v-model="rule.applicable" size="small" @change="onApplicableChange(rule)" placeholder="无条件(始终适用)" style="width:200px">
<el-option value="" label="无条件" />
<el-option v-for="x in applicableOptions" :key="x.value" :label="x.label" :value="x.value" />
</el-select>
@@ -199,12 +200,14 @@
<div style="font-weight:600;font-size:13px;margin-bottom:6px;color:#303133">{{ cat.title }}</div>
<el-checkbox-group v-model="selectedPresets">
<div v-for="p in cat.items" :key="p.name"
style="padding:8px 12px;border:1px solid #ebeef5;border-radius:6px;margin-bottom:4px;display:flex;align-items:center;gap:8px">
<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>
style="padding:8px 12px;border:1px solid #ebeef5;border-radius:6px;margin-bottom:4px;display:flex;align-items:center;gap:8px"
:style="existsRule(p)?{background:'#f5f7fa',borderColor:'#e4e7ed'}:{}">
<el-checkbox :value="p.name" :label="p.name" :disabled="existsRule(p)" style="margin-right:0;height:auto">
<span :style="{fontWeight:500,fontSize:'13px',color:existsRule(p)?'#c0c4cc':''}">{{ p.name }}</span>
</el-checkbox>
<span :style="{fontSize:'11px',fontWeight:500,color:p.type==='EXEMPT'?'#67c23a':p.type==='RATIO'||p.type==='DRATIO'?'#e6a23c':'#f56c6c'}">{{ p.type==='EXEMPT' ? '保号' : p.type==='RATIO'||p.type==='DRATIO' ? '缓刑7天' : '封号' }}</span>
<span style="color:#909399;font-size:11px;flex:1">{{ presetDesc(p) }}</span>
<span :style="{color:existsRule(p)?'#c0c4cc':'#909399',fontSize:'11px',flex:1}">{{ presetDesc(p) }}</span>
<span v-if="existsRule(p)" style="color:#c0c4cc;font-size:11px">(已添加)</span>
</div>
</el-checkbox-group>
</div>
@@ -294,6 +297,17 @@ const taskReqDefaults = [
{ metric:'avg_seed_time', label:'平均做种时间', threshold:24, unit:'小时', condition:'GTE', enabled:true },
]
const onApplicableChange = (rule) => {
const key = `${rule.type}|${rule.applicable||''}`
if (parsed.value.some(r => r !== rule && `${r.type}|${r.applicable||''}` === key)) {
ElMessage.warning('同类型+适用条件的规则已存在')
rule.applicable = ''
sync()
return
}
sync()
}
const onTypeChange = (rule) => {
if (rule.type === 'TASK') {
if (!rule.requirements || !rule.requirements.length) {
@@ -437,10 +451,13 @@ const sync = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) }
const addRule = () => {
const d = typeDefaults['KILL']
parsed.value = [...parsed.value, {
type:'KILL', metric:d.metric, threshold:0, unit:d.unit, condition:d.condition,
applicable:null, description:'', _fold:false
}]
const r = { type:'KILL', metric:d.metric, threshold:0, unit:d.unit, condition:d.condition,
applicable:null, description:'', _fold:false }
if (existsRule(r)) {
ElMessage.warning('活跃度(无条件)规则已存在,同类型+适用条件只能添加一次')
return
}
parsed.value = [...parsed.value, r]
}
const confirmRemove = async (idx) => {
@@ -454,9 +471,19 @@ const ensureTaskReqs = (rule) => {
}
}
const ruleKey = (r) => `${r.type}|${r.applicable||''}`
const existsRule = (rule) => {
return parsed.value.some(r => ruleKey(r) === ruleKey(rule))
}
const addPreset = (p) => {
const r = { ...p, _fold: false }
ensureTaskReqs(r)
if (existsRule(r)) {
ElMessage.warning(`规则「${p.name}」已存在,同类型+适用条件只能添加一次`)
return
}
parsed.value = [...parsed.value, r]
}
@@ -466,10 +493,17 @@ const importPresets = () => {
ensureTaskReqs(r)
return r
})
parsed.value = [...parsed.value, ...items]
const dupes = items.filter(r => existsRule(r))
if (dupes.length) {
ElMessage.warning(`跳过 ${dupes.length} 条重复规则`)
}
const newItems = items.filter(r => !existsRule(r))
if (newItems.length) {
parsed.value = [...parsed.value, ...newItems]
ElMessage.success(`已导入 ${newItems.length} 条规则`)
}
selectedPresets.value = []
showPresets.value = false
ElMessage.success(`已导入 ${items.length} 条规则`)
}
// 拖拽排序