feat(details): 审批卡片升级——撤回走后端中转、流程图弹窗同源嵌入、结构化步骤时间线
build / build (push) Successful in 3m17s

This commit is contained in:
chenxing
2026-09-17 01:10:41 +08:00
parent 830f9088f4
commit 468cb8670f
3 changed files with 107 additions and 18 deletions
+4
View File
@@ -123,6 +123,10 @@ class NoticeApi {
flowTrace(serid: number) {
return get<FlowTraceVo>(`${NOTICE}/flow/Trace`, { serid });
}
/** 撤回审核(2026-09-17:后端经 SDK 托管件中转,仅创建人本人+审批中;替代原先前端直调 flow-center) */
flowWithdraw(serid: number) {
return postForm<void>(`${NOTICE}/flow/Withdraw`, { serid });
}
/** 应用级流程绑定(应用设置 > 流程绑定;发通知页据此判断「提交审核」还是「发送」) */
getFlowBinding() {
return get<FlowBindingVo>(`${NOTICE}/flow/Binding`);
+17
View File
@@ -118,6 +118,23 @@ export interface FlowTraceVo {
finishTime?: string | null;
/** 办理轨迹(traceJson 解析后的数组) */
trace?: any[] | null;
/** 结构化步骤(2026-09-17 接入:steps[0] 固定"发起申请",同节点多任务每人一行) */
steps?: FlowProgressStepVo[] | null;
}
/** 流程步骤(SDK FlowProgressStep 同构:后端 /flow/Trace 的 steps 元素) */
export interface FlowProgressStepVo {
nodeId: string;
nodeName: string | null;
label: string | null;
active: boolean;
assignees?: {
userName?: string | null;
status?: number | null;
statusLabel?: string | null;
opinion?: string | null;
handleTime?: string | null;
}[] | null;
}
// ============================ 通知详情 ============================
+86 -18
View File
@@ -36,35 +36,59 @@ async function loadTrace() {
}
}
/** 撤回/催办直调 flow-center 用户面(同主域 CAS 免二次登录;本地 dev 走 flowHost CORS) */
/**
* 撤回:走本应用后端(2026-09-17 接入:后端经 SDK 托管件中转并复核创建人,
* 不再前端直调 flow-center——跨应用 CAS 票据 + CORS 双重外溢且"谁能撤"落在前端)。
* 催办:仍是"人"的动作,走 flow-center 用户面(同主域 CAS 免二次登录)。
*/
async function flowAction(action: "withdraw" | "urge") {
const instanceId = detail.value?.flowInstanceId;
if (!instanceId || !config.flowHost) return;
if (!instanceId) return;
flowActing.value = true;
try {
const resp = await fetch(`${config.flowHost}/api/flow/instance/${instanceId}/${action}`, {
method: "POST",
credentials: "include",
});
const res = await resp.json().catch(() => null);
if (resp.ok && res && res.code === 0) {
Message.success(action === "withdraw" ? "已撤回审核,可编辑后重新提交" : "已发送催办提醒");
load(); // 撤回后状态刷新
loadTrace();
if (action === "withdraw") {
await noticeApi.flowWithdraw(serid.value).send();
Message.success("已撤回审核,可编辑后重新提交");
await load();
await loadTrace();
} else {
Message.error(res?.msg || (action === "withdraw" ? "撤回失败(已有人办理时不可撤回)" : "催办失败"));
if (!config.flowHost) return;
const resp = await fetch(`${config.flowHost}/api/flow/instance/${instanceId}/urge`, {
method: "POST",
credentials: "include",
});
const res = await resp.json().catch(() => null);
if (resp.ok && res && res.code === 0) {
Message.success("已发送催办提醒");
} else {
Message.error(res?.msg || "催办失败");
}
}
} catch (e: any) {
Message.error("流程中心不可达:" + (e.message || e));
Message.error(
action === "withdraw"
? "撤回失败(已有人办理时不可撤回):" + (e?.msg || e?.message || e)
: "流程中心不可达:" + (e?.message || e),
);
} finally {
flowActing.value = false;
}
}
/** 查看流程图:flow-center 示意图深链(绑流程时;Notice 指定到人场景用人员视图) */
/* 流程图弹窗(2026-09-17:后端同源出图 /flow/Diagram,img 免 token 免跨域,不再深链 flow-center) */
const diagramVisible = ref(false);
function openFlowDiagram() {
if (!detail.value?.flowGuid || !config.flowHost) return;
window.open(`${config.flowHost}/#/diagram?flowGuid=${detail.value.flowGuid}&mode=user`, "_blank");
if (!detail.value?.flowGuid || !detail.value?.flowInstanceId) return;
diagramVisible.value = true;
}
const diagramSrc = computed(() =>
diagramVisible.value && detail.value?.flowInstanceId
? `${config.serviceHost}/api/notice/flow/Diagram?serid=${serid.value}`
: "",
);
function onDiagramError() {
diagramVisible.value = false;
Message.error("流程图加载失败(可能已过期,请刷新页面重试)");
}
const readGroups = ref<ReadGroup[]>([]);
const showRead = ref(false);
@@ -264,8 +288,26 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
办理意见:{{ flowTrace.flowResult.opinion }}
<template v-if="flowTrace.flowResult.rejectNode">({{ flowTrace.flowResult.rejectNode }})</template>
</div>
<!-- 节点时间线(traceJson 解析) -->
<div v-if="flowTrace.trace && flowTrace.trace.length" class="flow-timeline">
<!-- 结构化步骤时间线(2026-09-17 接入 SDK progress;steps[0] 固定"发起申请",同节点多任务每人一行) -->
<div v-if="flowTrace.steps && flowTrace.steps.length" class="flow-timeline">
<div
v-for="(s, i) in flowTrace.steps"
:key="'fs' + i"
class="flow-step"
:class="{ 'flow-step-active': s.active }"
>
<span class="flow-step-node">{{ s.label || s.nodeName || `节点${i + 1}` }}</span>
<template v-if="s.assignees && s.assignees.length">
<span v-for="(a, j) in s.assignees" :key="j" class="flow-step-user">
{{ a.userName || "" }}
<em v-if="a.statusLabel" class="flow-step-status">{{ a.statusLabel }}</em>
<em v-if="a.opinion" class="flow-step-op">({{ a.opinion }})</em>
</span>
</template>
</div>
</div>
<!-- 节点时间线(traceJson 解析,steps 不可用时的兜底) -->
<div v-else-if="flowTrace.trace && flowTrace.trace.length" class="flow-timeline">
<div v-for="(t, i) in flowTrace.trace" :key="i" class="flow-step">
<span class="flow-step-node">{{ t.nodeName || t.node || t.taskName || `节点${i + 1}` }}</span>
<span class="flow-step-user">{{ t.userName || t.assigneeName || t.handlerName || "" }}</span>
@@ -275,6 +317,13 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
</div>
</div>
<!-- 流程图弹窗(后端同源出图,免 token 免跨域) -->
<Modal v-model="diagramVisible" title="流程示意图" :width="860" footer-hide>
<div class="diagram-wrap">
<img v-if="diagramSrc" :src="diagramSrc" alt="流程示意图" @error="onDiagramError" />
</div>
</Modal>
<div v-if="viewType !== 0" class="meta">
<span>发文单位:{{ detail.DeptName || "—" }}</span>
<span>发布人:{{ detail.TeaName || "—" }}</span>
@@ -410,6 +459,25 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
.flow-step-op {
color: #ed4014;
}
.flow-step-active .flow-step-node {
color: #2d8cf0;
font-weight: 600;
}
.flow-step-status {
font-style: normal;
color: #808695;
margin-left: 4px;
}
.flow-step .flow-step-op {
font-style: normal;
}
.diagram-wrap {
text-align: center;
min-height: 200px;
}
.diagram-wrap img {
max-width: 100%;
}
.meta {
display: flex;
flex-wrap: wrap;