diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index a42c2c3..75454dc 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -227,6 +227,7 @@ const local: App.I18n.Schema = { details: '详情', details_closedoc: '资料关闭', details_index: '变更详情', + details_disposal: '处置申请', changepassword: '修改密码', home: '首页', my: '我的操作台', diff --git a/src/router/elegant/imports.ts b/src/router/elegant/imports.ts index 8d33eb3..3a422f4 100644 --- a/src/router/elegant/imports.ts +++ b/src/router/elegant/imports.ts @@ -23,6 +23,7 @@ export const views: Record Promise import("@/views/changePassword/index.vue"), changestatistics: () => import("@/views/changeStatistics/index.vue"), details_closedoc: () => import("@/views/details/closeDoc/index.vue"), + details_disposal: () => import("@/views/details/disposal/index.vue"), details_index: () => import("@/views/details/index/index.vue"), docs_archives: () => import("@/views/docs/archives/index.vue"), docs_query: () => import("@/views/docs/query/index.vue"), diff --git a/src/router/elegant/routes.ts b/src/router/elegant/routes.ts index 29cdff1..3b500f0 100644 --- a/src/router/elegant/routes.ts +++ b/src/router/elegant/routes.ts @@ -108,13 +108,23 @@ export const generatedRoutes: GeneratedRoute[] = [ path: '/details/closedoc', component: 'view.details_closedoc', meta: { - // activeMenu: 'my_mytask', title: '资料关闭', i18nKey: 'route.details_closedoc', constant: true, hideInMenu: true } }, + { + name: 'details_disposal', + path: '/details/disposal', + component: 'view.details_disposal', + meta: { + title: '处置申请', + i18nKey: 'route.details_disposal', + constant: true, + hideInMenu: true + } + }, { name: 'details_index', path: '/details/index', diff --git a/src/router/elegant/transform.ts b/src/router/elegant/transform.ts index f86f913..6aa1435 100644 --- a/src/router/elegant/transform.ts +++ b/src/router/elegant/transform.ts @@ -173,6 +173,7 @@ const routeMap: RouteMap = { "changestatistics": "/changestatistics", "details": "/details", "details_closedoc": "/details/closedoc", + "details_disposal": "/details/disposal", "details_index": "/details/index", "docs": "/docs", "docs_archives": "/docs/archives", diff --git a/src/service/api/file.ts b/src/service/api/file.ts index b7cfd19..538c437 100644 --- a/src/service/api/file.ts +++ b/src/service/api/file.ts @@ -15,4 +15,12 @@ export function deleteFileApi(id: number, fileId: number) { url: `/api/changes/${id}/attachments/${fileId}`, method: 'delete', }); +} + +// 文件列表 +export function fileListApi(change_id: number) { + return request({ + url: `/api/changes/${change_id}/attachments`, + method: 'get', + }); } \ No newline at end of file diff --git a/src/service/api/myTasks.ts b/src/service/api/myTasks.ts index 3dfdc65..6dd8d74 100644 --- a/src/service/api/myTasks.ts +++ b/src/service/api/myTasks.ts @@ -25,7 +25,7 @@ export function myTasksCloseApi(change_id:number) { // 资料关闭 export function myTasksCloseMaterialApi(change_id:number,data: any) { return request({ - url: `/api/materials/${change_id}/material-close`, + url: `/api/changes/${change_id}/material-close`, method: 'post', data, }); diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index 96fc81a..d53d190 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -87,6 +87,7 @@ declare module 'vue' { NTooltip: typeof import('naive-ui')['NTooltip'] NTree: typeof import('naive-ui')['NTree'] NTreeSelect: typeof import('naive-ui')['NTreeSelect'] + NUpload: typeof import('naive-ui')['NUpload'] NWatermark: typeof import('naive-ui')['NWatermark'] PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] @@ -180,6 +181,7 @@ declare global { const NTooltip: typeof import('naive-ui')['NTooltip'] const NTree: typeof import('naive-ui')['NTree'] const NTreeSelect: typeof import('naive-ui')['NTreeSelect'] + const NUpload: typeof import('naive-ui')['NUpload'] const NWatermark: typeof import('naive-ui')['NWatermark'] const PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] const ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] diff --git a/src/typings/elegant-router.d.ts b/src/typings/elegant-router.d.ts index 03cc503..9c5b021 100644 --- a/src/typings/elegant-router.d.ts +++ b/src/typings/elegant-router.d.ts @@ -27,6 +27,7 @@ declare module "@elegant-router/types" { "changestatistics": "/changestatistics"; "details": "/details"; "details_closedoc": "/details/closedoc"; + "details_disposal": "/details/disposal"; "details_index": "/details/index"; "docs": "/docs"; "docs_archives": "/docs/archives"; @@ -123,6 +124,7 @@ declare module "@elegant-router/types" { | "changepassword" | "changestatistics" | "details_closedoc" + | "details_disposal" | "details_index" | "docs_archives" | "docs_query" diff --git a/src/views/details/closeDoc/index.vue b/src/views/details/closeDoc/index.vue index 6a9736e..38495e7 100644 --- a/src/views/details/closeDoc/index.vue +++ b/src/views/details/closeDoc/index.vue @@ -4,9 +4,15 @@ import { Icon } from '@iconify/vue' import { useThemeStore } from '@/store/modules/theme'; import { useRouterPush } from '@/hooks/common/router'; import { useRoute } from 'vue-router'; -import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor } from '@/utils/common'; +import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor, formatTimestamp, anyToTimestamp } from '@/utils/common'; +import { uploadFileApi, deleteFileApi, fileListApi } from "@/service/api/file"; import { systemConfigApi } from '@/service/api/system'; import { getChangeDetailApi } from '@/service/api/change'; +import { myTasksCloseMaterialApi } from '@/service/api/myTasks'; + +import PDF from 'pdf-vue3' +import { VueOfficeDocx, VueOfficeExcel } from 'vue3-office-preview' +import 'vue3-office-preview/lib/style.css' const themeStore = useThemeStore(); const { routerPushByKey } = useRouterPush(); @@ -19,43 +25,220 @@ const currentInfo = ref(null); const currentId = ref(0); // 是否开启 AI 辅助功能 const isAiOn = ref(false); -// 变更等级选项 -const levelList = ref([]); -// 变更类型选项 -const typeList = ref([]); +// 培训签到上传文件列表 +const trainFiles = ref([]); +// pssr上传文件列表 +const pssrFiles = ref([]); +// 验收上传文件列表 +const acceptFiles = ref([]); // 实际投用日期 const useDate = ref(null); // 变更材料和实施费(万元) const price = ref(null); +// 验收状态 +const resultStatus = ref(0); +// 验收结论 +const acceptDesc = ref(''); +// 验收时间 +const acceptDate = ref(null); +// 附件选择列表弹框 +const filesModal = ref(false); +// 选中的文件列表 +const checkedFiles = ref([]); +// 附件列表 +const fileList = ref([]); +// 当前从变更附件选择的row下标 +const currentRowIndex = ref(-1); +// 当前预览文件 +const previewFile = ref(null); +// 预览文件弹框 +const previewModal = ref(false); const list = ref([ - {isArchiving:0,name:'变更申请表及审批记录',files:[{file_name:'审批记录(含会签意见).pdf',file_type:'pdf',file_url:''}]}, - {isArchiving:1,name:'风险分析记录(风险预分析 / HAZOP / FMEA / 风险检查表)',files:[]}, - {isArchiving:0,name:'培训签到与考核记录',files:[]}, - {isArchiving:0,name:'PSSR 检查记录(含现场照片)',files:[]}, - {isArchiving:2,name:'验收报告与运行数据',files:[]}, - {isArchiving:0,name:'更新后的 P&ID / 操作规程(受控最新版)',files:[]}, - {isArchiving:0,name:'设备 / 联锁台账更新记录',files:[]}, - {isArchiving:0,name:'应急预案修订',files:[]}, + {id:1,status:null,name:'变更申请表及审批记录',files:[]}, + {id:2,status:null,name:'风险分析记录(风险预分析 / HAZOP / FMEA / 风险检查表)',files:[]}, + {id:3,status:null,name:'培训签到与考核记录',files:[]}, + {id:4,status:null,name:'PSSR 检查记录(含现场照片)',files:[]}, + {id:5,status:null,name:'验收报告与运行数据',files:[]}, + {id:6,status:null,name:'更新后的 P&ID / 操作规程(受控最新版)',files:[]}, + {id:7,status:null,name:'设备 / 联锁台账更新记录',files:[]}, + {id:8,status:null,name:'应急预案修订',files:[]}, ]); -// 删除文件 -const removeFile = (item:any, fileIndex:number) => { - item.files.splice(fileIndex, 1); -} - // 返回 const back = () => { routerPushByKey('my_mytask'); }; +// 上传文件 +const onUploadFile = async (type:string = '',files:any,index:number = -1) => { + const file = files?.fileList[0]?.file; + if (!file) return; + const formData = new FormData(); + formData.append('file', file); + // 3:培训签到;4:PSSR;5:验收 + if(type==='3' || type==='4' || type==='5'){ + formData.append('step', type); + }else{ + formData.append('close_doc_id', currentId.value.toString()); + } + loading.value = true; + const {data,error} = await uploadFileApi(currentId.value, formData); + if(!error){ + // 是否是本地上传 + data.isLocalUpload = true; + console.log(data) + window.$message?.success('上传成功'); + if(type==='3'){ + trainFiles.value=[data]; + }else if(type==='4'){ + pssrFiles.value=[data]; + }else if(type==='5'){ + acceptFiles.value=[data]; + }else{ + list.value[index].files.push(data); + list.value[index].status = 1; + } + } + loading.value = false; +} + +// 下载文件 +const downloadFile = (type:string) => { + window.$message?.info('还差接口') +} +// 删除文件(真删) +const removeFileReal = async (type:string, fileIndex:number,index:number = -1) => { + let id = 0; + if(type==='3'){ + id = trainFiles.value[fileIndex].id; + }else if(type==='4'){ + id = pssrFiles.value[fileIndex].id; + }else if(type==='5'){ + id = acceptFiles.value[fileIndex].id; + }else{ + id = list.value[index]?.files[fileIndex]?.id; + } + loading.value = true; + const {error} = await deleteFileApi(currentId.value,id); + if(!error){ + if(type==='3'){ + trainFiles.value.splice(fileIndex, 1); + }else if(type==='4'){ + pssrFiles.value.splice(fileIndex, 1); + }else if(type==='5'){ + acceptFiles.value.splice(fileIndex, 1); + }else{ + list.value[index]?.files.splice(fileIndex, 1); + if(list.value[index]?.files.length === 0){ + list.value[index].status = 2; + } + } + window.$message?.success('删除成功'); + } + loading.value = false; +} +// 删除文件(假删) +const removeFile = (index:number, fileIndex:number) => { + // 判断是否是本地上传 + if(list.value[index]?.files[fileIndex]?.isLocalUpload){ + removeFileReal(list.value[index]?.name, fileIndex, index); + }else{ + list.value[index]?.files.splice(fileIndex, 1); + if(list.value[index]?.files.length === 0){ + list.value[index].status = 2; + } + } +} + +// 判断文件类型 +const handleFileType = (type:string) => { + if(type){ + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'] + const wordExtensions = ['docx'] + const excelExtensions = ['xlsx'] + if(wordExtensions.includes(type)){ + return 'word' + } + if(excelExtensions.includes(type)){ + return 'excel' + } + if(imageExtensions.includes(type)){ + return 'image' + } + if(type==='pdf'){ + return 'pdf' + } + } + return '' +} +// 打开预览 +const preview = (file:any) => { + console.log(file) + previewFile.value = file; + previewModal.value = true; +} + +// 打开附件选择 +const openFileList = (index:number) => { + currentRowIndex.value = index; + filesModal.value = true; +} +// 选择文件 +const chooseFiles = () => { + // 过滤出选中的文件 + const ids = new Set(checkedFiles.value); + const result = fileList.value.filter((item:any) => ids.has(item.id)); + // 合并文件列表 + let files = list.value[currentRowIndex.value]?.files.concat(result); + // 按id去重 + const uniqueList = [ + ...new Map(files.map((item:any) => [item.id, item])).values(), + ]; + list.value[currentRowIndex.value].files = uniqueList; + if(uniqueList.length > 0){ + list.value[currentRowIndex.value].status = 1; + } + filesModal.value = false; +} + +// 保存/申请关闭 +const submitCloseDoc = async (type:string) => { + if(!useDate.value){ + return window.$message?.error('请选择实际投用日期'); + } + if(!resultStatus.value){ + return window.$message?.error('请选择验收结论'); + } + const result = list.value.map(({ id, status }:{id:number,status:number} ) => ({ id, status })); + loading.value = true; + const {error} = await myTasksCloseMaterialApi(currentId.value,{ + type:type, + result_status:resultStatus.value, + note:acceptDesc.value, + use_date:formatTimestamp(useDate.value,'yyyy-MM-dd'), + accept_date:formatTimestamp(acceptDate.value,'yyyy-MM-dd'), + cost:price.value, + close_docs:result, + }); + if(!error){ + if(type==='save'){ + window.$message?.success('保存成功'); + } + if(type==='close'){ + back(); + window.$message?.success('申请关闭成功'); + } + } + loading.value = false; +} + // 获取变更详情 const getDetail = async () => { loading.value = true; const {data,error} = await getChangeDetailApi(currentId.value) if(!error){ currentInfo.value = data; - console.log(data) } loading.value = false; }; @@ -72,31 +255,25 @@ const getConfig = async () => { isAiOn.value = target.value==1?true:false; } } - // 解析 变更类型设置 - const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type'); - if (changeTypeGroup) { - typeList.value = changeTypeGroup.items.map((item:any) => ({ - ...item, - value: Number(item.value) - })); - } - // 解析 变更等级设置 - const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level'); - if (changeLevelGroup) { - levelList.value = changeLevelGroup.items.map((item:any) => ({ - ...item, - value: Number(item.value) - })); - } getDetail(); } loading.value = false; } +// 获取文件列表 +const getFileList = async () => { + const {data,error} = await fileListApi(currentId.value); + if(!error){ + console.log(data) + fileList.value = data; + } +} onMounted(async () => { + useDate.value = new Date().getTime(); if(route.query && route.query.id){ if(route.query.id){ currentId.value = Number(route.query.id); + getFileList(); getConfig(); } } @@ -105,7 +282,7 @@ onMounted(async () => {