Files
moc/src/views/my/initiateChange/modules/pssr.vue
T
2026-09-07 10:27:26 +08:00

170 lines
8.5 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.
<!-- PSSR检查内容 -->
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import Footer from './footer.vue';
import { uploadFileApi } from "@/service/api/file";
const themeStore = useThemeStore();
const props = defineProps(['type','isAiOpen','title','currentId'])
const emit = defineEmits(['save','submit','confirmTab']);
const uploadLoading = ref<boolean>(false);
const uploadFileRef = ref<any>(null);
const isUploaded = ref<boolean>(false);
const editing = ref<any>(null);
const list = ref<{ g: string; type:number; na_flag:number; items: any[] }[]>([
{ g: "一、风险分析行动项落实", type:1, na_flag:0, items: [] },
{ g: "二、现场设备安装检查确认", type:2, na_flag:0, items: [] },
{ g: "三、保护措施确认", type:3, na_flag:0, items: [] },
{ g: "四、资料归档确认", type:4, na_flag:0, items: [] },
]);
const pssrNa = ref<Record<string, boolean>>({});
// 添加检查项
const addItem = (g:any, gi: number) => {
list.value[gi].items.push({id:0, group_type:g.type, content: '新检查项(点击直接编辑内容)', phase: 1, na_flag: 0 });
}
// 上传纸质确认表
const uploadFile = async (e: any) => {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
formData.append('step', '2');
uploadLoading.value = true;
const {error} = await uploadFileApi(props.currentId, formData);
if(!error){
isUploaded.value = true;
window.$message?.success(`已上传 PSSR 纸质确认表:不做内容识别,上传即视为全部检查项完成确认(「不涉及」组除外),原件归档至变更档案`);
}
uploadLoading.value = false;
};
// 保存草稿
const saveDraft = () => {
emit('save');
}
// 提交
const submitConfirm = () => {
emit('submit', props.type);
}
// // 确认标签内容
const confirmTab = (tab:string, val:boolean) => {
emit('confirmTab', tab, val);
}
defineExpose({list})
</script>
<template>
<div class="p-4">
<div class="space-y-4 scroll">
<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="tabler:clipboard-check" class="size-16px text-violet-500" />
PSSR 投用前安全检查内容
</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>
<span class="flex items-center gap-2">
<NButton ghost size="small" :disabled="uploadLoading" class="text-xs" title="上传 PSSR 纸质确认表照片 / 扫描件(JPG / PNG / PDF),系统不做内容识别,上传即视为全部检查项完成确认" @click="uploadFileRef?.click()">
<Icon v-if="uploadLoading" icon="ri:loader-4-fill" class="size-14px animate-spin" />
<Icon v-else icon="material-symbols:upload" class="size-14px" />
上传纸质确认表
</NButton>
<input ref="uploadFileRef" type="file" accept="image/*,.pdf" class="hidden" @change="uploadFile" />
<span class="hidden text-xs text-slate-400 xl:inline">申请阶段点击条目内容直接编辑可增删项</span>
</span>
</div>
<div v-if="isUploaded" class="flex items-center gap-2 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-2 text-xs text-emerald-700">
<Icon icon="ix:success" class="size-16px" />
已上传 PSSR 纸质确认表上传即视为全部检查项完成确认未做内容识别),原件已归档至变更档案
</div>
<div v-for="(g, gi) in list" :key="g.g" class="border" :class="g.na_flag ? 'opacity-70' : ''" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center bg-slate-100 px-3 py-2 text-sm font-medium text-slate-700">
{{ g.g }}
<NTag v-if="g.na_flag" size="small" class="ml-2">不涉及</NTag>
<div class="ml-auto flex items-center">
<NButton v-if="g.items.length" text type="primary" class="text-xs font-normal hover:underline"
:title="pssrNa[g.g] ? '恢复本组检查清单' : '本次变更不涉及本组内容时点击标记,标记后不计入完成统计'"
@click="g.na_flag = g.na_flag ? 0 : 1">
{{ g.na_flag ? '恢复清单' : '标记不涉及' }}
</NButton>
<NButton text type="primary" v-if="!g.na_flag" class="ml-3 text-xs font-normal hover:underline" @click="addItem(g,gi)">
<Icon icon="ic:round-plus" class="size-14px" /> 添加检查项
</NButton>
</div>
</div>
<div v-if="g.na_flag" class="border-t bg-slate-50 px-3 py-3 text-xs text-slate-400">
本组内容经评估本次变更不涉及无现场安装作业 / 不改变保护措施 / 无需归档资料),已不计入 PSSR 完成统计如需恢复检查清单点击右上角恢复清单」。
</div>
<template v-else>
<div v-for="(it, i) in g.items" :key="`${gi}-${i}`" class="flex flex-wrap items-center gap-3 border-t px-3 py-2.5 text-sm">
<span class="w-5 text-slate-400">{{ i + 1 }}</span>
<NInput
v-if="editing === `pssr-c-${gi}-${i}`"
class="flex-1"
v-model:value="it.content"
type="textarea"
autofocus
rows="2"
@blur="editing = null"
/>
<button v-else @click="editing = `pssr-c-${gi}-${i}`" title="点击编辑内容"
class="min-w-0 flex-1 rounded-md px-1 text-left text-slate-800 transition hover:bg-[#EAF0F9] bg-transparent"
>
{{ it.content }}
</button>
<span class="flex items-center gap-1 py-1">
<NRadio :checked="it.phase === 1" :value="1" label="投用前完成"
@change="list = list.map((x, xi) => xi === gi ? { ...x, items: x.items.map((y, yi) => yi === i ? { ...y, phase: 1 } : y) } : x)" />
<NRadio :checked="it.phase === 2" :value="2" label="关闭前完成"
@change="list = list.map((x, xi) => xi === gi ? { ...x, items: x.items.map((y, yi) => yi === i ? { ...y, phase: 2 } : y) } : x)" />
</span>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
@positive-click="list = list.map((x, xi) => xi === gi ? { ...x, items: x.items.filter((_, yi) => yi !== i) } : x)"
>
<template #trigger>
<NButton text type="error" title="删除该项">
<Icon icon="iconamoon:trash" class="size-16px" />
</NButton>
</template>
确定删除吗?
</NPopconfirm>
</div>
</template>
</div>
<div class="rounded-lg bg-violet-50/70 px-4 py-2.5 text-xs text-violet-700">
完成时点说明:「投用前完成 / 关闭前完成」为互斥选择,点击即可切换。投用前完成的项未确认时属地负责人无法确认投用;关闭前完成的项不阻塞投用,但纳入关闭校验清单,未完成时变更主管部门专责无法确认关闭。
</div>
<div class="rounded-lg bg-violet-50/70 px-4 py-2.5 text-xs text-violet-700">
清单由 AI 依据变更类型与风险分析自动生成,专业人员审核后随审批发布;确认方式不作硬性要求——可直接点击确认;资料归档组确认后自动同步「变更关闭确认表」显示已归档。
</div>
</div>
</div>
<Footer
ref="footerRef"
:type="props.type"
:currentId="props.currentId"
:title="props.title"
:isAiOpen="props.isAiOpen"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</template>
<style scoped lang="scss">
.scroll {
height: calc(100vh - 393px);
overflow-y: auto;
}
</style>