Files
moc/src/views/my/closeConfirm/index.vue
T

213 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { getColorWithOpacity } from '@/utils/common';
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const emit = defineEmits<{ go: [v: string, preset?: string];}>();
const restoresDone = ref<boolean>(false);
const restores = ref<{ item: string, done: boolean }[]>([
{ item: "安全阀起跳压力已恢复至原设定 0.8MPa(恢复操作票 CAO-2026-0812)", done: true },
{ item: "恢复后运行参数核对:连续 24h 无起跳、阀体无泄漏", done: false },
{ item: "恢复性检查记录与现场照片已上传", done: false },
]);
const canRestore = ref<boolean>(false);
const readyToClose = ref<boolean>(false);
const checks = ref<{ item: string, done: boolean, note?: string }[]>([
{ item: "变更验收完成(结论:基本达到 · 遗留项 2 项已登记跟踪)", done: true },
{ item: "资料上传齐全(验收报告、运行记录、恢复确认记录)", done: false, note: "缺:更新后的安全阀台账" },
{ item: "行动项全部关闭(4/4,含「关闭前完成」标记项)", done: true },
{ item: "受控文件已更新至生效版(P&ID Rev.4、操作规程)", done: false, note: "安全阀台账待更新发布" },
{ item: "培训考核完成(12/12 人),签到与成绩已归档", done: true },
]);
const archiving = ref<boolean>(false);
const step = ref<number>(0);
const archivedHash = ref<string | null>(null);
const allDone = computed(() => checks.value.every((c: { done: boolean }) => c.done));
const isCloser = ref<boolean>(true);
const startArchive = () => { step.value = 0; archiving.value = true; };
// 关闭即归档:自动归档八步流程
const ARCHIVE_STEPS = [
"关闭触发:接收「同意关闭」归档指令(不可逆)",
"文件收集:申请表 / 审批记录 / 风险分析 / 培训 / PSSR / 验收单生成 PDF",
"附件归集:原始附件、培训签到照片、PSSR 现场照片、验收签字文件",
"受控文件复制:生效版与被替代旧版物理复制,与工作区脱离",
"清单生成:自动生成归档索引与文件更新清单(新旧版本对照)",
"打包锁定:写入归档区(装置 / 年份 / 变更单号),设为只读",
"哈希存证:计算归档包 SHA-256 哈希值入库,防篡改校验",
"状态更新:变更为「已关闭-已归档」,首页数据同步",
];
const closedAt = ref<string | null>(null);
const OVERDUE_LIST = [
{ id: "MOC-2026-R01-0039", title: "V-102 安全阀起跳压力临时调整", stage: "待关闭", days: 4 },
{ id: "MOC-2026-R02-0031", title: "冷冻盐水泵 P-103 叶轮材质变更", stage: "待验收", days: 7 },
{ id: "MOC-2026-R01-0029", title: "操作规程修订(夏季工况)", stage: "审批中", days: 3 },
];
</script>
<template>
<NSpace vertical :size="16">
<!-- 关闭确认 -->
<NCard
:bordered="false"
size="small"
:style="{
borderRadius: themeStore.themeRadius + 'px',
'--error-color': themeStore.otherColor.error,
'--success-color': themeStore.otherColor.success,
'--success-bg': getColorWithOpacity(themeStore.otherColor.success, 0.1),
'--warning-color': themeStore.otherColor.warning,
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning, 0.1),
'--theme-color': themeStore.themeColor,
'--theme-bg': getColorWithOpacity(themeStore.themeColor, 0.1),
}"
>
<div class="space-y-4">
<NCard :bordered="true" :content-style="{ padding: '0' }" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="py-3 px-4 border-b flex items-center justify-between">
<div class="flex items-center gap-2 text-base font-semibold">
<Icon icon="system-uicons:reset" class="size-16px text-[var(--error-color)]" />临时变更恢复确认 · 期限 30 天
</div>
<NTag v-if="restoresDone" size="small" type="success">恢复确认完成</NTag>
<NTag v-else size="small" type="error">已超期 4 天 · 已升级上报刘副总</NTag>
</div>
<div class="p-4">
<div class="flex flex-wrap gap-x-6 gap-y-1 text-xs text-slate-500">
<span>计划恢复时间:2026-08-14(临时调整起跳压力 0.88MPa → 恢复原设定 0.8MPa)</span>
<span class="text-[var(--error-color)]">超期未恢复:每日 08:00 提醒申请人与属地负责人,已自动升级上报分管领导</span>
</div>
<div class="space-y-2 pt-3">
<div v-for="(r, i) in restores" :key="r.item" class="flex items-center gap-3 rounded-lg border p-3"
:class="r.done ? 'border-emerald-200 bg-emerald-50/40' : 'border-amber-200 bg-amber-50/40'">
<Icon v-if="r.done" icon="material-symbols:check-circle-outline" class="size-20px text-[var(--success-color)]" />
<Icon v-else icon="system-uicons:reset" class="size-18px text-[var(--warning-color)]" />
<div class="flex-1 text-sm">{{ r.item }}</div>
<NButton v-if="!r.done && canRestore" size="small" ghost class="text-xs"
@click="restores = restores.map((x, xi) => (xi === i ? { ...x, done: true } : x)); toast.success('演示:恢复确认完成(移动端可拍照上传凭证)');">
<Icon icon="ant-design:camera-outlined" class="size-16px mr-1" />
确认恢复
</NButton>
<span v-if="!r.done && !canRestore" class="text-xs text-slate-400">待申请人 / 属地负责人确认</span>
</div>
</div>
<div v-if="restoresDone" class="mt-3 rounded-lg bg-emerald-50 px-3 py-2 text-xs text-emerald-700">
临时变更恢复确认完成,恢复操作票、参数核对记录与照片将自动纳入归档包「5-验收记录」
</div>
</div>
</NCard>
<NCard :bordered="true" :content-style="{ padding: '0' }" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="py-3 px-4 border-b flex items-center justify-between">
<div class="flex items-center gap-2 text-base font-semibold">
<Icon icon="material-symbols:check-circle-outline" class="size-18px text-slate-500" />关闭校验 · MOC-2026-R01-0039(紧急变更补办)
</div>
<NTag v-if="readyToClose" size="small" type="success">校验全绿 · 已推送专责</NTag>
<NTag v-else size="small" type="warning">超期 4 天</NTag>
</div>
<div class="p-4">
<div class="flex items-center gap-2 rounded-lg bg-[var(--theme-bg)] px-3 py-2 text-xs text-[var(--theme-color)]">
<Icon icon="lucide:bell" class="size-14px" />
前置条件(验收完成 / 资料齐全 / 行动项关闭 / 恢复确认 / 受控文件更新)全部满足后,系统自动推送变更主管部门专责(王海燕 · 安全环保部),并抄送申请人与属地负责人
</div>
<div class="space-y-2 pt-3">
<div v-for="(c, i) in checks" :key="c.item" class="flex items-center gap-3 rounded-lg border p-3"
:class="c.done ? 'border-emerald-200 bg-emerald-50/40' : 'border-red-200 bg-[#FFF2F0]'">
<Icon v-if="c.done" icon="material-symbols:check-circle-outline" class="size-20px text-[var(--success-color)]" />
<Icon v-else icon="ant-design:close-circle-outlined" class="size-18px text-[var(--error-color)]" />
<div class="flex-1 text-sm">{{ c.item }}<span v-if="c.note" class="ml-2 text-xs text-[var(--error-color)]">{{ c.note }}</span></div>
<NButton v-if="!c.done" text type="primary"
@click="checks = checks.map((x, xi) => (xi === i ? { ...x, done: true } : x)); toast.success('演示:资料已补充上传,关闭校验清单已同步更新并重新推送专责');">去补充</NButton>
</div>
</div>
<!-- 关闭按钮区 -->
<div v-if="!archivedHash && !archiving" class="mt-4 space-y-2">
<div v-if="readyToClose" class="flex items-center gap-2 rounded-lg bg-[var(--theme-bg)] px-3 py-2 text-xs text-[var(--theme-color)]">
<Icon icon="lucide:bell" class="size-14px" />
系统已于今日 09:12 自动推送关闭待办至变更主管部门专责(王海燕)· 系统消息 + 企业微信
</div>
<div class="flex flex-wrap items-center justify-between gap-2 rounded-lg bg-slate-50 p-3">
<span class="text-sm text-slate-500">
{{ !restoresDone
? '临时变更尚未完成恢复确认,禁止关闭(恢复确认是临时变更关闭的前置条件)'
: allDone
? isCloser
? '全部条件满足。确认关闭后系统记录关闭时间与关闭人,并自动生成归档包(副本归档 · 只读锁定 · 哈希存证)'
: '全部条件满足,待变更主管部门专责(王海燕 · 安全环保部)确认关闭'
: '存在未满足条件,禁止关闭;确需例外须填写说明并经分管领导确认' }}
</span>
<NButton v-if="isCloser" size="small" type="primary" :disabled="!readyToClose" class="text-xs" @click="startArchive">
<Icon icon="uil:lock" class="size-14px mr-1" /> 确认关闭并归档
</NButton>
<span v-else class="text-xs text-[var(--warning-color)]">仅变更主管部门专责可确认关闭 · 切换至「王海燕 · 安全工程师」体验</span>
</div>
</div>
<!-- 自动归档流程 -->
<div v-if="archiving" class="mt-4 space-y-1.5 rounded-lg border border-[var(--theme-color)] bg-[var(--theme-bg)] p-4">
<div class="mb-2 flex items-center gap-2 text-sm font-semibold text-[var(--theme-color)]">
<Icon icon="ri:loader-4-fill" class="size-18px animate-spin" />
系统自动归档中,无需人工整理…
</div>
<div v-for="(s, i) in ARCHIVE_STEPS" :key="s" class="flex items-center gap-2 text-xs"
:class="i < step ? 'text-[var(--success-color)]' : i === step ? 'font-medium text-[var(--theme-color)]' : 'text-slate-400'">
<Icon v-if="i < step" icon="material-symbols:check-circle-outline" class="size-16px" />
<Icon v-else-if="i === step" icon="ri:loader-4-fill" class="size-16px animate-spin" />
<span v-else class="h-3.5 w-3.5 shrink-0 rounded-full border border-slate-300"></span>
{{ i + 1 }}. {{ s }}
</div>
</div>
<!-- 归档完成 -->
<div v-if="archivedHash" class="mt-4 space-y-3 rounded-lg border border-emerald-300 bg-emerald-50/60 p-4">
<div class="flex flex-wrap items-center gap-2 text-sm font-semibold text-emerald-800">
<Icon icon="uil:lock" class="size-18px" /> 已关闭-已归档 · 归档包只读锁定,任何角色不可修改或删除
</div>
<div class="grid gap-1.5 text-xs text-emerald-800/90 sm:grid-cols-2">
<span>关闭时间:{{ closedAt }} · 关闭人:王海燕(变更管理专责 · 安全环保部)</span>
<span>归档路径:归档区 / 01-常减压装置 / 2026 / MOC-2026-R01-0039/</span>
<span class="flex items-center gap-1"><Hash class="h-3 w-3" /> SHA-256:{{ archivedHash }}</span>
<span>归档内容:6 类文件夹 + 归档索引.txt(共 24 个文件,18.4MB)</span>
<span>防篡改:系统定期校验哈希值,异常自动告警并从备份恢复</span>
</div>
<div class="flex gap-2">
<NButton size="small" type="success" class="text-xs" @click="emit('go', 'knowledge')">
<Icon icon="lucide:folder-archive" class="size-14px mr-1" /> 查看归档包
</NButton>
<NButton size="small" type="primary" class="text-xs" @click="toast.success('演示:下载完整归档包(ZIP,用于外部审计)')">
<Icon icon="material-symbols:download" class="size-18px" /> 下载 ZIP
</NButton>
</div>
</div>
</div>
</NCard>
<NCard :bordered="true" :content-style="{ padding: '0' }" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="py-3 px-4 border-b flex items-center justify-between">
<div class="flex items-center gap-2 text-base font-semibold">
<Icon icon="proicons:alert-triangle" class="size-18px text-slate-500" />关闭提醒
</div>
</div>
<div class="space-y-2 p-4">
<div v-for="o in OVERDUE_LIST" :key="o.id" class="flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-3 py-2">
<div class="min-w-0">
<div class="truncate text-sm">{{ o.title }}</div>
<div class="text-xs text-slate-500">{{ o.id }} · {{ o.stage }}</div>
</div>
<div class="flex items-center gap-2">
<NTag size="small" type="error">超期 {{ o.days }} 天</NTag>
<NButton size="small" class="text-xs" @click="toast.info('演示:已催办(系统消息 + 企业微信)')">催办</NButton>
</div>
</div>
</div>
</NCard>
</div>
</NCard>
</NSpace>
</template>