Files
moc/src/views/my/highRisk/modules/fmea.vue
T

225 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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="material-symbols:download" class="mr-1 size-14px" />
导出
</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>