本专业高风险页面修改以及删除多余文件
This commit is contained in:
@@ -1,146 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { aiKnowledgeIngest, pollTask, AiError } from '@/service/api/ai';
|
||||
import { docListApi, historyVersionListApi } from '@/service/api/doc';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||
|
||||
const uploadFiles = () => {
|
||||
window.$message?.success('演示:上传资料(限资料管理员 / 各专业专责维护;上传后进入受控评审,发布生效并生成新版本号,全程留痕)')
|
||||
}
|
||||
const KNOWLEDGE_DOCS = [
|
||||
{ name: "PID-R201-03 反应釜工艺流程图(Rev C)", cat: "工艺技术资料", device: "一车间", ver: "Rev C", date: "2026-07-29" },
|
||||
{ name: "聚合岗位操作规程(2026 版)", cat: "工艺技术资料", device: "一车间", ver: "V2026", date: "2026-07-26" },
|
||||
{ name: "M-201A 搅拌电机设备台账", cat: "设备资料", device: "一车间", ver: "V3.2", date: "2026-07-28" },
|
||||
{ name: "环己烷 MSDS(供应商 B)", cat: "化学品资料", device: "原料罐区", ver: "V1.0", date: "2026-07-18" },
|
||||
{ name: "AQ/T 3034 化工企业工艺安全管理实施导则", cat: "法规标准库", device: "全厂", ver: "2013 版", date: "2025-12-01" },
|
||||
{ name: "PSSR 检查表模板(设备类)", cat: "检查表模板库", device: "全厂", ver: "V2.1", date: "2026-05-10" },
|
||||
{ name: "2025 年度典型变更案例汇编", cat: "历史变更案例库", device: "全厂", ver: "2025 版", date: "2026-01-15" },
|
||||
{ name: "HAZOP 分析报告(R-101 温度变更)", cat: "历史变更案例库", device: "一车间", ver: "V1.0", date: "2026-07-26" },
|
||||
];
|
||||
const libHits = computed(() => (keyword.value ? KNOWLEDGE_DOCS.filter((d:any) => d.name.includes(keyword.value) || d.cat.includes(keyword.value)) : []));
|
||||
const preview = (d:any) => {
|
||||
window.$message?.info(`演示:在线预览 ${d.name}`)
|
||||
}
|
||||
const download = (d:any) => {
|
||||
window.$message?.success(`演示:下载 ${d.name}`)
|
||||
}
|
||||
// 已纳入 AI 知识库的条目(name -> true)与摄入中状态
|
||||
const ingested = ref<Record<string, boolean>>({});
|
||||
const ingesting = ref<Record<string, boolean>>({});
|
||||
const call = async (d:any) => {
|
||||
if (ingesting.value[d.name] || ingested.value[d.name]) return;
|
||||
ingesting.value[d.name] = true;
|
||||
try {
|
||||
const { task_id } = await aiKnowledgeIngest({ name: d.name, version: d.ver, category: d.cat });
|
||||
await pollTask(task_id, {});
|
||||
ingested.value[d.name] = true;
|
||||
window.$message?.success(`已将「${d.name}」纳入 AI 知识库(调用记录留痕)`);
|
||||
} catch (e: any) {
|
||||
if (e instanceof AiError) {
|
||||
window.$message?.warning(`AI 知识摄入失败:${e.message},可稍后重试`);
|
||||
} else {
|
||||
window.$message?.warning('AI 知识摄入失败,可稍后重试');
|
||||
}
|
||||
} finally {
|
||||
ingesting.value[d.name] = false;
|
||||
}
|
||||
}
|
||||
interface LibDoc { name: string; ver: string; date: string }
|
||||
interface LibFolder { label: string; docs: LibDoc[] }
|
||||
interface LibGroup { cat: string; folders: LibFolder[] }
|
||||
const LIB_ORGS: { org: string; groups: LibGroup[] }[] = [
|
||||
{ org: "一车间", groups: [
|
||||
{ cat: "工艺", folders: [
|
||||
{ label: "操作规程", docs: [
|
||||
{ name: "聚合岗位操作规程(2026 版)", ver: "V2026", date: "2026-07-26" },
|
||||
{ name: "干燥岗位操作规程(2026 版)", ver: "V2026", date: "2026-06-30" } ]},
|
||||
{ label: "PID 图纸", docs: [
|
||||
{ name: "PID-R201-03 反应釜工艺流程图(Rev C)", ver: "Rev C", date: "2026-07-29" },
|
||||
{ name: "PID-V102-01 罐区工艺流程图(Rev B)", ver: "Rev B", date: "2026-05-12" } ]},
|
||||
{ label: "工艺卡片", docs: [
|
||||
{ name: "R-201 反应釜工艺卡片(夏季工况)", ver: "V2.0", date: "2026-07-01" } ]},
|
||||
]},
|
||||
{ cat: "设备", folders: [
|
||||
{ label: "设备台账", docs: [
|
||||
{ name: "M-201A 搅拌电机设备台账", ver: "V3.2", date: "2026-07-28" },
|
||||
{ name: "P-204 输送泵设备台账", ver: "V2.8", date: "2026-06-15" } ]},
|
||||
{ label: "安全阀台账", docs: [
|
||||
{ name: "一车间安全阀台账(PSV-101 ~ PSV-118)", ver: "V5.1", date: "2026-07-10" } ]},
|
||||
]},
|
||||
{ cat: "仪表", folders: [
|
||||
{ label: "报警台账", docs: [
|
||||
{ name: "一车间 DCS 报警台账(2026H1)", ver: "V4.0", date: "2026-07-20" } ]},
|
||||
{ label: "联锁台账", docs: [
|
||||
{ name: "一车间联锁逻辑台账(SIS-01 ~ SIS-09)", ver: "V3.6", date: "2026-07-22" } ]},
|
||||
]},
|
||||
{ cat: "安全", folders: [
|
||||
{ label: "总图", docs: [{ name: "一车间装置总平面布置图", ver: "Rev D", date: "2026-04-18" } ]},
|
||||
{ label: "现场气体报警仪布置图", docs: [{ name: "一车间可燃 / 有毒气体报警仪布置图", ver: "Rev B", date: "2026-05-28" } ]},
|
||||
{ label: "防爆分区图", docs: [{ name: "一车间爆炸危险区域划分图", ver: "Rev C", date: "2026-03-15" } ]},
|
||||
]},
|
||||
]},
|
||||
{ org: "二车间", groups: [
|
||||
{ cat: "工艺", folders: [
|
||||
{ label: "操作规程", docs: [{ name: "二车间精馏岗位操作规程(2026 版)", ver: "V2026", date: "2026-06-20" } ]},
|
||||
{ label: "PID 图纸", docs: [{ name: "PID-T301-02 精馏塔工艺流程图(Rev A)", ver: "Rev A", date: "2026-03-08" } ]},
|
||||
]},
|
||||
{ cat: "设备", folders: [
|
||||
{ label: "设备台账", docs: [{ name: "P-103 冷冻盐水泵设备台账", ver: "V2.2", date: "2026-05-30" } ]},
|
||||
{ label: "安全阀台账", docs: [{ name: "二车间安全阀台账(PSV-201 ~ PSV-212)", ver: "V4.3", date: "2026-06-11" } ]},
|
||||
]},
|
||||
{ cat: "仪表", folders: [
|
||||
{ label: "报警台账", docs: [{ name: "二车间 DCS 报警台账(2026H1)", ver: "V3.5", date: "2026-07-02" } ]},
|
||||
{ label: "联锁台账", docs: [{ name: "二车间联锁逻辑台账(SIS-11 ~ SIS-16)", ver: "V2.9", date: "2026-06-25" } ]},
|
||||
]},
|
||||
{ cat: "安全", folders: [
|
||||
{ label: "总图", docs: [{ name: "二车间装置总平面布置图", ver: "Rev B", date: "2026-02-14" } ]},
|
||||
{ label: "现场气体报警仪布置图", docs: [{ name: "二车间可燃 / 有毒气体报警仪布置图", ver: "Rev A", date: "2026-04-02" } ]},
|
||||
{ label: "防爆分区图", docs: [{ name: "二车间爆炸危险区域划分图", ver: "Rev B", date: "2026-01-20" } ]},
|
||||
]},
|
||||
]},
|
||||
{ org: "全厂共用", groups: [
|
||||
{ cat: "法规", folders: [
|
||||
{ label: "法规标准库", docs: [{ name: "AQ/T 3034 化工企业工艺安全管理实施导则", ver: "2013 版", date: "2025-12-01" } ]},
|
||||
]},
|
||||
{ cat: "化学品", folders: [
|
||||
{ label: "MSDS", docs: [{ name: "环己烷 MSDS(供应商 B)", ver: "V1.0", date: "2026-07-18" } ]},
|
||||
]},
|
||||
{ cat: "模板", folders: [
|
||||
{ label: "检查表模板", docs: [{ name: "PSSR 检查表模板(设备类)", ver: "V2.1", date: "2026-05-10" } ]},
|
||||
]},
|
||||
{ cat: "案例", folders: [
|
||||
{ label: "历史变更案例", docs: [
|
||||
{ name: "2025 年度典型变更案例汇编", ver: "2025 版", date: "2026-01-15" },
|
||||
{ name: "HAZOP 分析报告(R-101 温度变更)", ver: "V1.0", date: "2026-07-26" } ]},
|
||||
]},
|
||||
]},
|
||||
];
|
||||
const openOrgs = ref<Record<string, boolean>>({ 一车间: true });
|
||||
const libSel = ref({ org: "一车间", cat: "工艺", label: "操作规程" });
|
||||
const libCur = computed(() => docList.value.find((o) => o.org_name === libSel.value.org)?.categories.find((g) => g.category === libSel.value.cat)?.folders.find((f) => f.folder === libSel.value.label));
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const rightLoading = ref<boolean>(false);
|
||||
// 展开节点
|
||||
const openOrgs = ref<Record<string, boolean>>({});
|
||||
// 选中的资料库节点
|
||||
const libSel = ref({ org: "", cat: "", label: "" });
|
||||
// 当前选中的资料库列表
|
||||
const libCur = computed(() => docList.value.find((o:any) => o.org_name === libSel.value.org)?.categories.find((g:any) => g.category === libSel.value.cat)?.folders.find((f:any) => f.folder === libSel.value.label));
|
||||
// 搜索关键词
|
||||
const keyword = ref<string>('');
|
||||
// 资料库树状列表
|
||||
const docList = ref<any[]>([]);
|
||||
// 历史版本列表
|
||||
const historyList = ref<any[]>([]);
|
||||
// 展示数还是列表
|
||||
const showTree = ref<boolean>(true);
|
||||
|
||||
// 在线预览资料
|
||||
const preview = (d:any) => {
|
||||
window.$message?.info(`演示:在线预览 ${d.name}`)
|
||||
}
|
||||
// 下载资料
|
||||
const download = (d:any) => {
|
||||
window.$message?.success(`演示:下载 ${d.name}`)
|
||||
}
|
||||
// 上传资料
|
||||
const uploadFiles = () => {
|
||||
window.$message?.success('演示:上传资料(限资料管理员 / 各专业专责维护;上传后进入受控评审,发布生效并生成新版本号,全程留痕)')
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const searchHandle = () => {
|
||||
if(!keyword.value) {
|
||||
showTree.value = true;
|
||||
return;
|
||||
};
|
||||
showTree.value = false;
|
||||
|
||||
}
|
||||
|
||||
// 获取资料库树状列表
|
||||
const getDocList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await docListApi();
|
||||
if(!error){
|
||||
console.log(data);
|
||||
docList.value = data ?? [];
|
||||
console.log(docList.value);
|
||||
if(docList.value.length){
|
||||
openOrgs.value = {[docList.value[0]?.org_name]:true};
|
||||
libSel.value = {org: docList.value[0]?.org_name, cat: docList.value[0]?.categories[0]?.category, label: docList.value[0]?.categories[0]?.folders[0]?.folder};
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -155,7 +71,7 @@ onMounted(() => {
|
||||
<!-- 我的草稿箱 -->
|
||||
<NSpin :show="loading" size="small">
|
||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
|
||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
|
||||
<NGi span="24 s:24 m:16">
|
||||
<div class="h-100% flex items-center gap-2">
|
||||
<Icon icon="mage:book" class="size-20px" :style="{ color: themeStore.themeColor }" />
|
||||
@@ -164,9 +80,9 @@ onMounted(() => {
|
||||
</NGi>
|
||||
<NGi span="24 s:24 m:8" class="flex items-center justify-end gap-12px">
|
||||
<div class="flex items-center gap-2">
|
||||
<NInput v-model:value="keyword" size="small" type="text" class="w-40 text-xs" clearable placeholder="全文检索…">
|
||||
<NInput v-model:value="keyword" size="small" type="text" class="w-40 text-xs" clearable placeholder="全文检索…" @clear="keyword = ''; searchHandle();">
|
||||
<template #suffix>
|
||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" />
|
||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchHandle" />
|
||||
</template>
|
||||
</NInput>
|
||||
<NButton size="small" type="primary" class="text-xs" @click="uploadFiles">
|
||||
@@ -179,10 +95,10 @@ onMounted(() => {
|
||||
按「组织 → 专业类别 → 资料文件夹」归档具体资料(操作规程 / PID 图纸 / 工艺卡片 / 设备台账 / 安全阀台账 / 报警台账 / 联锁台账 / 总图 / 现场气体报警仪布置图 / 防爆分区图等);资料由指定维护人(资料管理员 / 各专业专责)上传维护,版本受控;「调用」可将资料引用为当前变更的 AI 知识来源,调用记录留痕。
|
||||
</div>
|
||||
<!-- 全文检索:跨文件夹平铺结果 -->
|
||||
<div v-if="keyword">
|
||||
<div class="mb-1.5 text-xs text-slate-400">全文检索「{{ keyword }}」:跨全部文件夹命中 {{ libHits.length }} 份</div>
|
||||
<div v-if="!showTree">
|
||||
<div class="mb-1.5 text-xs text-slate-400">全文检索「{{ keyword }}」:跨全部文件夹命中 {{ historyList.length }} 份</div>
|
||||
<div class="divide-y rounded-lg border bg-white">
|
||||
<div v-for="d in libHits" :key="d.name" class="flex flex-wrap items-center gap-3 px-2 py-2.5">
|
||||
<div v-for="d in historyList" :key="d.name" class="flex flex-wrap items-center gap-3 px-2 py-2.5">
|
||||
<FileText class="h-4 w-4 shrink-0 text-slate-400" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm">{{ d.name }}</div>
|
||||
@@ -196,7 +112,7 @@ onMounted(() => {
|
||||
<button class="text-[#1D4E9C] hover:underline" @click="download(d)">下载</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="libHits.length === 0" class="py-6 text-center text-xs text-slate-400">未找到匹配「{{ keyword }}」的资料</div>
|
||||
<div v-if="historyList.length === 0" class="py-6 text-center text-xs text-slate-400">未找到匹配「{{ keyword }}」的资料</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文件夹树 + 当前文件夹资料 -->
|
||||
@@ -248,10 +164,9 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex shrink-0 gap-3 text-xs">
|
||||
<NButton type="primary" text class="hover:underline text-xs" @click="preview(d)">查看</NButton>
|
||||
<NButton type="primary" text class="hover:underline text-xs">查看历史</NButton>
|
||||
<NButton type="primary" text class="hover:underline text-xs" @click="preview(d)">预览</NButton>
|
||||
<NButton type="primary" text class="hover:underline text-xs" @click="download(d)">下载</NButton>
|
||||
<NTag v-if="ingested[d.name]" size="small" type="success" class="text-[10px]">已纳入 AI 知识库</NTag>
|
||||
<NButton v-else type="primary" text class="hover:underline text-xs" :loading="!!ingesting[d.name]" @click="call(d)">调用</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,54 +1,14 @@
|
||||
<!-- 本专业高风险 -->
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
import Risk from './modules/risk.vue';
|
||||
import History from './modules/history.vue';
|
||||
import Template from './modules/template.vue';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const TABS = [
|
||||
{ k: "highRisk", name: "高风险场景" },
|
||||
{ k: "history", name: "历史分析检索" },
|
||||
{ k: "tpl", name: "方法模板库" },
|
||||
];
|
||||
// 当前tab
|
||||
const currentTab = ref<string>("highRisk");
|
||||
|
||||
// 切换tab
|
||||
const tabChange = async (k: string) => {
|
||||
currentTab.value = k;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical :size="16">
|
||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-2 border px-3 py-2 mb-3"
|
||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||
>
|
||||
<NButton size="small" round v-for="t in TABS" :key="t.k" @click="tabChange(t.k)"
|
||||
class="text-xs"
|
||||
:type="currentTab === t.k ? 'primary' : 'default'"
|
||||
>
|
||||
{{ t.name }}
|
||||
</NButton>
|
||||
</div>
|
||||
<!-- 高风险场景 -->
|
||||
<template v-if="currentTab === 'highRisk'">
|
||||
<Risk />
|
||||
</template>
|
||||
<!-- 历史分析检索 -->
|
||||
<template v-if="currentTab === 'history'">
|
||||
<History />
|
||||
</template>
|
||||
<!-- 方法模板库 -->
|
||||
<template v-if="currentTab === 'tpl'">
|
||||
<Template />
|
||||
</template>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { historyAnalysisListApi } from '@/service/api/highRisk';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
// 查看历史分析检索
|
||||
const viewHistory = (id: string) => {
|
||||
window.$message?.info(`演示:在线查看 ${id} 完整分析记录`)
|
||||
};
|
||||
// 复用到新变更
|
||||
const copyHistory = (id: string) => {
|
||||
window.$message?.success(`演示:${id} 已复用到新变更草稿,分析行已带入待确认`)
|
||||
}
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 搜索关键词
|
||||
const keyword = ref<string>("");
|
||||
// 插件筛选
|
||||
const plugin = ref<string>("");
|
||||
// 插件列表
|
||||
const pluginList = ref<any[]>([
|
||||
{ label: "全部", value: '' },
|
||||
{ label: "风险预分析", value: 'RISK' },
|
||||
{ label: "HAZOP", value: 'HAZOP' },
|
||||
{ label: "FMEA", value: 'FMEA' },
|
||||
{ label: "风险检查表", value: 'RISK_CHECK' },
|
||||
]);
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 插件筛选
|
||||
const pluginChange = (v: string) => {
|
||||
plugin.value = v;
|
||||
getList();
|
||||
}
|
||||
// 重置
|
||||
const reset = () => {
|
||||
keyword.value = '';
|
||||
plugin.value = '';
|
||||
getList();
|
||||
};
|
||||
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await historyAnalysisListApi({
|
||||
kw: keyword.value,
|
||||
method: plugin.value,
|
||||
});
|
||||
if(!error){
|
||||
console.log(data);
|
||||
list.value = data ?? [];
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
})
|
||||
</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="mage:book" class="size-18px" :style="{color:themeStore.themeColor}" />
|
||||
<p class="text-base font-semibold">历史分析记录检索(企业风险知识资产)</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 min-h-28px">
|
||||
<NInput v-model:value="keyword" size="small" class="text-xs !w-200px" clearable placeholder="按单号 / 装置 / 关键词检索…" @clear="keyword = ''; getList()">
|
||||
<template #suffix>
|
||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="getList" />
|
||||
</template>
|
||||
</NInput>
|
||||
<NButton size="small" class="text-sm" @click="reset">
|
||||
<Icon icon="system-uicons:reset" class="size-12px" />重置
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 mb-3 flex flex-wrap items-center gap-1.5">
|
||||
<NButton
|
||||
size="tiny"
|
||||
round
|
||||
v-for="p in pluginList"
|
||||
:key="p.value"
|
||||
:type="plugin === p.value ? 'primary' : 'default'"
|
||||
@click="pluginChange(p.value)"
|
||||
>
|
||||
{{ p.label }}
|
||||
</NButton>
|
||||
<span class="ml-auto self-center text-[11px] text-slate-400">{{ list.length }} 条记录</span>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="space-y-3 px-4 pb-3">
|
||||
<div v-for="h in list" :key="h.id" class="flex flex-wrap items-center gap-3 rounded-lg border px-4 py-2.5">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="font-mono text-[11px] text-slate-400">{{ h.change_no }}</span>
|
||||
<NTag size="small" type="info" class="text-11px">{{ h.method==='RISK'?'风险预分析':h.method==='RISK_CHECK'?'风险检查': h.method }}</NTag>
|
||||
<span class="text-sm text-slate-800">{{ h.title }}</span>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-slate-500">{{ h.org_name }} · {{ h.rows }} 行分析记录 · {{ h?.analysis_time?.slice(0,10) || '' }}</div>
|
||||
</div>
|
||||
<NButton text type="primary" @click="viewHistory(h.id)">
|
||||
<Icon icon="lucide:eye" class="size-16px" />
|
||||
</NButton>
|
||||
<NButton ghost size="small" class="text-xs" @click="copyHistory(h.id)">复用到新变更</NButton>
|
||||
</div>
|
||||
<div v-if="list.length === 0" class="py-6 text-center text-xs text-slate-400">未找到匹配的分析记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll {
|
||||
height: calc(100vh - 305px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -39,7 +39,6 @@ const jumpTo = (r: any) => {
|
||||
};
|
||||
// 查看分析记录
|
||||
const viewAnalysis = (record: any) => {
|
||||
console.log(record);
|
||||
currentRecord.value = record;
|
||||
pluginDrawer.value = true;
|
||||
};
|
||||
@@ -340,7 +339,7 @@ onMounted(async () => {
|
||||
</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?.applicant_name }} · 分析记录 {{ s?.record_count }} 行
|
||||
变更:{{ 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>
|
||||
@@ -396,7 +395,7 @@ onMounted(async () => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll {
|
||||
height: calc(100vh - 368px);
|
||||
height: calc(100vh - 310px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { systemConfigApi } from '@/service/api/system';
|
||||
import { orgListApi } from '@/service/api/user';
|
||||
import { highRiskListApi } from '@/service/api/highRisk';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
const keyword = ref<string>("");
|
||||
const progressDrawer = ref<boolean>(false);
|
||||
const currentTab = ref<string>("highRisk");
|
||||
const range = ref<string>("近一个月");
|
||||
const dept = ref<string>("全部部门");
|
||||
const method = ref<string>("全部");
|
||||
// 当前记录
|
||||
const currentRecord = ref<any>(null);
|
||||
const TABS = [
|
||||
{ k: "highRisk", name: "高风险场景" },
|
||||
{ k: "history", name: "历史分析检索" },
|
||||
{ k: "tpl", name: "方法模板库" },
|
||||
];
|
||||
const RISK_RANGES = ["近一个月", "近三个月", "近半年", "全部"];
|
||||
const departments = ref<any[]>([
|
||||
{ id: 1, name: "全部部门" },
|
||||
{ id: 2, name: "一车间" },
|
||||
{ id: 3, name: "公用工程车间" },
|
||||
{ id: 4, name: "供应部" },
|
||||
{ id: 5, name: "三车间" },
|
||||
]);
|
||||
const list = ref<any[]>([]);
|
||||
const highRiskList = [
|
||||
{
|
||||
cid: "MOC-2026-R01-0036", item: "反应飞温", scene: "温度上限提高至 82℃ 后飞温反应加速,联锁动作前安全裕量收窄",
|
||||
method: "HAZOP", residual: "中", dept: "一车间", adate: "2026-08-02", owner: "王海燕",
|
||||
title: '聚合反应温度控制上限调整(78℃→82℃)', status: '审批中',
|
||||
info: 'MOC-2026-R01-0036 聚合反应温度控制上限调整(78℃→82℃)', count: 3,
|
||||
hazopRows: [
|
||||
{ node: "节点1 · 聚合釜 R-101", dev: "温度过高", cause: "温控回路失效或冷却水中断;82℃ 新上限更接近溶剂回流温度,超温溜温概率上升", cons: "反应溜温冲料,釜压骤升,物料喷溅造成灼烫与环境污染", L: 3, S: 4, safety: "DCS 温度高报 82℃、高高报及联锁 85℃(维持安全边界不变);紧急冷却有效", suggest: "投用前完成联锁测试并留存记录;投用首周每班复核温度趋势" },
|
||||
{ node: "节点1 · 聚合釜 R-101", dev: "温度过高", cause: "未知杂质含量升高(0.05%→0.12%),热稳定性尚未定性,长期累积加速釜壁结垢", cons: "副反应加剧,可能引发二次分解放热,搅拌负荷异常", L: 2, S: 4, safety: "小试热稳定性筛查;安全阀泄放能力不受本次调整影响,仍然有效", suggest: "将杂质含量纳入日常分析计划(每周 1 次,连续 8 周)" },
|
||||
{ node: "节点2 · DCS 温控与联锁", dev: "报警/联锁值设置不当", cause: "上限调整后报警值与联锁值未同步复核整定", cons: "报警提前量不足,操作响应时间被压缩", L: 2, S: 4, safety: "修改执行申请-复核双人确认;修改清单与变更单逐项核对", suggest: "联锁测试全覆盖并留存记录;修改期间工艺加强监控" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cid: "MOC-2026-R01-0035", item: "动火与临时用电燃爆", scene: "电机更换动火与临时用电作业引发可燃气体燃爆",
|
||||
method: "JSA", residual: "中", dept: "一车间", adate: "2026-07-30", owner: "王仪表",
|
||||
title: '反应釜搅拌器电机更换(55kW→75kW 防爆电机)', status: '审批中',
|
||||
info: 'MOC-2026-R01-0035 R-201 反应釜搅拌器电机更换(55kW→75kW 防爆电机)', count: 3,
|
||||
jsaRows: [
|
||||
{ item: "动火作业燃爆", scene: "步骤2:旧电机拆除与新电机安装动火,可燃蒸气聚集遇火源燃爆", inherent: "高", existing: "作业票证办理;动火点可燃气体检测合格;消防器材到位", suggest: "动火点下方设接火盘与防火毯,专人监护并每 30 分钟复测", residual: "中" },
|
||||
{ item: "临时用电触电", scene: "步骤1:临时电缆敷设与配电箱接线,绝缘破损导致触电", inherent: "中", existing: "临时用电票证;漏保校验合格", suggest: "电缆架空敷设并挂牌,每日开工前绝缘检查", residual: "低" },
|
||||
{ item: "起重吊装伤害", scene: "步骤3:75kW 电机吊装就位,吊具失效或指挥失误导致起重伤害", inherent: "中", existing: "吊具检验合格;起重指挥持证", suggest: "吊装区设警戒线,禁止交叉作业", residual: "低" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cid: null, changeTitle: "MOC-2026-R01-0048 R-101 温度联锁修改", item: "VOCs 直排超标", scene: "活性炭吸附饱和穿透,VOCs 直排大气超标",
|
||||
method: "JSA", residual: "高", dept: "一车间", adate: "2026-07-31", owner: "王仪表",
|
||||
title: '温度联锁修改', status: '审批中',
|
||||
info: 'MOC-2026-R01-0048 R-101 温度联锁修改', count: 2,
|
||||
jsaRows: [
|
||||
{ item: "吸附饱和穿透", scene: "步骤3:活性炭吸附床运行后期,吸附饱和穿透导致 VOCs 直排大气", inherent: "高", existing: "出口 VOCs 在线监测报警;按周期更换活性炭", suggest: "增设穿透点压差监测与双床切换程序;更换周期由 6 个月缩短至 4 个月", residual: "中" },
|
||||
{ item: "联锁参数误改", scene: "步骤3:DCS 参数修改与联锁测试,参数误改导致报警/联锁失效", inherent: "高", existing: "参数修改执行申请-复核双人确认;修改清单与变更单逐项核对", suggest: "联锁测试全覆盖并留存记录;修改期间工艺加强监控", residual: "中" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cid: "MOC-2026-R02-0041", item: "超压与窒息", scene: "氮气减压阀组失效导致下游设备超压、作业人员窒息",
|
||||
method: "检查表", residual: "低", dept: "公用工程车间", adate: "2026-07-29", owner: "周设备",
|
||||
title: '公用工程氮气减压阀组改造', status: '审批中',
|
||||
info: 'MOC-2026-R02-0041 公用工程氮气减压阀组改造', count: 2,
|
||||
jsaRows: [
|
||||
{ item: "下游设备超压", scene: "减压阀失效全开,高压氮气直入低压管网导致下游设备超压", inherent: "高", existing: "下游设安全阀;管网压力高报", suggest: "减压阀双阀串联并定期校验;安全阀起跳压力复核", residual: "低" },
|
||||
{ item: "人员窒息", scene: "阀组间通风不良,氮气泄漏积聚导致氧含量不足", inherent: "中", existing: "阀组间强制通风;进入前测氧", suggest: "增设固定式氧含量报警仪并联动风机", residual: "低" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cid: "MOC-2026-R01-0039", item: "超压保护裕量降低", scene: "安全阀起跳压力临时上调期间,V-102 超压保护裕量降低",
|
||||
method: "AI 预分析", residual: "中", dept: "一车间", adate: "2026-07-15", owner: "王海燕",
|
||||
title: '紧急变更:V-102 安全阀起跳压力临时调整(补办手续)', status: '审批中',
|
||||
info: 'MOC-2026-R01-0039 紧急变更:V-102 安全阀起跳压力临时调整(补办手续)', count: 2,
|
||||
jsaRows: [
|
||||
{ item: "超压保护裕量降低", scene: "起跳压力由 1.0MPa 临时上调至 1.15MPa,V-102 超压工况下保护动作延迟", inherent: "高", existing: "临时期限 30 天;操作压力监控加强至每 2 小时记录", suggest: "恢复前完成新安全阀校验更换;临时期间压力联锁值同步下调验证", residual: "中" },
|
||||
{ item: "紧急变更手续补办遗漏", scene: "紧急变更先行实施,后补手续存在资料缺项风险", inherent: "中", existing: "紧急变更 48 小时内补办审批", suggest: "资料归档清单逐项核对,安全部门复核", residual: "低" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cid: "MOC-2026-R03-0040", item: "原料杂质副反应", scene: "新供应商环己烷杂质谱变化引发副反应,影响产品色相与收率",
|
||||
method: "HAZOP", residual: "中", dept: "供应部", adate: "2026-07-18", owner: "陈质量",
|
||||
title: '原料环己烷供应商变更(新增 B 供应商)', status: '审批中',
|
||||
info: 'MOC-2026-R03-0040 原料环己烷供应商变更(新增 B 供应商)', count: 2,
|
||||
hazopRows: [
|
||||
{ node: "节点1 · 原料接收与储存", dev: "杂质含量高", cause: "B 供应商环己烷杂质谱(苯/硫)高于原供应商,进厂检验项目未覆盖", cons: "副反应加剧,产品色相超标、收率下降,结垢加速", L: 3, S: 3, safety: "进厂检验按原指标执行;供应商 COA 随货同行", suggest: "检验项目增加杂质谱分析;首批三批加严检验" },
|
||||
{ node: "节点2 · 聚合反应", dev: "副反应加剧", cause: "微量苯参与链转移,分子量分布变宽", cons: "产品性能波动,下游加工投诉", L: 2, S: 3, safety: "聚合配方留有调节余量", suggest: "建立 B 供应商原料的配方微调作业指导" },
|
||||
],
|
||||
},
|
||||
{
|
||||
cid: "MOC-2026-R03-0042", item: "回路切换失误", scene: "自动化改造调试期间控制回路切换失误,造成装置非计划停车",
|
||||
method: "JSA", residual: "中", dept: "三车间", adate: "2026-07-31", owner: "王仪表",
|
||||
title: '三车间自动化和安全隐患整改项目变更', status: '审批中',
|
||||
info: 'MOC-2026-R03-0042 三车间自动化和安全隐患整改项目变更', count: 2,
|
||||
jsaRows: [
|
||||
{ item: "回路切换失误", scene: "步骤4:新旧控制系统切换,回路误切导致装置非计划停车", inherent: "高", existing: "切换方案审批;切换前模拟演练", suggest: "切换双人复核逐项签字;保留一键回切手段 72 小时", residual: "中" },
|
||||
{ item: "静电损伤卡件", scene: "步骤2:卡件插拔作业静电防护不到位,DCS 卡件损坏", inherent: "中", existing: "佩戴防静电手环", suggest: "卡件备件现场备妥,作业区铺设防静电垫", residual: "低" },
|
||||
],
|
||||
},
|
||||
];
|
||||
const historyList = [
|
||||
{ id: "HAZOP-2025-018", change: "MOC-2025-R01-0023 常压炉燃料气切断阀改造", method: "HAZOP", unit: "01-常减压装置", scope: "燃料气系统节点", rows: 12, date: "2025-10-20" },
|
||||
{ id: "HAZOP-2024-006", change: "MOC-2024-R01-0003 减一线换热器 E-108 芯子更换", method: "HAZOP", unit: "01-常减压装置", scope: "换热系统节点", rows: 8, date: "2024-03-15" },
|
||||
{ id: "JSA-2025-041", change: "MOC-2025-R02-0014 再生器旋风分离器更换", method: "JSA", unit: "02-催化裂化装置", scope: "受限空间作业", rows: 6, date: "2025-05-08" },
|
||||
{ id: "JSA-2026-012", change: "MOC-2026-R01-0035 搅拌电机更换", method: "JSA", unit: "01-常减压装置", scope: "检维修作业", rows: 5, date: "2026-07-29" },
|
||||
{ id: "SCL-2025-033", change: "MOC-2025-R02-0021 气压机出口压力联锁逻辑修改", method: "检查表", unit: "02-催化裂化装置", scope: "联锁系统", rows: 14, date: "2025-09-18" },
|
||||
{ id: "AI-2026-008", change: "MOC-2026-R03-0008 聚合釜搅拌器密封形式变更", method: "AI 预分析", unit: "03-聚合装置", scope: "—", rows: 4, date: "2026-05-06" },
|
||||
];
|
||||
const templateList = [
|
||||
{ name: "HAZOP 节点划分模板", desc: "含偏差引导词库(过高/过低/无/反向等 18 个)与节点划分示例", tag: "HAZOP" },
|
||||
{ name: "JSA 作业步骤分析模板", desc: "作业步骤分解 → 危害识别 → 控制措施三段式,含检维修示例", tag: "JSA" },
|
||||
{ name: "安全检查表(SCL)模板库", desc: "按设备类型 28 套(机泵 / 换热器 / 塔器 / 储罐 / 电气 / 仪表)", tag: "检查表" },
|
||||
{ name: "风险矩阵 5×5 分级定义", desc: "可能性 × 严重性分级标准(公司 Q/SH 0600),AI 评级引用此定义", tag: "通用" },
|
||||
];
|
||||
const isHazop = computed(() => !!currentRecord.value?.hazopRows);
|
||||
const th = "border-b bg-slate-50 px-3 py-2 text-left text-[11px] font-semibold text-slate-500";
|
||||
const td = "border-b px-3 py-2 align-top text-xs leading-relaxed text-slate-700";
|
||||
|
||||
const hazopNodes = computed(() => {
|
||||
const scene = currentRecord.value;
|
||||
if (!scene?.hazopRows) return [] as { node: string; dev: string; n: number }[];
|
||||
const nodes = Array.from(new Map(scene.hazopRows.map((r:any) => [`${r.node}|${r.dev}`, { node: r.node, dev: r.dev, n: 0 }])).values());
|
||||
scene.hazopRows.forEach((r:any) => { const k:any = nodes.find((n:any) => n.node === r.node && n.dev === r.dev); if (k) k.n += 1; });
|
||||
return nodes;
|
||||
});
|
||||
const sel = ref(0);
|
||||
const selRows = computed(() => {
|
||||
const scene = currentRecord.value;
|
||||
if (!scene?.hazopRows) return [] as any;
|
||||
const n:any = hazopNodes.value[sel.value];
|
||||
return scene.hazopRows.filter((r:any) => r.node === n?.node && r.dev === n?.dev);
|
||||
});
|
||||
// HAZOP 风险矩阵:RR = L × S
|
||||
const rrTag = (L: number, S: number) => {
|
||||
const v = L * S;
|
||||
return v >= 12 ? { label: `较大 (${v})`, cls: "error" }
|
||||
: v >= 8 ? { label: `一般 (${v})`, cls: "warning" }
|
||||
: { label: `低 (${v})`, cls: "success" };
|
||||
};
|
||||
|
||||
// 高风险场景查看变更
|
||||
const openSceneChange = (record: any) => {
|
||||
routerPushByKey('details_index', { query: { params: JSON.stringify(record) } })
|
||||
};
|
||||
// 查看分析记录
|
||||
const viewAnalysis = (record: any) => {
|
||||
currentRecord.value = record;
|
||||
progressDrawer.value = true;
|
||||
};
|
||||
// 查看历史分析检索
|
||||
const viewHistory = (id: string) => {
|
||||
window.$message?.info(`演示:在线查看 ${id} 完整分析记录`)
|
||||
};
|
||||
// 复用到新变更
|
||||
const copyHistory = (id: string) => {
|
||||
window.$message?.success(`演示:${id} 已复用到新变更草稿,分析行已带入待确认`)
|
||||
}
|
||||
// 预览模板
|
||||
const previewTemplate = (name: string) => {
|
||||
window.$message?.info(`演示:在线预览「${name}」`)
|
||||
}
|
||||
// 下载模板
|
||||
const downloadTemplate = (name: string) => {
|
||||
window.$message?.success(`演示:下载「${name}」(Word)`)
|
||||
}
|
||||
// 导出
|
||||
const exportFile = () => {
|
||||
window.$message?.success('分析记录表已导出(Excel)');
|
||||
}
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 组织树
|
||||
const orgTree = ref<any[]>([]);
|
||||
// 筛选条件
|
||||
const formInfo = ref<any>({
|
||||
kw: '',
|
||||
org_id: 1,
|
||||
from: null,
|
||||
to: null,
|
||||
});
|
||||
const pagination = ref<{page: number, pageSize: number, itemCount: number}>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
itemCount: 0,
|
||||
})
|
||||
// 变更类型列表
|
||||
const typeList = ref<any[]>([{ label: "全部", value: null }]);
|
||||
// 变更等级列表
|
||||
const levelList = ref<any[]>([{ label: "全部", value: null }]);
|
||||
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await highRiskListApi({
|
||||
kw: formInfo.value.kw,
|
||||
org_id: formInfo.value.org_id,
|
||||
from: formInfo.value.from,
|
||||
to: 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 getConfig = async () => {
|
||||
const {data,error} = await systemConfigApi();
|
||||
if(!error){
|
||||
// 解析 变更类型设置
|
||||
const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type');
|
||||
if (changeTypeGroup) {
|
||||
typeList.value = typeList.value.concat(changeTypeGroup.items);
|
||||
}
|
||||
// 解析 变更等级设置
|
||||
const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level');
|
||||
if (changeLevelGroup) {
|
||||
levelList.value = levelList.value.concat(changeLevelGroup.items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 获取组织列表
|
||||
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;
|
||||
}
|
||||
|
||||
// 切换tab
|
||||
const tabChange = async (k: string) => {
|
||||
list.value = []
|
||||
currentTab.value = k;
|
||||
loading.value = true;
|
||||
if(k === 'highRisk'){
|
||||
await getConfig();
|
||||
getOrgList();
|
||||
}
|
||||
if(k === 'history'){
|
||||
setTimeout(() => {
|
||||
list.value = historyList;
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
}
|
||||
if(k === 'tpl'){
|
||||
setTimeout(() => {
|
||||
list.value = templateList;
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tabChange('tpl');
|
||||
})
|
||||
</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="akar-icons:file" class="size-16px" :style="{color:themeStore.themeColor}" />
|
||||
<p class="text-base font-semibold">方法模板库(AI 风险预分析的知识来源之一)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="space-y-3 px-4 pb-3">
|
||||
<div class="grid gap-3 grid-cols-2">
|
||||
<div v-for="t in list" :key="t.name" class="rounded-lg border p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-slate-800">{{ t.name }}</span>
|
||||
<NTag size="small" type="info">{{ t.tag }}</NTag>
|
||||
</div>
|
||||
<p class="mt-1.5 text-xs leading-relaxed text-slate-500">{{ t.desc }}</p>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<NButton ghost size="small" class="text-xs" @click="previewTemplate(t.name)">查看</NButton>
|
||||
<NButton ghost size="small" class="text-xs" @click="downloadTemplate(t.name)">
|
||||
<Icon icon="material-symbols:download" class="size-14px" />下载
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="list.length === 0" class="py-6 text-center text-xs text-slate-400">未找到匹配的方法模板库</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll {
|
||||
height: calc(100vh - 267px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user