变更培训内容完成
This commit is contained in:
@@ -8,6 +8,8 @@ import {
|
||||
} from "@/typings/model";
|
||||
import { getColorWithOpacity } from "@/utils/common";
|
||||
import HighlightText from "@/views/home/modules/HighlightText.vue";
|
||||
import Ed from "@/views/home/modules/Ed.vue";
|
||||
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
@@ -29,7 +31,7 @@ const form = ref<ApplyForm>({
|
||||
name: "", dept: "", mainChanges: [""], related: "", purposes: [], effect: "",
|
||||
type: "", level: "", duration: "", urgent: false, date: null, restoreDate: null,
|
||||
});
|
||||
const tab = ref<string>("form");
|
||||
const tab = ref<string>("train");
|
||||
const TABS = [
|
||||
{ id: "pre", name: "变更预识别", icon: 'lucide:sparkles' },
|
||||
{ id: "form", name: "变更申请表", icon: 'akar-icons:file' },
|
||||
@@ -390,8 +392,94 @@ const finishApprover = () => {
|
||||
sheet.value = null;
|
||||
window.$message?.success('审批流程已更新(底部固定行摘要同步刷新)');
|
||||
}
|
||||
// 风险识别分析记录表
|
||||
const TOOL_CARDS = [
|
||||
{ key: "HAZOP" as const, name: "HAZOP 分析", desc: "节点-偏离结构化分析:原因 / 后果 / LS 风险矩阵 / 安全措施" },
|
||||
{ key: "JSA" as const, name: "JSA 分析", desc: "作业步骤分解:危害因素识别与 LS 风险分级管控" },
|
||||
{ key: "检查表法" as const, name: "设备 SCL", desc: "设备 / 管理检查项目对照标准逐项核查" },
|
||||
{ key: "通用检查表" as const, name: "风险检查表", desc: "变更通用检查项:是 / 否 / 不涉及逐项判定,输出风险描述与管控措施" },
|
||||
];
|
||||
const pluginDone = ref<Record<string, number>>({});
|
||||
const riskRecords = ref<any>([]);
|
||||
const updRec = (i: number, patch: Partial<any>) => {
|
||||
riskRecords.value = riskRecords.value.map((x, j) => (j === i ? { ...x, ...patch } : x));
|
||||
};
|
||||
const addRiskRecords = (items: any[], mode: "manual" | "ai") => {
|
||||
riskRecords.value = [...riskRecords.value, ...items];
|
||||
if (mode === "ai") {
|
||||
plugin.value = null;
|
||||
window.$message?.success(`AI 已自动整理形成风险分析记录表(新增 ${items.length} 项),支持手动修改`);
|
||||
} else {
|
||||
window.$message?.success(`已选入 ${items.length} 条风险记录,可继续挑选或返回查看记录表`);
|
||||
}
|
||||
};
|
||||
const lvlNext = (v: string) => (v === "高" ? "中" : v === "中" ? "低" : "高");
|
||||
const pssrGroups = ref<{ g: string; items: any[] }[]>([
|
||||
{ g: "一、风险分析行动项落实", items: [
|
||||
{ c: "联锁跳车电流设定值校核并留存记录", p: "投用前" },
|
||||
{ c: "电缆与开关容量校核升级完成", p: "投用前" },
|
||||
{ c: "操作人员培训完成并考核合格", p: "投用前" },
|
||||
] },
|
||||
{ g: "二、现场设备安装检查确认", items: [
|
||||
{ c: "电机安装就位,联轴器对中合格", p: "投用前" },
|
||||
{ c: "地脚螺栓紧固,减振垫安装到位", p: "投用前" },
|
||||
{ c: "电缆接线、防爆格兰头密封良好", p: "投用前" },
|
||||
] },
|
||||
{ g: "三、保护措施确认", items: [
|
||||
{ c: "联锁跳车设定值与新电机额定电流匹配", p: "投用前" },
|
||||
{ c: "电机外壳接地、防护罩完好", p: "投用前" },
|
||||
{ c: "紧急停机按钮功能试验正常", p: "投用前" },
|
||||
] },
|
||||
{ g: "四、资料归档确认", items: [
|
||||
{ c: "P&ID(图号 PID-R201-03)已更新并受控", p: "关闭前" },
|
||||
{ c: "设备台账、备件清单已更新", p: "关闭前" },
|
||||
{ c: "培训签到表、考核记录已归档", p: "关闭前" },
|
||||
] },
|
||||
]);
|
||||
const toPssr = (i: number) => {
|
||||
const rec = riskRecords.value[i];
|
||||
if (!rec || rec.pssr) return;
|
||||
pssrGroups.value = pssrGroups.value.map((g:any, gi:any) => gi === 0
|
||||
? { ...g, items: [...g.items, { c: `${rec.suggest}(来源:风险分析记录表 · ${rec.item})`, p: "投用前" }] }
|
||||
: g);
|
||||
updRec(i, { pssr: true });
|
||||
window.$message?.success("建议措施已转为 PSSR 行动项(见「PSSR 检查内容」· 一、风险分析行动项落实)");
|
||||
};
|
||||
const toPssrAll = () => {
|
||||
const rest = riskRecords.value.map((x:any, i:any) => (x.pssr ? -1 : i)).filter((i:any) => i >= 0);
|
||||
if (!rest.length) { window.$message?.info("所有建议措施均已转为 PSSR 行动项"); return; }
|
||||
const items = rest.map((i:any) => ({ c: `${riskRecords.value[i].suggest}(来源:风险分析记录表 · ${riskRecords.value[i].item})`, p: "投用前" }));
|
||||
pssrGroups.value = pssrGroups.value.map((g:any, gi:any) => gi === 0 ? { ...g, items: [...g.items, ...items] } : g);
|
||||
riskRecords.value = riskRecords.value.map((x:any) => ({ ...x, pssr: true }));
|
||||
window.$message?.success(`已将 ${rest.length} 条建议措施全部转为 PSSR 行动项(见「PSSR 检查内容」)`);
|
||||
};
|
||||
const aiOrganize = () => {
|
||||
const gen: any[] = [
|
||||
{ item: "超温溜温冲料", scene: "反应温度提升至 90℃ 后更接近溶剂回流温度,温控失效或冷却水中断时反应溜温,釜压骤升可能导致冲料与物料喷溅", inherent: "高", existing: "DCS 温度高报 95℃、高高报及联锁 100℃(随变更上调并重新整定);紧急冷却系统有效", suggest: "1. 投用前完成联锁传动测试并留存记录;2. 投用首周每班复核温度趋势,纳入交接班巡检", residual: "中", source: "AI 整理" },
|
||||
{ item: "未知杂质累积影响", scene: "未知杂质含量由 0.05% 升至 0.12%,热稳定性尚未定性,长期累积加速釜壁结垢与搅拌负荷异常", inherent: "中", existing: "小试热稳定性筛查;安全阀泄放能力不受本次调整影响", suggest: "1. 将杂质含量纳入日常分析计划(每周 1 次,连续 8 周);2. 跟踪结垢趋势,安排周期清洗", residual: "低", source: "AI 整理" },
|
||||
{ item: "联锁 / 报警设定失效", scene: "报警 / 联锁值上调后(高报 85→95℃、联锁 90→100℃),原有报警裕度改变,存在误报、漏报风险", inherent: "中", existing: "参数修改执行申请-复核双人确认", suggest: "1. 联锁逻辑图与台账同步更新并归档;2. 联锁测试纳入 PSSR 必查项", residual: "低", source: "AI 整理" },
|
||||
];
|
||||
riskRecords.value = [...riskRecords.value, ...gen];
|
||||
window.$message?.success("AI 已根据变更内容自动整理形成风险分析记录表(3 项),支持手动修改");
|
||||
};
|
||||
// ---------- 变更培训 ----------
|
||||
const trainText = ref(
|
||||
`培训对象:R-201 岗位操作人员(4 人)、维修班(2 人)
|
||||
培训方式:现场讲解 + 操作演示
|
||||
|
||||
一、变更内容
|
||||
M-201A 搅拌电机由 55kW 更换为 75kW 防爆电机(ExdⅡBT4 → ExdⅡCT4),额定电流 102A → 138A;电缆与开关容量同步升级,联锁跳车电流设定值同步校核调整。
|
||||
|
||||
二、变化的操作方式
|
||||
1. 搅拌器启动:变更前直接启动、观察电流 ≤102A;变更后启动后核对电流 ≤138A,确认变频器参数已更新(注意:首次启动空载试运行 30 分钟)
|
||||
2. 高粘度工况操作:变更前粘度 >3000cP 需降速运行;变更后粘度 ≤6000cP 可全速运行(注意:严禁超过 6000cP 上限)
|
||||
3. 过载跳闸处置:变更前复位后直接重启;变更后先查明原因并记录,联锁复位需班长确认(注意:跳闸电流设定值已调整)
|
||||
|
||||
三、潜在风险与应对措施
|
||||
1. 风险:电机 / 电缆过热 → 应对:投用首周每班测温并记录,红外点检复核
|
||||
2. 风险:联锁设定错误导致拒动 / 误动 → 应对:投用前完成跳车试验并留存记录
|
||||
3. 风险:防爆接合面或格兰头密封失效 → 应对:检查防爆接合面与格兰头,严禁带电开盖`
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
@@ -757,6 +845,142 @@ const finishApprover = () => {
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<!-- ===== 风险记录表 ===== -->
|
||||
<template v-if="tab === 'risk'">
|
||||
<div class="border p-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
|
||||
<Icon icon="lucide:shield-alert" class="size-16px" />
|
||||
风险识别工具(按变更等级匹配)
|
||||
</span>
|
||||
<NTag size="small" :type="form?.level === '重要' ? 'error' : 'default'">
|
||||
{{ form?.level === '重要' ? '重要变更' : '一般变更' }}
|
||||
</NTag>
|
||||
<span class="text-xs text-slate-400">
|
||||
{{ form?.level === '重要' ? '规则:HAZOP 必做,同时开展 JSA 与检查表法(SCL)' : '规则:JSA + 检查表法(SCL),涉及工艺安全边界的应升级 HAZOP' }};可在申请表「风险分析」栏手动重选
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid gap-2 grid-cols-4 mt-3">
|
||||
<div v-for="t in TOOL_CARDS" :key="t.key" :title="t.desc"
|
||||
class="flex items-center gap-2 rounded-lg border px-3 py-2 transition"
|
||||
:class="pluginDone[t.key] !== undefined ? 'border-[var(--theme-color)] bg-[var(--theme-color)]' : tools.includes(t.key) ? 'border-[var(--theme-color)] bg-white' : 'border-slate-200 bg-white'"
|
||||
:style="{'--theme-color': themeStore.themeColor}"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div
|
||||
class="flex items-center gap-1.5 text-xs font-semibold"
|
||||
:class="pluginDone[t.key] !== undefined ? 'text-white' : tools.includes(t.key) ? 'text-[var(--theme-color)]' : 'text-slate-500'"
|
||||
:style="{'--theme-color': themeStore.themeColor}"
|
||||
>
|
||||
<Icon icon="fluent:wand-24-regular" class="size-16px" />
|
||||
{{ t.name }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-[10px]" :class="pluginDone[t.key] !== undefined ? 'text-white/75' : 'text-slate-400'">
|
||||
{{ pluginDone[t.key] !== undefined ? `已使用并分析了 ${pluginDone[t.key]} 条内容` : '未使用此工具分析' }}
|
||||
</div>
|
||||
</div>
|
||||
<NButton size="small" class="text-xs" :type="pluginDone[t.key] !== undefined ? 'default' : tools.includes(t.key) ? 'primary' : 'default'"
|
||||
@click="if (!tools.includes(t.key)) toggleTool(t.key); plugin = t.key;">
|
||||
使用
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex flex-wrap items-center gap-2 bg-slate-100 px-3 py-2">
|
||||
<span class="flex items-center gap-1.5 text-sm font-medium text-slate-700">
|
||||
<Icon icon="proicons:alert-triangle" class="size-16px text-amber-500" />
|
||||
风险分析记录表
|
||||
</span>
|
||||
<span class="text-[11px] text-slate-400">共 {{ riskRecords.length }} 项</span>
|
||||
<span class="ml-auto hidden text-[11px] text-slate-400 lg:block">插件结果「入表」或 AI 自动整理形成;单元格点击修改,风险等级点击切换</span>
|
||||
<NButton ghost size="small" color="#7c3aed" class="text-xs" @click="toPssrAll">
|
||||
<Icon icon="tabler:clipboard-check" class="size-14px" />
|
||||
全部转 PSSR 行动项
|
||||
</NButton>
|
||||
<NButton ghost size="small" type="primary" class="text-xs" @click="aiOrganize">
|
||||
<Icon icon="lucide:sparkles" class="size-12px" />
|
||||
AI 自动整理
|
||||
</NButton>
|
||||
</div>
|
||||
<div v-if="riskRecords.length === 0" class="px-3 py-6 text-center text-xs text-slate-400">
|
||||
尚无风险记录:调用上方插件完成分析后逐条「入表」,或点击「AI 自动整理」形成记录表
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto p-3">
|
||||
<table class="w-full min-w-[960px] border-collapse text-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="h in ['风险项', '场景描述', '固有风险', '现有措施', '建议措施', '剩余风险', '来源', '操作']" :key="h"
|
||||
class="border bg-slate-50 px-2 py-1.5 text-[11px] font-medium text-slate-500">{{ h }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(rec, i) in riskRecords" :key="i">
|
||||
<td class="border px-1.5 py-1 align-top font-medium text-slate-700">
|
||||
<Ed :v="rec.item" @update="(x: string) => updRec(i, { item: x })" />
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.scene" @update="(x: string) => updRec(i, { scene: x })" /></td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag v-if="rec.inherent==='高'" type="error" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
||||
{{ rec.inherent }}
|
||||
</NTag>
|
||||
<NTag v-if="rec.inherent==='中'" type="warning" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
||||
{{ rec.inherent }}
|
||||
</NTag>
|
||||
<NTag v-if="rec.inherent==='低'" type="success" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
||||
{{ rec.inherent }}
|
||||
</NTag>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.existing" @update="(x: string) => updRec(i, { existing: x })" /></td>
|
||||
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.suggest" @update="(x: string) => updRec(i, { suggest: x })" /></td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag v-if="rec.residual==='高'" type="error" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { residual: lvlNext(rec.residual) as '高' | '中' | '低' })">
|
||||
{{ rec.residual }}
|
||||
</NTag>
|
||||
<NTag v-if="rec.residual==='中'" type="warning" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { residual: lvlNext(rec.residual) as '高' | '中' | '低' })">
|
||||
{{ rec.residual }}
|
||||
</NTag>
|
||||
<NTag v-if="rec.residual==='低'" type="success" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { residual: lvlNext(rec.residual) as '高' | '中' | '低' })">
|
||||
{{ rec.residual }}
|
||||
</NTag>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag size="small" type="info">{{ rec.source }}</NTag>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag v-if="rec.pssr" size="small" :color="{ color: '#f5f3ff', textColor: '#7c3aed', borderColor: '#f5f3ff' }">已转 PSSR</NTag>
|
||||
<NButton text size="tiny" color="#7c3aed" v-else class="hover:underline" @click="toPssr(i)">转 PSSR</NButton>
|
||||
<NButton text type="error" size="tiny" class="mt-2" @click="riskRecords = riskRecords.filter((_, j) => j !== i)">删除</NButton>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 变更培训内容 -->
|
||||
<template v-if="tab === 'train'">
|
||||
<div class="flex flex-wrap items-center gap-3 mb-3">
|
||||
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
|
||||
<Icon icon="lucide:graduation-cap" class="size-16px text-violet-500" />
|
||||
变更培训内容
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
||||
✨ AI 生成 · 需人工确认
|
||||
</span>
|
||||
<span class="ml-auto text-xs text-slate-400">AI 生成内容均可点击修改自定义;审批通过后,相关人员在 PC / 移动端完成学习与签到确认</span>
|
||||
</div>
|
||||
<!-- 培训内容 -->
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex flex-wrap items-center gap-2 bg-slate-100 px-3 py-2">
|
||||
<span class="text-sm font-medium text-slate-700">培训内容</span>
|
||||
<span class="text-[11px] text-slate-400">含培训对象、培训方式、变更内容、操作方式、潜在风险与应对措施,一个文本框自动分段换行</span>
|
||||
<span class="ml-auto text-[11px] text-slate-400">内容支持直接修改</span>
|
||||
</div>
|
||||
<NInput v-model:value="trainText" type="textarea" :bordered="false" :autosize="{minRows: 4}" class="font-mono text-sm" placeholder="" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部 -->
|
||||
@@ -978,11 +1202,9 @@ const finishApprover = () => {
|
||||
.scroll {
|
||||
height: calc(100vh - 326px);
|
||||
overflow-y: auto;
|
||||
@include scrollbar();
|
||||
}
|
||||
.scroll2 {
|
||||
height: calc(100vh - 409px);
|
||||
overflow-y: auto;
|
||||
@include scrollbar();
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user