hazop插件提交
This commit is contained in:
@@ -96,3 +96,28 @@ export function hazopRowAddApi(dev_id: number, data: {
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑偏离分析行
|
||||||
|
export function hazopRowEditApi(row_id: number, data: {
|
||||||
|
cause: string,
|
||||||
|
consequence: string,
|
||||||
|
l_value: number,
|
||||||
|
s_value: number,
|
||||||
|
risk_value:string,
|
||||||
|
safeguards:string,
|
||||||
|
suggestions:string
|
||||||
|
}) {
|
||||||
|
return request({
|
||||||
|
url: `/api/hazop/rows/${row_id}`,
|
||||||
|
method: 'put',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除偏离分析行
|
||||||
|
export function hazopRowDelApi(row_id: number) {
|
||||||
|
return request({
|
||||||
|
url: `/api/hazop/rows/${row_id}`,
|
||||||
|
method: 'delete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useMessage } from "naive-ui";
|
|||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { AiError, aiHazopDeviation } from '@/service/api/ai';
|
import { AiError, aiHazopDeviation } from '@/service/api/ai';
|
||||||
import Ed from "./Ed.vue";
|
|
||||||
import { matrixConfigApi } from "@/service/api/system";
|
import { matrixConfigApi } from "@/service/api/system";
|
||||||
import {
|
import {
|
||||||
hazopNodesApi,
|
hazopNodesApi,
|
||||||
@@ -17,7 +16,9 @@ import {
|
|||||||
hazopNodeDelApi,
|
hazopNodeDelApi,
|
||||||
hazopObjDelApi,
|
hazopObjDelApi,
|
||||||
hazopDevDelApi,
|
hazopDevDelApi,
|
||||||
hazopRowAddApi
|
hazopRowAddApi,
|
||||||
|
hazopRowEditApi,
|
||||||
|
hazopRowDelApi,
|
||||||
} from "@/service/api/plugin";
|
} from "@/service/api/plugin";
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
@@ -99,6 +100,38 @@ const nodes = ref<any>([]);
|
|||||||
// 表格样式
|
// 表格样式
|
||||||
const TH = "border bg-slate-100 px-2 py-1.5 text-[11px] font-medium text-slate-500";
|
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 TD = "border px-1.5 py-1 align-top";
|
||||||
|
// 偏离总数
|
||||||
|
const devTotal = computed(() => {
|
||||||
|
let total = 0;
|
||||||
|
nodes.value.forEach((node:any) => {
|
||||||
|
node.objects.forEach((obj:any) => {
|
||||||
|
total += obj.deviations.length;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return total;
|
||||||
|
});
|
||||||
|
// 已分析偏离数
|
||||||
|
const devDone = computed(() => {
|
||||||
|
let nonEmptyCount = 0;
|
||||||
|
nodes.value.forEach((node:any) => {
|
||||||
|
node.objects.forEach((obj:any) => {
|
||||||
|
obj.deviations.forEach((dev:any) => {
|
||||||
|
if (dev.rows && dev.rows.length > 0) {
|
||||||
|
nonEmptyCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return nonEmptyCount;
|
||||||
|
});
|
||||||
|
// 已完成记录总数
|
||||||
|
const recTotal = computed(() => {
|
||||||
|
const totalRows = nodes.value.flatMap((node:any) => node.objects)
|
||||||
|
.flatMap((obj:any) => obj.deviations)
|
||||||
|
.filter((d:any) => d.rows && d.rows.length > 0)
|
||||||
|
.reduce((sum:number, d:any) => sum + d.rows.length, 0);
|
||||||
|
return totalRows;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const HAZOP_AI:any = {
|
const HAZOP_AI:any = {
|
||||||
@@ -124,9 +157,6 @@ const lowerOf = (lv: "高" | "中" | "低"): "高" | "中" | "低" => {
|
|||||||
const sel = ref({ n: 0, d: 0 });
|
const sel = ref({ n: 0, d: 0 });
|
||||||
const analyzing = ref(false);
|
const analyzing = ref(false);
|
||||||
|
|
||||||
const devTotal = computed(() => 0);
|
|
||||||
const devDone = computed(() => 0);
|
|
||||||
const recTotal = computed(() => 0);
|
|
||||||
|
|
||||||
const toRec = (obj: string, dev: string, row: any): any => ({
|
const toRec = (obj: string, dev: string, row: any): any => ({
|
||||||
item: `${dev}(${obj})`,
|
item: `${dev}(${obj})`,
|
||||||
@@ -138,12 +168,6 @@ const toRec = (obj: string, dev: string, row: any): any => ({
|
|||||||
source: "HAZOP",
|
source: "HAZOP",
|
||||||
});
|
});
|
||||||
|
|
||||||
const updRow = (ri: number, patch: Partial<any>) => {
|
|
||||||
const row = currentDev.value.rows[ri];
|
|
||||||
if (row) Object.assign(row, patch);
|
|
||||||
};
|
|
||||||
const clampLS = (v: string) => Math.max(1, Math.min(5, +v || 1));
|
|
||||||
|
|
||||||
const runAi = async () => {
|
const runAi = async () => {
|
||||||
if (currentDev.value.rows.length) return message.info("当前偏离已有分析记录,可手动新增行补充");
|
if (currentDev.value.rows.length) return message.info("当前偏离已有分析记录,可手动新增行补充");
|
||||||
analyzing.value = true;
|
analyzing.value = true;
|
||||||
@@ -202,6 +226,21 @@ const selectWorker = (n: string) => {
|
|||||||
sels.value = sels.value.includes(n) ? sels.value.filter((x) => x !== n) : [...sels.value, n];
|
sels.value = sels.value.includes(n) ? sels.value.filter((x) => x !== n) : [...sels.value, n];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
const save = () => {
|
||||||
|
window.$message?.success("已手动保存,分析记录数已同步至工具卡片(内容修改实时自动保存,退出不丢失)");
|
||||||
|
};
|
||||||
|
|
||||||
|
// AI整理如表
|
||||||
|
const exportAll = () => {
|
||||||
|
emit("export", nodes.value.flatMap((n) => n.devs.flatMap((d) => d.rows.map((row) => toRec(n.obj, d.name, row)))), "ai");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出 Word
|
||||||
|
const exportWord = () => {
|
||||||
|
window.$message?.success('HAZOP 分析报告已导出(Word)')
|
||||||
|
}
|
||||||
|
|
||||||
// 根据l和s获取风险等级
|
// 根据l和s获取风险等级
|
||||||
const riskLevel = (l: number, s: number) => {
|
const riskLevel = (l: number, s: number) => {
|
||||||
const num = l * s;
|
const num = l * s;
|
||||||
@@ -216,8 +255,9 @@ const riskLevel = (l: number, s: number) => {
|
|||||||
|
|
||||||
// 新增行
|
// 新增行
|
||||||
const addRow = async () => {
|
const addRow = async () => {
|
||||||
console.log(l_options.value)
|
if(!currentDev.value){
|
||||||
console.log(s_options.value)
|
return window.$message?.warning("请先在左侧选择偏离")
|
||||||
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const {data,error} = await hazopRowAddApi(currentDev.value?.id,{
|
const {data,error} = await hazopRowAddApi(currentDev.value?.id,{
|
||||||
cause: "",
|
cause: "",
|
||||||
@@ -229,35 +269,39 @@ const addRow = async () => {
|
|||||||
suggestions: "",
|
suggestions: "",
|
||||||
})
|
})
|
||||||
if(!error){
|
if(!error){
|
||||||
console.log(data)
|
currentDev.value.rows.push(data);
|
||||||
// getHazopNodes();
|
|
||||||
window.$message?.success("新增行成功")
|
window.$message?.success("新增行成功")
|
||||||
}
|
}
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
|
// 编辑行
|
||||||
|
const editRow = async (row: any) => {
|
||||||
|
const {error} = await hazopRowEditApi(row.id,{
|
||||||
|
cause: row.cause,
|
||||||
|
consequence: row.consequence,
|
||||||
|
l_value: row.l_value,
|
||||||
|
s_value: row.s_value,
|
||||||
|
risk_value: riskLevel(row.l_value, row.s_value)?.name || '',
|
||||||
|
safeguards: row.safeguards,
|
||||||
|
suggestions: row.suggestions,
|
||||||
|
})
|
||||||
|
}
|
||||||
// 删除行
|
// 删除行
|
||||||
const delRow = (index: number) => currentDev.value.rows.splice(index, 1);
|
const delRow = async (index: number, row_id: number) => {
|
||||||
|
loading.value = true;
|
||||||
|
const {error} = await hazopRowDelApi(row_id);
|
||||||
|
if(!error){
|
||||||
|
currentDev.value.rows.splice(index, 1);
|
||||||
|
window.$message?.success("删除成功")
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 展开收起
|
// 展开收起
|
||||||
const leftToggle = (id: string) => {
|
const leftToggle = (id: string) => {
|
||||||
collapsed.value = { ...collapsed.value, [id]: !collapsed.value[id] }
|
collapsed.value = { ...collapsed.value, [id]: !collapsed.value[id] }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存
|
|
||||||
const save = () => {
|
|
||||||
window.$message?.success("已手动保存,分析记录数已同步至工具卡片(内容修改实时自动保存,退出不丢失)");
|
|
||||||
};
|
|
||||||
|
|
||||||
// AI整理如表
|
|
||||||
const exportAll = () => {
|
|
||||||
emit("export", nodes.value.flatMap((n) => n.devs.flatMap((d) => d.rows.map((row) => toRec(n.obj, d.name, row)))), "ai");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出 Word
|
|
||||||
const exportWord = () => {
|
|
||||||
window.$message?.success('HAZOP 分析报告已导出(Word)')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
const openModal = (type: string,obj: string, title: string,editName: string = '') => {
|
const openModal = (type: string,obj: string, title: string,editName: string = '') => {
|
||||||
if(type==='add'){
|
if(type==='add'){
|
||||||
@@ -511,6 +555,7 @@ const getHazopNodes = async () => {
|
|||||||
treeLoading.value = true;
|
treeLoading.value = true;
|
||||||
const {data,error} = await hazopNodesApi(props.currentId);
|
const {data,error} = await hazopNodesApi(props.currentId);
|
||||||
if(!error){
|
if(!error){
|
||||||
|
console.log(data)
|
||||||
nodes.value = data;
|
nodes.value = data;
|
||||||
}
|
}
|
||||||
treeLoading.value = false;
|
treeLoading.value = false;
|
||||||
@@ -660,17 +705,17 @@ onMounted(() => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th :class="TH" class="w-16">操作</th>
|
<th :class="TH" class="w-16">操作</th>
|
||||||
<th :class="TH">原因</th>
|
<th :class="TH" class="w-60">原因</th>
|
||||||
<th :class="TH">后果</th>
|
<th :class="TH" class="w-60">后果</th>
|
||||||
<th :class="TH" class="w-8">L</th>
|
<th :class="TH" class="w-6">L</th>
|
||||||
<th :class="TH" class="w-8">S</th>
|
<th :class="TH" class="w-6">S</th>
|
||||||
<th :class="TH" class="w-20">RR</th>
|
<th :class="TH" class="w-20">RR</th>
|
||||||
<th :class="TH">安全措施</th>
|
<th :class="TH" class="w-60">安全措施</th>
|
||||||
<th :class="TH">建议措施</th>
|
<th :class="TH" class="w-60">建议措施</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-if="currentDev?.rows?.length === 0">
|
<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="8" class="px-3 py-8 text-center text-xs text-slate-400">
|
||||||
当前偏离暂无分析记录,点击右上角「AI 分析当前偏离」自动生成,或「新增行」手动填写
|
当前偏离暂无分析记录,点击右上角「AI 分析当前偏离」自动生成,或「新增行」手动填写
|
||||||
</td>
|
</td>
|
||||||
@@ -681,33 +726,53 @@ onMounted(() => {
|
|||||||
<NButton text type="primary" class="mt-1px text-[11px] hover:underline" title="选入风险分析记录表"
|
<NButton text type="primary" class="mt-1px text-[11px] hover:underline" title="选入风险分析记录表"
|
||||||
@click="message.success('已选入 1 条风险记录,可继续挑选或返回查看记录表')">入表
|
@click="message.success('已选入 1 条风险记录,可继续挑选或返回查看记录表')">入表
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton text type="error" title="删除行" @click="delRow(Number(ri))">
|
|
||||||
<Icon icon="ep:delete" class="size-14px" />
|
<NPopconfirm
|
||||||
</NButton>
|
positive-text="确定"
|
||||||
|
:positiveButtonProps="{ size: 'tiny' }"
|
||||||
|
:negativeButtonProps="{ size: 'tiny' }"
|
||||||
|
@positive-click="delRow(Number(ri), row.id)"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton text type="error" title="删除行">
|
||||||
|
<Icon icon="ep:delete" class="size-14px" />
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
确定删除吗?
|
||||||
|
</NPopconfirm>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<Ed :v="row.cause" @update="(x) => updRow(Number(ri), { cause: x })" />
|
<NInput type="textarea" size="small" v-model:value="row.cause" :autosize="{minRows:1}" class="text-xs" @blur="editRow(row)" />
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<Ed :v="row.consequence" @update="(x) => updRow(Number(ri), { consequence: x })" />
|
<NInput type="textarea" size="small" v-model:value="row.consequence" :autosize="{minRows:1}" class="text-xs" @blur="editRow(row)" />
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD" class="text-center">
|
<td :class="TD" class="text-center">
|
||||||
<NInputNumber v-model:value="row.lvalue" size="tiny" :min="l_options[0].key" :max="l_options[l_options.length-1].key" :show-button="false" class="text-center w-10" />
|
<NInputNumber v-model:value="row.l_value" size="tiny" :min="l_options[0].key" :max="l_options[l_options.length-1].key" :show-button="false" placeholder="" class="text-center w-10" @blur="editRow(row)" />
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD" class="text-center">
|
<td :class="TD" class="text-center">
|
||||||
<NInputNumber v-model:value="row.svalue" size="tiny" :min="s_options[0].key" :max="l_options[s_options.length-1].key" :show-button="false" class="text-center w-10" />
|
<NInputNumber v-model:value="row.s_value" size="tiny" :min="s_options[0].key" :max="s_options[s_options.length-1].key" :show-button="false" placeholder="" class="text-center w-10" @blur="editRow(row)" />
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD" class="text-center">
|
<td :class="TD" class="text-center">
|
||||||
<span class="inline-block rounded px-1.5 py-0.5 text-[11px] font-medium">
|
<NTag
|
||||||
{{ row.risk_value }}
|
v-if="row.l_value>0 && row.s_value>0"
|
||||||
</span>
|
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>
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<Ed :v="row.safeguards" @update="(x) => updRow(Number(ri), { safeguards: x })" />
|
<NInput type="textarea" size="small" v-model:value="row.safeguards" :autosize="{minRows:1}" class="text-xs" @blur="editRow(row)" />
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<Ed :v="row.suggestions" @update="(x) => updRow(Number(ri), { suggestions: x })" />
|
<NInput type="textarea" size="small" v-model:value="row.suggestions" :autosize="{minRows:1}" class="text-xs" @blur="editRow(row)" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user