变更详情页面修改

This commit is contained in:
2026-09-22 16:34:08 +08:00
parent ffe0a974e8
commit e2409fe19b
-264
View File
@@ -135,77 +135,10 @@ const questions = ref<any>([
],
},
]);
const role = ref<string>("applicant");
// 判定说明抽屉
const describeDrawer = ref<boolean>(false);
const levelDrawer = ref<boolean>(false);
// ai 标注抽屉
const aiDrawer = ref<boolean>(false);
const mandClosed = ref<boolean>(false);
const confirmClose = () => {
mandClosed.value = true;
window.$message?.success('强制合规项已确认闭环,操作已留痕');
}
const conflictHide = ref<boolean>(false);
// 关键参数对比(静态示例作 fallback,AI 审批辅助完成后回填)
const paramDiff = ref([
{ k: "反应温度", before: "80℃", after: "90℃", over: false, note: "仍在溶剂沸点与物料分解温度的安全裕度内" },
{ k: "保温时间", before: "8 h", after: "4 h", over: false, note: "终点判断窗口变窄,须修订操作规程" },
{ k: "DCS 温度高报警", before: "85℃", after: "95℃", over: true, note: "超出原报警裕度,须重新整定验证" },
{ k: "联锁高高报", before: "90℃", after: "100℃", over: true, note: "接近设备设计温度上限,投用前须完成联锁测试" },
{ k: "未知杂质含量", before: "0.05%", after: "0.12%", over: false, note: "结构未定,须纳入日常分析计划" },
]);
const aiRead = ref<boolean>(false);
const roleName = ref<string>('张工艺');
const suggs = ref([
{ id: 1, text: "将未知杂质含量纳入日常分析计划(每周 1 次,连续 8 周)", st: "待定" },
{ id: 2, text: "修订操作规程并补充超温应急处置卡后再投用", st: "待定" },
]);
const opinion = ref<string>("");
// 已阅 AI 建议
const readSuggest = () => {
aiRead.value = true;
window.$message?.success(`${roleName} 已阅 AI 建议,时间与操作已写入审批留痕`);
}
// 采纳为行动项
const acceptSuggest = (s: any) => {
suggs.value = suggs.value.map((x:any) => (x.id === s.id ? { ...x, st: '已采纳' } : x));
opinion.value = (opinion.value ? `${opinion.value}\n` : '') + `采纳 AI 建议:${s.text}`;
window.$message?.success('已采纳,自动生成 PSSR 行动项(来源:AI 建议),并纳入审批意见');
}
// 不采纳 AI 建议
const rejectSuggest = (s: any) => {
suggs.value = suggs.value.map((x:any) => (x.id === s.id ? { ...x, st: '不采纳' } : x));
window.$message?.info('不采纳理由「现有分析频次已覆盖风险」已记录留痕');
}
// 查看归档包
const viewArchive = (name: string) => {
window.$message?.info(`新窗口打开 ${name} 归档包,不影响当前审批`);
}
// AI 审批辅助(抽屉打开时触发,异步任务轮询;失败保留静态示例,不阻塞页面)
const aiAssistLoading = ref<boolean>(false);
const aiAssistStep = ref<string>('');
const aiAssistProgress = ref<number>(0);
// 强制合规描述(静态示例作 fallback)
const mandText = ref<string>('本次变更涉及 DCS 联锁设定值修改,依据 AQ/T 3034《化工企业工艺安全管理实施导则》,投用前须完成联锁测试并留存记录。');
const mandItem = ref<string>('联锁测试记录须在投用前完成并上传归档');
// 冲突提示
const conflictText = ref<string>('涉及设备 R-101 关联变更 MOC-2026-0038「机封冲洗方案调整」正在审批中,请确认施工时序与隔离方案无冲突。');
// 属地影响摘要
const deptRows = ref([
{ label: '涉及单元:', text: '一车间 R-201 反应釜及配套 DCS 回路,不影响相邻单元。' },
{ label: '现行规程影响:', text: '操作规程第 4.2 节(报警处置)需同步修订,变更关闭前完成受控换版。' },
{ label: '交接班提示:', text: '投用后首个班次需向班组交代新报警值与处置卡位置,建议纳入班前会内容。' },
]);
// 参考信息(相似变更 / 历史经验)
const similarText = ref<string>('相似变更 MOC-2025-0112「酯化反应温度上调」投用后运行正常,无异常记录');
const experienceText = ref<string>('历史经验:温度类变更投用后首周超温报警占比约 38%,建议关注投用首周报警趋势');
const aiFail = (e: any) => {
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
else window.$message?.warning("AI 分析失败,可手动填写");
@@ -404,17 +337,6 @@ onMounted(() => {
</NButton>
<div class="min-w-0 flex-1 truncate text-base font-bold text-slate-800">{{ currentInfo?.title }}</div>
<NTag size="small" type="info" class="font-mono text-xs">编号:{{ currentInfo?.change_no }}</NTag>
<!-- 审批中 -->
<NButton
v-if="currentStatus === 'APPROVING' && isAiOn"
ghost
size="small"
type="primary"
class="text-xs"
@click="aiDrawer = !aiDrawer"
>
<Icon icon="lucide:sparkles" class="size-12px mr-1" /> AI 标注
</NButton>
</div>
<div class="space-y-3 scroll">
<!-- 变更申请表 -->
@@ -783,192 +705,6 @@ onMounted(() => {
</div>
</NDrawerContent>
</NDrawer>
<!-- AI 审批辅助(右侧抽屉查看) -->
<NDrawer v-model:show="aiDrawer" :width="576" placement="right">
<NDrawerContent closable>
<template #header>
<span class="flex items-center gap-1.5 text-sm font-bold text-slate-700">
<Icon icon="lucide:sparkles" class="size-16px text-violet-500 mr-1" />
AI 审批辅助(抽屉式,正文全窗口阅读)
</span>
</template>
<div class="space-y-4">
<!-- AI 分析进度(异步任务轮询) -->
<div v-if="aiAssistLoading" class="rounded-lg border border-violet-200 bg-violet-50 px-3 py-2.5 text-xs text-violet-700">
<div class="flex items-center gap-2">
<Icon icon="eos-icons:loading" class="size-14px" />
<span>{{ aiAssistStep || 'AI 分析中…' }}({{ aiAssistProgress }}%)</span>
</div>
<NProgress type="line" :percentage="aiAssistProgress" :show-indicator="false" class="mt-1.5" processing />
</div>
<!-- 合规警告条(强制合规项,不可关闭;闭环后转绿) -->
<div
class="px-3 py-2.5 text-sm"
:class="mandClosed ? 'text-[var(--success-color)] bg-[var(--success-bg-color)]' : 'text-[var(--warning-color)] bg-[var(--warning-bg-color)]'"
:style="{
borderRadius:themeStore.themeRadius+'px',
'--success-color':themeStore.otherColor.success,
'--success-bg-color':getColorWithOpacity(themeStore.otherColor.success,0.1),
'--warning-color':themeStore.otherColor.warning,
'--warning-bg-color':getColorWithOpacity(themeStore.otherColor.warning,0.1)
}"
>
<div class="flex items-center gap-2">
<Icon icon="proicons:alert-triangle" class="size-16px" />
<span class="font-semibold">强制合规</span>
<span class="ml-auto shrink-0">
<NTag v-if="mandClosed" size="small" type="success">
<template #icon>
<Icon icon="lucide:shield-check" class="size-12px" />
</template>
已闭环
</NTag>
<NTag v-else size="small" type="warning">未闭环</NTag>
</span>
</div>
<p class="mt-1 text-xs leading-relaxed">
{{ mandText }}
</p>
<div v-if="!mandClosed" class="mt-1 text-right">
<NButton text type="primary" class="text-xs" @click="confirmClose">确认闭环(留痕)</NButton>
</div>
</div>
<!-- 变更冲突提示(同设备在途变更,可收起) -->
<div
v-if="!conflictHide"
class="bg-[var(--bg-color)] px-3 py-2.5 text-sm text-[var(--theme-color)]"
:style="{
borderRadius:themeStore.themeRadius+'px',
'--theme-color':themeStore.themeColor,
'--bg-color':getColorWithOpacity(themeStore.themeColor,0.1),
}"
>
<div class="flex items-center gap-2">
<Icon icon="lucide:bell" class="size-14px" />
<span class="font-semibold">冲突提示</span>
<NButton text type="primary" class="ml-auto shrink-0 text-xs" @click="conflictHide = true">知道了</NButton>
</div>
<p class="mt-1 text-xs leading-relaxed">
{{ conflictText }}
</p>
</div>
<!-- 属地影响摘要(仅属地负责人视角) -->
<div v-if="role === 'dept'" class="border" :style="{borderRadius:themeStore.themeRadius+'px'}">
<div class="flex items-center gap-2 border-b bg-slate-50/70 px-3 py-2">
<Icon icon="lucide:sparkles" class="size-16px text-violet-500" />
<span class="text-sm font-semibold text-slate-700">属地影响摘要</span>
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
✨ AI 生成 · 需人工确认
</span>
</div>
<div class="space-y-1.5 px-3 py-2.5 text-xs leading-relaxed text-slate-600">
<p v-for="(r, i) in deptRows" :key="i"><b class="text-slate-700">{{ r.label }}</b>{{ r.text }}</p>
</div>
</div>
<!-- 变更前后关键参数对比(变化标蓝 / 超限标红) -->
<div
class="border"
:style="{
borderRadius:themeStore.themeRadius+'px',
'--error-color':themeStore.otherColor.error,
'--theme-color':themeStore.themeColor
}"
>
<div class="border-b bg-slate-50/70 px-3 py-2 text-xs font-semibold text-slate-600">
关键参数对比(AI 提取) · <span class="font-bold text-[var(--theme-color)]">蓝=变化</span> · <span class="font-bold text-[var(--error-color)]">红=超限</span>
</div>
<div v-for="d in paramDiff" :key="d.k" class="border-b px-3 py-2 last:border-b-0">
<div class="flex flex-wrap items-baseline gap-x-2 text-sm">
<span class="w-24 shrink-0 text-xs text-slate-500">{{ d.k }}</span>
<span class="text-xs text-slate-400">{{ d.before }}</span>
<span class="text-slate-300">→</span>
<span
class="text-sm font-semibold"
:class="d.over ? 'text-[var(--error-color)]' : 'text-[var(--theme-color)]'"
>{{ d.after }}</span>
<NTag v-if="d.over" size="small" type="error">超限</NTag>
</div>
<div
class="mt-0.5 text-[11px] leading-snug"
:class="d.over ? 'text-[var(--error-color)]' : 'text-slate-400'"
>{{ d.note }}</div>
</div>
</div>
<!-- AI 审批建议(版本冻结 · 建议分级 · 已阅留痕 · 无感审计) -->
<div
class="border"
:style="{
borderRadius:themeStore.themeRadius+'px',
'--error-color':themeStore.otherColor.error,
'--theme-color':themeStore.themeColor,
'--warning-color':themeStore.otherColor.warning,
}"
>
<div class="flex flex-wrap items-center gap-2 border-b bg-slate-50/70 px-3 py-2">
<Icon icon="lucide:sparkles" class="size-14px text-violet-500" />
<span class="text-sm font-semibold text-slate-700">AI 审批建议</span>
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
✨ AI 生成 · 需人工确认
</span>
<NTag size="small">基于提交版本 V1 一次性分析 · 版本冻结</NTag>
<div class="ml-auto">
<NTag v-if="aiRead" size="small" type="success">✓ {{ roleName }} 已阅 AI 建议(已留痕)</NTag>
<NButton v-else size="small" class="text-xs" @click="readSuggest">已阅 AI 建议</NButton>
</div>
</div>
<div class="space-y-3 p-3">
<!-- 强制合规项 -->
<div class="mb-1 flex items-center gap-1.5 text-xs font-semibold text-[var(--error-color)]"><span class="inline-block h-2 w-2 rounded-full bg-[var(--error-color)]" /> 强制合规项(驳回前必须处理,否则无法通过)</div>
<div class="flex flex-wrap items-center gap-2 rounded-lg border border-red-100 bg-red-50/50 px-3 py-2 text-sm">
<span class="min-w-0 flex-1 text-slate-700">{{ mandItem }}</span>
<NTag v-if="mandClosed" size="small" type="success">已闭环</NTag>
<NTag v-else size="small" type="warning">未闭环</NTag>
</div>
<!-- 推荐改进项 -->
<div>
<div class="mb-1 flex items-center gap-1.5 text-xs font-semibold text-[var(--warning-color)]"><span class="inline-block h-2 w-2 rounded-full bg-[var(--warning-color)]" /> 推荐改进项(可采纳为行动项,可忽略但须填理由)</div>
<div class="space-y-1.5">
<div v-for="s in suggs" :key="s.id" class="rounded-lg border px-3 py-2 text-sm">
<div class="leading-relaxed text-slate-700">{{ s.text }}</div>
<div class="mt-1.5">
<div v-if="s.st === '待定'" class="flex gap-3">
<NButton text type="success" class="text-xs hover:underline"
@click="acceptSuggest(s)">
采纳为行动项
</NButton>
<NButton text class="text-xs hover:underline"
@click="rejectSuggest(s)">
已知悉,不采纳
</NButton>
</div>
<NTag v-else-if="s.st === '已采纳'" size="small" type="success">已采纳 → 已生成 PSSR 行动项</NTag>
<NTag v-else size="small" type="default">已知悉不采纳(理由已留痕)</NTag>
</div>
</div>
</div>
</div>
<!-- 参考信息 -->
<div>
<div class="mb-1 flex items-center gap-1.5 text-xs font-semibold text-[var(--theme-color)]"><span class="inline-block h-2 w-2 rounded-full bg-[var(--theme-color)]" /> 参考信息(纯参考,无强制力)</div>
<div class="space-y-1.5">
<div class="flex flex-wrap items-center gap-2 rounded-lg border border-[#DFE9F6] bg-[#EAF0F9]/60 px-3 py-2 text-sm">
<span class="min-w-0 flex-1 text-slate-700">{{ similarText }}</span>
<NButton text type="primary" class="hover:underline text-xs" @click="viewArchive('MOC-2025-0112')">查看归档包</NButton>
</div>
<div class="rounded-lg border border-[#DFE9F6] bg-[#EAF0F9]/60 px-3 py-2 text-sm text-slate-700">
{{ experienceText }}
</div>
</div>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-2 text-[11px] leading-5 text-slate-400">
查看、采纳、已阅等操作均由后台自动记录(无感审计),供内审追溯;AI 建议不强制采纳,决策主导权在审批人。驳回重新提交后,AI 将对新版本重新分析一次。
</div>
</div>
</div>
</div>
</NDrawerContent>
</NDrawer>
<!-- 附件在线预览弹窗 -->
<NModal
v-model:show="previewModal"