FEMA矩阵配置完成,FEMA插件静态页面完成
This commit is contained in:
@@ -95,13 +95,13 @@ export function setApproverApi(id:number,data:any) {
|
||||
}
|
||||
|
||||
// 获取风险矩阵配置
|
||||
export function matrixConfigApi(bizType:string) {
|
||||
export function matrixConfigApi() {
|
||||
return request({
|
||||
url: `/api/risk-matrix/${bizType}`,
|
||||
url: `/api/matrix`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
// 保存风险矩阵配置
|
||||
// 保存风险矩阵配置(HAZOP)
|
||||
export function saveMatrixConfigApi(bizType:string,data:any) {
|
||||
return request({
|
||||
url: `/api/risk-matrix/${bizType}`,
|
||||
@@ -109,5 +109,13 @@ export function saveMatrixConfigApi(bizType:string,data:any) {
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 保存风险矩阵配置(FMEA)
|
||||
export function saveFmeaMatrixConfigApi(data:any) {
|
||||
return request({
|
||||
url: `/api/fmea-matrix`,
|
||||
method: 'put',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -99,9 +99,9 @@ const PURPOSE_OPTIONS = [
|
||||
const UPDATE_DOC_OPTIONS = ['PID图纸', '操作规程', '总图', '设备台账', '工艺卡片', '联锁台账', '应急预案'];
|
||||
// 风险分析选项
|
||||
const RISK_TOOLS_FORM = [
|
||||
{ key: "RISK", label: "风险预分析" },
|
||||
{ key: "HAZOP", label: "HAZOP" },
|
||||
{ key: "JSA", label: "JSA" },
|
||||
{ key: "SCL", label: "设备SCL" },
|
||||
{ key: "FMEA", label: "FMEA" },
|
||||
{ key: "RISK_CHECK", label: "风险检查表" },
|
||||
];
|
||||
const questions = ref<any>([
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -737,8 +737,8 @@ const getMenuOptions = (type: string) => {
|
||||
{ label: '删除分析对象', key: 'delObj' },
|
||||
]
|
||||
const devOptions = [
|
||||
{ label: '编辑分离', key: 'editDev' },
|
||||
{ label: '删除分离', key: 'delDev' },
|
||||
{ label: '编辑偏离', key: 'editDev' },
|
||||
{ label: '删除偏离', key: 'delDev' },
|
||||
]
|
||||
const map:any = {
|
||||
node: nodeOptions,
|
||||
@@ -1105,7 +1105,7 @@ onBeforeUnmount(() => {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="!currentDev || currentDev?.rows?.length === 0">
|
||||
<td colspan="8" class="px-3 py-8 text-center text-xs text-slate-400">
|
||||
<td colspan="9" class="px-3 py-8 text-center text-xs text-slate-400">
|
||||
当前偏离暂无分析记录,点击右上角「AI 分析当前偏离」自动生成,或「新增行」手动填写
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1389,9 +1389,9 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="mr-[5px] cursor-grab"
|
||||
:draggable="true"
|
||||
@dragstart="onDeviceDragStart($event, objIndex)"
|
||||
@dragstart="onDeviceDragStart($event, Number(objIndex))"
|
||||
@dragover.prevent
|
||||
@drop="onDeviceDrop($event, objIndex)"
|
||||
@drop="onDeviceDrop($event, Number(objIndex))"
|
||||
>
|
||||
<Icon icon="gravity-ui:grip" class="size-12px" />
|
||||
</div>
|
||||
@@ -1428,9 +1428,9 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="mr-[5px] cursor-grab"
|
||||
:draggable="true"
|
||||
@dragstart="onDeviateDragStart($event, objIndex, devIndex)"
|
||||
@dragstart="onDeviateDragStart($event, Number(objIndex), Number(devIndex))"
|
||||
@dragover.prevent
|
||||
@drop="onDeviateDrop($event, objIndex, devIndex)"
|
||||
@drop="onDeviateDrop($event, Number(objIndex), Number(devIndex))"
|
||||
>
|
||||
<Icon icon="gravity-ui:grip" class="size-12px" />
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useThemeStore } from '@/store/modules/theme';
|
||||
import RiskPre from "./riskPre.vue";
|
||||
import Hazop from "./hazop.vue";
|
||||
import Jsa from "./jsa.vue";
|
||||
import Scl from "./scl.vue";
|
||||
import Fmea from "./fmea.vue";
|
||||
import RiskCheck from "./riskCheck.vue";
|
||||
import Footer from './footer.vue'
|
||||
import { matrixConfigApi } from "@/service/api/system";
|
||||
@@ -19,12 +19,13 @@ const emit = defineEmits(['save','submit','confirmTab']);
|
||||
watch(() => props.currentForm, (newVal:any) => {
|
||||
const omit = (obj:any, fields: string[]) => Object.fromEntries(Object.entries(obj).filter(([k]) => !fields.includes(k)));
|
||||
const result = newVal?.records.map((item:any) => omit(item, [
|
||||
'id', 'created_at', 'updated_at', 'change_id', 'source_row_id'
|
||||
'created_at', 'updated_at', 'change_id', 'source_row_id'
|
||||
]));
|
||||
riskRecords.value = result || [];
|
||||
})
|
||||
|
||||
const hazopRiskMatrixConfig = ref<any>(null);
|
||||
const fmeaRiskMatrixConfig = ref<any>(null);
|
||||
const jsaRiskMatrixConfig = ref<any>(null);
|
||||
const pluginDone = computed(() =>
|
||||
riskRecords.value.reduce((acc:any, item:any) => {
|
||||
@@ -33,7 +34,7 @@ const pluginDone = computed(() =>
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
);
|
||||
const tools = ref<string[]>(["RISK","HAZOP","JSA", "RISK_CHECK"]);
|
||||
const tools = ref<string[]>(["RISK","HAZOP","FMEA", "RISK_CHECK"]);
|
||||
const riskRecords = ref<any>([]);
|
||||
const pluginDrawer = ref<boolean>(false);
|
||||
const currentPlugin = ref<string>('');
|
||||
@@ -42,7 +43,7 @@ const currentPlugin = ref<string>('');
|
||||
const TOOL_CARDS = [
|
||||
{ key: "RISK", name: "风险预分析" },
|
||||
{ key: "HAZOP", name: "HAZOP 分析" },
|
||||
{ key: "JSA", name: "JSA 分析" },
|
||||
{ key: "FMEA", name: "FMEA 分析" },
|
||||
{ key: "RISK_CHECK", name: "风险检查表" },
|
||||
];
|
||||
|
||||
@@ -157,6 +158,10 @@ return result;
|
||||
}
|
||||
// 整理入表
|
||||
const exportAll = (type: string, items: any[]) => {
|
||||
// console.log(items)
|
||||
// console.log(riskRecords.value)
|
||||
// console.log([...riskRecords.value, ...items])
|
||||
// 如果有id且id!=0,就用id去重,否则根据对象内容去重
|
||||
riskRecords.value = uniqueById([...riskRecords.value, ...items]);
|
||||
if (type === "all") {
|
||||
pluginDrawer.value = false;
|
||||
@@ -173,10 +178,7 @@ const usePlugin = (key: string) => {
|
||||
pluginDrawer.value = true;
|
||||
}
|
||||
// 关闭插件弹框
|
||||
const closePlugin = (type: string = "", count: any = undefined) => {
|
||||
if(type){
|
||||
pluginDone.value[type] = count;
|
||||
}
|
||||
const closePlugin = () => {
|
||||
pluginDrawer.value = false;
|
||||
}
|
||||
|
||||
@@ -193,26 +195,20 @@ const confirmTab = (tab:string, val:boolean) => {
|
||||
emit('confirmTab', tab, val);
|
||||
}
|
||||
|
||||
// 获取HAZOP风险矩阵配置
|
||||
const getHazopRiskMatrixConfig = async () => {
|
||||
const {data,error} = await matrixConfigApi("HAZOP");
|
||||
if(!error){
|
||||
hazopRiskMatrixConfig.value = data;
|
||||
}
|
||||
}
|
||||
// 获取JSA风险矩阵配置
|
||||
const getJsaRiskMatrixConfig = async () => {
|
||||
const {data,error} = await matrixConfigApi("JSA");
|
||||
const getRiskMatrixConfig = async () => {
|
||||
const {data,error} = await matrixConfigApi();
|
||||
if(!error){
|
||||
jsaRiskMatrixConfig.value = data;
|
||||
hazopRiskMatrixConfig.value = data?.hazop || null;
|
||||
jsaRiskMatrixConfig.value = data?.jsa || null;
|
||||
fmeaRiskMatrixConfig.value = data?.fmea || null;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({riskRecords})
|
||||
|
||||
onMounted(() => {
|
||||
getHazopRiskMatrixConfig();
|
||||
getJsaRiskMatrixConfig();
|
||||
getRiskMatrixConfig();
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -229,7 +225,7 @@ onMounted(() => {
|
||||
{{ formInfo?.change_level === 2 ? '重要变更' : '一般变更' }}
|
||||
</NTag>
|
||||
<span class="text-xs text-slate-400">
|
||||
{{ formInfo?.change_level === 2 ? '规则:HAZOP 必做,同时开展 JSA' : '规则:JSA,涉及工艺安全边界的应升级 HAZOP' }}
|
||||
{{ formInfo?.change_level === 2 ? '规则:HAZOP 必做' : '规则:涉及工艺安全边界的应升级 HAZOP' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid gap-2 grid-cols-4 mt-3">
|
||||
@@ -355,6 +351,16 @@ onMounted(() => {
|
||||
{{jsaRiskLevel(rec.l_value, rec.s_value)?.name}}
|
||||
</NTag>
|
||||
</div>
|
||||
<div v-if="rec.source==='RISK_CHECK'">
|
||||
<NTag
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
>
|
||||
<span>
|
||||
{{rec.inherent_level?rec.inherent_level:'-'}}
|
||||
</span>
|
||||
</NTag>
|
||||
</div>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1">
|
||||
<NInput type="textarea" size="small" v-model:value="rec.safeguards" :autosize="{minRows:1}" class="text-xs" />
|
||||
@@ -421,9 +427,19 @@ onMounted(() => {
|
||||
</span>
|
||||
</NTag>
|
||||
</div>
|
||||
<div v-if="rec.source==='RISK_CHECK'">
|
||||
<NTag
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
>
|
||||
<span>
|
||||
{{rec.residual_level?rec.residual_level:'-'}}
|
||||
</span>
|
||||
</NTag>
|
||||
</div>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag size="small" type="info" class="text-11px">{{ rec.source==='RISK'?'风险预分析':rec.source }}</NTag>
|
||||
<NTag size="small" type="info" class="text-11px">{{ rec.source==='RISK'?'风险预分析':rec.source==='RISK_CHECK'?'风险检查': rec.source }}</NTag>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag v-if="rec.to_pssr" size="small" :color="{ color: '#f5f3ff', textColor: '#7c3aed', borderColor: '#f5f3ff' }">已转 PSSR</NTag>
|
||||
@@ -470,14 +486,11 @@ onMounted(() => {
|
||||
<template v-if="currentPlugin === 'HAZOP'">
|
||||
<Hazop :isAiOpen="props.isAiOpen" :currentId="props.currentId" :hazopRiskMatrixConfig="hazopRiskMatrixConfig" @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
<template v-if="currentPlugin === 'JSA'">
|
||||
<Jsa :isAiOpen="props.isAiOpen" :currentId="props.currentId" :jsaRiskMatrixConfig="jsaRiskMatrixConfig" @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
<template v-if="currentPlugin === 'SCL'">
|
||||
<Scl @close="closePlugin" @toTable="exportAll" />
|
||||
<template v-if="currentPlugin === 'FMEA'">
|
||||
<Fmea :isAiOpen="props.isAiOpen" :currentId="props.currentId" :fmeaRiskMatrixConfig="fmeaRiskMatrixConfig" @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
<template v-if="currentPlugin === 'RISK_CHECK'">
|
||||
<RiskCheck :isAiOpen="props.isAiOpen" :currentId="props.currentId" @close="closePlugin" />
|
||||
<RiskCheck :isAiOpen="props.isAiOpen" :currentId="props.currentId" @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Icon } from '@iconify/vue'
|
||||
import { riskCheckDetailApi, riskCheckSaveApi } from '@/service/api/plugin'
|
||||
|
||||
const props = defineProps(['isAiOpen','currentId'])
|
||||
const emit = defineEmits(['close']);
|
||||
const emit = defineEmits(['close','toTable']);
|
||||
|
||||
// answer 1 是
|
||||
// answer 0 否
|
||||
@@ -16,35 +16,35 @@ const RISK_CHECKS_INIT = [
|
||||
group_label: "安全健康",
|
||||
items: [
|
||||
{
|
||||
question_id: 1,
|
||||
question_id: 'RISK_CHECK'+1,
|
||||
answer: null,
|
||||
question: "是否存在任何可能导致安全问题的化学反应?检查化学反应禁忌表。",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 2,
|
||||
question_id: 'RISK_CHECK'+2,
|
||||
answer: null,
|
||||
question: "是否存在任何易燃易爆化学物质或粉尘?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 3,
|
||||
question_id: 'RISK_CHECK'+3,
|
||||
answer: null,
|
||||
question: "是否存在任何有危险的原材料流速、成分或温度条件变更?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 4,
|
||||
question_id: 'RISK_CHECK'+4,
|
||||
answer: null,
|
||||
question: "是否会因加热、冷却或精确的温度控制失控而导致安全问题?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 5,
|
||||
question_id: 'RISK_CHECK'+5,
|
||||
answer: null,
|
||||
question: "是否具有为安全操作所必需的仪表、控制、紧急制动、开关或报警?",
|
||||
risk_desc: "",
|
||||
@@ -57,14 +57,14 @@ const RISK_CHECKS_INIT = [
|
||||
group_label: "工艺与操作",
|
||||
items: [
|
||||
{
|
||||
question_id: 6,
|
||||
question_id: 'RISK_CHECK'+6,
|
||||
answer: null,
|
||||
question: "变更后是否需要对操作人员进行专门培训?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 7,
|
||||
question_id: 'RISK_CHECK'+7,
|
||||
answer: null,
|
||||
question: "是否涉及应急预案或消防设施的调整?",
|
||||
risk_desc: "",
|
||||
@@ -73,11 +73,10 @@ const RISK_CHECKS_INIT = [
|
||||
],
|
||||
}
|
||||
];
|
||||
// 是否是ai生成
|
||||
const isAiGenerated = ref<boolean>(false);
|
||||
const loading = ref<boolean>(false);
|
||||
const riskGroups = ref<any[]>(RISK_CHECKS_INIT);
|
||||
const riskFilter = ref<number>(-1);
|
||||
const analyzing = ref<boolean>(false);
|
||||
|
||||
// 过滤风险项
|
||||
const filterChange = (v: number) => {
|
||||
@@ -92,11 +91,6 @@ const filterChange = (v: number) => {
|
||||
riskGroups.value = result;
|
||||
}
|
||||
};
|
||||
// 返回工具列表
|
||||
const onBack = () => {
|
||||
const count = riskGroups.value.flatMap((group: any) =>group.items.filter((item: any) => item.answer !== null)).length;
|
||||
emit('close','RISK_CHECK',count===0?undefined:count);
|
||||
}
|
||||
// 保存
|
||||
const save = async () => {
|
||||
loading.value = true;
|
||||
@@ -108,6 +102,51 @@ const save = async () => {
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
// AI整理如表、入表
|
||||
const enterToTable = (type: string, groupIndex: number = -1, questionIndex: number = -1) => {
|
||||
if(type==='single'){
|
||||
const row = riskGroups.value[groupIndex]?.items[questionIndex];
|
||||
const newRow = {
|
||||
scene: row.risk_desc || '',
|
||||
source: 'RISK_CHECK',
|
||||
item: row.question || '',
|
||||
inherent_level: '',
|
||||
safeguards: row?.control_measure || '',
|
||||
suggestions: '',
|
||||
residual_level: '',
|
||||
to_pssr: 0,
|
||||
}
|
||||
emit('toTable',type,newRow?[newRow]:[]);
|
||||
}
|
||||
if(type==='all'){
|
||||
const enrichedRows:any[] = [];
|
||||
riskGroups.value.forEach((group:any) => {
|
||||
group.items.forEach((row:any) => {
|
||||
enrichedRows.push({
|
||||
scene: row.risk_desc || '',
|
||||
source: 'RISK_CHECK',
|
||||
item: row.question || '',
|
||||
inherent_level: '',
|
||||
safeguards: row?.control_measure || '',
|
||||
suggestions: '',
|
||||
residual_level: '',
|
||||
to_pssr: 0,
|
||||
});
|
||||
})
|
||||
})
|
||||
emit('toTable',type,enrichedRows);
|
||||
}
|
||||
}
|
||||
|
||||
// AI 自动分析工序
|
||||
const runAi = async () => {
|
||||
analyzing.value = true;
|
||||
setTimeout(() => {
|
||||
analyzing.value = false;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// 获取风险检查表详情
|
||||
const getRiskCheckDetail = async () => {
|
||||
loading.value = true;
|
||||
@@ -130,7 +169,7 @@ onMounted(() => {
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="onBack">
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="emit('close')">
|
||||
<Icon icon="basil:arrow-left-outline" class="size-18px" /> 返回工具列表
|
||||
</NButton>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="save">保存</NButton>
|
||||
@@ -142,27 +181,37 @@ onMounted(() => {
|
||||
<NRadio :value=1>仅看「是」</NRadio>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
<span v-if="isAiGenerated" class="ml-auto inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
||||
✨ AI 生成 · 需人工确认
|
||||
</span>
|
||||
<div class="ml-auto flex items-center gap-1.5">
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="enterToTable('all')">
|
||||
<Icon icon="carbon:document-import" class="size-14px mr-1" /> 全部整理入表
|
||||
</NButton>
|
||||
<NButton size="small" type="primary" class="text-xs" :disabled="analyzing" @click="runAi">
|
||||
<Icon v-if="analyzing" icon="ri:loader-4-fill" class="size-14px animate-spin" />
|
||||
<Icon v-else icon="lucide:sparkles" class="size-12px" />
|
||||
AI 自动分析
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[11px] text-slate-400">逐项判定「是 / 否 / NA」;判定为「是」的检查项输出风险描述与管控措施,完成后点「返回工具列表」回显完成项数</p>
|
||||
<div class="space-y-4 scroll">
|
||||
<div v-for="group in riskGroups" :key="group.group_id" class="rounded-lg border">
|
||||
<div v-for="(group,groupIndex) in riskGroups" :key="group.group_id" class="rounded-lg border">
|
||||
<div class="bg-slate-100 px-3 py-2 text-sm font-medium text-slate-700">{{ group.group_label }}</div>
|
||||
<div v-for="item in group.items" :key="item.question_id"
|
||||
<div v-for="(item,questionIndex) in group.items" :key="item.question_id"
|
||||
class="grid gap-3 border-t px-3 py-3 grid-cols-[1.1fr_1fr_1fr] min-h-77px">
|
||||
<div>
|
||||
<div class="text-sm text-slate-700">{{ item.question }}</div>
|
||||
<div class="mt-2 flex items-center gap-4">
|
||||
<NRadioGroup v-model:value="item.answer" name="radiogroup">
|
||||
<div class="flex gap-2">
|
||||
<NRadio :value="1">是</NRadio>
|
||||
<NRadio :value=0>否</NRadio>
|
||||
<NRadio :value=2>NA</NRadio>
|
||||
<div class="flex items-start gap-2">
|
||||
<NButton text type="primary" class="text-xs mt-2px" @click="enterToTable('single',Number(groupIndex),Number(questionIndex))">入表</NButton>
|
||||
<div>
|
||||
<div class="text-sm text-slate-700">{{ item.question }}</div>
|
||||
<div class="mt-2">
|
||||
<NRadioGroup v-model:value="item.answer" name="radiogroup">
|
||||
<div class="flex gap-2">
|
||||
<NRadio :value="1">是</NRadio>
|
||||
<NRadio :value=0>否</NRadio>
|
||||
<NRadio :value=2>NA</NRadio>
|
||||
</div>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="item.answer === 1">
|
||||
<NInput v-model:value="item.risk_desc" type="textarea" size="small" :autosize="{minRows:2}" placeholder="风险描述" />
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
deleteSystemConfigApi,
|
||||
updateConfigApi,
|
||||
matrixConfigApi,
|
||||
saveMatrixConfigApi
|
||||
saveMatrixConfigApi,
|
||||
saveFmeaMatrixConfigApi
|
||||
} from '@/service/api/system';
|
||||
import { localStg } from '@/utils/storage';
|
||||
|
||||
@@ -311,12 +312,13 @@ const dimensionModal = ref<boolean>(false);
|
||||
const dimForm = ref<{s:{key:string, label:string}[], l:{key:string, label:string}[]}>({ s: [], l: [] });
|
||||
// 当前选中的风险矩阵索引
|
||||
const activeMatrix = ref('HAZOP');
|
||||
// 当前选中的风险矩阵
|
||||
const curMatrix = ref<any>(null);
|
||||
// 风险矩阵配置
|
||||
const hazopMatrix = ref<any>(null);
|
||||
const fmeaMatrix = ref<any>(null);
|
||||
// 查找匹配的等级配置
|
||||
function findMatchingConfig(num:number) {
|
||||
for (let i = 0; i < curMatrix.value?.risk_grades.length; i++) {
|
||||
const item = curMatrix.value?.risk_grades[i];
|
||||
for (let i = 0; i < hazopMatrix.value?.risk_grades.length; i++) {
|
||||
const item = hazopMatrix.value?.risk_grades[i];
|
||||
if (num >= item.min && num <= item.max) {
|
||||
return item; // 返回整条数据
|
||||
}
|
||||
@@ -335,12 +337,11 @@ const cellLevel = (s:number, l:number) => {
|
||||
// 风险矩阵tabs切换
|
||||
const handleTabChange = (v: string) => {
|
||||
activeMatrix.value = v;
|
||||
matrixConfig();
|
||||
}
|
||||
|
||||
// 打开风险矩阵编辑弹窗
|
||||
const openDimEdit = () => {
|
||||
dimForm.value = { s: [...curMatrix.value?.consequence], l: [...curMatrix.value?.probability] };
|
||||
dimForm.value = { s: [...hazopMatrix.value?.consequence], l: [...hazopMatrix.value?.probability] };
|
||||
dimensionModal.value = true;
|
||||
}
|
||||
// 添加后果严重性 S
|
||||
@@ -361,11 +362,11 @@ const deleteL = (i:number) => {
|
||||
}
|
||||
// 添加等级配置
|
||||
const addLevelConfig = () => {
|
||||
curMatrix.value?.risk_grades.push({name:null,bg_color:'#333333',font_color:'#ffffff', min:null, max:null});
|
||||
hazopMatrix.value?.risk_grades.push({name:null,bg_color:'#333333',font_color:'#ffffff', min:null, max:null});
|
||||
}
|
||||
// 删除等级配置
|
||||
const delLevelConfig = (index:number) => {
|
||||
curMatrix.value?.risk_grades.splice(index, 1);
|
||||
hazopMatrix.value?.risk_grades.splice(index, 1);
|
||||
}
|
||||
// 矩阵维度分级定义保存
|
||||
const dimensionSave = () => {
|
||||
@@ -377,31 +378,109 @@ const dimensionSave = () => {
|
||||
if(hasEmptyLabelL){
|
||||
return window.$message?.warning("请完善发生可能性L信息");
|
||||
}
|
||||
curMatrix.value.consequence = [...dimForm.value.s];
|
||||
curMatrix.value.probability = [...dimForm.value.l];
|
||||
hazopMatrix.value.consequence = [...dimForm.value.s];
|
||||
hazopMatrix.value.probability = [...dimForm.value.l];
|
||||
dimensionModal.value = false;
|
||||
}
|
||||
// *************** 风险矩阵设置(FMEA) ***************//
|
||||
const fmeaModal = ref<boolean>(false);
|
||||
const fmeaType = ref<string>('S');
|
||||
// 添加RPN区间
|
||||
const addRPN = () => {
|
||||
fmeaMatrix.value?.risk_grades.push({name:'',bg_color:'#333333',font_color:'#ffffff', min_rpn:null, max_rpn:null, control_measure:''});
|
||||
}
|
||||
// 删除RPN区间
|
||||
const delRPN = (index:number) => {
|
||||
fmeaMatrix.value?.risk_grades.splice(index, 1);
|
||||
}
|
||||
// 新增取值
|
||||
const addGrade = () => {
|
||||
fmeaMatrix.value?.s_grades.push({value:null, name:'', describe:''});
|
||||
}
|
||||
// 删除取值
|
||||
const delGrade = (index:number) => {
|
||||
fmeaMatrix.value?.s_grades.splice(index, 1);
|
||||
}
|
||||
// 保存FMEA分级表信息
|
||||
const saveFmeaGrade = () => {
|
||||
let hasNull = false;
|
||||
if(fmeaType.value === 'S'){
|
||||
if(fmeaMatrix.value.s_grades.length === 0){
|
||||
return window.$message?.warning("请添加S分级表信息");
|
||||
}
|
||||
hasNull = fmeaMatrix.value.s_grades.some((item:any) =>
|
||||
item.score === null || item.label.trim() === ''
|
||||
);
|
||||
}
|
||||
if(fmeaType.value === 'P'){
|
||||
if(fmeaMatrix.value.p_grades.length === 0){
|
||||
return window.$message?.warning("请添加P分级表信息");
|
||||
}
|
||||
hasNull = fmeaMatrix.value.p_grades.some((item:any) =>
|
||||
item.score === null || item.label.trim() === ''
|
||||
);
|
||||
}
|
||||
if(fmeaType.value === 'D'){
|
||||
if(fmeaMatrix.value.d_grades.length === 0){
|
||||
return window.$message?.warning("请添加D分级表信息");
|
||||
}
|
||||
hasNull = fmeaMatrix.value.d_grades.some((item:any) =>
|
||||
item.score === null || item.label.trim() === ''
|
||||
);
|
||||
}
|
||||
if(hasNull){
|
||||
return window.$message?.warning("请完善取值、等级信息");
|
||||
}
|
||||
fmeaModal.value = false;
|
||||
}
|
||||
|
||||
// 保存风险矩阵
|
||||
const saveMatrix = async () => {
|
||||
if(curMatrix.value.risk_grades.length === 0){
|
||||
return window.$message?.warning("请添加按分值区间定级信息");
|
||||
// HAZOP矩阵
|
||||
if(activeMatrix.value === 'HAZOP'){
|
||||
if(hazopMatrix.value.risk_grades.length === 0){
|
||||
return window.$message?.warning("请添加按分值区间定级信息");
|
||||
}
|
||||
const hasNull = hazopMatrix.value.risk_grades.some((item:any) =>
|
||||
item.name === null || item.min === null || item.max === null
|
||||
);
|
||||
if(hasNull){
|
||||
return window.$message?.warning("请完善按分值区间定级信息");
|
||||
}
|
||||
matrixLoading.value = true;
|
||||
const {error} = await saveMatrixConfigApi(activeMatrix.value,{
|
||||
consequence:hazopMatrix.value.consequence,
|
||||
probability:hazopMatrix.value.probability,
|
||||
risk_grades:hazopMatrix.value.risk_grades,
|
||||
});
|
||||
if(!error){
|
||||
window.$message?.success("保存成功");
|
||||
}
|
||||
matrixLoading.value = false;
|
||||
}
|
||||
const hasNull = curMatrix.value.risk_grades.some((item:any) =>
|
||||
item.name === null || item.min === null || item.max === null
|
||||
);
|
||||
if(hasNull){
|
||||
return window.$message?.warning("请完善按分值区间定级信息");
|
||||
// FMEA矩阵
|
||||
if(activeMatrix.value === 'FMEA'){
|
||||
if(fmeaMatrix.value.risk_grades.length === 0){
|
||||
return window.$message?.warning("请添加FMEA分析矩阵信息");
|
||||
}
|
||||
const hasNull = fmeaMatrix.value.risk_grades.some((item:any) =>
|
||||
item.name === null || item.min_rpn === null || item.max_rpn === null
|
||||
);
|
||||
if(hasNull){
|
||||
return window.$message?.warning("请完善等级、RPN区间等信息");
|
||||
}
|
||||
matrixLoading.value = true;
|
||||
const {error} = await saveFmeaMatrixConfigApi({
|
||||
risk_grades:fmeaMatrix.value.risk_grades,
|
||||
s_grades:fmeaMatrix.value.s_grades,
|
||||
p_grades:fmeaMatrix.value.p_grades,
|
||||
d_grades:fmeaMatrix.value.d_grades,
|
||||
});
|
||||
if(!error){
|
||||
window.$message?.success("保存成功");
|
||||
}
|
||||
matrixLoading.value = false;
|
||||
}
|
||||
matrixLoading.value = true;
|
||||
const {error} = await saveMatrixConfigApi(activeMatrix.value,{
|
||||
consequence:curMatrix.value.consequence,
|
||||
probability:curMatrix.value.probability,
|
||||
risk_grades:curMatrix.value.risk_grades,
|
||||
});
|
||||
if(!error){
|
||||
window.$message?.success("保存成功");
|
||||
}
|
||||
matrixLoading.value = false;
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
@@ -441,11 +520,10 @@ const getConfig = async () => {
|
||||
|
||||
// 获取风险矩阵配置
|
||||
const matrixConfig = async () => {
|
||||
const {data,error} = await matrixConfigApi(activeMatrix.value);
|
||||
const {data,error} = await matrixConfigApi();
|
||||
if(!error){
|
||||
curMatrix.value = data;
|
||||
}else{
|
||||
curMatrix.value = null;
|
||||
hazopMatrix.value = data?.hazop || null;
|
||||
fmeaMatrix.value = data?.fmea || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,7 +822,7 @@ onMounted(() => {
|
||||
<Icon icon="icon-park-outline:all-application" class="size-16px mr-1" />
|
||||
风险矩阵设置(按分析方法)
|
||||
</div>
|
||||
<NButton size="small" class="text-xs" @click="openDimEdit">
|
||||
<NButton v-if="activeMatrix==='HAZOP'" size="small" class="text-xs" @click="openDimEdit">
|
||||
<Icon icon="akar-icons:edit" class="size-14px mr-1" />
|
||||
维度分级定义
|
||||
</NButton>
|
||||
@@ -755,32 +833,32 @@ onMounted(() => {
|
||||
<div class="py-3 px-4">
|
||||
<NTabs type="segment" animated :on-update:value="handleTabChange">
|
||||
<NTabPane name="HAZOP" tab="HAZOP分析矩阵">
|
||||
<div v-if="curMatrix" class="flex flex-wrap items-start gap-6">
|
||||
<div v-if="hazopMatrix" 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?.probability" :key="li" class="px-0.5 pb-1 font-normal leading-tight text-slate-500">
|
||||
<th v-for="(lb, li) in hazopMatrix?.probability" :key="li" class="px-0.5 pb-1 font-normal leading-tight text-slate-500">
|
||||
{{ lb.key }}<br />{{ lb.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row,rowIndex) in curMatrix?.consequence" :key="row.key">
|
||||
<tr v-for="(row,rowIndex) in hazopMatrix?.consequence" :key="row.key">
|
||||
<td class="whitespace-nowrap pr-1.5 text-right leading-tight text-slate-500">
|
||||
{{ curMatrix?.consequence[curMatrix?.consequence.length - (Number(rowIndex)+1)]?.key }}
|
||||
{{ curMatrix?.consequence[curMatrix?.consequence.length - (Number(rowIndex)+1)]?.label }}
|
||||
{{ hazopMatrix?.consequence[hazopMatrix?.consequence.length - (Number(rowIndex)+1)]?.key }}
|
||||
{{ hazopMatrix?.consequence[hazopMatrix?.consequence.length - (Number(rowIndex)+1)]?.label }}
|
||||
</td>
|
||||
<td v-for="(col,colIndex) in curMatrix?.probability" :key="col.key" class="p-0.5">
|
||||
<td v-for="(col,colIndex) in hazopMatrix?.probability" :key="col.key" class="p-0.5">
|
||||
<div
|
||||
class="flex h-9 w-12 items-center justify-center rounded font-semibold"
|
||||
:style="{
|
||||
backgroundColor:cellLevel(curMatrix?.consequence.length - Number(rowIndex), (Number(colIndex)+1))?.bg_color,
|
||||
color:cellLevel(curMatrix?.consequence.length - Number(rowIndex), (Number(colIndex)+1))?.font_color,
|
||||
backgroundColor:cellLevel(hazopMatrix?.consequence.length - Number(rowIndex), (Number(colIndex)+1))?.bg_color,
|
||||
color:cellLevel(hazopMatrix?.consequence.length - Number(rowIndex), (Number(colIndex)+1))?.font_color,
|
||||
}"
|
||||
>
|
||||
{{ cellScore(curMatrix?.consequence.length - Number(rowIndex), (Number(colIndex)+1)) }}
|
||||
{{ cellScore(hazopMatrix?.consequence.length - Number(rowIndex), (Number(colIndex)+1)) }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -801,7 +879,7 @@ onMounted(() => {
|
||||
风险值 = 严重性 S × 可能性 L,按分值区间定级:
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(lv,index) in curMatrix?.risk_grades" :key="lv.name" class="flex items-center gap-2 rounded-lg border px-2.5 py-2">
|
||||
<div v-for="(lv,index) in hazopMatrix?.risk_grades" :key="lv.name" class="flex items-center gap-2 rounded-lg border px-2.5 py-2">
|
||||
<span class="text-xs">背景</span>
|
||||
<NColorPicker v-model:value="lv.bg_color">
|
||||
<template #trigger="{ value, onClick, ref: triggerRef }">
|
||||
@@ -865,7 +943,112 @@ onMounted(() => {
|
||||
</div>
|
||||
<div v-else class="py-5 text-center">暂无矩阵配置</div>
|
||||
</NTabPane>
|
||||
<NTabPane name="JSA" tab="JSA分析矩阵">
|
||||
<NTabPane name="FMEA" tab="FMEA分析矩阵">
|
||||
<div v-if="fmeaMatrix">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<p class="font-600">风险分级区间(RPN = S × P × D)</p>
|
||||
<NButton size="tiny" ghost type="primary" @click="addRPN">
|
||||
<Icon icon="ic:round-plus" class="size-14px" />新增区间
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="flex items-center gap-5">
|
||||
<p class="font-600">S / P / D 分级表</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<NButton size="tiny" ghost @click="fmeaType = 'S'; fmeaModal = true">S分级表</NButton>
|
||||
<NButton size="tiny" ghost @click="fmeaType = 'P'; fmeaModal = true">P分级表</NButton>
|
||||
<NButton size="tiny" ghost @click="fmeaType = 'D'; fmeaModal = true">D分级表</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NTable size="small" :single-line="false" class="mt-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-160px text-center">等级</th>
|
||||
<th class="w-80px !text-center">背景</th>
|
||||
<th class="w-80px !text-center">字体</th>
|
||||
<th class="w-200px">RPN区间</th>
|
||||
<th>管控要求</th>
|
||||
<th class="w-80px !text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="fmeaMatrix?.risk_grades.length===0">
|
||||
<td colspan="6" class="!py-2.5 text-center !text-slate-400">暂无数据</td>
|
||||
</tr>
|
||||
<tr v-for="(item, index) in fmeaMatrix?.risk_grades" :key="index">
|
||||
<td><NInput size="small" v-model:value="item.name" /></td>
|
||||
<td>
|
||||
<div class="flex items-center justify-center">
|
||||
<NColorPicker v-model:value="item.bg_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>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center justify-center">
|
||||
<NColorPicker v-model:value="item.font_color" :modes="['hex']">
|
||||
<template #trigger="{ value, onClick, ref: triggerRef }">
|
||||
<div
|
||||
:ref="triggerRef"
|
||||
:style="{
|
||||
width: '40px',
|
||||
height: '26px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: value || '#fff',
|
||||
cursor: 'pointer',
|
||||
border: '2px solid #fff',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
}"
|
||||
@click="onClick"
|
||||
></div>
|
||||
</template>
|
||||
</NColorPicker>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<NInputNumber v-model:value="item.min_rpn" size="small" :min="0" :show-button="false" class="text-center" />
|
||||
<span>~</span>
|
||||
<NInputNumber v-model:value="item.max_rpn" size="small" :min="0" :show-button="false" class="text-center" />
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<NInput v-model:value="item.control_measure" size="small" type="textarea" :autosize="{ minRows: 1 }" />
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="delRPN(Number(index))"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error"><Icon icon="ep:delete" class="size-14px" /></NButton>
|
||||
</template>
|
||||
确定删除吗?
|
||||
</NPopconfirm>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</NTable>
|
||||
</div>
|
||||
</NTabPane>
|
||||
<!-- <NTabPane name="JSA" tab="JSA分析矩阵">
|
||||
<div v-if="curMatrix" class="flex flex-wrap items-start gap-6">
|
||||
<div class="shrink-0">
|
||||
<table class="border-collapse text-center text-[11px]">
|
||||
@@ -902,8 +1085,6 @@ onMounted(() => {
|
||||
<span>横轴:发生可能性 L</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 等级分值区间 -->
|
||||
<div class="min-w-72 flex-1">
|
||||
<div class="mb-2 text-xs text-slate-500 flex items-center gap-2">
|
||||
<NButton size="tiny" ghost type="primary" @click="addLevelConfig">
|
||||
@@ -965,7 +1146,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="py-5 text-center">暂无矩阵配置</div>
|
||||
</NTabPane>
|
||||
</NTabPane> -->
|
||||
</NTabs>
|
||||
</div>
|
||||
</NCard>
|
||||
@@ -1136,7 +1317,7 @@ onMounted(() => {
|
||||
<NModal
|
||||
v-model:show="dimensionModal"
|
||||
preset="card"
|
||||
:title="curMatrix?.tool+'矩阵维度分级定义'"
|
||||
:title="hazopMatrix?.biz_type+'矩阵维度分级定义'"
|
||||
:auto-focus="false"
|
||||
:style="{ width: '500px', height: 'auto' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
@@ -1210,6 +1391,118 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
<!-- 风险矩阵设置(FMEA) -->
|
||||
<NModal
|
||||
v-model:show="fmeaModal"
|
||||
preset="card"
|
||||
:title="fmeaType+'分级表'"
|
||||
:auto-focus="false"
|
||||
:style="{ width: '760px', height: 'auto' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
>
|
||||
<div>
|
||||
<NButton size="tiny" ghost type="primary" @click="addGrade"><Icon icon="ic:round-plus" class="size-14px" />新增取值</NButton>
|
||||
<NTable size="small" :single-line="false" class="mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-80px !text-center">取值</th>
|
||||
<th class="w-160px">等级</th>
|
||||
<th>说明</th>
|
||||
<th class="w-80px !text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="fmeaType==='S'">
|
||||
<tr v-if="fmeaMatrix?.s_grades?.length===0">
|
||||
<td colspan="4" class="!py-2.5 text-center !text-slate-400">暂无数据</td>
|
||||
</tr>
|
||||
<tr v-for="(item, index) in fmeaMatrix?.s_grades" :key="index">
|
||||
<td><NInputNumber size="small" v-model:value="item.score" :min="0" :show-button="false" class="text-center" /></td>
|
||||
<td>
|
||||
<NInput v-model:value="item.label" size="small" />
|
||||
</td>
|
||||
<td>
|
||||
<NInput v-model:value="item.description" size="small" type="textarea" :autosize="{ minRows: 1 }" />
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="delGrade(Number(index))"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error"><Icon icon="ep:delete" class="size-14px" /></NButton>
|
||||
</template>
|
||||
确定删除吗?
|
||||
</NPopconfirm>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-if="fmeaType==='P'">
|
||||
<tr v-if="fmeaMatrix?.p_grades?.length===0">
|
||||
<td colspan="4" class="!py-2.5 text-center !text-slate-400">暂无数据</td>
|
||||
</tr>
|
||||
<tr v-for="(item, index) in fmeaMatrix?.p_grades" :key="index">
|
||||
<td><NInputNumber size="small" v-model:value="item.score" :min="0" :show-button="false" class="text-center" /></td>
|
||||
<td>
|
||||
<NInput v-model:value="item.label" size="small" />
|
||||
</td>
|
||||
<td>
|
||||
<NInput v-model:value="item.description" size="small" type="textarea" :autosize="{ minRows: 1 }" />
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="delGrade(Number(index))"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error"><Icon icon="ep:delete" class="size-14px" /></NButton>
|
||||
</template>
|
||||
确定删除吗?
|
||||
</NPopconfirm>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-if="fmeaType==='D'">
|
||||
<tr v-if="fmeaMatrix?.d_grades?.length===0">
|
||||
<td colspan="4" class="!py-2.5 text-center !text-slate-400">暂无数据</td>
|
||||
</tr>
|
||||
<tr v-for="(item, index) in fmeaMatrix?.d_grades" :key="index">
|
||||
<td><NInputNumber size="small" v-model:value="item.score" :min="0" :show-button="false" class="text-center" /></td>
|
||||
<td>
|
||||
<NInput v-model:value="item.label" size="small" />
|
||||
</td>
|
||||
<td>
|
||||
<NInput v-model:value="item.description" size="small" type="textarea" :autosize="{ minRows: 1 }" />
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="delGrade(Number(index))"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error"><Icon icon="ep:delete" class="size-14px" /></NButton>
|
||||
</template>
|
||||
确定删除吗?
|
||||
</NPopconfirm>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</NTable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-[10px]">
|
||||
<NButton size="small" @click="fmeaModal = false">取消</NButton>
|
||||
<NButton size="small" type="primary" @click="saveFmeaGrade">确定</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user