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 @@
+
+
+
+
+
+ 批量删除
+
+
+
+
+ {{ row.approvalStatusName || statusLabel(row.approvalStatus) }}
+
+
+
+
+ {{ syncLabel(row.syncStatus) }}
+
+ -
+
+
+
+
+ 查看
+
+
+ 编辑
+
+
+ 删除
+
+
+ {{ operation.label }}
+
+
+
+
+
+
+
+
+