From 2b51619728b6a09454ae6ef95053e5cdfae39576 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 27 Oct 2026 16:45:38 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=8F=98=E6=9B=B4=E8=8F=9C=E5=8D=95=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E4=B8=8EMK=E5=AE=A1=E6=89=B9=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- src/api/business/project-change.js | 52 +++ src/option/business/project-change.js | 161 ++++++++ src/router/page/index.js | 11 + src/router/views/index.js | 12 + src/utils/mk-approval.js | 6 + .../business/project-apply-public-view.vue | 3 +- src/views/business/project-apply.vue | 175 ++++++++- src/views/business/project-change.vue | 362 ++++++++++++++++++ 8 files changed, 771 insertions(+), 11 deletions(-) create mode 100644 src/api/business/project-change.js create mode 100644 src/option/business/project-change.js create mode 100644 src/views/business/project-change.vue diff --git a/src/api/business/project-change.js b/src/api/business/project-change.js new file mode 100644 index 0000000..f1de353 --- /dev/null +++ b/src/api/business/project-change.js @@ -0,0 +1,52 @@ +import { createCrudApi } from './common'; +import request from '@/axios'; + +const baseUrl = '/blade-transport/project-change'; +const api = createCrudApi(baseUrl); + +export const getList = api.getList; +export const getDetail = api.getDetail; +export const submit = api.submit; +export const remove = api.remove; + +export const createFromProject = (projectId, changeType) => + request({ + url: `${baseUrl}/create-from-project`, + method: 'post', + params: { projectId, changeType }, + }); + +export const saveDraft = row => + request({ + url: `${baseUrl}/save-draft`, + method: 'post', + data: row, + }); + +export const submitApproval = id => + request({ + url: `${baseUrl}/submit-approval`, + method: 'post', + params: { id }, + }); + +export const approve = id => + request({ + url: `${baseUrl}/approve`, + method: 'post', + params: { id }, + }); + +export const reject = id => + request({ + url: `${baseUrl}/reject`, + method: 'post', + params: { id }, + }); + +export const withdraw = id => + request({ + url: `${baseUrl}/withdraw`, + method: 'post', + params: { id }, + }); diff --git a/src/option/business/project-change.js b/src/option/business/project-change.js new file mode 100644 index 0000000..659162b --- /dev/null +++ b/src/option/business/project-change.js @@ -0,0 +1,161 @@ +import { + approvalStatusOptions, + createCrudOption, + hcsSyncStatusOptions, + projectTypeOptions, +} from './common'; + +export const changeTypeOptions = [ + { label: '项目变更', value: '项目变更' }, + { label: '项目备案调整', value: '项目备案调整' }, +]; + +export const config = { + title: '项目变更', + permission: 'project_change', + statusProp: 'approvalStatus', + statusTextProp: 'approvalStatusName', + deleteStatus: ['draft', 'withdrawn', 'rejected'], + editStatus: ['draft', 'withdrawn', 'rejected'], + viewStatus: ['approved', 'rejected', 'withdrawn'], + operations: [ + { + action: 'submitApproval', + label: '提交', + statusProp: 'approvalStatus', + status: ['draft'], + permission: 'project_change_submit', + }, + { + action: 'withdraw', + label: '撤回', + statusProp: 'approvalStatus', + status: ['reviewing'], + permission: 'project_change_withdraw', + }, + { + action: 'mkAudit', + label: '流程', + statusProp: 'approvalStatus', + status: ['reviewing'], + permission: 'project_change_approve', + mkOnly: true, + }, + { + action: 'approve', + label: '通过', + type: 'success', + statusProp: 'approvalStatus', + status: ['reviewing'], + permission: 'project_change_approve', + localOnly: true, + }, + { + action: 'reject', + label: '驳回', + type: 'warning', + statusProp: 'approvalStatus', + status: ['reviewing'], + permission: 'project_change_reject', + localOnly: true, + }, + ], +}; + +export const option = createCrudOption([ + { + label: '项目编号', + prop: 'projectCode', + search: true, + searchPlaceholder: '请输入', + searchOrder: 70, + minWidth: 160, + }, + { + label: '项目名称', + prop: 'projectName', + search: true, + searchPlaceholder: '请输入', + searchOrder: 60, + minWidth: 180, + }, + { + label: '变更类型', + prop: 'changeType', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 10, + dicData: changeTypeOptions, + minWidth: 130, + }, + { + label: '项目类型', + prop: 'projectType', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 20, + dicData: projectTypeOptions, + minWidth: 110, + }, + { + label: '客户名称', + prop: 'customerNames', + minWidth: 160, + }, + { + label: '下游承运商', + prop: 'carrierNames', + minWidth: 160, + }, + { + label: '承办部门', + prop: 'undertakeDeptName', + search: true, + searchPlaceholder: '请输入', + searchOrder: 50, + minWidth: 140, + }, + { + label: '项目负责人', + prop: 'principalUserName', + search: true, + searchPlaceholder: '请输入', + searchOrder: 40, + minWidth: 120, + }, + { + label: '审批状态', + prop: 'approvalStatus', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 30, + dicData: approvalStatusOptions, + minWidth: 110, + }, + { + label: '当前节点', + prop: 'currentNode', + minWidth: 120, + }, + { + label: '当前处理人', + prop: 'currentProcessor', + minWidth: 120, + }, + { + label: '审核通过时间', + prop: 'approvedTime', + type: 'datetime', + minWidth: 170, + }, + { + label: '同步状态', + prop: 'syncStatus', + type: 'select', + dicData: hcsSyncStatusOptions, + minWidth: 110, + }, +]); diff --git a/src/router/page/index.js b/src/router/page/index.js index 11c3a57..b009065 100644 --- a/src/router/page/index.js +++ b/src/router/page/index.js @@ -95,6 +95,17 @@ export default [ bizType: 'project-apply', }, }, + { + path: '/business/project-change/public-view', + name: '查看项目变更信息', + component: () => import('@/views/business/project-apply-public-view.vue'), + meta: { + keepAlive: false, + isTab: false, + isAuth: false, + bizType: 'project-change', + }, + }, { path: '/business/contract-manage/public-view', name: '查看合同信息', diff --git a/src/router/views/index.js b/src/router/views/index.js index 6d11ebd..6d4db1d 100644 --- a/src/router/views/index.js +++ b/src/router/views/index.js @@ -198,6 +198,18 @@ export default [ }, ], }, + { + path: '/business/project-change/form', + component: Layout, + children: [ + { + path: '', + name: '项目变更表单', + meta: { keepAlive: false, activeMenu: '/business/project-change' }, + component: loadView(() => import('@/views/business/project-apply.vue')), + }, + ], + }, { path: '/business/waybill-manage/form', component: Layout, diff --git a/src/utils/mk-approval.js b/src/utils/mk-approval.js index 9e224b2..51fd02d 100644 --- a/src/utils/mk-approval.js +++ b/src/utils/mk-approval.js @@ -15,6 +15,12 @@ export const MK_BIZ = { rejected: ['rejected', 'change_rejected', 'withdrawn'], approved: ['approved', 'change_approved'], }, + 'project-change': { + dictName: '提交项目变更审核流', + subjectPrefix: '项目变更审批', + rejected: ['rejected', 'withdrawn'], + approved: ['approved'], + }, 'contract-manage': { dictName: '提交合同审核流', subjectPrefix: '合同审批', diff --git a/src/views/business/project-apply-public-view.vue b/src/views/business/project-apply-public-view.vue index 8fb6219..412045f 100644 --- a/src/views/business/project-apply-public-view.vue +++ b/src/views/business/project-apply-public-view.vue @@ -84,7 +84,8 @@ export default { this.postFormData('save', lbpmFormValues, this.buildFormData()); }, postFormData(status, formValues, formData) { - postMkPublicProcessMessage('project-apply', { + const bizType = this.$route.meta?.bizType || 'project-apply'; + postMkPublicProcessMessage(bizType, { status, formValues: formValues || {}, formData, diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 965f9a0..ff81715 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -810,7 +810,21 @@ @@ -985,6 +999,7 @@ import { projectTypeOptions, } from '@/option/business/common'; import { config, option } from '@/option/business/project-apply'; +import * as projectChangeApi from '@/api/business/project-change'; const emptyForm = () => ({ projectType: '', @@ -1060,6 +1075,9 @@ const attachmentViewerPlugins = [ const projectApplyListRoute = '/business/project-apply'; const projectApplyFormRoute = '/business/project-apply/form'; const projectApplyListRefreshKey = 'project-apply-list-refresh'; +const projectChangeListRoute = '/business/project-change'; +const projectChangeFormRoute = '/business/project-change/form'; +const projectChangeListRefreshKey = 'project-change-list-refresh'; const otherAttachmentType = { label: '其他附件', value: '其他附件' }; @@ -1245,7 +1263,40 @@ export default { }, ], changeReason: [ - { max: 2000, message: '变更原因不能超过2000个字符', trigger: 'blur' }, + { + validator: (rule, value, callback) => { + if (!this.isChangeDialog) { + callback(); + return; + } + if (!value || !String(value).trim()) { + callback(new Error('请输入变更原因')); + return; + } + if (String(value).length > 2000) { + callback(new Error('变更原因不能超过2000个字符')); + return; + } + callback(); + }, + trigger: 'blur', + }, + ], + changeType: [ + { + validator: (rule, value, callback) => { + if (!this.isChangeDialog) { + callback(); + return; + } + if (!value) { + callback(new Error('请选择变更类型')); + return; + } + callback(); + }, + trigger: 'change', + }, ], projectIntro: [ { max: 1000, message: '项目简介不能超过1000个字符', trigger: 'blur' }, @@ -1374,8 +1425,11 @@ export default { isMajorProjectSupplementDialog() { return this.dialogType === 'majorSupplement'; }, + isProjectChangeModule() { + return this.$route.path === projectChangeFormRoute; + }, isChangeDialog() { - return this.dialogType === 'change'; + return this.dialogType === 'change' || this.isProjectChangeModule; }, isNewProjectDialog() { return ['add', 'majorSupplement'].includes(this.dialogType); @@ -1384,7 +1438,13 @@ export default { return this.isFormPageInstance; }, isPublicViewPage() { - return this.$route.path === '/business/project-apply/public-view'; + return ( + this.$route.path === '/business/project-apply/public-view' || + this.$route.path === '/business/project-change/public-view' + ); + }, + publicBizType() { + return this.$route.meta?.bizType || 'project-apply'; }, projectFormContainer() { // 表单页实例始终用页面容器,避免 keep-alive 失活时误切弹窗 @@ -1451,7 +1511,7 @@ export default { this.loadCargoTypeOptions(); this.loadTransportTypeOptions(); this.loadSettlementModeOptions(); - if (this.$route.path === projectApplyFormRoute) { + if (this.$route.path === projectApplyFormRoute || this.$route.path === projectChangeFormRoute) { this.isFormPageInstance = true; this.openProjectFormPage(); } @@ -1517,14 +1577,14 @@ export default { }, openPublicProjectForm() { const id = this.$route.query.id; - this.dialogType = 'view'; + this.dialogType = this.publicBizType === 'project-change' ? 'change' : 'view'; this.dialogReadonly = true; this.projectBox = true; if (!id) { this.$message.error('缺少项目ID'); return; } - getMkPublicDetail('project-apply', id) + getMkPublicDetail(this.publicBizType, id) .then(res => { const detail = res.data?.data || {}; this.applyPublicDictOptions(detail); @@ -1587,9 +1647,25 @@ export default { openProjectFormPage() { const type = this.$route.query.mode || 'add'; const id = this.$route.query.id; - this.dialogType = type; + this.dialogType = this.isProjectChangeModule ? (type === 'view' ? 'view' : 'change') : type; this.dialogReadonly = type === 'view'; this.projectBox = true; + if (this.isProjectChangeModule) { + if (!id) { + this.$message.error('缺少项目变更ID'); + return; + } + projectChangeApi.getDetail(id).then(res => { + const detail = res.data.data || {}; + this.applyProjectDetail({ + ...detail, + changeType: detail.changeType || '项目变更', + changeNo: detail.changeNo || '', + changeDate: detail.changeDate || this.$dayjs().format('YYYY-MM-DD'), + }); + }); + return; + } if (['add', 'majorSupplement'].includes(type)) { this.applyProjectDetail({ ...emptyForm(), @@ -1613,6 +1689,12 @@ export default { }, closeProjectForm() { if (this.isProjectFormPage) { + if (this.isProjectChangeModule) { + sessionStorage.setItem(projectChangeListRefreshKey, '1'); + this.$router.$avueRouter?.closeTag?.(); + this.$router.push(projectChangeListRoute); + return; + } this.markProjectApplyListNeedRefresh(); this.$router.$avueRouter?.closeTag?.(); this.$router.push(projectApplyListRoute); @@ -1858,7 +1940,7 @@ export default { }, handleCustomOperation(operation, row) { if (operation.action === 'startChange') { - this.openProjectDialog('change', row); + this.startIndependentProjectChange(row); return; } if (operation.action === 'mkAudit') { @@ -2169,6 +2251,52 @@ export default { this.closeProjectForm(); }); }, + startIndependentProjectChange(row) { + if (!row?.id) { + this.$message.warning('项目ID为空,无法发起变更'); + return; + } + const loading = this.$loading({ + lock: true, + text: '正在创建项目变更单…', + background: 'rgba(255, 255, 255, 0.6)', + }); + projectChangeApi + .createFromProject(row.id) + .then(res => { + if (res.data?.success === false) { + this.$message.error(res.data?.msg || '创建项目变更失败'); + return; + } + const detail = res.data?.data || {}; + if (!detail.id) { + this.$message.error('创建项目变更失败:未返回变更单ID'); + return; + } + this.$router.push({ + path: projectChangeFormRoute, + query: { mode: 'edit', id: detail.id, name: '项目变更' }, + }); + }) + .finally(() => loading.close()); + }, + saveProjectChangeDraft() { + if (!this.isProjectChangeModule) return; + this.submitLoading = true; + projectChangeApi + .saveDraft(this.normalizeSubmitForm({ includeChangeType: true })) + .then(res => { + if (res.data?.success === false || res.data?.data === false) { + this.$message.error(res.data?.msg || '保存失败,请联系管理员'); + return; + } + this.$message.success('保存成功!'); + this.closeProjectForm(); + }) + .finally(() => { + this.submitLoading = false; + }); + }, submitChangeProject() { this.$refs.projectForm.validate(valid => { if (!valid) { @@ -2177,7 +2305,34 @@ export default { } if (!this.validateAttachmentFileTypes()) return; this.submitLoading = true; - // 变更记录仅在提交时生成,保存草稿不落记录 + if (this.isProjectChangeModule) { + const payload = this.normalizeSubmitForm({ includeChangeType: true }); + const approvalStatus = this.form.approvalStatus || ''; + projectChangeApi + .submit(payload) + .then(res => { + if (res.data?.success === false || res.data?.data === false) { + this.$message.error(res.data?.msg || '提交失败,请联系管理员'); + return; + } + const id = this.form.id; + return submitMkApprovalFlow({ + bizType: 'project-change', + bizId: id, + subjectName: this.form.projectName || '', + approvalStatus, + mkEnabled: this.mkEnabled, + }).then(() => { + this.$message.success('提交成功!'); + this.closeProjectForm(); + }); + }) + .finally(() => { + this.submitLoading = false; + }); + return; + } + // 兼容旧入口:变更记录仅在提交时生成,保存草稿不落记录 this.api .submitChange(this.normalizeSubmitForm({ includeChangeType: true })) .then(res => { diff --git a/src/views/business/project-change.vue b/src/views/business/project-change.vue new file mode 100644 index 0000000..7fa0031 --- /dev/null +++ b/src/views/business/project-change.vue @@ -0,0 +1,362 @@ + + + + +