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:
@@ -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} 条规则`)
|
||||
}
|
||||
|
||||
// 拖拽排序
|
||||
|
||||
Reference in New Issue
Block a user