变更关闭确认表完成

This commit is contained in:
2026-08-14 15:47:16 +08:00
parent efcbcb4321
commit 2f4bc63cfa
+172 -1
View File
@@ -31,7 +31,7 @@ const form = ref<ApplyForm>({
name: "", dept: "", mainChanges: [""], related: "", purposes: [], effect: "",
type: "", level: "", duration: "", urgent: false, date: null, restoreDate: null,
});
const tab = ref<string>("accept");
const tab = ref<string>("close");
const TABS = [
{ id: "pre", name: "变更预识别", icon: 'lucide:sparkles' },
{ id: "form", name: "变更申请表", icon: 'akar-icons:file' },
@@ -527,6 +527,42 @@ const acceptRows = ref([
]);
const acceptNote = ref("");
// ---------- 变更关闭确认表 ----------
const closeFold = ref<Record<string, boolean>>({});
const uploadDoc = ref<string | null>(null);
const uploadSel = ref<string | null>(null);
const UPDATE_DOC_KW: Record<string, string> = { 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;
}
</script>
<template>
@@ -1250,6 +1286,81 @@ const acceptNote = ref("");
流程说明投用运行满验收周期后系统向车间属地与涉及专业推送验收任务各责任单位按上表逐项给出评估结论并上传佐证数据报表 / 化验报告 / 现场照片全部完成后汇总至综合验收结论结论与佐证资料一并纳入变更关闭校验验收不通过时退回整改或评估恢复原状
</div>
</template>
<!-- 变更关闭确认表 -->
<template v-if="tab === 'close'">
<div class="flex flex-wrap items-center gap-3">
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
<Icon icon="quill:folder-open" class="size-16px" />
变更关闭确认表资料归档清单
</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" class="text-xs">AI 辅助判定一次 · 支持人工改判留痕</NTag>
<span class="ml-auto text-xs text-slate-400">判定为的资料全部归档后变更主管部门专责方可确认关闭</span>
</div>
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center gap-3 bg-slate-100 px-3 py-2 text-xs font-medium text-slate-500">
<span class="w-5" />
<span class="flex-1">资料名称与申请表需更新的资料勾选联动勾选资料标记需更新版本</span>
<span class="w-48 text-center">是否涉及AI 判定可改判</span>
<span class="w-32 text-center">归档状态</span>
</div>
<template v-for="cat in (['图纸与规程类', '台账与制度类', '过程与结果文件类'])" :key="cat">
<div v-if="closeDocs.filter((d) => d.cat === cat).length">
<button @click="closeFold = { ...closeFold, [cat]: !closeFold[cat] }"
class="flex w-full items-center gap-2 border-t bg-slate-50 px-3 py-2 text-left text-xs font-semibold text-slate-600 transition hover:bg-[#EAF0F9]">
<Icon icon="formkit:right" class="size-14px text-slate-400 transition" :class="closeFold[cat] ? '' : 'rotate-90'" />
{{ cat }}
<span class="font-normal text-slate-400">
{{ closeDocs.filter((d) => d.cat === cat).length }} · 已归档
{{ closeDocs.filter((d) => d.cat === cat && d.scope === '是' && d.archived).length }}/{{ closeDocs.filter((d) => d.cat === cat && d.scope === '是').length }}
</span>
<span class="ml-auto text-[11px] font-normal text-slate-400">{{ closeFold[cat] ? '点击展开' : '点击折叠' }}</span>
</button>
<template v-if="!closeFold[cat]">
<div v-for="d in closeDocs.filter((x) => x.cat === cat)" :key="d.name" class="flex flex-wrap items-center gap-3 border-t px-3 py-2.5 text-sm">
<span class="w-5 text-slate-400">{{ closeDocs.indexOf(d) + 1 }}</span>
<span class="min-w-0 flex-1 text-slate-800">
{{ d.name }}
<NTag v-if="needUpdate(d.name)" size="small" type="info" class="ml-2">需更新版本</NTag>
</span>
<span class="flex w-48 shrink-0 justify-center gap-1.5">
<NButton size="tiny" round v-for="v in (['是', '否', '不涉及'])" :key="v"
@click="changeScope(d, v)"
class="text-[11px]"
:type="d.scope === v ? 'primary' : 'default'">
{{ v }}
</NButton>
</span>
<span class="flex w-32 shrink-0 items-center justify-center gap-2">
<template v-if="d.scope === '是'">
<NTag v-if="d.archived" size="small" type="success">已归档</NTag>
<template v-else>
<NTag size="small" type="warning">待上传</NTag>
<NButton text type="primary" class="text-xs hover:underline" @click="uploadDoc = d.name; uploadSel = null;">上传</NButton>
</template>
</template>
<NTag v-else size="small">{{ d.scope }}</NTag>
</span>
</div>
</template>
</div>
</template>
</div>
<div class="flex flex-wrap items-center gap-4 text-xs text-slate-500">
<span>
统计涉及 <b class="text-[#17407F]">{{ closeDocs.filter((d) => d.scope === '是').length }}</b> ·
已归档 <b class="text-emerald-600">{{ closeDocs.filter((d) => d.scope === '是' && d.archived).length }}</b> ·
待上传 <b class="text-amber-600">{{ closeDocs.filter((d) => d.scope === '是' && !d.archived).length }}</b> ·
需更新版本 <b class="text-[#1D4E9C]">{{ closeDocs.filter((d) => needUpdate(d.name)).length }}</b> 联动申请表勾选
</span>
<span class="text-slate-400">资料上传支持 PC / 移动端拍照上传版本受控</span>
</div>
<div class="rounded-lg bg-[#EAF0F9] px-4 py-2.5 text-xs leading-5 text-[#17407F]">
关闭校验说明判定为的资料项全部归档后系统将全部流程完毕信号推送变更主管部门专责专责确认后点击关闭系统记录变更关闭时间 / 不涉及 AI 辅助判定一次人工改判须填写理由并留痕临时变更恢复完成后恢复操作票与参数核对记录自动追加至本清单
</div>
</template>
</div>
</div>
@@ -1464,11 +1575,71 @@ const acceptNote = ref("");
</div>
</NDrawerContent>
</NDrawer>
<!-- 资料上传弹窗 -->
<NModal
:show="uploadDoc !== null"
preset="card"
:auto-focus="false"
:style="{ width: '550px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
@update:show="(v: boolean) => !v && (uploadDoc = null)"
>
<template #header>
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
<Icon icon="material-symbols:upload" class="size-20px" /> 资料上传归档 {{ uploadDoc }}
</div>
</template>
<div class="space-y-3">
<div>
<div class="mb-1 text-xs font-medium text-slate-500">系统文档存储地址受控目录</div>
<div class="flex items-center gap-2 rounded-lg border bg-slate-50 px-3 py-2">
<Icon icon="quill:folder-open" class="size-16px" />
<code class="min-w-0 flex-1 truncate text-xs text-slate-600">{{ uploadDocInfo?.storePath }}</code>
<NButton text type="primary" class="text-xs hover:underline" @click="openDir">打开目录</NButton>
</div>
</div>
<div>
<div class="mb-1 text-xs font-medium text-slate-500">方式一从存储地址选择资料</div>
<div class="space-y-1.5">
<p v-for="f in uploadDocInfo?.candidates" :key="f" @click="uploadSel = f"
class="flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-left text-xs transition cursor-pointer"
:class="uploadSel === f ? 'border-[var(--theme-color)] bg-[var(--theme-color)] text-[var(--theme-color)] text-white' : 'bg-white text-slate-600 hover:border-[var(--theme-color)] hover:text-[var(--theme-color)]'"
:style="{'--theme-color': themeStore.themeColor}"
>
<Icon icon="akar-icons:file" class="size-14px mr-1" /> {{ f }}
<Icon v-if="uploadSel === f" icon="ix:success" class="ml-auto size-16px" />
</p>
</div>
</div>
<div>
<div class="mb-1 text-xs font-medium text-slate-500">方式二拖拽本地文件到区域</div>
<button @click="uploadFiles"
class="flex h-20 w-full flex-col items-center justify-center gap-1 rounded-lg border-2 border-dashed px-3 text-xs transition"
:class="uploadSel?.startsWith('本地文件') ? 'border-[var(--theme-color)] bg-[#EAF0F9] text-[var(--theme-color)]' : 'border-slate-300 text-slate-400 hover:border-[var(--theme-color)] hover:text-[var(--theme-color)]'"
:style="{'--theme-color': themeStore.themeColor}"
>
<Icon icon="material-symbols:upload" class="size-24px" />
{{ uploadSel?.startsWith('本地文件') ? uploadSel : '将资料文件拖拽到此处,或点击选择本地文件' }}
</button>
</div>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<NButton @click="uploadDoc = null">取消</NButton>
<NButton type="primary" :disabled="!uploadSel" @click="uploadConfirm">
确认上传归档
</NButton>
</div>
</template>
</NModal>
</NCard>
</NSpace>
</template>
<style scoped lang="scss">
:deep(.n-button .n-button__content) {
width: 100%;
}
.scroll {
height: calc(100vh - 326px);
overflow-y: auto;