diff --git a/src/api/module/approval.ts b/src/api/module/approval.ts index 087eb03..9a39c53 100644 --- a/src/api/module/approval.ts +++ b/src/api/module/approval.ts @@ -61,6 +61,45 @@ export interface ApprovalPage { hasNext: boolean; } +/* ===== v2.1 应用内审批:业务详情卡片数据源(/oapi/flow/tasks/by-biz 经本应用后端代理) ===== */ + +/** 出口(多出口节点办理时须选其一;单出口节点该数组为空) */ +export interface ByBizOutlet { + name: string; + toNodeId: string; + toNodeName: string; +} + +/** 当前人在本通知上的一条待办任务 */ +export interface ByBizTask { + taskId: number; + nodeId: string; + nodeName: string; + arriveTime: string; + outlets: ByBizOutlet[]; +} + +/** + * by-biz 出参。语义要点: + * - tasks 长度 0/1/N 都是成功响应,N>1 时卡片不得猜选(引导列表/任务页); + * - "通知不存在/不可见/不是我的待办" 三者同形(tasks 空 + instanceId 可能为 null), + * 前端不得据此反推是否存在别人的待办; + * - currentNodes 是**节点 id** 逗号串(办件原文口径),人读名称用 tasks[].nodeName。 + */ +export interface ByBizResult { + instanceId: number | null; + bizType: string; + bizId: string; + status: number | null; + currentNodes: string | null; + tasks: ByBizTask[]; +} + +/** 换址类端点统一返回 { pageUrl } */ +export interface PageUrlResult { + pageUrl: string; +} + export const approvalApi = { /** 我的待审核(bizType=notice 固定在后端) */ todo(page = 1, size = 10): Method> { @@ -70,9 +109,24 @@ export const approvalApi = { instances(status?: number, page = 1, size = 10): Method> { return get(`${APPROVAL}/Instances`, status ? { status, page, size } : { page, size }); }, - /** 通过(opinion 可选) */ - approve(taskId: number, opinion?: string): Method { - return postJSON(`${APPROVAL}/Approve?taskId=${taskId}`, opinion ? { opinion } : {}); + /** 当前人在该通知上的待办(卡片可写/只读、多待办与多出口判定的唯一依据) */ + byBiz(serid: number): Method { + return get(`${APPROVAL}/ByBiz`, { serid }); + }, + /** 审批记录抽屉地址(token 只读页,现取现用) */ + recordUrl(serid: number): Method { + return get(`${APPROVAL}/RecordUrl`, { serid }); + }, + /** 完整办理任务页地址(后端已确认 taskId 属于当前人才签发;CAS 页无 token) */ + taskPageUrl(serid: number, taskId: number): Method { + return get(`${APPROVAL}/TaskPageUrl`, { serid, taskId }); + }, + /** 通过(opinion 可选;多出口节点必须带 edgeName,否则后端业务错) */ + approve(taskId: number, opinion?: string, edgeName?: string): Method { + const body: Record = {}; + if (opinion) body.opinion = opinion; + if (edgeName) body.edgeName = edgeName; + return postJSON(`${APPROVAL}/Approve?taskId=${taskId}`, body); }, /** 拒绝(opinion 必填) */ reject(taskId: number, opinion: string): Method { diff --git a/src/api/module/approvalAdapter.ts b/src/api/module/approvalAdapter.ts new file mode 100644 index 0000000..3eb7b18 --- /dev/null +++ b/src/api/module/approvalAdapter.ts @@ -0,0 +1,25 @@ +import approvalApi from "@/api/module/approval"; +import { noticeApi } from "@/api/module/notice"; +import type { FlowApprovalApi } from "@/components/flow-approval/types"; + +/** + * Notice ↔ flow-approval 共享组件的适配器(方案 §5.4 的 Notice 侧落点)。 + * + * 组件契约用 `bizId: string`(跨应用通用),Notice 后端用 `serid: number`; + * trace 走通知自己的进度接口而不新建 approval 端点,避免同一份轨迹两个来源。 + * + * 这一层是**唯一的**映射位置:列表页与详情页卡片共用,改端点只改这里。 + * 身份字段(operatorId/schId/name)在此一律不出现——由 NoticeService 从 CAS 会话取。 + */ +export const noticeFlowApprovalApi: FlowApprovalApi = { + todo: (p) => approvalApi.todo(p?.page ?? 1, p?.size ?? 10).send(), + instances: (p) => approvalApi.instances(p?.status, p?.page ?? 1, p?.size ?? 10).send(), + byBiz: (bizId) => approvalApi.byBiz(Number(bizId)).send(), + trace: (bizId) => noticeApi.flowTrace(Number(bizId)).send(), + approve: (taskId, opinion, edgeName) => approvalApi.approve(taskId, opinion, edgeName).send(), + reject: (taskId, opinion) => approvalApi.reject(taskId, opinion).send(), + recordUrl: (bizId) => approvalApi.recordUrl(Number(bizId)).send(), + taskPageUrl: (bizId, taskId) => approvalApi.taskPageUrl(Number(bizId), taskId).send(), +}; + +export default noticeFlowApprovalApi; diff --git a/src/components/flow-approval/FlowApprovalCard.vue b/src/components/flow-approval/FlowApprovalCard.vue new file mode 100644 index 0000000..5d297a5 --- /dev/null +++ b/src/components/flow-approval/FlowApprovalCard.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/src/components/flow-approval/FlowApprovalList.vue b/src/components/flow-approval/FlowApprovalList.vue new file mode 100644 index 0000000..9eaba08 --- /dev/null +++ b/src/components/flow-approval/FlowApprovalList.vue @@ -0,0 +1,348 @@ + + + + + diff --git a/src/components/flow-approval/FlowRecordDrawer.vue b/src/components/flow-approval/FlowRecordDrawer.vue new file mode 100644 index 0000000..0018eef --- /dev/null +++ b/src/components/flow-approval/FlowRecordDrawer.vue @@ -0,0 +1,109 @@ + - - diff --git a/src/views/notice/NoticeDetails.vue b/src/views/notice/NoticeDetails.vue index 6250b79..31891b1 100644 --- a/src/views/notice/NoticeDetails.vue +++ b/src/views/notice/NoticeDetails.vue @@ -5,6 +5,9 @@ import { Message } from "view-ui-plus"; import { noticeApi } from "@/api/module/notice"; import type { NoticeDetailVo, NextPrevVo, ReadGroup, FlowTraceVo } from "@/types/notice"; import FilePreviewModal from "@/components/FilePreviewModal.vue"; +import FlowApprovalCard from "@/components/flow-approval/FlowApprovalCard.vue"; +import FlowTaskEmbed from "@/components/flow-approval/FlowTaskEmbed.vue"; +import noticeFlowApprovalApi from "@/api/module/approvalAdapter"; import Utils from "@/utils"; import { fitDiagram } from "@/utils/diagramImg"; import config from "@/assets/config/host.config"; @@ -110,6 +113,40 @@ async function openFlowEmbed() { } } +/* ============ v2.1 审批动作:卡片办理后刷新;完整办理走 CAS 去壳任务页 ============ */ + +/** 卡片里通过/驳回成功:刷新进度与详情(业务状态由 flow-center 回调推进,前端不本地写) */ +async function onApprovalHandled() { + await Promise.all([load(), loadTrace()]); +} + +const taskVisible = ref(false); +const taskPageUrl = ref(""); +const taskCurrentId = ref(""); + +/** 任务页地址由后端签发(它会先确认 taskId 属于当前人待办),前端不拼 flow-center 基址 */ +async function openTaskFromCard(taskId: number) { + taskPageUrl.value = ""; + try { + const res: any = await noticeFlowApprovalApi.taskPageUrl(String(serid.value), taskId); + if (!res?.pageUrl) { + Message.error(res?.msg || "办理页地址获取失败"); + return; + } + taskCurrentId.value = taskId; + taskPageUrl.value = res.pageUrl; + taskVisible.value = true; + } catch (e: any) { + Message.error(e?.msg || e?.message || "办理页打开失败"); + } +} + +/** 任务页里办完:关弹窗并刷新进度/详情,不等轮询 */ +async function onTaskHandled() { + taskVisible.value = false; + await Promise.all([load(), loadTrace()]); +} + const readGroups = ref([]); const showRead = ref(false); const prevId = ref(0); @@ -345,6 +382,14 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number; + + +
@@ -359,6 +404,10 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
+ + +
发文单位:{{ detail.DeptName || "—" }} 发布人:{{ detail.TeaName || "—" }}