系统配置静态页完成

This commit is contained in:
2026-08-26 13:06:14 +08:00
parent 2908a10ee5
commit 26f33d8219
2 changed files with 532 additions and 26 deletions
+2
View File
@@ -65,6 +65,7 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal'] NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NProgress: typeof import('naive-ui')['NProgress'] NProgress: typeof import('naive-ui')['NProgress']
NRadio: typeof import('naive-ui')['NRadio'] NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup'] NRadioGroup: typeof import('naive-ui')['NRadioGroup']
@@ -152,6 +153,7 @@ declare global {
const NMessageProvider: typeof import('naive-ui')['NMessageProvider'] const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
const NModal: typeof import('naive-ui')['NModal'] const NModal: typeof import('naive-ui')['NModal']
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
const NPopconfirm: typeof import('naive-ui')['NPopconfirm']
const NProgress: typeof import('naive-ui')['NProgress'] const NProgress: typeof import('naive-ui')['NProgress']
const NRadio: typeof import('naive-ui')['NRadio'] const NRadio: typeof import('naive-ui')['NRadio']
const NRadioGroup: typeof import('naive-ui')['NRadioGroup'] const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
+524 -20
View File
@@ -9,29 +9,158 @@ const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16)); const gap = computed(() => (appStore.isMobile ? 0 : 16));
const aiEnabled = ref<boolean>(true);
// AI 辅助功能配置 // AI 辅助功能配置
const aiEnabled = ref<boolean>(true);
const aiConfig = ref<{address:string, mode:number|null, key:string,timeout:null|number}>({ const aiConfig = ref<{address:string, mode:number|null, key:string,timeout:null|number}>({
address:'', address:'',
mode:null, mode:null,
key:'', key:'',
timeout:null, timeout:null,
}) })
const aiTested = ref<string>("none");
const modeOptions = ref<{label:string, value:number}[]>([ const modeOptions = ref<{label:string, value:number}[]>([
{label:'kimi-k2(企业私有化部署)',value:1} {label:'kimi-k2(企业私有化部署)',value:1}
]) ])
const testing = ref<boolean>(false);
const showLog = ref<boolean>(false);
const AI_LOGS = [
{ scene: "AI-01 变更识别", user: "张工艺", change: "MOC-2026-R01-0041", time: "08-24 09:12", cost: "1.8s", adopted: "修改后采纳" },
{ scene: "AI-03 风险预分析", user: "张工艺", change: "MOC-2026-R01-0041", time: "08-24 09:15", cost: "2.4s", adopted: "采纳" },
{ scene: "AI-07 审批辅助", user: "李主任", change: "MOC-2026-R01-0039", time: "08-24 08:47", cost: "1.2s", adopted: "采纳" },
{ scene: "AI-06 PSSR 生成", user: "张工艺", change: "MOC-2026-R02-0052", time: "08-23 16:20", cost: "2.1s", adopted: "未采纳" },
{ scene: "AI-09 关闭辅助判定", user: "系统管理员", change: "MOC-2026-R01-0035", time: "08-23 15:02", cost: "1.5s", adopted: "采纳" },
];
// 变更类型 // 变更类型
const typeRef = ref<any>(null);
const typeModal = ref<boolean>(false);
const typeLoading = ref<boolean>(false);
const currentType = ref<string>('add');
const typeInfo = ref<{id:number, name:string, desc?:string}>({
id:0,
name:'',
desc:'',
})
const typeRules = {
name:{required:true, message:'请输入类型名称', trigger:'blur'},
}
const typeList = ref<{id:number, name:string, desc?:string}[]>([ const typeList = ref<{id:number, name:string, desc?:string}[]>([
{id:1,name:'工艺', desc:'工艺参数、配方、操作条件、操作规程等工艺技术类变更'}, {id:1,name:'工艺', desc:'工艺参数、配方、操作条件、操作规程等工艺技术类变更'},
{id:2,name:'设备', desc:'设备本体、附件、材质、规格、备件等硬件类变更'}, {id:2,name:'设备', desc:'设备本体、附件、材质、规格、备件等硬件类变更'},
{id:3,name:'管理', desc:'制度、职责、程序、组织与人员等管理类变更'}, {id:3,name:'管理', desc:'制度、职责、程序、组织与人员等管理类变更'},
]) ])
// 新增类型
const addType = () => {
typeInfo.value = {
id:0,
name:'',
desc:'',
}
currentType.value = 'add';
typeModal.value = true;
}
// 编辑类型
const editType = (row:any) => {
typeInfo.value = {
id:row.id,
name:row.name,
desc:row.desc,
}
currentType.value = 'edit';
typeModal.value = true;
}
// 删除类型
const deleteType = (item:any) => {
typeList.value = typeList.value.filter((i:any) => i.id !== item.id);
}
// 保存类型
const typeSave = async (e:MouseEvent) => {
e.preventDefault()
typeRef.value?.validate((errors:any) => {
if (!errors) {
typeLoading.value = true;
try {
// await addTypeApi(typeInfo.value);
window.$message?.success('操作成功')
typeLoading.value = false;
typeModal.value = false;
} finally {
typeLoading.value = false;
}
}
})
}
// 变更等级 // 变更等级
const levelRef = ref<any>(null);
const levelModal = ref<boolean>(false);
const levelLoading = ref<boolean>(false);
const currentLevel = ref<string>('add');
const toolsOptions = ref<{label:string, value:number}[]>([
{label:'AI预分析',value:1},
{label:'检查表法',value:2},
{label:'HAZOP',value:3},
{label:'JSA',value:4},
{label:'SCL',value:5},
])
const levelInfo = ref<{id:number, name:string, way:string,tools:number[]}>({
id:0,
name:'',
way:'',
tools:[],
})
const levelRules = {
name:{required:true, message:'请输入等级名称', trigger:'blur'},
way:{required:true, message:'请输入等级判定方式', trigger:'blur'},
tools:{type:'array', required:true, message:'请选择分析工具', trigger:['blur','change']},
}
const levelList = ref<{id:number, name:string, way:string,tools:{id:number, name:string}[]}[]>([ const levelList = ref<{id:number, name:string, way:string,tools:{id:number, name:string}[]}[]>([
{id:1,name:'一般', way:'等级评分< 20 分',tools:[{id:1,name:'AI预分析'},{id:2,name:'检查表法'}]}, {id:1,name:'一般', way:'等级评分< 20 分',tools:[{id:1,name:'AI预分析'},{id:2,name:'检查表法'}]},
{id:2,name:'重要', way:'等级评分 > 20 分',tools:[{id:1,name:'AI预分析'},{id:2,name:'HAZOP'},{id:3,name:'JSA'},{id:4,name:'SCL'}]}, {id:2,name:'重要', way:'等级评分 > 20 分',tools:[{id:1,name:'AI预分析'},{id:2,name:'HAZOP'},{id:3,name:'JSA'},{id:4,name:'SCL'}]},
]) ])
// 新增等级
const addLevel = () => {
levelInfo.value = {
id:0,
name:'',
way:'',
tools:[],
}
currentLevel.value = 'add';
levelModal.value = true;
}
// 编辑类型
const editLevel = (row:any) => {
levelInfo.value = {
id:row.id,
name:row.name,
way:row.way,
tools:row.tools.map((item:any) => (item.id)),
}
currentLevel.value = 'edit';
levelModal.value = true;
}
// 删除类型
const deleteLevel = (item:any) => {
levelList.value = levelList.value.filter((i:any) => i.id !== item.id);
}
// 保存类型
const levelSave = async (e:MouseEvent) => {
e.preventDefault()
levelRef.value?.validate((errors:any) => {
if (!errors) {
levelLoading.value = true;
try {
// await addLevelApi(levelInfo.value);
window.$message?.success('操作成功')
levelLoading.value = false;
levelModal.value = false;
} finally {
levelLoading.value = false;
}
}
})
}
// 变更编号规则 // 变更编号规则
const ruleInfo = ref<{prefix:string, year:null|string, code:number|null, digit:number|null, rule:number|null}>({ const ruleInfo = ref<{prefix:string, year:null|string, code:number|null, digit:number|null, rule:number|null}>({
prefix:'MOC', prefix:'MOC',
@@ -50,14 +179,91 @@ const ruleOptions = ref<{label:string, value:number}[]>([
{label:'按年度+装置代码重置',value:1} {label:'按年度+装置代码重置',value:1}
]) ])
// ---------- 风险矩阵设置(按分析方法) ----------
const dimensionModal = ref<boolean>(false);
const dimensionLoading = ref<boolean>(false);
const dimForm = ref({ s: [], l: [] });
const LV_CLS = [
"border-emerald-300 bg-emerald-100 text-emerald-700",
"border-yellow-300 bg-yellow-100 text-yellow-700",
"border-orange-300 bg-orange-100 text-orange-700",
"border-red-300 bg-red-100 text-red-700",
];
const matrices = ref([
{
tool: "HAZOP",
sLabels: ["轻微", "一般", "较重", "严重", "灾难"],
lLabels: ["极少发生", "较少发生", "偶尔发生", "可能发生", "频繁发生"],
levels: [
{ name: "低", min: 1, max: 4, cls: LV_CLS[0] },
{ name: "一般", min: 5, max: 9, cls: LV_CLS[1] },
{ name: "较大", min: 10, max: 16, cls: LV_CLS[2] },
{ name: "重大", min: 17, max: 25, cls: LV_CLS[3] },
],
},
{
tool: "JSA",
sLabels: ["轻微伤害", "轻伤", "重伤", "单人死亡", "多人死亡"],
lLabels: ["极不可能", "不太可能", "有可能", "较可能", "很可能"],
levels: [
{ name: "低", min: 1, max: 6, cls: LV_CLS[0] },
{ name: "一般", min: 7, max: 12, cls: LV_CLS[1] },
{ name: "较大", min: 13, max: 19, cls: LV_CLS[2] },
{ name: "重大", min: 20, max: 25, cls: LV_CLS[3] },
],
},
{
tool: "SCL",
sLabels: ["轻微影响", "一般影响", "较大影响", "严重影响", "灾难性影响"],
lLabels: ["极少", "较少", "偶尔", "可能", "经常"],
levels: [
{ name: "低", min: 1, max: 2, cls: LV_CLS[0] },
{ name: "一般", min: 3, max: 6, cls: LV_CLS[1] },
{ name: "较大", min: 7, max: 12, cls: LV_CLS[2] },
{ name: "重大", min: 13, max: 25, cls: LV_CLS[3] },
],
},
]);
const activeMatrix = ref(0);
const curMatrix = computed(() => matrices.value[activeMatrix.value]);
const cellScore = (s:number, l:number) => (s + 1) * (l + 1);
const cellLevel = (s:number, l:number) => {
const sc = cellScore(s, l);
return curMatrix.value.levels.find((lv:{min:number,max:number}) => sc >= lv.min && sc <= lv.max);
}
// AI 辅助功能切换
const toggle = (v: boolean) => { const toggle = (v: boolean) => {
window.$message?.success(v ? "AI 辅助功能已启用" : "AI 辅助功能已关闭"); window.$message?.success(v ? "AI 辅助功能已启用" : "AI 辅助功能已关闭");
aiEnabled.value = v; aiEnabled.value = v;
} }
const testConn = () => {
testing.value = true;
setTimeout(() => {
testing.value = false;
aiTested.value = "ok";
window.$message?.success(`连接测试通过:${aiConfig.value.mode} 响应正常(耗时 0.8s`);
}, 900);
}
const saveAiCfg = () => {
window.$message?.success("AI 接口配置已保存(配置变更留痕,立即生效)");
}
const dimensionSave = () => {
dimensionLoading.value = true;
setTimeout(() => {
window.$message?.success("操作成功");
dimensionLoading.value = false;
dimensionModal.value = false;
}, 1000);
}
const openDimEdit = () => {
dimForm.value = { s: [...curMatrix.value.sLabels], l: [...curMatrix.value.lLabels] };
dimensionModal.value = true;
}
// tabs 切换
const handleTabChange = (v: number) => {
activeMatrix.value = v;
}
</script> </script>
@@ -96,6 +302,11 @@ const toggle = (v: boolean) => {
当前状态{{ aiEnabled ? "已启用 —— 变更申请界面提供 AI 变更识别与申请表生成" : "已关闭 —— 变更申请界面为纯手动模式" }} 当前状态{{ aiEnabled ? "已启用 —— 变更申请界面提供 AI 变更识别与申请表生成" : "已关闭 —— 变更申请界面为纯手动模式" }}
</div> </div>
<div class="space-y-2 border rounded-md p-3 mt-3"> <div class="space-y-2 border rounded-md p-3 mt-3">
<div class="flex items-center">
<Icon icon="bx:plug" class="size-14px text-slate-400 mr-1" />
AI 接口配置
<NTag v-if="aiTested === 'ok'" size="small" type="success" class="ml-auto">连接正常</NTag>
</div>
<div class="flex items-center"> <div class="flex items-center">
<p class="w-100px text-left">接口地址</p> <p class="w-100px text-left">接口地址</p>
<div class="flex-1"> <div class="flex-1">
@@ -121,9 +332,9 @@ const toggle = (v: boolean) => {
</div> </div>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<NButton size="small" type="primary" class="text-xs">测试连接</NButton> <NButton size="small" type="primary" class="text-xs" :loading="testing" @click="testConn">测试连接</NButton>
<NButton size="small" class="text-xs">保存配置</NButton> <NButton size="small" class="text-xs" @click="saveAiCfg">保存配置</NButton>
<NButton size="small" class="text-xs"> <NButton size="small" class="text-xs" @click="showLog = true">
<Icon icon="akar-icons:file" class="size-14px mr-1" /> <Icon icon="akar-icons:file" class="size-14px mr-1" />
调用记录查询 调用记录查询
</NButton> </NButton>
@@ -140,7 +351,7 @@ const toggle = (v: boolean) => {
<Icon icon="ep:setting" class="size-16px mr-1" /> <Icon icon="ep:setting" class="size-16px mr-1" />
变更类型设置支持自定义 变更类型设置支持自定义
</div> </div>
<NButton size="small" type="primary" class="text-xs"> <NButton size="small" type="primary" class="text-xs" @click="addType">
<Icon icon="ic:round-plus" class="size-16px" />新增类型 <Icon icon="ic:round-plus" class="size-16px" />新增类型
</NButton> </NButton>
</div> </div>
@@ -153,12 +364,22 @@ const toggle = (v: boolean) => {
<p class="flex-1">{{item.desc}}</p> <p class="flex-1">{{item.desc}}</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<NButton text type="primary" class="text-xs"> <NButton text type="primary" class="text-xs" @click="editType(item)">
<Icon icon="akar-icons:edit" class="size-14px" />编辑 <Icon icon="akar-icons:edit" class="size-14px" />编辑
</NButton> </NButton>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
@positive-click="deleteType(item)"
>
<template #trigger>
<NButton text type="error" class="text-xs"> <NButton text type="error" class="text-xs">
<Icon icon="ep:delete" class="size-12px" />删除 <Icon icon="ep:delete" class="size-12px" />删除
</NButton> </NButton>
</template>
确定删除吗
</NPopconfirm>
</div> </div>
</div> </div>
</template> </template>
@@ -188,7 +409,7 @@ const toggle = (v: boolean) => {
<Icon icon="ep:setting" class="size-16px mr-1" /> <Icon icon="ep:setting" class="size-16px mr-1" />
变更等级设定支持自定义 变更等级设定支持自定义
</div> </div>
<NButton size="small" type="primary" class="text-xs"> <NButton size="small" type="primary" class="text-xs" @click="addLevel">
<Icon icon="ic:round-plus" class="size-16px" />新增等级 <Icon icon="ic:round-plus" class="size-16px" />新增等级
</NButton> </NButton>
</div> </div>
@@ -202,12 +423,22 @@ const toggle = (v: boolean) => {
<p class="flex-1">判定方式<b>{{item.way}}</b></p> <p class="flex-1">判定方式<b>{{item.way}}</b></p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<NButton text type="primary" class="text-xs"> <NButton text type="primary" class="text-xs" @click="editLevel(item)">
<Icon icon="akar-icons:edit" class="size-14px" />编辑 <Icon icon="akar-icons:edit" class="size-14px" />编辑
</NButton> </NButton>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
@positive-click="deleteLevel(item)"
>
<template #trigger>
<NButton text type="error" class="text-xs"> <NButton text type="error" class="text-xs">
<Icon icon="ep:delete" class="size-12px" />删除 <Icon icon="ep:delete" class="size-12px" />删除
</NButton> </NButton>
</template>
确定删除吗
</NPopconfirm>
</div> </div>
</div> </div>
<div class="flex flex-wrap mt-2"> <div class="flex flex-wrap mt-2">
@@ -302,7 +533,7 @@ const toggle = (v: boolean) => {
<Icon icon="icon-park-outline:all-application" class="size-16px mr-1" /> <Icon icon="icon-park-outline:all-application" class="size-16px mr-1" />
风险矩阵设置按分析方法 风险矩阵设置按分析方法
</div> </div>
<NButton size="small" class="text-xs"> <NButton size="small" class="text-xs" @click="openDimEdit">
<Icon icon="akar-icons:edit" class="size-14px mr-1" /> <Icon icon="akar-icons:edit" class="size-14px mr-1" />
维度分级定义 维度分级定义
</NButton> </NButton>
@@ -311,21 +542,294 @@ const toggle = (v: boolean) => {
</NButton> </NButton>
</div> </div>
<div class="py-3 px-4"> <div class="py-3 px-4">
<NTabs type="segment" animated> <NTabs type="segment" animated :on-update:value="handleTabChange">
<NTabPane name="chap1" tab="HAZOP分析矩阵"> <NTabPane :name="0" tab="HAZOP分析矩阵">
HAZOP分析矩阵 <div class="flex flex-wrap items-start gap-6">
<div class="shrink-0">
<table class="border-collapse text-center text-[11px]">
<thead>
<tr>
<th class="px-1"></th>
<th v-for="(lb, li) in curMatrix.lLabels" :key="li" class="px-0.5 pb-1 font-normal leading-tight text-slate-500">
L{{ li + 1 }}<br />{{ lb }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in 5" :key="row">
<td class="whitespace-nowrap pr-1.5 text-right leading-tight text-slate-500">S{{ 6 - row }} {{ curMatrix.sLabels[5 - row] }}</td>
<td v-for="col in 5" :key="col" class="p-0.5">
<div class="flex h-9 w-12 items-center justify-center rounded border font-semibold" :class="cellLevel(5 - row, col - 1)?.cls">
{{ cellScore(5 - row, col - 1) }}
</div>
</td>
</tr>
</tbody>
</table>
<div class="mt-1.5 flex justify-between text-[11px] text-slate-400">
<span>纵轴后果严重性 S</span>
<span>横轴发生可能性 L</span>
</div>
</div>
<!-- 等级分值区间 -->
<div class="min-w-72 flex-1">
<div class="mb-2 text-xs text-slate-500">风险值 = 严重性 S × 可能性 L5×5按分值区间定级</div>
<div class="space-y-2">
<div v-for="lv in curMatrix.levels" :key="lv.name" class="flex items-center gap-2 rounded-lg border px-2.5 py-2">
<span class="h-3 w-3 shrink-0 rounded-sm border" :class="lv.cls"></span>
<NInput v-model:value="lv.name" size="small" class="!w-16 text-xs" />
<span class="shrink-0 text-xs text-slate-400">分值</span>
<NInputNumber v-model:value="lv.min" :min="1" :max="25" size="small" :show-button="false" class="!w-14 text-center text-xs" />
<span class="text-slate-400"></span>
<NInputNumber v-model:value="lv.max" :min="1" :max="25" size="small" :show-button="false" class="!w-14 text-center text-xs" />
<NTag size="small" :class="lv.cls" class="ml-auto">{{ lv.name }}风险</NTag>
</div>
</div>
<p class="mt-2.5 text-[11px] text-slate-400">
各分析方法独立维护矩阵与维度分级风险分析记录表按所选工具对应的矩阵自动定级分值区间不得重叠须落在 125 在途分析记录按原矩阵保留定级快照
</p>
</div>
</div>
</NTabPane> </NTabPane>
<NTabPane name="chap2" tab="JSA分析矩阵"> <NTabPane :name="1" tab="JSA分析矩阵">
JSA分析矩阵 <div class="flex flex-wrap items-start gap-6">
<div class="shrink-0">
<table class="border-collapse text-center text-[11px]">
<thead>
<tr>
<th class="px-1"></th>
<th v-for="(lb, li) in curMatrix.lLabels" :key="li" class="px-0.5 pb-1 font-normal leading-tight text-slate-500">
L{{ li + 1 }}<br />{{ lb }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in 5" :key="row">
<td class="whitespace-nowrap pr-1.5 text-right leading-tight text-slate-500">S{{ 6 - row }} {{ curMatrix.sLabels[5 - row] }}</td>
<td v-for="col in 5" :key="col" class="p-0.5">
<div class="flex h-9 w-12 items-center justify-center rounded border font-semibold" :class="cellLevel(5 - row, col - 1)?.cls">
{{ cellScore(5 - row, col - 1) }}
</div>
</td>
</tr>
</tbody>
</table>
<div class="mt-1.5 flex justify-between text-[11px] text-slate-400">
<span>纵轴后果严重性 S</span>
<span>横轴发生可能性 L</span>
</div>
</div>
<!-- 等级分值区间 -->
<div class="min-w-72 flex-1">
<div class="mb-2 text-xs text-slate-500">风险值 = 严重性 S × 可能性 L5×5按分值区间定级</div>
<div class="space-y-2">
<div v-for="lv in curMatrix.levels" :key="lv.name" class="flex items-center gap-2 rounded-lg border px-2.5 py-2">
<span class="h-3 w-3 shrink-0 rounded-sm border" :class="lv.cls"></span>
<NInput v-model:value="lv.name" size="small" class="!w-16 text-xs" />
<span class="shrink-0 text-xs text-slate-400">分值</span>
<NInputNumber v-model:value="lv.min" :min="1" :max="25" size="small" :show-button="false" class="!w-14 text-center text-xs" />
<span class="text-slate-400"></span>
<NInputNumber v-model:value="lv.max" :min="1" :max="25" size="small" :show-button="false" class="!w-14 text-center text-xs" />
<NTag size="small" :class="lv.cls" class="ml-auto">{{ lv.name }}风险</NTag>
</div>
</div>
<p class="mt-2.5 text-[11px] text-slate-400">
各分析方法独立维护矩阵与维度分级风险分析记录表按所选工具对应的矩阵自动定级分值区间不得重叠须落在 125 在途分析记录按原矩阵保留定级快照
</p>
</div>
</div>
</NTabPane> </NTabPane>
<NTabPane name="chap3" tab="SCL分析矩阵"> <NTabPane :name="2" tab="SCL分析矩阵">
SCL分析矩阵 <div class="flex flex-wrap items-start gap-6">
<div class="shrink-0">
<table class="border-collapse text-center text-[11px]">
<thead>
<tr>
<th class="px-1"></th>
<th v-for="(lb, li) in curMatrix.lLabels" :key="li" class="px-0.5 pb-1 font-normal leading-tight text-slate-500">
L{{ li + 1 }}<br />{{ lb }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in 5" :key="row">
<td class="whitespace-nowrap pr-1.5 text-right leading-tight text-slate-500">S{{ 6 - row }} {{ curMatrix.sLabels[5 - row] }}</td>
<td v-for="col in 5" :key="col" class="p-0.5">
<div class="flex h-9 w-12 items-center justify-center rounded border font-semibold" :class="cellLevel(5 - row, col - 1)?.cls">
{{ cellScore(5 - row, col - 1) }}
</div>
</td>
</tr>
</tbody>
</table>
<div class="mt-1.5 flex justify-between text-[11px] text-slate-400">
<span>纵轴后果严重性 S</span>
<span>横轴发生可能性 L</span>
</div>
</div>
<!-- 等级分值区间 -->
<div class="min-w-72 flex-1">
<div class="mb-2 text-xs text-slate-500">风险值 = 严重性 S × 可能性 L5×5按分值区间定级</div>
<div class="space-y-2">
<div v-for="lv in curMatrix.levels" :key="lv.name" class="flex items-center gap-2 rounded-lg border px-2.5 py-2">
<span class="h-3 w-3 shrink-0 rounded-sm border" :class="lv.cls"></span>
<NInput v-model:value="lv.name" size="small" class="!w-16 text-xs" />
<span class="shrink-0 text-xs text-slate-400">分值</span>
<NInputNumber v-model:value="lv.min" :min="1" :max="25" size="small" :show-button="false" class="!w-14 text-center text-xs" />
<span class="text-slate-400"></span>
<NInputNumber v-model:value="lv.max" :min="1" :max="25" size="small" :show-button="false" class="!w-14 text-center text-xs" />
<NTag size="small" :class="lv.cls" class="ml-auto">{{ lv.name }}风险</NTag>
</div>
</div>
<p class="mt-2.5 text-[11px] text-slate-400">
各分析方法独立维护矩阵与维度分级风险分析记录表按所选工具对应的矩阵自动定级分值区间不得重叠须落在 125 在途分析记录按原矩阵保留定级快照
</p>
</div>
</div>
</NTabPane> </NTabPane>
</NTabs> </NTabs>
</div> </div>
</NCard> </NCard>
</NGi> </NGi>
</NGrid> </NGrid>
<!-- AI调用记录查询 -->
<NDrawer v-model:show="showLog" :width="700" placement="right">
<NDrawerContent title="调用记录查询" closable>
<table class="w-full text-xs">
<thead>
<tr class="border-b text-left text-slate-500">
<th class="py-2 font-semibold">AI 场景</th>
<th class="py-2 font-semibold">调用人</th>
<th class="py-2 font-semibold">关联变更</th>
<th class="py-2 font-semibold">时间</th>
<th class="py-2 text-right font-semibold">耗时</th>
<th class="py-2 text-right font-semibold">采纳结果</th>
</tr>
</thead>
<tbody>
<tr v-for="(r, i) in AI_LOGS" :key="i" class="border-b last:border-0">
<td class="py-2 text-slate-700">{{ r.scene }}</td>
<td class="py-2 text-slate-600">{{ r.user }}</td>
<td class="py-2 font-mono text-slate-500">{{ r.change }}</td>
<td class="py-2 text-slate-500">{{ r.time }}</td>
<td class="py-2 text-right text-slate-600">{{ r.cost }}</td>
<td class="py-2 text-right">
<NTag v-if="r.adopted === '采纳'" size="small" type="success">{{ r.adopted }}</NTag>
<NTag v-if="r.adopted === '修改后采纳'" size="small" type="warning">{{ r.adopted }}</NTag>
<NTag v-if="r.adopted === '未采纳'" size="small">{{ r.adopted }}</NTag>
</td>
</tr>
</tbody>
</table>
<p class="mt-3 text-[11px] text-slate-400">调用记录含输入摘要脱敏模型与提示词版本耗时与采纳结果支持采纳率统计完整输出存对象存储</p>
</NDrawerContent>
</NDrawer>
<!-- 新增/编辑类型 -->
<NModal
v-model:show="typeModal"
preset="card"
:title="currentType === 'add' ? '新增变更类型' : '编辑变更类型'"
:auto-focus="false"
:style="{ width: '450px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<div>
<NForm
ref="typeRef"
label-width="auto"
:model="typeInfo"
:rules="typeRules"
>
<NFormItem label="类型名称" path="name">
<NInput v-model:value="typeInfo.name" placeholder="如:仪表控制" />
</NFormItem>
<NFormItem label="类型定义描述" path="desc">
<NInput v-model:value="typeInfo.desc" type="textarea" placeholder="该类型覆盖的变更范围说明" />
</NFormItem>
</NForm>
</div>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton size="small" @click="typeModal = false">取消</NButton>
<NButton size="small" type="primary" :loading="typeLoading" @click="typeSave">确定</NButton>
</div>
</template>
</NModal>
<!-- 新增/编辑等级 -->
<NModal
v-model:show="levelModal"
preset="card"
:title="currentLevel === 'add' ? '新增变更等级' : '编辑变更等级'"
:auto-focus="false"
:style="{ width: '500px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<div>
<NForm
ref="levelRef"
label-width="auto"
:model="levelInfo"
:rules="levelRules"
>
<NFormItem label="等级名称" path="name">
<NInput v-model:value="levelInfo.name" placeholder="如:重大" />
</NFormItem>
<NFormItem label="等级判定方式" path="way">
<NInput v-model:value="levelInfo.way" placeholder="如:等级评分 ≥ 20 分" />
</NFormItem>
<NFormItem label="对应分析工具(多选)" path="tools">
<NSelect v-model:value="levelInfo.tools" multiple :options="toolsOptions" placeholder="请选择分析工具" />
</NFormItem>
</NForm>
</div>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton size="small" @click="levelModal = false">取消</NButton>
<NButton size="small" type="primary" :loading="levelLoading" @click="levelSave">确定</NButton>
</div>
</template>
</NModal>
<!-- 维度分级定义 -->
<NModal
v-model:show="dimensionModal"
preset="card"
:title="curMatrix.tool+'矩阵维度分级定义'"
:auto-focus="false"
:style="{ width: '500px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<div class="space-y-4">
<div>
<div class="mb-1.5 text-xs font-semibold text-slate-600">后果严重性 SS1 S5 递增</div>
<div class="grid grid-cols-5 gap-1.5">
<label v-for="(_, i) in dimForm.s" :key="'s' + i" class="text-center text-[10px] text-slate-400">
S{{ i + 1 }}
<NInput v-model:value="dimForm.s[i]" size="small" class="mt-0.5 text-center text-xs" />
</label>
</div>
</div>
<div>
<div class="mb-1.5 text-xs font-semibold text-slate-600">发生可能性 LL1 L5 递增</div>
<div class="grid grid-cols-5 gap-1.5">
<label v-for="(_, i) in dimForm.l" :key="'l' + i" class="text-center text-[10px] text-slate-400">
L{{ i + 1 }}
<NInput v-model:value="dimForm.l[i]" size="small" class="mt-0.5 text-center text-xs" />
</label>
</div>
</div>
</div>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton size="small" @click="dimensionModal = false">取消</NButton>
<NButton size="small" type="primary" :loading="dimensionLoading" @click="dimensionSave">确定</NButton>
</div>
</template>
</NModal>
</NSpace> </NSpace>
</template> </template>