SCL插件完成
This commit is contained in:
@@ -10,7 +10,7 @@ import HighlightText from "./modules/HighlightText.vue";
|
|||||||
import Ed from "./modules/Ed.vue";
|
import Ed from "./modules/Ed.vue";
|
||||||
import Hazop from "./modules/hazop.vue";
|
import Hazop from "./modules/hazop.vue";
|
||||||
import Jsa from "./modules/jsa.vue";
|
import Jsa from "./modules/jsa.vue";
|
||||||
|
import Scl from "./modules/scl.vue";
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
|
|
||||||
@@ -1653,6 +1653,9 @@ const onBack = () => {
|
|||||||
<template v-else-if="currentPlugin === 'JSA'">
|
<template v-else-if="currentPlugin === 'JSA'">
|
||||||
<Jsa @back="onBack" @export="exportAll" />
|
<Jsa @back="onBack" @export="exportAll" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="currentPlugin === 'SCL'">
|
||||||
|
<Scl @back="onBack" @export="exportAll" />
|
||||||
|
</template>
|
||||||
</NDrawerContent>
|
</NDrawerContent>
|
||||||
</NDrawer>
|
</NDrawer>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|||||||
@@ -58,11 +58,10 @@ const nodes = ref<any>(JSON.parse(JSON.stringify(HAZOP_INIT)));
|
|||||||
const sel = ref({ n: 0, d: 0 });
|
const sel = ref({ n: 0, d: 0 });
|
||||||
const analyzing = ref(false);
|
const analyzing = ref(false);
|
||||||
|
|
||||||
const cur = computed(() => nodes.value[sel.value.n].devs[sel.value.d]);
|
const recTotal = computed(() => steps.value.reduce((a, s) => a + s.rows.length, 0));
|
||||||
const recTotal = computed(() => nodes.value.reduce((a, n) => a + n.devs.reduce((b, d) => b + d.rows.length, 0), 0));
|
|
||||||
|
|
||||||
const updRow = (ri: number, patch: Partial<any>) => {
|
const updRow = (si: number, ri: number, patch: Partial<any>) => {
|
||||||
const row = cur.value.rows[ri];
|
const row = steps.value[si]?.rows[ri];
|
||||||
if (row) Object.assign(row, patch);
|
if (row) Object.assign(row, patch);
|
||||||
};
|
};
|
||||||
const clampLS = (v: string) => Math.max(1, Math.min(5, +v || 1));
|
const clampLS = (v: string) => Math.max(1, Math.min(5, +v || 1));
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import { useMessage } from "naive-ui";
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import Ed from "./Ed.vue";
|
||||||
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const message = useMessage();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
back: [];
|
||||||
|
done: [records: number];
|
||||||
|
export: [items: any[], mode: "manual" | "ai"];
|
||||||
|
save: [];
|
||||||
|
}>();
|
||||||
|
const lowerOf = (lv: "高" | "中" | "低"): "高" | "中" | "低" => {
|
||||||
|
return lv === "高" ? "中" : "低";
|
||||||
|
}
|
||||||
|
const inherentOf = (l: number, s: number): "高" | "中" | "低" => {
|
||||||
|
const v = l * s;
|
||||||
|
return v >= 10 ? "高" : v >= 5 ? "中" : "低";
|
||||||
|
}
|
||||||
|
const toRec = (row: any): any => ({
|
||||||
|
item: row.item || "(未命名检查项)",
|
||||||
|
scene: `标准:${row.std};不符合可能导致:${row.cons}`,
|
||||||
|
inherent: inherentOf(row.l, row.s),
|
||||||
|
existing: row.ctrl,
|
||||||
|
suggest: row.sug,
|
||||||
|
residual: lowerOf(inherentOf(row.l, row.s)),
|
||||||
|
source: "SCL",
|
||||||
|
});
|
||||||
|
|
||||||
|
const gi = ref(0);
|
||||||
|
const cur = computed(() => groups.value[gi.value]);
|
||||||
|
const recTotal = computed(() => groups.value.reduce((a, g) => a + g.rows.length, 0));
|
||||||
|
|
||||||
|
const updRow = (ri: number, patch: Partial<any>) => {
|
||||||
|
const row = cur.value.rows[ri];
|
||||||
|
if (row) Object.assign(row, patch);
|
||||||
|
};
|
||||||
|
const clampLS = (v: string) => Math.max(1, Math.min(5, +v || 1));
|
||||||
|
const addRow = () => cur.value.rows.push({ item: "", std: "", cons: "", l: 2, s: 3, ctrl: "", sug: "" });
|
||||||
|
const delRow = (ri: number) => cur.value.rows.splice(ri, 1);
|
||||||
|
const exportAll = () => emit("export", groups.value.flatMap((g) => g.rows.map(toRec)), "ai");
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
message.success("已手动保存,分析记录数已同步至工具卡片(内容修改实时自动保存,退出不丢失)");
|
||||||
|
};
|
||||||
|
const SCL_INFO = {
|
||||||
|
dev: "R-101 缩合反应釜",
|
||||||
|
mat: "缩合反应体系(有机溶剂 / 反应物料)",
|
||||||
|
param: "设计温度:-10/120 ℃,设计压力:0.6 MPa;变更后操作温度:90±2 ℃(原 80±2 ℃),常压",
|
||||||
|
};
|
||||||
|
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 ANALYSIS_POOL = [
|
||||||
|
{ name: "张工艺", org: "一车间", post: "工艺工程师" },
|
||||||
|
{ name: "王仪表", org: "技术部", post: "仪表工程师" },
|
||||||
|
{ name: "周设备", org: "设备科", post: "设备工程师" },
|
||||||
|
{ name: "刘文静", org: "技术部", post: "工艺员" },
|
||||||
|
{ name: "王海燕", org: "安全环保部", post: "安全工程师" },
|
||||||
|
{ name: "李主任", org: "一车间", post: "车间主任" },
|
||||||
|
{ name: "吴海涛", org: "设备科", post: "维修班长" },
|
||||||
|
{ name: "陈国栋", org: "一车间", post: "班长" },
|
||||||
|
];
|
||||||
|
const open = ref(false);
|
||||||
|
const q = ref("");
|
||||||
|
const sels = ref<string[]>(["张工艺", "王仪表"]);
|
||||||
|
const list = computed(() => ANALYSIS_POOL.filter((x) => !q.value || x.name.includes(q.value)));
|
||||||
|
const toggle = (n: string) => {
|
||||||
|
sels.value = sels.value.includes(n) ? sels.value.filter((x) => x !== n) : [...sels.value, n];
|
||||||
|
};
|
||||||
|
const rrOf = (l: number, s: number) => {
|
||||||
|
const v = l * s;
|
||||||
|
if (v >= 15) return { t: `重大 (${v})`, c: "bg-red-500 text-white" };
|
||||||
|
if (v >= 10) return { t: `较大 (${v})`, c: "bg-[#F5A623] text-white" };
|
||||||
|
if (v >= 5) return { t: `一般 (${v})`, c: "bg-amber-300 text-amber-900" };
|
||||||
|
return { t: `低 (${v})`, c: "bg-emerald-500 text-white" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups = ref([
|
||||||
|
{ g: "本体", rows: [
|
||||||
|
{ item: "釜体及夹套壁厚", std: "测厚值 ≥ 设计最小壁厚,无超标腐蚀减薄", cons: "壁厚不足导致强度失效、泄漏", l: 1, s: 4, ctrl: "工程控制:定期测厚;管理措施:年度检验计划", sug: "变更后温度提升,复核腐蚀速率评估" },
|
||||||
|
{ item: "搅拌器运行状态", std: "电流、振动在正常范围,无异响", cons: "搅拌失效导致传热不均、局部过热", l: 2, s: 3, ctrl: "工程控制:电流在线监视;培训教育:异常处置规程", sug: "投用首周每班记录搅拌电流趋势" },
|
||||||
|
] },
|
||||||
|
{ g: "安全附件", rows: [
|
||||||
|
{ item: "安全阀", std: "校验在有效期内,起跳压力 0.5 MPa,铅封完好", cons: "超压时无法正常泄放", l: 1, s: 5, ctrl: "工程控制:定期校验;管理措施:校验台账管理", sug: "本次温度变更不影响泄放工况,维持原校验周期" },
|
||||||
|
{ item: "温度 / 压力仪表", std: "校验合格,现场示值与 DCS 一致", cons: "测量失真导致误判", l: 2, s: 3, ctrl: "管理措施:周期校验;现场与 DCS 比对核查", sug: "变更投用前完成一次示值比对" },
|
||||||
|
] },
|
||||||
|
{ g: "DCS 联锁与报警", rows: [
|
||||||
|
{ item: "温度高报 / 联锁设定值", std: "高报 95℃、高高报及联锁 100℃,与变更单一致", cons: "设定错误导致保护失效或误动作", l: 2, s: 5, ctrl: "管理措施:双人复核修改;工程控制:联锁测试", sug: "测试记录归档至变更资料" },
|
||||||
|
{ item: "联锁传动测试", std: "试验动作正确率 100%", cons: "联锁拒动 / 误动", l: 1, s: 5, ctrl: "管理措施:投用前强制测试;培训教育", sug: "纳入 PSSR 必查项" },
|
||||||
|
] },
|
||||||
|
{ g: "操作规程与应急", rows: [
|
||||||
|
{ item: "新版操作规程", std: "90℃ / 4h 工艺参数已修订发布并完成培训", cons: "沿用旧规程导致误操作", l: 2, s: 3, ctrl: "管理措施:规程受控发放;培训教育:签到考核", sug: "旧版规程现场收回,防止混用" },
|
||||||
|
{ item: "超温应急处置卡", std: "处置卡已更新并放置岗位", cons: "应急处置不当扩大事态", l: 1, s: 4, ctrl: "培训教育:应急演练;个体防护:应急物资就位", sug: "投用前组织一次超温处置桌面演练" },
|
||||||
|
] },
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap items-center gap-2 rounded-lg border border-[#ACC6E5] bg-[#EAF0F9] px-3 py-2"
|
||||||
|
:style="{
|
||||||
|
'--theme-color': themeStore.themeColor
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NButton size="small" ghost type="primary" class="text-xs" @click="emit('back')">
|
||||||
|
<Icon icon="basil:arrow-left-outline" class="size-18px" />
|
||||||
|
返回工具列表
|
||||||
|
</NButton>
|
||||||
|
<NButton size="small" ghost type="primary" class="text-xs" @click="save">保存</NButton>
|
||||||
|
<span class="text-sm font-bold text-[var(--theme-color)]">SCL 安全检查表插件</span>
|
||||||
|
<NTag size="small" type="primary">插件</NTag>
|
||||||
|
<NTag size="small">MOC 变更:缩合反应温度及时间优化(演示)</NTag>
|
||||||
|
<span class="text-[11px] text-slate-500">{{ `${groups.length} 个检查分类 · ${recTotal} 项检查记录` }}</span>
|
||||||
|
<div class="relative">
|
||||||
|
<NButton size="small" ghost type="primary" class="text-[11px]" @click="open = !open">
|
||||||
|
<Icon icon="lucide:users" class="size-12px" /> 分析组成员 · 已选 {{ sels.length }} 人
|
||||||
|
</NButton>
|
||||||
|
<template v-if="open">
|
||||||
|
<div class="fixed inset-0 z-10" @click="open = false"></div>
|
||||||
|
<div class="absolute right-0 top-8 z-20 w-72 rounded-lg border bg-white p-2 shadow-xl">
|
||||||
|
<div class="mb-1.5 flex items-center justify-between">
|
||||||
|
<span class="text-xs font-semibold text-slate-700">选择分析组成员(岗位人员库)</span>
|
||||||
|
<span class="text-[11px] text-slate-400">已选 {{ sels.length }} 人</span>
|
||||||
|
</div>
|
||||||
|
<NInput v-model:value="q" size="small" placeholder="搜索人名…" class="mb-1.5 text-xs" />
|
||||||
|
<div class="max-h-48 space-y-0.5 overflow-y-auto">
|
||||||
|
<div v-for="x in list" :key="x.name" @click="toggle(x.name)"
|
||||||
|
class="cursor-pointer flex w-full items-center min-h-34px gap-2 rounded-md px-2 py-1.5 text-left text-xs"
|
||||||
|
:class="sels.includes(x.name) ? 'bg-[#DFE9F6]' : 'hover:bg-slate-50'">
|
||||||
|
<span class="flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded border text-[10px]"
|
||||||
|
:class="sels.includes(x.name) ? 'border-[var(--theme-color)] bg-[var(--theme-color)] text-white' : 'border-slate-300'">{{ sels.includes(x.name) ? "✓" : "" }}</span>
|
||||||
|
<span class="font-medium text-slate-800">{{ x.name }}</span>
|
||||||
|
<span class="text-[11px] text-slate-400">{{ x.org }} · {{ x.post }}</span>
|
||||||
|
<NTag v-if="sels[0] === x.name" size="small" type="primary" class="ml-auto">组长</NTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1.5 border-t pt-1.5 text-[11px] leading-4 text-slate-400">
|
||||||
|
首位选择者默认为组长;成员为选择式(非手写),参与记录自动归属到人,用于「参与风险分析次数」统计采集。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto flex items-center gap-1.5">
|
||||||
|
<NButton size="small" ghost type="primary" class="text-xs" :disabled="recTotal === 0" @click="exportAll">
|
||||||
|
<Icon icon="lucide:sparkles" class="size-12px mr-1" /> AI 整理入表
|
||||||
|
</NButton>
|
||||||
|
<NButton size="small" ghost type="primary" class="text-xs" @click="message.success('SCL 检查表已导出(Word)')">
|
||||||
|
<Icon icon="material-symbols:download" class="size-14px" /> 导出 Word
|
||||||
|
</NButton>
|
||||||
|
<NButton size="small" type="primary" class="text-xs" @click="message.success('演示:AI 辅助分析完成,已按变更内容核对检查标准(支持手动修改)')">
|
||||||
|
<Icon icon="lucide:sparkles" class="size-12px" />
|
||||||
|
辅助分析
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 设备信息头 -->
|
||||||
|
<div class="grid gap-2 rounded-lg border bg-white p-3 text-xs sm:grid-cols-3">
|
||||||
|
<div><span class="text-slate-400">设备名称:</span><b class="text-slate-700">{{ SCL_INFO.dev }}</b></div>
|
||||||
|
<div><span class="text-slate-400">物料信息:</span><b class="text-slate-700">{{ SCL_INFO.mat }}</b></div>
|
||||||
|
<div class="sm:col-span-3"><span class="text-slate-400">运行参数:</span><span class="text-slate-600">{{ SCL_INFO.param }}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex gap-3"
|
||||||
|
:style="{'--theme-color':themeStore.themeColor}"
|
||||||
|
>
|
||||||
|
<!-- 左:检查分类 -->
|
||||||
|
<div class="shrink-0 rounded-lg border bg-white p-2 w-44">
|
||||||
|
<div v-for="(g, i) in groups" :key="g.g" @click="gi = i"
|
||||||
|
class="cursor-pointer flex w-full items-center rounded-md px-3 py-1.5 text-left text-xs transition"
|
||||||
|
:class="gi === i ? 'bg-[var(--theme-color)] text-white' : 'text-slate-600 hover:bg-slate-100'">
|
||||||
|
<span class="flex-1">{{ g.g }}</span>
|
||||||
|
<span class="rounded-full px-1.5 text-[10px]" :class="gi === i ? 'bg-white/25' : 'bg-slate-100 text-slate-400'">{{ g.rows.length }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右:检查表 -->
|
||||||
|
<div class="min-w-0 flex-1 bg-white">
|
||||||
|
<div class="flex items-center gap-2 pb-2">
|
||||||
|
<NButton size="small" class="text-xs" @click="addRow">
|
||||||
|
<Icon icon="ic:round-plus" class="size-14px" />新增检查项
|
||||||
|
</NButton>
|
||||||
|
<span class="text-[11px] text-slate-400">单元格点击即可修改;L / S 取值 1-5,风险等级按矩阵实时重算;「入表」将本条选入风险分析记录表</span>
|
||||||
|
</div>
|
||||||
|
<table class="w-full min-w-[900px] border-collapse text-xs">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th :class="TH">检查项目</th>
|
||||||
|
<th :class="TH">标准</th>
|
||||||
|
<th :class="TH">不符合标准情况及后果</th>
|
||||||
|
<th :class="TH" class="w-8">L</th>
|
||||||
|
<th :class="TH" class="w-8">S</th>
|
||||||
|
<th :class="TH" class="w-20">RR</th>
|
||||||
|
<th :class="TH">现有控制措施</th>
|
||||||
|
<th :class="TH">建议措施</th>
|
||||||
|
<th :class="TH" class="w-16">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(row, ri) in cur.rows" :key="ri">
|
||||||
|
<td :class="TD"><Ed :v="row.item" @update="(x) => updRow(ri, { item: x })" /></td>
|
||||||
|
<td :class="TD"><Ed :v="row.std" @update="(x) => updRow(ri, { std: x })" /></td>
|
||||||
|
<td :class="TD"><Ed :v="row.cons" @update="(x) => updRow(ri, { cons: x })" /></td>
|
||||||
|
<td :class="TD" class="text-center">
|
||||||
|
<input :value="row.l" @change="(e) => updRow(ri, { l: clampLS((e.target as HTMLInputElement).value) })"
|
||||||
|
class="w-7 rounded border border-transparent text-center hover:border-slate-200" />
|
||||||
|
</td>
|
||||||
|
<td :class="TD" class="text-center">
|
||||||
|
<input :value="row.s" @change="(e) => updRow(ri, { s: clampLS((e.target as HTMLInputElement).value) })"
|
||||||
|
class="w-7 rounded border border-transparent text-center hover:border-slate-200" />
|
||||||
|
</td>
|
||||||
|
<td :class="TD" class="text-center">
|
||||||
|
<span class="inline-block rounded px-1.5 py-0.5 text-[11px] font-medium" :class="rrOf(row.l, row.s).c">{{ rrOf(row.l, row.s).t }}</span>
|
||||||
|
</td>
|
||||||
|
<td :class="TD"><Ed :v="row.ctrl" @update="(x) => updRow(ri, { ctrl: x })" /></td>
|
||||||
|
<td :class="TD"><Ed :v="row.sug" @update="(x) => updRow(ri, { sug: x })" /></td>
|
||||||
|
<td :class="TD">
|
||||||
|
<div class="flex items-center justify-between px-1 mt-1">
|
||||||
|
<NButton text type="primary" class="mt-1px text-[11px] hover:underline" title="选入风险分析记录表"
|
||||||
|
@click="emit('export', [toRec(row)], 'manual')">入表
|
||||||
|
</NButton>
|
||||||
|
<NButton text type="error" title="删除行" @click="delRow(ri)">
|
||||||
|
<Icon icon="ep:delete" class="size-14px" />
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user