feat: 新增工作流模块与动态表单发布

- src/api/workflow: 工作流模型、实例、任务接口与类型定义
- src/views/workflow: 流程设计、发起、审批中心(待办/已办/我发起)页面与 BPMN 设计器、预览器
- src/views/dynamic-form/components/publish: 发布弹窗拆分为多步(入口配置/方式/结果),原 PublishDialog 大幅精简
- src/api/form: 新增 getWorkflowOptions,表单定义增加 category 区分普通/工作流表单
- src/api/system/role: 新增 getCodeOptions 供流程候选组选择
- src/stores/permission: 路由重载先摘旧路由再注册新路由,避免空窗触发 404
- vite.config: 预构建 bpmn-js,避免设计器首进重优化刷新
- src/views/login、.env.development、LayoutToolbar: 本地开发联调调整(昵称展示、本地接口)
- src/api/system/user: 重置密码改用 data 传参
- src/views/codegen: 支持 FormTypeEnum 日期字段范围查询
- package.json: 版本号 4.9.0;删除误存的 dev-output.txt
This commit is contained in:
Ray.Hao committed 2026-09-08 10:34:15 +08:00
1 parent 3503a399fb
commit 2ab0b31cfc
37 files changed
+5760 -1326

No files matched your search

@@ -0,0 +1,174 @@
<template>
<el-timeline v-if="stages.length > 0" class="approval-flow">
<template v-for="(stage, index) in stages" :key="stage.nodeId">
<!-- 环节的实际办理记录:驳回重办同一节点产生多条,全部按时间展示 -->
<el-timeline-item
v-for="record in recordsOf(stage.nodeId)"
:key="`${stage.nodeId}-${record.startTime ?? index}`"
:type="record.endTime ? 'success' : 'primary'"
:hollow="!record.endTime"
>
<div class="approval-flow__title">
<div class="approval-flow__name">
<span>{{ stage.nodeName }}</span>
<el-tag v-if="record.assignee" size="small" effect="plain">
{{ record.assignee }}
</el-tag>
</div>
<el-tag v-if="record.endTime" size="small" type="success" effect="light">已办理</el-tag>
<el-tag v-else size="small" type="warning" effect="light">进行中</el-tag>
</div>
<div class="approval-flow__time">
<template v-if="record.endTime">办理于 {{ record.endTime }}</template>
<template v-else>
{{
record.assignee ? `等待 ${record.assignee} 办理` : `待 ${describe(stage)} 认领办理`
}}
</template>
</div>
<div v-if="record.comment" class="approval-flow__comment">{{ record.comment }}</div>
</el-timeline-item>
<!-- 无办理记录:运行中按位置区分"未到"与"未经过"(网关分支跳过),已结束均为未经过;
两种情况均展示配置的办理人,便于了解该环节本应由谁处理 -->
<el-timeline-item v-if="!hasRecord(stage.nodeId)" type="info" hollow>
<div class="approval-flow__title" :class="{ 'is-skipped': !isUpcoming(index) }">
<span>{{ stage.nodeName }}</span>
<el-tag size="small" type="info" effect="plain">{{ pendingLabel(index) }}</el-tag>
</div>
<div
v-if="describe(stage)"
class="approval-flow__time"
:class="{ 'is-skipped': !isUpcoming(index) }"
>
{{ describe(stage) }}
</div>
</el-timeline-item>
</template>
</el-timeline>
<el-empty v-else description="暂无审批流程" :image-size="64" />
</template>
<script setup lang="ts">
import type { ApprovalHistoryItem, InstanceStatus, ProcessStageItem } from "@/api/workflow";
defineOptions({
name: "ApprovalFlowTimeline",
});
/**
* 审批流程时间线(流程走向 + 审批记录融合)
*
* @description 以设计环节为骨架按编排顺序展示,实际办理记录按 activityId 归位到环节:
* 已办(办理人/意见/时间)、进行中(待认领或等待办理)、未到(运行中位于当前环节之后)、
* 未经过(网关分支跳过或流程结束仍未到达),驳回重办的多轮记录全部保留
*/
const props = defineProps<{
/** 审批环节(按 BPMN 编排顺序,含分支未走节点) */
stages: ProcessStageItem[];
/** 审批记录(按时间正序) */
history: ApprovalHistoryItem[];
/** 当前进行中环节下标(运行中有效) */
active: number;
/** 实例状态:非运行中视为已结束,无记录环节均为"未经过" */
status: InstanceStatus;
}>();
/** 节点ID -> 办理记录(驳回重办为多条) */
const recordMap = computed(() => {
const map = new Map<string, ApprovalHistoryItem[]>();
props.history.forEach((item) => {
if (!item.activityId) return;
const list = map.get(item.activityId) ?? [];
list.push(item);
map.set(item.activityId, list);
});
return map;
});
function recordsOf(nodeId: string): ApprovalHistoryItem[] {
return recordMap.value.get(nodeId) ?? [];
}
function hasRecord(nodeId: string): boolean {
return recordsOf(nodeId).length > 0;
}
/** 是否"未到":流程运行中且位于当前环节之后 */
function isUpcoming(index: number): boolean {
return props.status === "running" && index >= props.active;
}
/** 无记录环节状态标签:未到(后续环节)/ 未经过(分支跳过或已结束) */
function pendingLabel(index: number): string {
return isUpcoming(index) ? "未到" : "未经过";
}
/** 环节办理人描述:角色(成员账号);发起人办理环节标注"发起人" */
function describe(stage: ProcessStageItem): string {
if (stage.initiator) {
return "发起人";
}
const roles = stage.roleNames.join("、");
const users = stage.userNames.join("、");
if (roles && users) {
return `${roles}(${users})`;
}
return roles || users || "未配置办理人";
}
</script>
<style lang="scss" scoped>
.approval-flow {
padding-left: 4px;
/* Element Plus 默认 wrapper 上移 3px(为单行纯文本设计),多行内容下
标签顶部与节点连线贴边,视觉上边框被裁切;归零后按内容自然排版 */
:deep(.el-timeline-item__wrapper) {
top: 0;
}
&__title {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 24px;
font-weight: 600;
color: var(--el-text-color-primary);
/* 未经过环节整体弱化,与已办/进行中形成层级差 */
&.is-skipped {
font-weight: 400;
color: var(--el-text-color-secondary);
}
}
&__name {
display: flex;
gap: 8px;
align-items: center;
min-width: 0;
}
&__time {
margin-top: 4px;
font-size: 12px;
line-height: 20px;
color: var(--el-text-color-secondary);
}
/* 审批意见引用块:左侧色条 + 浅底,与时间/标题形成视觉分层 */
&__comment {
padding: 6px 10px;
margin-top: 6px;
font-size: 13px;
line-height: 20px;
color: var(--el-text-color-regular);
word-break: break-all;
background: var(--el-fill-color-light);
border-left: 3px solid var(--el-color-primary-light-5);
border-radius: 4px;
}
}
</style>