@@ -0,0 +1,213 @@
< 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.88 MPa → 恢复原设定 0.8 MPa ) < / 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.4 MB ) < / 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 >