refactor(process-config): 重构过程配置弹窗为手写 el-dialog

- 将 process-config 弹窗由 Avue 内置弹窗改为手写 el-dialog 对齐 customer-archive 风格
- 移除 avue-crud 原有的弹窗相关生命周期和插槽方法
- 新增 openProcessConfig、submitProcessConfig、resetProcess 等方法管理弹窗状态和数据提交
- 更新模板结构,新增具备基本信息、过程节点、节点设置的表单布局和底部操作按钮
- 关闭原 avue-crud 弹窗的内置按钮,改由手写对话框控制操作
- 优化样式,增加弹窗内部滚动,保持 footer 固定
- 微调业务组件样式,调整边框和表单布局宽度以适配新弹窗设计
This commit is contained in:
2026-08-14 08:46:39 +08:00
parent 2b4179f3cd
commit 9948c8f33b
5 changed files with 256 additions and 233 deletions
+13
View File
@@ -31,3 +31,16 @@
3. option:将 `expireStatus` 列的 `search: true → search: false`(否则会在搜索网格里多渲染一个空输入框);`searchIndex` 设为 ≥ 真实搜索列数(vehicle: `4 → 6`ship 已是 `6`),使 `columnLen ≤ searchIndex`Avue 不再显示「展开」按钮。 3. option:将 `expireStatus` 列的 `search: true → search: false`(否则会在搜索网格里多渲染一个空输入框);`searchIndex` 设为 ≥ 真实搜索列数(vehicle: `4 → 6`ship 已是 `6`),使 `columnLen ≤ searchIndex`Avue 不再显示「展开」按钮。
- 原理:Avue `header-search3.js``#search-menu` 插槽内容渲染进 `menu-form`,位于默认搜索/重置按钮之后、展开按钮之前;`order: -1` 将标签推到按钮左侧。 - 原理:Avue `header-search3.js``#search-menu` 插槽内容渲染进 `menu-form`,位于默认搜索/重置按钮之后、展开按钮之前;`order: -1` 将标签推到按钮左侧。
- 验证:`node --check` 两个 option 文件通过;页面刷新即可见效果。 - 验证:`node --check` 两个 option 文件通过;页面刷新即可见效果。
## process-config.vue 弹窗重构为手写 el-dialog(对齐 customer-archive 风格)
- 需求:用户要求 `/business/process-config.vue` 排版风格改成和 `/vehicle/customer-archive.vue` 一样,保持风格统一。
- 根因:两页弹窗架构根本不同——customer-archive 用手写 `<el-dialog>` + `section-card` 分组 + `#footer` 按钮;process-config 原用 Avue 内置弹窗(`#basicInfo-form`/`#nodeConfigJson-form` 插槽 + `#menu-form-before` 保存按钮)。要真正统一,只能把 process-config 改成手写弹窗。
- 模板改动(已完成):
- 移除 avue-crud 的 `:before-open`/`@row-save`/`@row-update`/`@row-del``#menu-form-before``#basicInfo-form``#nodeConfigJson-form` 插槽。
-`</avue-crud>` 后新增手写 `<el-dialog class="process-dialog" v-model="processBox" width="92%" top="4vh" append-to-body @closed="resetProcess">`,内部 `<el-form ref="processForm" :model="form" label-width="calc(6em + 24px)" :disabled="dialogReadonly" class="process-form dialog-form-label-fixed">`,含 3 个 `section-card`:基本信息 / 现行过程节点(时间线)/ 节点设置(节点表格);`#footer` 含 保存草稿(plain) + 保存并启用(primary) + 取消/关闭。
- 顶部「新建过程配置」、表格「查看/编辑」链接改调 `openProcessConfig()` / `openProcessConfig(row, true)` / `openProcessConfig(row, false)`
- 脚本改动(本次完成):删除 Avue 生命周期方法 `beforeOpen/rowSave/rowUpdate/submitRow/stopSubmitLoading/handleProcessConfigSave`;新增 `openProcessConfig(row, readonly)`load 详情后 `this.form=data` + `selectedProjectId` + `parseNodeConfig` + 开框;新增则清空表单 + `resetNodeRows`)、`submitProcessConfig(mode)``this.$refs.processForm.validate``syncNodeFields``normalizeRow``validateRow``confirmDuplicateVoucher``api.submit`,成功后关框并刷新)、`resetProcess()``@closed` 重置 `dialogReadonly/submitting/submitStatus/form/selectedProjectId` + `resetNodeRows` + `clearValidate`)。`data()` 已含 `processBox/submitting``computed.dialogTitle``rowDel`/`handleCopy`/`handleAction` 不变(独立 API 调用)。
- option 改动(已完成):`src/option/business/process-config.js``saveBtn/updateBtn/addBtn/viewBtn/editBtn/delBtn:false`,关闭 Avue 内置弹窗按钮。
- 样式:新增 `:deep(.process-dialog .el-dialog__body){max-height:72vh;overflow:auto}`,对齐 customer-archive 的 `.archive-dialog` 滚动行为(长内容时 body 滚动、footer 固定)。
- 验证:`vite build --outDir /tmp/pc-build-check` 全量编译通过(仅动态导入 chunk 警告,与本改动无关);之前 `npm run build` 失败仅因沙箱 safe-delete 守卫清理旧 dist/ 触发,非代码错误。
+4
View File
@@ -177,6 +177,10 @@ export const option = {
]), ]),
saveBtn: false, saveBtn: false,
updateBtn: false, updateBtn: false,
addBtn: false,
viewBtn: false,
editBtn: false,
delBtn: false,
dialogTop: '10px', dialogTop: '10px',
dialogWidth: '96%', dialogWidth: '96%',
menuWidth: 220, menuWidth: 220,
@@ -113,7 +113,7 @@ export const option = {
value: 'projectName', value: 'projectName',
}, },
filterable: true, filterable: true,
span: 12, span: 8,
minWidth: 180, minWidth: 180,
rules: textRule('项目名称', 100, true), rules: textRule('项目名称', 100, true),
}, },
@@ -128,7 +128,7 @@ export const option = {
prop: 'projectCode', prop: 'projectCode',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12, span: 8,
minWidth: 150, minWidth: 150,
rules: textRule('项目编号', 50), rules: textRule('项目编号', 50),
}, },
@@ -137,7 +137,7 @@ export const option = {
prop: 'undertakeDeptName', prop: 'undertakeDeptName',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12, span: 8,
minWidth: 150, minWidth: 150,
rules: textRule('承办部门', 50, true), rules: textRule('承办部门', 50, true),
}, },
@@ -146,7 +146,7 @@ export const option = {
prop: 'projectFundLimit', prop: 'projectFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12, span: 8,
minWidth: 200, minWidth: 200,
rules: [nonNegativeRule('项目资金使用额度')], rules: [nonNegativeRule('项目资金使用额度')],
}, },
@@ -155,7 +155,7 @@ export const option = {
prop: 'usedFundLimit', prop: 'usedFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12, span: 8,
minWidth: 220, minWidth: 220,
value: 0, value: 0,
rules: [nonNegativeRule('已使用项目资金额度')], rules: [nonNegativeRule('已使用项目资金额度')],
@@ -165,7 +165,7 @@ export const option = {
prop: 'remainingFundLimit', prop: 'remainingFundLimit',
hide: true, hide: true,
disabled: true, disabled: true,
span: 12, span: 8,
minWidth: 230, minWidth: 230,
value: 0, value: 0,
rules: [nonNegativeRule('剩余项目资金使用额度')], rules: [nonNegativeRule('剩余项目资金使用额度')],
@@ -174,7 +174,7 @@ export const option = {
label: '申请临时额度(万元)', label: '申请临时额度(万元)',
prop: 'applyLimit', prop: 'applyLimit',
formslot: true, formslot: true,
span: 12, span: 8,
minWidth: 180, minWidth: 180,
rules: [ rules: [
{ required: true, message: '请输入申请临时额度', trigger: 'blur' }, { required: true, message: '请输入申请临时额度', trigger: 'blur' },
@@ -187,7 +187,7 @@ export const option = {
type: 'date', type: 'date',
format: 'YYYY-MM-DD', format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
span: 12, span: 8,
minWidth: 140, minWidth: 140,
rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }], rules: [{ required: true, message: '请选择申请有效期至', trigger: 'change' }],
}, },
@@ -13681,7 +13681,7 @@ export default {
min-width: 56px; min-width: 56px;
height: 22px; height: 22px;
margin: 0 14px; margin: 0 14px;
border-bottom: 3px solid #303133; border-bottom: 4px solid #e4e7ed;
} }
&__transport-plan-waybill-card { &__transport-plan-waybill-card {
+134 -128
View File
@@ -8,10 +8,6 @@
v-model="form" v-model="form"
ref="crud" ref="crud"
:permission="permissionList" :permission="permissionList"
:before-open="beforeOpen"
@row-save="rowSave"
@row-update="rowUpdate"
@row-del="rowDel"
@search-change="searchChange" @search-change="searchChange"
@search-reset="searchReset" @search-reset="searchReset"
@selection-change="selectionChange" @selection-change="selectionChange"
@@ -25,7 +21,7 @@
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
v-if="hasPermission('process_config_add')" v-if="hasPermission('process_config_add')"
@click="$refs.crud.rowAdd()" @click="openProcessConfig()"
>新建过程配置 >新建过程配置
</el-button> </el-button>
<el-button <el-button
@@ -46,27 +42,6 @@
</el-button> </el-button>
</template> </template>
<template #menu-form-before="{ disabled }">
<template v-if="!dialogReadonly">
<el-button
plain
:loading="disabled"
:disabled="disabled"
@click="handleProcessConfigSave('draft')"
>
保存草稿
</el-button>
<el-button
type="primary"
:loading="disabled"
:disabled="disabled"
@click="handleProcessConfigSave('enabled')"
>
保存并启用
</el-button>
</template>
</template>
<template #status="{ row }"> <template #status="{ row }">
<el-tag :type="statusTagType(row.status)"> <el-tag :type="statusTagType(row.status)">
{{ row.statusName || displayStatus(row.status) }} {{ row.statusName || displayStatus(row.status) }}
@@ -79,13 +54,70 @@
</span> </span>
</template> </template>
<template #basicInfo-form> <template #menu="{ row, index }">
<section-card title="基本信息"> <el-link
type="primary"
v-if="hasPermission('process_config_view')"
@click="openProcessConfig(row, true)"
>
查看
</el-link>
<el-link
type="primary"
v-if="canEdit(row)"
@click="openProcessConfig(row, false)"
>
编辑
</el-link>
<el-link
type="primary"
v-if="hasPermission('process_config_copy')"
@click="handleCopy(row)"
>
复制
</el-link>
<el-link
type="primary"
v-if="canEnable(row)"
@click="handleAction('enable', row)"
>
启用
</el-link>
<el-link
type="primary"
v-if="canDisable(row)"
@click="handleAction('disable', row)"
>
停用
</el-link>
<el-link
type="danger"
v-if="canDelete(row)"
@click="rowDel(row)"
>
删除
</el-link>
</template>
</avue-crud>
<el-dialog
:title="dialogTitle"
append-to-body
v-model="processBox"
width="92%"
top="4vh"
class="process-dialog"
@closed="resetProcess"
>
<el-form <el-form
ref="processForm"
:model="form" :model="form"
label-position="right" label-position="right"
label-width="calc(6em + 24px)" label-width="calc(6em + 24px)"
:disabled="dialogReadonly"
class="process-form dialog-form-label-fixed"
> >
<section-card title="基本信息">
<el-row :gutter="18"> <el-row :gutter="18">
<el-col :span="6"> <el-col :span="6">
<el-form-item label="配置名称" required> <el-form-item label="配置名称" required>
@@ -149,12 +181,8 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form>
</section-card> </section-card>
</template>
<template #nodeConfigJson-form>
<div class="process-config-page__custom-form">
<section-card title="现行过程节点"> <section-card title="现行过程节点">
<div class="process-config-page__timeline" v-if="enabledNodes.length"> <div class="process-config-page__timeline" v-if="enabledNodes.length">
<div class="process-config-page__timeline-line"></div> <div class="process-config-page__timeline-line"></div>
@@ -358,54 +386,20 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</section-card> </section-card>
</div> </el-form>
</template>
<template #menu="{ row, index }"> <template #footer>
<el-link <template v-if="!dialogReadonly">
type="primary" <el-button plain :loading="submitting" :disabled="submitting" @click="submitProcessConfig('draft')">
v-if="hasPermission('process_config_view')" 保存草稿
@click="$refs.crud.rowView(row, index)" </el-button>
> <el-button type="primary" :loading="submitting" :disabled="submitting" @click="submitProcessConfig('enabled')">
查看 保存并启用
</el-link> </el-button>
<el-link
type="primary"
v-if="canEdit(row)"
@click="$refs.crud.rowEdit(row, index)"
>
编辑
</el-link>
<el-link
type="primary"
v-if="hasPermission('process_config_copy')"
@click="handleCopy(row)"
>
复制
</el-link>
<el-link
type="primary"
v-if="canEnable(row)"
@click="handleAction('enable', row)"
>
启用
</el-link>
<el-link
type="primary"
v-if="canDisable(row)"
@click="handleAction('disable', row)"
>
停用
</el-link>
<el-link
type="danger"
v-if="canDelete(row)"
@click="rowDel(row)"
>
删除
</el-link>
</template> </template>
</avue-crud> <el-button @click="processBox = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
</template>
</el-dialog>
<empty-pagination <empty-pagination
:page="page" :page="page"
@@ -519,6 +513,8 @@ export default {
}, },
selectionList: [], selectionList: [],
submitStatus: 1, submitStatus: 1,
processBox: false,
submitting: false,
}; };
}, },
computed: { computed: {
@@ -540,6 +536,10 @@ export default {
enabledNodes() { enabledNodes() {
return this.nodeRows.filter(item => item.enabled); return this.nodeRows.filter(item => item.enabled);
}, },
dialogTitle() {
if (this.dialogReadonly) return '查看过程配置';
return this.form.id ? '编辑过程配置' : '新增过程配置';
},
}, },
created() { created() {
this.resetNodeRows(); this.resetNodeRows();
@@ -853,11 +853,6 @@ export default {
} }
return true; return true;
}, },
stopSubmitLoading(loading) {
if (typeof loading === 'function') {
loading();
}
},
needsDuplicateVoucherConfirm() { needsDuplicateVoucherConfirm() {
const signNode = this.nodeRows.find(item => item.key === 'sign'); const signNode = this.nodeRows.find(item => item.key === 'sign');
const returnNode = this.nodeRows.find(item => item.key === 'return'); const returnNode = this.nodeRows.find(item => item.key === 'return');
@@ -887,39 +882,74 @@ export default {
} }
); );
}, },
submitRow(row, done, loading, status = this.submitStatus) { openProcessConfig(row, readonly = false) {
this.dialogReadonly = readonly;
this.submitStatus = 1; this.submitStatus = 1;
if (row && row.id) {
api.getDetail(row.id).then(res => {
const data = res.data.data || {};
this.form = data;
this.selectedProjectId = data.projectIds ? String(data.projectIds) : '';
this.ensureSelectedProjectOption();
this.parseNodeConfig(data);
this.processBox = true;
});
return;
}
this.form = {
configName: '',
projectIds: '',
projectNames: '',
defaultFinishDays: '',
remark: '',
status: 0,
};
this.selectedProjectId = '';
this.resetNodeRows();
this.processBox = true;
},
submitProcessConfig(mode) {
this.$refs.processForm.validate(valid => {
if (!valid) return;
this.submitting = true;
this.submitStatus = mode === 'draft' ? 2 : 1;
this.syncNodeFields(); this.syncNodeFields();
const submitRow = this.normalizeRow({ ...row, status }); const submitRow = this.normalizeRow({ ...this.form, status: this.submitStatus });
if (!this.validateRow(submitRow)) { if (!this.validateRow(submitRow)) {
this.stopSubmitLoading(loading); this.submitting = false;
return; return;
} }
this.confirmDuplicateVoucher() this.confirmDuplicateVoucher()
.then(() => api.submit(submitRow)) .then(() => api.submit(submitRow))
.then(() => { .then(() => {
this.submitting = false;
this.processBox = false;
this.onLoad(this.page); this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' }); this.$message({ type: 'success', message: '操作成功!' });
done();
}) })
.catch(error => { .catch(error => {
this.submitting = false;
if (error !== 'cancel' && error !== 'close') { if (error !== 'cancel' && error !== 'close') {
window.console.log(error); window.console.log(error);
} }
this.stopSubmitLoading(loading); });
}); });
}, },
rowSave(row, done, loading) { resetProcess() {
this.submitRow(row, done, loading); this.dialogReadonly = false;
}, this.submitting = false;
rowUpdate(row, index, done, loading) { this.submitStatus = 1;
this.submitRow(row, done, loading); this.form = {
}, configName: '',
handleProcessConfigSave(mode) { projectIds: '',
this.submitStatus = mode === 'draft' ? 2 : 1; projectNames: '',
if (this.$refs.crud && typeof this.$refs.crud.rowSave === 'function') { defaultFinishDays: '',
this.$refs.crud.rowSave(); remark: '',
} status: 0,
};
this.selectedProjectId = '';
this.resetNodeRows();
this.$refs.processForm?.clearValidate();
}, },
rowDel(row) { rowDel(row) {
this.$confirm('确定将选择数据删除?', { this.$confirm('确定将选择数据删除?', {
@@ -963,35 +993,6 @@ export default {
} }
this.selectionClear(); this.selectionClear();
}, },
beforeOpen(done, type) {
this.dialogReadonly = type === 'view';
this.submitStatus = 1;
if (type === 'add') {
this.form = {
configName: '',
projectIds: '',
projectNames: '',
defaultFinishDays: '',
remark: '',
status: 0,
};
this.selectedProjectId = '';
this.resetNodeRows();
done();
return;
}
if (['edit', 'view'].includes(type)) {
api.getDetail(this.form.id).then(res => {
this.form = res.data.data || {};
this.selectedProjectId = this.form.projectIds ? String(this.form.projectIds) : '';
this.ensureSelectedProjectOption();
this.parseNodeConfig(this.form);
done();
});
return;
}
done();
},
searchReset() { searchReset() {
this.query = {}; this.query = {};
this.applyDefaultDeptSearch(); this.applyDefaultDeptSearch();
@@ -1239,5 +1240,10 @@ export default {
:deep(.el-table__body tr:nth-child(even) > td.el-table__cell) { :deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
background: #fafafa; background: #fafafa;
} }
:deep(.process-dialog .el-dialog__body) {
max-height: 72vh;
overflow: auto;
}
} }
</style> </style>