feat: 流程配置页附件开关;待办详情支持审批人补传附件(ADR 0016)
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult } from '@/api';
|
||||
import { THINK_API_URL } from '@/config/setting';
|
||||
import type { ApprovalAttachment } from '../instance/model';
|
||||
|
||||
/**
|
||||
* 上传审批附件(报送人与当前待办处理人都可上传,ADR 0016)
|
||||
*/
|
||||
export async function uploadApprovalAttachment(data: {
|
||||
instanceId: number;
|
||||
file: File;
|
||||
category?: string;
|
||||
}) {
|
||||
const form = new FormData();
|
||||
form.append('file', data.file);
|
||||
form.append('instanceId', String(data.instanceId));
|
||||
form.append('category', data.category || 'OTHER');
|
||||
const res = await request.post<ApiResult<ApprovalAttachment>>(
|
||||
THINK_API_URL + '/approval/attachment/upload',
|
||||
form
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -27,6 +27,8 @@ export interface ApprovalFlowNode {
|
||||
name: string;
|
||||
groups?: ApprovalFlowNodeGroup[];
|
||||
writableFields?: ApprovalFlowWritableField[];
|
||||
/** 处理人通过前必须本次新传附件;SUBMIT 节点上表示整单提交必须上传 */
|
||||
attachmentRequired?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,6 +69,11 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-if="isAttachmentNode" :md="8" :sm="24">
|
||||
<a-form-item label="通过前必须上传附件">
|
||||
<a-switch v-model:checked="form.attachmentRequired" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-if="needOrgCode" :md="8" :sm="24">
|
||||
<a-form-item label="组织编码">
|
||||
<a-select
|
||||
@@ -216,9 +221,12 @@
|
||||
groups: (props.node.groups || []).map((group) => ({ ...group })),
|
||||
writableFields: (props.node.writableFields || []).map((field) => ({
|
||||
...field
|
||||
}))
|
||||
})),
|
||||
attachmentRequired: !!props.node.attachmentRequired
|
||||
});
|
||||
|
||||
const isAttachmentNode = computed(() => form.type !== 'DISPATCH');
|
||||
|
||||
const isOrgResolver = (resolver?: string) => {
|
||||
return !!resolver && ORG_RESOLVERS.indexOf(resolver) !== -1;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,15 @@ export const NODE_TYPE_OPTIONS = [
|
||||
{ value: 'ARCHIVE', label: '归档' }
|
||||
];
|
||||
|
||||
/** 提交环节:链首只读节点,不在可添加类型里 */
|
||||
export const NODE_SUBMIT = 'SUBMIT';
|
||||
|
||||
/** 节点类型显示用(比可选项多一个提交环节) */
|
||||
export const NODE_TYPE_LABELS = [
|
||||
...NODE_TYPE_OPTIONS,
|
||||
{ value: NODE_SUBMIT, label: '提交' }
|
||||
];
|
||||
|
||||
/** 需要固定组织编码的解析器 */
|
||||
export const ORG_RESOLVERS = ['orgLeaderByCode', 'orgDeputyByCode'];
|
||||
|
||||
@@ -17,6 +26,6 @@ export const WRITABLE_TYPES = ['APPROVE', 'COUNTERSIGN', 'CONFIRM'];
|
||||
|
||||
/** 节点类型显示名 */
|
||||
export function nodeTypeLabel(type?: string) {
|
||||
const item = NODE_TYPE_OPTIONS.find((option) => option.value === type);
|
||||
const item = NODE_TYPE_LABELS.find((option) => option.value === type);
|
||||
return item ? item.label : type || '';
|
||||
}
|
||||
|
||||
@@ -61,6 +61,19 @@
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="submit-stage">
|
||||
<div class="submit-stage-header">
|
||||
<span class="submit-stage-title">提交环节</span>
|
||||
<a-radio-group v-model:value="submitMode" size="small">
|
||||
<a-radio-button value="FOLLOW">跟随代码</a-radio-button>
|
||||
<a-radio-button value="ON">必须上传附件</a-radio-button>
|
||||
<a-radio-button value="OFF">不必上传附件</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="submit-stage-tip">
|
||||
「跟随代码」沿用单据实现里声明的附件要求;选定后本环节作为链首只读节点发布,不产生待办。
|
||||
</div>
|
||||
</div>
|
||||
<a-empty
|
||||
v-if="!nodes.length"
|
||||
description="该流程尚未发布,点击「载入出厂模板」开始编辑"
|
||||
@@ -145,11 +158,6 @@
|
||||
} from '@/api/approval/flow/model';
|
||||
import type { ApprovalOrganization } from '@/api/approval/instance/model';
|
||||
|
||||
/* 深拷贝节点:编辑区不能直接改列表里的对象 */
|
||||
const cloneNodes = (nodes?: ApprovalFlowNode[]) => {
|
||||
return JSON.parse(JSON.stringify(nodes || [])) as ApprovalFlowNode[];
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const flows = ref<ApprovalFlow[]>([]);
|
||||
@@ -162,12 +170,43 @@
|
||||
const historyLoading = ref(false);
|
||||
const history = ref<ApprovalFlowVersion[]>([]);
|
||||
|
||||
/* 提交环节:FOLLOW 沿用代码声明,ON/OFF 会以链首 SUBMIT 节点发布(ADR 0016) */
|
||||
const submitMode = ref('FOLLOW');
|
||||
|
||||
/* 加载一条节点链:链首 SUBMIT 拆成独立的提交环节开关 */
|
||||
const setChain = (chain?: ApprovalFlowNode[]) => {
|
||||
const list = chain || [];
|
||||
const head = list.length && list[0].type === 'SUBMIT' ? list[0] : null;
|
||||
submitMode.value = head
|
||||
? head.attachmentRequired
|
||||
? 'ON'
|
||||
: 'OFF'
|
||||
: 'FOLLOW';
|
||||
nodes.value = list.filter((node) => node.type !== 'SUBMIT');
|
||||
editorRevision.value = editorRevision.value + 1;
|
||||
};
|
||||
|
||||
/* 发布时拼回完整链:提交环节固定在最前 */
|
||||
const buildChain = () => {
|
||||
if (submitMode.value === 'FOLLOW') {
|
||||
return nodes.value;
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'submit',
|
||||
type: 'SUBMIT',
|
||||
name: '提交',
|
||||
attachmentRequired: submitMode.value === 'ON'
|
||||
},
|
||||
...nodes.value
|
||||
];
|
||||
};
|
||||
|
||||
/* 加载编辑区:整链替换时递增 revision,强制节点卡片重新初始化 */
|
||||
const applyFlow = (flow: ApprovalFlow) => {
|
||||
current.value = flow;
|
||||
flowCode.value = flow.flowCode;
|
||||
nodes.value = cloneNodes(flow.nodes);
|
||||
editorRevision.value = editorRevision.value + 1;
|
||||
setChain(flow.nodes);
|
||||
};
|
||||
|
||||
const refreshFlows = (keepCode?: string) => {
|
||||
@@ -236,8 +275,7 @@
|
||||
okText: '载入',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
nodes.value = cloneNodes(current.value?.factoryNodes);
|
||||
editorRevision.value = editorRevision.value + 1;
|
||||
setChain(current.value?.factoryNodes);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -260,8 +298,7 @@
|
||||
};
|
||||
|
||||
const loadVersion = (item: ApprovalFlowVersion) => {
|
||||
nodes.value = cloneNodes(item.nodes);
|
||||
editorRevision.value = editorRevision.value + 1;
|
||||
setChain(item.nodes);
|
||||
historyVisible.value = false;
|
||||
message.info('已载入 v' + item.version + ',确认后点「保存并发布」');
|
||||
};
|
||||
@@ -284,7 +321,7 @@
|
||||
saving.value = true;
|
||||
return publishFlowConfig({
|
||||
flowCode: current.value!.flowCode,
|
||||
nodes: nodes.value
|
||||
nodes: buildChain()
|
||||
})
|
||||
.then((data) => {
|
||||
saving.value = false;
|
||||
@@ -349,6 +386,31 @@
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.submit-stage {
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border: 1px dashed rgba(0, 0, 0, 0.12);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.submit-stage-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.submit-stage-title {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-stage-tip {
|
||||
margin-top: 6px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.flow-add {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@@ -210,6 +210,16 @@
|
||||
{{ formatApprovalTime(file.createTime) }}
|
||||
</span>
|
||||
</div>
|
||||
<a-upload
|
||||
v-if="canUploadAttachment"
|
||||
:show-upload-list="false"
|
||||
:custom-request="uploadRequest"
|
||||
accept=".jpg,.jpeg,.png,.pdf,.doc,.docx,.xls,.xlsx"
|
||||
>
|
||||
<a-button type="link" size="small" :loading="uploading">
|
||||
上传附件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
@@ -386,6 +396,7 @@
|
||||
formatFileSize
|
||||
} from '../../format';
|
||||
import DocumentView from '../../components/document-view.vue';
|
||||
import { uploadApprovalAttachment } from '@/api/approval/attachment';
|
||||
|
||||
const props = defineProps<{
|
||||
// 抽屉是否打开
|
||||
@@ -447,6 +458,38 @@
|
||||
});
|
||||
};
|
||||
|
||||
// 待办处理人可在审批中补传附件(ADR 0016)
|
||||
const uploading = ref(false);
|
||||
const canUploadAttachment = computed(() =>
|
||||
Boolean(
|
||||
detail.value?.permissions?.canApprove ||
|
||||
detail.value?.permissions?.canConfirm ||
|
||||
detail.value?.permissions?.canArchive
|
||||
)
|
||||
);
|
||||
|
||||
const uploadRequest = (options: any) => {
|
||||
if (!props.instanceId) {
|
||||
return;
|
||||
}
|
||||
uploading.value = true;
|
||||
uploadApprovalAttachment({
|
||||
instanceId: props.instanceId,
|
||||
file: options.file
|
||||
})
|
||||
.then(() => {
|
||||
uploading.value = false;
|
||||
options.onSuccess?.({});
|
||||
message.success('附件已上传');
|
||||
load();
|
||||
})
|
||||
.catch((e: any) => {
|
||||
uploading.value = false;
|
||||
options.onError?.(e);
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const load = async () => {
|
||||
if (!props.instanceId) {
|
||||
detail.value = null;
|
||||
|
||||
Reference in New Issue
Block a user