1045 lines
53 KiB
Vue
1045 lines
53 KiB
Vue
<script setup lang="ts">
|
||
import { ref, computed, onMounted } from 'vue';
|
||
import { useAppStore } from '@/store/modules/app';
|
||
import { Icon } from '@iconify/vue'
|
||
import { useThemeStore } from '@/store/modules/theme';
|
||
import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor } from '@/utils/common';
|
||
import { systemConfigApi, addSystemConfigApi, editSystemConfigApi, deleteSystemConfigApi, updateConfigApi } from '@/service/api/system';
|
||
|
||
const appStore = useAppStore();
|
||
const themeStore = useThemeStore();
|
||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||
|
||
// *********************AI 辅助功能配置*********************//
|
||
const aiConfig = ref<any[]>([])
|
||
const switchLoading = ref<boolean>(false);
|
||
const modeOptions = ref<{label:string, value:number}[]>([
|
||
{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: "采纳" },
|
||
];
|
||
|
||
// AI 辅助功能切换
|
||
const toggle = async (v: boolean, item:any) => {
|
||
switchLoading.value = true;
|
||
const {error} = await updateConfigApi({items:[{
|
||
id:item.id,
|
||
label:item.label,
|
||
remark:item.remark,
|
||
value:v ? '1' : '0'
|
||
}]});
|
||
if(!error){
|
||
item.value = v ? '1' : '0';
|
||
window.$message?.success(v ? "AI 辅助功能已启用" : "AI 辅助功能已关闭");
|
||
}
|
||
switchLoading.value = false;
|
||
}
|
||
// 测试连接
|
||
const testConn = () => {
|
||
testing.value = true;
|
||
setTimeout(() => {
|
||
testing.value = false;
|
||
window.$message?.success(`连接测试通过:${aiConfig.value.mode} 响应正常(耗时 0.8s)`);
|
||
}, 900);
|
||
}
|
||
// 保存ai配置
|
||
const saveAiCfg = () => {
|
||
window.$message?.success("AI 接口配置已保存(配置变更留痕,立即生效)");
|
||
}
|
||
|
||
// *********************变更类型*********************//
|
||
|
||
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, label:string, remark?:string, color:string,value:string,group_key:string}>({
|
||
id:0,
|
||
label:'',
|
||
remark:'',
|
||
color:'',
|
||
value: '',
|
||
group_key:'change_type'
|
||
})
|
||
const typeRules = {
|
||
label:{required:true, message:'请输入类型名称', trigger:'blur'},
|
||
color:{required:true, message:'请选择类型颜色', trigger:['blur','change']},
|
||
}
|
||
const typeList = ref<{id:number, label:string, remark?:string, color:string,value:string}[]>([])
|
||
// 新增类型
|
||
const addType = () => {
|
||
typeInfo.value = {
|
||
id:0,
|
||
label:'',
|
||
remark:'',
|
||
color:'',
|
||
value: '',
|
||
group_key:'change_type'
|
||
}
|
||
currentType.value = 'add';
|
||
typeModal.value = true;
|
||
}
|
||
// 编辑类型
|
||
const editType = (row:any) => {
|
||
typeInfo.value = {
|
||
id:row.id,
|
||
label:row.label,
|
||
remark:row.remark,
|
||
color:row.color,
|
||
value: row.value,
|
||
group_key:'change_type'
|
||
}
|
||
currentType.value = 'edit';
|
||
typeModal.value = true;
|
||
}
|
||
// 删除类型
|
||
const deleteType = async (id:number) => {
|
||
const {error} = await deleteSystemConfigApi(id);
|
||
if(!error){
|
||
typeList.value = typeList.value.filter((i:any) => i.id !== id);
|
||
window.$message?.success('删除成功')
|
||
}
|
||
}
|
||
// 保存类型
|
||
const typeSave = async (e:MouseEvent) => {
|
||
e.preventDefault()
|
||
typeRef.value?.validate(async (errors:any) => {
|
||
if (!errors) {
|
||
typeLoading.value = true;
|
||
// 新增类型
|
||
if(currentType.value === 'add'){
|
||
// 去掉id字段
|
||
const { id, ...rest } = typeInfo.value;
|
||
rest.value = (typeList.value.length + 1).toString();
|
||
const {error} = await addSystemConfigApi(rest);
|
||
if(!error){
|
||
window.$message?.success('添加成功')
|
||
typeModal.value = false;
|
||
}
|
||
}
|
||
// 编辑类型
|
||
if(currentType.value === 'edit'){
|
||
// 去掉id字段
|
||
const { id, ...rest } = typeInfo.value;
|
||
const {error} = await editSystemConfigApi(typeInfo.value.id, rest);
|
||
if(!error){
|
||
window.$message?.success('编辑成功')
|
||
typeModal.value = false;
|
||
}
|
||
}
|
||
typeLoading.value = false;
|
||
getConfig();
|
||
}
|
||
})
|
||
}
|
||
|
||
// *********************变更等级*********************//
|
||
|
||
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, label:string, remark:string,color:string,value:string,group_key:string}>({
|
||
id:0,
|
||
label:'',
|
||
remark:'',
|
||
color:'',
|
||
value: '',
|
||
group_key:'change_level'
|
||
})
|
||
const levelRules = {
|
||
label:{required:true, message:'请输入等级名称', trigger:'blur'},
|
||
color:{required:true, message:'请选择等级颜色', trigger:['blur','change']},
|
||
remark:{required:true, message:'请输入等级判定方式', trigger:'blur'},
|
||
}
|
||
const levelList = ref<{id:number, label:string, remark:string,color:string,value:string}[]>([])
|
||
// 新增等级
|
||
const addLevel = () => {
|
||
levelInfo.value = {
|
||
id:0,
|
||
label:'',
|
||
remark:'',
|
||
color:'',
|
||
value:'',
|
||
group_key:'change_level'
|
||
}
|
||
currentLevel.value = 'add';
|
||
levelModal.value = true;
|
||
}
|
||
// 编辑等级
|
||
const editLevel = (row:any) => {
|
||
levelInfo.value = {
|
||
id:row.id,
|
||
label:row.label,
|
||
remark:row.remark,
|
||
color:row.color,
|
||
value:row.value,
|
||
group_key:'change_level'
|
||
}
|
||
currentLevel.value = 'edit';
|
||
levelModal.value = true;
|
||
}
|
||
// 删除等级
|
||
const deleteLevel = async (id:number) => {
|
||
const {error} = await deleteSystemConfigApi(id);
|
||
if(!error){
|
||
levelList.value = levelList.value.filter((i:any) => i.id !== id);
|
||
window.$message?.success('删除成功')
|
||
}
|
||
}
|
||
// 保存等级
|
||
const levelSave = async (e:MouseEvent) => {
|
||
e.preventDefault()
|
||
levelRef.value?.validate(async (errors:any) => {
|
||
if (!errors) {
|
||
levelLoading.value = true;
|
||
if(currentLevel.value === 'add'){
|
||
// 去掉id字段
|
||
const { id, ...rest } = levelInfo.value;
|
||
rest.value = (levelList.value.length + 1).toString();
|
||
const {error} = await addSystemConfigApi(rest);
|
||
if(!error){
|
||
window.$message?.success('添加成功')
|
||
levelModal.value = false;
|
||
}
|
||
}
|
||
if(currentLevel.value === 'edit'){
|
||
// 去掉id字段
|
||
const { id, ...rest } = levelInfo.value;
|
||
const {error} = await editSystemConfigApi(levelInfo.value.id, rest);
|
||
if(!error){
|
||
window.$message?.success('编辑成功')
|
||
levelModal.value = false;
|
||
}
|
||
}
|
||
levelLoading.value = false;
|
||
getConfig();
|
||
}
|
||
})
|
||
}
|
||
|
||
// *********************变更编号规则*********************//
|
||
|
||
const ruleLoading = ref<boolean>(false);
|
||
const previewNumber = computed(() => {
|
||
let prefix = ruleConfig.value.find((item:any) => item.item_key === 'prefix')?.value || '';
|
||
let year = '';
|
||
let code = ruleConfig.value.find((item:any) => item.item_key === 'unit_code')?.value || '';
|
||
let digit = '';
|
||
const currentYear = new Date().getFullYear();
|
||
if (ruleConfig.value.find((item:any) => item.item_key === 'year_format')?.value === 'yyyy') {
|
||
year = currentYear.toString();
|
||
} else if (ruleConfig.value.find((item:any) => item.item_key === 'year_format')?.value === 'yy') {
|
||
year = currentYear.toString().substring(2);
|
||
}
|
||
if (ruleConfig.value.find((item:any) => item.item_key === 'seq_length')?.value) {
|
||
digit = '0'.repeat(ruleConfig.value.find((item:any) => item.item_key === 'seq_length')?.value - 1) + '1';
|
||
}
|
||
return prefix+'-' + year + '-' + code + '-' + digit;
|
||
});
|
||
const ruleConfig = ref<any[]>([])
|
||
const yearOptions = ref<{label:string, value:string}[]>([
|
||
{label:'四位年份(yyyy)',value:'yyyy'},
|
||
{label:'两位年份(yy)',value:'yy'},
|
||
])
|
||
const codeOptions = ref<{label:string, value:string}[]>([
|
||
{label:'申请部门装置代码',value:'R01'},
|
||
{label:'公司统一编号(不带装置代码)',value:'R02'},
|
||
])
|
||
const ruleOptions = ref<{label:string, value:string}[]>([
|
||
{label:'按年度+装置代码重置',value:'YEARLY+CODE'},
|
||
{label:'按年度重置',value:'YEARLY'},
|
||
{label:'连续不重置',value:'CONTINUE'},
|
||
])
|
||
|
||
// 保存编号规则
|
||
const saveRule = async () => {
|
||
if(ruleConfig.value.find((item:any) => item.item_key === 'prefix')?.value===''){
|
||
return window.$message?.warning(`请输入${ruleConfig.value.find((item:any) => item.item_key === 'prefix')?.label}`);
|
||
}
|
||
ruleLoading.value = true;
|
||
const {error} = await updateConfigApi({items:ruleConfig.value});
|
||
if(!error){
|
||
window.$message?.success("保存成功");
|
||
}
|
||
ruleLoading.value = false;
|
||
}
|
||
|
||
// *************** 风险矩阵设置(按分析方法) ***************//
|
||
|
||
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);
|
||
}
|
||
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;
|
||
}
|
||
|
||
// 获取配置
|
||
const getConfig = async () => {
|
||
const {data,error} = await systemConfigApi();
|
||
if(!error){
|
||
// 解析 AI 辅助功能配置
|
||
const aiAssistGroup = data.find((group:{group_key:string}) => group.group_key === 'ai_assist');
|
||
if (aiAssistGroup) {
|
||
aiConfig.value = aiAssistGroup.items;
|
||
const target = aiConfig.value.find((item:any) => item.item_key === 'timeout_seconds');
|
||
if (target) {
|
||
target.value = Number(target.value);
|
||
}
|
||
}
|
||
// 解析 变更类型设置
|
||
const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type');
|
||
if (changeTypeGroup) {
|
||
typeList.value = changeTypeGroup.items;
|
||
}
|
||
// 解析 变更等级设置
|
||
const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level');
|
||
if (changeLevelGroup) {
|
||
levelList.value = changeLevelGroup.items;
|
||
}
|
||
// 解析 变更编号规则
|
||
const changeCodeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_no_rule');
|
||
if (changeCodeGroup) {
|
||
ruleConfig.value = changeCodeGroup.items;
|
||
const target = ruleConfig.value.find((item:any) => item.item_key === 'seq_length');
|
||
if (target) {
|
||
target.value = Number(target.value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
getConfig();
|
||
})
|
||
|
||
</script>
|
||
|
||
<template>
|
||
<NSpace vertical :size="16">
|
||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||
<!-- AI 辅助功能 -->
|
||
<NGi span="24 s:24 m:24 l:12">
|
||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
||
:style="{
|
||
borderRadius: themeStore.themeRadius + 'px',
|
||
height: '100%',
|
||
'--success-color': themeStore.otherColor.success,
|
||
'--warning-color': themeStore.otherColor.warning,
|
||
'--success-bg': getColorWithOpacity(themeStore.otherColor.success, 0.1),
|
||
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning, 0.1),
|
||
}"
|
||
>
|
||
<p class=" text-base font-semibold border-b border-slate-100 p-3 flex items-center">
|
||
<Icon icon="lucide:sparkles" class="size-14px text-violet-500 mr-1" />AI 辅助功能
|
||
</p>
|
||
<div class="py-3 px-4">
|
||
<template v-for="item in aiConfig" :key="item.item_key">
|
||
<div v-if="item.item_key==='enabled'" class="rounded-lg border p-4">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-sm font-medium">{{item.label}}</div>
|
||
<NSwitch size="small" :value="item.value==='1'" :loading="switchLoading" @update:value="(v)=>toggle(v,item)" />
|
||
</div>
|
||
<div class="mt-1 text-xs text-slate-500">
|
||
{{item.remark}}
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='enabled'" class="mt-3 flex items-center gap-2 rounded-lg px-3 py-2 text-xs"
|
||
:class="item.value==='1' ? 'bg-[var(--success-bg)] text-[var(--success-color)]' : 'bg-[var(--warning-bg)] text-[var(--warning-color)]'">
|
||
<Icon v-if="item.value==='1'" icon="material-symbols:check-circle-outline" class="size-16px" />
|
||
<Icon v-else icon="proicons:alert-triangle" class="size-16px" />
|
||
当前状态:{{ item.value==='1' ? "已启用 —— 变更申请界面提供 AI 变更识别与申请表生成" : "已关闭 —— 变更申请界面为纯手动模式" }}
|
||
</div>
|
||
</template>
|
||
<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 接口配置
|
||
</div>
|
||
<template v-for="item in aiConfig" :key="item.item_key">
|
||
<div v-if="item.item_key==='api_url'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NInput v-model:value="item.value" placeholder="请输入接口地址" />
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='model'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NSelect v-model:value="item.value" :options="modeOptions" placeholder="请选择模型" />
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='api_key'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NInput v-model:value="item.value" placeholder="请输入API Key" />
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='timeout_seconds'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NInputNumber v-model:value="item.value" :min="1" :show-button="false" :precision="0" placeholder="请输入超时时间" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<!-- <div class="flex items-center gap-3">
|
||
<NButton size="small" type="primary" class="text-xs" :loading="testing" @click="testConn">测试连接</NButton>
|
||
<NButton size="small" class="text-xs" @click="saveAiCfg">保存配置</NButton>
|
||
<NButton size="small" class="text-xs" @click="showLog = true">
|
||
<Icon icon="akar-icons:file" class="size-14px mr-1" />
|
||
调用记录查询
|
||
</NButton>
|
||
</div> -->
|
||
</div>
|
||
</div>
|
||
</NCard>
|
||
</NGi>
|
||
<!-- 变更类型设置(支持自定义) -->
|
||
<NGi span="24 s:24 m:24 l:12">
|
||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
|
||
<div class="flex items-center justify-between text-base font-semibold border-b border-slate-100 p-3">
|
||
<div class="flex items-center">
|
||
<Icon icon="ep:setting" class="size-16px mr-1" />
|
||
变更类型设置(支持自定义)
|
||
</div>
|
||
<NButton size="small" type="primary" class="text-xs" @click="addType">
|
||
<Icon icon="ic:round-plus" class="size-16px" />新增类型
|
||
</NButton>
|
||
</div>
|
||
<div class="py-3 px-4">
|
||
<div v-if="typeList.length" class="border rounded-md">
|
||
<template v-for="(item,index) in typeList" :key="item.id">
|
||
<div class="p-3 flex items-center gap-2" :class="{'border-t':index !== 0}">
|
||
<div class="flex-1 flex gap-2">
|
||
<NTag
|
||
size="small"
|
||
:color="{
|
||
color: tagBgColor(item.color),
|
||
textColor: tagTextColor(item.color),
|
||
borderColor: tagBorderColor(item.color),
|
||
}"
|
||
>{{item.label}}</NTag>
|
||
<p class="flex-1">{{item.remark}}</p>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<NButton text type="primary" class="text-xs" @click="editType(item)">
|
||
<Icon icon="akar-icons:edit" class="size-14px" />编辑
|
||
</NButton>
|
||
<NPopconfirm
|
||
positive-text="确定"
|
||
:positiveButtonProps="{ size: 'tiny' }"
|
||
:negativeButtonProps="{ size: 'tiny' }"
|
||
@positive-click="deleteType(item.id)"
|
||
>
|
||
<template #trigger>
|
||
<NButton text type="error" class="text-xs">
|
||
<Icon icon="ep:delete" class="size-12px" />删除
|
||
</NButton>
|
||
</template>
|
||
确定删除吗?
|
||
</NPopconfirm>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div v-else>
|
||
<NEmpty description="暂无变更类型"></NEmpty>
|
||
</div>
|
||
<p class="mt-3 text-xs text-slate-400">每个类型可关联默认审批流程模板、材料清单模板和 PSSR 检查模板;历史变更单保留原类型快照,不受修改/删除影响。</p>
|
||
</div>
|
||
</NCard>
|
||
</NGi>
|
||
</NGrid>
|
||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||
<!-- 变更等级设定(支持自定义) -->
|
||
<NGi span="24 s:24 m:24 l:12">
|
||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
||
:style="{
|
||
borderRadius: themeStore.themeRadius + 'px',
|
||
height: '100%',
|
||
'--theme-color': themeStore.themeColor,
|
||
'--warning-color': themeStore.otherColor.warning,
|
||
'--error-color': themeStore.otherColor.error,
|
||
}"
|
||
>
|
||
<div class="flex items-center justify-between text-base font-semibold border-b border-slate-100 p-3">
|
||
<div class="flex items-center">
|
||
<Icon icon="ep:setting" class="size-16px mr-1" />
|
||
变更等级设定(支持自定义)
|
||
</div>
|
||
<NButton size="small" type="primary" class="text-xs" @click="addLevel">
|
||
<Icon icon="ic:round-plus" class="size-16px" />新增等级
|
||
</NButton>
|
||
</div>
|
||
<div class="py-3 px-4">
|
||
<div v-if="levelList.length" class="space-y-2">
|
||
<div v-for="item in levelList" :key="item.id" class="border rounded-md p-3">
|
||
<div class="flex items-center">
|
||
<div class="flex-1 flex items-center gap-2">
|
||
|
||
<NTag
|
||
size="small"
|
||
:color="{
|
||
color: tagBgColor(item.color),
|
||
textColor: tagTextColor(item.color),
|
||
borderColor: tagBorderColor(item.color),
|
||
}"
|
||
>
|
||
{{item.label}}
|
||
</NTag>
|
||
<p class="flex-1">判定方式:<b>{{item.remark}}</b></p>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<NButton text type="primary" class="text-xs" @click="editLevel(item)">
|
||
<Icon icon="akar-icons:edit" class="size-14px" />编辑
|
||
</NButton>
|
||
<NPopconfirm
|
||
positive-text="确定"
|
||
:positiveButtonProps="{ size: 'tiny' }"
|
||
:negativeButtonProps="{ size: 'tiny' }"
|
||
@positive-click="deleteLevel(item.id)"
|
||
>
|
||
<template #trigger>
|
||
<NButton text type="error" class="text-xs">
|
||
<Icon icon="ep:delete" class="size-12px" />删除
|
||
</NButton>
|
||
</template>
|
||
确定删除吗?
|
||
</NPopconfirm>
|
||
</div>
|
||
</div>
|
||
<!-- <div class="flex flex-wrap mt-2">
|
||
<p>对应分析工具:</p>
|
||
<div class="flex items-center flex-wrap gap-2">
|
||
<NTag v-for="tool in item.tools" :key="tool.id" size="small" type="info">{{tool.name}}</NTag>
|
||
</div>
|
||
</div> -->
|
||
</div>
|
||
</div>
|
||
<div v-else>
|
||
<NEmpty description="暂无变更等级"></NEmpty>
|
||
</div>
|
||
<p class="mt-3 text-xs text-slate-400">等级决定审批流程、风险分析深度与会签范围;申请时系统按判定方式自动定级并推荐对应分析工具,支持人工改判留痕。勾选 HAZOP/JSA/SCL的风险定级规则见下方「风险矩阵设置」。</p>
|
||
</div>
|
||
</NCard>
|
||
</NGi>
|
||
<!-- 变更编号规则 -->
|
||
<NGi span="24 s:24 m:24 l:12">
|
||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
||
:style="{
|
||
borderRadius: themeStore.themeRadius + 'px',
|
||
height: '100%',
|
||
'--theme-color': themeStore.themeColor,
|
||
'--warning-color': themeStore.otherColor.warning,
|
||
'--error-color': themeStore.otherColor.error,
|
||
}"
|
||
>
|
||
<div class="flex items-center justify-between text-base font-semibold border-b border-slate-100 p-3">
|
||
<div class="flex items-center">
|
||
<Icon icon="ep:setting" class="size-16px mr-1" />
|
||
变更编号规则
|
||
</div>
|
||
</div>
|
||
<div class="py-3 px-4">
|
||
<div class="space-y-2">
|
||
<template v-for="item in ruleConfig" :key="item.item_key">
|
||
<div v-if="item.item_key==='prefix'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NInput v-model:value="item.value" :placeholder="`请输入${item.label}`" />
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='year_format'" class="flex items-center">
|
||
<p class="w-100px text-left">年份格式</p>
|
||
<div class="flex-1">
|
||
<NSelect v-model:value="item.value" :options="yearOptions" :placeholder="`请选择${item.label}`" />
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='unit_code'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NSelect v-model:value="item.value" :options="codeOptions" :placeholder="`请选择${item.label}`" />
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='seq_length'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NInputNumber v-model:value="item.value" :min="1" :show-button="false" :precision="0" :placeholder="`请输入${item.label}`" />
|
||
</div>
|
||
</div>
|
||
<div v-if="item.item_key==='seq_reset'" class="flex items-center">
|
||
<p class="w-100px text-left">{{item.label}}</p>
|
||
<div class="flex-1">
|
||
<NSelect v-model:value="item.value" :options="ruleOptions" :placeholder="`请选择${item.label}`" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<div class="flex items-center p-3 bg-[#f8fafc] rounded-md">
|
||
<span>编号示例预览:</span>
|
||
<span class="text-[var(--theme-color)]">{{previewNumber}}</span>
|
||
<NButton size="small" type="primary" class="text-xs ml-auto" :loading="ruleLoading" @click="saveRule">保存规则</NButton>
|
||
</div>
|
||
</div>
|
||
<p class="mt-3 text-xs text-slate-400">保存草稿即预占编号,提交后正式生效;规则修改不影响历史单据编号。</p>
|
||
</div>
|
||
</NCard>
|
||
</NGi>
|
||
</NGrid>
|
||
<!-- 风险矩阵设置(按分析方法) -->
|
||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||
<NGi span="24 s:24 m:24 l:24">
|
||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
||
:style="{
|
||
borderRadius: themeStore.themeRadius + 'px',
|
||
height: '100%',
|
||
'--theme-color': themeStore.themeColor,
|
||
'--warning-color': themeStore.otherColor.warning,
|
||
'--error-color': themeStore.otherColor.error,
|
||
}"
|
||
>
|
||
<div class="flex items-center justify-between text-base font-semibold border-b border-slate-100 p-3">
|
||
<div class="flex items-center">
|
||
<Icon icon="icon-park-outline:all-application" class="size-16px mr-1" />
|
||
风险矩阵设置(按分析方法)
|
||
</div>
|
||
<NButton size="small" class="text-xs" @click="openDimEdit">
|
||
<Icon icon="akar-icons:edit" class="size-14px mr-1" />
|
||
维度分级定义
|
||
</NButton>
|
||
<NButton size="small" type="primary" class="text-xs">
|
||
保存矩阵
|
||
</NButton>
|
||
</div>
|
||
<div class="py-3 px-4">
|
||
<NTabs type="segment" animated :on-update:value="handleTabChange">
|
||
<NTabPane :name="0" tab="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 × 可能性 L(5×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">
|
||
各分析方法独立维护矩阵与维度分级;风险分析记录表按所选工具对应的矩阵自动定级。分值区间不得重叠、须落在 1–25 内;在途分析记录按原矩阵保留定级快照。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</NTabPane>
|
||
<NTabPane :name="1" tab="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 × 可能性 L(5×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">
|
||
各分析方法独立维护矩阵与维度分级;风险分析记录表按所选工具对应的矩阵自动定级。分值区间不得重叠、须落在 1–25 内;在途分析记录按原矩阵保留定级快照。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</NTabPane>
|
||
<NTabPane :name="2" tab="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 × 可能性 L(5×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">
|
||
各分析方法独立维护矩阵与维度分级;风险分析记录表按所选工具对应的矩阵自动定级。分值区间不得重叠、须落在 1–25 内;在途分析记录按原矩阵保留定级快照。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</NTabPane>
|
||
</NTabs>
|
||
</div>
|
||
</NCard>
|
||
</NGi>
|
||
</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="label">
|
||
<NInput v-model:value="typeInfo.label" placeholder="如:仪表控制" />
|
||
</NFormItem>
|
||
<NFormItem label="颜色" path="color">
|
||
<NColorPicker v-model:value="typeInfo.color" :modes="['hex']">
|
||
<template #trigger="{ value, onClick, ref: triggerRef }">
|
||
<div
|
||
:ref="triggerRef"
|
||
:style="{
|
||
width: '40px',
|
||
height: '26px',
|
||
borderRadius: '8px',
|
||
backgroundColor: value || '#333',
|
||
cursor: 'pointer',
|
||
border: '2px solid #fff',
|
||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||
}"
|
||
@click="onClick"
|
||
></div>
|
||
</template>
|
||
</NColorPicker>
|
||
<NTag
|
||
v-if="typeInfo.label"
|
||
size="small"
|
||
class="ml-5"
|
||
:color="{
|
||
color: tagBgColor(typeInfo.color),
|
||
textColor: tagTextColor(typeInfo.color),
|
||
borderColor: tagBorderColor(typeInfo.color),
|
||
}"
|
||
>
|
||
{{typeInfo.label}}
|
||
</NTag>
|
||
</NFormItem>
|
||
<NFormItem label="类型定义描述" path="remark">
|
||
<NInput v-model:value="typeInfo.remark" 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="label">
|
||
<NInput v-model:value="levelInfo.label" placeholder="如:重大" />
|
||
</NFormItem>
|
||
<NFormItem label="颜色" path="color">
|
||
<NColorPicker v-model:value="levelInfo.color" :modes="['hex']">
|
||
<template #trigger="{ value, onClick, ref: triggerRef }">
|
||
<div
|
||
:ref="triggerRef"
|
||
:style="{
|
||
width: '40px',
|
||
height: '26px',
|
||
borderRadius: '8px',
|
||
backgroundColor: value || '#333',
|
||
cursor: 'pointer',
|
||
border: '2px solid #fff',
|
||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||
}"
|
||
@click="onClick"
|
||
></div>
|
||
</template>
|
||
</NColorPicker>
|
||
<NTag
|
||
v-if="levelInfo.label"
|
||
size="small"
|
||
class="ml-5"
|
||
:color="{
|
||
color: tagBgColor(levelInfo.color),
|
||
textColor: tagTextColor(levelInfo.color),
|
||
borderColor: tagBorderColor(levelInfo.color),
|
||
}"
|
||
>
|
||
{{levelInfo.label}}
|
||
</NTag>
|
||
</NFormItem>
|
||
<NFormItem label="等级判定方式" path="remark">
|
||
<NInput v-model:value="levelInfo.remark" 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">后果严重性 S(S1 → 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">发生可能性 L(L1 → 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>
|
||
</template>
|
||
|
||
<style scoped></style>
|