style: 统一行尾符为LF(gitattributes renormalize,无代码变更)
This commit is contained in:
@@ -1,224 +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="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>
|
||||
<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>
|
||||
|
||||
@@ -1,403 +1,403 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, h } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { getTimeRangeStamp, formatTimestamp } from '@/utils/common';
|
||||
import { matrixConfigApi } from '@/service/api/system';
|
||||
import { orgListApi } from '@/service/api/user';
|
||||
import { highRiskListApi } from '@/service/api/highRisk';
|
||||
|
||||
import RiskPre from "./riskPre.vue";
|
||||
import Hazop from "./hazop.vue";
|
||||
import Fmea from "./fmea.vue";
|
||||
import RiskCheck from "./riskCheck.vue";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 当前记录
|
||||
const currentRecord = ref<any>(null);
|
||||
const RISK_RANGES = [
|
||||
{ label: "近一个月", value: "month" },
|
||||
{ label: "近三个月", value: "quarter" },
|
||||
{ label: "近半年", value: "half_year" },
|
||||
{ label: "全部", value: 'all' }
|
||||
];
|
||||
// 风险矩阵配置
|
||||
const hazopMatrix = ref<any>(null);
|
||||
const fmeaMatrix = ref<any>(null);
|
||||
// 插件抽屉
|
||||
const pluginDrawer = ref<boolean>(false);
|
||||
// 高风险场景列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 查看变更
|
||||
const jumpTo = (r: any) => {
|
||||
routerPushByKey("details_index",{ query: { id: r?.change_id?.toString(),key:'my_highrisk' } });
|
||||
};
|
||||
// 查看分析记录
|
||||
const viewAnalysis = (record: any) => {
|
||||
currentRecord.value = record;
|
||||
pluginDrawer.value = true;
|
||||
};
|
||||
// 时间范围
|
||||
const timeRange = ref<string>('month');
|
||||
// 组织树
|
||||
const orgTree = ref<any[]>([]);
|
||||
// 筛选条件
|
||||
const formInfo = ref<any>({
|
||||
kw: '',
|
||||
org_id: 1,
|
||||
from: getTimeRangeStamp(timeRange.value)?.from ?? null,
|
||||
to: getTimeRangeStamp(timeRange.value)?.to ?? null,
|
||||
});
|
||||
const pagination = ref<{page: number, pageSize: number, itemCount: number}>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
itemCount: 0,
|
||||
})
|
||||
|
||||
// 自定义渲染展开图标
|
||||
const renderSwitcherIcon = (node:any) => {
|
||||
// 如果节点没有 children 或 children 为空数组,则不显示图标
|
||||
if (!node.option.children || node.option.children.length === 0) {
|
||||
return null; // 返回 null 表示不渲染任何图标
|
||||
}
|
||||
// 有子节点:返回 undefined 表示使用默认展开图标,也可自定义
|
||||
return h(Icon,{
|
||||
icon: node.option.expanded ? 'bi:caret-down-fill' : 'bi:caret-right-fill',
|
||||
class: 'size-12px',
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const searchHandle = () => {
|
||||
pagination.value.page = 1;
|
||||
getList();
|
||||
}
|
||||
// 组织选择改变
|
||||
const orgChange = (val:any) => {
|
||||
formInfo.value.org_id = val;
|
||||
searchHandle();
|
||||
}
|
||||
// 时间范围改变
|
||||
const timeRangeChange = (val: string) => {
|
||||
timeRange.value = val;
|
||||
if(val === 'all'){
|
||||
formInfo.value.from = null;
|
||||
formInfo.value.to = null;
|
||||
}else{
|
||||
const timeRange = getTimeRangeStamp(val);
|
||||
formInfo.value.from = timeRange?.from ?? null;
|
||||
formInfo.value.to = timeRange?.to ?? null;
|
||||
}
|
||||
searchHandle();
|
||||
}
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
timeRange.value = 'month';
|
||||
formInfo.value.kw = '';
|
||||
formInfo.value.org_id = 1;
|
||||
formInfo.value.from = getTimeRangeStamp('month')?.from ?? null;
|
||||
formInfo.value.to = getTimeRangeStamp('month')?.to ?? null;
|
||||
searchHandle();
|
||||
};
|
||||
|
||||
// 分页改变
|
||||
const pageChange = (page: number) => {
|
||||
pagination.value.page = page;
|
||||
getList();
|
||||
}
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await highRiskListApi({
|
||||
kw: formInfo.value.kw,
|
||||
org_id: formInfo.value.org_id,
|
||||
from: formatTimestamp(formInfo.value.from),
|
||||
to: formatTimestamp(formInfo.value.to),
|
||||
page_num: pagination.value.page,
|
||||
page_size: pagination.value.pageSize,
|
||||
});
|
||||
if(!error){
|
||||
list.value = data?.list ?? [];
|
||||
pagination.value.itemCount = data?.total ?? 0;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 获取组织列表
|
||||
const getOrgList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await orgListApi();
|
||||
if(!error){
|
||||
orgTree.value = data;
|
||||
if(orgTree.value.length > 0){
|
||||
getList();
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 获取风险矩阵配置
|
||||
const matrixConfig = async () => {
|
||||
const {data,error} = await matrixConfigApi();
|
||||
if(!error){
|
||||
hazopMatrix.value = data?.hazop || null;
|
||||
fmeaMatrix.value = data?.fmea || null;
|
||||
}
|
||||
}
|
||||
|
||||
// 风险矩阵等级匹配
|
||||
const matchRiskLevel = (configList:any,name: string) => {
|
||||
if(!configList){
|
||||
return null;
|
||||
}
|
||||
const target = configList.find((item:any) => item.name === name);
|
||||
return target ?? null;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
matrixConfig();
|
||||
getOrgList();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 高风险场景 -->
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex items-center justify-between px-4 py-3 border-b mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="lucide:shield-check" class="size-16px" :style="{color:themeStore.themeColor}" />
|
||||
<p class="text-base font-semibold">变更新增高风险场景</p>
|
||||
<span class="text-xs text-slate-400 mt-2px">{{ list.length }} 个高风险场景</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 min-h-28px">
|
||||
<NTag size="small" type="primary" class="text-11px font-medium">单笔变更的分析编制在变更上下文内完成</NTag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 mb-3 flex flex-wrap items-center gap-1.5">
|
||||
<NTreeSelect
|
||||
class="!w-200px"
|
||||
:value="formInfo.org_id"
|
||||
:options="orgTree"
|
||||
size="small"
|
||||
default-expand-all
|
||||
label-field="label"
|
||||
key-field="id"
|
||||
:render-switcher-icon="renderSwitcherIcon"
|
||||
placeholder="请选择组织层级"
|
||||
@update:value="orgChange"
|
||||
/>
|
||||
<span class="mr-1 text-xs text-slate-400">时间</span>
|
||||
<NButton
|
||||
size="tiny"
|
||||
round
|
||||
v-for="r in RISK_RANGES"
|
||||
:key="r.value"
|
||||
:type="timeRange === r.value ? 'primary' : 'default'"
|
||||
@click="timeRangeChange(r.value)"
|
||||
>
|
||||
{{ r.label }}
|
||||
</NButton>
|
||||
<NInput v-model:value="formInfo.kw" size="small" class="text-xs !w-200px ml-auto" clearable placeholder="关键词检索…" @clear="formInfo.kw = ''; searchHandle()">
|
||||
<template #suffix>
|
||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchHandle" />
|
||||
</template>
|
||||
</NInput>
|
||||
<NButton size="small" class="text-sm" @click="reset">
|
||||
<Icon icon="system-uicons:reset" class="size-12px" />重置
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div v-if="list.length" class="space-y-3 px-4 pb-3">
|
||||
<div v-for="(s, i) in list" :key="`${s.cid ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-3 rounded-lg border px-4 py-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<NTag size="small" type="info" class="text-11px">{{ s.source==='RISK'?'风险预分析':s.source==='RISK_CHECK'?'风险检查': s.source }}</NTag>
|
||||
<span class="text-sm font-medium text-slate-800">{{ s.item }}</span>
|
||||
<!-- 固有风险等级 -->
|
||||
<template v-if="s.source==='RISK'">
|
||||
<NTag
|
||||
v-if="s.inherent_level && s.inherent_level === '低'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="success"
|
||||
>
|
||||
{{s.inherent_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.inherent_level && s.inherent_level === '中'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="warning"
|
||||
>
|
||||
{{s.inherent_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.inherent_level && s.inherent_level === '高'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="error"
|
||||
>
|
||||
{{s.inherent_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='HAZOP'">
|
||||
<NTag
|
||||
v-if="s.inherent_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(hazopMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
textColor: matchRiskLevel(hazopMatrix?.risk_grades,s.inherent_level)?.font_color,
|
||||
borderColor: matchRiskLevel(hazopMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
固有风险 · {{s.inherent_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='FMEA'">
|
||||
<NTag
|
||||
v-if="s.inherent_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(fmeaMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
textColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.inherent_level)?.font_color,
|
||||
borderColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
固有风险 · {{s.inherent_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='RISK_CHECK'">
|
||||
<span>
|
||||
{{s.inherent_level ?? '-'}}
|
||||
</span>
|
||||
</template>
|
||||
<!-- 剩余风险等级 -->
|
||||
<template v-if="s.source==='RISK'">
|
||||
<NTag
|
||||
v-if="s.residual_level && s.residual_level === '低'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="success"
|
||||
>
|
||||
{{s.residual_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.residual_level && s.residual_level === '中'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="warning"
|
||||
>
|
||||
{{s.residual_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.residual_level && s.residual_level === '高'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="error"
|
||||
>
|
||||
{{s.residual_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='HAZOP'">
|
||||
<NTag
|
||||
v-if="s.residual_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(hazopMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
textColor: matchRiskLevel(hazopMatrix?.risk_grades,s.residual_level)?.font_color,
|
||||
borderColor: matchRiskLevel(hazopMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
剩余风险 · {{s.residual_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='FMEA'">
|
||||
<NTag
|
||||
v-if="s.residual_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(fmeaMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
textColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.residual_level)?.font_color,
|
||||
borderColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
剩余风险 · {{s.residual_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='RISK_CHECK'">
|
||||
<span>
|
||||
{{s.residual_level ?? '-'}}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-slate-600">{{ s.scene }}</div>
|
||||
<div class="mt-0.5 text-xs text-slate-400">
|
||||
变更:{{ s?.change_title }} · {{ s?.org_name }} · 分析日期 {{ s?.created_at?.slice(0, 10) }} · 分析记录 {{ s?.record_count }} 行
|
||||
</div>
|
||||
</div>
|
||||
<NButton ghost size="small" class="text-xs" @click="jumpTo(s)">查看变更</NButton>
|
||||
<NButton size="small" type="primary" class="text-xs" @click="viewAnalysis(s)">查看分析记录</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-100% flex items-center justify-center">
|
||||
<NEmpty description="暂无数据"></NEmpty>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-3 px-4 py-2 border-t">
|
||||
<NPagination
|
||||
v-model:page="pagination.page"
|
||||
:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:on-update:page="pageChange"
|
||||
>
|
||||
<template #prefix="{ itemCount }">
|
||||
共 {{ itemCount }} 条
|
||||
</template>
|
||||
</NPagination>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 插件抽屉 -->
|
||||
<NDrawer v-model:show="pluginDrawer" width="80%" placement="right" :auto-focus="false">
|
||||
<NDrawerContent closable>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>风险分析记录表</span>
|
||||
<NTag v-if="currentRecord?.source" size="small" type="info">
|
||||
<span v-if="currentRecord?.source === 'RISK'">风险预分析</span>
|
||||
<span v-else-if="currentRecord?.source === 'HAZOP'">HAZOP</span>
|
||||
<span v-else-if="currentRecord?.source === 'FMEA'">FMEA</span>
|
||||
<span v-else-if="currentRecord?.source === 'RISK_CHECK'">风险检查</span>
|
||||
<span v-else>未知分析记录</span>
|
||||
</NTag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'RISK'">
|
||||
<RiskPre :currentId="currentRecord?.change_id" :info="currentRecord" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'HAZOP'">
|
||||
<Hazop :currentId="currentRecord?.change_id" :info="currentRecord" :hazopRiskMatrixConfig="hazopMatrix" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'FMEA'">
|
||||
<Fmea :currentId="currentRecord?.change_id" :info="currentRecord" :fmeaRiskMatrixConfig="fmeaMatrix" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'RISK_CHECK'">
|
||||
<RiskCheck :currentId="currentRecord?.change_id" :info="currentRecord" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll {
|
||||
height: calc(100vh - 310px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, h } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { getTimeRangeStamp, formatTimestamp } from '@/utils/common';
|
||||
import { matrixConfigApi } from '@/service/api/system';
|
||||
import { orgListApi } from '@/service/api/user';
|
||||
import { highRiskListApi } from '@/service/api/highRisk';
|
||||
|
||||
import RiskPre from "./riskPre.vue";
|
||||
import Hazop from "./hazop.vue";
|
||||
import Fmea from "./fmea.vue";
|
||||
import RiskCheck from "./riskCheck.vue";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 当前记录
|
||||
const currentRecord = ref<any>(null);
|
||||
const RISK_RANGES = [
|
||||
{ label: "近一个月", value: "month" },
|
||||
{ label: "近三个月", value: "quarter" },
|
||||
{ label: "近半年", value: "half_year" },
|
||||
{ label: "全部", value: 'all' }
|
||||
];
|
||||
// 风险矩阵配置
|
||||
const hazopMatrix = ref<any>(null);
|
||||
const fmeaMatrix = ref<any>(null);
|
||||
// 插件抽屉
|
||||
const pluginDrawer = ref<boolean>(false);
|
||||
// 高风险场景列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 查看变更
|
||||
const jumpTo = (r: any) => {
|
||||
routerPushByKey("details_index",{ query: { id: r?.change_id?.toString(),key:'my_highrisk' } });
|
||||
};
|
||||
// 查看分析记录
|
||||
const viewAnalysis = (record: any) => {
|
||||
currentRecord.value = record;
|
||||
pluginDrawer.value = true;
|
||||
};
|
||||
// 时间范围
|
||||
const timeRange = ref<string>('month');
|
||||
// 组织树
|
||||
const orgTree = ref<any[]>([]);
|
||||
// 筛选条件
|
||||
const formInfo = ref<any>({
|
||||
kw: '',
|
||||
org_id: 1,
|
||||
from: getTimeRangeStamp(timeRange.value)?.from ?? null,
|
||||
to: getTimeRangeStamp(timeRange.value)?.to ?? null,
|
||||
});
|
||||
const pagination = ref<{page: number, pageSize: number, itemCount: number}>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
itemCount: 0,
|
||||
})
|
||||
|
||||
// 自定义渲染展开图标
|
||||
const renderSwitcherIcon = (node:any) => {
|
||||
// 如果节点没有 children 或 children 为空数组,则不显示图标
|
||||
if (!node.option.children || node.option.children.length === 0) {
|
||||
return null; // 返回 null 表示不渲染任何图标
|
||||
}
|
||||
// 有子节点:返回 undefined 表示使用默认展开图标,也可自定义
|
||||
return h(Icon,{
|
||||
icon: node.option.expanded ? 'bi:caret-down-fill' : 'bi:caret-right-fill',
|
||||
class: 'size-12px',
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const searchHandle = () => {
|
||||
pagination.value.page = 1;
|
||||
getList();
|
||||
}
|
||||
// 组织选择改变
|
||||
const orgChange = (val:any) => {
|
||||
formInfo.value.org_id = val;
|
||||
searchHandle();
|
||||
}
|
||||
// 时间范围改变
|
||||
const timeRangeChange = (val: string) => {
|
||||
timeRange.value = val;
|
||||
if(val === 'all'){
|
||||
formInfo.value.from = null;
|
||||
formInfo.value.to = null;
|
||||
}else{
|
||||
const timeRange = getTimeRangeStamp(val);
|
||||
formInfo.value.from = timeRange?.from ?? null;
|
||||
formInfo.value.to = timeRange?.to ?? null;
|
||||
}
|
||||
searchHandle();
|
||||
}
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
timeRange.value = 'month';
|
||||
formInfo.value.kw = '';
|
||||
formInfo.value.org_id = 1;
|
||||
formInfo.value.from = getTimeRangeStamp('month')?.from ?? null;
|
||||
formInfo.value.to = getTimeRangeStamp('month')?.to ?? null;
|
||||
searchHandle();
|
||||
};
|
||||
|
||||
// 分页改变
|
||||
const pageChange = (page: number) => {
|
||||
pagination.value.page = page;
|
||||
getList();
|
||||
}
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await highRiskListApi({
|
||||
kw: formInfo.value.kw,
|
||||
org_id: formInfo.value.org_id,
|
||||
from: formatTimestamp(formInfo.value.from),
|
||||
to: formatTimestamp(formInfo.value.to),
|
||||
page_num: pagination.value.page,
|
||||
page_size: pagination.value.pageSize,
|
||||
});
|
||||
if(!error){
|
||||
list.value = data?.list ?? [];
|
||||
pagination.value.itemCount = data?.total ?? 0;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 获取组织列表
|
||||
const getOrgList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await orgListApi();
|
||||
if(!error){
|
||||
orgTree.value = data;
|
||||
if(orgTree.value.length > 0){
|
||||
getList();
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 获取风险矩阵配置
|
||||
const matrixConfig = async () => {
|
||||
const {data,error} = await matrixConfigApi();
|
||||
if(!error){
|
||||
hazopMatrix.value = data?.hazop || null;
|
||||
fmeaMatrix.value = data?.fmea || null;
|
||||
}
|
||||
}
|
||||
|
||||
// 风险矩阵等级匹配
|
||||
const matchRiskLevel = (configList:any,name: string) => {
|
||||
if(!configList){
|
||||
return null;
|
||||
}
|
||||
const target = configList.find((item:any) => item.name === name);
|
||||
return target ?? null;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
matrixConfig();
|
||||
getOrgList();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 高风险场景 -->
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex items-center justify-between px-4 py-3 border-b mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="lucide:shield-check" class="size-16px" :style="{color:themeStore.themeColor}" />
|
||||
<p class="text-base font-semibold">变更新增高风险场景</p>
|
||||
<span class="text-xs text-slate-400 mt-2px">{{ list.length }} 个高风险场景</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 min-h-28px">
|
||||
<NTag size="small" type="primary" class="text-11px font-medium">单笔变更的分析编制在变更上下文内完成</NTag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 mb-3 flex flex-wrap items-center gap-1.5">
|
||||
<NTreeSelect
|
||||
class="!w-200px"
|
||||
:value="formInfo.org_id"
|
||||
:options="orgTree"
|
||||
size="small"
|
||||
default-expand-all
|
||||
label-field="label"
|
||||
key-field="id"
|
||||
:render-switcher-icon="renderSwitcherIcon"
|
||||
placeholder="请选择组织层级"
|
||||
@update:value="orgChange"
|
||||
/>
|
||||
<span class="mr-1 text-xs text-slate-400">时间</span>
|
||||
<NButton
|
||||
size="tiny"
|
||||
round
|
||||
v-for="r in RISK_RANGES"
|
||||
:key="r.value"
|
||||
:type="timeRange === r.value ? 'primary' : 'default'"
|
||||
@click="timeRangeChange(r.value)"
|
||||
>
|
||||
{{ r.label }}
|
||||
</NButton>
|
||||
<NInput v-model:value="formInfo.kw" size="small" class="text-xs !w-200px ml-auto" clearable placeholder="关键词检索…" @clear="formInfo.kw = ''; searchHandle()">
|
||||
<template #suffix>
|
||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchHandle" />
|
||||
</template>
|
||||
</NInput>
|
||||
<NButton size="small" class="text-sm" @click="reset">
|
||||
<Icon icon="system-uicons:reset" class="size-12px" />重置
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div v-if="list.length" class="space-y-3 px-4 pb-3">
|
||||
<div v-for="(s, i) in list" :key="`${s.cid ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-3 rounded-lg border px-4 py-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<NTag size="small" type="info" class="text-11px">{{ s.source==='RISK'?'风险预分析':s.source==='RISK_CHECK'?'风险检查': s.source }}</NTag>
|
||||
<span class="text-sm font-medium text-slate-800">{{ s.item }}</span>
|
||||
<!-- 固有风险等级 -->
|
||||
<template v-if="s.source==='RISK'">
|
||||
<NTag
|
||||
v-if="s.inherent_level && s.inherent_level === '低'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="success"
|
||||
>
|
||||
{{s.inherent_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.inherent_level && s.inherent_level === '中'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="warning"
|
||||
>
|
||||
{{s.inherent_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.inherent_level && s.inherent_level === '高'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="error"
|
||||
>
|
||||
{{s.inherent_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='HAZOP'">
|
||||
<NTag
|
||||
v-if="s.inherent_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(hazopMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
textColor: matchRiskLevel(hazopMatrix?.risk_grades,s.inherent_level)?.font_color,
|
||||
borderColor: matchRiskLevel(hazopMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
固有风险 · {{s.inherent_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='FMEA'">
|
||||
<NTag
|
||||
v-if="s.inherent_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(fmeaMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
textColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.inherent_level)?.font_color,
|
||||
borderColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.inherent_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
固有风险 · {{s.inherent_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='RISK_CHECK'">
|
||||
<span>
|
||||
{{s.inherent_level ?? '-'}}
|
||||
</span>
|
||||
</template>
|
||||
<!-- 剩余风险等级 -->
|
||||
<template v-if="s.source==='RISK'">
|
||||
<NTag
|
||||
v-if="s.residual_level && s.residual_level === '低'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="success"
|
||||
>
|
||||
{{s.residual_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.residual_level && s.residual_level === '中'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="warning"
|
||||
>
|
||||
{{s.residual_level}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="s.residual_level && s.residual_level === '高'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="error"
|
||||
>
|
||||
{{s.residual_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='HAZOP'">
|
||||
<NTag
|
||||
v-if="s.residual_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(hazopMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
textColor: matchRiskLevel(hazopMatrix?.risk_grades,s.residual_level)?.font_color,
|
||||
borderColor: matchRiskLevel(hazopMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
剩余风险 · {{s.residual_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='FMEA'">
|
||||
<NTag
|
||||
v-if="s.residual_level"
|
||||
size="small"
|
||||
:color="{
|
||||
color: matchRiskLevel(fmeaMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
textColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.residual_level)?.font_color,
|
||||
borderColor: matchRiskLevel(fmeaMatrix?.risk_grades,s.residual_level)?.bg_color,
|
||||
}"
|
||||
>
|
||||
剩余风险 · {{s.residual_level}}
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-if="s.source==='RISK_CHECK'">
|
||||
<span>
|
||||
{{s.residual_level ?? '-'}}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-slate-600">{{ s.scene }}</div>
|
||||
<div class="mt-0.5 text-xs text-slate-400">
|
||||
变更:{{ s?.change_title }} · {{ s?.org_name }} · 分析日期 {{ s?.created_at?.slice(0, 10) }} · 分析记录 {{ s?.record_count }} 行
|
||||
</div>
|
||||
</div>
|
||||
<NButton ghost size="small" class="text-xs" @click="jumpTo(s)">查看变更</NButton>
|
||||
<NButton size="small" type="primary" class="text-xs" @click="viewAnalysis(s)">查看分析记录</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-100% flex items-center justify-center">
|
||||
<NEmpty description="暂无数据"></NEmpty>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-3 px-4 py-2 border-t">
|
||||
<NPagination
|
||||
v-model:page="pagination.page"
|
||||
:page-size="pagination.pageSize"
|
||||
:item-count="pagination.itemCount"
|
||||
:on-update:page="pageChange"
|
||||
>
|
||||
<template #prefix="{ itemCount }">
|
||||
共 {{ itemCount }} 条
|
||||
</template>
|
||||
</NPagination>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 插件抽屉 -->
|
||||
<NDrawer v-model:show="pluginDrawer" width="80%" placement="right" :auto-focus="false">
|
||||
<NDrawerContent closable>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>风险分析记录表</span>
|
||||
<NTag v-if="currentRecord?.source" size="small" type="info">
|
||||
<span v-if="currentRecord?.source === 'RISK'">风险预分析</span>
|
||||
<span v-else-if="currentRecord?.source === 'HAZOP'">HAZOP</span>
|
||||
<span v-else-if="currentRecord?.source === 'FMEA'">FMEA</span>
|
||||
<span v-else-if="currentRecord?.source === 'RISK_CHECK'">风险检查</span>
|
||||
<span v-else>未知分析记录</span>
|
||||
</NTag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'RISK'">
|
||||
<RiskPre :currentId="currentRecord?.change_id" :info="currentRecord" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'HAZOP'">
|
||||
<Hazop :currentId="currentRecord?.change_id" :info="currentRecord" :hazopRiskMatrixConfig="hazopMatrix" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'FMEA'">
|
||||
<Fmea :currentId="currentRecord?.change_id" :info="currentRecord" :fmeaRiskMatrixConfig="fmeaMatrix" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
<template v-if="currentRecord?.source === 'RISK_CHECK'">
|
||||
<RiskCheck :currentId="currentRecord?.change_id" :info="currentRecord" @close="pluginDrawer = false" />
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll {
|
||||
height: calc(100vh - 310px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,88 +1,88 @@
|
||||
<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(['isAiOpen','currentId'])
|
||||
const emit = defineEmits(['close','toTable']);
|
||||
// 表格样式
|
||||
const TH = "border bg-slate-100 px-2 py-1.5 text-[11px] font-medium text-slate-500";
|
||||
const TD = "border px-1.5 py-1 align-top";
|
||||
|
||||
// 加载状态
|
||||
const loading = ref<boolean>(false);
|
||||
// 风险分析记录
|
||||
const riskRecords = ref<any[]>([]);
|
||||
|
||||
// 获取详情
|
||||
const getDetail = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await analysisRecordListApi(props.currentId);
|
||||
if(!error){
|
||||
riskRecords.value = data?.risk_summary?.rows || [];
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="space-y-3" :style="{'--theme-color': themeStore.themeColor}">
|
||||
<div class="bg-white">
|
||||
<div class="flex items-center gap-2 px-3 py-2 text-xs font-semibold text-slate-600 border border-b-0 rounded-tl-lg rounded-tr-lg">
|
||||
风险分析记录表
|
||||
</div>
|
||||
<p v-if="riskRecords.length===0" class="border py-5 text-center text-xs text-slate-400">暂无风险分析记录</p>
|
||||
<div v-else class="scroll">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th :class="TH" class="w-30">风险项</th>
|
||||
<th :class="TH" class="w-60">场景描述</th>
|
||||
<th :class="TH" class="w-20">固有风险</th>
|
||||
<th :class="TH" class="w-50">现有措施</th>
|
||||
<th :class="TH" class="w-60">建议措施</th>
|
||||
<th :class="TH" class="w-20">剩余风险</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in riskRecords" :key="rowIndex">
|
||||
<td :class="TD">
|
||||
<span>{{row.risk_item}}</span>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<span>{{row.scene_desc}}</span>
|
||||
</td>
|
||||
<td :class="TD" class="text-center">
|
||||
<span>{{row.risk_value}}</span>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<span>{{row.safeguards}}</span>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<span>{{row.suggestions}}</span>
|
||||
</td>
|
||||
<td :class="TD" class="text-center">
|
||||
<span>{{row.residual_risk_value}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll{
|
||||
height: calc(100vh - 135px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
<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(['isAiOpen','currentId'])
|
||||
const emit = defineEmits(['close','toTable']);
|
||||
// 表格样式
|
||||
const TH = "border bg-slate-100 px-2 py-1.5 text-[11px] font-medium text-slate-500";
|
||||
const TD = "border px-1.5 py-1 align-top";
|
||||
|
||||
// 加载状态
|
||||
const loading = ref<boolean>(false);
|
||||
// 风险分析记录
|
||||
const riskRecords = ref<any[]>([]);
|
||||
|
||||
// 获取详情
|
||||
const getDetail = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await analysisRecordListApi(props.currentId);
|
||||
if(!error){
|
||||
riskRecords.value = data?.risk_summary?.rows || [];
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="space-y-3" :style="{'--theme-color': themeStore.themeColor}">
|
||||
<div class="bg-white">
|
||||
<div class="flex items-center gap-2 px-3 py-2 text-xs font-semibold text-slate-600 border border-b-0 rounded-tl-lg rounded-tr-lg">
|
||||
风险分析记录表
|
||||
</div>
|
||||
<p v-if="riskRecords.length===0" class="border py-5 text-center text-xs text-slate-400">暂无风险分析记录</p>
|
||||
<div v-else class="scroll">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th :class="TH" class="w-30">风险项</th>
|
||||
<th :class="TH" class="w-60">场景描述</th>
|
||||
<th :class="TH" class="w-20">固有风险</th>
|
||||
<th :class="TH" class="w-50">现有措施</th>
|
||||
<th :class="TH" class="w-60">建议措施</th>
|
||||
<th :class="TH" class="w-20">剩余风险</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in riskRecords" :key="rowIndex">
|
||||
<td :class="TD">
|
||||
<span>{{row.risk_item}}</span>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<span>{{row.scene_desc}}</span>
|
||||
</td>
|
||||
<td :class="TD" class="text-center">
|
||||
<span>{{row.risk_value}}</span>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<span>{{row.safeguards}}</span>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<span>{{row.suggestions}}</span>
|
||||
</td>
|
||||
<td :class="TD" class="text-center">
|
||||
<span>{{row.residual_risk_value}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll{
|
||||
height: calc(100vh - 135px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user