From 429a7f9152cbcb6a7f80ab9271eed6564d2d7466 Mon Sep 17 00:00:00 2001 From: chenxing Date: Thu, 17 Sep 2026 01:03:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E8=AF=A6=E6=83=85=E9=A1=B5?= =?UTF-8?q?=E7=88=B6=E5=AD=90=E5=AF=BC=E8=88=AA/=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E6=8C=89=E9=92=AE/=E5=8A=9E=E4=BB=B6?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=86=85=E5=B5=8C=E6=B5=81=E7=A8=8B=E5=9B=BE?= =?UTF-8?q?=EF=BC=88=E5=8A=9E=E4=BB=B6=E8=BF=9B=E5=BA=A6=E6=8A=BD=E5=B1=89?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=B1=E7=BB=AA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/layout.vue | 5 ++++- src/router/routes.ts | 13 +++++++++---- src/views/flow/InstanceDetail.vue | 14 ++++++++++++++ src/views/flow/MyInstances.vue | 11 ++++++----- src/views/flow/Workbench.vue | 7 ++++--- src/views/flow/admin/Callbacks.vue | 10 +++++----- src/views/flow/admin/Defs.vue | 17 +++++++++-------- src/views/flow/admin/Instances.vue | 3 ++- src/views/flow/admin/Maps.vue | 3 ++- 9 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/components/layout.vue b/src/components/layout.vue index ca359b2..8d38d4c 100644 --- a/src/components/layout.vue +++ b/src/components/layout.vue @@ -49,7 +49,7 @@ const menuArr = computed(() => flaten(modList.value)); const routeChange = (to: any) => { const meta: any = to.meta || {}; - const { modId, title, parentId } = meta; + const { modId, title } = meta; const navArr: any[] = []; // 优先按 path 匹配菜单(平台模块 id 与路由 meta.modId 不保证一致), @@ -68,6 +68,9 @@ const routeChange = (to: any) => { return; } + // 详情页父级(左侧高亮 + 面包屑"父页>本页"):路由 meta.parentId 静态默认, + // 可被来源列表跳转时的 ?pid=<父菜单modId> 动态覆盖(同一详情页多个入口,UserBase 同款范式) + const parentId = Number(to.query?.pid) || meta.parentId; if (parentId) { const parent = menuArr.value.find((x: any) => x.modId === parentId); if (parent) { diff --git a/src/router/routes.ts b/src/router/routes.ts index e329525..6a0ef28 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -7,6 +7,10 @@ import type { RouteRecordRaw } from "vue-router"; * ② meta.modId = 对应 app_module.custom_id(后端 FlowBaseController 取 custom_id 作 modId)。 * 数据/授权维护见 UserBase 侧「模块管理」与 flow-center接入UserBase菜单.sql。 * 办理页 /task/:taskId 为 UserMsg jumpPath 落地页(不挂菜单)。 + *

+ * 详情/深链页(不挂菜单)通过 meta.parentId 关联父级菜单(UserBase 同款范式):左侧菜单保持 + * 父级高亮、面包屑补全"父页>本页"。同一详情页可能被多个列表进入(如办件详情既可从"我的办件" + * 也可从"办件记录"),此时列表页跳转带 ?pid=<父菜单modId> 动态覆盖 meta.parentId 静态默认。 */ const routes: RouteRecordRaw[] = [ { @@ -25,20 +29,21 @@ const routes: RouteRecordRaw[] = [ path: "/task/:taskId", name: "flow_task_handle", component: () => import("@/views/flow/TaskHandle.vue"), - meta: { title: "任务办理" }, + // 父级默认指向我的待办所在的"工作台"(外部消息落地页无 pid,走静态默认) + meta: { title: "任务办理", parentId: 100 }, }, { path: "/instance/:instanceId", name: "flow_instance_detail", component: () => import("@/views/flow/InstanceDetail.vue"), - meta: { title: "办件详情" }, + meta: { title: "办件详情", parentId: 102 }, }, { // 流程示意图深链页(不挂菜单,同 /task/:taskId 模式);跨应用入口 window.open 整页跳转 path: "/diagram", name: "flow_diagram", component: () => import("@/views/flow/FlowDiagram.vue"), - meta: { title: "流程示意图" }, + meta: { title: "流程示意图", parentId: 103 }, }, { path: "/admin/instances", @@ -58,7 +63,7 @@ const routes: RouteRecordRaw[] = [ path: "/admin/defs/editor", name: "flow_def_editor", component: () => import("@/views/flow/admin/def-editor/index.vue"), - meta: { title: "流程编辑器" }, + meta: { title: "流程编辑器", parentId: 103 }, }, { path: "/admin/maps", diff --git a/src/views/flow/InstanceDetail.vue b/src/views/flow/InstanceDetail.vue index 82143b7..7239a1b 100644 --- a/src/views/flow/InstanceDetail.vue +++ b/src/views/flow/InstanceDetail.vue @@ -53,6 +53,9 @@ const branchText = computed(() => { return `${arrived}/${branches.length} 分支已到达汇聚点`; }); +/** 流程示意图图片直嵌(2026-09-16 "CAS 验证码式":后端 /diagram.png 返回 PNG 流,img 直显) */ +const diagramSrc = computed(() => `/api/flow/diagram.png?instanceId=${route.params.instanceId}`); + function statusName(s: number) { return INSTANCE_STATUS[s] || s; } @@ -110,6 +113,14 @@ onMounted(async () => { + + + 流程示意图 + + @@ -145,6 +156,9 @@ export default { methods: { taskStatusName } }; .k { width: 120px; color: #888; flex-shrink: 0; } .v { white-space: pre-wrap; word-break: break-all; } .muted { color: #999; } +.diagram-box { display: flex; justify-content: center; } +.diagram-img { max-width: 100%; } +.diagram-more { float: right; font-size: 12px; font-weight: normal; margin-right: 16px; } .node { font-weight: 600; margin-right: 6px; } .opinion { color: #666; } .time { color: #aaa; font-size: 12px; } diff --git a/src/views/flow/MyInstances.vue b/src/views/flow/MyInstances.vue index e72a691..780275a 100644 --- a/src/views/flow/MyInstances.vue +++ b/src/views/flow/MyInstances.vue @@ -4,6 +4,7 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { Message } from "view-ui-plus"; import { flowApi, INSTANCE_STATUS } from "@/api/module/flow"; +import Utils from "@/utils"; const router = useRouter(); const loading = ref(false); @@ -23,13 +24,13 @@ const columns = [ { title: "当前节点", key: "currentNodes", width: 150 }, { title: "提交时间", width: 170, render: (h: any, p: any) => h("span", (p.row.submitTime || "").replace("T", " ")) }, { title: "办结时间", width: 170, render: (h: any, p: any) => h("span", (p.row.finishTime || "").replace("T", " ") || "-") }, - { title: "操作", width: 180, render: (h: any, p: any) => { - const btns = [h("Button", { size: "small", onClick: () => router.push(`/instance/${p.row.instanceId}`) }, () => "详情")]; + { title: "操作", width: 150, render: (h: any, p: any) => { + const btns = [Utils.operatingTextHandle(h, "详情", "", () => router.push(`/instance/${p.row.instanceId}?pid=101`))]; if (p.row.status === 10) { - btns.push(h("Button", { size: "small", onClick: () => onWithdraw(p.row) }, () => "撤回")); - btns.push(h("Button", { size: "small", onClick: () => onUrge(p.row) }, () => "催办")); + btns.push(Utils.operatingTextHandle(h, "撤回", "", () => onWithdraw(p.row))); + btns.push(Utils.operatingTextHandle(h, "催办", "", () => onUrge(p.row))); } - return h("div", { style: "display:flex;gap:4px" }, btns); + return btns; } }, ]; diff --git a/src/views/flow/Workbench.vue b/src/views/flow/Workbench.vue index e068336..c63fb24 100644 --- a/src/views/flow/Workbench.vue +++ b/src/views/flow/Workbench.vue @@ -4,6 +4,7 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { Message } from "view-ui-plus"; import { flowApi, TASK_STATUS, INSTANCE_STATUS } from "@/api/module/flow"; +import Utils from "@/utils"; const router = useRouter(); const tab = ref<"todo" | "done">("todo"); @@ -18,7 +19,7 @@ const todoColumns = [ { title: "当前节点", key: "nodeName", width: 160 }, { title: "提交人", key: "submitter", width: 100, render: (h: any, p: any) => h("span", p.row.submitterName || "-") }, { title: "到达时间", key: "arriveTime", width: 170, render: (h: any, p: any) => h("span", (p.row.arriveTime || "").replace("T", " ")) }, - { title: "操作", width: 90, render: (h: any, p: any) => h("Button", { size: "small", type: "primary", onClick: () => goHandle(p.row) }, () => "办理") }, + { title: "操作", width: 90, render: (h: any, p: any) => Utils.operatingTextHandle(h, "办理", "", () => goHandle(p.row)) }, ]; const doneColumns = [ @@ -28,7 +29,7 @@ const doneColumns = [ { title: "意见", key: "opinion", minWidth: 140 }, { title: "办理时间", width: 170, render: (h: any, p: any) => h("span", (p.row.handleTime || "").replace("T", " ")) }, { title: "办件状态", width: 100, render: (h: any, p: any) => h("span", INSTANCE_STATUS[p.row.instanceStatus] || p.row.instanceStatus) }, - { title: "操作", width: 90, render: (h: any, p: any) => h("Button", { size: "small", onClick: () => router.push(`/instance/${p.row.instanceId}`) }, () => "详情") }, + { title: "操作", width: 90, render: (h: any, p: any) => Utils.operatingTextHandle(h, "详情", "", () => router.push(`/instance/${p.row.instanceId}?pid=100`)) }, ]; function submitterNameFallback(row: any) { @@ -36,7 +37,7 @@ function submitterNameFallback(row: any) { } function goHandle(row: any) { - router.push(`/task/${row.taskId}`); + router.push(`/task/${row.taskId}?pid=100`); } async function load() { diff --git a/src/views/flow/admin/Callbacks.vue b/src/views/flow/admin/Callbacks.vue index d2eebfe..5a69caa 100644 --- a/src/views/flow/admin/Callbacks.vue +++ b/src/views/flow/admin/Callbacks.vue @@ -3,6 +3,7 @@ import { ref, onMounted } from "vue"; import { Message, Modal } from "view-ui-plus"; import { flowApi } from "@/api/module/flow"; +import Utils from "@/utils"; const LOG_STATUS: Record = { 0: "待推/重试中", 1: "成功", 2: "死信" }; const EVENT_OPTIONS = ["flow.submitted", "flow.task.created", "flow.task.handled", "flow.completed", "flow.rejected", "flow.withdrawn"]; @@ -19,11 +20,10 @@ const regColumns = [ { title: "订阅事件", key: "eventTypes", width: 200, render: (h: any, p: any) => h("span", p.row.eventTypes || "全部") }, { title: "状态", width: 80, render: (h: any, p: any) => h("Tag", { color: p.row.status === 0 ? "green" : "gray" }, () => (p.row.status === 0 ? "启用" : "停用")) }, { title: "操作", width: 220, render: (h: any, p: any) => { - const btns: any[] = [ - h("Button", { size: "small", onClick: () => onRegStatus(p.row, p.row.status === 0) }, () => (p.row.status === 0 ? "停用" : "启用")), - h("Button", { size: "small", type: "warning", onClick: () => onResetSecret(p.row) }, () => "重置secret"), + return [ + Utils.operatingTextHandle(h, p.row.status === 0 ? "停用" : "启用", "", () => onRegStatus(p.row, p.row.status === 0)), + Utils.operatingTextHandle(h, "重置secret", "text-error", () => onResetSecret(p.row)), ]; - return h("div", { style: "display:flex;gap:4px" }, btns); } }, ]; @@ -60,7 +60,7 @@ const hungColumns = [ { title: "末次结果", key: "lastError", minWidth: 220, render: (h: any, p: any) => h("span", p.row.lastError || p.row.lastHttpStatus || "-") }, { title: "下次重推", width: 160, render: (h: any, p: any) => h("span", (p.row.nextRetryTime || "").replace("T", " ") || "-") }, { title: "操作", width: 120, render: (h: any, p: any) => - h("Button", { size: "small", type: "error", onClick: () => onMarkDead(p.row) }, () => "标记死信") }, + Utils.operatingTextHandle(h, "标记死信", "text-error", () => onMarkDead(p.row)) }, ]; async function loadRegs() { diff --git a/src/views/flow/admin/Defs.vue b/src/views/flow/admin/Defs.vue index 85e8e15..7338586 100644 --- a/src/views/flow/admin/Defs.vue +++ b/src/views/flow/admin/Defs.vue @@ -6,6 +6,7 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { Message } from "view-ui-plus"; import { flowApi } from "@/api/module/flow"; +import Utils from "@/utils"; const router = useRouter(); const DEF_STATUS: Record = { 0: "草稿", 1: "已发布", 2: "停用" }; @@ -25,18 +26,18 @@ const columns = [ } }, { title: "GUID", key: "flowGuid", width: 200 }, { title: "发布时间", width: 160, render: (h: any, p: any) => h("span", (p.row.publishTime || "").replace("T", " ") || "-") }, - { title: "操作", width: 330, render: (h: any, p: any) => { + { title: "操作", width: 320, fixed: "right", render: (h: any, p: any) => { const btns: any[] = []; if (p.row.status === 0) { - btns.push(h("Button", { size: "small", type: "primary", onClick: () => onEdit(p.row) }, () => "编辑")); - btns.push(h("Button", { size: "small", type: "success", onClick: () => onPublish(p.row) }, () => "发布")); + btns.push(Utils.operatingTextHandle(h, "编辑", "", () => onEdit(p.row))); + btns.push(Utils.operatingTextHandle(h, "发布", "", () => onPublish(p.row))); } else if (p.row.status === 1) { - btns.push(h("Button", { size: "small", onClick: () => onNewVersion(p.row) }, () => "升版本")); - btns.push(h("Button", { size: "small", type: "warning", onClick: () => onOffline(p.row) }, () => "停用")); + btns.push(Utils.operatingTextHandle(h, "升版本", "", () => onNewVersion(p.row))); + btns.push(Utils.operatingTextHandle(h, "停用", "text-error", () => onOffline(p.row))); } - btns.push(h("Button", { size: "small", onClick: () => onDiagram(p.row) }, () => "图示")); - btns.push(h("Button", { size: "small", onClick: () => onVersions(p.row) }, () => "版本历史")); - return h("div", { style: "display:flex;gap:4px" }, btns); + btns.push(Utils.operatingTextHandle(h, "图示", "", () => onDiagram(p.row))); + btns.push(Utils.operatingTextHandle(h, "版本历史", "", () => onVersions(p.row))); + return btns; } }, ]; diff --git a/src/views/flow/admin/Instances.vue b/src/views/flow/admin/Instances.vue index 8ae5d73..d0fd063 100644 --- a/src/views/flow/admin/Instances.vue +++ b/src/views/flow/admin/Instances.vue @@ -3,6 +3,7 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { flowApi, INSTANCE_STATUS } from "@/api/module/flow"; +import Utils from "@/utils"; const router = useRouter(); const loading = ref(false); @@ -21,7 +22,7 @@ const columns = [ { title: "提交人", key: "submitterName", width: 100 }, { title: "提交时间", width: 160, render: (h: any, p: any) => h("span", (p.row.submitTime || "").replace("T", " ")) }, { title: "操作", width: 90, render: (h: any, p: any) => - h("Button", { size: "small", onClick: () => router.push(`/instance/${p.row.id}`) }, () => "详情") }, + Utils.operatingTextHandle(h, "详情", "", () => router.push(`/instance/${p.row.id}?pid=102`)) }, ]; async function load() { diff --git a/src/views/flow/admin/Maps.vue b/src/views/flow/admin/Maps.vue index 5baf987..9fb29b7 100644 --- a/src/views/flow/admin/Maps.vue +++ b/src/views/flow/admin/Maps.vue @@ -3,6 +3,7 @@ import { ref, onMounted } from "vue"; import { Message } from "view-ui-plus"; import { flowApi } from "@/api/module/flow"; +import Utils from "@/utils"; const loading = ref(false); const deptKeyFilter = ref(""); @@ -60,7 +61,7 @@ const columns = [ { title: "状态", width: 90, render: (h: any, p: any) => h("Tag", { color: p.row.status === 0 ? "green" : "gray" }, () => (p.row.status === 0 ? "启用" : "停用")) }, { title: "操作", width: 100, render: (h: any, p: any) => p.row.status === 0 - ? h("Button", { size: "small", type: "warning", onClick: () => onDisable(p.row) }, () => "停用") + ? Utils.operatingTextHandle(h, "停用", "text-error", () => onDisable(p.row)) : h("span", "-") }, ];