高风险场景查看分析记录,hazop和fmea完成
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { analysisRecordListApi } from "@/service/api/highRisk";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const props = defineProps(['currentId', 'info', 'fmeaRiskMatrixConfig'])
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 当前偏离
|
||||
const currentDev = ref<any>(null);
|
||||
// 展开收起
|
||||
const collapsed = ref<Record<string, boolean>>({})
|
||||
// 节点列表
|
||||
const nodes = ref<any>([]);
|
||||
|
||||
// 点击偏离
|
||||
const clickDev = (dev:any) => {
|
||||
currentDev.value = dev
|
||||
}
|
||||
|
||||
// 根据l和s计算RR风险等级
|
||||
const riskLevel = (l: number, s: number) => {
|
||||
const num = l * s;
|
||||
for (let i = 0; i < props.fmeaRiskMatrixConfig?.risk_grades.length; i++) {
|
||||
const item = props.fmeaRiskMatrixConfig?.risk_grades[i];
|
||||
if (num >= item.min && num <= item.max) {
|
||||
return item; // 返回整条数据
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 计算RR1风险等级
|
||||
const countRiskLevel = (row: any) => {
|
||||
const l = row.l_value;
|
||||
const s = row.s_value;
|
||||
const newArr = [...row?.safeguards || [],...row?.suggestions || []];
|
||||
const result:any = { L: 0, S: 0 };
|
||||
newArr.forEach(item => {
|
||||
if (!item.value) return; // 跳过空值
|
||||
const parts = item.value.split('_');
|
||||
if (parts.length === 2) {
|
||||
const type = parts[0].toUpperCase(); // 统一大写
|
||||
const num = parseInt(parts[1], 10);
|
||||
if (!isNaN(num) && (type === 'L' || type === 'S')) {
|
||||
result[type] += num;
|
||||
}
|
||||
}
|
||||
});
|
||||
const newL = (l - result.L) > 0 ? l - result.L : 1;
|
||||
const newS = (s - result.S) > 0 ? s - result.S : 1;
|
||||
return riskLevel(newL, newS);
|
||||
}
|
||||
// 展开收起
|
||||
const leftToggle = (id: string) => {
|
||||
collapsed.value = { ...collapsed.value, [id]: !collapsed.value[id] }
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
window.$message?.info('还差接口')
|
||||
}
|
||||
|
||||
// 获取记录
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await analysisRecordListApi(props.currentId);
|
||||
if(!error){
|
||||
nodes.value = data?.fmea?.nodes || [];
|
||||
clickDev(nodes.value[0]?.deviations[0] || null)
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="flex items-center gap-4 mb-3">
|
||||
<p v-if="props.info?.change_title" class="text-xs text-slate-500">变更:<span class="text-slate-800">{{ props.info?.change_title }}</span></p>
|
||||
<p v-if="props.info?.created_at" class="text-xs text-slate-500">分析日期:{{ props.info?.created_at?.slice(0,10) }}</p>
|
||||
<p v-if="props.info?.applicant_name" class="text-xs text-slate-500">责任人:{{ props.info?.applicant_name }}</p>
|
||||
<p class="ml-auto text-xs text-slate-500">分析编制与修改在变更上下文内完成,此处只读</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex gap-3"
|
||||
:style="{
|
||||
'--theme-color': themeStore.themeColor,
|
||||
'--success-color': themeStore.otherColor.success
|
||||
}"
|
||||
>
|
||||
<!-- 左:节点 / 偏离树 -->
|
||||
<div class="w-240px">
|
||||
<div class="shrink-0 rounded-md border p-2 scroll">
|
||||
<p v-if="nodes.length === 0" class="text-center text-xs text-slate-400 py-10">暂无数据</p>
|
||||
<div v-for="(node, nodeIndex) in nodes" :key="nodeIndex">
|
||||
<div
|
||||
class="cursor-pointer flex items-center select-none"
|
||||
@click="leftToggle(node.id)"
|
||||
>
|
||||
<Icon v-if="collapsed[node.id]" icon="akar-icons:chevron-right" class="size-12px" />
|
||||
<Icon v-else icon="akar-icons:chevron-down" class="size-12px" />
|
||||
<p class="flex-1 px-2 py-1.5 text-xs font-semibold text-slate-700">步骤{{ Number(nodeIndex) + 1 }}:{{ node.node_name }}</p>
|
||||
</div>
|
||||
<template v-if="!collapsed[node.id]">
|
||||
<div v-for="(dev, devIndex) in node.deviations" :key="devIndex"
|
||||
@click="clickDev(dev)"
|
||||
class="cursor-pointer flex items-center ml-4 gap-1.5 rounded-md px-2 py-1.5 text-left text-xs transition"
|
||||
:class="currentDev?.id === dev.id ? 'bg-[var(--theme-color)] text-white' : 'text-slate-600 hover:bg-slate-100'"
|
||||
>
|
||||
<span class="flex-1">{{ dev.deviation }}</span>
|
||||
<span class="rounded-full bg-emerald-100 px-1.5 text-[10px] text-emerald-600">
|
||||
{{ dev.rows.length }}条
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右:FMEA 记录表 -->
|
||||
<div class="min-w-0 flex-1 bg-white">
|
||||
<div class="scroll">
|
||||
<NTable size="small" class="w-full text-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-60">原因</th>
|
||||
<th class="w-60">后果</th>
|
||||
<th class="w-6">L</th>
|
||||
<th class="w-6">S</th>
|
||||
<th class="w-20">RR</th>
|
||||
<th class="w-60">安全措施</th>
|
||||
<th class="w-60">建议措施</th>
|
||||
<th class="w-20">RR1</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="!currentDev || currentDev?.rows?.length === 0">
|
||||
<td colspan="9" class="px-3 py-8 text-center text-xs text-slate-400">
|
||||
当前暂无分析记录
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="(row, ri) in currentDev?.rows" :key="ri">
|
||||
<td class="align-top">
|
||||
<p class="text-xs">{{row.cause}}</p>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<p class="text-xs">{{row.consequence}}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-xs text-center w-10">{{row.l_value}}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="text-xs text-center w-10">{{row.s_value}}</p>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<NTag
|
||||
v-if="row.l_value>0 && row.s_value>0"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
:color="{
|
||||
color: riskLevel(row.l_value, row.s_value)?.bg_color,
|
||||
textColor: riskLevel(row.l_value, row.s_value)?.font_color,
|
||||
borderColor: riskLevel(row.l_value, row.s_value)?.bg_color,
|
||||
}"
|
||||
>
|
||||
{{riskLevel(row.l_value, row.s_value)?.name}}({{row.l_value * row.s_value}})
|
||||
</NTag>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<div v-if="row.safeguards && row.safeguards.length" class="flex flex-col gap-3">
|
||||
<div v-for="(sItem, si) in row.safeguards" :key="si">
|
||||
<p class="text-xs">{{Number(si)+1}}、{{sItem.name}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<div v-if="row.suggestions && row.suggestions.length" class="flex flex-col gap-2">
|
||||
<div v-for="(sItem2, si2) in row.suggestions" :key="si2">
|
||||
<p class="text-xs">{{Number(si2)+1}}、{{sItem2.name}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<NTag
|
||||
v-if="row.l_value>0 && row.s_value>0"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
:color="{
|
||||
color: countRiskLevel(row)?.bg_color,
|
||||
textColor: countRiskLevel(row)?.font_color,
|
||||
borderColor: countRiskLevel(row)?.bg_color,
|
||||
}"
|
||||
>
|
||||
{{countRiskLevel(row)?.name}}
|
||||
</NTag>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</NTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2 pt-3">
|
||||
<NButton size="small" @click="exportData">
|
||||
<Icon icon="bi:download" class="mr-1 size-12px" />
|
||||
导出
|
||||
</NButton>
|
||||
<NButton type="primary" size="small" @click="emit('close')">关闭</NButton>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll {
|
||||
height: calc(100vh - 155px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user