生成变更申请单一键生成全部内容:回填申请表后并行生成等级判定表/风险检查表/培训内容/PSSR/验收评价;风险检查表数据提升至风险分析页并接入aiRiskCheck预填(42901自动重试一次),各标签页重新生成仅生成本页内容
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, provide, ref } from 'vue';
|
import { computed, nextTick, onMounted, provide, ref } from 'vue';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { getColorWithOpacity } from "@/utils/common";
|
import { getColorWithOpacity } from "@/utils/common";
|
||||||
@@ -62,14 +62,28 @@ const changeApplyForm = ref<any>(null)
|
|||||||
// 当前申请表所选变更类型的文本编码(供 PSSR 等 AI 接口使用)
|
// 当前申请表所选变更类型的文本编码(供 PSSR 等 AI 接口使用)
|
||||||
const applyTypeLabel = computed(() => typeList.value.find((t) => t.value === changeApplyForm.value?.change_type)?.label ?? '');
|
const applyTypeLabel = computed(() => typeList.value.find((t) => t.value === changeApplyForm.value?.change_type)?.label ?? '');
|
||||||
|
|
||||||
// AI 生成变更申请单(变更预识别页派发):回填申请表并切换标签
|
// AI 生成变更申请单(变更预识别页派发):回填申请表,随后后台并行生成
|
||||||
const onApplyGenerated = (res: any) => {
|
// 等级判定表 / 风险检查表 / 培训内容 / PSSR / 验收评价(各组件自行处理 loading 与报错,失败不阻断)
|
||||||
|
const onApplyGenerated = async (res: any) => {
|
||||||
if (res?.name) title.value = res.name;
|
if (res?.name) title.value = res.name;
|
||||||
changeApplyRef.value?.applyAiResult(res);
|
changeApplyRef.value?.applyAiResult(res);
|
||||||
// 直接切 tab 不经过 tabChange,需同步捕获预识别表单供其他标签页 AI 接口使用
|
// 直接切 tab 不经过 tabChange,需同步捕获预识别/申请表表单供其他标签页 AI 接口使用
|
||||||
changePreForm.value = changePreRef.value?.form;
|
changePreForm.value = changePreRef.value?.form;
|
||||||
|
changeApplyForm.value = changeApplyRef.value?.form;
|
||||||
tab.value = 'form';
|
tab.value = 'form';
|
||||||
window.$message?.success("变更申请单已生成:AI 自动总结名称、预填内容,均可手动修改");
|
window.$message?.success("变更申请单已生成:AI 自动总结名称、预填内容,均可手动修改");
|
||||||
|
// 等 props 同步后并行生成其余内容(等级判定/风险检查表/培训/PSSR/验收评价)
|
||||||
|
await nextTick();
|
||||||
|
const jobs = [
|
||||||
|
changeApplyRef.value?.generateLevel?.(),
|
||||||
|
riskAnalysisRef.value?.generateRiskCheck?.(),
|
||||||
|
changeTrainingRef.value?.onRegenerate?.(),
|
||||||
|
pssrRef.value?.onRegenerate?.(),
|
||||||
|
evaluationRef.value?.onRegenerate?.(),
|
||||||
|
].filter(Boolean);
|
||||||
|
window.$message?.info("正在后台生成:等级判定表 / 风险检查表 / 变更培训内容 / PSSR检查内容 / 验收评价(风险检查表约 1~3 分钟)");
|
||||||
|
await Promise.allSettled(jobs);
|
||||||
|
window.$message?.info("全部 AI 内容已生成完毕:请逐标签页检查确认;如有失败项,可在对应标签页点击「重新生成」单独重试");
|
||||||
}
|
}
|
||||||
|
|
||||||
// **********************tab切换***********************
|
// **********************tab切换***********************
|
||||||
@@ -332,6 +346,7 @@ onMounted(() => {
|
|||||||
:title="title"
|
:title="title"
|
||||||
:currentId="currentId"
|
:currentId="currentId"
|
||||||
:formInfo="changeApplyForm"
|
:formInfo="changeApplyForm"
|
||||||
|
:preInfo="changePreForm"
|
||||||
@save="saveDraft"
|
@save="saveDraft"
|
||||||
@submit="submitConfirm"
|
@submit="submitConfirm"
|
||||||
@confirmTab="confirmTab"
|
@confirmTab="confirmTab"
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ const confirmTab = (tab:string, val:boolean) => {
|
|||||||
emit('confirmTab', tab, val);
|
emit('confirmTab', tab, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({form, applyAiResult})
|
defineExpose({form, applyAiResult, generateLevel: useAiLevel})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const confirmTab = (tab:string, val:boolean) => {
|
|||||||
emit('confirmTab', tab, val);
|
emit('confirmTab', tab, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({form})
|
defineExpose({form, onRegenerate})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ const findNodeById = (tree:any,id:number) => {
|
|||||||
return null; // 未找到
|
return null; // 未找到
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({list})
|
defineExpose({list, onRegenerate})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ const confirmTab = (tab:string, val:boolean) => {
|
|||||||
emit('confirmTab', tab, val);
|
emit('confirmTab', tab, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({list})
|
defineExpose({list, onRegenerate})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,295 +1,353 @@
|
|||||||
<!-- 风险分析记录表 -->
|
<!-- 风险分析记录表 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import Ed from "./Ed.vue";
|
import { AiError, aiRiskCheck } from "@/service/api/ai";
|
||||||
import Hazop from "./hazop.vue";
|
import Ed from "./Ed.vue";
|
||||||
import Jsa from "./jsa.vue";
|
import Hazop from "./hazop.vue";
|
||||||
import Scl from "./scl.vue";
|
import Jsa from "./jsa.vue";
|
||||||
import RiskCheck from "./riskCheck.vue";
|
import Scl from "./scl.vue";
|
||||||
import Footer from './footer.vue'
|
import RiskCheck from "./riskCheck.vue";
|
||||||
|
import Footer from './footer.vue'
|
||||||
const themeStore = useThemeStore();
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
const props = defineProps(['type','isAiOpen','title','currentId','formInfo'])
|
|
||||||
const emit = defineEmits(['save','submit','confirmTab']);
|
const props = defineProps(['type','isAiOpen','title','currentId','formInfo','preInfo'])
|
||||||
|
const emit = defineEmits(['save','submit','confirmTab']);
|
||||||
const form = ref<{ content: string }>({
|
|
||||||
content: ''
|
const footerRef = ref<any>(null);
|
||||||
});
|
|
||||||
const pluginDone = ref<Record<string, number>>({});
|
const aiFail = (e: any) => {
|
||||||
const tools = ref<string[]>(["HAZOP","JSA", "SCL", "RISK_CHECK"]);
|
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||||
const riskRecords = ref<any>([]);
|
else window.$message?.warning("AI 分析失败,可手动填写");
|
||||||
const pluginDrawer = ref<boolean>(false);
|
};
|
||||||
const currentPlugin = ref<string>('');
|
|
||||||
const lvlNext = (v: string) => (v === "高" ? "中" : v === "中" ? "低" : "高");
|
const form = ref<{ content: string }>({
|
||||||
|
content: ''
|
||||||
// 风险识别分析记录表
|
});
|
||||||
const TOOL_CARDS = [
|
const pluginDone = ref<Record<string, number>>({});
|
||||||
{ key: "HAZOP" as const, name: "HAZOP 分析", desc: "节点-偏离结构化分析:原因 / 后果 / LS 风险矩阵 / 安全措施" },
|
const tools = ref<string[]>(["HAZOP","JSA", "SCL", "RISK_CHECK"]);
|
||||||
{ key: "JSA" as const, name: "JSA 分析", desc: "作业步骤分解:危害因素识别与 LS 风险分级管控" },
|
const riskRecords = ref<any>([]);
|
||||||
{ key: "SCL" as const, name: "设备 SCL", desc: "设备 / 管理检查项目对照标准逐项核查" },
|
// 风险检查表检查项(数据提升到本组件:AI 预填/重新生成随时可写,插件抽屉打开即渲染)
|
||||||
{ key: "RISK_CHECK" as const, name: "风险检查表", desc: "变更通用检查项:是 / 否 / 不涉及逐项判定,输出风险描述与管控措施" },
|
const riskChecks = ref<any[]>([]);
|
||||||
];
|
const riskCheckLoading = ref<boolean>(false);
|
||||||
const pssrGroups = ref<{ g: string; items: any[] }[]>([
|
const pluginDrawer = ref<boolean>(false);
|
||||||
{ g: "一、风险分析行动项落实", items: [
|
const currentPlugin = ref<string>('');
|
||||||
{ c: "联锁跳车电流设定值校核并留存记录", p: "投用前" },
|
const lvlNext = (v: string) => (v === "高" ? "中" : v === "中" ? "低" : "高");
|
||||||
{ c: "电缆与开关容量校核升级完成", p: "投用前" },
|
|
||||||
{ c: "操作人员培训完成并考核合格", p: "投用前" },
|
// AI 生成/重新生成风险检查表(footer 派发;整表生成约 1~3 分钟,42901 闸门满自动重试一次)
|
||||||
] },
|
const generateRiskCheck = async () => {
|
||||||
{ g: "二、现场设备安装检查确认", items: [
|
const info = props.preInfo;
|
||||||
{ c: "电机安装就位,联轴器对中合格", p: "投用前" },
|
if (!info?.description) {
|
||||||
{ c: "地脚螺栓紧固,减振垫安装到位", p: "投用前" },
|
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
||||||
{ c: "电缆接线、防爆格兰头密封良好", p: "投用前" },
|
}
|
||||||
] },
|
if (riskCheckLoading.value) return;
|
||||||
{ g: "三、保护措施确认", items: [
|
riskCheckLoading.value = true;
|
||||||
{ c: "联锁跳车设定值与新电机额定电流匹配", p: "投用前" },
|
footerRef.value?.setRegenerateLoading(true);
|
||||||
{ c: "电机外壳接地、防护罩完好", p: "投用前" },
|
try {
|
||||||
{ c: "紧急停机按钮功能试验正常", p: "投用前" },
|
for (let attempt = 0; attempt < 2; attempt++) {
|
||||||
] },
|
try {
|
||||||
{ g: "四、资料归档确认", items: [
|
const res = await aiRiskCheck(info.description, (info.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })));
|
||||||
{ c: "P&ID(图号 PID-R201-03)已更新并受控", p: "关闭前" },
|
const items = (res?.items ?? []).map((x: any, i: number) => ({
|
||||||
{ c: "设备台账、备件清单已更新", p: "关闭前" },
|
id: i + 1,
|
||||||
{ c: "培训签到表、考核记录已归档", p: "关闭前" },
|
group: x.group || '安全健康',
|
||||||
] },
|
q: x.q ?? '',
|
||||||
]);
|
ans: x.ans ?? 'NA',
|
||||||
|
risk: x.risk ?? '',
|
||||||
const toPssrAll = () => {
|
measure: x.measure ?? '',
|
||||||
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; }
|
if (!items.length) {
|
||||||
const items = rest.map((i:any) => ({ c: `${riskRecords.value[i].suggest}(来源:风险分析记录表 · ${riskRecords.value[i].item})`, p: "投用前" }));
|
window.$message?.warning("AI 未生成检查项,可在风险检查表插件中手动维护");
|
||||||
pssrGroups.value = pssrGroups.value.map((g:any, gi:any) => gi === 0 ? { ...g, items: [...g.items, ...items] } : g);
|
return;
|
||||||
riskRecords.value = riskRecords.value.map((x:any) => ({ ...x, pssr: true }));
|
}
|
||||||
window.$message?.success(`已将 ${rest.length} 条建议措施全部转为 PSSR 行动项(见「PSSR 检查内容」)`);
|
riskChecks.value = items;
|
||||||
};
|
window.$message?.success(`风险检查表已由 AI 生成(${items.length} 项),打开「风险检查表」工具卡逐项确认`);
|
||||||
|
return;
|
||||||
const lsLevel = (l: number | null, s: number | null) => {
|
} catch (e: any) {
|
||||||
const rr = (l ?? 0) * (s ?? 0);
|
if (e instanceof AiError && e.code === 42901 && attempt === 0) {
|
||||||
return rr >= 10 ? "高" : rr >= 5 ? "中" : "低";
|
const waitSec = (e.data?.retry_after as number) ?? 30;
|
||||||
};
|
window.$message?.info(`当前 AI 生成任务较多,${waitSec} 秒后自动重试一次…`);
|
||||||
|
await new Promise((r) => setTimeout(r, waitSec * 1000));
|
||||||
const aiOrganize = async () => {
|
continue;
|
||||||
// let form = changePreRef.value?.form;
|
}
|
||||||
// analyzing.value = true;
|
aiFail(e);
|
||||||
// try {
|
return;
|
||||||
// const res = await aiRiskOrganize(form.description, (form.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })));
|
}
|
||||||
// const gen = (res?.records ?? []).map((r) => ({
|
}
|
||||||
// item: r.item,
|
} finally {
|
||||||
// scene: r.scene,
|
riskCheckLoading.value = false;
|
||||||
// inherent: lsLevel(r.l, r.s),
|
footerRef.value?.setRegenerateLoading(false);
|
||||||
// existing: r.existing,
|
}
|
||||||
// suggest: r.suggest,
|
};
|
||||||
// residual: lsLevel(r.residual_l, r.residual_s),
|
const onRegenerate = generateRiskCheck;
|
||||||
// source: "AI 整理",
|
|
||||||
// }));
|
// 风险识别分析记录表
|
||||||
// riskRecords.value = [...riskRecords.value, ...gen];
|
const TOOL_CARDS = [
|
||||||
// window.$message?.success(`AI 已根据变更内容自动整理形成风险分析记录表(${gen.length} 项),支持手动修改`);
|
{ key: "HAZOP" as const, name: "HAZOP 分析", desc: "节点-偏离结构化分析:原因 / 后果 / LS 风险矩阵 / 安全措施" },
|
||||||
// } catch (e: any) {
|
{ key: "JSA" as const, name: "JSA 分析", desc: "作业步骤分解:危害因素识别与 LS 风险分级管控" },
|
||||||
// aiFail(e);
|
{ key: "SCL" as const, name: "设备 SCL", desc: "设备 / 管理检查项目对照标准逐项核查" },
|
||||||
// } finally {
|
{ key: "RISK_CHECK" as const, name: "风险检查表", desc: "变更通用检查项:是 / 否 / 不涉及逐项判定,输出风险描述与管控措施" },
|
||||||
// analyzing.value = false;
|
];
|
||||||
// }
|
const pssrGroups = ref<{ g: string; items: any[] }[]>([
|
||||||
};
|
{ g: "一、风险分析行动项落实", items: [
|
||||||
|
{ c: "联锁跳车电流设定值校核并留存记录", p: "投用前" },
|
||||||
const toPssr = (i: number) => {
|
{ c: "电缆与开关容量校核升级完成", p: "投用前" },
|
||||||
const rec = riskRecords.value[i];
|
{ c: "操作人员培训完成并考核合格", p: "投用前" },
|
||||||
if (!rec || rec.pssr) return;
|
] },
|
||||||
pssrGroups.value = pssrGroups.value.map((g:any, gi:any) => gi === 0
|
{ g: "二、现场设备安装检查确认", items: [
|
||||||
? { ...g, items: [...g.items, { c: `${rec.suggest}(来源:风险分析记录表 · ${rec.item})`, p: "投用前" }] }
|
{ c: "电机安装就位,联轴器对中合格", p: "投用前" },
|
||||||
: g);
|
{ c: "地脚螺栓紧固,减振垫安装到位", p: "投用前" },
|
||||||
updRec(i, { pssr: true });
|
{ c: "电缆接线、防爆格兰头密封良好", p: "投用前" },
|
||||||
window.$message?.success("建议措施已转为 PSSR 行动项(见「PSSR 检查内容」· 一、风险分析行动项落实)");
|
] },
|
||||||
};
|
{ g: "三、保护措施确认", items: [
|
||||||
|
{ c: "联锁跳车设定值与新电机额定电流匹配", p: "投用前" },
|
||||||
const updRec = (i: number, patch: Partial<any>) => {
|
{ c: "电机外壳接地、防护罩完好", p: "投用前" },
|
||||||
riskRecords.value = riskRecords.value.map((x, j) => (j === i ? { ...x, ...patch } : x));
|
{ c: "紧急停机按钮功能试验正常", p: "投用前" },
|
||||||
};
|
] },
|
||||||
|
{ g: "四、资料归档确认", items: [
|
||||||
const exportAll = (items: any[], mode: "manual" | "ai") => {
|
{ c: "P&ID(图号 PID-R201-03)已更新并受控", p: "关闭前" },
|
||||||
riskRecords.value = [...riskRecords.value, ...items];
|
{ c: "设备台账、备件清单已更新", p: "关闭前" },
|
||||||
if (mode === "ai") {
|
{ c: "培训签到表、考核记录已归档", p: "关闭前" },
|
||||||
pluginDrawer.value = false;
|
] },
|
||||||
currentPlugin.value = '';
|
]);
|
||||||
window.$message?.success(`AI 已自动整理形成风险分析记录表(新增 ${items.length} 项),支持手动修改`);
|
|
||||||
} else {
|
const toPssrAll = () => {
|
||||||
window.$message?.success(`已选入 ${items.length} 条风险记录,可继续挑选或返回查看记录表`);
|
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 }));
|
||||||
const usePlugin = (key: string) => {
|
window.$message?.success(`已将 ${rest.length} 条建议措施全部转为 PSSR 行动项(见「PSSR 检查内容」)`);
|
||||||
currentPlugin.value = key;
|
};
|
||||||
pluginDrawer.value = true;
|
|
||||||
}
|
const lsLevel = (l: number | null, s: number | null) => {
|
||||||
// 关闭插件弹框
|
const rr = (l ?? 0) * (s ?? 0);
|
||||||
const closePlugin = () => {
|
return rr >= 10 ? "高" : rr >= 5 ? "中" : "低";
|
||||||
pluginDrawer.value = false;
|
};
|
||||||
}
|
|
||||||
|
const aiOrganize = async () => {
|
||||||
// 保存草稿
|
// let form = changePreRef.value?.form;
|
||||||
const saveDraft = () => {
|
// analyzing.value = true;
|
||||||
emit('save');
|
// try {
|
||||||
}
|
// const res = await aiRiskOrganize(form.description, (form.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })));
|
||||||
// 提交
|
// const gen = (res?.records ?? []).map((r) => ({
|
||||||
const submitConfirm = () => {
|
// item: r.item,
|
||||||
emit('submit', props.type);
|
// scene: r.scene,
|
||||||
}
|
// inherent: lsLevel(r.l, r.s),
|
||||||
// // 确认标签内容
|
// existing: r.existing,
|
||||||
const confirmTab = (tab:string, val:boolean) => {
|
// suggest: r.suggest,
|
||||||
emit('confirmTab', tab, val);
|
// residual: lsLevel(r.residual_l, r.residual_s),
|
||||||
}
|
// source: "AI 整理",
|
||||||
|
// }));
|
||||||
defineExpose({form})
|
// riskRecords.value = [...riskRecords.value, ...gen];
|
||||||
</script>
|
// window.$message?.success(`AI 已根据变更内容自动整理形成风险分析记录表(${gen.length} 项),支持手动修改`);
|
||||||
|
// } catch (e: any) {
|
||||||
<template>
|
// aiFail(e);
|
||||||
<div class="p-4">
|
// } finally {
|
||||||
<div class="space-y-4 scroll">
|
// analyzing.value = false;
|
||||||
<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" />
|
const toPssr = (i: number) => {
|
||||||
风险识别工具(按变更等级匹配)
|
const rec = riskRecords.value[i];
|
||||||
</span>
|
if (!rec || rec.pssr) return;
|
||||||
<NTag size="small" :type="formInfo?.change_level === 2 ? 'error' : 'default'">
|
pssrGroups.value = pssrGroups.value.map((g:any, gi:any) => gi === 0
|
||||||
{{ formInfo?.change_level === 2 ? '重要变更' : '一般变更' }}
|
? { ...g, items: [...g.items, { c: `${rec.suggest}(来源:风险分析记录表 · ${rec.item})`, p: "投用前" }] }
|
||||||
</NTag>
|
: g);
|
||||||
<span class="text-xs text-slate-400">
|
updRec(i, { pssr: true });
|
||||||
{{ formInfo?.change_level === 2 ? '规则:HAZOP 必做,同时开展 JSA 与检查表法(SCL)' : '规则:JSA + 检查表法(SCL),涉及工艺安全边界的应升级 HAZOP' }}
|
window.$message?.success("建议措施已转为 PSSR 行动项(见「PSSR 检查内容」· 一、风险分析行动项落实)");
|
||||||
</span>
|
};
|
||||||
</div>
|
|
||||||
<div class="grid gap-2 grid-cols-4 mt-3">
|
const updRec = (i: number, patch: Partial<any>) => {
|
||||||
<div v-for="t in TOOL_CARDS" :key="t.key" :title="t.desc"
|
riskRecords.value = riskRecords.value.map((x, j) => (j === i ? { ...x, ...patch } : x));
|
||||||
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}"
|
const exportAll = (items: any[], mode: "manual" | "ai") => {
|
||||||
>
|
riskRecords.value = [...riskRecords.value, ...items];
|
||||||
<div class="min-w-0 flex-1">
|
if (mode === "ai") {
|
||||||
<div
|
pluginDrawer.value = false;
|
||||||
class="flex items-center gap-1.5 text-xs font-semibold"
|
currentPlugin.value = '';
|
||||||
:class="pluginDone[t.key] !== undefined ? 'text-white' : tools.includes(t.key) ? 'text-[var(--theme-color)]' : 'text-slate-500'"
|
window.$message?.success(`AI 已自动整理形成风险分析记录表(新增 ${items.length} 项),支持手动修改`);
|
||||||
:style="{'--theme-color': themeStore.themeColor}"
|
} else {
|
||||||
>
|
window.$message?.success(`已选入 ${items.length} 条风险记录,可继续挑选或返回查看记录表`);
|
||||||
<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]} 条内容` : '未使用此工具分析' }}
|
const usePlugin = (key: string) => {
|
||||||
</div>
|
currentPlugin.value = key;
|
||||||
</div>
|
pluginDrawer.value = true;
|
||||||
<NButton size="small" class="text-xs" type="primary" @click="usePlugin(t.key)">
|
}
|
||||||
使用
|
// 关闭插件弹框
|
||||||
</NButton>
|
const closePlugin = () => {
|
||||||
</div>
|
pluginDrawer.value = false;
|
||||||
</div>
|
}
|
||||||
</div>
|
|
||||||
|
// 保存草稿
|
||||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
const saveDraft = () => {
|
||||||
<div class="flex flex-wrap items-center gap-2 bg-slate-100 px-3 py-2">
|
emit('save');
|
||||||
<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" />
|
// 提交
|
||||||
风险分析记录表
|
const submitConfirm = () => {
|
||||||
</span>
|
emit('submit', props.type);
|
||||||
<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">
|
const confirmTab = (tab:string, val:boolean) => {
|
||||||
<Icon icon="tabler:clipboard-check" class="size-14px" />
|
emit('confirmTab', tab, val);
|
||||||
全部转 PSSR 行动项
|
}
|
||||||
</NButton>
|
|
||||||
<NButton v-if="isAiOpen" ghost size="small" type="primary" class="text-xs" @click="aiOrganize">
|
defineExpose({form, generateRiskCheck})
|
||||||
<Icon icon="lucide:sparkles" class="size-12px" />
|
</script>
|
||||||
AI 自动整理
|
|
||||||
</NButton>
|
<template>
|
||||||
</div>
|
<div class="p-4">
|
||||||
<div v-if="riskRecords.length === 0" class="px-3 py-6 text-center text-xs text-slate-400">
|
<div class="space-y-4 scroll">
|
||||||
尚无风险记录:调用上方插件完成分析后逐条「入表」,或点击「AI 自动整理」形成记录表
|
<div class="border p-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
</div>
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<div v-else class="overflow-x-auto p-3">
|
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
|
||||||
<table class="w-full min-w-[960px] border-collapse text-xs">
|
<Icon icon="lucide:shield-alert" class="size-16px" />
|
||||||
<thead>
|
风险识别工具(按变更等级匹配)
|
||||||
<tr>
|
</span>
|
||||||
<th v-for="h in ['风险项', '场景描述', '固有风险', '现有措施', '建议措施', '剩余风险', '来源', '操作']" :key="h"
|
<NTag size="small" :type="formInfo?.change_level === 2 ? 'error' : 'default'">
|
||||||
class="border bg-slate-50 px-2 py-1.5 text-[11px] font-medium text-slate-500">{{ h }}</th>
|
{{ formInfo?.change_level === 2 ? '重要变更' : '一般变更' }}
|
||||||
</tr>
|
</NTag>
|
||||||
</thead>
|
<span class="text-xs text-slate-400">
|
||||||
<tbody>
|
{{ formInfo?.change_level === 2 ? '规则:HAZOP 必做,同时开展 JSA 与检查表法(SCL)' : '规则:JSA + 检查表法(SCL),涉及工艺安全边界的应升级 HAZOP' }}
|
||||||
<tr v-for="(rec, i) in riskRecords" :key="i">
|
</span>
|
||||||
<td class="border px-1.5 py-1 align-top font-medium text-slate-700">
|
</div>
|
||||||
<Ed :v="rec.item" @update="(x) => updRec(i, { item: x })" />
|
<div class="grid gap-2 grid-cols-4 mt-3">
|
||||||
</td>
|
<div v-for="t in TOOL_CARDS" :key="t.key" :title="t.desc"
|
||||||
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.scene" @update="(x) => updRec(i, { scene: x })" /></td>
|
class="flex items-center gap-2 rounded-lg border px-3 py-2 transition"
|
||||||
<td class="border px-1.5 py-1 text-center align-top">
|
: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'"
|
||||||
<NTag v-if="rec.inherent==='高'" type="error" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
:style="{'--theme-color': themeStore.themeColor}"
|
||||||
{{ rec.inherent }}
|
>
|
||||||
</NTag>
|
<div class="min-w-0 flex-1">
|
||||||
<NTag v-if="rec.inherent==='中'" type="warning" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
<div
|
||||||
{{ rec.inherent }}
|
class="flex items-center gap-1.5 text-xs font-semibold"
|
||||||
</NTag>
|
:class="pluginDone[t.key] !== undefined ? 'text-white' : tools.includes(t.key) ? 'text-[var(--theme-color)]' : 'text-slate-500'"
|
||||||
<NTag v-if="rec.inherent==='低'" type="success" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
:style="{'--theme-color': themeStore.themeColor}"
|
||||||
{{ rec.inherent }}
|
>
|
||||||
</NTag>
|
<Icon icon="fluent:wand-24-regular" class="size-16px" />
|
||||||
</td>
|
{{ t.name }}
|
||||||
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.existing" @update="(x) => updRec(i, { existing: x })" /></td>
|
</div>
|
||||||
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.suggest" @update="(x) => updRec(i, { suggest: x })" /></td>
|
<div class="mt-0.5 text-[10px]" :class="pluginDone[t.key] !== undefined ? 'text-white/75' : 'text-slate-400'">
|
||||||
<td class="border px-1.5 py-1 text-center align-top">
|
{{ pluginDone[t.key] !== undefined ? `已使用并分析了 ${pluginDone[t.key]} 条内容` : '未使用此工具分析' }}
|
||||||
<NTag v-if="rec.residual==='高'" type="error" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { residual: lvlNext(rec.residual) as '高' | '中' | '低' })">
|
</div>
|
||||||
{{ rec.residual }}
|
</div>
|
||||||
</NTag>
|
<NButton size="small" class="text-xs" type="primary" @click="usePlugin(t.key)">
|
||||||
<NTag v-if="rec.residual==='中'" type="warning" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { residual: lvlNext(rec.residual) as '高' | '中' | '低' })">
|
使用
|
||||||
{{ rec.residual }}
|
</NButton>
|
||||||
</NTag>
|
</div>
|
||||||
<NTag v-if="rec.residual==='低'" type="success" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { residual: lvlNext(rec.residual) as '高' | '中' | '低' })">
|
</div>
|
||||||
{{ rec.residual }}
|
</div>
|
||||||
</NTag>
|
|
||||||
</td>
|
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<td class="border px-1.5 py-1 text-center align-top">
|
<div class="flex flex-wrap items-center gap-2 bg-slate-100 px-3 py-2">
|
||||||
<NTag size="small" type="info">{{ rec.source }}</NTag>
|
<span class="flex items-center gap-1.5 text-sm font-medium text-slate-700">
|
||||||
</td>
|
<Icon icon="proicons:alert-triangle" class="size-16px text-amber-500" />
|
||||||
<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>
|
</span>
|
||||||
<NButton text size="tiny" color="#7c3aed" v-else class="hover:underline" @click="toPssr(i)">转 PSSR</NButton>
|
<span class="text-[11px] text-slate-400">共 {{ riskRecords.length }} 项</span>
|
||||||
<NButton text type="error" size="tiny" class="mt-2" @click="riskRecords = riskRecords.filter((_, j) => j !== i)">删除</NButton>
|
<span class="ml-auto hidden text-[11px] text-slate-400 lg:block">插件结果「入表」或 AI 自动整理形成;单元格点击修改,风险等级点击切换</span>
|
||||||
</td>
|
<NButton ghost size="small" color="#7c3aed" class="text-xs" @click="toPssrAll">
|
||||||
</tr>
|
<Icon icon="tabler:clipboard-check" class="size-14px" />
|
||||||
</tbody>
|
全部转 PSSR 行动项
|
||||||
</table>
|
</NButton>
|
||||||
</div>
|
<NButton v-if="isAiOpen" ghost size="small" type="primary" class="text-xs" @click="aiOrganize">
|
||||||
</div>
|
<Icon icon="lucide:sparkles" class="size-12px" />
|
||||||
</div>
|
AI 自动整理
|
||||||
</div>
|
</NButton>
|
||||||
<Footer
|
</div>
|
||||||
ref="footerRef"
|
<div v-if="riskRecords.length === 0" class="px-3 py-6 text-center text-xs text-slate-400">
|
||||||
:type="props.type"
|
尚无风险记录:调用上方插件完成分析后逐条「入表」,或点击「AI 自动整理」形成记录表
|
||||||
:currentId="props.currentId"
|
</div>
|
||||||
:title="props.title"
|
<div v-else class="overflow-x-auto p-3">
|
||||||
:isAiOpen="props.isAiOpen"
|
<table class="w-full min-w-[960px] border-collapse text-xs">
|
||||||
:currentForm="form"
|
<thead>
|
||||||
@save="saveDraft"
|
<tr>
|
||||||
@submit="submitConfirm"
|
<th v-for="h in ['风险项', '场景描述', '固有风险', '现有措施', '建议措施', '剩余风险', '来源', '操作']" :key="h"
|
||||||
@confirmTab="confirmTab"
|
class="border bg-slate-50 px-2 py-1.5 text-[11px] font-medium text-slate-500">{{ h }}</th>
|
||||||
/>
|
</tr>
|
||||||
<!-- 插件抽屉 -->
|
</thead>
|
||||||
<NDrawer v-model:show="pluginDrawer" :width="1200" placement="right">
|
<tbody>
|
||||||
<NDrawerContent>
|
<tr v-for="(rec, i) in riskRecords" :key="i">
|
||||||
<template v-if="currentPlugin === 'HAZOP'">
|
<td class="border px-1.5 py-1 align-top font-medium text-slate-700">
|
||||||
<Hazop :isAiOpen="props.isAiOpen" :currentId="props.currentId" @close="closePlugin" @export="exportAll" />
|
<Ed :v="rec.item" @update="(x) => updRec(i, { item: x })" />
|
||||||
</template>
|
</td>
|
||||||
<template v-else-if="currentPlugin === 'JSA'">
|
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.scene" @update="(x) => updRec(i, { scene: x })" /></td>
|
||||||
<Jsa @close="closePlugin" @export="exportAll" />
|
<td class="border px-1.5 py-1 text-center align-top">
|
||||||
</template>
|
<NTag v-if="rec.inherent==='高'" type="error" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
||||||
<template v-else-if="currentPlugin === 'SCL'">
|
{{ rec.inherent }}
|
||||||
<Scl @close="closePlugin" @export="exportAll" />
|
</NTag>
|
||||||
</template>
|
<NTag v-if="rec.inherent==='中'" type="warning" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
||||||
<template v-else-if="currentPlugin === 'RISK_CHECK'">
|
{{ rec.inherent }}
|
||||||
<RiskCheck @close="closePlugin" />
|
</NTag>
|
||||||
</template>
|
<NTag v-if="rec.inherent==='低'" type="success" size="small" class="cursor-pointer" title="点击切换等级" @click="updRec(i, { inherent: lvlNext(rec.inherent) as '高' | '中' | '低' })">
|
||||||
</NDrawerContent>
|
{{ rec.inherent }}
|
||||||
</NDrawer>
|
</NTag>
|
||||||
</template>
|
</td>
|
||||||
<style scoped lang="scss">
|
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.existing" @update="(x) => updRec(i, { existing: x })" /></td>
|
||||||
.scroll {
|
<td class="border px-1.5 py-1 align-top"><Ed :v="rec.suggest" @update="(x) => updRec(i, { suggest: x })" /></td>
|
||||||
height: calc(100vh - 393px);
|
<td class="border px-1.5 py-1 text-center align-top">
|
||||||
overflow-y: auto;
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer
|
||||||
|
ref="footerRef"
|
||||||
|
:type="props.type"
|
||||||
|
:currentId="props.currentId"
|
||||||
|
:title="props.title"
|
||||||
|
:isAiOpen="props.isAiOpen"
|
||||||
|
:currentForm="form"
|
||||||
|
@save="saveDraft"
|
||||||
|
@submit="submitConfirm"
|
||||||
|
@confirmTab="confirmTab"
|
||||||
|
@regenerate="onRegenerate"
|
||||||
|
/>
|
||||||
|
<!-- 插件抽屉 -->
|
||||||
|
<NDrawer v-model:show="pluginDrawer" :width="1200" placement="right">
|
||||||
|
<NDrawerContent>
|
||||||
|
<template v-if="currentPlugin === 'HAZOP'">
|
||||||
|
<Hazop :isAiOpen="props.isAiOpen" :currentId="props.currentId" @close="closePlugin" @export="exportAll" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="currentPlugin === 'JSA'">
|
||||||
|
<Jsa @close="closePlugin" @export="exportAll" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="currentPlugin === 'SCL'">
|
||||||
|
<Scl @close="closePlugin" @export="exportAll" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="currentPlugin === 'RISK_CHECK'">
|
||||||
|
<RiskCheck v-model="riskChecks" :loading="riskCheckLoading" @close="closePlugin" @back="closePlugin" />
|
||||||
|
</template>
|
||||||
|
</NDrawerContent>
|
||||||
|
</NDrawer>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.scroll {
|
||||||
|
height: calc(100vh - 393px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -4,48 +4,23 @@ import { useMessage } from "naive-ui";
|
|||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
|
||||||
|
// 检查项数据由父组件(riskAnalysis)持有:AI 预填 / 重新生成在父级完成,此处负责渲染与人工确认
|
||||||
|
const props = defineProps<{ modelValue: any[]; loading?: boolean }>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
back: [];
|
back: [];
|
||||||
|
"update:modelValue": [value: any[]];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const RISK_CHECKS_INIT = [
|
const riskChecks = computed<any[]>({
|
||||||
{
|
get: () => props.modelValue ?? [],
|
||||||
id: 1, group: "安全健康", ans: "是",
|
set: (v) => emit("update:modelValue", v),
|
||||||
q: "是否存在任何可能导致安全问题的化学反应?检查化学反应禁忌表。",
|
});
|
||||||
risk: "反应温度从80℃升至90℃,反应速率加快,放热更集中,可能引发反应失控或副反应,导致超温超压。",
|
const setAns = (item: any, a: string) => {
|
||||||
measure: "1. 进行反应量热试验或查阅反应热数据,确认90℃下的放热速率和绝热温升;2. 评估现有冷却系统能力是否满足最大放热需求;3. 更新化学反应风险评估报告;4. 验证反应釜搅拌、冷却系统完好。",
|
riskChecks.value = riskChecks.value.map((x) => (x.id === item.id ? { ...x, ans: a } : x));
|
||||||
},
|
};
|
||||||
{ id: 2, group: "安全健康", ans: "否", q: "是否存在任何易燃易爆化学物质或粉尘?" },
|
|
||||||
{
|
|
||||||
id: 3, group: "安全健康", ans: "是",
|
|
||||||
q: "是否存在任何有危险的原材料流速、成分或温度条件变更?",
|
|
||||||
risk: "反应温度条件从80±2℃变更至90±2℃,接近设备设计温度上限100℃,可能造成副反应增加或杂质生成。",
|
|
||||||
measure: "1. 通过小试实验验证90℃下的副反应种类及含量,确保产品杂质合格;2. 确认原料A和B在此温度下的稳定性;3. 将温度控制精度要求写入操作参数表。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4, group: "安全健康", ans: "是",
|
|
||||||
q: "是否会因加热、冷却或精确的温度控制失控而导致安全问题?",
|
|
||||||
risk: "温度控制失效(如加热阀门卡死、冷却中断)可能使实际温度超过100℃,导致设备超温、压力升高或反应失控。",
|
|
||||||
measure: "1. 调试并确认DCS温度高报警(95℃)和高高报警联锁(100℃)功能正常;2. 执行联锁回路功能测试(温度变送器、DCS卡件、电磁阀、切断阀);3. 确认加热介质切断阀为故障安全型。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5, group: "安全健康", ans: "是",
|
|
||||||
q: "是否具有为安全操作所必需的仪表、控制、紧急制动、开关或报警?",
|
|
||||||
risk: "变更后温度报警值上调,现有温度仪表量程可能不足或精度不够,影响安全操作。",
|
|
||||||
measure: "1. 检查温度变送器量程是否覆盖0~100℃,精度是否优于0.5级;2. 确认DCS报警值设定正确且已组态;3. 在DCS画面中明显标注新报警值。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6, group: "工艺与操作", ans: "是",
|
|
||||||
q: "变更后是否需要对操作人员进行专门培训?",
|
|
||||||
risk: "新工况(90℃ / 4小时)与原操作习惯差异较大,误操作可能引发超温或产品质量波动。",
|
|
||||||
measure: "1. 修订操作规程并组织专项培训考核;2. 投用首周实行双人复核操作;3. 关键参数调整权限上收至班长。",
|
|
||||||
},
|
|
||||||
{ id: 7, group: "工艺与操作", ans: "否", q: "是否涉及应急预案或消防设施的调整?" },
|
|
||||||
];
|
|
||||||
// 说明:本页面暂无「AI 预填检查表」交互按钮,检查项为静态示例数据,保持现状;
|
|
||||||
// 如需接入可调用 aiRiskCheck(content, rows)(src/service/api/ai.ts,耗时约 1~3 分钟,需处理 42901 限流重试)。
|
|
||||||
const pluginDone = ref<Record<string, number>>({});
|
const pluginDone = ref<Record<string, number>>({});
|
||||||
const riskChecks = ref<any[]>(RISK_CHECKS_INIT.map((x) => ({ ...x })));
|
|
||||||
const riskGroups = computed(() => [...new Set(riskChecks.value.map((x) => x.group))]);
|
const riskGroups = computed(() => [...new Set(riskChecks.value.map((x) => x.group))]);
|
||||||
const riskFilter = ref<string>("全部");
|
const riskFilter = ref<string>("全部");
|
||||||
const onBack = () => {
|
const onBack = () => {
|
||||||
@@ -67,6 +42,12 @@ const save = () => {
|
|||||||
<NButton size="small" ghost type="primary" class="text-xs" @click="save">保存</NButton>
|
<NButton size="small" ghost type="primary" class="text-xs" @click="save">保存</NButton>
|
||||||
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">变更风险检查表(通用)</span>
|
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">变更风险检查表(通用)</span>
|
||||||
<NTag size="small" type="info">插件</NTag>
|
<NTag size="small" type="info">插件</NTag>
|
||||||
|
<NTag v-if="loading" size="small" type="warning">
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" />
|
||||||
|
</template>
|
||||||
|
AI 生成中…(整表约 1~3 分钟)
|
||||||
|
</NTag>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<NRadioGroup v-model:value="riskFilter" name="radiogroup">
|
<NRadioGroup v-model:value="riskFilter" name="radiogroup">
|
||||||
<NRadio v-for="song in [{label:'全部',value:'全部'},{label:'仅看「是」',value:'是'}]" :key="song.value" :value="song.value">
|
<NRadio v-for="song in [{label:'全部',value:'全部'},{label:'仅看「是」',value:'是'}]" :key="song.value" :value="song.value">
|
||||||
@@ -79,6 +60,9 @@ const save = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[11px] text-slate-400">逐项判定「是 / 否 / 不涉及」;判定为「是」的检查项输出风险描述与管控措施,完成后点「返回工具列表」回显完成项数</p>
|
<p class="text-[11px] text-slate-400">逐项判定「是 / 否 / 不涉及」;判定为「是」的检查项输出风险描述与管控措施,完成后点「返回工具列表」回显完成项数</p>
|
||||||
|
<div v-if="!riskChecks.length && !loading" class="rounded-lg border border-dashed px-4 py-6 text-center text-xs text-slate-400">
|
||||||
|
暂无检查项:由「生成变更申请单」自动预填,或点击页脚「重新生成」由 AI 生成(耗时约 1~3 分钟)
|
||||||
|
</div>
|
||||||
<div v-for="g in riskGroups" :key="g" class="overflow-hidden rounded-lg border">
|
<div v-for="g in riskGroups" :key="g" class="overflow-hidden rounded-lg border">
|
||||||
<div class="bg-slate-100 px-3 py-2 text-sm font-medium text-slate-700">{{ g }}</div>
|
<div class="bg-slate-100 px-3 py-2 text-sm font-medium text-slate-700">{{ g }}</div>
|
||||||
<div v-for="item in riskChecks.filter((x) => x.group === g && (riskFilter === '全部' || x.ans === '是'))" :key="item.id"
|
<div v-for="item in riskChecks.filter((x) => x.group === g && (riskFilter === '全部' || x.ans === '是'))" :key="item.id"
|
||||||
@@ -89,7 +73,7 @@ const save = () => {
|
|||||||
<span v-for="a in ['是', '否', 'NA']" :key="a">
|
<span v-for="a in ['是', '否', 'NA']" :key="a">
|
||||||
<NRadio
|
<NRadio
|
||||||
:checked="item.ans === a"
|
:checked="item.ans === a"
|
||||||
@change="riskChecks = riskChecks.map((x) => (x.id === item.id ? { ...x, ans: a } : x))"
|
@change="setAns(item, a)"
|
||||||
>
|
>
|
||||||
{{ a }}
|
{{ a }}
|
||||||
</NRadio>
|
</NRadio>
|
||||||
|
|||||||
Reference in New Issue
Block a user