feat(ui): 应用页规范全量重构 + 工作台与流程轨迹优化
build / build (push) Successful in 3m17s

应用页规范(bsch)重构:
- 6 个列表页迁规范骨架: page-wrapper/tabs-wrapper + search-wrap 搜索区(关键字 200px 即输即搜, 去查询按钮) + link-table table-bottom-box 定高表格 + page-panel 分页栏
- 分页列表统一接 TablePageAlovaNew(页长 [10,20,30,40]/customPageCount 记忆/增删智能翻页); dataApi 须返回 Method, 调 .send() 会导致表格静默空数据
- 操作列统一图标式 operatingHandle(Tooltip+fixed right 居中); 各列表补序号列; 危险操作统一 MyConfirm 二次确认
- 弹窗表单必填红星 + 统一提示「请填写红色星号内容!」
- TablePageAlovaNew 补页长记忆与重载后清空勾选; 新增 src/types OperatorEnum

工作台与流程轨迹优化:
- 已办列表去重: 每办件一行, doneCount>1 显示「共办理 N 次」标签
- 新增 TraceTimeline 轨迹时间流(时间升序/kind/轮次, 按人员/科室切换写回 URL), 接入详情/办理页
- 新增流程源码只读抽屉 SourceDrawer, 接入定义列表与图示页
- 办理人映射/流程定义新增「启用」回路(/admin/map/enable, /admin/def/enable)
- 图示视图口径统一 dept/user 两档, 缺省人员视图; 状态列 Tag 色映射统一导出
This commit is contained in:
chenxing
2026-09-18 16:52:40 +08:00
parent 0083246039
commit cdf04f0930
16 changed files with 1172 additions and 481 deletions
+94 -35
View File
@@ -1,10 +1,12 @@
<script setup lang="ts">
// 流程定义管理:列表 + 发布/停用/升版本/版本历史/图示。
// 新建与编辑走独立的全屏可视化编辑器(/admin/defs/editor,钉钉式卡片流,见《流程中心流程编辑器方案.md》);
// 本页不再直接编辑 JSON 文本(原 M1 textarea 已由编辑器取代,编辑器内置"源码"高级模式兜底)。
// 流程定义管理:列表 + 发布/启用/停用/升版本/版本历史/图示/源码。
// 新建与编辑走独立的全屏可视化编辑器(/admin/defs/editor,钉钉式卡片流,见《流程编辑器方案.md》);
// 本页不再直接编辑 JSON 文本(编辑器内置"源码"高级模式兜底);行内「源码」为只读查看+复制。
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm";
import SourceDrawer from "@/views/flow/components/SourceDrawer.vue";
import { flowApi } from "@/api/module/flow";
import Utils from "@/utils";
@@ -15,28 +17,40 @@ const loading = ref(false);
const rows = ref<any[]>([]);
const versionVisible = ref(false);
const versions = ref<any[]>([]);
// 源码抽屉(列表行已带回 contentJson,无需再请求)
const sourceVisible = ref(false);
const sourceText = ref("");
const sourceTitle = ref("");
// 操作按钮区 + 表格,无搜索、无分页
const tableHeight = ref(Utils.useTableHeight({ isOperat: true }));
const columns = [
{ title: "序号", type: "index", width: 70, align: "center" },
{ title: "流程", key: "name", minWidth: 180 },
{ title: "编码", key: "code", width: 160 },
{ title: "版本", key: "version", width: 70 },
{ title: "状态", width: 90, render: (h: any, p: any) => {
{ title: "版本", key: "version", width: 70, align: "center" },
{ title: "状态", width: 90, align: "center", render: (h: any, p: any) => {
const c = p.row.status === 1 ? "green" : p.row.status === 0 ? "orange" : "gray";
return h("Tag", { color: c }, () => DEF_STATUS[p.row.status] || p.row.status);
} },
{ title: "GUID", key: "flowGuid", width: 200 },
{ title: "发布时间", width: 160, render: (h: any, p: any) => h("span", (p.row.publishTime || "").replace("T", " ") || "-") },
{ title: "操作", width: 320, fixed: "right", render: (h: any, p: any) => {
{ title: "操作", width: 250, align: "center", fixed: "right", render: (h: any, p: any) => {
const btns: any[] = [];
if (p.row.status === 0) {
btns.push(Utils.operatingTextHandle(h, "编辑", "", () => onEdit(p.row)));
btns.push(Utils.operatingTextHandle(h, "发布", "", () => onPublish(p.row)));
btns.push(Utils.operatingHandle(h, "编辑", "icon-edit", () => onEdit(p.row)));
btns.push(Utils.operatingHandle(h, "发布", "icon-fasong", () => onPublish(p.row)));
} else if (p.row.status === 1) {
btns.push(Utils.operatingTextHandle(h, "升版本", "", () => onNewVersion(p.row)));
btns.push(Utils.operatingTextHandle(h, "停用", "text-error", () => onOffline(p.row)));
btns.push(Utils.operatingHandle(h, "升版本", "icon-tianjia", () => onNewVersion(p.row)));
btns.push(Utils.operatingHandle(h, "停用", "icon-stop", () => onOffline(p.row)));
} else if (p.row.status === 2) {
// 与「停用」对称的专用启用通道(后端 /def/enable:仅停用版→已发布,重跑发布校验;
// 草稿不走这里,草稿用「发布」)
btns.push(Utils.operatingHandle(h, "启用", "icon-play", () => onEnable(p.row)));
}
btns.push(Utils.operatingTextHandle(h, "图示", "", () => onDiagram(p.row)));
btns.push(Utils.operatingTextHandle(h, "版本历史", "", () => onVersions(p.row)));
btns.push(Utils.operatingHandle(h, "图示", "icon-tupian", () => onDiagram(p.row)));
btns.push(Utils.operatingHandle(h, "源码", "icon-liebiao", () => onSource(p.row)));
btns.push(Utils.operatingHandle(h, "版本历史", "icon-shijian", () => onVersions(p.row)));
return btns;
} },
];
@@ -76,10 +90,52 @@ async function onPublish(row: any) {
}
}
async function onOffline(row: any) {
await flowApi.defOffline(row.id).send();
Message.success("已停用");
load();
function onOffline(row: any) {
MyConfirm({
title: "停用",
msg: `确定停用流程「${row.name}」吗?`,
info: "停用后无法发起新办件,进行中的办件不受影响",
async onOk() {
await flowApi.defOffline(row.id).send();
Message.success("已停用");
load();
},
});
}
/** 启用停用版:专用 enable 通道(与停用对称;重跑静态校验后置回已发布) */
function onEnable(row: any) {
MyConfirm({
title: "启用",
msg: `确定启用流程「${row.name}」吗?`,
info: "启用将重新执行发布校验",
async onOk() {
try {
await flowApi.defEnable(row.id).send();
Message.success("已启用");
load();
} catch (e: any) {
Message.error(e?.message || "启用失败");
}
},
});
}
/** 源码:列表行已带回 contentJson(defList 返回全量实体),直接开抽屉,无需再拉接口 */
function onSource(row: any) {
sourceText.value = prettyJson(row.contentJson);
sourceTitle.value = `${row.name}(${row.code} v${row.version})`;
sourceVisible.value = true;
}
/** contentJson 字符串 → 缩进 JSON(解析失败则原样展示,不让展示把页面弄崩) */
function prettyJson(raw: any): string {
if (!raw) return "";
try {
return JSON.stringify(typeof raw === "string" ? JSON.parse(raw) : raw, null, 2);
} catch {
return String(raw);
}
}
async function onVersions(row: any) {
@@ -88,31 +144,34 @@ async function onVersions(row: any) {
}
function onDiagram(row: any) {
router.push({ path: "/diagram", query: { defId: row.id, mode: "auto" } });
// 不传 mode:后端缺省=人员视图(两档由图示页自身的 科室/人员 切换控制)
router.push({ path: "/diagram", query: { defId: row.id } });
}
onMounted(load);
</script>
<template>
<div class="page">
<div class="filter">
<Button type="primary" @click="onNew">新建流程</Button>
</div>
<Table :columns="columns" :data="rows" :loading="loading" stripe />
<div class="page-wrapper">
<div class="main-container">
<!-- 操作按钮区:主操作靠左(bsch应用页规范 §1) -->
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onNew">新建流程</Button>
</div>
<Modal v-model="versionVisible" title="版本历史" width="640">
<Table :columns="[
{ title: '版本', key: 'version', width: 80 },
{ title: '名称', key: 'name' },
{ title: '状态', width: 90, render: (h, p) => h('Tag', () => DEF_STATUS[p.row.status]) },
{ title: '发布时间', width: 160, render: (h, p) => (p.row.publishTime || '').replace('T', ' ') || '-' },
]" :data="versions" size="small" />
</Modal>
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="tableHeight" no-data-text="暂无数据" />
<Modal v-model="versionVisible" title="版本历史" width="640">
<Table :columns="[
{ title: '版本', key: 'version', width: 80 },
{ title: '名称', key: 'name' },
{ title: '状态', width: 90, render: (h, p) => h('Tag', () => DEF_STATUS[p.row.status]) },
{ title: '发布时间', width: 160, render: (h, p) => (p.row.publishTime || '').replace('T', ' ') || '-' },
]" :data="versions" size="small" no-data-text="暂无数据" />
</Modal>
<SourceDrawer v-model="sourceVisible" :text="sourceText" :subtitle="sourceTitle" />
</div>
</div>
</template>
<style scoped>
.page { padding: 12px 16px; }
.filter { margin-bottom: 10px; }
</style>