From d0b7c370b2b9e64496daba6c384d141bd4c2c19d Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Sun, 1 Nov 2026 15:47:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B5=81=E7=A8=8B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=A1=B5=E9=99=84=E4=BB=B6=E5=BC=80=E5=85=B3=EF=BC=9B=E5=BE=85?= =?UTF-8?q?=E5=8A=9E=E8=AF=A6=E6=83=85=E6=94=AF=E6=8C=81=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E4=BA=BA=E8=A1=A5=E4=BC=A0=E9=99=84=E4=BB=B6=EF=BC=88ADR=20001?= =?UTF-8?q?6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/approval/attachment/index.ts | 26 ++++++ src/api/approval/flow/model/index.ts | 2 + .../approval/flow/components/node-card.vue | 10 ++- src/views/approval/flow/constants.ts | 11 ++- src/views/approval/flow/index.vue | 86 ++++++++++++++++--- .../todo/components/detail-drawer.vue | 43 ++++++++++ 6 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 src/api/approval/attachment/index.ts diff --git a/src/api/approval/attachment/index.ts b/src/api/approval/attachment/index.ts new file mode 100644 index 0000000..43b5e90 --- /dev/null +++ b/src/api/approval/attachment/index.ts @@ -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>( + 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)); +} diff --git a/src/api/approval/flow/model/index.ts b/src/api/approval/flow/model/index.ts index ef2c61a..3055847 100644 --- a/src/api/approval/flow/model/index.ts +++ b/src/api/approval/flow/model/index.ts @@ -27,6 +27,8 @@ export interface ApprovalFlowNode { name: string; groups?: ApprovalFlowNodeGroup[]; writableFields?: ApprovalFlowWritableField[]; + /** 处理人通过前必须本次新传附件;SUBMIT 节点上表示整单提交必须上传 */ + attachmentRequired?: boolean; } /** diff --git a/src/views/approval/flow/components/node-card.vue b/src/views/approval/flow/components/node-card.vue index c4d929b..03b8f64 100644 --- a/src/views/approval/flow/components/node-card.vue +++ b/src/views/approval/flow/components/node-card.vue @@ -69,6 +69,11 @@ /> + + + + + ({ ...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; }; diff --git a/src/views/approval/flow/constants.ts b/src/views/approval/flow/constants.ts index b8758ae..8ca14d5 100644 --- a/src/views/approval/flow/constants.ts +++ b/src/views/approval/flow/constants.ts @@ -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 || ''; } diff --git a/src/views/approval/flow/index.vue b/src/views/approval/flow/index.vue index eea5174..60f0658 100644 --- a/src/views/approval/flow/index.vue +++ b/src/views/approval/flow/index.vue @@ -61,6 +61,19 @@ +
+
+ 提交环节 + + 跟随代码 + 必须上传附件 + 不必上传附件 + +
+
+ 「跟随代码」沿用单据实现里声明的附件要求;选定后本环节作为链首只读节点发布,不产生待办。 +
+
{ - return JSON.parse(JSON.stringify(nodes || [])) as ApprovalFlowNode[]; - }; - const loading = ref(false); const saving = ref(false); const flows = ref([]); @@ -162,12 +170,43 @@ const historyLoading = ref(false); const history = ref([]); + /* 提交环节: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; } diff --git a/src/views/approval/todo/components/detail-drawer.vue b/src/views/approval/todo/components/detail-drawer.vue index 88d9b0a..5814b3c 100644 --- a/src/views/approval/todo/components/detail-drawer.vue +++ b/src/views/approval/todo/components/detail-drawer.vue @@ -210,6 +210,16 @@ {{ formatApprovalTime(file.createTime) }} + + + 上传附件 + + @@ -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;