mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2026-05-25 21:35:05 +00:00
feat: add configurable DSL rule manager and simplify dynamic-group settings
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
<el-button icon="Share" type="primary" @click="prepareCapture">{{ t('prepareCapture') }}</el-button>
|
||||
<el-button icon="Setting" type="primary" @click="state.showWatermarkDialog = true">{{ t('setWatermark') }}</el-button>
|
||||
<el-button icon="Picture" type="primary" plain @click="openAssetManager">素材管理</el-button>
|
||||
<el-button icon="EditPen" type="primary" plain @click="openRuleManager">规则管理</el-button>
|
||||
<el-button v-if="!props.isEmbed" type="info" @click="loadExample">{{ t('loadExample') }}</el-button>
|
||||
<el-button v-if="!props.isEmbed" type="info" @click="showUpdateLog">{{ t('updateLog') }}</el-button>
|
||||
<el-button v-if="!props.isEmbed" type="warning" @click="showFeedbackForm">{{ t('feedback') }}</el-button>
|
||||
@@ -163,6 +164,158 @@
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 规则管理对话框 -->
|
||||
<el-dialog v-model="state.showRuleManagerDialog" title="规则管理" width="80%">
|
||||
<div class="rule-manager-actions">
|
||||
<el-button size="small" type="primary" @click="addExpressionRule">新增规则</el-button>
|
||||
<el-button size="small" type="primary" plain @click="addRuleVariable">新增变量</el-button>
|
||||
<el-button size="small" @click="exportRuleBundle">导出规则变量</el-button>
|
||||
<el-button size="small" @click="triggerRuleBundleImport">导入规则变量</el-button>
|
||||
<el-button size="small" @click="reloadRuleManagerDraft">重载当前配置</el-button>
|
||||
<el-button size="small" type="success" @click="applyRuleManagerConfig">应用并生效</el-button>
|
||||
<el-button size="small" type="warning" plain @click="restoreDefaultRuleConfig">恢复默认</el-button>
|
||||
<input
|
||||
ref="ruleBundleImportInputRef"
|
||||
type="file"
|
||||
accept=".json,application/json"
|
||||
class="asset-upload-input"
|
||||
@change="handleRuleBundleImport"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="ruleManagerTab" class="rule-manager-tabs">
|
||||
<el-tab-pane label="规则" name="rules">
|
||||
<div class="rule-table-wrap">
|
||||
<el-table
|
||||
v-if="ruleConfigDraft.expressionRules.length > 0"
|
||||
:data="ruleConfigDraft.expressionRules"
|
||||
size="small"
|
||||
border
|
||||
class="rule-table"
|
||||
>
|
||||
<el-table-column label="启用" width="70" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.enabled" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="级别" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.severity"
|
||||
size="small"
|
||||
:class="['rule-inline-select', 'severity-select', `severity-select--${row.severity || 'warning'}`]"
|
||||
>
|
||||
<el-option label="warning" value="warning" />
|
||||
<el-option label="error" value="error" />
|
||||
<el-option label="info" value="info" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="id" label="规则 ID" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="条件" min-width="260" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span class="rule-cell-ellipsis">{{ row.condition }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提示" min-width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span class="rule-cell-ellipsis">{{ row.message }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button size="small" text type="primary" @click="openExpressionRuleEditor($index)">编辑</el-button>
|
||||
<el-button size="small" text type="danger" @click="removeExpressionRule($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无规则,点击“新增规则”创建" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="变量" name="variables">
|
||||
<div class="variable-list">
|
||||
<div
|
||||
v-for="(item, index) in ruleConfigDraft.ruleVariables"
|
||||
:key="`${item.key}-${index}`"
|
||||
class="variable-item"
|
||||
>
|
||||
<el-form-item label="Key" class="variable-key">
|
||||
<el-input v-model="item.key" placeholder="如: fire_supporters" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Value" class="variable-value">
|
||||
<el-input
|
||||
v-model="item.value"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="逗号或换行分隔,如:辉夜姬,座敷童子"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button size="small" text type="danger" @click="removeRuleVariable(index)">删除</el-button>
|
||||
</div>
|
||||
<el-empty v-if="ruleConfigDraft.ruleVariables.length === 0" description="暂无变量,点击“新增变量”创建" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="文档说明" name="docs">
|
||||
<div class="rule-docs">
|
||||
<h4>作用域约定</h4>
|
||||
<pre>{{ ruleScopeDoc }}</pre>
|
||||
<h4>可用上下文</h4>
|
||||
<pre>{{ ruleContextDoc }}</pre>
|
||||
<h4>支持语法</h4>
|
||||
<pre>{{ ruleSyntaxDoc }}</pre>
|
||||
<h4>支持函数</h4>
|
||||
<pre>{{ ruleFunctionDoc }}</pre>
|
||||
<h4>表达式示例</h4>
|
||||
<pre>{{ ruleExamplesDoc }}</pre>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="ruleEditorVisible" title="编辑规则" width="56%">
|
||||
<el-form v-if="ruleEditorDraft" label-width="96px" class="rule-editor-form">
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="ruleEditorDraft.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则 ID">
|
||||
<el-input v-model="ruleEditorDraft.id" placeholder="unique_rule_id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="级别">
|
||||
<el-select
|
||||
v-model="ruleEditorDraft.severity"
|
||||
:class="['severity-select', `severity-select--${ruleEditorDraft.severity || 'warning'}`]"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="warning" value="warning" />
|
||||
<el-option label="error" value="error" />
|
||||
<el-option label="info" value="info" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警 code">
|
||||
<el-input v-model="ruleEditorDraft.code" placeholder="CUSTOM_EXPRESSION" />
|
||||
</el-form-item>
|
||||
<el-form-item label="条件表达式">
|
||||
<el-input
|
||||
v-model="ruleEditorDraft.condition"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder='示例:count(intersect(map(ctx.team.shikigamis, "name"), getVar("供火式神"))) == 0'
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="提示文案">
|
||||
<el-input v-model="ruleEditorDraft.message" placeholder="规则提示文案" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cancelRuleEditor">取消</el-button>
|
||||
<el-button type="primary" @click="saveRuleEditor">保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -185,6 +338,16 @@ import {
|
||||
subscribeCustomAssetStore,
|
||||
type CustomAssetItem
|
||||
} from '@/utils/customAssets';
|
||||
import {
|
||||
readSharedGroupRulesConfig,
|
||||
writeSharedGroupRulesConfig
|
||||
} from '@/utils/groupRulesConfigSource';
|
||||
import {
|
||||
DEFAULT_GROUP_RULES_CONFIG,
|
||||
type GroupRulesConfig,
|
||||
type ExpressionRuleDefinition,
|
||||
type RuleVariableDefinition
|
||||
} from '@/configs/groupRules';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
isEmbed?: boolean;
|
||||
@@ -217,6 +380,7 @@ const state = reactive({
|
||||
showFeedbackFormDialog: false, // 控制反馈表单对话框的显示状态
|
||||
showDataPreviewDialog: false, // 控制数据预览对话框的显示状态
|
||||
showAssetManagerDialog: false, // 控制素材管理对话框的显示状态
|
||||
showRuleManagerDialog: false, // 控制规则管理对话框的显示状态
|
||||
previewDataContent: '', // 存储预览的数据内容
|
||||
});
|
||||
const assetLibraries = ASSET_LIBRARIES.map((item) => ({
|
||||
@@ -225,12 +389,186 @@ const assetLibraries = ASSET_LIBRARIES.map((item) => ({
|
||||
}));
|
||||
const assetManagerLibrary = ref(assetLibraries[0]?.id || 'shikigami');
|
||||
const assetUploadInputRef = ref<HTMLInputElement | null>(null);
|
||||
const ruleBundleImportInputRef = ref<HTMLInputElement | null>(null);
|
||||
const managedAssets = reactive<Record<string, CustomAssetItem[]>>({});
|
||||
assetLibraries.forEach((item) => {
|
||||
managedAssets[item.id] = [];
|
||||
});
|
||||
let unsubscribeAssetStore: (() => void) | null = null;
|
||||
|
||||
const ruleManagerTab = ref<'rules' | 'variables' | 'docs'>('rules');
|
||||
|
||||
const cloneRuleConfig = (config: GroupRulesConfig): GroupRulesConfig => ({
|
||||
version: config.version,
|
||||
fireShikigamiWhitelist: [...config.fireShikigamiWhitelist],
|
||||
shikigamiYuhunBlacklist: config.shikigamiYuhunBlacklist.map((rule) => ({ ...rule })),
|
||||
shikigamiConflictPairs: config.shikigamiConflictPairs.map((rule) => ({ ...rule })),
|
||||
expressionRules: config.expressionRules.map((rule) => ({ ...rule })),
|
||||
ruleVariables: config.ruleVariables.map((item) => ({ ...item }))
|
||||
});
|
||||
|
||||
const createExpressionRule = (): ExpressionRuleDefinition => ({
|
||||
id: `rule_${Date.now()}`,
|
||||
enabled: true,
|
||||
severity: 'warning',
|
||||
code: 'CUSTOM_EXPRESSION',
|
||||
condition: 'false',
|
||||
message: '请补充规则提示文案'
|
||||
});
|
||||
|
||||
const createRuleVariable = (): RuleVariableDefinition => ({
|
||||
key: `var_${Date.now()}`,
|
||||
value: ''
|
||||
});
|
||||
|
||||
const ruleConfigDraft = ref<GroupRulesConfig>(cloneRuleConfig(readSharedGroupRulesConfig()));
|
||||
const ruleEditorVisible = ref(false);
|
||||
const editingRuleIndex = ref<number | null>(null);
|
||||
const ruleEditorDraft = ref<ExpressionRuleDefinition | null>(null);
|
||||
|
||||
const cloneExpressionRule = (rule: ExpressionRuleDefinition): ExpressionRuleDefinition => ({
|
||||
id: rule.id || `rule_${Date.now()}`,
|
||||
enabled: rule.enabled !== false,
|
||||
severity: rule.severity || 'warning',
|
||||
code: rule.code || 'CUSTOM_EXPRESSION',
|
||||
condition: rule.condition || 'false',
|
||||
message: rule.message || '请补充规则提示文案'
|
||||
});
|
||||
|
||||
const normalizeText = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
|
||||
const normalizeSeverity = (value: unknown): 'warning' | 'error' | 'info' => {
|
||||
return value === 'error' || value === 'info' ? value : 'warning';
|
||||
};
|
||||
const normalizeImportedExpressionRules = (value: unknown): ExpressionRuleDefinition[] => {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value
|
||||
.map((item) => {
|
||||
if (!item || typeof item !== 'object') return null;
|
||||
const raw = item as Record<string, unknown>;
|
||||
const id = normalizeText(raw.id);
|
||||
const condition = normalizeText(raw.condition);
|
||||
const message = normalizeText(raw.message);
|
||||
if (!id || !condition || !message) return null;
|
||||
const code = normalizeText(raw.code);
|
||||
return {
|
||||
id,
|
||||
condition,
|
||||
message,
|
||||
enabled: raw.enabled !== false,
|
||||
severity: normalizeSeverity(raw.severity),
|
||||
...(code ? { code } : {})
|
||||
};
|
||||
})
|
||||
.filter((item): item is ExpressionRuleDefinition => !!item);
|
||||
};
|
||||
const normalizeImportedRuleVariables = (value: unknown): RuleVariableDefinition[] => {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value
|
||||
.map((item) => {
|
||||
if (!item || typeof item !== 'object') return null;
|
||||
const raw = item as Record<string, unknown>;
|
||||
const key = normalizeText(raw.key);
|
||||
if (!key) return null;
|
||||
const variableValue = typeof raw.value === 'string' ? raw.value : String(raw.value ?? '');
|
||||
return { key, value: variableValue };
|
||||
})
|
||||
.filter((item): item is RuleVariableDefinition => !!item);
|
||||
};
|
||||
|
||||
const ruleScopeDoc = `规则新增字段(建议): scopeKind
|
||||
- team: 在队伍组(dynamic-group: team)上执行(当前已生效)
|
||||
- shikigami: 在式神组(dynamic-group: shikigami)上执行(规划中,当前未生效)
|
||||
|
||||
注意
|
||||
- scopeKind 决定“规则运行上下文”
|
||||
- 告警点击定位固定为:触发该规则的 dynamic-group`;
|
||||
|
||||
const ruleContextDoc = `当 scopeKind = "team"(队伍规则)
|
||||
ctx.team.shikigamis: 式神数组
|
||||
- 单项示例: { nodeId: "n1", assetId: "sp_kaguya", name: "辉夜姬", library: "shikigami" }
|
||||
|
||||
ctx.team.yuhuns: 御魂数组
|
||||
- 单项示例: { nodeId: "n2", assetId: "p4_poshi", name: "破势", library: "yuhun" }
|
||||
|
||||
ctx.group.id / ctx.group.name
|
||||
- 示例: "team-1" / "冲榜队A"
|
||||
|
||||
当 scopeKind = "shikigami"(式神规则,规划中,当前未生效)
|
||||
ctx.unit.shikigami: 当前式神对象(单个)
|
||||
- 示例: { nodeId: "n1", assetId: "sp_kaguya", name: "辉夜姬", library: "shikigami" }
|
||||
|
||||
ctx.unit.yuhuns: 当前式神关联御魂数组
|
||||
- 单项示例: { nodeId: "n2", assetId: "p4_poshi", name: "破势", library: "yuhun" }
|
||||
|
||||
通用共享变量
|
||||
shared.vars(变量 tab 配置后的 key/value 映射)
|
||||
- 示例:
|
||||
shared.vars["供火式神"] = ["辉夜姬", "座敷童子"]
|
||||
shared.vars["输出式神"] = ["阿修罗", "茨木童子"]`;
|
||||
|
||||
const ruleFunctionDoc = `count(value)
|
||||
- 用途: 计算数量(数组长度 / 字符串长度)
|
||||
- team 示例: count(ctx.team.shikigamis) >= 5
|
||||
- shikigami 示例: count(ctx.unit.yuhuns) >= 1
|
||||
|
||||
contains(collection, target)
|
||||
- 用途: 判断集合或字符串是否包含目标值
|
||||
- team 示例: contains(map(ctx.team.shikigamis, "name"), "辉夜姬")
|
||||
|
||||
intersect(leftArray, rightArray)
|
||||
- 用途: 取数组交集(去重)
|
||||
- team 示例: count(intersect(map(ctx.team.shikigamis, "name"), getVar("供火式神"))) > 0
|
||||
|
||||
map(collection, "fieldName")
|
||||
- 用途: 提取对象数组字段
|
||||
- team 示例: map(ctx.team.shikigamis, "name")
|
||||
- shikigami 示例: map(ctx.unit.yuhuns, "name")
|
||||
|
||||
unique(collection)
|
||||
- 用途: 数组去重
|
||||
- team 示例: count(unique(map(ctx.team.yuhuns, "name"))) >= 2
|
||||
|
||||
exists(value)
|
||||
- 用途: 判断值是否存在(非 null/空串;数组需长度>0)
|
||||
- 示例: exists(getVar("核心式神"))
|
||||
|
||||
lower(value) / upper(value)
|
||||
- 用途: 字符串大小写转换
|
||||
- 示例: contains(lower("PoShi"), "poshi")
|
||||
|
||||
getVar("变量Key")
|
||||
- 用途: 获取变量 tab 里配置的值(通常返回字符串数组)
|
||||
- 示例: getVar("供火式神")`;
|
||||
|
||||
const ruleSyntaxDoc = `支持
|
||||
- 字面量: "文本" / 数字 / true / false / null
|
||||
- 数组: ["辉夜姬", "座敷童子"]
|
||||
- 路径: ctx.team.shikigamis / ctx.unit.shikigami / shared.vars
|
||||
- 函数调用: count(...), contains(...), intersect(...), map(...)
|
||||
- 逻辑运算: && || !
|
||||
- 比较运算: == != > >= < <=
|
||||
- 算术运算: + - * /
|
||||
- 括号: ( ... )
|
||||
|
||||
不支持
|
||||
- index 语法(如 getIndexOf / arr[0])
|
||||
- 自定义遍历语法(for/while/foreach)
|
||||
- 自定义函数定义
|
||||
- 赋值语句
|
||||
- eval/new Function`;
|
||||
|
||||
const ruleExamplesDoc = `1) [team] 队伍至少有一个供火式神
|
||||
count(intersect(map(ctx.team.shikigamis, "name"), getVar("供火式神"))) > 0
|
||||
|
||||
2) [team] 队伍里不能同时出现千姬和腹肌清姬
|
||||
contains(map(ctx.team.shikigamis, "name"), "千姬") && contains(map(ctx.team.shikigamis, "name"), "腹肌清姬")
|
||||
|
||||
3) [team] 队伍御魂至少 2 种(避免全员同御魂)
|
||||
count(unique(map(ctx.team.yuhuns, "name"))) >= 2
|
||||
|
||||
4) [规划中的 shikigami scope] 当前式神是辉夜姬且其关联御魂包含破势
|
||||
ctx.unit.shikigami.name == "辉夜姬" && contains(map(ctx.unit.yuhuns, "name"), "破势")`;
|
||||
|
||||
const refreshManagedAssets = (library?: string) => {
|
||||
if (library) {
|
||||
managedAssets[library] = listCustomAssets(library);
|
||||
@@ -246,6 +584,166 @@ const openAssetManager = () => {
|
||||
state.showAssetManagerDialog = true;
|
||||
};
|
||||
|
||||
const reloadRuleManagerDraft = () => {
|
||||
ruleConfigDraft.value = cloneRuleConfig(readSharedGroupRulesConfig());
|
||||
cancelRuleEditor();
|
||||
};
|
||||
|
||||
const openRuleManager = () => {
|
||||
reloadRuleManagerDraft();
|
||||
ruleManagerTab.value = 'rules';
|
||||
state.showRuleManagerDialog = true;
|
||||
ruleEditorVisible.value = false;
|
||||
editingRuleIndex.value = null;
|
||||
ruleEditorDraft.value = null;
|
||||
};
|
||||
|
||||
const exportRuleBundle = () => {
|
||||
try {
|
||||
const payload = {
|
||||
version: ruleConfigDraft.value.version,
|
||||
expressionRules: ruleConfigDraft.value.expressionRules,
|
||||
ruleVariables: ruleConfigDraft.value.ruleVariables
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = `rule-bundle-${new Date().toISOString().slice(0, 10)}.json`;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showMessage('success', '规则变量已导出');
|
||||
} catch (error) {
|
||||
console.error('导出规则变量失败:', error);
|
||||
showMessage('error', '导出失败');
|
||||
}
|
||||
};
|
||||
|
||||
const triggerRuleBundleImport = () => {
|
||||
ruleBundleImportInputRef.value?.click();
|
||||
};
|
||||
|
||||
const handleRuleBundleImport = async (event: Event) => {
|
||||
const target = event.target as HTMLInputElement | null;
|
||||
const file = target?.files?.[0];
|
||||
if (!file) {
|
||||
if (target) target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const rawText = await file.text();
|
||||
const parsed = JSON.parse(rawText) as Record<string, unknown>;
|
||||
const importedRules = normalizeImportedExpressionRules(parsed.expressionRules);
|
||||
const importedVariables = normalizeImportedRuleVariables(parsed.ruleVariables);
|
||||
if (!importedRules.length && !importedVariables.length) {
|
||||
showMessage('warning', '导入文件中没有可用的规则或变量');
|
||||
return;
|
||||
}
|
||||
ruleConfigDraft.value = {
|
||||
...ruleConfigDraft.value,
|
||||
expressionRules: importedRules,
|
||||
ruleVariables: importedVariables
|
||||
};
|
||||
cancelRuleEditor();
|
||||
showMessage('success', '规则变量已导入,请点击“应用并生效”');
|
||||
} catch (error) {
|
||||
console.error('导入规则变量失败:', error);
|
||||
showMessage('error', '导入失败,文件格式错误');
|
||||
} finally {
|
||||
if (target) target.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const addExpressionRule = () => {
|
||||
const newRule = createExpressionRule();
|
||||
ruleConfigDraft.value.expressionRules.push(newRule);
|
||||
openExpressionRuleEditor(ruleConfigDraft.value.expressionRules.length - 1);
|
||||
ruleManagerTab.value = 'rules';
|
||||
};
|
||||
|
||||
const removeExpressionRule = (index: number) => {
|
||||
if (editingRuleIndex.value === index) {
|
||||
cancelRuleEditor();
|
||||
}
|
||||
ruleConfigDraft.value.expressionRules.splice(index, 1);
|
||||
if (editingRuleIndex.value != null && editingRuleIndex.value > index) {
|
||||
editingRuleIndex.value -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
const openExpressionRuleEditor = (index: number) => {
|
||||
const target = ruleConfigDraft.value.expressionRules[index];
|
||||
if (!target) return;
|
||||
editingRuleIndex.value = index;
|
||||
ruleEditorDraft.value = cloneExpressionRule(target);
|
||||
ruleEditorVisible.value = true;
|
||||
};
|
||||
|
||||
const cancelRuleEditor = () => {
|
||||
ruleEditorVisible.value = false;
|
||||
editingRuleIndex.value = null;
|
||||
ruleEditorDraft.value = null;
|
||||
};
|
||||
|
||||
const saveRuleEditor = () => {
|
||||
const index = editingRuleIndex.value;
|
||||
const draft = ruleEditorDraft.value;
|
||||
if (index == null || !draft) {
|
||||
return;
|
||||
}
|
||||
const ruleId = draft.id?.trim();
|
||||
const condition = draft.condition?.trim();
|
||||
const message = draft.message?.trim();
|
||||
if (!ruleId || !condition || !message) {
|
||||
showMessage('warning', '规则 ID、条件表达式、提示文案不能为空');
|
||||
return;
|
||||
}
|
||||
const normalized = cloneExpressionRule({
|
||||
...draft,
|
||||
id: ruleId,
|
||||
condition,
|
||||
message
|
||||
});
|
||||
ruleConfigDraft.value.expressionRules[index] = normalized;
|
||||
cancelRuleEditor();
|
||||
};
|
||||
|
||||
const addRuleVariable = () => {
|
||||
ruleConfigDraft.value.ruleVariables.push(createRuleVariable());
|
||||
ruleManagerTab.value = 'variables';
|
||||
};
|
||||
|
||||
const removeRuleVariable = (index: number) => {
|
||||
ruleConfigDraft.value.ruleVariables.splice(index, 1);
|
||||
};
|
||||
|
||||
const applyRuleManagerConfig = () => {
|
||||
try {
|
||||
const normalized = writeSharedGroupRulesConfig(ruleConfigDraft.value);
|
||||
ruleConfigDraft.value = cloneRuleConfig(normalized);
|
||||
showMessage('success', '规则配置已生效');
|
||||
} catch (error) {
|
||||
console.error('应用规则配置失败:', error);
|
||||
showMessage('error', '规则配置应用失败');
|
||||
}
|
||||
};
|
||||
|
||||
const restoreDefaultRuleConfig = () => {
|
||||
ElMessageBox.confirm('恢复默认会覆盖当前规则和变量,是否继续?', '提示', {
|
||||
confirmButtonText: '恢复默认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
const normalized = writeSharedGroupRulesConfig(DEFAULT_GROUP_RULES_CONFIG);
|
||||
ruleConfigDraft.value = cloneRuleConfig(normalized);
|
||||
cancelRuleEditor();
|
||||
showMessage('success', '已恢复默认规则配置');
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
});
|
||||
};
|
||||
|
||||
const getManagedAssets = (libraryId: string) => {
|
||||
return managedAssets[libraryId] || [];
|
||||
};
|
||||
@@ -763,4 +1261,118 @@ const handleClose = (done) => {
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.rule-manager-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.rule-manager-tabs {
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.rule-table-wrap,
|
||||
.variable-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.variable-item {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.rule-table :deep(.el-table__cell) {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.rule-inline-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.severity-select--warning :deep(.el-input__wrapper) {
|
||||
background: #fff7ed;
|
||||
box-shadow: inset 0 0 0 1px #fed7aa;
|
||||
}
|
||||
|
||||
.severity-select--warning :deep(.el-input__inner) {
|
||||
color: #9a3412;
|
||||
}
|
||||
|
||||
.severity-select--error :deep(.el-input__wrapper) {
|
||||
background: #fef2f2;
|
||||
box-shadow: inset 0 0 0 1px #fecaca;
|
||||
}
|
||||
|
||||
.severity-select--error :deep(.el-input__inner) {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.severity-select--info :deep(.el-input__wrapper) {
|
||||
background: #eff6ff;
|
||||
box-shadow: inset 0 0 0 1px #bfdbfe;
|
||||
}
|
||||
|
||||
.severity-select--info :deep(.el-input__inner) {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.rule-cell-ellipsis {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rule-editor-form :deep(.el-form-item) {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.variable-item {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.variable-key,
|
||||
.variable-value {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rule-docs {
|
||||
max-height: 460px;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.rule-docs h4 {
|
||||
margin: 6px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.rule-docs pre {
|
||||
margin: 0 0 12px;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.variable-item {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user