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>
|
<el-button size="small">快速添加<el-icon style="margin-left:2px"><ArrowDown /></el-icon></el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<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">
|
<div style="font-size:12px">
|
||||||
<span style="font-weight:600">{{ p.name }}</span>
|
<span :style="{fontWeight:600,color:existsRule(p)?'#c0c4cc':''}">{{ p.name }}</span>
|
||||||
<span style="color:#909399;margin-left:4px">{{ typeLabel(p.type) }}</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>
|
</div>
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
@@ -170,7 +171,7 @@
|
|||||||
<!-- 适用条件(通用) -->
|
<!-- 适用条件(通用) -->
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<span class="field-label">适用条件</span>
|
<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 value="" label="无条件" />
|
||||||
<el-option v-for="x in applicableOptions" :key="x.value" :label="x.label" :value="x.value" />
|
<el-option v-for="x in applicableOptions" :key="x.value" :label="x.label" :value="x.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -199,12 +200,14 @@
|
|||||||
<div style="font-weight:600;font-size:13px;margin-bottom:6px;color:#303133">{{ cat.title }}</div>
|
<div style="font-weight:600;font-size:13px;margin-bottom:6px;color:#303133">{{ cat.title }}</div>
|
||||||
<el-checkbox-group v-model="selectedPresets">
|
<el-checkbox-group v-model="selectedPresets">
|
||||||
<div v-for="p in cat.items" :key="p.name"
|
<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">
|
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">
|
:style="existsRule(p)?{background:'#f5f7fa',borderColor:'#e4e7ed'}:{}">
|
||||||
<span style="font-weight:500;font-size:13px">{{ p.name }}</span>
|
<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>
|
</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="{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>
|
</div>
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,6 +297,17 @@ const taskReqDefaults = [
|
|||||||
{ metric:'avg_seed_time', label:'平均做种时间', threshold:24, unit:'小时', condition:'GTE', enabled:true },
|
{ 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) => {
|
const onTypeChange = (rule) => {
|
||||||
if (rule.type === 'TASK') {
|
if (rule.type === 'TASK') {
|
||||||
if (!rule.requirements || !rule.requirements.length) {
|
if (!rule.requirements || !rule.requirements.length) {
|
||||||
@@ -437,10 +451,13 @@ const sync = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) }
|
|||||||
|
|
||||||
const addRule = () => {
|
const addRule = () => {
|
||||||
const d = typeDefaults['KILL']
|
const d = typeDefaults['KILL']
|
||||||
parsed.value = [...parsed.value, {
|
const r = { type:'KILL', metric:d.metric, threshold:0, unit:d.unit, condition:d.condition,
|
||||||
type:'KILL', metric:d.metric, threshold:0, unit:d.unit, condition:d.condition,
|
applicable:null, description:'', _fold:false }
|
||||||
applicable:null, description:'', _fold:false
|
if (existsRule(r)) {
|
||||||
}]
|
ElMessage.warning('活跃度(无条件)规则已存在,同类型+适用条件只能添加一次')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parsed.value = [...parsed.value, r]
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmRemove = async (idx) => {
|
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 addPreset = (p) => {
|
||||||
const r = { ...p, _fold: false }
|
const r = { ...p, _fold: false }
|
||||||
ensureTaskReqs(r)
|
ensureTaskReqs(r)
|
||||||
|
if (existsRule(r)) {
|
||||||
|
ElMessage.warning(`规则「${p.name}」已存在,同类型+适用条件只能添加一次`)
|
||||||
|
return
|
||||||
|
}
|
||||||
parsed.value = [...parsed.value, r]
|
parsed.value = [...parsed.value, r]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,10 +493,17 @@ const importPresets = () => {
|
|||||||
ensureTaskReqs(r)
|
ensureTaskReqs(r)
|
||||||
return 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 = []
|
selectedPresets.value = []
|
||||||
showPresets.value = false
|
showPresets.value = false
|
||||||
ElMessage.success(`已导入 ${items.length} 条规则`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拖拽排序
|
// 拖拽排序
|
||||||
|
|||||||
Reference in New Issue
Block a user