From 7d05236399a858b229b8d5dc89b0e4dcb8cdb7f3 Mon Sep 17 00:00:00 2001 From: bestlee Date: Fri, 4 Sep 2026 11:24:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E8=B5=B7=E5=8F=98=E6=9B=B4=EF=BC=8C?= =?UTF-8?q?=E5=BA=95=E9=83=A8=E5=B0=81=E8=A3=85=E9=80=9A=E7=94=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enum/index.ts | 3 +- src/service/api/change.ts | 23 +- src/service/api/system.ts | 17 + src/store/modules/change/index.ts | 47 + src/typings/api/change.d.ts | 40 + src/views/my/initiateChange/index.vue | 1354 ++--------------- .../initiateChange/modules/approvalDrawer.vue | 77 + .../my/initiateChange/modules/changeApply.vue | 615 ++++---- .../my/initiateChange/modules/changePre.vue | 253 ++- .../initiateChange/modules/changeTraining.vue | 56 + .../initiateChange/modules/closeConfirm.vue | 281 ++++ .../my/initiateChange/modules/evaluation.vue | 247 +++ .../my/initiateChange/modules/footer.vue | 259 ++++ src/views/my/initiateChange/modules/pssr.vue | 246 +++ .../initiateChange/modules/riskAnalysis.vue | 335 ++++ src/views/systemManage/setting/index.vue | 12 +- src/views/systemManage/template/index.vue | 2 +- src/views/systemManage/user/index.vue | 1 + 18 files changed, 2260 insertions(+), 1608 deletions(-) create mode 100644 src/store/modules/change/index.ts create mode 100644 src/typings/api/change.d.ts create mode 100644 src/views/my/initiateChange/modules/approvalDrawer.vue create mode 100644 src/views/my/initiateChange/modules/changeTraining.vue create mode 100644 src/views/my/initiateChange/modules/closeConfirm.vue create mode 100644 src/views/my/initiateChange/modules/evaluation.vue create mode 100644 src/views/my/initiateChange/modules/footer.vue create mode 100644 src/views/my/initiateChange/modules/pssr.vue create mode 100644 src/views/my/initiateChange/modules/riskAnalysis.vue diff --git a/src/enum/index.ts b/src/enum/index.ts index 2739b3a..b4f5289 100644 --- a/src/enum/index.ts +++ b/src/enum/index.ts @@ -3,5 +3,6 @@ export enum SetupStoreId { Theme = 'theme-store', Auth = 'auth-store', Route = 'route-store', - Tab = 'tab-store' + Tab = 'tab-store', + Change = 'change-store', } diff --git a/src/service/api/change.ts b/src/service/api/change.ts index 9ae91f0..ac6be21 100644 --- a/src/service/api/change.ts +++ b/src/service/api/change.ts @@ -53,11 +53,11 @@ export function applicationApi(id: number) { * */ export function applicationSaveApi(id: number, params: { - change_type: number, - change_level: number, - duration_type: number, - restore_deadline: string, - org_id: number, + change_type: number | null, + change_level: any, + duration_type: number | null, + restore_deadline: number | null, + org_id: number | null, urgent: number, purposes: string[], effect: string, @@ -67,7 +67,7 @@ export function applicationSaveApi(id: number, params: { update_docs: string[], disciplines: string[], manage_dept: string, - plan_use_date: string, + plan_use_date: number | null, risk_tools: string[], }) { return request({ @@ -76,3 +76,14 @@ export function applicationSaveApi(id: number, params: { data: params }); } + +// 变更培训内容保存 +export function trainingSaveApi(id: number, params: { + content: string, +}) { + return request({ + url: `/api/changes/${id}/train/content`, + method: 'put', + data: params + }); +} diff --git a/src/service/api/system.ts b/src/service/api/system.ts index f4f3048..ccaa3ee 100644 --- a/src/service/api/system.ts +++ b/src/service/api/system.ts @@ -77,6 +77,23 @@ export function deleteTemplateApi(id:number) { }); } +// 获取审批流程(按三键匹配模板) +export function getFlowApi(data:{category:number | null,level:number | null,duration:number | null}) { + return request({ + url: '/api/approval-templates/match', + method: 'get', + params: data, + }); +} +// 本单审批流程·审批人设置 +export function setApproverApi(id:number,data:any) { + return request({ + url: `/api/changes/${id}/approval-flow`, + method: 'put', + data, + }); +} + // 获取风险矩阵配置 export function matrixConfigApi(bizType:string) { return request({ diff --git a/src/store/modules/change/index.ts b/src/store/modules/change/index.ts new file mode 100644 index 0000000..d1e57d3 --- /dev/null +++ b/src/store/modules/change/index.ts @@ -0,0 +1,47 @@ +import { computed, ref } from 'vue'; +import { defineStore } from 'pinia'; +import { SetupStoreId } from '@/enum'; + +export const useChangeStore = defineStore(SetupStoreId.Change, () => { + const stepsInfo = ref([]); + const flowInfo = ref([]); + // 获取选中模板信息 + const getStepsInfo = computed(() => stepsInfo.value); + // 获取审批链信息 + const getFlowInfo = computed(() => flowInfo.value); + + // 更新选中模板信息 + function setStepsInfo(data: Api.Change.StepInfo[]) { + stepsInfo.value = data; + setFlowInfo(data); + } + + function getSelectedMembers(steps: Api.Change.StepInfo[]) { + return steps.map(step => { + // 先找 is_sign === 1 的成员 + const signed = step.members.find((m: any) => m.is_sign === 1); + // 若没有则取第一个,若成员列表为空则返回 null + const selected = signed || (step.members.length > 0 ? step.members[0] : null); + return { + stepId: step.id, + name: step.name, + countersign: step.countersign, // 添加会签标识 + selectedMember: selected? [selected] : [], // 选中的成员对象 + }; + }); + } + // 更新审批链信息 + function setFlowInfo(data: Api.Change.StepInfo[]) { + const sortedSteps = [...data || []].sort((a, b) => a.step_order - b.step_order) + flowInfo.value = getSelectedMembers(sortedSteps); + } + function updateFlowInfo(data: Api.Change.FlowInfo[]) { + flowInfo.value = data; + } + return { + getStepsInfo, + setStepsInfo, + getFlowInfo, + updateFlowInfo + } +}); \ No newline at end of file diff --git a/src/typings/api/change.d.ts b/src/typings/api/change.d.ts new file mode 100644 index 0000000..17d2994 --- /dev/null +++ b/src/typings/api/change.d.ts @@ -0,0 +1,40 @@ +declare namespace Api { + /** + * namespace Change + * + * backend api module: "change" + */ + namespace Change { + interface MemberInfo { + id: number; + is_sign: number; + org_id: number; + org_name: string; + specialty: string; + user_id: number; + user_name: string; + } + interface StepInfo { + id: number; + countersign: boolean; + name: string; + step_order: number; + members: MemberInfo[]; + } + interface TemplateInfo { + id: number; + category: number; + created_at?: string; + duration: number; + level: number; + steps: StepInfo[]; + updated_at?: string; + } + interface FlowInfo { + stepId: number; + countersign: boolean; + name: string; + selectedMember: MemberInfo[]; + } + } +} \ No newline at end of file diff --git a/src/views/my/initiateChange/index.vue b/src/views/my/initiateChange/index.vue index a1b8b49..4ae80a6 100644 --- a/src/views/my/initiateChange/index.vue +++ b/src/views/my/initiateChange/index.vue @@ -2,9 +2,6 @@ import { computed, onMounted, ref } from 'vue'; import { Icon } from '@iconify/vue' import { useThemeStore } from '@/store/modules/theme'; -import { - CHANGES, type ChangeLevel, type ChangeOrder, type ChangeStatus, type ChangeType, -} from "@/typings/model"; import { getColorWithOpacity } from "@/utils/common"; import { AiError, @@ -12,456 +9,53 @@ import { aiRiskOrganize, aiRiskPreAnalysis, } from "@/service/api/ai"; -import { precheckSaveApi, applicationSaveApi } from "@/service/api/change"; -import Ed from "./modules/Ed.vue"; -import Hazop from "./modules/hazop.vue"; -import Jsa from "./modules/jsa.vue"; -import Scl from "./modules/scl.vue"; -import RiskCheck from "./modules/riskCheck.vue"; import { localStg } from '@/utils/storage'; import { queryUnsavedApi } from '@/service/api/change'; +import { systemConfigApi, templateListApi, } from '@/service/api/system'; +import { orgListApi } from '@/service/api/user'; import ChangePre from "./modules/changePre.vue"; import ChangeApply from "./modules/changeApply.vue"; - +import ChangeTraining from "./modules/changeTraining.vue"; +import RiskAnalysis from "./modules/riskAnalysis.vue"; +import Pssr from "./modules/pssr.vue"; +import Evaluation from "./modules/evaluation.vue"; +import CloseConfirm from "./modules/closeConfirm.vue"; const themeStore = useThemeStore(); -interface ApplyForm { - name: string; - dept: string; - mainChanges: string[]; - related: string; - purposes: string[]; - effect: string; - type: string; - level: string; - duration: string; - urgent: boolean; - date: any; - restoreDate: any; -} -const form = ref({ - name: "", dept: "", mainChanges: [""], related: "", purposes: [], effect: "", - type: "", level: "", duration: "", urgent: false, date: null, restoreDate: null, -}); - -const blockedTabs = ref([]); - -// ---------- 变更预识别 ---------- -const desc = ref( - "某缩合反应现在控在80℃要反应8小时,车间反馈产能跟不上。我们小试做了几组,90℃下4小时转化率就能到98%,但有个未知杂质会从0.05%涨到0.12%。这个杂质结构还没定,不过不是基因毒性警示结构。想申请把温度提到90℃,同时把反应时间缩到4小时。", -); -const rows = ref(null); -// AI 调用失败统一提示:不阻塞页面、不清空已有数据,保留手动编辑能力(P1) -const aiFail = (e: any) => { - if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`); - else window.$message?.warning("AI 分析失败,可手动填写"); -}; - -const editing = ref(null); - -// ---------- 生成变更申请单 ---------- -const materialsText = ref(""); -const updateDocs = ref([]); -const disciplines = ref([]); -const tools = ref(["HAZOP","JSA", "SCL", "RISK_CHECK"]); -const UPDATE_DOC_OPTIONS = ['PID图纸', '操作规程', '总图', '设备台账', '工艺卡片', '联锁台账', '应急预案']; -const DISCIPLINE_OPTIONS = ['技术', '仪表', '电气', '环保', '质量']; - -// ---------- 审批流程设置 ---------- -const approvers = ref>({ - 部门审核: "李主任(一车间主任)", - 最终批准: "李主任(部门负责人 · 固定)", -}); -const signers = ref>({}); -let demoSeq = 43; -function nextChangeId() { - return `MOC-2026-R01-${String(demoSeq++).padStart(4, "0")}`; -} -const today = () => new Date().toISOString().slice(0, 10); -interface LedgerRow { - id: string; - changeId?: string; // 关联 CHANGES,可打开完整详情 - title: string; - type: ChangeType; - level: ChangeLevel; - duration: "永久" | "临时"; - urgent?: boolean; - applyTime: string; // 申请时间 - dept: string; // 申请部门 - applicant: string; // 申请人 - planUse: string; // 计划投用时间 - status: ChangeStatus; - statusDetail: string; // 当前环节说明 - statusTime: string; // 状态更新时间 - overdueDays?: number; - approveTime?: string; // 审批通过时间 - useTime?: string; // 实际投用时间 - handlers: { name: string; done: boolean }[]; // 接班人(当前待办/已办) -} -const LEDGER_ROWS: LedgerRow[] = [ - { id: "MOC-2026-R01-0035", changeId: "MOC-2026-R01-0035", title: "R-201 反应釜搅拌器电机更换(55kW→75kW 防爆电机)", type: "设备", level: "重要", duration: "永久", applyTime: "2026-07-28 09:12", dept: "一车间", applicant: "张工艺", planUse: "2026-08-10", status: "审批中", statusDetail: "专业会签中(周设备、王仪表待签)", statusTime: "2026-07-30 14:20", handlers: [{ name: "王仪表", done: true }, { name: "周设备", done: false }, { name: "王仪表", done: false }] }, - { id: "MOC-2026-R01-0036", changeId: "MOC-2026-R01-0036", title: "聚合反应温度控制上限调整(78℃→82℃)", type: "工艺", level: "重要", duration: "永久", applyTime: "2026-07-25 10:05", dept: "一车间", applicant: "孙丽", planUse: "2026-08-05", status: "审批中", statusDetail: "安全审查(王海燕)", statusTime: "2026-07-29 16:40", handlers: [{ name: "王海燕", done: false }] }, - { id: "MOC-2026-R02-0038", changeId: "MOC-2026-R02-0038", title: "DCS 罐区液位报警值 LIA-301 调整", type: "工艺", level: "一般", duration: "永久", applyTime: "2026-07-20 08:30", dept: "一车间", applicant: "吴强", planUse: "2026-07-31", status: "待PSSR", statusDetail: "培训 / PSSR 资料已传 · 待确认投用", statusTime: "2026-07-27 11:15", approveTime: "2026-07-26 16:40", handlers: [{ name: "吴强", done: false }] }, - { id: "MOC-2026-R01-0039", changeId: "MOC-2026-R01-0039", title: "V-102 安全阀起跳压力临时调整(紧急补办)", type: "设备", level: "一般", duration: "临时", urgent: true, applyTime: "2026-07-15 19:40", dept: "一车间", applicant: "张工艺", planUse: "2026-07-16(已投用)", status: "待关闭", statusDetail: "待关闭确认(资料更新核查中)", statusTime: "2026-07-27 09:02", approveTime: "2026-07-15 21:05", useTime: "2026-07-16 06:30", overdueDays: 4, handlers: [{ name: "李主任", done: false }] }, - { id: "MOC-2026-R03-0040", changeId: "MOC-2026-R03-0040", title: "原料环己烷供应商变更(新增 B 供应商)", type: "管理", level: "重要", duration: "永久", applyTime: "2026-07-18 14:22", dept: "供应部", applicant: "刘芳", planUse: "2026-08-01", status: "实施中", statusDetail: "实施准备中 · 培训 / PSSR 资料待上传", statusTime: "2026-07-26 10:30", handlers: [{ name: "刘芳", done: false }] }, - { id: "MOC-2026-R02-0031", title: "冷冻盐水泵 P-103 叶轮材质变更", type: "设备", level: "一般", duration: "永久", applyTime: "2026-06-28 09:50", dept: "二车间", applicant: "周涛", planUse: "2026-07-15", status: "待验收", statusDetail: "变更验收(72h 运行验证)", statusTime: "2026-07-24 15:44", overdueDays: 7, handlers: [{ name: "李主任", done: false }] }, - { id: "MOC-2026-R01-0029", title: "操作规程修订(夏季工况)", type: "管理", level: "一般", duration: "临时", applyTime: "2026-06-20 11:26", dept: "一车间", applicant: "张工艺", planUse: "2026-07-01", status: "审批中", statusDetail: "部门审核(李主任)", statusTime: "2026-06-21 08:40", handlers: [{ name: "李主任", done: false }] }, - { id: "MOC-2026-R02-0033", title: "包装线贴标机控制程序升级", type: "设备", level: "一般", duration: "永久", applyTime: "2026-07-08 13:35", dept: "二车间", applicant: "吴强", planUse: "2026-08-02", status: "已批准", statusDetail: "待实施(计划 8 月 2 日投用)", statusTime: "2026-07-22 09:12", handlers: [{ name: "周设备", done: false }] }, - { id: "MOC-2026-R03-0027", title: "化验室通风橱整体更换", type: "设备", level: "一般", duration: "永久", applyTime: "2026-06-12 15:10", dept: "质量部", applicant: "陈静", planUse: "2026-06-25", status: "已关闭", statusDetail: "关闭确认完成,已归档", statusTime: "2026-07-02 10:18", handlers: [{ name: "钱峰", done: true }] }, - { id: "MOC-2026-R01-0024", title: "氮气管网压力分级调整", type: "工艺", level: "重要", duration: "永久", applyTime: "2026-05-30 09:00", dept: "一车间", applicant: "孙丽", planUse: "2026-06-20", status: "已关闭", statusDetail: "关闭确认完成,已归档", statusTime: "2026-06-28 16:30", handlers: [{ name: "李主任", done: true }, { name: "刘副总", done: true }] }, -]; -const ROLES = [ - { id: "applicant", name: "申请人", person: "张工艺", org: "一车间 · 工艺组", desc: "发起变更申请、AI 辅助判定、跟踪进度" }, - { id: "process", name: "专业会签(仪表)", person: "王仪表", org: "技术部 · 仪表科", desc: "专业会签、HAZOP 分析、确认连带变更" }, - { id: "process2", name: "专业会签(设备)", person: "周设备", org: "设备管理部 · 设备科", desc: "专业会签、设备完整性检查、行动项落实" }, - { id: "safety", name: "安全工程师", person: "王海燕", org: "安全环保部", desc: "安全审查、PSSR 检查表确认、风险分析组织" }, - { id: "dept", name: "车间主任", person: "李主任", org: "一车间", desc: "部门审核、现场验收、变更关闭确认" }, - { id: "vp", name: "分管领导", person: "刘副总", org: "公司领导", desc: "重要变更最终批准、移动端审批" }, - { id: "admin", name: "系统管理员", person: "刘敏", org: "信息化部", desc: "流程配置、字典维护、权限分配" }, - { id: "quality", name: "质量工程师", person: "陈质量", org: "质量部", desc: "质量会签、产品合格判定、检验方案确认" }, - { id: "viewer", name: "非审批人员", person: "吴新", org: "相关部门", desc: "以信息查看为主,不参与变更流程" }, -]; -function nowStr() { - const d = new Date(); - const p = (n: number) => String(n).padStart(2, "0"); - return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; -} -type DemoListener = () => void; -const demoListeners = new Set(); -function emitDemo() { demoListeners.forEach((f) => f()); } -function submitChange(c: ChangeOrder) { - CHANGES.unshift(c); - const cur = c.flow.find((n) => n.status === "current"); - LEDGER_ROWS.unshift({ - id: c.id, - changeId: c.id, - title: c.title, - type: c.type, - level: c.level, - duration: c.duration, - urgent: c.urgent || undefined, - applyTime: nowStr(), - dept: c.org.split("·")[0].trim(), - applicant: c.applicant, - planUse: c.date || "待定", - status: c.status, - statusDetail: cur ? `${cur.name}(待处理)` : "审批中", - statusTime: nowStr(), - handlers: (cur?.roles ?? []).map((rid) => ({ name: ROLES.find((r) => r.id === rid)?.person ?? rid, done: false })), - }); - emitDemo(); -} -// ---------- 提交 ---------- -const submitApply = () => { - if (!desc.value.trim()) { window.$message?.warning("请先录入变更描述并完成 AI 识别"); return; } - if (!form.value) { window.$message?.warning("请先在「变更预识别」页点击「生成变更申请单」,生成申请表后再提交"); return; } - const unconfirmed = ["form", "risk", "train", "pssr", "accept", "close"].filter((t) => !confirmed.value[t]); - if (unconfirmed.length) { - blockedTabs.value = unconfirmed; - window.$message?.warning(`全部标签页确认后方可提交,请先完成以下标签页的「内容确认完毕」(标签栏已用红点标出):${unconfirmed.map((t) => TAB_NAME[t]).join("、")}`); - return; - } - blockedTabs.value = []; - const id = nextChangeId(); - const level: ChangeLevel = form.value.level === "重要" ? "重要" : "一般"; - const flow: any[] = level === "重要" - ? [ - { name: "部门审核", kind: "审批", roles: ["dept"], status: "current" }, - { name: "专业会签", kind: "会签", roles: ["process", "process2"], status: "todo", locked: true, members: [{ name: "王仪表(仪表)", done: false }, { name: "周设备(设备)", done: false }] }, - { name: "安全审查", kind: "审批", roles: ["safety"], status: "todo", locked: true }, - { name: "分管领导最终批准", kind: "最终批准", roles: ["vp"], status: "todo", locked: true }, - ] - : [ - { name: "部门审核", kind: "审批", roles: ["dept"], status: "current" }, - { name: "专业审批", kind: "审批", roles: ["process"], status: "todo", locked: true }, - { name: "部门负责人批准", kind: "最终批准", roles: ["dept"], status: "todo", locked: true }, - ]; - const order: ChangeOrder = { - id, - title: form.value.name || desc.value.slice(0, 24) || "未命名变更", - type: (form.value.type || "工艺") as ChangeType, - level, - duration: form.value.duration === "临时" ? "临时" : "永久", - urgent: !!form.value.urgent, - status: "审批中", - applicant: ROLES[0].person, - org: form.value.dept || ROLES[0].org, - device: "—", - date: today(), - purpose: form.value.purposes.join("、") || "提高生产效能", - effect: form.value.effect || "—", - before: rows.value?.map((x) => `${x.item}:${x.before}`).join(";") || desc.value.slice(0, 60), - after: rows.value?.map((x) => `${x.item}:${x.after}`).join(";") || "—", - related: form.value.related ? form.value.related.split("、") : updateDocs.value, - materials: materialsText.value ? materialsText.value.split("\n").filter(Boolean) : [], - riskTools: tools.value, - flow, - }; - submitChange(order); - window.$message?.success(`已提交 ${id},进入「部门审核」节点(台账 / 审批中心 / 角标已联动)`); - // emit("openChange", order); -}; - -const DEPT_APPROVER_OPTS = ["李主任(一车间主任)", "赵主任(二车间主任)", "陈主任(三车间主任)"].map((x) => ({ label: x, value: x })); -const SIGNER_POOL = ["王仪表(技术部)", "王仪表(技术部 · 仪表)", "周设备(设备科 · 电气)", "王海燕(安全环保部)", "王海燕(质量部)"]; -const signerOpts = (cur: string) => [...new Set([cur, ...SIGNER_POOL])].map((x) => ({ label: x, value: x })); - -const previewFile = ref(null); -const DISCIPLINE_SIGNER: Record = { - 技术: "王仪表(技术部)", 仪表: "王仪表(技术部 · 仪表)", 电气: "周设备(设备科 · 电气)", 环保: "王海燕(安全环保部)", 质量: "王海燕(质量部)", -}; -const toggleDiscipline = (d:any) => { - const result = disciplines.value.reduce((acc: Record, key: string) => { - acc[key] = DISCIPLINE_SIGNER[key]; // 如果键不存在,值为 undefined - return acc; - }, {}); - signers.value = {...result}; -}; - -const confirmTab = (t: string) => { - const v = !confirmed.value[t]; - confirmed.value = { ...confirmed.value, [t]: v }; - if (v) window.$message?.success(`已确认${TAB_NAME[t]}内容完毕(标签页栏已标记 ✓)`); - else window.$message?.info(`已取消${TAB_NAME[t]}的确认标记`); -}; -const regenerate = () => window.$message?.success(`演示:已根据变更描述重新生成${TAB_NAME[tab.value]}(本页内容已刷新)`); -const download = () => window.$message?.success(`演示:${TAB_NAME[tab.value]}已下载(Word/PDF)`); - -const finishApprover = () => { - sheet.value = ''; - window.$message?.success('审批流程已更新(底部固定行摘要同步刷新)'); -} -// 风险识别分析记录表 -const TOOL_CARDS = [ - { key: "HAZOP" as const, name: "HAZOP 分析", desc: "节点-偏离结构化分析:原因 / 后果 / LS 风险矩阵 / 安全措施" }, - { key: "JSA" as const, name: "JSA 分析", desc: "作业步骤分解:危害因素识别与 LS 风险分级管控" }, - { key: "SCL" as const, name: "设备 SCL", desc: "设备 / 管理检查项目对照标准逐项核查" }, - { key: "RISK_CHECK" as const, name: "风险检查表", desc: "变更通用检查项:是 / 否 / 不涉及逐项判定,输出风险描述与管控措施" }, -]; -const pluginDone = ref>({}); -const riskRecords = ref([]); -const updRec = (i: number, patch: Partial) => { - riskRecords.value = riskRecords.value.map((x, j) => (j === i ? { ...x, ...patch } : x)); -}; -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 检查内容」)`); -}; -// L/S → 页面风险等级(高/中/低)换算,对齐官方 5×5 矩阵阈值 -const lsLevel = (l: number | null, s: number | null) => { - const rr = (l ?? 0) * (s ?? 0); - return rr >= 10 ? "高" : rr >= 5 ? "中" : "低"; -}; -const aiOrganize = async () => { - let form = changePreRef.value?.form; - analyzing.value = true; - 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) => ({ - item: r.item, - scene: r.scene, - inherent: lsLevel(r.l, r.s), - existing: r.existing, - suggest: r.suggest, - residual: lsLevel(r.residual_l, r.residual_s), - source: "AI 整理", - })); - riskRecords.value = [...riskRecords.value, ...gen]; - window.$message?.success(`AI 已根据变更内容自动整理形成风险分析记录表(${gen.length} 项),支持手动修改`); - } catch (e: any) { - aiFail(e); - } finally { - analyzing.value = false; - } -}; -// ---------- 变更培训 ---------- -// 注:培训区块暂无独立「AI 生成」触发按钮,保留静态示例内容; -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. 风险:防爆接合面或格兰头密封失效 → 应对:检查防爆接合面与格兰头,严禁带电开盖` -); -// ---------- PSSR 检查内容 ---------- -// 注:PSSR 区块暂无独立「AI 生成」触发按钮,保留静态示例清单; -// 接入时可在此调 aiPssr({ content: desc.value, change_type: form.value.type, risk_records: riskRecords.value }), -// 将 groups(g, items: content/phase) 映射为 pssrGroups 的 { g, items: { c, p } } 结构。 -const pssrDone = ref>({}); -const pssrNa = ref>({}); -const pssrActive = computed(() => pssrGroups.value.filter((g:any) => !pssrNa.value[g.g])); -const pssrTotal = computed(() => pssrActive.value.reduce((a:any, g:any) => a + g.items.length, 0)); -const pssrDoneCount = computed(() => pssrActive.value.reduce((a:any, g:any) => a + g.items.filter((it:any) => pssrDone.value[it.c]).length, 0)); -const pssrAllDone = computed(() => pssrDoneCount.value === pssrTotal.value && (pssrTotal.value > 0 || (pssrGroups.value.length > 0 && pssrGroups.value.every((g) => !!pssrNa.value[g.g])))); -const pssrFileRef = ref(null); -const pssrSheet = ref(null); - const closeDocs = ref([ - { name: "PID 图纸(PID-R201-03,受控最新版)", cat: "图纸与规程类", scope: "是", archived: true }, - { name: "操作规程(含超温应急处置卡)", cat: "图纸与规程类", scope: "是", archived: false }, - { name: "应急预案修订", cat: "图纸与规程类", scope: "不涉及", archived: false }, - { name: "总图更新", cat: "图纸与规程类", scope: "否", archived: false }, - { name: "DCS 联锁台账(新报警 / 联锁值)", cat: "台账与制度类", scope: "是", archived: false }, - { name: "设备台账与备件清单", cat: "台账与制度类", scope: "是", archived: true }, - { name: "风险分析报告(HAZOP / 检查表法)", cat: "过程与结果文件类", scope: "是", archived: true }, - { name: "培训签到与考核记录", cat: "过程与结果文件类", scope: "是", archived: true }, - { name: "PSSR 检查记录(含现场照片)", cat: "过程与结果文件类", scope: "是", archived: false }, - { name: "验收报告与化验数据", cat: "过程与结果文件类", scope: "是", archived: false }, -]); -const onPssrFile = (e: Event) => { - const input = e.target as HTMLInputElement; - const f = input.files?.[0]; input.value = ""; - if (!f) return; - pssrSheet.value = f.name; - const at = today(); - const n = { ...pssrDone.value }; - pssrGroups.value.forEach((g:any) => { if (!pssrNa.value[g.g]) g.items.forEach((it:any) => { n[it.c] = { by: "纸质确认表上传", at }; }); }); - pssrDone.value = n; - const archGroup = pssrGroups.value[pssrGroups.value.length - 1]; - if (archGroup && !pssrNa.value[archGroup.g]) { - closeDocs.value = closeDocs.value.map((d:any) => (d.name.includes("PID") || d.name.includes("设备台账") || d.name.includes("培训签到") ? { ...d, archived: true } : d)); - } - window.$message?.success(`已上传 PSSR 纸质确认表「${f.name}」:不做内容识别,上传即视为全部检查项完成确认(「不涉及」组除外),原件归档至变更档案`); -}; - -// ---------- 验收评价 ---------- -// 注:验收清单暂无独立「AI 生成」触发按钮,保留静态示例行; -// 接入时可在此调 aiAcceptance({ purpose: form.value.purposes.join('、'), effect: form.value.effect }), -// 将 rows(item/basis/standard/method/owner_suggest) 映射为 acceptRows 行结构(id 自行补,result 默认「待验收」)。 -const acceptRows = ref([ - { id: 1, item: "反应转化率", basis: "转化率不低于 98%", standard: "转化率 ≥ 98%(连续 3 批化验合格)", method: "化验室逐批取样分析,出具检验报告", owner: "车间 · 张工艺", result: "待验收" }, - { id: 2, item: "单批反应时间 / 产能", basis: "反应时间缩短 50%、产能提升一倍", standard: "保温时间 4h±0.5h,产能提升 ≥ 80%", method: "DCS 批次记录统计,对比变更前 30 天均值", owner: "车间 · 李主任", result: "待验收" }, - { id: 3, item: "未知杂质含量", basis: "杂质水平受控、产品质量合格", standard: "杂质 ≤ 0.15%,产品质量指标全部合格", method: "每批化验跟踪,连续 5 批数据趋势评估", owner: "质量 · 王海燕", result: "待验收" }, - { id: 4, item: "温控与联锁有效性", basis: "温度窗口上调后保护层有效", standard: "温度波动 ≤ ±2℃,高报/联锁动作正确率 100%", method: "DCS 趋势核查 + 联锁测试记录复核", owner: "仪表 · 王仪表", result: "待验收" }, - { id: 5, item: "设备运行状态", basis: "负荷变化后设备长周期稳定", standard: "搅拌电流、釜壁温度无异常趋势,无泄漏", method: "设备巡检 + 振动/温度记录评估", owner: "设备 · 周设备", result: "待验收" }, -]); -const acceptNote = ref(""); - -// ---------- 变更关闭确认表 ---------- -const closeFold = ref>({}); -const uploadDoc = ref(null); -const uploadSel = ref(null); -const UPDATE_DOC_KW: Record = { PID图纸: "PID", 操作规程: "操作规程", 总图: "总图", 设备台账: "设备台账", 工艺卡片: "工艺卡片", 联锁台账: "联锁台账", 应急预案: "应急预案" }; -const needUpdate = (name: string) => updateDocs.value.some((d:any) => name.includes(UPDATE_DOC_KW[d] ?? d)); - -const changeScope = (d: any, v: string) => { - closeDocs.value = closeDocs.value.map((x:any) => (x.name === d.name ? { ...x, scope: v } : x)); - if (v !== d.scope) window.$message?.info(`演示:「${d.name}」改判为「${v}」,改判理由与操作人已留痕`); -} -const uploadDocInfo = computed(() => { - if (!uploadDoc.value) return null; - const doc = closeDocs.value.find((x) => x.name === uploadDoc.value); - const base = uploadDoc.value.replace(/[((].*$/, ""); - return { - storePath: `\\\\PLANT-DMS\\变更管理\\2026\\MOC-2026-R01-0041\\${doc?.cat ?? ""}\\`, - candidates: [`${base}_RevC_受控版.pdf`, `${base}_2026-08 更新版.docx`], - base, - }; -}); -const openDir = () => { - window.$message?.info('演示:已在文档管理系统中打开该目录') -} -const uploadFiles = () => { - uploadSel.value = `本地文件:${uploadDocInfo.value?.base}_最新版.pdf`; - window.$message?.info('演示:已接收拖拽 / 选择的本地文件'); -} -const uploadConfirm = () => { - closeDocs.value = closeDocs.value.map((x) => (x.name === uploadDoc.value ? { ...x, archived: true } : x)); - window.$message?.success(`「${uploadDoc.value}」已上传并归档(${uploadSel.value},受控版本,哈希留痕)`); - uploadDoc.value = null; -} - -const pluginDrawer = ref(false); -const currentPlugin = ref(""); - -const exportAll = (items: any[], mode: "manual" | "ai") => { - riskRecords.value = [...riskRecords.value, ...items]; - if (mode === "ai") { - pluginDrawer.value = false; - currentPlugin.value = ''; - window.$message?.success(`AI 已自动整理形成风险分析记录表(新增 ${items.length} 项),支持手动修改`); - } else { - window.$message?.success(`已选入 ${items.length} 条风险记录,可继续挑选或返回查看记录表`); - } -} -const onBack = () => { - pluginDrawer.value = false; -} - - // **********************通用字段*********************** const changePreRef = ref(null); const changeApplyRef = ref(null); +const changeTrainingRef = ref(null); +// 组织树 +const orgTree = ref([]); +// 变更类型选项 +const typeList = ref<{label: string, value: number, color: string}[]>([]); +// 变更等级选项 +const levelList = ref<{label: string, value: number, color: string}[]>([]); +// 模板列表 +const templateList = ref([]); + // 是否开启ai功能 const aiEnabled = ref(localStg.get('userInfo')?.ai_assist_enabled=='1' ? true : false); -// 保存loading -const saveLoading = ref(false); -// 提交loading -const submitLoading = ref(false); -// 下载loading -const downloadLoading = ref(false); -// 重新生成loading -const regenerateLoading = ref(false); -// 风险预分析loading -const analyzing = ref(false); - // 生成变更申请单loading -const generateLoading = ref(false); // btn disabled const btnDisabled = ref(false); // 变更名称 const title = ref(''); +// 变更编号 +const changeNo = ref(''); // 当前id const currentId = ref(0); // 是否确认 -const confirmed = ref>({}); -// 抽屉类型 -const sheet = ref(''); +const confirmed = ref<{[key: string]: boolean}>({ + 'pre': false, + 'form': false, + 'risk': false, + 'train': false, + 'pssr': false, + 'accept': false, + 'close': false, +}); // 变更预分析表单数据 const changePreForm = ref(null) @@ -476,7 +70,9 @@ const TABS = [ { id: "accept", name: "验收评价", icon: 'material-symbols:check-circle-outline' }, { id: "close", name: "变更关闭确认表", icon: 'quill:folder-open' }, ]; +// 当前标签 const tab = ref(aiEnabled.value ? "pre" : "form"); +// tab 列表 const tabList = computed(() => { return aiEnabled.value ? TABS : TABS.filter((x) => x.id !== "pre"); }); @@ -493,179 +89,10 @@ const tabChange = (id: string) => { } tab.value = id; } -// 更新loading -const updateLoading = (type:string,loading: boolean) => { - btnDisabled.value = loading; - if(type === 'analyzing'){ - analyzing.value = loading; - } - if(type==='save'){ - saveLoading.value = loading; - } -} -// **********************变更预识别*********************** -// 变更编号 -const changeNo = ref(''); -// 运行风险预分析 -const runRiskAnalysis = async () => { - let form = changePreRef.value?.form; - if(form?.description.trim() === '') { - return window.$message?.warning('请先填写变更内容描述'); - } - updateLoading('analyzing',true); - changePreRef.value?.updateBtnDisabled(true); - try { - const res = await aiRiskPreAnalysis(form?.description, form?.compare_rows); - changePreRef.value?.updateForm(res,true); - window.$message?.success("AI 风险预分析报告已生成(危害严重性 / 事件概率 / 保护措施影响),内容支持手动修改"); - } catch (e: any) { - aiFail(e); - } finally { - updateLoading('analyzing',false); - changePreRef.value?.updateBtnDisabled(false); - } -}; -// 生成变更申请单 -const genForm = async () => { - window.$message?.info("AI 正在生成变更申请单"); - generateLoading.value = true; - try { - const res = await aiApplication(desc.value, (form.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text }))); - const pick = (v: string | null | undefined, opts: string[], cur: string) => (v && opts.includes(v) ? v : cur); - const main = (res?.main_changes ?? []).filter((x) => x && x.trim()); - const purposes = (res?.purposes ?? []).filter((x) => PURPOSE_OPTIONS.includes(x)); - const docs = (res?.update_docs ?? []).filter((x) => UPDATE_DOC_OPTIONS.includes(x)); - const discs = (res?.disciplines ?? []).filter((x) => DISCIPLINE_OPTIONS.includes(x)); - const aiTools = (res?.risk_tools ?? []) - .map((x) => RISK_TOOLS_FORM.find((t) => t.key === x || t.label === x)?.key) - .filter((x): x is string => !!x); - form.value = { - ...form.value, - name: res?.name || form.value.name, - mainChanges: main.length ? main : form.value.mainChanges, - related: (res?.related_changes ?? []).filter(Boolean).join("、") || form.value.related, - purposes: purposes.length ? purposes : form.value.purposes, - effect: res?.effect || form.value.effect, - type: pick(res?.change_type, ['工艺', '设备', '管理'], form.value.type), - duration: pick(res?.duration_suggestion, ['永久', '临时'], form.value.duration), - }; - if (res?.materials_text) materialsText.value = res.materials_text; - if (docs.length) updateDocs.value = docs; - if (discs.length) { - disciplines.value = discs; - toggleDiscipline(discs); - } - if (res?.signers && Object.keys(res.signers).length) signers.value = { ...res.signers }; - if (aiTools.length) tools.value = aiTools; - currentPlugin.value = ""; - tab.value = "form"; - window.$message?.success("变更申请单已生成:AI 自动总结名称、预填内容,均可手动修改"); - } catch (e: any) { - aiFail(e); - } finally { - generateLoading.value = false; - } -}; - -// --------------------- 变更申请表 --------------------- -// 变更目的选项 -const PURPOSE_OPTIONS = [ - "满足市场需求", "提高生产效能", "改善操作条件", "增加安全性能", - "达到环保要求", "法律合规整改", "减少能耗物损", "稳定系统运行", - "完善管理制度", "达到质量改进", "设备可靠性升级", -]; - -// 风险分析选项 -const RISK_TOOLS_FORM = [ - { key: "HAZOP", label: "HAZOP" }, - { key: "JSA", label: "JSA" }, - { key: "SCL", label: "设备SCL" }, - { key: "RISK_CHECK", label: "风险检查表" }, -]; -const TAB_NAME: Record = { - pre: "变更预识别", form: "变更申请表", risk: "风险分析记录表", - train: "变更培训内容", pssr: "PSSR检查内容", accept: "验收评价", close: "变更关闭确认表", -}; - -// 变更申请表表单必填判断 -const changeApplyRules = () => { - if(title.value.trim()===''){ - window.$message?.warning('请填写变更名称'); - return true - } - let form = changeApplyRef.value?.form; - if(form?.manage_dept?.trim() === ''){ - window.$message?.warning('请填写申请部门'); - return true - } - if(form?.main_changes?.length===0){ - window.$message?.warning('请添加主要变更内容'); - return true - } - if(form?.main_changes.some((str:string)=>str.trim()==='')){ - window.$message?.warning('请填写主要变更内容'); - return true - } - if(form?.change_type===null){ - window.$message?.warning('请选择变更类型'); - return true - } - if(form?.change_level===null){ - window.$message?.warning('请选择变更等级'); - return true - } - if(form?.duration_type===null){ - window.$message?.warning('请选择变更时限'); - return true - } - if(form?.duration_type === 1 && form?.restore_deadline===null){ - window.$message?.warning('请选择计划恢复时间'); - return true - } - return false -} - -// 保存草稿 -const saveDraft = async () => { - // 变更预识别 - if(tab.value === 'pre'){ - if(title.value.trim()===''){ - return window.$message?.warning('请填写变更名称'); - } - changePreForm.value = changePreRef.value?.form; - if(changePreForm.value?.description?.trim() === ''){ - return window.$message?.warning('请填写变更内容描述'); - } - updateLoading('save', true); - changePreRef.value?.updateBtnDisabled(true); - const {error} = await precheckSaveApi({ - change_id: currentId.value, - title: title.value, - description: changePreForm.value?.description || '', - severity: changePreForm.value?.severity || '', - probability: changePreForm.value?.probability || '', - protection: changePreForm.value?.protection || '', - compare_rows: changePreForm.value?.compare_rows || [], - }) - if(!error){ - window.$message?.success("已保存为草稿,可在左侧「我的草稿」中继续编辑"); - } - updateLoading('save', false); - changePreRef.value?.updateBtnDisabled(false); - } - // 变更申请表 - if(tab.value === 'form'){ - if(changeApplyRules()){ - return - } - updateLoading('save', true); - const {error} = await applicationSaveApi(currentId.value, changeApplyRef.value?.form); - if(!error){ - window.$message?.success("已保存为草稿,可在左侧「我的草稿」中继续编辑"); - } - updateLoading('save', false); - } +// 标签内容确认 +const confirmTab = (tab: string, val: boolean) => { + confirmed.value[tab] = val; } // 更新按钮禁用状态 @@ -681,8 +108,49 @@ const getId = async () => { changeNo.value = data.change_no; } } +// 获取配置 +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 = changeTypeGroup.items.map((item:any) => ({ + label: item.label, + value: Number(item.value), // 或者 parseInt(item.value, 10) + color: item.color + })); + } + // 解析 变更等级设置 + const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level'); + if (changeLevelGroup) { + levelList.value = changeLevelGroup.items.map((item:any) => ({ + label: item.label, + value: Number(item.value), // 或者 parseInt(item.value, 10) + color: item.color + })); + } + } +} +// 获取模板配置 +const getTemplateConfig = async () => { + const {data,error} = await templateListApi(); + if(!error){ + templateList.value = data; + } +} +// 获取组织列表 +const getOrgList = async () => { + const {data,error} = await orgListApi(); + if(!error){ + orgTree.value = data; + } +} -onMounted(async () => { +onMounted(() => { + getOrgList(); + getConfig(); + getTemplateConfig(); getId(); }); @@ -690,16 +158,10 @@ onMounted(async () => { - - diff --git a/src/views/my/initiateChange/modules/approvalDrawer.vue b/src/views/my/initiateChange/modules/approvalDrawer.vue new file mode 100644 index 0000000..33dff21 --- /dev/null +++ b/src/views/my/initiateChange/modules/approvalDrawer.vue @@ -0,0 +1,77 @@ + + + + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/changeApply.vue b/src/views/my/initiateChange/modules/changeApply.vue index f464fca..ecb500c 100644 --- a/src/views/my/initiateChange/modules/changeApply.vue +++ b/src/views/my/initiateChange/modules/changeApply.vue @@ -1,34 +1,36 @@ \ No newline at end of file + + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/changePre.vue b/src/views/my/initiateChange/modules/changePre.vue index 9ef6a35..92e0aa3 100644 --- a/src/views/my/initiateChange/modules/changePre.vue +++ b/src/views/my/initiateChange/modules/changePre.vue @@ -3,12 +3,14 @@ import { ref } from 'vue'; import { Icon } from '@iconify/vue' import { useThemeStore } from '@/store/modules/theme'; -import { AiError, aiRecognize } from "@/service/api/ai"; +import { AiError, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai"; +import { precheckSaveApi } from "@/service/api/change"; import ChangeCompareTable from "./ChangeCompareTable.vue"; const themeStore = useThemeStore(); -const emit = defineEmits(['updateBtn']); +const props = defineProps(['title','currentId']) + const aiFail = (e: any) => { if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`); @@ -18,6 +20,12 @@ const aiFail = (e: any) => { const isAiGenerated = ref(false); // 变更识别loading const preLoading = ref(false); +// 生成变更申请单loading +const generateLoading = ref(false); +// 保存loading +const saveLoading = ref(false); +// 风险预分析loading +const analyzing = ref(false); // 编辑风险预分析 const preRiskEdit = ref(''); // 是否显示险预分析 @@ -44,105 +52,204 @@ const runIdentify = async () => { return window.$message?.warning("请先输入变更内容描述"); } form.value.compare_rows = []; - emit('updateBtn', true); + btnDisabled.value = true; preLoading.value = true; try { const res = await aiRecognize(form.value.description); form.value.compare_rows = (res?.rows ?? []).map((x, i) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })); + isAiGenerated.value = true; window.$message?.success(`AI 变更识别完成,共 ${form.value.compare_rows.length} 项,请逐项人工确认(可直接修改)`); } catch (e: any) { form.value.compare_rows = []; aiFail(e); } finally { preLoading.value = false; - emit('updateBtn', false); + btnDisabled.value = false; } }; -// 更新表单 -const updateForm = (res: any, isShow: boolean) => { - form.value = { - ...form.value, - severity: res?.severity ?? form.value.severity, - probability: res?.probability ?? form.value.probability, - protection: res?.protection ?? form.value.protection, - }; - preRiskShow.value = isShow; -} -// 更新按钮禁用状态 -const updateBtnDisabled = (disabled: boolean) => { - btnDisabled.value = disabled; +// 运行风险预分析 +const runRiskAnalysis = async () => { + if(form.value.description.trim() === '') { + return window.$message?.warning('请先填写变更内容描述'); + } + analyzing.value = true; + btnDisabled.value = true; + try { + const res = await aiRiskPreAnalysis(form.value.description, form.value.compare_rows); + form.value = { + ...form.value, + severity: res?.severity ?? form.value.severity, + probability: res?.probability ?? form.value.probability, + protection: res?.protection ?? form.value.protection, + }; + preRiskShow.value = true; + window.$message?.success("AI 风险预分析报告已生成(危害严重性 / 事件概率 / 保护措施影响),内容支持手动修改"); + } catch (e: any) { + aiFail(e); + } finally { + analyzing.value = false; + btnDisabled.value = false; + } +}; +// 生成变更申请单 +const genForm = async () => { + // window.$message?.info("AI 正在生成变更申请单"); + // generateLoading.value = true; + // try { + // const res = await aiApplication(desc.value, (form.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text }))); + // const pick = (v: string | null | undefined, opts: string[], cur: string) => (v && opts.includes(v) ? v : cur); + // const main = (res?.main_changes ?? []).filter((x) => x && x.trim()); + // const purposes = (res?.purposes ?? []).filter((x) => PURPOSE_OPTIONS.includes(x)); + // const docs = (res?.update_docs ?? []).filter((x) => UPDATE_DOC_OPTIONS.includes(x)); + // const discs = (res?.disciplines ?? []).filter((x) => DISCIPLINE_OPTIONS.includes(x)); + // const aiTools = (res?.risk_tools ?? []) + // .map((x) => RISK_TOOLS_FORM.find((t) => t.key === x || t.label === x)?.key) + // .filter((x): x is string => !!x); + // form.value = { + // ...form.value, + // name: res?.name || form.value.name, + // mainChanges: main.length ? main : form.value.mainChanges, + // related: (res?.related_changes ?? []).filter(Boolean).join("、") || form.value.related, + // purposes: purposes.length ? purposes : form.value.purposes, + // effect: res?.effect || form.value.effect, + // type: pick(res?.change_type, ['工艺', '设备', '管理'], form.value.type), + // duration: pick(res?.duration_suggestion, ['永久', '临时'], form.value.duration), + // }; + // if (res?.materials_text) materialsText.value = res.materials_text; + // if (docs.length) updateDocs.value = docs; + // if (discs.length) { + // disciplines.value = discs; + // toggleDiscipline(discs); + // } + // if (res?.signers && Object.keys(res.signers).length) signers.value = { ...res.signers }; + // if (aiTools.length) tools.value = aiTools; + // currentPlugin.value = ""; + // tab.value = "form"; + // window.$message?.success("变更申请单已生成:AI 自动总结名称、预填内容,均可手动修改"); + // } catch (e: any) { + // aiFail(e); + // } finally { + // generateLoading.value = false; + // } +}; + +// 保存草稿 +const saveDraft = async () => { + // 变更预识别 + if(props.title?.trim()===''){ + return window.$message?.warning('请填写变更名称'); + } + if(form.value.description?.trim() === ''){ + return window.$message?.warning('请填写变更内容描述'); + } + saveLoading.value = true; + btnDisabled.value = true; + const {error} = await precheckSaveApi({ + change_id: props.currentId, + title: props.title, + description: form.value.description, + severity: form.value.severity, + probability: form.value.probability, + protection: form.value.protection, + compare_rows: form.value.compare_rows, + }) + if(!error){ + window.$message?.success("已保存为草稿,可在左侧「我的草稿」中继续编辑"); + } + saveLoading.value = false; + btnDisabled.value = false; } -defineExpose({form,updateForm,updateBtnDisabled}) +defineExpose({form}) \ No newline at end of file +
+ 预识别阶段不设审批人与提交:完成识别与预分析后,请切换至「变更申请表」选择审批人并提交 +
+ + 生成变更申请单 + + + + 风险预分析 + + 保存 +
+
+ + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/changeTraining.vue b/src/views/my/initiateChange/modules/changeTraining.vue new file mode 100644 index 0000000..cea38cc --- /dev/null +++ b/src/views/my/initiateChange/modules/changeTraining.vue @@ -0,0 +1,56 @@ + + + + + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/closeConfirm.vue b/src/views/my/initiateChange/modules/closeConfirm.vue new file mode 100644 index 0000000..48e7bcd --- /dev/null +++ b/src/views/my/initiateChange/modules/closeConfirm.vue @@ -0,0 +1,281 @@ + + + + + + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/evaluation.vue b/src/views/my/initiateChange/modules/evaluation.vue new file mode 100644 index 0000000..e7cb253 --- /dev/null +++ b/src/views/my/initiateChange/modules/evaluation.vue @@ -0,0 +1,247 @@ + + + + + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/footer.vue b/src/views/my/initiateChange/modules/footer.vue new file mode 100644 index 0000000..35f9764 --- /dev/null +++ b/src/views/my/initiateChange/modules/footer.vue @@ -0,0 +1,259 @@ + + + + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/pssr.vue b/src/views/my/initiateChange/modules/pssr.vue new file mode 100644 index 0000000..f61e5d0 --- /dev/null +++ b/src/views/my/initiateChange/modules/pssr.vue @@ -0,0 +1,246 @@ + + + + + \ No newline at end of file diff --git a/src/views/my/initiateChange/modules/riskAnalysis.vue b/src/views/my/initiateChange/modules/riskAnalysis.vue new file mode 100644 index 0000000..3b88c8c --- /dev/null +++ b/src/views/my/initiateChange/modules/riskAnalysis.vue @@ -0,0 +1,335 @@ + + + + + \ No newline at end of file diff --git a/src/views/systemManage/setting/index.vue b/src/views/systemManage/setting/index.vue index 9adc122..733e806 100644 --- a/src/views/systemManage/setting/index.vue +++ b/src/views/systemManage/setting/index.vue @@ -609,9 +609,9 @@ onMounted(() => { 变更等级设定(支持自定义) - +
@@ -635,7 +635,7 @@ onMounted(() => { 编辑 - { 确定删除吗? - + -->
diff --git a/src/views/systemManage/template/index.vue b/src/views/systemManage/template/index.vue index c8f6aac..b34e349 100644 --- a/src/views/systemManage/template/index.vue +++ b/src/views/systemManage/template/index.vue @@ -282,7 +282,7 @@ const saveTemplate = async (e: MouseEvent) => { const setStepCountersign = (target: any, v: boolean) => { currentTemplate.value.steps.forEach((s:any) => { s.countersign = s === target ? v : false; - if (!s.countersign) s.members.forEach((m:any) => { m.specialty = ""; m.is_sign = false; }); + if (!s.countersign) s.members.forEach((m:any) => { m.specialty = ""; m.is_sign = 0; }); }); } // 当前打开人员选择器的步骤(步骤行「+」按钮切换) diff --git a/src/views/systemManage/user/index.vue b/src/views/systemManage/user/index.vue index 17b3413..f664765 100644 --- a/src/views/systemManage/user/index.vue +++ b/src/views/systemManage/user/index.vue @@ -733,6 +733,7 @@ onMounted(() => {