|
@@ -1,5 +1,5 @@
|
|
|
<script setup lang="ts" name="aidopS8WatchRuleConfig">
|
|
<script setup lang="ts" name="aidopS8WatchRuleConfig">
|
|
|
-import { computed, onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref } from 'vue';
|
|
|
|
|
|
|
+import { computed, onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
import AidopDemoShell from '../../components/AidopDemoShell.vue';
|
|
import AidopDemoShell from '../../components/AidopDemoShell.vue';
|
|
|
import WatchRuleWizardDialog from './components/WatchRuleWizardDialog.vue';
|
|
import WatchRuleWizardDialog from './components/WatchRuleWizardDialog.vue';
|
|
@@ -22,9 +22,15 @@ const exceptionTypes = ref<S8ExceptionTypeConfigRow[]>([]);
|
|
|
const drawerOpen = ref(false);
|
|
const drawerOpen = ref(false);
|
|
|
const editingRow = ref<S8WatchRuleConfigRow | null>(null);
|
|
const editingRow = ref<S8WatchRuleConfigRow | null>(null);
|
|
|
|
|
|
|
|
-// A-03: filter state
|
|
|
|
|
-const filterSceneCode = ref('');
|
|
|
|
|
-const filterSeverity = ref('');
|
|
|
|
|
|
|
+// CONFIG-RULE-CENTER-SHELL-MVP-1:业务化筛选 + 前端本地分页 + 运行态列折叠。
|
|
|
|
|
+const searchKeyword = ref('');
|
|
|
|
|
+const filterMechanism = ref<'' | 'DATE' | 'VALUE_RANGE' | 'RATIO'>('');
|
|
|
|
|
+const filterStageCode = ref('');
|
|
|
|
|
+const filterEnabledStatus = ref<'' | 'ENABLED' | 'DISABLED'>('');
|
|
|
|
|
+const showAdvancedColumns = ref(false);
|
|
|
|
|
+const currentPage = ref(1);
|
|
|
|
|
+const pageSize = ref(10);
|
|
|
|
|
+const PAGE_SIZE_OPTIONS = [10, 20, 50];
|
|
|
|
|
|
|
|
// B-05: advanced fields collapse
|
|
// B-05: advanced fields collapse
|
|
|
const advancedExpanded = ref(false);
|
|
const advancedExpanded = ref(false);
|
|
@@ -180,17 +186,71 @@ function stopAutoRefresh() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// A-03: filtered rows
|
|
|
|
|
|
|
+// CONFIG-RULE-CENTER-SHELL-MVP-1:业务化筛选 + 前端分页 + 业务字段解析。
|
|
|
|
|
+function parseRuleBizFields(row: S8WatchRuleConfigRow): { objectLabel: string; metricLabel: string; thresholdDisplay: string } {
|
|
|
|
|
+ const fallback = { objectLabel: '未配置', metricLabel: '未配置', thresholdDisplay: '未配置' };
|
|
|
|
|
+ if (!row.paramsJson) return fallback;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const p = JSON.parse(row.paramsJson) as Record<string, unknown>;
|
|
|
|
|
+ return {
|
|
|
|
|
+ objectLabel: typeof p.objectLabel === 'string' && p.objectLabel ? p.objectLabel : '未配置',
|
|
|
|
|
+ metricLabel: typeof p.metricLabel === 'string' && p.metricLabel ? p.metricLabel : '未配置',
|
|
|
|
|
+ thresholdDisplay: typeof p.thresholdDisplay === 'string' && p.thresholdDisplay ? p.thresholdDisplay : '未配置',
|
|
|
|
|
+ };
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return fallback;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const filteredRows = computed(() => {
|
|
const filteredRows = computed(() => {
|
|
|
let r = rows.value;
|
|
let r = rows.value;
|
|
|
- if (filterSceneCode.value) r = r.filter(x => x.sceneCode === filterSceneCode.value);
|
|
|
|
|
- if (filterSeverity.value) r = r.filter(x => (x.severity ?? '').toUpperCase() === filterSeverity.value);
|
|
|
|
|
|
|
+ const kw = searchKeyword.value.trim().toLowerCase();
|
|
|
|
|
+ if (kw) {
|
|
|
|
|
+ r = r.filter(x => {
|
|
|
|
|
+ const code = (x.ruleCode ?? '').toLowerCase();
|
|
|
|
|
+ const display = watchRuleDisplayName(x.ruleCode).toLowerCase();
|
|
|
|
|
+ return code.includes(kw) || display.includes(kw);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filterMechanism.value) r = r.filter(x => x.ruleMechanism === filterMechanism.value);
|
|
|
|
|
+ if (filterStageCode.value) r = r.filter(x => x.stageCode === filterStageCode.value);
|
|
|
|
|
+ if (filterEnabledStatus.value === 'ENABLED') r = r.filter(x => x.enabled === true);
|
|
|
|
|
+ else if (filterEnabledStatus.value === 'DISABLED') r = r.filter(x => x.enabled === false);
|
|
|
return r;
|
|
return r;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const sceneCodeOptions = computed(() =>
|
|
|
|
|
- [...new Set(rows.value.map(r => r.sceneCode).filter((c): c is string => !!c))].sort(),
|
|
|
|
|
-);
|
|
|
|
|
|
|
+const paginatedRows = computed(() => {
|
|
|
|
|
+ const start = (currentPage.value - 1) * pageSize.value;
|
|
|
|
|
+ return filteredRows.value.slice(start, start + pageSize.value);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 6 chip 统计始终基于全量 rows,不受筛选影响。
|
|
|
|
|
+const centerStats = computed(() => ({
|
|
|
|
|
+ total: rows.value.length,
|
|
|
|
|
+ enabled: rows.value.filter(x => x.enabled === true).length,
|
|
|
|
|
+ disabled: rows.value.filter(x => x.enabled === false).length,
|
|
|
|
|
+ date: rows.value.filter(x => x.ruleMechanism === 'DATE').length,
|
|
|
|
|
+ valueRange: rows.value.filter(x => x.ruleMechanism === 'VALUE_RANGE').length,
|
|
|
|
|
+ ratio: rows.value.filter(x => x.ruleMechanism === 'RATIO').length,
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+function ensureCurrentPageInRange() {
|
|
|
|
|
+ const total = filteredRows.value.length;
|
|
|
|
|
+ const maxPage = Math.max(1, Math.ceil(total / pageSize.value));
|
|
|
|
|
+ if (currentPage.value > maxPage) currentPage.value = maxPage;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetFilters() {
|
|
|
|
|
+ searchKeyword.value = '';
|
|
|
|
|
+ filterMechanism.value = '';
|
|
|
|
|
+ filterStageCode.value = '';
|
|
|
|
|
+ filterEnabledStatus.value = '';
|
|
|
|
|
+ currentPage.value = 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 任一筛选/搜索变化后回到第一页。
|
|
|
|
|
+watch([searchKeyword, filterMechanism, filterStageCode, filterEnabledStatus], () => { currentPage.value = 1; });
|
|
|
|
|
+watch(pageSize, () => { currentPage.value = 1; });
|
|
|
|
|
|
|
|
// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举两档;旧值兜底兼容回显。
|
|
// S8-SEVERITY-FOLLOW-SERIOUS-STANDARDIZE-EXEC-1:业务枚举两档;旧值兜底兼容回显。
|
|
|
function severityLabel(s?: string | null): string {
|
|
function severityLabel(s?: string | null): string {
|
|
@@ -478,6 +538,8 @@ async function loadRows() {
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
|
try {
|
|
try {
|
|
|
rows.value = await s8ConfigApi.watchRules.list({ tenantId: TENANT_ID, factoryId: FACTORY_ID });
|
|
rows.value = await s8ConfigApi.watchRules.list({ tenantId: TENANT_ID, factoryId: FACTORY_ID });
|
|
|
|
|
+ // CONFIG-RULE-CENTER-SHELL-MVP-1:自动刷新后若 currentPage 越界则回退到最后一页,不打断用户翻页。
|
|
|
|
|
+ ensureCurrentPageInRange();
|
|
|
} catch (e: any) {
|
|
} catch (e: any) {
|
|
|
ElMessage.error(e?.message ?? '加载监视规则失败');
|
|
ElMessage.error(e?.message ?? '加载监视规则失败');
|
|
|
} finally {
|
|
} finally {
|
|
@@ -930,6 +992,8 @@ function openWizard() {
|
|
|
}
|
|
}
|
|
|
async function onWizardSaved() {
|
|
async function onWizardSaved() {
|
|
|
await loadRows();
|
|
await loadRows();
|
|
|
|
|
+ // CONFIG-RULE-CENTER-SHELL-MVP-1:保存新规则后回到第 1 页,让用户看到新建结果。
|
|
|
|
|
+ currentPage.value = 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
@@ -945,19 +1009,39 @@ onDeactivated(() => stopAutoRefresh());
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
- <AidopDemoShell title="监视规则配置" subtitle="S8 / 配置 / 监视规则(参数模板化编辑 + 调度运行态;列表 30 秒自动刷新)">
|
|
|
|
|
- <!-- A-03: toolbar with scene/severity filters; CONFIG-RULE-WIZARD-MVP-1: 新增"添加规则"入口 -->
|
|
|
|
|
|
|
+ <AidopDemoShell title="规则配置中心" subtitle="配置异常监控规则、报警机制、监控对象与判定标准。新建后默认停用,演示前由人工启用。">
|
|
|
|
|
+ <!-- CONFIG-RULE-CENTER-SHELL-MVP-1:主操作 + 统计 chip + 业务化筛选 + 前端分页 -->
|
|
|
<div class="rule-toolbar">
|
|
<div class="rule-toolbar">
|
|
|
- <el-button size="small" type="primary" @click="openWizard">添加规则</el-button>
|
|
|
|
|
|
|
+ <el-button size="small" type="primary" @click="openWizard">新建监控配置</el-button>
|
|
|
<el-button size="small" @click="loadRows" :loading="loading">手动刷新</el-button>
|
|
<el-button size="small" @click="loadRows" :loading="loading">手动刷新</el-button>
|
|
|
- <el-select v-model="filterSceneCode" size="small" clearable placeholder="场景筛选" style="width: 140px">
|
|
|
|
|
- <el-option v-for="c in sceneCodeOptions" :key="c" :label="c" :value="c" />
|
|
|
|
|
|
|
+ <el-checkbox v-model="showAdvancedColumns" size="small" style="margin-left: 8px">显示运行态</el-checkbox>
|
|
|
|
|
+ <span class="rule-toolbar__hint">每 30 秒自动刷新;编辑抽屉打开期间暂停刷新</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="rule-stats">
|
|
|
|
|
+ <el-tag size="small" type="info" effect="plain">全部 {{ centerStats.total }}</el-tag>
|
|
|
|
|
+ <el-tag size="small" type="success" effect="plain">启用 {{ centerStats.enabled }}</el-tag>
|
|
|
|
|
+ <el-tag size="small" type="info" effect="plain">停用 {{ centerStats.disabled }}</el-tag>
|
|
|
|
|
+ <el-tag size="small" type="warning" effect="plain">日期类 {{ centerStats.date }}</el-tag>
|
|
|
|
|
+ <el-tag size="small" type="warning" effect="plain">数值超差类 {{ centerStats.valueRange }}</el-tag>
|
|
|
|
|
+ <el-tag size="small" type="warning" effect="plain">比例类 {{ centerStats.ratio }}</el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="rule-filters">
|
|
|
|
|
+ <el-input v-model="searchKeyword" size="small" clearable placeholder="搜索规则名称 / 编码" style="width: 240px" />
|
|
|
|
|
+ <el-select v-model="filterMechanism" size="small" clearable placeholder="报警机制" style="width: 150px">
|
|
|
|
|
+ <el-option label="日期类" value="DATE" />
|
|
|
|
|
+ <el-option label="数值超差类" value="VALUE_RANGE" />
|
|
|
|
|
+ <el-option label="比例类" value="RATIO" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
- <el-select v-model="filterSeverity" size="small" clearable placeholder="严重度筛选" style="width: 120px">
|
|
|
|
|
- <el-option label="关注" value="FOLLOW" />
|
|
|
|
|
- <el-option label="严重" value="SERIOUS" />
|
|
|
|
|
|
|
+ <el-select v-model="filterStageCode" size="small" clearable filterable placeholder="阶段维度" style="width: 170px">
|
|
|
|
|
+ <el-option v-for="n in stageNodes" :key="n.nodeCode" :label="n.nodeName" :value="n.nodeCode" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
- <span class="rule-toolbar__hint">每 30 秒自动刷新;编辑抽屉打开期间暂停刷新</span>
|
|
|
|
|
|
|
+ <el-select v-model="filterEnabledStatus" size="small" clearable placeholder="状态" style="width: 130px">
|
|
|
|
|
+ <el-option label="启用" value="ENABLED" />
|
|
|
|
|
+ <el-option label="停用" value="DISABLED" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <el-button size="small" @click="resetFilters">重置</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<WatchRuleWizardDialog
|
|
<WatchRuleWizardDialog
|
|
@@ -971,21 +1055,12 @@ onDeactivated(() => stopAutoRefresh());
|
|
|
@saved="onWizardSaved"
|
|
@saved="onWizardSaved"
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
- <!-- A-02: updated labels; A-03: filteredRows + sceneCode/severity columns -->
|
|
|
|
|
- <el-table v-loading="loading" :data="filteredRows" border stripe size="small">
|
|
|
|
|
- <el-table-column prop="ruleCode" label="规则编码" width="180" />
|
|
|
|
|
- <el-table-column label="规则名称" width="170" show-overflow-tooltip>
|
|
|
|
|
|
|
+ <!-- CONFIG-RULE-CENTER-SHELL-MVP-1:业务台账(默认隐藏运行态列) -->
|
|
|
|
|
+ <el-table v-loading="loading" :data="paginatedRows" border stripe size="small">
|
|
|
|
|
+ <el-table-column label="规则名称" width="180" show-overflow-tooltip>
|
|
|
<template #default="{ row }">{{ watchRuleDisplayName(row.ruleCode) }}</template>
|
|
<template #default="{ row }">{{ watchRuleDisplayName(row.ruleCode) }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="规则类型" width="100">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-tag :type="row.ruleType ? 'success' : 'info'" size="small">
|
|
|
|
|
- {{ ruleTypeLabel(row.ruleType) }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="sceneCode" label="场景" width="120" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column label="机制" width="100">
|
|
|
|
|
|
|
+ <el-table-column label="报警机制" width="110">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-tag v-if="row.ruleMechanism" size="small" type="info">{{ ruleMechanismLabel(row.ruleMechanism) }}</el-tag>
|
|
<el-tag v-if="row.ruleMechanism" size="small" type="info">{{ ruleMechanismLabel(row.ruleMechanism) }}</el-tag>
|
|
|
<span v-else>—</span>
|
|
<span v-else>—</span>
|
|
@@ -994,34 +1069,55 @@ onDeactivated(() => stopAutoRefresh());
|
|
|
<el-table-column label="阶段维度" width="140" show-overflow-tooltip>
|
|
<el-table-column label="阶段维度" width="140" show-overflow-tooltip>
|
|
|
<template #default="{ row }">{{ stageNodeLabel(row.stageCode) }}</template>
|
|
<template #default="{ row }">{{ stageNodeLabel(row.stageCode) }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="订单流程" width="160" show-overflow-tooltip>
|
|
|
|
|
|
|
+ <el-table-column label="订单流程" width="140" show-overflow-tooltip>
|
|
|
<template #default="{ row }">{{ orderFlowNodeLabel(row.orderFlowCode) }}</template>
|
|
<template #default="{ row }">{{ orderFlowNodeLabel(row.orderFlowCode) }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="严重度" width="80">
|
|
|
|
|
|
|
+ <el-table-column label="监控对象" width="130" show-overflow-tooltip>
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
- <el-tag v-if="row.severity" :type="severityTagType(row.severity)" size="small">
|
|
|
|
|
- {{ severityLabel(row.severity) }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- <span v-else>—</span>
|
|
|
|
|
|
|
+ <span :class="{ 'rule-biz-empty': parseRuleBizFields(row).objectLabel === '未配置' }">{{ parseRuleBizFields(row).objectLabel }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="监控指标" width="140" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span :class="{ 'rule-biz-empty': parseRuleBizFields(row).metricLabel === '未配置' }">{{ parseRuleBizFields(row).metricLabel }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="判定标准" width="140" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span :class="{ 'rule-biz-empty': parseRuleBizFields(row).thresholdDisplay === '未配置' }">{{ parseRuleBizFields(row).thresholdDisplay }}</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="启用" width="80">
|
|
|
|
|
|
|
+ <el-table-column label="状态" width="80">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
- <el-tag :type="row.enabled ? 'success' : 'info'" size="small">
|
|
|
|
|
- {{ row.enabled ? '启用' : '停用' }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
|
|
+ <el-tag :type="row.enabled ? 'success' : 'info'" size="small">{{ row.enabled ? '启用' : '停用' }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="严重度" width="80">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag v-if="row.severity" :type="severityTagType(row.severity)" size="small">{{ severityLabel(row.severity) }}</el-tag>
|
|
|
|
|
+ <span v-else>—</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="检测频率" width="100">
|
|
<el-table-column label="检测频率" width="100">
|
|
|
<template #default="{ row }">{{ pollIntervalLabel(row.pollIntervalSeconds) }}</template>
|
|
<template #default="{ row }">{{ pollIntervalLabel(row.pollIntervalSeconds) }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="持续异常次数" width="115">
|
|
|
|
|
|
|
+ <el-table-column prop="ruleCode" label="内部编码" width="200" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }"><span class="rule-code-mono">{{ row.ruleCode }}</span></template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <!-- 运行态高级列:默认隐藏,由"显示运行态"开关控制 -->
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="规则类型" width="100">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="row.ruleType ? 'success' : 'info'" size="small">{{ ruleTypeLabel(row.ruleType) }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" prop="sceneCode" label="场景" width="120" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="持续异常次数" width="115">
|
|
|
<template #default="{ row }">连续 {{ row.triggerCountRequired ?? 1 }} 次</template>
|
|
<template #default="{ row }">连续 {{ row.triggerCountRequired ?? 1 }} 次</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="持续正常次数" width="115">
|
|
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="持续正常次数" width="115">
|
|
|
<template #default="{ row }">连续 {{ row.recoverCountRequired ?? 1 }} 次</template>
|
|
<template #default="{ row }">连续 {{ row.recoverCountRequired ?? 1 }} 次</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="上次执行" width="160">
|
|
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="上次执行" width="160">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-tooltip v-if="row.lastRunAt" :content="String(row.lastRunAt)" placement="top">
|
|
<el-tooltip v-if="row.lastRunAt" :content="String(row.lastRunAt)" placement="top">
|
|
|
<span>{{ row.lastRunAt }}</span>
|
|
<span>{{ row.lastRunAt }}</span>
|
|
@@ -1029,30 +1125,29 @@ onDeactivated(() => stopAutoRefresh());
|
|
|
<span v-else>—</span>
|
|
<span v-else>—</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="下次执行" width="160">
|
|
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="下次执行" width="160">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-tag v-if="isPaused(row)" type="warning" size="small">已暂停</el-tag>
|
|
<el-tag v-if="isPaused(row)" type="warning" size="small">已暂停</el-tag>
|
|
|
<el-tag v-else-if="isRunning(row)" type="info" size="small">执行中</el-tag>
|
|
<el-tag v-else-if="isRunning(row)" type="info" size="small">执行中</el-tag>
|
|
|
<span v-else>{{ nextRunDisplay(row) }}</span>
|
|
<span v-else>{{ nextRunDisplay(row) }}</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="上次状态" width="100">
|
|
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="上次状态" width="100">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-tag v-if="row.lastStatus" :type="lastStatusTagType(row.lastStatus)" size="small">{{ lastStatusLabel(row.lastStatus) }}</el-tag>
|
|
<el-tag v-if="row.lastStatus" :type="lastStatusTagType(row.lastStatus)" size="small">{{ lastStatusLabel(row.lastStatus) }}</el-tag>
|
|
|
<span v-else>—</span>
|
|
<span v-else>—</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="失败次数" width="90">
|
|
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="失败次数" width="90">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-badge v-if="(row.consecutiveFailureCount ?? 0) > 0" :value="row.consecutiveFailureCount" type="danger" />
|
|
<el-badge v-if="(row.consecutiveFailureCount ?? 0) > 0" :value="row.consecutiveFailureCount" type="danger" />
|
|
|
<span v-else>0</span>
|
|
<span v-else>0</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="耗时" width="80">
|
|
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="耗时" width="80">
|
|
|
<template #default="{ row }">{{ row.lastDurationMs != null ? `${row.lastDurationMs}ms` : '—' }}</template>
|
|
<template #default="{ row }">{{ row.lastDurationMs != null ? `${row.lastDurationMs}ms` : '—' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <!-- A-05: mapLastError() applied -->
|
|
|
|
|
- <el-table-column label="错误摘要" min-width="180" show-overflow-tooltip>
|
|
|
|
|
|
|
+ <el-table-column v-if="showAdvancedColumns" label="错误摘要" min-width="180" show-overflow-tooltip>
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-tooltip v-if="row.lastError" :content="String(row.lastError)" placement="top">
|
|
<el-tooltip v-if="row.lastError" :content="String(row.lastError)" placement="top">
|
|
|
<span class="rule-error">{{ mapLastError(row.lastError) }}</span>
|
|
<span class="rule-error">{{ mapLastError(row.lastError) }}</span>
|
|
@@ -1074,6 +1169,17 @@ onDeactivated(() => stopAutoRefresh());
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
+ <div class="rule-pagination">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ v-model:current-page="currentPage"
|
|
|
|
|
+ v-model:page-size="pageSize"
|
|
|
|
|
+ :page-sizes="PAGE_SIZE_OPTIONS"
|
|
|
|
|
+ :total="filteredRows.length"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ background
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<el-drawer v-model="drawerOpen" :title="formTitle" size="540px" direction="rtl" :before-close="(done: () => void) => { closeDrawer(); done(); }">
|
|
<el-drawer v-model="drawerOpen" :title="formTitle" size="540px" direction="rtl" :before-close="(done: () => void) => { closeDrawer(); done(); }">
|
|
|
<div v-if="editingRow" class="rule-edit">
|
|
<div v-if="editingRow" class="rule-edit">
|
|
|
|
|
|
|
@@ -1381,6 +1487,34 @@ onDeactivated(() => stopAutoRefresh());
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
|
|
+/* CONFIG-RULE-CENTER-SHELL-MVP-1 */
|
|
|
|
|
+.rule-stats {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ margin: 8px 0 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.rule-filters {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.rule-biz-empty {
|
|
|
|
|
+ color: var(--el-text-color-placeholder);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.rule-code-mono {
|
|
|
|
|
+ font-family: var(--el-font-family-mono, ui-monospace, monospace);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+}
|
|
|
|
|
+.rule-pagination {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+}
|
|
|
.rule-edit {
|
|
.rule-edit {
|
|
|
padding: 0 4px 80px;
|
|
padding: 0 4px 80px;
|
|
|
}
|
|
}
|