FEMA矩阵配置完成,FEMA插件静态页面完成
This commit is contained in:
@@ -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="风险描述" />
|
||||
|
||||
Reference in New Issue
Block a user