待投用变更页面完成

This commit is contained in:
2026-09-24 16:03:36 +08:00
parent c6ebecda4b
commit 252b960f04
2 changed files with 159 additions and 120 deletions
+10
View File
@@ -0,0 +1,10 @@
import { request } from '../request';
// 列表查询
export function pendingChangeListApi(params:{page_num:number,page_size:number}) {
return request({
url: `/api/pssr/pending-use`,
method: 'get',
params,
});
}
+149 -120
View File
@@ -1,101 +1,110 @@
<!-- 待投用变更 -->
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { getColorWithOpacity } from '@/utils/common'; import { localStg } from '@/utils/storage';
import previewComponent from '@/components/previewFile/index.vue'
import { tagTextColor, tagBgColor, tagBorderColor, downloadFileFun } from '@/utils/common';
import { systemConfigApi } from '@/service/api/system';
import { pendingChangeListApi } from '@/service/api/pendingChange';
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush(); const { routerPushByKey } = useRouterPush();
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const btnLoading = ref<boolean>(false); // 组织
const useTime = ref<any>(null); const org = ref<string>(localStg.get('userInfo')?.department?.label || '');
const useTimeModal = ref<boolean>(false); // 列表
const currentRow = ref<any>(null);
const list = ref<any[]>([]); const list = ref<any[]>([]);
const lists = [ // 分页
{ const pagination = ref<{page: number, pageSize: number, itemCount: number}>({
id: "MOC-2026-R02-0038",statusDetail: "培训 / PSSR 资料已传 · 待确认投用", page: 1,
approveTime: "2026-07-31", applicant: "吴强", level: "一般", type: "工艺", pageSize: 20,
title: 'DCS 罐区液位报警值 LIA-301 调整', status: '待PSSR', duration: "永久", itemCount: 0,
useTime: "", planUse: "2026-08-01", pssr: "PSSR 检查确认表-签字版.pdf", })
hasChange: true, // 变更类型列表
}, const typeList = ref<any[]>([]);
{ // 变更等级列表
id: "MOC-2026-R02-0038",statusDetail: "培训 / PSSR 资料已传 · 待确认投用", const levelList = ref<any[]>([]);
approveTime: "2026-07-31", applicant: "吴强", level: "重要", type: "工艺", // 附件在线预览弹窗
title: 'DCS 罐区液位报警值 LIA-301 调整', status: '待PSSR', duration: "临时", const previewModal = ref<boolean>(false);
useTime: "2026-07-16 06:30", planUse: "2026-08-01", pssr: "", // 附件在线预览文件
hasChange: true, const previewFile = ref<any>(null);
},
];
const timePickerProps = {
format: 'HH:mm', // 时间选择器面板上显示的格式
seconds: false // 隐藏秒选择器[reference:3][reference:4]
}
// 禁用今天之前的日期(今天可选)
const disabledDate = (ts: number) => {
const today = new Date()
today.setHours(0, 0, 0, 0) // 今天的 00:00:00
return ts < today.getTime() // 时间戳小于今天 0 点则禁用
}
// 获取列表数据
const getList = () => {
loading.value = true;
setTimeout(() => {
list.value = lists;
loading.value = false;
}, 1000);
}
// 查看 // 查看
const viewFile = (record: any) => { const viewFile = (file: any) => {
window.$message?.info(`预览「${record.pssr}」(上传件不做内容识别,原件已随单归档)`) previewFile.value = file;
previewModal.value = true;
}; };
// 下载 // 下载
const downloadFile = (record: any) => { const downloadFile = (file: any) => {
window.$message?.success(`下载「${record.pssr}」`) downloadFileFun(file.file_url, file.file_name);
}; };
// 打开变更单 // 打开变更单
const openChange = (record: any) => { const openChange = (record: any) => {
routerPushByKey('details_index', { query: { params: JSON.stringify(record) } }) routerPushByKey("details_index",{ query: { id: record?.id?.toString(),status:record?.status?.toString() || '',key:'my_pendingchange' } });
}; };
// 填写投用日期
const fillUseTime = (record: any) => { // 分页改变
useTime.value = null; const pageChange = (page: number) => {
currentRow.value = record; pagination.value.page = page;
useTimeModal.value = true; getList();
};
// 保存投用日期
const saveUseTime = () => {
if (!useTime.value) {
return window.$message?.warning("请选择投用时间");
}
btnLoading.value = true;
setTimeout(() => {
btnLoading.value = false;
useTimeModal.value = false;
}, 1000);
} }
// 匹配对象
const matchObj= (list:any[],val:any) => {
const item = list.find((item:any) => item.value == val);
return item || null;
}
// 获取列表数据
const getList = async () => {
loading.value = true;
const {data,error} = await pendingChangeListApi({
page_num:pagination.value.page,
page_size:pagination.value.pageSize,
});
if(!error){
list.value = data?.list || [];
pagination.value.itemCount = data?.total || 0;
}
loading.value = false;
}
// 获取配置
const getConfig = async () => {
loading.value = true;
const {data,error} = await systemConfigApi();
if(!error){
// 解析 变更类型设置
const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type');
if (changeTypeGroup) {
typeList.value = typeList.value.concat(changeTypeGroup.items);
}
// 解析 变更等级设置
const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level');
if (changeLevelGroup) {
levelList.value = levelList.value.concat(changeLevelGroup.items);
}
getList();
}
loading.value = false;
}
onMounted(() => { onMounted(() => {
getList(); getConfig();
}) })
</script> </script>
<template> <template>
<NSpace vertical :size="16"> <NSpace vertical :size="16">
<!-- PSSR监督 -->
<NSpin :show="loading" size="small"> <NSpin :show="loading" size="small">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }"> <NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex flex-wrap items-center gap-2 mb-3"> <div class="flex flex-wrap items-center gap-2 mb-3">
<span class="text-sm font-semibold text-slate-700">待投用变更(本车间 · 一车间)</span> <span class="text-sm font-semibold text-slate-700">待投用变更(本车间 · {{ org }})</span>
<NTag size="small" type="primary">{{ list.length }} 起</NTag> <NTag size="small" type="primary">{{ pagination.itemCount }} 起</NTag>
<span class="ml-auto text-[11px] text-slate-400">按审批通过时间排序 · 投用时间由发起人在资料关闭页填报 · 支持属地负责人在此代录投用日期</span> <span class="ml-auto text-[11px] text-slate-400">按审批通过时间排序 · 投用时间由发起人在资料关闭页填报 · 支持属地负责人在此代录投用日期</span>
</div> </div>
<div class="scroll"> <div class="scroll">
@@ -107,28 +116,51 @@ onMounted(() => {
}" }"
> >
<div v-for="(s, i) in list" :key="`${s.id ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-x-3 gap-y-2 border p-4" :style="{ borderRadius: themeStore.themeRadius + 'px' }"> <div v-for="(s, i) in list" :key="`${s.id ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-x-3 gap-y-2 border p-4" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NTag v-if="s.status==='待PSSR'" type="warning" size="small">{{ s.status }}</NTag> <!-- 状态标签 -->
<NTag v-if="s.status==='待关闭'" type="error" size="small">{{ s.status }}</NTag> <NTag v-if="s.status==='APPROVING'" size="small" type="warning">审批中</NTag>
<NTag v-if="s.status==='实施中'" type="primary" size="small">{{ s.status }}</NTag> <NTag v-if="s.status==='APPROVED'" size="small" type="success">已通过</NTag>
<NTag v-if="s.status==='待验收'" type="success" size="small">{{ s.status }}</NTag> <NTag v-if="s.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
<NTag v-if="s.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
<NTag v-if="s.status==='CLOSED'" size="small" type="success">已关闭</NTag>
<NTag v-if="s.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
<NTag v-if="s.status==='REJECTED'" size="small" type="error">已驳回</NTag>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-1.5"> <div class="flex flex-wrap items-center gap-1.5">
<p class="truncate text-sm font-medium text-slate-800">{{ s.title }}</p> <p class="truncate text-sm font-medium text-slate-800">{{ s.title }}</p>
<NTag v-if="s.level==='一般'" size="small">{{s.level}}</NTag> <!-- 等级 -->
<NTag v-if="s.level==='重要'" size="small" type="error">{{s.level}}</NTag> <NTag
<NTag size="small" type="primary">{{s.type}}</NTag> size="small"
<NTag v-if="s.duration==='永久'" size="small">{{s.duration}}</NTag> :color="{
<NTag v-if="s.duration==='临时'" size="small" type="warning">{{s.duration}}</NTag> color: tagBgColor(matchObj(levelList,s.change_level)?.color),
textColor: tagTextColor(matchObj(levelList,s.change_level)?.color),
borderColor: tagBorderColor(matchObj(levelList,s.change_level)?.color),
}"
>
{{ matchObj(levelList,s.change_level)?.label }}
</NTag>
<!-- 类型 -->
<NTag
size="small"
:color="{
color: tagBgColor(matchObj(typeList,s.change_type)?.color),
textColor: tagTextColor(matchObj(typeList,s.change_type)?.color),
borderColor: tagBorderColor(matchObj(typeList,s.change_type)?.color),
}"
>
{{ matchObj(typeList,s.change_type)?.label }}
</NTag>
<NTag v-if="s.duration_type===2" size="small" type="warning">临时</NTag>
<NTag v-if="s.duration_type===1" size="small" type="info">永久</NTag>
</div> </div>
<p class="mt-0.5 text-[11px] text-slate-400">{{ s.id }} · {{ s.applicant }}</p> <p class="mt-0.5 text-[11px] text-slate-400">{{ s.change_no }} · {{ s.applicant_name }}</p>
<p class="text-slate-400 text-[11px]">审批通过时间:<span class="font-medium text-slate-600">{{ s.approveTime ?? '—' }}</span></p> <p class="text-slate-400 text-[11px]">审批通过时间:<span class="font-medium text-slate-600">{{ s.approve_time?.slice(0,16) }}</span></p>
<p class="text-slate-400 text-[11px]">变更投用时间:<span class="font-medium text-[var(--warning-color)]">{{ s.useTime ? s.useTime : `待投用(计划 ${s.planUse})` }}</span></p> <p class="text-slate-400 text-[11px]">变更投用时间:<span class="font-medium text-[var(--warning-color)]">{{ s.use_time ? s.use_time?.slice(0,10) : `待投用(计划 ${s.plan_use_date?.slice(0,10)})` }}</span></p>
<p class="mt-0.5 text-[11px] text-slate-400">{{ s.statusDetail }}</p>
</div> </div>
<div class="flex items-center gap-1.5 rounded-md border bg-slate-50/60 px-2 py-1"> <div class="flex items-center gap-1.5 rounded-md border bg-slate-50/60 px-2 py-1">
<Icon icon="akar-icons:file" class="size-14px text-[var(--theme-color)]" /> <Icon icon="akar-icons:file" class="size-14px text-[var(--theme-color)]" />
<span class="text-[11px] font-medium text-slate-600">PSSR 确认表</span> <span class="text-[11px] font-medium text-slate-600">PSSR 确认表</span>
<template v-if="s.pssr"> <template v-if="s.pssr_attachment">
<NButton ghost size="tiny" class="text-11px" @click="viewFile(s)">查看</NButton> <NButton ghost size="tiny" class="text-11px" @click="viewFile(s)">查看</NButton>
<NButton ghost size="tiny" class="text-11px" @click="downloadFile(s)">下载</NButton> <NButton ghost size="tiny" class="text-11px" @click="downloadFile(s)">下载</NButton>
</template> </template>
@@ -136,60 +168,57 @@ onMounted(() => {
<span class="text-[11px] text-[var(--warning-color)]">待传</span> <span class="text-[11px] text-[var(--warning-color)]">待传</span>
</template> </template>
</div> </div>
<NButton v-if="!s.useTime" size="small" type="primary" class="text-xs" @click="fillUseTime(s)">填写投用日期</NButton> <NButton size="small" ghost class="text-xs" @click="openChange(s)">打开变更单</NButton>
<NButton v-if="s.hasChange" size="small" ghost class="text-xs" @click="openChange(s)">打开变更单</NButton>
</div> </div>
<div v-if="list.length === 0" class="h-100% flex items-center justify-center"> <div v-if="list.length === 0" class="h-100% flex items-center justify-center">
<NEmpty description="暂无数据"></NEmpty> <NEmpty description="暂无数据"></NEmpty>
</div> </div>
</div> </div>
</div> </div>
</NCard> <div class="flex justify-end mt-3">
<!-- 填写投用日期弹窗 --> <NPagination
<NModal v-model:page="pagination.page"
v-model:show="useTimeModal" :page-size="pagination.pageSize"
preset="card" :item-count="pagination.itemCount"
title="填写投用日期" :on-update:page="pageChange"
:auto-focus="false"
:style="{ width: '450px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<template #default>
<div
class="space-y-3 text-sm"
:style="{
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning, 0.1),
'--warning-color': themeStore.otherColor.warning,
}"
> >
<p class="text-xs text-slate-500">{{ currentRow?.id }} · {{ currentRow?.title }}</p> <template #prefix="{ itemCount }">
<div class="rounded-lg border border-[var(--warning-color)] bg-[var(--warning-bg)] px-3 py-2 text-xs leading-relaxed text-[var(--warning-color)]"> 共 {{ itemCount }} 条
提示:投用前应完成变更培训与 PSSR 确认(资料已上传)。投用后变更进入待验收阶段,临时变更自投用日起计算时限。 </template>
</div> </NPagination>
<NDatePicker </div>
v-model:value="useTime" </NCard>
type="datetime" <!-- 附件在线预览弹窗 -->
:is-date-disabled="disabledDate" <NModal
format="yyyy-MM-dd HH:mm" v-model:show="previewModal"
placeholder="请选择投用时间" preset="card"
:time-picker-props="timePickerProps" :auto-focus="false"
/> :style="{ width: '80%', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<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?.file_name }}
</div> </div>
</template> </template>
<template #footer> <template #default>
<div class="flex justify-end gap-[10px]"> <div class="space-y-3">
<NButton size="small" @click="useTimeModal = false">取消</NButton> <div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
<NButton size="small" type="primary" :loading="btnLoading" @click="saveUseTime">确认投用</NButton> <span>上传人:{{ previewFile?.uploader_name }}</span>
</div> <span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
</template> <span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</NModal> </div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
</div>
</template>
</NModal>
</NSpin> </NSpin>
</NSpace> </NSpace>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.scroll { .scroll {
height: calc(100vh - 180px); height: calc(100vh - 220px);
overflow-y: auto; overflow-y: auto;
} }
</style> </style>