✨ 角色权限联动与临时额度/项目备案/司机车辆审核MK
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,7 +7,8 @@ import {
|
||||
|
||||
export const changeTypeOptions = [
|
||||
{ label: '项目变更', value: '项目变更' },
|
||||
{ label: '项目备案调整', value: '项目备案调整' },
|
||||
{ label: '项目备案流程(本部流程)', value: '项目备案流程(本部流程)' },
|
||||
{ label: '项目备案流程(运输部流程)', value: '项目备案流程(运输部流程)' },
|
||||
];
|
||||
|
||||
export const config = {
|
||||
|
||||
@@ -85,13 +85,6 @@ export const config = {
|
||||
status: ['draft', 'withdrawn', 'rejected'],
|
||||
permission: 'temporary_credit_limit_submit',
|
||||
},
|
||||
{
|
||||
action: 'flow',
|
||||
label: '流程',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'temporary_credit_limit_view',
|
||||
},
|
||||
{
|
||||
action: 'withdraw',
|
||||
label: '撤回',
|
||||
@@ -99,6 +92,14 @@ export const config = {
|
||||
status: ['reviewing'],
|
||||
permission: 'temporary_credit_limit_withdraw',
|
||||
},
|
||||
{
|
||||
action: 'mkAudit',
|
||||
label: '流程',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'temporary_credit_limit_approve',
|
||||
mkOnly: true,
|
||||
},
|
||||
{
|
||||
action: 'approve',
|
||||
label: '审批通过',
|
||||
@@ -106,6 +107,7 @@ export const config = {
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'temporary_credit_limit_approve',
|
||||
localOnly: true,
|
||||
},
|
||||
{
|
||||
action: 'reject',
|
||||
@@ -114,6 +116,7 @@ export const config = {
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'temporary_credit_limit_reject',
|
||||
localOnly: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -106,6 +106,17 @@ export default [
|
||||
bizType: 'project-change',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/business/project-record-change/public-view',
|
||||
name: '查看项目备案信息',
|
||||
component: () => import('@/views/business/project-apply-public-view.vue'),
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
isTab: false,
|
||||
isAuth: false,
|
||||
bizType: 'project-record-change',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/business/contract-manage/public-view',
|
||||
name: '查看合同信息',
|
||||
@@ -194,6 +205,39 @@ export default [
|
||||
bizType: 'bill-payment',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/business/temporary-credit-limit/public-view',
|
||||
name: '查看临时额度申请信息',
|
||||
component: () => import('@/views/business/temporary-credit-limit-public-view.vue'),
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
isTab: false,
|
||||
isAuth: false,
|
||||
bizType: 'temporary-credit-limit',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/transportCapacity/cert-audit-driver/public-view',
|
||||
name: '查看司机认证审核信息',
|
||||
component: () => import('@/views/transportCapacity/cert-audit-driver-public-view.vue'),
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
isTab: false,
|
||||
isAuth: false,
|
||||
bizType: 'cert-audit-driver',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/transportCapacity/cert-audit-vehicle/public-view',
|
||||
name: '查看车辆认证审核信息',
|
||||
component: () => import('@/views/transportCapacity/cert-audit-vehicle-public-view.vue'),
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
isTab: false,
|
||||
isAuth: false,
|
||||
bizType: 'cert-audit-vehicle',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: '主页',
|
||||
|
||||
@@ -21,6 +21,19 @@ export const MK_BIZ = {
|
||||
rejected: ['rejected', 'withdrawn'],
|
||||
approved: ['approved'],
|
||||
},
|
||||
/** 项目备案流程(本部/运输部)共用 */
|
||||
'project-record-change': {
|
||||
dictName: '提交项目备案审核流',
|
||||
subjectPrefix: '项目备案审批',
|
||||
rejected: ['rejected', 'withdrawn'],
|
||||
approved: ['approved'],
|
||||
},
|
||||
'temporary-credit-limit': {
|
||||
dictName: '提交临时额度审核流',
|
||||
subjectPrefix: '临时额度审批',
|
||||
rejected: ['rejected', 'withdrawn'],
|
||||
approved: ['approved'],
|
||||
},
|
||||
'contract-manage': {
|
||||
dictName: '提交合同审核流',
|
||||
subjectPrefix: '合同审批',
|
||||
@@ -69,10 +82,42 @@ export const MK_BIZ = {
|
||||
rejected: ['returned'],
|
||||
approved: ['approved'],
|
||||
},
|
||||
'cert-audit-driver': {
|
||||
dictName: '提交司机审核流',
|
||||
subjectPrefix: '司机认证审批',
|
||||
// 认证记录 status:0审核中 1已通过 2已驳回
|
||||
rejected: ['2'],
|
||||
approved: ['1'],
|
||||
},
|
||||
'cert-audit-vehicle': {
|
||||
dictName: '提交车辆审核流',
|
||||
subjectPrefix: '车辆认证审批',
|
||||
rejected: ['2'],
|
||||
approved: ['1'],
|
||||
},
|
||||
};
|
||||
|
||||
/** 按认证对象类型解析 MK 业务类型 */
|
||||
export function resolveCertAuditMkBizType(bizType) {
|
||||
return String(bizType || '') === 'vehicle' ? 'cert-audit-vehicle' : 'cert-audit-driver';
|
||||
}
|
||||
|
||||
const MK_SWITCH_NAME = '开启MK';
|
||||
|
||||
/** 项目备案类变更类型(含历史「项目备案调整」) */
|
||||
export const PROJECT_RECORD_CHANGE_TYPES = [
|
||||
'项目备案流程(本部流程)',
|
||||
'项目备案流程(运输部流程)',
|
||||
'项目备案调整',
|
||||
];
|
||||
|
||||
/** 按变更类型选择项目变更 / 项目备案 MK 业务类型 */
|
||||
export function resolveProjectChangeMkBizType(changeType) {
|
||||
return PROJECT_RECORD_CHANGE_TYPES.includes(String(changeType || '').trim())
|
||||
? 'project-record-change'
|
||||
: 'project-change';
|
||||
}
|
||||
|
||||
/** 进程内缓存,避免每次提交都打字典;登录后同一会话开关一般不变 */
|
||||
let mkEnabledCache = null;
|
||||
let mkEnabledPromise = null;
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
ref="treeMenu"
|
||||
:default-checked-keys="menuTreeObj"
|
||||
:props="props"
|
||||
@check="handleMenuCheck"
|
||||
>
|
||||
</el-tree>
|
||||
</el-tab-pane>
|
||||
@@ -102,7 +103,7 @@
|
||||
style="margin-bottom: 12px; background: #f5f7fa; padding: 6px 10px; border-radius: 4px"
|
||||
>
|
||||
<span style="color: #606266; font-size: 13px"
|
||||
>仅展示菜单权限已勾选的菜单,已配置的数据权限会保留。勾选多个菜单后点击「设置」可批量配置</span
|
||||
>菜单权限勾选/反选会自动同步到此页;仅展示已勾选菜单,已配置的数据权限会保留。可再调整勾选后点击「设置」批量配置</span
|
||||
>
|
||||
<el-button type="primary" size="small" @click="openDataScopeSetting">设置</el-button>
|
||||
</el-row>
|
||||
@@ -191,20 +192,8 @@
|
||||
<el-radio :value="1">所在部门及下级</el-radio>
|
||||
<el-radio :value="2">全部组织</el-radio>
|
||||
<el-radio :value="3">本人创建</el-radio>
|
||||
<el-radio :value="4">自定义组织</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="Number(dataScopeSetting.scopeType) === 4" label="组织">
|
||||
<el-tree
|
||||
ref="treeCustomDept"
|
||||
:data="deptTree"
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
:props="{ label: 'title', children: 'children' }"
|
||||
style="width: 100%; max-height: 320px; overflow: auto; border: 1px solid #ebeef5; padding: 8px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -228,7 +217,6 @@ import {
|
||||
update,
|
||||
} from '@/api/system/role';
|
||||
import { getRoleMenuDataScopeTree, submitRoleMenuDataScope } from '@/api/system/roleDataScope';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { mapGetters } from 'vuex';
|
||||
import website from '@/config/website';
|
||||
|
||||
@@ -253,7 +241,6 @@ export default {
|
||||
currentRoleName: '',
|
||||
roleDataScopeMenuTree: [],
|
||||
roleDataScopeMap: {},
|
||||
deptTree: [],
|
||||
dataScopeSetting: {
|
||||
roleName: '',
|
||||
scopeType: 2,
|
||||
@@ -430,6 +417,11 @@ export default {
|
||||
if (!tree) return;
|
||||
const allKeys = this.getAllNodeKeys(dataList);
|
||||
tree.setCheckedKeys(allKeys);
|
||||
if (treeRef === 'treeMenu') {
|
||||
this.$nextTick(() => {
|
||||
this.syncDataScopeTreeFromMenu();
|
||||
});
|
||||
}
|
||||
},
|
||||
handleInvertSelect(treeRef, dataList, isLinked) {
|
||||
const tree = this.$refs[treeRef];
|
||||
@@ -444,6 +436,11 @@ export default {
|
||||
const invertedKeys = allKeys.filter(key => !checkedKeys.has(key));
|
||||
tree.setCheckedKeys(invertedKeys);
|
||||
}
|
||||
if (treeRef === 'treeMenu') {
|
||||
this.$nextTick(() => {
|
||||
this.syncDataScopeTreeFromMenu();
|
||||
});
|
||||
}
|
||||
},
|
||||
handleLinkedChange(treeRef) {
|
||||
const tree = this.$refs[treeRef];
|
||||
@@ -452,6 +449,9 @@ export default {
|
||||
const halfCheckedKeys = tree.getHalfCheckedKeys();
|
||||
this.$nextTick(() => {
|
||||
tree.setCheckedKeys([...checkedKeys, ...halfCheckedKeys]);
|
||||
if (treeRef === 'treeMenu') {
|
||||
this.syncDataScopeTreeFromMenu();
|
||||
}
|
||||
});
|
||||
},
|
||||
// el-tree 会把 setCheckedKeys 的入参记录为默认勾选集并在树数据重载时回放,
|
||||
@@ -461,6 +461,50 @@ export default {
|
||||
if (!tree) return;
|
||||
tree.setCheckedKeys(keys);
|
||||
},
|
||||
handleMenuCheck() {
|
||||
this.syncDataScopeTreeFromMenu();
|
||||
},
|
||||
/**
|
||||
* 按当前菜单权限勾选结果过滤数据权限树,并同步勾选状态。
|
||||
* 取消勾选的菜单从数据权限树移除;已配置的数据权限标签仍保留在 roleDataScopeMap。
|
||||
*/
|
||||
syncDataScopeTreeFromMenu() {
|
||||
const treeMenu = this.$refs.treeMenu;
|
||||
if (!treeMenu) {
|
||||
return;
|
||||
}
|
||||
const checkedKeys = treeMenu.getCheckedKeys() || [];
|
||||
const checkedKeySet = new Set(checkedKeys);
|
||||
this.roleDataScopeMenuTree = this.filterMenuTreeByChecked(this.menuGrantList, checkedKeySet);
|
||||
this.$nextTick(() => {
|
||||
const treeDataScope = this.$refs.treeRoleDataScope;
|
||||
if (!treeDataScope) {
|
||||
return;
|
||||
}
|
||||
const visibleKeys = new Set(this.getAllNodeKeys(this.roleDataScopeMenuTree));
|
||||
const syncKeys = checkedKeys.filter(key => visibleKeys.has(key));
|
||||
treeDataScope.setCheckedKeys(syncKeys);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 仅保留已勾选菜单及其祖先节点,保持树层级。
|
||||
*/
|
||||
filterMenuTreeByChecked(nodes, checkedKeySet) {
|
||||
if (!nodes || nodes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const result = [];
|
||||
nodes.forEach(node => {
|
||||
const children = this.filterMenuTreeByChecked(node.children || [], checkedKeySet);
|
||||
if (checkedKeySet.has(node.id) || children.length > 0) {
|
||||
result.push({
|
||||
...node,
|
||||
children,
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
submit() {
|
||||
const menuList = this.$refs.treeMenu.getCheckedKeys();
|
||||
const apiScopeList = this.$refs.treeApiScope.getCheckedKeys();
|
||||
@@ -484,7 +528,7 @@ export default {
|
||||
return getRoleMenuDataScopeTree(roleId).then(res => {
|
||||
const data = res.data.data || {};
|
||||
this.currentRoleName = data.roleName || '';
|
||||
this.roleDataScopeMenuTree = data.menuTree || [];
|
||||
// 菜单树由菜单权限勾选实时联动生成;这里只加载已配置的数据权限标签
|
||||
const scopeMap = {};
|
||||
(data.scopes || []).forEach(item => {
|
||||
scopeMap[String(item.menuId)] = item;
|
||||
@@ -507,20 +551,7 @@ export default {
|
||||
scopeType: 2,
|
||||
menuIds,
|
||||
};
|
||||
const loadDept =
|
||||
this.deptTree && this.deptTree.length
|
||||
? Promise.resolve()
|
||||
: getDeptTree(this.userInfo.tenantId).then(res => {
|
||||
this.deptTree = res.data.data || [];
|
||||
});
|
||||
loadDept.then(() => {
|
||||
this.dataScopeSettingBox = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.treeCustomDept) {
|
||||
this.$refs.treeCustomDept.setCheckedKeys([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
submitDataScopeSetting() {
|
||||
const roleId = this.currentRoleIds && this.currentRoleIds[0];
|
||||
@@ -529,27 +560,17 @@ export default {
|
||||
return;
|
||||
}
|
||||
const scopeType = Number(this.dataScopeSetting.scopeType || 2);
|
||||
let deptIds = [];
|
||||
if (scopeType === 4) {
|
||||
deptIds = this.$refs.treeCustomDept ? this.$refs.treeCustomDept.getCheckedKeys() : [];
|
||||
if (!deptIds.length) {
|
||||
this.$message.warning('自定义组织时请勾选组织');
|
||||
return;
|
||||
}
|
||||
}
|
||||
submitRoleMenuDataScope({
|
||||
roleId,
|
||||
menuIds: this.dataScopeSetting.menuIds,
|
||||
scopeType,
|
||||
deptIds,
|
||||
deptIds: [],
|
||||
}).then(() => {
|
||||
this.$message.success('数据权限设置成功');
|
||||
this.dataScopeSettingBox = false;
|
||||
this.loadRoleDataScopeTree(roleId).then(() => {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.treeRoleDataScope) {
|
||||
this.$refs.treeRoleDataScope.setCheckedKeys([]);
|
||||
}
|
||||
this.syncDataScopeTreeFromMenu();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -663,6 +684,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.applyCheckedKeys('treeMenu', this.menuTreeObj);
|
||||
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
|
||||
this.syncDataScopeTreeFromMenu();
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -682,6 +704,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.applyCheckedKeys('treeMenu', this.menuTreeObj);
|
||||
this.applyCheckedKeys('treeApiScope', this.apiScopeTreeObj);
|
||||
this.syncDataScopeTreeFromMenu();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1248,7 +1248,11 @@ import { getList as getUserList } from '@/api/system/user';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import {
|
||||
loadMkEnabled,
|
||||
resolveProjectChangeMkBizType,
|
||||
submitMkApprovalFlow,
|
||||
} from '@/utils/mk-approval';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||
import PdfPreview from '@/components/pdf-preview/main.vue';
|
||||
@@ -1353,7 +1357,8 @@ const optionalSentinelFields = [
|
||||
|
||||
const changeTypeOptions = [
|
||||
{ label: '项目变更', value: '项目变更' },
|
||||
{ label: '项目备案调整', value: '项目备案调整' },
|
||||
{ label: '项目备案流程(本部流程)', value: '项目备案流程(本部流程)' },
|
||||
{ label: '项目备案流程(运输部流程)', value: '项目备案流程(运输部流程)' },
|
||||
];
|
||||
|
||||
const attachmentViewerPlugins = [
|
||||
@@ -1756,12 +1761,16 @@ export default {
|
||||
isPublicViewPage() {
|
||||
return (
|
||||
this.$route.path === '/business/project-apply/public-view' ||
|
||||
this.$route.path === '/business/project-change/public-view'
|
||||
this.$route.path === '/business/project-change/public-view' ||
|
||||
this.$route.path === '/business/project-record-change/public-view'
|
||||
);
|
||||
},
|
||||
publicBizType() {
|
||||
return this.$route.meta?.bizType || 'project-apply';
|
||||
},
|
||||
isPublicProjectChangeView() {
|
||||
return ['project-change', 'project-record-change'].includes(this.publicBizType);
|
||||
},
|
||||
projectFormContainer() {
|
||||
// 表单页实例始终用页面容器,避免 keep-alive 失活时误切弹窗
|
||||
return this.isFormPageInstance ? 'div' : 'el-dialog';
|
||||
@@ -1900,7 +1909,7 @@ export default {
|
||||
},
|
||||
openPublicProjectForm() {
|
||||
const id = this.$route.query.id;
|
||||
this.dialogType = this.publicBizType === 'project-change' ? 'change' : 'view';
|
||||
this.dialogType = this.isPublicProjectChangeView ? 'change' : 'view';
|
||||
this.dialogReadonly = true;
|
||||
this.projectBox = true;
|
||||
if (!id) {
|
||||
@@ -2535,10 +2544,20 @@ export default {
|
||||
},
|
||||
resolveChangeType(row = {}) {
|
||||
if (row.changeType) return row.changeType;
|
||||
const node = row.currentNode || '';
|
||||
if (String(node).includes('备案')) return '项目备案调整';
|
||||
const node = String(row.currentNode || '');
|
||||
if (node.includes('运输部')) return '项目备案流程(运输部流程)';
|
||||
if (node.includes('本部') || node.includes('备案')) return '项目备案流程(本部流程)';
|
||||
return '项目变更';
|
||||
},
|
||||
buildProjectChangeMkTempVarData(row = {}) {
|
||||
const changeType = this.resolveChangeType(row);
|
||||
return {
|
||||
projectType: row.projectType || this.form.projectType || '',
|
||||
// MK 临时变量:变更类型(本部/运输部备案分流)
|
||||
changeType,
|
||||
变更类型: changeType,
|
||||
};
|
||||
},
|
||||
submitProject() {
|
||||
this.$refs.projectForm.validate(valid => {
|
||||
if (!valid) {
|
||||
@@ -2681,6 +2700,7 @@ export default {
|
||||
if (this.isProjectChangeModule) {
|
||||
const payload = this.normalizeSubmitForm({ includeChangeType: true });
|
||||
const approvalStatus = this.form.approvalStatus || '';
|
||||
const changeType = this.resolveChangeType(this.form);
|
||||
projectChangeApi
|
||||
.submit(payload)
|
||||
.then(res => {
|
||||
@@ -2690,12 +2710,15 @@ export default {
|
||||
}
|
||||
const id = this.form.id;
|
||||
return submitMkApprovalFlow({
|
||||
bizType: 'project-change',
|
||||
bizType: resolveProjectChangeMkBizType(changeType),
|
||||
bizId: id,
|
||||
subjectName: this.form.projectName || '',
|
||||
approvalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
tempVarData: { projectType: this.form.projectType || '' },
|
||||
tempVarData: this.buildProjectChangeMkTempVarData({
|
||||
...this.form,
|
||||
changeType,
|
||||
}),
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功!');
|
||||
this.closeProjectForm();
|
||||
@@ -2715,13 +2738,17 @@ export default {
|
||||
return;
|
||||
}
|
||||
const id = this.form.id;
|
||||
const changeType = this.resolveChangeType(this.form);
|
||||
return submitMkApprovalFlow({
|
||||
bizType: 'project-apply',
|
||||
bizId: id,
|
||||
subjectName: this.form.projectName || '',
|
||||
approvalStatus: this.form.approvalStatus || 'change_rejected',
|
||||
mkEnabled: this.mkEnabled,
|
||||
tempVarData: { projectType: this.form.projectType || '' },
|
||||
tempVarData: this.buildProjectChangeMkTempVarData({
|
||||
...this.form,
|
||||
changeType,
|
||||
}),
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功!');
|
||||
this.closeProjectForm();
|
||||
|
||||
@@ -75,7 +75,11 @@ import * as api from '@/api/business/project-change';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { config, option } from '@/option/business/project-change';
|
||||
import { approvalStatusOptions } from '@/option/business/common';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import {
|
||||
loadMkEnabled,
|
||||
resolveProjectChangeMkBizType,
|
||||
submitMkApprovalFlow,
|
||||
} from '@/utils/mk-approval';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
@@ -217,16 +221,21 @@ export default {
|
||||
// 先本地提交再调 MK,避免 MK 回写 reviewing 后本地 submitApproval 被编辑校验拦住。
|
||||
const afterMk =
|
||||
operation.action === 'submitApproval'
|
||||
? request().then(() =>
|
||||
submitMkApprovalFlow({
|
||||
bizType: 'project-change',
|
||||
? request().then(() => {
|
||||
const changeType = row.changeType || '项目变更';
|
||||
return submitMkApprovalFlow({
|
||||
bizType: resolveProjectChangeMkBizType(changeType),
|
||||
bizId: row.id,
|
||||
subjectName: row.projectName || '',
|
||||
approvalStatus: row.approvalStatus || '',
|
||||
mkEnabled: this.mkEnabled,
|
||||
tempVarData: { projectType: row.projectType || '' },
|
||||
tempVarData: {
|
||||
projectType: row.projectType || '',
|
||||
changeType,
|
||||
变更类型: changeType,
|
||||
},
|
||||
});
|
||||
})
|
||||
)
|
||||
: request();
|
||||
const loading =
|
||||
operation.action === 'approve'
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<mk-public-shell biz-type="temporary-credit-limit" :get-form="getForm">
|
||||
<div class="temporary-credit-limit-public-view">
|
||||
<div class="temporary-credit-limit-public-view__title">查看临时额度申请</div>
|
||||
<template v-if="detail">
|
||||
<div class="temporary-credit-limit-public-view__section-title">项目额度信息</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="项目名称">{{ detail.projectName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目编号">{{ detail.projectCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="承办部门">{{ detail.undertakeDeptName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目资金使用额度(万元)">
|
||||
{{ formatAmount(detail.projectFundLimit) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="已使用项目资金使用额度(万元)">
|
||||
{{ formatAmount(detail.usedFundLimit) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="剩余项目资金使用额度(万元)">
|
||||
{{ formatAmount(detail.remainingFundLimit) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="temporary-credit-limit-public-view__section-title">临时额度信息</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="申请单号">{{ detail.applicationNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请临时额度(万元)">
|
||||
{{ formatAmount(detail.applyLimit) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请有效期至">{{ detail.validUntil || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批状态">
|
||||
{{ detail.approvalStatusName || detail.approvalStatus || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{ detail.applicantName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请部门">{{ detail.applyDeptName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
<el-empty v-else description="暂无数据" />
|
||||
</div>
|
||||
</mk-public-shell>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
||||
|
||||
export default {
|
||||
name: 'TemporaryCreditLimitPublicView',
|
||||
components: { MkPublicShell },
|
||||
data() {
|
||||
return {
|
||||
detail: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadDetail();
|
||||
},
|
||||
methods: {
|
||||
loadDetail() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
getMkPublicDetail('temporary-credit-limit', id).then(res => {
|
||||
this.detail = res.data?.data || null;
|
||||
});
|
||||
},
|
||||
formatAmount(value) {
|
||||
if (value === undefined || value === null || value === '' || Number(value) === -1) {
|
||||
return '-';
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getForm() {
|
||||
const detail = this.detail || {};
|
||||
return {
|
||||
...detail,
|
||||
subject: detail.projectName || detail.applicationNo || '',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.temporary-credit-limit-public-view {
|
||||
padding: 16px 20px 24px;
|
||||
|
||||
&__title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
position: relative;
|
||||
margin: 16px 0 12px;
|
||||
padding-left: 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
margin-top: -7px;
|
||||
background: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -329,8 +329,10 @@ import {
|
||||
getDetail as getProjectDetail,
|
||||
getList as getProjectList,
|
||||
} from '@/api/business/project-apply';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { config, option } from '@/option/business/temporary-credit-limit';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||
@@ -426,6 +428,7 @@ export default {
|
||||
flowBox: false,
|
||||
flowUrl: '',
|
||||
processInstanceId: '',
|
||||
mkEnabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -455,6 +458,13 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.loadProjectOptions();
|
||||
loadMkEnabled()
|
||||
.then(enabled => {
|
||||
this.mkEnabled = !!enabled;
|
||||
})
|
||||
.catch(() => {
|
||||
this.mkEnabled = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
buildTableOption() {
|
||||
@@ -543,11 +553,19 @@ export default {
|
||||
},
|
||||
customOperations(row) {
|
||||
return (this.config.operations || []).filter(operation => {
|
||||
if (operation.mkOnly && !this.mkEnabled) return false;
|
||||
if (operation.localOnly && this.mkEnabled) return false;
|
||||
const permission = operation.permission || `${this.config.permission}_${operation.action}`;
|
||||
const statusProp = operation.statusProp || this.config.statusProp || 'status';
|
||||
// MK「流程」入口允许只读行查看审核页;本地审批/提交/撤回仍禁止只读
|
||||
if (
|
||||
row.readonly &&
|
||||
['submitApproval', 'withdraw', 'approve', 'reject'].includes(operation.action)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
this.hasPermission(permission) &&
|
||||
!row.readonly &&
|
||||
(!operation.status?.length || operation.status.includes(row[statusProp]))
|
||||
);
|
||||
});
|
||||
@@ -736,6 +754,10 @@ export default {
|
||||
});
|
||||
},
|
||||
handleOperation(operation, row) {
|
||||
if (operation.action === 'mkAudit') {
|
||||
this.handleMkAudit(row);
|
||||
return;
|
||||
}
|
||||
if (operation.action === 'flow') {
|
||||
this.openFlow(row);
|
||||
return;
|
||||
@@ -744,12 +766,49 @@ export default {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
const previousApprovalStatus = row.approvalStatus || '';
|
||||
const request = () => this.api[operation.action](row.id);
|
||||
// 先本地提交再调 MK,避免 MK 回写 reviewing 后本地 submitApproval 被编辑校验拦住
|
||||
const afterMk =
|
||||
operation.action === 'submitApproval'
|
||||
? request().then(() =>
|
||||
submitMkApprovalFlow({
|
||||
bizType: 'temporary-credit-limit',
|
||||
bizId: row.id,
|
||||
subjectName: row.projectName || row.applicationNo || '',
|
||||
approvalStatus: previousApprovalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
})
|
||||
.then(() => this.api[operation.action](row.id))
|
||||
.then(() => {
|
||||
)
|
||||
: request();
|
||||
return afterMk.then(res => {
|
||||
if (res?.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || `${operation.label}失败,请联系管理员`);
|
||||
return;
|
||||
}
|
||||
this.$message.success(`${operation.label}成功`);
|
||||
this.onLoad(this.page, this.query);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleMkAudit(row) {
|
||||
if (!row?.id) {
|
||||
this.$message.warning('临时额度申请ID为空,无法打开审核页');
|
||||
return;
|
||||
}
|
||||
getMKApprovalUrl({ bizId: String(row.id) })
|
||||
.then(res => {
|
||||
const url = res?.data?.data;
|
||||
if (!url) {
|
||||
this.$message.error('获取审核跳转地址失败');
|
||||
return;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('获取审核跳转地址失败');
|
||||
});
|
||||
},
|
||||
openFlow(row, loaded = false) {
|
||||
const processInstanceId = row.processInstanceId || row.processInstanceID || row.procInstId;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<mk-public-shell biz-type="cert-audit-driver" :get-form="getForm">
|
||||
<cert-audit-public-content ref="page" biz-type="driver" />
|
||||
</mk-public-shell>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
||||
import CertAuditPublicContent from './components/cert-audit-public-content/main.vue';
|
||||
|
||||
export default {
|
||||
name: 'CertAuditDriverPublicView',
|
||||
components: { MkPublicShell, CertAuditPublicContent },
|
||||
methods: {
|
||||
getForm() {
|
||||
return this.$refs.page?.getForm?.() || {};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<mk-public-shell biz-type="cert-audit-vehicle" :get-form="getForm">
|
||||
<cert-audit-public-content ref="page" biz-type="vehicle" />
|
||||
</mk-public-shell>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
||||
import CertAuditPublicContent from './components/cert-audit-public-content/main.vue';
|
||||
|
||||
export default {
|
||||
name: 'CertAuditVehiclePublicView',
|
||||
components: { MkPublicShell, CertAuditPublicContent },
|
||||
methods: {
|
||||
getForm() {
|
||||
return this.$refs.page?.getForm?.() || {};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -61,6 +61,14 @@
|
||||
</section-card>
|
||||
|
||||
<section-card title="审核意见">
|
||||
<el-alert
|
||||
v-if="mkEnabled && auditable"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="已开启 MK 审核流:请在列表点击「提交 / 流程」完成审批,此处仅可查看"
|
||||
class="cert-audit-detail__notice"
|
||||
/>
|
||||
<el-form label-position="right" label-width="90px" class="cert-audit-detail__form">
|
||||
<el-form-item label="驳回原因">
|
||||
<el-input
|
||||
@@ -69,7 +77,7 @@
|
||||
:rows="3"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
:disabled="!auditable"
|
||||
:disabled="!localAuditable"
|
||||
placeholder="驳回时必填,不超过 200 字;通过时无需填写"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -78,18 +86,20 @@
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button @click="visible = false">{{ mkEnabled ? '关闭' : '取消' }}</el-button>
|
||||
<el-button
|
||||
v-if="!mkEnabled"
|
||||
type="danger"
|
||||
:disabled="!auditable || loadFailed"
|
||||
:disabled="!localAuditable || loadFailed"
|
||||
:loading="rejectLoading"
|
||||
@click="handleReject"
|
||||
>
|
||||
驳回
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!mkEnabled"
|
||||
type="primary"
|
||||
:disabled="!auditable || loadFailed"
|
||||
:disabled="!localAuditable || loadFailed"
|
||||
:loading="approveLoading"
|
||||
@click="handleApprove"
|
||||
>
|
||||
@@ -124,6 +134,11 @@ export default {
|
||||
type: String,
|
||||
default: 'driver',
|
||||
},
|
||||
// 开启 MK 后详情页仅查看,本地通过/驳回隐藏
|
||||
mkEnabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'success'],
|
||||
data() {
|
||||
@@ -166,6 +181,9 @@ export default {
|
||||
auditable() {
|
||||
return String(this.detail.status) === String(CERT_RECORD_STATUS.PENDING);
|
||||
},
|
||||
localAuditable() {
|
||||
return this.auditable && !this.mkEnabled;
|
||||
},
|
||||
diffKey() {
|
||||
return `${this.detail.id || this.recordId || ''}-${this.detail.submitTime || ''}`;
|
||||
},
|
||||
|
||||
@@ -39,7 +39,21 @@
|
||||
<template #menu="{ row }">
|
||||
<!-- 只有审核中的记录能审;已通过/已驳回只能查看,弹窗内也会禁用审核按钮 -->
|
||||
<el-link type="primary" v-if="canAudit" @click="openDetail(row)">
|
||||
{{ isPending(row) ? '审核' : '查看' }}
|
||||
{{ isPending(row) ? (mkEnabled ? '查看' : '审核') : '查看' }}
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="canAudit && mkEnabled && isPending(row)"
|
||||
type="primary"
|
||||
@click="handleMkSubmit(row)"
|
||||
>
|
||||
提交
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="canAudit && mkEnabled && isPending(row)"
|
||||
type="primary"
|
||||
@click="handleMkAudit(row)"
|
||||
>
|
||||
流程
|
||||
</el-link>
|
||||
</template>
|
||||
</avue-crud>
|
||||
@@ -48,6 +62,7 @@
|
||||
v-model="detailVisible"
|
||||
:record-id="detailRecordId"
|
||||
:biz-type="bizType"
|
||||
:mk-enabled="mkEnabled"
|
||||
@success="handleAuditSuccess"
|
||||
/>
|
||||
</basic-container>
|
||||
@@ -56,8 +71,14 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getPendingPage } from '@/api/transportCapacity/certification-audit';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { CERT_RECORD_STATUS, certStatusTag, formatCertDateTime } from '@/utils/cert-audit';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
import {
|
||||
loadMkEnabled,
|
||||
resolveCertAuditMkBizType,
|
||||
submitMkApprovalFlow,
|
||||
} from '@/utils/mk-approval';
|
||||
import CertAuditDetail from '../cert-audit-detail/main.vue';
|
||||
|
||||
/**
|
||||
@@ -111,6 +132,7 @@ export default {
|
||||
],
|
||||
detailVisible: false,
|
||||
detailRecordId: '',
|
||||
mkEnabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -122,12 +144,65 @@ export default {
|
||||
return this.isAdmin || this.permission?.[this.auditPermissionCode] === true;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
loadMkEnabled()
|
||||
.then(enabled => {
|
||||
this.mkEnabled = !!enabled;
|
||||
})
|
||||
.catch(() => {
|
||||
this.mkEnabled = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
certStatusTag,
|
||||
formatCertDateTime,
|
||||
isPending(row) {
|
||||
return String(row.status) === String(CERT_RECORD_STATUS.PENDING);
|
||||
},
|
||||
handleMkSubmit(row) {
|
||||
if (!row?.id) {
|
||||
this.$message.warning('认证记录ID为空,无法提交审核流');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定提交该认证记录到 MK 审核流?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() =>
|
||||
submitMkApprovalFlow({
|
||||
bizType: resolveCertAuditMkBizType(this.bizType),
|
||||
bizId: row.id,
|
||||
subjectName: row.bizKey || row.driverName || '',
|
||||
approvalStatus: String(row.status ?? ''),
|
||||
mkEnabled: this.mkEnabled,
|
||||
tempVarData: {
|
||||
bizType: this.bizType,
|
||||
bizKey: row.bizKey || '',
|
||||
},
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功');
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
);
|
||||
},
|
||||
handleMkAudit(row) {
|
||||
if (!row?.id) {
|
||||
this.$message.warning('认证记录ID为空,无法打开审核页');
|
||||
return;
|
||||
}
|
||||
getMKApprovalUrl({ bizId: String(row.id) })
|
||||
.then(res => {
|
||||
const url = res?.data?.data;
|
||||
if (!url) {
|
||||
this.$message.error('获取审核跳转地址失败,请先提交审核流');
|
||||
return;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('获取审核跳转地址失败');
|
||||
});
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
getPendingPage(this.bizType, page.currentPage, page.pageSize, {
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="cert-audit-public-content">
|
||||
<div class="cert-audit-public-content__title">{{ pageTitle }}</div>
|
||||
<template v-if="detail">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="认证对象">{{ detail.bizKey || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态">
|
||||
{{ detail.statusText || certStatusTag(detail.status).label }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="提交人">{{ detail.submitUserName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">
|
||||
{{ formatCertDateTime(detail.submitTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="认证类型">{{ bizTypeLabel }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否已认证">
|
||||
{{ detail.certified ? '已认证' : '未认证' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.rejectReason" label="驳回原因" :span="2">
|
||||
{{ detail.rejectReason }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="cert-audit-public-content__section-title">待审内容 vs 当前生效值</div>
|
||||
<cert-audit-diff
|
||||
:key="diffKey"
|
||||
:biz-type="bizType"
|
||||
:pending-json="detail.pendingSnapshotJson"
|
||||
:effective-json="detail.effectiveSnapshotJson"
|
||||
/>
|
||||
</template>
|
||||
<el-empty v-else description="暂无数据" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { certStatusTag, formatCertDateTime } from '@/utils/cert-audit';
|
||||
import CertAuditDiff from '../cert-audit-diff/main.vue';
|
||||
|
||||
export default {
|
||||
name: 'CertAuditPublicContent',
|
||||
components: { CertAuditDiff },
|
||||
props: {
|
||||
bizType: {
|
||||
type: String,
|
||||
default: 'driver',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bizTypeLabel() {
|
||||
return this.bizType === 'vehicle' ? '车辆认证' : '司机认证';
|
||||
},
|
||||
pageTitle() {
|
||||
const key = this.detail?.bizKey ? `:${this.detail.bizKey}` : '';
|
||||
return `${this.bizTypeLabel}审核${key}`;
|
||||
},
|
||||
mkBizType() {
|
||||
return this.bizType === 'vehicle' ? 'cert-audit-vehicle' : 'cert-audit-driver';
|
||||
},
|
||||
diffKey() {
|
||||
return `${this.detail?.id || ''}-${this.detail?.submitTime || ''}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadDetail();
|
||||
},
|
||||
methods: {
|
||||
certStatusTag,
|
||||
formatCertDateTime,
|
||||
loadDetail() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
getMkPublicDetail(this.mkBizType, id).then(res => {
|
||||
this.detail = res.data?.data || null;
|
||||
});
|
||||
},
|
||||
getForm() {
|
||||
const detail = this.detail || {};
|
||||
return {
|
||||
...detail,
|
||||
subject: detail.bizKey || '',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cert-audit-public-content {
|
||||
padding: 16px 20px 24px;
|
||||
|
||||
&__title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
position: relative;
|
||||
margin: 16px 0 12px;
|
||||
padding-left: 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
margin-top: -7px;
|
||||
background: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user