发起变更-变更申请表完成
This commit is contained in:
Vendored
+2
@@ -68,6 +68,7 @@ declare module 'vue' {
|
||||
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
@@ -206,6 +207,7 @@ declare global {
|
||||
const NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
||||
const NCard: typeof import('naive-ui')['NCard']
|
||||
const NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
const NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
const NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
const NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
const NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import {
|
||||
@@ -22,14 +22,14 @@ interface ApplyForm {
|
||||
level: string;
|
||||
duration: string;
|
||||
urgent: boolean;
|
||||
date: string;
|
||||
restoreDate: string;
|
||||
date: any;
|
||||
restoreDate: any;
|
||||
}
|
||||
const form = ref<ApplyForm>({
|
||||
name: "", dept: "", mainChanges: [""], related: "", purposes: [], effect: "",
|
||||
type: "", level: "", duration: "", urgent: false, date: "", restoreDate: "",
|
||||
type: "", level: "", duration: "", urgent: false, date: null, restoreDate: null,
|
||||
});
|
||||
const tab = ref<string>("pre");
|
||||
const tab = ref<string>("form");
|
||||
const TABS = [
|
||||
{ id: "pre", name: "变更预识别", icon: 'lucide:sparkles' },
|
||||
{ id: "form", name: "变更申请表", icon: 'akar-icons:file' },
|
||||
@@ -259,8 +259,8 @@ const submitApply = () => {
|
||||
duration: form.value.duration === "临时" ? "临时" : "永久",
|
||||
urgent: !!form.value.urgent,
|
||||
status: "审批中",
|
||||
applicant: r.person,
|
||||
org: form.value.dept || r.org,
|
||||
applicant: ROLES[0].person,
|
||||
org: form.value.dept || ROLES[0].org,
|
||||
device: "—",
|
||||
date: today(),
|
||||
purpose: form.value.purposes.join("、") || "提高生产效能",
|
||||
@@ -277,6 +277,122 @@ const submitApply = () => {
|
||||
// emit("openChange", order);
|
||||
};
|
||||
|
||||
const PURPOSE_OPTIONS = [
|
||||
"满足市场需求", "提高生产效能", "改善操作条件", "增加安全性能",
|
||||
"达到环保要求", "法律合规整改", "减少能耗物损", "稳定系统运行",
|
||||
"完善管理制度", "达到质量改进", "设备可靠性升级",
|
||||
];
|
||||
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 levelScores = ref<number[]>([2, 2, 3, 1, 2, 3]);
|
||||
const totalScore = computed(() => levelScores.value.reduce((a, b) => a + b, 0));
|
||||
const levelResult = computed(() => (totalScore.value >= 20 ? "重要变更" : "一般变更"));
|
||||
const RISK_TOOLS_FORM = [
|
||||
{ key: "HAZOP", label: "HAZOP" },
|
||||
{ key: "JSA", label: "JSA" },
|
||||
{ key: "检查表法", label: "设备SCL" },
|
||||
{ key: "通用检查表", label: "风险检查表" },
|
||||
];
|
||||
const recommendTools = () => {
|
||||
const lv = form.value?.level === "重要" ? "重要" : "一般";
|
||||
tools.value = lv === "重要" ? ["HAZOP", "JSA", "检查表法", "通用检查表"] : ["JSA", "检查表法", "通用检查表"];
|
||||
window.$message?.success(`已按「${lv}变更」规则匹配风险分析工具,可继续手动重选`);
|
||||
};
|
||||
const toggleTool = (t: string) => {
|
||||
tools.value = tools.value.includes(t) ? tools.value.filter((x) => x !== t) : [...tools.value, t];
|
||||
};
|
||||
|
||||
const files = ref<string[]>(["小试实验报告.pdf", "杂质初步分析.docx"]);
|
||||
const previewFile = ref<string | null>(null);
|
||||
const DISCIPLINE_SIGNER: Record<string, string> = {
|
||||
技术: "王仪表(技术部)", 仪表: "王仪表(技术部 · 仪表)", 电气: "周设备(设备科 · 电气)", 环保: "王海燕(安全环保部)", 质量: "王海燕(质量部)",
|
||||
};
|
||||
const toggleDiscipline = (d:any) => {
|
||||
const result = disciplines.value.reduce((acc: Record<string, string>, key: string) => {
|
||||
acc[key] = DISCIPLINE_SIGNER[key]; // 如果键不存在,值为 undefined
|
||||
return acc;
|
||||
}, {});
|
||||
signers.value = {...result};
|
||||
};
|
||||
|
||||
const TAB_NAME: Record<string, string> = {
|
||||
pre: "变更预识别", form: "变更申请表", risk: "风险分析记录表",
|
||||
train: "变更培训内容", pssr: "PSSR检查内容", accept: "验收评价", close: "变更关闭确认表",
|
||||
};
|
||||
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 downloadPdf = () => window.$message?.success(`演示:${previewFile.value} 已下载`);
|
||||
const LEVEL_DIMS = [
|
||||
{
|
||||
name: "1. 设备影响", note: "不合适的材质将会导致",
|
||||
options: [
|
||||
{ label: "对设备带来一定的损害及腐蚀", score: 1 },
|
||||
{ label: "对设备带来中等的损害及腐蚀", score: 2 },
|
||||
{ label: "对设备带来明显的损害及腐蚀", score: 3 },
|
||||
{ label: "对设备带来重大的损害及腐蚀", score: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "2. 财务影响", note: "工艺设计错误将会导致",
|
||||
options: [
|
||||
{ label: "一定损失,a<1万人民币", score: 1 },
|
||||
{ label: "中等损失,直接损失1万≤a<10万人民币", score: 2 },
|
||||
{ label: "大量损失,直接损失10万≤a<100万人民币", score: 3 },
|
||||
{ label: "巨额损失,直接损失100万人民币≤a", score: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "3. 质量影响", note: "",
|
||||
options: [
|
||||
{ label: "对质量带来一定的风险(非关键工席、非关键设备设施的变更)", score: 1 },
|
||||
{ label: "对质量带来中等风险(B类原料替代、生产辅助设备变更)", score: 2 },
|
||||
{ label: "对质量带来明显的风险(主要工艺参数、中间体生产工艺变更)", score: 3 },
|
||||
{ label: "对质量有很高风险(主要工艺路线、关键工艺参数变更)", score: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "4. 设计的复杂程度", note: "工艺设计包含了",
|
||||
options: [
|
||||
{ label: "少量的简单系统(单个设备或单个部件)", score: 1 },
|
||||
{ label: "大量的简单系统(多个单类设备或部件)", score: 2 },
|
||||
{ label: "至少一个复杂系统(涉及多设备/工艺相应调整)", score: 3 },
|
||||
{ label: "大量的复杂系统(需要装置配套改造调整)", score: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "5. 工艺设计的成熟度", note: "",
|
||||
options: [
|
||||
{ label: "被频繁使用,且被证明为专利方设计", score: 1 },
|
||||
{ label: "被少量使用,或被证明为专利方设计", score: 2 },
|
||||
{ label: "被相关工业设计方法或参考文献认可(非专利方)", score: 3 },
|
||||
{ label: "新的,未发表的", score: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "6. 工艺危害", note: "物料性质和工艺条件",
|
||||
options: [
|
||||
{ label: "无危害(常压、-10℃≤温度<40℃、无毒、无腐蚀不燃)", score: 1 },
|
||||
{ label: "可燃/负压/低正压(0~0.3MPa)、40℃≤温度<80℃", score: 2 },
|
||||
{ label: "易燃/中压(0.3~1.0MPa)、80℃≤温度<150℃", score: 3 },
|
||||
{ label: "极度危害/高压(>1.0MPa)、温度≥150℃", score: 4 },
|
||||
],
|
||||
},
|
||||
];
|
||||
const finishApprover = () => {
|
||||
sheet.value = null;
|
||||
window.$message?.success('审批流程已更新(底部固定行摘要同步刷新)');
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -323,8 +439,8 @@ const submitApply = () => {
|
||||
</div>
|
||||
<!-- 中间 -->
|
||||
<div class="p-4">
|
||||
<div class="space-y-4 scroll">
|
||||
<!-- ===== 变更内容描述 ===== -->
|
||||
<div class="space-y-4" :class="tab === 'pre'?'scroll':'scroll2'">
|
||||
<!-- ===== 变更预识别 ===== -->
|
||||
<template v-if="tab === 'pre'">
|
||||
<!-- 变更内容描述 -->
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
@@ -452,10 +568,214 @@ const submitApply = () => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- ===== 变更申请表 ===== -->
|
||||
<template v-if="tab === 'form' && form">
|
||||
<!-- 申请部门 / 申请人 -->
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">申请部门 <span class="text-red-500">*</span></div>
|
||||
<NInput v-model:value="form.dept" placeholder="请输入申请部门" />
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">申请人</div>
|
||||
<NInput v-model:value="ROLES[0].person" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 变更目的 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更目的</div>
|
||||
<div class="rounded-lg border border-[#ACC6E5] bg-[#EAF0F9]/40 p-3">
|
||||
<NCheckboxGroup v-model:value="form.purposes">
|
||||
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
||||
<NCheckbox v-for="p in PURPOSE_OPTIONS" :key="p" :value="p" :label="p" />
|
||||
</div>
|
||||
</NCheckboxGroup>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 预期效果 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">预期效果</div>
|
||||
<NInput type="textarea" v-model:value="form.effect" rows="2" placeholder="" />
|
||||
</div>
|
||||
<!-- 主要变更内容 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">主要变更内容 <span class="text-red-500">*</span></div>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div v-for="(m, i) in form.mainChanges" :key="i" class="flex items-center gap-2">
|
||||
<span class="w-5 shrink-0 text-sm text-slate-400">{{ i + 1 }}.</span>
|
||||
<NInput v-model:value="m as string" placeholder="" />
|
||||
<NButton text type="error" @click="form = { ...form, mainChanges: form.mainChanges.filter((_, j) => j !== i) }">
|
||||
<Icon icon="line-md:close-circle" class="size-16px" />
|
||||
</NButton>
|
||||
</div>
|
||||
<NButton text type="primary" class="text-xs" @click="form = { ...form, mainChanges: [...form.mainChanges, ''] }">
|
||||
<Icon icon="ic:round-plus" class="size-14px" /> 添加变更点
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 连带变更内容 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">连带变更内容</div>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div v-for="(m, i) in (form.related ? form.related.split('、') : [''])" :key="i" class="flex items-center gap-2">
|
||||
<span class="w-5 shrink-0 text-sm text-slate-400">{{ i + 1 }}.</span>
|
||||
<NInput v-model:value="m as string" placeholder="连带变更点(如:更新操作规程)" />
|
||||
<NButton text type="error" @click="form = { ...form, related: (form.related ? form.related.split('、') : ['']).filter((_, j) => j !== i).join('、') }">
|
||||
<Icon icon="line-md:close-circle" class="size-16px" />
|
||||
</NButton>
|
||||
</div>
|
||||
<NButton text type="primary" class="text-xs" @click="form = { ...form, related: form.related ? `${form.related}、` : '、' }">
|
||||
<Icon icon="ic:round-plus" class="size-14px" /> 添加连带变更点
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 变更类型 / 变更等级 -->
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更类型 <span class="text-red-500">*</span></div>
|
||||
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
||||
<NRadioGroup v-model:value="form.type">
|
||||
<div class="flex gap-x-2">
|
||||
<NRadio v-for="t in ['工艺', '设备', '管理']" :key="t" :value="t">{{t}}</NRadio>
|
||||
</div>
|
||||
</NRadioGroup>
|
||||
<NButton text type="primary" class="ml-auto text-xs hover:underline" @click="sheet = 'type'">
|
||||
<Icon icon="lucide:sparkles" class="size-12px" />
|
||||
AI 判定说明
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更等级 <span class="text-red-500">*</span></div>
|
||||
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
||||
<NRadioGroup v-model:value="form.level">
|
||||
<div class="flex gap-x-2">
|
||||
<NRadio v-for="t in ['一般', '重要']" :key="t" :value="t">{{t}}</NRadio>
|
||||
</div>
|
||||
</NRadioGroup>
|
||||
<NButton text type="primary" class="ml-auto text-xs hover:underline" @click="sheet = 'level'">
|
||||
<Icon icon="lucide:list-checks" class="size-12px" /> 等级判定表 ({{ totalScore }}分) →
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 变更时限 / 风险分析 -->
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更时限 <span class="text-red-500">*</span></div>
|
||||
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
||||
<NRadioGroup v-model:value="form.duration">
|
||||
<div class="flex gap-x-2">
|
||||
<NRadio v-for="t in ['永久', '临时']" :key="t" :value="t">{{t}}</NRadio>
|
||||
</div>
|
||||
</NRadioGroup>
|
||||
<NCheckbox v-model:checked="form.urgent">紧急</NCheckbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">风险分析</div>
|
||||
<div class="flex flex-1 flex-wrap items-center gap-2 rounded-lg bg-slate-50 px-3 py-2">
|
||||
<NButton size="small" round v-for="t in RISK_TOOLS_FORM" :key="t.key" @click="toggleTool(t.key)" title="点击选用 / 取消;AI 辅助分析在各工具插件内使用"
|
||||
class="text-sm"
|
||||
:ghost="tools.includes(t.key) ? false : true"
|
||||
:type="tools.includes(t.key) ? 'primary' : 'default'"
|
||||
>
|
||||
{{ t.label }}
|
||||
</NButton>
|
||||
<NButton type="primary" ghost dashed size="small" round @click="recommendTools" class="text-xs">
|
||||
按等级重新推荐
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 计划投用时间 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">计划投用时间</div>
|
||||
<div class="flex flex-1 flex-wrap items-center gap-2">
|
||||
<NDatePicker v-model:value="form.date" type="date" class="!max-w-[220px]" />
|
||||
<template v-if="form.duration === '临时'">
|
||||
<span class="whitespace-nowrap text-sm text-slate-500">计划恢复时间 <span class="text-red-500">*</span></span>
|
||||
<NDatePicker v-model:value="form.restoreDate" type="date" class="!max-w-[220px]" />
|
||||
<span class="text-xs text-amber-600">临时变更到期系统自动提醒恢复原状</span>
|
||||
</template>
|
||||
<span v-else class="whitespace-nowrap text-xs text-slate-300">(勾选「临时」后在此并排填写计划恢复时间)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="form.urgent" class="text-xs text-red-500">紧急变更为单独标记:走快速通道,事后限期补办风险分析与完整审批</div>
|
||||
<!-- 所需材料 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">所需材料</div>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="text-[11px] text-slate-400">施工所需材料(生成变更申请表时系统自动生成,支持手动修改)</div>
|
||||
<NInput type="textarea" v-model:value="materialsText" rows="4" placeholder="点击「生成变更申请单」后由系统按变更内容自动生成,也可直接填写" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 资料上传 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">资料上传</div>
|
||||
<div class="flex flex-1 flex-wrap items-center gap-2 rounded-lg border border-dashed p-3">
|
||||
<NButton size="small" class="text-xs" v-for="f in files" :key="f" @click="previewFile = f" title="点击在线预览">
|
||||
<Icon icon="akar-icons:file" class="size-14px" />
|
||||
{{ f }}
|
||||
</NButton>
|
||||
<NButton @click="files = [...files, `资料_${files.length + 1}.pdf`]">
|
||||
<Icon icon="material-symbols:upload" class="size-16px" /> 资料上传
|
||||
</NButton>
|
||||
<span class="text-xs text-slate-400">(点击附件名弹窗预览,支持 PDF / Word / 图片在线查看)</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 需更新的资料 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">需更新的资料</div>
|
||||
<div class="flex flex-1 flex-wrap items-center gap-x-4 gap-y-1.5 rounded-lg bg-slate-50 px-3 py-2">
|
||||
<NCheckboxGroup v-model:value="updateDocs">
|
||||
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
||||
<NCheckbox v-for="p in ['PID图纸', '操作规程', '总图', '设备台账', '工艺卡片', '联锁台账', '应急预案']" :key="p" :value="p" :label="p" />
|
||||
</div>
|
||||
</NCheckboxGroup>
|
||||
<span class="self-center text-xs text-slate-400">(多选,变更关闭前逐项确认上传最新版本)</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 涉及专业 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">涉及专业</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 rounded-lg bg-slate-50 px-3 py-2">
|
||||
<NCheckboxGroup v-model:value="disciplines" @update:value="toggleDiscipline">
|
||||
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
||||
<NCheckbox v-for="p in ['技术', '仪表', '电气', '环保', '质量']" :key="p" :value="p" :label="p" />
|
||||
</div>
|
||||
</NCheckboxGroup>
|
||||
<span class="self-center text-xs text-slate-400">(多选,决定审批流中专业会签/审批的路由范围)</span>
|
||||
</div>
|
||||
<div v-if="Object.keys(signers).length > 0" class="mt-1.5 flex flex-wrap items-center gap-1.5 text-xs text-slate-500">
|
||||
<Icon icon="lucide:users" class="size-12px" />已联动专业会签:
|
||||
<NTag size="small" type="info" v-for="(p, d) in signers" :key="d">{{ d }} · {{ p }}</NTag>
|
||||
<span class="text-slate-400">(在底部「审批流程设置」中可调整人选)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部 -->
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 bg-[#EAF0F9] px-4 py-2.5 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
|
||||
<!-- 全局按钮:下载 / 重新生成 / 确认完毕 -->
|
||||
<div v-if="tab !== 'pre'" class="mt-4 flex flex-wrap items-center justify-between gap-2 border-t p-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<NButton ghost type="success" @click="download">
|
||||
<Icon icon="material-symbols:download" class="size-16px" /> 下载{{ TAB_NAME[tab] }}
|
||||
</NButton>
|
||||
<NButton ghost type="warning" @click="regenerate">
|
||||
<Icon icon="tdesign:refresh" class="size-14px" /> 重新生成{{ TAB_NAME[tab] }}
|
||||
</NButton>
|
||||
<NButton ghost :type="confirmed[tab] ? 'success' : 'primary'" @click="confirmTab(tab)">
|
||||
<Icon icon="ix:success" class="size-16px" /> {{ confirmed[tab] ? '已确认完毕' : '本标签内容确认完毕' }}
|
||||
</NButton>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400">审批人 / 保存 / 提交均在底部固定行,滚动时保持可见</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 bg-[#EAF0F9] px-4 py-2.5 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
|
||||
<template v-if="tab === 'pre'">
|
||||
<span class="text-xs text-slate-400">预识别阶段不设审批人与提交:完成识别与预分析后,请切换至「变更申请表」选择审批人并提交</span>
|
||||
<div class="ml-auto flex shrink-0 gap-2">
|
||||
@@ -471,24 +791,185 @@ const submitApply = () => {
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-slate-500">
|
||||
<Users class="h-3.5 w-3.5 shrink-0 text-[#1D4E9C]" />
|
||||
<Icon icon="lucide:users" class="size-14px" />
|
||||
<span class="min-w-0 truncate">
|
||||
部门审核:{{ approvers.部门审核 }} → 专业会签:{{ Object.keys(signers).length ? Object.values(signers).join('、') : '未涉及专业 · 免会签' }} → 最终批准:{{ approvers.最终批准 }}
|
||||
</span>
|
||||
<button :class="['shrink-0 border-[#6E93C6] bg-white text-[#1D4E9C] hover:bg-[#DFE9F6]']" @click="sheet = 'approver'">
|
||||
<SlidersHorizontal class="h-3 w-3" /> 审批流程设置
|
||||
</button>
|
||||
<NButton ghost size="small" type="primary" @click="sheet = 'approver'">
|
||||
<Icon icon="octicon:sliders-24" class="size-14px" />
|
||||
审批流程设置
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="ml-auto flex shrink-0 items-center gap-2">
|
||||
<button :class="['!h-8 !px-3 !text-xs']" @click="saveDraft">保存</button>
|
||||
<NButton size="small" type="primary" @click="saveDraft">保存</NButton>
|
||||
<span v-if="!form" class="flex items-center text-xs text-amber-600">请先在「变更预识别」页生成变更申请单</span>
|
||||
<button :class="['!h-8 !px-3 !text-xs']" @click="submitApply" :disabled="!form" :title="form ? '提交进入审批流' : '请先生成变更申请单'">
|
||||
提交 <ChevronRight class="h-4 w-4" />
|
||||
</button>
|
||||
<NButton size="small" type="primary" @click="submitApply" :disabled="!form" :title="form ? '提交进入审批流' : '请先生成变更申请单'">
|
||||
提交 <Icon icon="formkit:right" class="size-14px" />
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 附件在线预览弹窗 -->
|
||||
<NModal
|
||||
:show="previewFile !== null"
|
||||
preset="card"
|
||||
:auto-focus="false"
|
||||
:style="{ width: '700px', height: 'auto' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
@update:show="(v: boolean) => !v && (previewFile = null)"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile }}
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||
<span>上传人:{{ ROLES[0].person }}</span><span>上传时间:{{ today() }}</span><span>版本:V1(受控)</span>
|
||||
</div>
|
||||
<div class="flex h-72 flex-col items-center justify-center gap-2 rounded-lg border bg-slate-50 text-slate-400">
|
||||
<Icon icon="akar-icons:file" class="size-42px" />
|
||||
<span class="text-sm">文件在线预览区(演示)</span>
|
||||
<span class="text-xs">正式环境由文档预览服务渲染 PDF / Word / 图片内容,支持缩放、翻页与移动端查看</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-[10px]">
|
||||
<NButton ghost size="small" @click="downloadPdf">
|
||||
<Icon icon="material-symbols:download" class="size-16px" /> 下载
|
||||
</NButton>
|
||||
<NButton size="small" type="primary" @click="previewFile = null">关闭</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
<!-- 侧边抽屉:AI 类型判定 / 等级判定表 / 审批流程设置 -->
|
||||
<NDrawer :show="sheet !== null" @update:show="(v: boolean) => !v && (sheet = null)" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'">
|
||||
<NDrawerContent>
|
||||
<template #header>
|
||||
<div v-if="sheet === 'type'" class="text-base font-semibold text-slate-800">AI 变更类型判定说明</div>
|
||||
<div v-if="sheet === 'level'" class="text-base font-semibold text-slate-800">变更等级判定表</div>
|
||||
<div v-if="sheet === 'approver'" class="text-base font-semibold text-slate-800">审批流程设置</div>
|
||||
</template>
|
||||
<div class="h-full space-y-4 overflow-y-auto">
|
||||
<!-- AI 变更类型判定说明 -->
|
||||
<template v-if="sheet === 'type'">
|
||||
<div class="flex items-center gap-2">
|
||||
<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 type="info" size="small">判定:工艺</NTag>
|
||||
<NTag size="small">置信度:高</NTag>
|
||||
</div>
|
||||
<div class="space-y-3 text-sm leading-relaxed text-slate-700">
|
||||
<p><b>判定结论:</b>本次变更涉及反应温度(80℃→90℃)、保温时间(8h→4h)等工艺参数调整及 DCS 报警/联锁值修改,未涉及设备本体更换或材质变更,依据变更类型规则判定为「工艺」类变更。</p>
|
||||
<p><b>判定依据:</b></p>
|
||||
<ul class="list-disc space-y-1 pl-5">
|
||||
<li>命中规则 2.1:操作参数超出原设计控制范围 → 工艺类</li>
|
||||
<li>命中规则 2.4:DCS 报警值/联锁值调整 → 工艺类(仪表联动)</li>
|
||||
<li>排除规则 3.2:无设备规格型号变化 → 不构成设备类</li>
|
||||
</ul>
|
||||
<p><b>知识库引用:</b>工艺卡片 R-101(Rev B)、DCS 联锁台账、《变更管理制度》第 4.2 条。</p>
|
||||
<p class="rounded-lg bg-amber-50 p-3 text-xs text-amber-700">AI 判定结果仅供参考,最终类型以人工确认为准;如调整为其他类型,系统将记录修改留痕。</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 变更等级判定表 -->
|
||||
<template v-if="sheet === 'level'">
|
||||
<div class="flex items-center gap-2">
|
||||
<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" :type="totalScore >= 20 ? 'error' : 'warning'">
|
||||
{{ levelResult }}
|
||||
</NTag>
|
||||
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(<20 分为一般,≥20 分为重要)</span>
|
||||
</div>
|
||||
<div class="grid gap-3 grid-cols-2">
|
||||
<div v-for="(dim, di) in LEVEL_DIMS" :key="dim.name" class="rounded-lg border p-3">
|
||||
<div class="mb-2 flex items-baseline justify-between gap-2">
|
||||
<span class="text-sm font-semibold text-slate-700">
|
||||
{{ dim.name }}
|
||||
<span v-if="dim.note" class="ml-1 text-xs font-normal text-slate-400">({{ dim.note }})</span>
|
||||
</span>
|
||||
<span class="text-sm text-slate-500">得分 <b class="text-[#17407F]">{{ levelScores[di] }}</b></span>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<button v-for="op in dim.options" :key="op.score" @click="levelScores = levelScores.map((s, i) => (i === di ? op.score : s))"
|
||||
class="flex w-full items-start gap-2 rounded-lg px-2 py-1.5 text-left text-xs transition"
|
||||
:class="levelScores[di] === op.score ? 'bg-[#EAF0F9] text-[var(--theme-color)]' : 'text-slate-600 hover:bg-slate-50 bg-white'"
|
||||
:style="{'--theme-color': themeStore.themeColor}"
|
||||
>
|
||||
<span
|
||||
class="mt-0.5 flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full border"
|
||||
:class="levelScores[di] === op.score ? 'border-[var(--theme-color)]' : 'border-slate-300'"
|
||||
:style="{'--theme-color': themeStore.themeColor}"
|
||||
>
|
||||
<span
|
||||
v-if="levelScores[di] === op.score"
|
||||
class="h-1.5 w-1.5 rounded-full bg-[var(--theme-color)]"
|
||||
:style="{'--theme-color': themeStore.themeColor}"
|
||||
/>
|
||||
</span>
|
||||
<span class="flex-1 leading-snug">{{ op.label }}({{ op.score }}分)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-violet-200 bg-violet-50/50 p-4">
|
||||
<div class="mb-2 flex items-center gap-2 text-sm font-semibold text-violet-700">
|
||||
<Icon icon="lucide:sparkles" class="size-14px" />
|
||||
AI 评分分析过程
|
||||
</div>
|
||||
<div class="space-y-3 text-xs leading-relaxed text-slate-600">
|
||||
<p>我们分析变更内容:缩合反应温度从80℃提高到90℃,反应时间从8小时缩短到4小时。设备材质SS304,设计温度100℃,设计压力0.2MPa,压力不变(常压)。物料为原料A与原料B,未具体说明毒性、腐蚀性。逐维度评分如下。</p>
|
||||
<p><b class="text-[#17407F]">1. 设备影响:</b>温度从80℃升至90℃,仍在设计温度100℃范围内,但接近上限。SS304 在 80→90℃ 下腐蚀速率可能略有增加,但变化不大;保守起见给 2 分(中等损害)。</p>
|
||||
<p><b class="text-[#17407F]">2. 财务影响:</b>变更目的是提升产能,但若设计错误可能导致反应失败、批次报废。温度提高10℃、时间缩短一半,控制不好可能影响产品质量,损失可能中等(1万~10万),取 2 分。</p>
|
||||
<p><b class="text-[#17407F]">3. 质量影响:</b>涉及主要工艺参数变更且有未知杂质(0.05%→0.12%)结构未定,对质量带来明显风险,取 3 分。</p>
|
||||
<p class="text-slate-400">(各维度得分均可人工改判,修改留痕;改判后等级自动回填至申请表)</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 审批流程设置 -->
|
||||
<template v-if="sheet === 'approver'">
|
||||
<p class="text-xs leading-relaxed text-slate-400">审批链:部门审核 → 专业会签 → 最终批准。专业会签人由申请表「涉及专业」勾选自动增减,可在此调整人选;重要变更自动追加安全审查与分管领导终批。</p>
|
||||
<div class="rounded-lg border p-3">
|
||||
<div class="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
|
||||
<NTag type="info" size="small">1</NTag> 部门审核
|
||||
</div>
|
||||
<NSelect :value="approvers.部门审核" :options="DEPT_APPROVER_OPTS"
|
||||
@update:value="(v: string) => approvers = { ...approvers, 部门审核: v }" />
|
||||
</div>
|
||||
<div class="rounded-lg border p-3">
|
||||
<div class="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
|
||||
<NTag type="info" size="small">2</NTag> 专业会签
|
||||
<span class="text-[11px] font-normal text-slate-400">(由「涉及专业」勾选联动)</span>
|
||||
</div>
|
||||
<p v-if="Object.keys(signers).length === 0" class="text-xs text-slate-400">未勾选涉及专业,本环节免会签;如需会签,请回申请表勾选「涉及专业」。</p>
|
||||
<div v-else class="space-y-2">
|
||||
<div v-for="(p, d) in signers" :key="d" class="flex items-center gap-2">
|
||||
<NTag size="small">{{ d }}</NTag>
|
||||
<NSelect class="flex-1" :value="p" :options="signerOpts(p)"
|
||||
@update:value="(v: string) => signers = { ...signers, [d]: v }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border p-3">
|
||||
<div class="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
|
||||
<NTag type="info" size="small">3</NTag> 最终批准
|
||||
<NTag size="small" class="text-11px">制度固定</NTag>
|
||||
</div>
|
||||
<NInput :value="approvers.最终批准" disabled />
|
||||
<p class="mt-1.5 text-[11px] text-slate-400">重要变更:自动追加「安全审查(安全环保部)」与「分管领导终批」节点,无需手动设置。</p>
|
||||
</div>
|
||||
<NButton type="primary" class="w-full" @click="finishApprover">完成设置</NButton>
|
||||
</template>
|
||||
</div>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</template>
|
||||
@@ -499,4 +980,9 @@ const submitApply = () => {
|
||||
overflow-y: auto;
|
||||
@include scrollbar();
|
||||
}
|
||||
.scroll2 {
|
||||
height: calc(100vh - 409px);
|
||||
overflow-y: auto;
|
||||
@include scrollbar();
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user