!735 2.4.2:工作流的更新

Merge pull request !735 from 芋道源码/feature/bpm
This commit is contained in:
芋道源码 2025-03-15 05:24:26 +00:00 committed by Gitee
commit d2b993311f
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
51 changed files with 2930 additions and 1494 deletions

View File

@ -117,37 +117,59 @@
### 工作流程
| | 功能 | 描述 |
|----|-------|-----------------------------------------|
| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
![功能图](/.image/common/bpm-feature.png)
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
| BPMN 设计器 | 钉钉/飞书设计器 |
|------------------------------|--------------------------------|
| ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) |
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
>
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
| 功能列表 | 功能描述 | 是否完成 |
|------------|-------------------------------------------------------------------------------------|------|
| SIMPLE 设计器 | 仿钉钉/飞书设计器支持拖拽搭建表单流程10 分钟快速完成审批流程配置 | ✅ |
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
| 转办 | A 转给其 B 审批B 审批后,进入下一节点 | ✅ |
| 委派 | A 转给其 B 审批B 审批后,转给 AA 继续审批后进入下一节点 | ✅ |
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
### 支付系统
| | 功能 | 描述 |
|-----|------|---------------------------|
| 🚀 | 商户信息 | 管理商户信息,支持 Saas 场景下的多商户功能 |
| 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 |
| 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 |
| 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 |
ps核心功能已经实现正在对接微信小程序中...
| 🚀 | 回调通知 | 查看支付回调业务的【支付】【退款】的通知结果 |
| 🚀 | 接入示例 | 提供接入支付系统的【支付】【退款】的功能实战 |
### 基础设施
| | 功能 | 描述 |
|----|----------|----------------------------------------------|
|-----|-----------|----------------------------------------------|
| 🚀 | 代码生成 | 前后端代码的生成Java、Vue、SQL、单元测试支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
@ -155,6 +177,7 @@ ps核心功能已经实现正在对接微信小程序中...
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3MinIO、阿里云、腾讯云、七牛云、本地、FTP、数据库等 |
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |

View File

@ -114,7 +114,8 @@ const include = [
'element-plus/es/components/segmented/style/css',
'@element-plus/icons-vue',
'element-plus/es/components/footer/style/css',
'element-plus/es/components/empty/style/css'
'element-plus/es/components/empty/style/css',
'element-plus/es/components/mention/style/css'
]
const exclude = ['@iconify/json']

View File

@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => {
params
})
}
export const getSimpleProcessDefinitionList = async () => {
return await request.get({
url: '/bpm/process-definition/simple-list'
})
}

View File

@ -90,7 +90,12 @@ export const getProcessInstanceCopyPage = async (params: any) => {
// 获取审批详情
export const getApprovalDetail = async (params: any) => {
return await request.get({ url: 'bpm/process-instance/get-approval-detail', params })
return await request.get({ url: '/bpm/process-instance/get-approval-detail', params })
}
// 获取下一个执行的流程节点
export const getNextApprovalNodes = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-next-approval-nodes', params })
}
// 获取表单字段权限

View File

@ -0,0 +1 @@
<svg t="1740116949537" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1153" width="200" height="200"><path d="M440.32 296.96h283.30496v145.92h66.56V230.4H440.32V17.92H17.92v424.96H440.32V296.96zM373.76 376.32H84.48v-291.84H373.76v291.84zM586.24 588.8v143.36512H298.66496V586.24h-66.56v212.48512H586.24V1013.76H1008.64v-424.96h-422.4z m355.84 358.4h-289.28v-291.84H942.08v291.84z" p-id="1154" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 465 B

View File

@ -0,0 +1 @@
<svg t="1739406626368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1300" width="200" height="200"><path d="M803.221 925.573H224.356c-68.568 0-124.352-55.784-124.352-124.353V222.356c0-68.568 55.784-124.352 124.352-124.352h355.311v64H224.356c-33.278 0-60.352 27.074-60.352 60.352V801.22c0 33.278 27.074 60.353 60.352 60.353H803.22c33.278 0 60.353-27.074 60.353-60.353V448.208h64V801.22c0 68.569-55.784 124.353-124.352 124.353z" fill="#ffffff" p-id="1301"></path><path d="M300.357 756.916l35.024-195.867L770.117 84.404c10.05-11.02 25.015-18.052 41.058-19.293 16.017-1.247 31.987 3.379 43.841 12.667l83.662 65.549c21.643 16.956 24.254 45.964 5.942 66.038l-437.613 479.8-206.65 67.751z m104.994-170.751l-13.14 73.487 69.671-22.842 415.465-455.517-59.909-46.939-412.087 451.811z" fill="#ffffff" p-id="1302"></path><path d="M732.25 220.897l41.144-49.023 81.151 68.11-41.145 49.023z" fill="#ffffff" p-id="1303"></path></svg>

After

Width:  |  Height:  |  Size: 964 B

View File

@ -15,6 +15,12 @@
</div>
<div class="handler-item-text">审批人</div>
</div>
<div class="handler-item" @click="addNode(NodeType.TRANSACTOR_NODE)">
<div class="transactor handler-item-icon">
<span class="iconfont icon-transactor icon-size"></span>
</div>
<div class="handler-item-text">办理人</div>
</div>
<div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
<div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span>
@ -57,6 +63,12 @@
</div>
<div class="handler-item-text">触发器</div>
</div>
<div class="handler-item" @click="addNode(NodeType.CHILD_PROCESS_NODE)">
<div class="handler-item-icon child-process">
<span class="iconfont icon-size icon-child-process"></span>
</div>
<div class="handler-item-text">子流程</div>
</div>
</div>
<template #reference>
<div class="add-icon"><Icon icon="ep:plus" /></div>
@ -114,13 +126,13 @@ const addNode = (type: number) => {
}
popoverShow.value = false
if (type === NodeType.USER_TASK_NODE) {
if (type === NodeType.USER_TASK_NODE || type === NodeType.TRANSACTOR_NODE) {
const id = 'Activity_' + generateUUID()
const data: SimpleFlowNode = {
id: id,
name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string,
name: NODE_DEFAULT_NAME.get(type) as string,
showText: '',
type: NodeType.USER_TASK_NODE,
type: type,
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
//
rejectHandler: {
@ -277,6 +289,31 @@ const addNode = (type: number) => {
}
emits('update:childNode', data)
}
if (type === NodeType.CHILD_PROCESS_NODE) {
const data: SimpleFlowNode = {
id: 'Activity_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.CHILD_PROCESS_NODE) as string,
showText: '',
type: NodeType.CHILD_PROCESS_NODE,
childNode: props.childNode,
childProcessSetting: {
calledProcessDefinitionKey: '',
calledProcessDefinitionName: '',
async: false,
skipStartUserNode: false,
startUserSetting: {
type: 1
},
timeoutSetting: {
enable: false
},
multiInstanceSetting: {
enable: false
}
}
}
emits('update:childNode', data)
}
}
</script>

View File

@ -6,7 +6,11 @@
/>
<!-- 审批节点 -->
<UserTaskNode
v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE"
v-if="
currentNode &&
(currentNode.type === NodeType.USER_TASK_NODE ||
currentNode.type === NodeType.TRANSACTOR_NODE)
"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
@find:parent-node="findFromParentNode"
@ -56,6 +60,12 @@
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 子流程节点 -->
<ChildProcessNode
v-if="currentNode && currentNode.type === NodeType.CHILD_PROCESS_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 递归显示孩子节点 -->
<ProcessNodeTree
v-if="currentNode && currentNode.childNode"
@ -81,6 +91,7 @@ import InclusiveNode from './nodes/InclusiveNode.vue'
import DelayTimerNode from './nodes/DelayTimerNode.vue'
import RouterNode from './nodes/RouterNode.vue'
import TriggerNode from './nodes/TriggerNode.vue'
import ChildProcessNode from './nodes/ChildProcessNode.vue'
import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node'
defineOptions({

View File

@ -1,6 +1,6 @@
<template>
<div class="simple-process-model-container position-relative">
<div class="position-absolute top-0px right-0px bg-#fff">
<div class="position-absolute top-0px right-0px bg-#fff z-index-button-group">
<el-row type="flex" justify="end">
<el-button-group key="scale-control" size="default">
<el-button v-if="!readonly" size="default" @click="exportJson">
@ -23,10 +23,19 @@
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
<el-button size="default" @click="resetPosition">重置</el-button>
</el-button-group>
</el-row>
</div>
<div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
<div
class="simple-process-model"
:style="`transform: translate(${currentX}px, ${currentY}px) scale(${scaleValue / 100});`"
@mousedown="startDrag"
@mousemove="onDrag"
@mouseup="stopDrag"
@mouseleave="stopDrag"
@mouseenter="setGrabCursor"
>
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
</div>
</div>
@ -76,11 +85,51 @@ const emits = defineEmits<{
const processNodeTree = useWatchNode(props)
provide('readonly', props.readonly)
// TODO
/** 拖拽、放大缩小等操作 */
let scaleValue = ref(100)
const MAX_SCALE_VALUE = 200
const MIN_SCALE_VALUE = 50
const isDragging = ref(false)
const startX = ref(0)
const startY = ref(0)
const currentX = ref(0)
const currentY = ref(0)
const initialX = ref(0)
const initialY = ref(0)
const setGrabCursor = () => {
document.body.style.cursor = 'grab'
}
const resetCursor = () => {
document.body.style.cursor = 'default'
}
const startDrag = (e: MouseEvent) => {
isDragging.value = true
startX.value = e.clientX - currentX.value
startY.value = e.clientY - currentY.value
setGrabCursor() //
}
const onDrag = (e: MouseEvent) => {
if (!isDragging.value) return
e.preventDefault() //
// 使 requestAnimationFrame
requestAnimationFrame(() => {
currentX.value = e.clientX - startX.value
currentY.value = e.clientY - startY.value
})
}
const stopDrag = () => {
isDragging.value = false
resetCursor() //
}
//
const zoomIn = () => {
if (scaleValue.value == MAX_SCALE_VALUE) {
return
@ -88,7 +137,6 @@ const zoomIn = () => {
scaleValue.value += 10
}
//
const zoomOut = () => {
if (scaleValue.value == MIN_SCALE_VALUE) {
return
@ -100,20 +148,15 @@ const processReZoom = () => {
scaleValue.value = 100
}
const resetPosition = () => {
currentX.value = initialX.value
currentY.value = initialY.value
}
/** 校验节点设置 */
const errorDialogVisible = ref(false)
let errorNodes: SimpleFlowNode[] = []
const saveSimpleFlowModel = async () => {
errorNodes = []
validateNode(processNodeTree.value, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
emits('save', processNodeTree.value)
}
// showText
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
if (node) {
const { type, showText, conditionNodes } = node
@ -193,6 +236,30 @@ const importLocalFile = () => {
}
}
}
//
onMounted(() => {
initialX.value = currentX.value
initialY.value = currentY.value
})
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.simple-process-model-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
user-select: none; //
}
.simple-process-model {
position: relative; //
min-width: 100%; // 100%
min-height: 100%; // 100%
}
.z-index-button-group {
z-index: 10;
}
</style>

View File

@ -45,4 +45,3 @@ watch(
provide('tasks', approveTasks)
provide('processInstance', currentProcessInstance)
</script>
p

View File

@ -23,6 +23,11 @@ export enum NodeType {
*/
COPY_TASK_NODE = 12,
/**
*
*/
TRANSACTOR_NODE = 13,
/**
*
*/
@ -33,6 +38,11 @@ export enum NodeType {
*/
TRIGGER_NODE = 15,
/**
*
*/
CHILD_PROCESS_NODE = 20,
/**
*
*/
@ -123,6 +133,8 @@ export interface SimpleFlowNode {
reasonRequire?: boolean
// 触发器设置
triggerSetting?: TriggerSetting
// 子流程
childProcessSetting?: ChildProcessSetting
}
// 候选人策略枚举 用于审批节点。抄送节点 )
export enum CandidateStrategy {
@ -150,6 +162,10 @@ export enum CandidateStrategy {
*
*/
USER = 30,
/**
*
*/
APPROVE_USER_SELECT = 34,
/**
*
*/
@ -506,6 +522,8 @@ NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器')
NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点')
NODE_DEFAULT_TEXT.set(NodeType.TRIGGER_NODE, '请设置触发器')
NODE_DEFAULT_TEXT.set(NodeType.TRANSACTOR_NODE, '请设置办理人')
NODE_DEFAULT_TEXT.set(NodeType.CHILD_PROCESS_NODE, '请设置子流程')
export const NODE_DEFAULT_NAME = new Map<number, string>()
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
@ -515,15 +533,20 @@ NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器')
NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支')
NODE_DEFAULT_NAME.set(NodeType.TRIGGER_NODE, '触发器')
NODE_DEFAULT_NAME.set(NodeType.TRANSACTOR_NODE, '办理人')
NODE_DEFAULT_NAME.set(NodeType.CHILD_PROCESS_NODE, '子流程')
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
export const CANDIDATE_STRATEGY: DictDataVO[] = [
{ label: '指定成员', value: CandidateStrategy.USER },
{ label: '指定角色', value: CandidateStrategy.ROLE },
{ label: '指定岗位', value: CandidateStrategy.POST },
{ label: '部门成员', value: CandidateStrategy.DEPT_MEMBER },
{ label: '部门负责人', value: CandidateStrategy.DEPT_LEADER },
{ label: '连续多级部门负责人', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER },
{ label: '指定岗位', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER },
{ label: '发起人自选', value: CandidateStrategy.START_USER_SELECT },
{ label: '审批人自选', value: CandidateStrategy.APPROVE_USER_SELECT },
{ label: '发起人本人', value: CandidateStrategy.START_USER },
{ label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
{ label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
@ -627,6 +650,16 @@ export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
{ id: OperationButtonType.RETURN, displayName: '退回', enable: true }
]
// 办理人默认的按钮权限设置
export const TRANSACTOR_DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
{ id: OperationButtonType.APPROVE, displayName: '办理', enable: true },
{ id: OperationButtonType.REJECT, displayName: '拒绝', enable: false },
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
{ id: OperationButtonType.RETURN, displayName: '退回', enable: false }
]
// 发起人的按钮权限。暂时定死,不可以编辑
export const START_USER_BUTTON_SETTING: ButtonSetting[] = [
{ id: OperationButtonType.APPROVE, displayName: '提交', enable: true },
@ -717,7 +750,7 @@ export type RouterSetting = {
export type TriggerSetting = {
type: TriggerTypeEnum
httpRequestSetting?: HttpRequestTriggerSetting
normalFormSetting?: NormalFormTriggerSetting
formSettings?: FormTriggerSetting[]
}
/**
@ -729,9 +762,17 @@ export enum TriggerTypeEnum {
*/
HTTP_REQUEST = 1,
/**
*
* HTTP
*/
UPDATE_NORMAL_FORM = 2 // TODO @jasonFORM_UPDATE
HTTP_CALLBACK = 2,
/**
*
*/
FORM_UPDATE = 10,
/**
*
*/
FORM_DELETE = 11
}
/**
@ -751,12 +792,110 @@ export type HttpRequestTriggerSetting = {
/**
*
*/
export type NormalFormTriggerSetting = {
// 更新表单字段
export type FormTriggerSetting = {
// 条件类型
conditionType?: ConditionType
// 条件表达式
conditionExpression?: string
// 条件组
conditionGroups?: ConditionGroup
// 更新表单字段配置
updateFormFields?: Record<string, any>
// 删除表单字段配置
deleteFields?: string[]
}
export const TRIGGER_TYPES: DictDataVO[] = [
{ label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST },
{ label: '修改表单数据', value: TriggerTypeEnum.UPDATE_NORMAL_FORM }
{ label: '发送 HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST },
{ label: '接收 HTTP 回调', value: TriggerTypeEnum.HTTP_CALLBACK },
{ label: '修改表单数据', value: TriggerTypeEnum.FORM_UPDATE },
{ label: '删除表单数据', value: TriggerTypeEnum.FORM_DELETE }
]
/**
*
*/
export type ChildProcessSetting = {
calledProcessDefinitionKey: string
calledProcessDefinitionName: string
async: boolean
inVariables?: IOParameter[]
outVariables?: IOParameter[]
skipStartUserNode: boolean
startUserSetting: StartUserSetting
timeoutSetting: TimeoutSetting
multiInstanceSetting: MultiInstanceSetting
}
export type IOParameter = {
source: string
target: string
}
export type StartUserSetting = {
type: ChildProcessStartUserTypeEnum
formField?: string
emptyType?: ChildProcessStartUserEmptyTypeEnum
}
export type TimeoutSetting = {
enable: boolean
type?: DelayTypeEnum
timeExpression?: string
}
export type MultiInstanceSetting = {
enable: boolean
sequential?: boolean
approveRatio?: number
sourceType?: ChildProcessMultiInstanceSourceTypeEnum
source?: string
}
export enum ChildProcessStartUserTypeEnum {
/**
*
*/
MAIN_PROCESS_START_USER = 1,
/**
*
*/
FROM_FORM = 2
}
export const CHILD_PROCESS_START_USER_TYPE = [
{ label: '同主流程发起人', value: ChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER },
{ label: '表单', value: ChildProcessStartUserTypeEnum.FROM_FORM }
]
export enum ChildProcessStartUserEmptyTypeEnum {
/**
*
*/
MAIN_PROCESS_START_USER = 1,
/**
*
*/
CHILD_PROCESS_ADMIN = 2,
/**
*
*/
MAIN_PROCESS_ADMIN = 3
}
export const CHILD_PROCESS_START_USER_EMPTY_TYPE = [
{ label: '同主流程发起人', value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER },
{ label: '子流程管理员', value: ChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN },
{ label: '主流程管理员', value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN }
]
export enum ChildProcessMultiInstanceSourceTypeEnum {
/**
*
*/
FIXED_QUANTITY = 1,
/**
*
*/
NUMBER_FORM = 2,
/**
*
*/
MULTIPLE_FORM = 3
}
export const CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = [
{ label: '固定数量', value: ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY },
{ label: '数字表单', value: ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM },
{ label: '多选表单', value: ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM }
]

View File

@ -15,7 +15,10 @@ import {
AssignEmptyHandlerType,
FieldPermissionType,
HttpRequestParam,
ProcessVariableEnum
ProcessVariableEnum,
ConditionType,
ConditionGroup,
COMPARISON_OPERATORS
} from './consts'
import { parseFormFields } from '@/components/FormCreate/src/utils'
@ -201,7 +204,7 @@ export function useNodeForm(nodeType: NodeType) {
const deptTreeOptions = inject('deptTree', ref()) // 部门树
const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
if (nodeType === NodeType.USER_TASK_NODE) {
if (nodeType === NodeType.USER_TASK_NODE || nodeType === NodeType.TRANSACTOR_NODE) {
configForm.value = {
candidateStrategy: CandidateStrategy.USER,
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
@ -307,6 +310,11 @@ export function useNodeForm(nodeType: NodeType) {
showText = `表单内部门负责人`
}
// 审批人自选
if (configForm.value?.candidateStrategy === CandidateStrategy.APPROVE_USER_SELECT) {
showText = `审批人自选`
}
// 发起人自选
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
showText = `发起人自选`
@ -543,6 +551,66 @@ export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): stri
if (taskStatus === TaskStatusEnum.CANCEL) {
return 'status-cancel'
}
return ''
}
/** 条件组件文字展示 */
export function getConditionShowText(
conditionType: ConditionType | undefined,
conditionExpression: string | undefined,
conditionGroups: ConditionGroup | undefined,
fieldOptions: Array<Record<string, any>>
) {
let showText = ''
if (conditionType === ConditionType.EXPRESSION) {
if (conditionExpression) {
showText = `表达式:${conditionExpression}`
}
}
if (conditionType === ConditionType.RULE) {
// 条件组是否为与关系
const groupAnd = conditionGroups?.and
let warningMessage: undefined | string = undefined
const conditionGroup = conditionGroups?.conditions.map((item) => {
return (
'(' +
item.rules
.map((rule) => {
if (rule.leftSide && rule.rightSide) {
return (
getFormFieldTitle(fieldOptions, rule.leftSide) +
' ' +
getOpName(rule.opCode) +
' ' +
rule.rightSide
)
} else {
// 有一条规则不完善。提示错误
warningMessage = '请完善条件规则'
return ''
}
})
.join(item.and ? ' 且 ' : ' 或 ') +
' ) '
)
})
if (warningMessage) {
showText = ''
} else {
showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ')
}
}
return showText
}
/** 获取表单字段名称*/
const getFormFieldTitle = (fieldOptions: Array<Record<string, any>>, field: string) => {
const item = fieldOptions.find((item) => item.field === field)
return item?.title
}
/** 获取操作符名称 */
const getOpName = (opCode: string): string => {
const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
return opName?.label
}

View File

@ -0,0 +1,610 @@
<template>
<el-drawer
:append-to-body="true"
v-model="settingVisible"
:show-close="false"
:size="550"
:before-close="saveConfig"
>
<template #header>
<div class="config-header">
<input
v-if="showInput"
type="text"
class="config-editable-input"
@blur="blurEvent()"
v-mountedFocus
v-model="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name">
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
</div>
<div class="divide-line"></div>
</div>
</template>
<el-tabs type="border-card" v-model="activeTabName">
<el-tab-pane label="子流程" name="child">
<div>
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
<el-form-item label="是否异步" prop="async">
<el-switch v-model="configForm.async" active-text="异步" inactive-text="不异步" />
</el-form-item>
<el-form-item label="选择子流程" prop="calledProcessDefinitionKey">
<el-select
v-model="configForm.calledProcessDefinitionKey"
clearable
@change="handleCalledElementChange"
>
<el-option
v-for="(item, index) in childProcessOptions"
:key="index"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-form-item>
<el-form-item label="是否自动跳过子流程发起节点" prop="skipStartUserNode">
<el-switch
v-model="configForm.skipStartUserNode"
active-text="跳过"
inactive-text="不跳过"
/>
</el-form-item>
<el-form-item label="主→子变量传递" prop="inVariables">
<div class="flex pt-2" v-for="(item, index) in configForm.inVariables" :key="index">
<div class="mr-2">
<el-form-item
:prop="`inVariables.${index}.source`"
:rules="{
required: true,
message: '变量不能为空',
trigger: 'blur'
}"
>
<el-select class="w-200px!" v-model="item.source">
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-form-item
:prop="`inVariables.${index}.target`"
:rules="{
required: true,
message: '变量不能为空',
trigger: 'blur'
}"
>
<el-select class="w-200px!" v-model="item.target">
<el-option
v-for="(field, fIdx) in childFormFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="deleteVariable(index, configForm.inVariables)"
/>
</div>
</div>
<el-button type="primary" text @click="addVariable(configForm.inVariables)">
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item
v-if="configForm.async === false"
label="子→主变量传递"
prop="outVariables"
>
<div class="flex pt-2" v-for="(item, index) in configForm.outVariables" :key="index">
<div class="mr-2">
<el-form-item
:prop="`outVariables.${index}.source`"
:rules="{
required: true,
message: '变量不能为空',
trigger: 'blur'
}"
>
<el-select class="w-200px!" v-model="item.source">
<el-option
v-for="(field, fIdx) in childFormFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-form-item
:prop="`outVariables.${index}.target`"
:rules="{
required: true,
message: '变量不能为空',
trigger: 'blur'
}"
>
<el-select class="w-200px!" v-model="item.target">
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="deleteVariable(index, configForm.outVariables)"
/>
</div>
</div>
<el-button type="primary" text @click="addVariable(configForm.outVariables)">
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item label="子流程发起人" prop="startUserType">
<el-radio-group v-model="configForm.startUserType">
<el-radio
v-for="item in CHILD_PROCESS_START_USER_TYPE"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="configForm.startUserType === ChildProcessStartUserTypeEnum.FROM_FORM"
label="当子流程发起人为空时"
prop="startUserType"
>
<el-radio-group v-model="configForm.startUserEmptyType">
<el-radio
v-for="item in CHILD_PROCESS_START_USER_EMPTY_TYPE"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="configForm.startUserType === 2"
label="发起人表单"
prop="startUserFormField"
>
<el-select class="w-200px!" v-model="configForm.startUserFormField">
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
/>
</el-select>
</el-form-item>
<el-divider content-position="left">超时设置</el-divider>
<el-form-item label="启用开关" prop="timeoutEnable">
<el-switch
v-model="configForm.timeoutEnable"
active-text="开启"
inactive-text="关闭"
/>
</el-form-item>
<div v-if="configForm.timeoutEnable">
<el-form-item prop="timeoutType">
<el-radio-group v-model="configForm.timeoutType">
<el-radio-button
v-for="item in DELAY_TYPE"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-radio-group>
</el-form-item>
<el-form-item v-if="configForm.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION">
<el-form-item prop="timeDuration">
<el-input-number
class="mr-2"
:style="{ width: '100px' }"
v-model="configForm.timeDuration"
:min="1"
controls-position="right"
/>
</el-form-item>
<el-select v-model="configForm.timeUnit" class="mr-2" :style="{ width: '100px' }">
<el-option
v-for="item in TIME_UNIT_TYPES"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-text>后进入下一节点</el-text>
</el-form-item>
<el-form-item
v-if="configForm.timeoutType === DelayTypeEnum.FIXED_DATE_TIME"
prop="dateTime"
>
<el-date-picker
class="mr-2"
v-model="configForm.dateTime"
type="datetime"
placeholder="请选择日期和时间"
value-format="YYYY-MM-DDTHH:mm:ss"
/>
<el-text>后进入下一节点</el-text>
</el-form-item>
</div>
<el-divider content-position="left">多实例设置</el-divider>
<el-form-item label="启用开关" prop="multiInstanceEnable">
<el-switch
v-model="configForm.multiInstanceEnable"
active-text="开启"
inactive-text="关闭"
/>
</el-form-item>
<div v-if="configForm.multiInstanceEnable">
<el-form-item prop="sequential">
<el-switch
v-model="configForm.sequential"
active-text="串行"
inactive-text="并行"
/>
</el-form-item>
<el-form-item prop="approveRatio">
<el-text>完成比例(%)</el-text>
<el-input-number
class="ml-10px"
v-model="configForm.approveRatio"
:min="10"
:max="100"
:step="10"
/>
</el-form-item>
<el-form-item prop="multiInstanceSourceType">
<el-text>多实例来源</el-text>
<el-select
class="ml-10px w-200px!"
v-model="configForm.multiInstanceSourceType"
@change="handleMultiInstanceSourceTypeChange"
>
<el-option
v-for="item in CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY">
<el-input-number v-model="configForm.multiInstanceSource" :min="1" />
</el-form-item>
<el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM">
<el-select class="w-200px!" v-model="configForm.multiInstanceSource">
<el-option
v-for="(field, fIdx) in digitalFormFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
/>
</el-select>
</el-form-item>
<el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM">
<el-select class="w-200px!" v-model="configForm.multiInstanceSource">
<el-option
v-for="(field, fIdx) in multiFormFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
/>
</el-select>
</el-form-item>
</div>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-divider />
<div>
<el-button type="primary" @click="saveConfig"> </el-button>
<el-button @click="closeDrawer"> </el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { getModelList } from '@/api/bpm/model'
import { getForm } from '@/api/bpm/form'
import {
SimpleFlowNode,
NodeType,
TIME_UNIT_TYPES,
TimeUnitType,
DelayTypeEnum,
DELAY_TYPE,
IOParameter,
ChildProcessStartUserTypeEnum,
CHILD_PROCESS_START_USER_TYPE,
ChildProcessStartUserEmptyTypeEnum,
CHILD_PROCESS_START_USER_EMPTY_TYPE,
CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE,
ChildProcessMultiInstanceSourceTypeEnum
} from '../consts'
import { useWatchNode, useDrawer, useNodeName, useFormFieldsAndStartUser } from '../node'
import { parseFormFields } from '@/components/FormCreate/src/utils'
import { convertTimeUnit } from '../utils'
defineOptions({
name: 'ChildProcessNodeConfig'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
//
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
//
const currentNode = useWatchNode(props)
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.CHILD_PROCESS_NODE)
// Tab
const activeTabName = ref('child')
//
const formRef = ref() // Ref
//
const formRules = reactive({
async: [{ required: true, message: '是否异步不能为空', trigger: 'change' }],
calledProcessDefinitionKey: [{ required: true, message: '子流程不能为空', trigger: 'change' }],
skipStartUserNode: [
{ required: true, message: '是否自动跳过子流程发起节点不能为空', trigger: 'change' }
],
startUserType: [{ required: true, message: '子流程发起人不能为空', trigger: 'change' }],
startUserEmptyType: [
{ required: true, message: '当子流程发起人为空时不能为空', trigger: 'change' }
],
startUserFormField: [{ required: true, message: '发起人表单不能为空', trigger: 'change' }],
timeoutEnable: [{ required: true, message: '超时设置是否开启不能为空', trigger: 'change' }],
timeoutType: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
timeDuration: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
dateTime: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
multiInstanceEnable: [{ required: true, message: '多实例设置不能为空', trigger: 'change' }]
})
type ChildProcessFormType = {
async: boolean
calledProcessDefinitionKey: string
skipStartUserNode: boolean
inVariables?: IOParameter[]
outVariables?: IOParameter[]
startUserType: ChildProcessStartUserTypeEnum
startUserEmptyType: ChildProcessStartUserEmptyTypeEnum
startUserFormField: string
timeoutEnable: boolean
timeoutType: DelayTypeEnum
timeDuration: number
timeUnit: TimeUnitType
dateTime: string
multiInstanceEnable: boolean
sequential: boolean
approveRatio: number
multiInstanceSourceType: ChildProcessMultiInstanceSourceTypeEnum
multiInstanceSource: string
}
const configForm = ref<ChildProcessFormType>({
async: false,
calledProcessDefinitionKey: '',
skipStartUserNode: false,
inVariables: [],
outVariables: [],
startUserType: ChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER,
startUserEmptyType: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER,
startUserFormField: '',
timeoutEnable: false,
timeoutType: DelayTypeEnum.FIXED_TIME_DURATION,
timeDuration: 1,
timeUnit: TimeUnitType.HOUR,
dateTime: '',
multiInstanceEnable: false,
sequential: false,
approveRatio: 100,
multiInstanceSourceType: ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY,
multiInstanceSource: ''
})
const childProcessOptions = ref()
const formFieldOptions = useFormFieldsAndStartUser()
const digitalFormFieldOptions = computed(() => {
return formFieldOptions.filter((item) => item.type === 'inputNumber')
})
const multiFormFieldOptions = computed(() => {
return formFieldOptions.filter((item) => item.type === 'select' || item.type === 'checkbox')
})
const childFormFieldOptions = ref()
//
const saveConfig = async () => {
activeTabName.value = 'child'
if (!formRef) return false
const valid = await formRef.value.validate()
if (!valid) return false
const childInfo = childProcessOptions.value.find(
(option: any) => option.key === configForm.value.calledProcessDefinitionKey
)
currentNode.value.name = nodeName.value!
if (currentNode.value.childProcessSetting) {
// 1.
currentNode.value.childProcessSetting.async = configForm.value.async
// 2.
currentNode.value.childProcessSetting.calledProcessDefinitionKey = childInfo.key
currentNode.value.childProcessSetting.calledProcessDefinitionName = childInfo.name
// 3.
currentNode.value.childProcessSetting.skipStartUserNode = configForm.value.skipStartUserNode
// 4. ->
currentNode.value.childProcessSetting.inVariables = configForm.value.inVariables
// 5. ->
currentNode.value.childProcessSetting.outVariables = configForm.value.outVariables
// 6.
currentNode.value.childProcessSetting.startUserSetting.type = configForm.value.startUserType
currentNode.value.childProcessSetting.startUserSetting.emptyType =
configForm.value.startUserEmptyType
currentNode.value.childProcessSetting.startUserSetting.formField =
configForm.value.startUserFormField
// 7.
currentNode.value.childProcessSetting.timeoutSetting = {
enable: configForm.value.timeoutEnable
}
if (configForm.value.timeoutEnable) {
currentNode.value.childProcessSetting.timeoutSetting.type = configForm.value.timeoutType
if (configForm.value.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION) {
currentNode.value.childProcessSetting.timeoutSetting.timeExpression = getIsoTimeDuration()
}
if (configForm.value.timeoutType === DelayTypeEnum.FIXED_DATE_TIME) {
currentNode.value.childProcessSetting.timeoutSetting.timeExpression =
configForm.value.dateTime
}
}
// 8.
currentNode.value.childProcessSetting.multiInstanceSetting = {
enable: configForm.value.multiInstanceEnable
}
if (configForm.value.multiInstanceEnable) {
currentNode.value.childProcessSetting.multiInstanceSetting.sequential =
configForm.value.sequential
currentNode.value.childProcessSetting.multiInstanceSetting.approveRatio =
configForm.value.approveRatio
currentNode.value.childProcessSetting.multiInstanceSetting.sourceType =
configForm.value.multiInstanceSourceType
currentNode.value.childProcessSetting.multiInstanceSetting.source =
configForm.value.multiInstanceSource
}
}
currentNode.value.showText = `调用子流程:${childInfo.name}`
settingVisible.value = false
return true
}
//
const showChildProcessNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name
if (node.childProcessSetting) {
// 1.
configForm.value.async = node.childProcessSetting.async
// 2.
configForm.value.calledProcessDefinitionKey =
node.childProcessSetting?.calledProcessDefinitionKey
// 3.
configForm.value.skipStartUserNode = node.childProcessSetting.skipStartUserNode
// 4. ->
configForm.value.inVariables = node.childProcessSetting.inVariables
// 5. ->
configForm.value.outVariables = node.childProcessSetting.outVariables
// 6.
configForm.value.startUserType = node.childProcessSetting.startUserSetting.type
configForm.value.startUserEmptyType = node.childProcessSetting.startUserSetting.emptyType ?? ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER
configForm.value.startUserFormField = node.childProcessSetting.startUserSetting.formField ?? ''
// 7.
configForm.value.timeoutEnable = node.childProcessSetting.timeoutSetting.enable ?? false
if (configForm.value.timeoutEnable) {
configForm.value.timeoutType =
node.childProcessSetting.timeoutSetting.type ?? DelayTypeEnum.FIXED_TIME_DURATION
//
if (configForm.value.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION) {
const strTimeDuration = node.childProcessSetting.timeoutSetting.timeExpression ?? ''
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
configForm.value.timeDuration = parseInt(parseTime)
configForm.value.timeUnit = convertTimeUnit(parseTimeUnit)
}
//
if (configForm.value.timeoutType === DelayTypeEnum.FIXED_DATE_TIME) {
configForm.value.dateTime = node.childProcessSetting.timeoutSetting.timeExpression ?? ''
}
}
// 8.
configForm.value.multiInstanceEnable =
node.childProcessSetting.multiInstanceSetting.enable ?? false
if (configForm.value.multiInstanceEnable) {
configForm.value.sequential =
node.childProcessSetting.multiInstanceSetting.sequential ?? false
configForm.value.approveRatio =
node.childProcessSetting.multiInstanceSetting.approveRatio ?? 100
configForm.value.multiInstanceSourceType =
node.childProcessSetting.multiInstanceSetting.sourceType ??
ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY
configForm.value.multiInstanceSource =
node.childProcessSetting.multiInstanceSetting.source ?? ''
}
}
loadFormInfo()
}
defineExpose({ openDrawer, showChildProcessNodeConfig }) //
const addVariable = (arr?: IOParameter[]) => {
arr?.push({
source: '',
target: ''
})
}
const deleteVariable = (index: number, arr?: IOParameter[]) => {
arr?.splice(index, 1)
}
const handleCalledElementChange = () => {
configForm.value.inVariables = []
configForm.value.outVariables = []
loadFormInfo()
}
const loadFormInfo = async () => {
const childInfo = childProcessOptions.value.find(
(option) => option.key === configForm.value.calledProcessDefinitionKey
)
const formInfo = await getForm(childInfo.formId)
childFormFieldOptions.value = []
if (formInfo.fields) {
formInfo.fields.forEach((fieldStr: string) => {
parseFormFields(JSON.parse(fieldStr), childFormFieldOptions.value)
})
}
}
const getIsoTimeDuration = () => {
let strTimeDuration = 'PT'
if (configForm.value.timeUnit === TimeUnitType.MINUTE) {
strTimeDuration += configForm.value.timeDuration + 'M'
}
if (configForm.value.timeUnit === TimeUnitType.HOUR) {
strTimeDuration += configForm.value.timeDuration + 'H'
}
if (configForm.value.timeUnit === TimeUnitType.DAY) {
strTimeDuration += configForm.value.timeDuration + 'D'
}
return strTimeDuration
}
const handleMultiInstanceSourceTypeChange = () => {
configForm.value.multiInstanceSource = ''
}
onMounted(async () => {
childProcessOptions.value = await getModelList(undefined)
})
</script>
<style lang="scss" scoped></style>

View File

@ -43,15 +43,12 @@
</el-drawer>
</template>
<script setup lang="ts">
import {
SimpleFlowNode,
ConditionType,
COMPARISON_OPERATORS,
} from '../consts'
import { SimpleFlowNode, ConditionType } from '../consts'
import { getDefaultConditionNodeName } from '../utils'
import { useFormFieldsAndStartUser } from '../node'
import { useFormFieldsAndStartUser, getConditionShowText } from '../node'
import Condition from './components/Condition.vue'
const message = useMessage() //
import { cloneDeep } from 'lodash-es'
defineOptions({
name: 'ConditionNodeConfig'
})
@ -67,9 +64,51 @@ const props = defineProps({
})
const settingVisible = ref(false)
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
const condition = ref<any>()
const condition = ref<any>({
conditionType: ConditionType.RULE, //
conditionExpression: '',
conditionGroups: {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
}
})
const open = () => {
condition.value = currentNode.value.conditionSetting
// 使使
if (currentNode.value.conditionSetting) {
condition.value = cloneDeep(currentNode.value.conditionSetting)
} else {
//
condition.value = {
conditionType: ConditionType.RULE,
conditionExpression: '',
conditionGroups: {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
}
}
}
settingVisible.value = true
}
@ -93,8 +132,6 @@ const blurEvent = () => {
getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow)
}
defineExpose({ open }) // open
//
@ -111,84 +148,41 @@ const handleClose = async (done: (cancel?: boolean) => void) => {
}
}
/** 保存配置 */
const fieldOptions = useFormFieldsAndStartUser() //
const conditionRef = ref()
//
const saveConfig = async () => {
if (!currentNode.value.conditionSetting?.defaultFlow) {
//
const valid = await conditionRef.value.validate()
if (!valid) return false
const showText = getShowText()
const showText = getConditionShowText(
condition.value?.conditionType,
condition.value?.conditionExpression,
condition.value.conditionGroups,
fieldOptions
)
if (!showText) {
return false
}
currentNode.value.showText = showText
currentNode.value.conditionSetting!.conditionType = condition.value?.conditionType
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) {
currentNode.value.conditionSetting.conditionGroups = undefined
currentNode.value.conditionSetting.conditionExpression = condition.value?.conditionExpression
}
if (currentNode.value.conditionSetting!.conditionType === ConditionType.RULE) {
currentNode.value.conditionSetting!.conditionExpression = undefined
currentNode.value.conditionSetting!.conditionGroups = condition.value?.conditionGroups
}
// 使 cloneDeep
currentNode.value.conditionSetting = cloneDeep({
...currentNode.value.conditionSetting,
conditionType: condition.value?.conditionType,
conditionExpression:
condition.value?.conditionType === ConditionType.EXPRESSION
? condition.value?.conditionExpression
: undefined,
conditionGroups:
condition.value?.conditionType === ConditionType.RULE
? condition.value?.conditionGroups
: undefined
})
}
settingVisible.value = false
return true
}
const getShowText = (): string => {
let showText = ''
if (condition.value?.conditionType === ConditionType.EXPRESSION) {
if (condition.value.conditionExpression) {
showText = `表达式:${condition.value.conditionExpression}`
}
}
if (condition.value?.conditionType === ConditionType.RULE) {
//
const groupAnd = condition.value.conditionGroups?.and
let warningMesg: undefined | string = undefined
const conditionGroup = condition.value.conditionGroups?.conditions.map((item) => {
return (
'(' +
item.rules
.map((rule) => {
if (rule.leftSide && rule.rightSide) {
return (
getFieldTitle(rule.leftSide) + ' ' + getOpName(rule.opCode) + ' ' + rule.rightSide
)
} else {
//
warningMesg = '请完善条件规则'
return ''
}
})
.join(item.and ? ' 且 ' : ' 或 ') +
' ) '
)
})
if (warningMesg) {
message.warning(warningMesg)
showText = ''
} else {
showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ')
}
}
return showText
}
//
const fieldOptions = useFormFieldsAndStartUser()
/** 获取字段名称 */
const getFieldTitle = (field: string) => {
const item = fieldOptions.find((item) => item.field === field)
return item?.title
}
/** 获取操作符名称 */
const getOpName = (opCode: string): string => {
const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
return opName?.label
}
</script>
<style lang="scss" scoped>

View File

@ -195,9 +195,15 @@
<div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles">
<span class="setting-title-label">只读</span>
<span class="setting-title-label">可编辑</span>
<span class="setting-title-label">隐藏</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
只读
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
可编辑
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
隐藏
</span>
</div>
</div>
<div
@ -368,6 +374,18 @@ const showCopyTaskNodeConfig = (node: SimpleFlowNode) => {
getNodeConfigFormFields(node.fieldsPermission)
}
/** 批量更新权限 */
const updatePermission = (type: string) => {
fieldsPermissionConfig.value.forEach((field) => {
field.permission =
type === 'READ'
? FieldPermissionType.READ
: type === 'WRITE'
? FieldPermissionType.WRITE
: FieldPermissionType.NONE
})
}
defineExpose({ openDrawer, showCopyTaskNodeConfig }) //
</script>

View File

@ -36,7 +36,8 @@
placement="top"
:content="getUserNicknames(startUserIds)"
>
{{ getUserNicknames(startUserIds.slice(0,2)) }} {{ startUserIds.length }} 人可发起流程
{{ getUserNicknames(startUserIds.slice(0, 2)) }}
{{ startUserIds.length }} 人可发起流程
</el-tooltip>
</el-text>
</el-tab-pane>
@ -46,9 +47,15 @@
<div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles">
<span class="setting-title-label">只读</span>
<span class="setting-title-label">可编辑</span>
<span class="setting-title-label">隐藏</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
只读
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
可编辑
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
隐藏
</span>
</div>
</div>
<div
@ -157,6 +164,17 @@ const showStartUserNodeConfig = (node: SimpleFlowNode) => {
getNodeConfigFormFields(node.fieldsPermission)
}
/** 批量更新权限 */
const updatePermission = (type: string) => {
fieldsPermissionConfig.value.forEach((field) => {
field.permission =
type === 'READ'
? FieldPermissionType.READ
: type === 'WRITE'
? FieldPermissionType.WRITE
: FieldPermissionType.NONE
})
}
defineExpose({ openDrawer, showStartUserNodeConfig }) //
</script>

View File

@ -3,7 +3,7 @@
:append-to-body="true"
v-model="settingVisible"
:show-close="false"
:size="550"
:size="630"
:before-close="saveConfig"
>
<template #header>
@ -26,7 +26,7 @@
<div>
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
<el-form-item label="触发器类型" prop="type">
<el-select v-model="configForm.type">
<el-select v-model="configForm.type" @change="changeTriggerType">
<el-option
v-for="(item, index) in TRIGGER_TYPES"
:key="index"
@ -36,100 +36,68 @@
</el-select>
</el-form-item>
<!-- HTTP 请求触发器 -->
<div
v-if="configForm.type === TriggerTypeEnum.HTTP_REQUEST && configForm.httpRequestSetting"
>
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<!-- 请求地址-->
<el-form-item label="请求地址" prop="httpRequestSetting.url">
<el-input v-model="configForm.httpRequestSetting.url" />
</el-form-item>
<!-- 请求头请求体设置-->
<HttpRequestParamSetting
:header="configForm.httpRequestSetting.header"
:body="configForm.httpRequestSetting.body"
:bind="'httpRequestSetting'"
/>
<!-- 返回值设置-->
<el-form-item label="返回值">
<el-alert
title="通过请求返回值, 可以修改流程表单的值"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item>
<div
class="flex pt-2"
v-for="(item, index) in configForm.httpRequestSetting.response"
:key="index"
>
<div class="mr-2">
<el-form-item
:prop="`httpRequestSetting.response.${index}.key`"
:rules="{
required: true,
message: '表单字段不能为空',
trigger: 'blur'
}"
>
<el-select class="w-160px!" v-model="item.key" placeholder="请选择表单字段">
<el-option
v-for="(field, fIdx) in formFields"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-form-item
:prop="`httpRequestSetting.response.${index}.value`"
:rules="{
required: true,
message: '请求返回字段不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.value" placeholder="请求返回字段" />
</el-form-item>
</div>
<div class="mr-1 pt-1 cursor-pointer">
<Icon
icon="ep:delete"
:size="18"
@click="deleteHttpResponseSetting(configForm.httpRequestSetting.response!, index)"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addHttpResponseSetting(configForm.httpRequestSetting.response!)"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</div>
<div
v-if="
configForm.type === TriggerTypeEnum.UPDATE_NORMAL_FORM && configForm.normalFormSetting
[TriggerTypeEnum.HTTP_REQUEST, TriggerTypeEnum.HTTP_CALLBACK].includes(
configForm.type
) && configForm.httpRequestSetting
"
>
<el-divider content-position="left">修改表单设置</el-divider>
<HttpRequestSetting
v-model:setting="configForm.httpRequestSetting"
:responseEnable="configForm.type === TriggerTypeEnum.HTTP_REQUEST"
:formItemPrefix="'httpRequestSetting'"
/>
</div>
<!-- 表单数据修改触发器 -->
<div v-if="configForm.type === TriggerTypeEnum.FORM_UPDATE">
<div v-for="(formSetting, index) in configForm.formSettings" :key="index">
<el-card class="w-580px mt-4">
<template #header>
<div class="flex items-center justify-between">
<div>修改表单设置 {{ index + 1 }}</div>
<el-button
type="primary"
plain
circle
v-if="configForm.formSettings!.length > 1"
@click="deleteFormSetting(index)"
>
<Icon icon="ep:close" />
</el-button>
</div>
</template>
<!-- 条件设置 -->
<ConditionDialog
:ref="`condition-${index}`"
@update-condition="(val) => handleConditionUpdate(index, val)"
/>
<div class="cursor-pointer" v-if="formSetting.conditionType">
<el-tag
type="success"
effect="light"
closable
@close="deleteFormSettingCondition(formSetting)"
@click="openFormSettingCondition(index, formSetting)"
>
{{ showConditionText(formSetting) }}
</el-tag>
</div>
<el-button
v-else
type="primary"
text
@click="addFormSettingCondition(index, formSetting)"
>
<Icon icon="ep:link" class="mr-5px" />添加条件
</el-button>
<el-divider content-position="left">修改表单字段设置</el-divider>
<!-- 表单字段修改设置 -->
<div
class="flex items-center"
v-for="key in Object.keys(configForm.normalFormSetting.updateFormFields!)"
v-for="key in Object.keys(formSetting.updateFormFields || {})"
:key="key"
>
<div class="mr-2 flex items-center">
@ -137,7 +105,7 @@
<el-select
class="w-160px!"
:model-value="key"
@update:model-value="(newKey) => updateFormFieldKey(key, newKey)"
@update:model-value="(newKey) => updateFormFieldKey(formSetting, key, newKey)"
placeholder="请选择表单字段"
:disabled="key !== ''"
>
@ -154,7 +122,7 @@
<div class="mx-2"><el-form-item>的值设置为</el-form-item></div>
<div class="mr-2">
<el-form-item
:prop="`normalFormSetting.updateFormFields.${key}`"
:prop="`formSettings.${index}.updateFormFields.${key}`"
:rules="{
required: true,
message: '值不能为空',
@ -163,7 +131,7 @@
>
<el-input
class="w-160px"
v-model="configForm.normalFormSetting.updateFormFields![key]"
v-model="formSetting.updateFormFields![key]"
placeholder="请输入"
:disabled="!key"
/>
@ -171,12 +139,95 @@
</div>
<div class="mr-1 pt-1 cursor-pointer">
<el-form-item>
<Icon icon="ep:delete" :size="18" @click="deleteFormFieldSetting(key)" />
<Icon
icon="ep:delete"
:size="18"
@click="deleteFormFieldSetting(formSetting, key)"
/>
</el-form-item>
</div>
</div>
<el-button type="primary" text @click="addFormFieldSetting()">
<Icon icon="ep:plus" class="mr-5px" />添加修改字段
<!-- 添加表单字段按钮 -->
<el-button type="primary" text @click="addFormFieldSetting(formSetting)">
<Icon icon="ep:memo" class="mr-5px" />添加修改字段
</el-button>
</el-card>
</div>
<!-- 添加新的设置 -->
<el-button class="mt-6" type="primary" text @click="addFormSetting">
<Icon icon="ep:setting" class="mr-5px" />添加设置
</el-button>
</div>
<!-- 表单数据删除触发器 -->
<div v-if="configForm.type === TriggerTypeEnum.FORM_DELETE">
<div v-for="(formSetting, index) in configForm.formSettings" :key="index">
<el-card class="w-580px mt-4">
<template #header>
<div class="flex items-center justify-between">
<div>删除表单设置 {{ index + 1 }}</div>
<el-button
type="primary"
plain
circle
v-if="configForm.formSettings!.length > 1"
@click="deleteFormSetting(index)"
>
<Icon icon="ep:close" />
</el-button>
</div>
</template>
<!-- 条件设置 -->
<ConditionDialog
:ref="`condition-${index}`"
@update-condition="(val) => handleConditionUpdate(index, val)"
/>
<div class="cursor-pointer" v-if="formSetting.conditionType">
<el-tag
type="warning"
effect="light"
closable
@close="deleteFormSettingCondition(formSetting)"
@click="openFormSettingCondition(index, formSetting)"
>
{{ showConditionText(formSetting) }}
</el-tag>
</div>
<el-button
v-else
type="primary"
text
@click="addFormSettingCondition(index, formSetting)"
>
<Icon icon="ep:link" class="mr-5px" />添加条件
</el-button>
<el-divider content-position="left">删除表单字段设置</el-divider>
<!-- 表单字段删除设置 -->
<div class="flex flex-wrap gap-2">
<el-select
v-model="formSetting.deleteFields"
multiple
placeholder="请选择要删除的字段"
class="w-full"
>
<el-option
v-for="field in formFields"
:key="field.field"
:label="field.title"
:value="field.field"
/>
</el-select>
</div>
</el-card>
</div>
<!-- 添加新的设置 -->
<el-button class="mt-6" type="primary" text @click="addFormSetting">
<Icon icon="ep:setting" class="mr-5px" />添加设置
</el-button>
</div>
</el-form>
@ -191,9 +242,19 @@
</el-drawer>
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, TriggerSetting, TRIGGER_TYPES, TriggerTypeEnum } from '../consts'
import { useWatchNode, useDrawer, useNodeName, useFormFields } from '../node'
import HttpRequestParamSetting from './components/HttpRequestParamSetting.vue'
import {
SimpleFlowNode,
NodeType,
TriggerSetting,
TRIGGER_TYPES,
TriggerTypeEnum,
FormTriggerSetting,
DEFAULT_CONDITION_GROUP_VALUE
} from '../consts'
import { useWatchNode, useDrawer, useNodeName, useFormFields, getConditionShowText } from '../node'
import HttpRequestSetting from './components/HttpRequestSetting.vue'
import ConditionDialog from './components/ConditionDialog.vue'
const { proxy } = getCurrentInstance() as any
defineOptions({
name: 'TriggerNodeConfig'
@ -227,52 +288,153 @@ const configForm = ref<TriggerSetting>({
body: [],
response: []
},
normalFormSetting: { updateFormFields: {} }
formSettings: [
{
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
updateFormFields: {},
deleteFields: []
}
]
})
//
const formFields = useFormFields()
//
const optionalUpdateFormFields = computed(() => {
const usedFields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
return formFields.map((field) => ({
title: field.title,
field: field.field,
disabled: usedFields.includes(field.field)
disabled: false
}))
})
const updateFormFieldKey = (oldKey: string, newKey: string) => {
if (!configForm.value.normalFormSetting?.updateFormFields) return
const value = configForm.value.normalFormSetting.updateFormFields[oldKey]
delete configForm.value.normalFormSetting.updateFormFields[oldKey]
configForm.value.normalFormSetting.updateFormFields[newKey] = value
let originalSetting: TriggerSetting | undefined
/** 触发器类型改变了 */
const changeTriggerType = () => {
if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
configForm.value.httpRequestSetting =
originalSetting?.type === TriggerTypeEnum.HTTP_REQUEST && originalSetting.httpRequestSetting
? originalSetting.httpRequestSetting
: {
url: '',
header: [],
body: [],
response: []
}
configForm.value.formSettings = undefined
return
}
/** 添加 HTTP 请求返回值设置项*/
const addHttpResponseSetting = (responseSetting: Record<string, string>[]) => {
responseSetting.push({
key: '',
value: ''
if (configForm.value.type === TriggerTypeEnum.HTTP_CALLBACK) {
configForm.value.httpRequestSetting =
originalSetting?.type === TriggerTypeEnum.HTTP_CALLBACK && originalSetting.httpRequestSetting
? originalSetting.httpRequestSetting
: {
url: '',
header: [],
body: [],
response: []
}
configForm.value.formSettings = undefined
return
}
if (configForm.value.type === TriggerTypeEnum.FORM_UPDATE) {
configForm.value.formSettings =
originalSetting?.type === TriggerTypeEnum.FORM_UPDATE && originalSetting.formSettings
? originalSetting.formSettings
: [
{
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
updateFormFields: {},
deleteFields: []
}
]
configForm.value.httpRequestSetting = undefined
return
}
if (configForm.value.type === TriggerTypeEnum.FORM_DELETE) {
configForm.value.formSettings =
originalSetting?.type === TriggerTypeEnum.FORM_DELETE && originalSetting.formSettings
? originalSetting.formSettings
: [
{
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
updateFormFields: undefined,
deleteFields: []
}
]
configForm.value.httpRequestSetting = undefined
return
}
}
/** 添加新的修改表单设置 */
const addFormSetting = () => {
configForm.value.formSettings!.push({
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
updateFormFields: {},
deleteFields: []
})
}
/** 删除 HTTP 请求返回值设置项 */
const deleteHttpResponseSetting = (responseSetting: Record<string, string>[], index: number) => {
responseSetting.splice(index, 1)
/** 删除修改表单设置 */
const deleteFormSetting = (index: number) => {
configForm.value.formSettings!.splice(index, 1)
}
/** 添加修改表单设置项 */
const addFormFieldSetting = () => {
if (configForm.value.normalFormSetting!.updateFormFields === undefined) {
configForm.value.normalFormSetting!.updateFormFields = {}
/** 添加条件配置 */
const addFormSettingCondition = (index: number, formSetting: FormTriggerSetting) => {
const conditionDialog = proxy.$refs[`condition-${index}`][0]
conditionDialog.open(formSetting)
}
configForm.value.normalFormSetting!.updateFormFields[''] = undefined
/** 删除条件配置 */
const deleteFormSettingCondition = (formSetting: FormTriggerSetting) => {
formSetting.conditionType = undefined
}
/** 删除修改表单设置项 */
const deleteFormFieldSetting = (key: string) => {
if (!configForm.value.normalFormSetting?.updateFormFields) return
delete configForm.value.normalFormSetting.updateFormFields[key]
/** 打开条件配置弹窗 */
const openFormSettingCondition = (index: number, formSetting: FormTriggerSetting) => {
const conditionDialog = proxy.$refs[`condition-${index}`][0]
conditionDialog.open(formSetting)
}
/** 处理条件配置保存 */
const handleConditionUpdate = (index: number, condition: any) => {
configForm.value.formSettings![index].conditionType = condition.conditionType
configForm.value.formSettings![index].conditionExpression = condition.conditionExpression
configForm.value.formSettings![index].conditionGroups = condition.conditionGroups
}
/** 条件配置展示 */
const showConditionText = (formSetting: FormTriggerSetting) => {
return getConditionShowText(
formSetting.conditionType,
formSetting.conditionExpression,
formSetting.conditionGroups,
formFields
)
}
/** 添加修改字段设置项 */
const addFormFieldSetting = (formSetting: FormTriggerSetting) => {
if (!formSetting) return
if (!formSetting.updateFormFields) {
formSetting.updateFormFields = {}
}
formSetting.updateFormFields[''] = undefined
}
/** 更新字段 KEY */
const updateFormFieldKey = (formSetting: FormTriggerSetting, oldKey: string, newKey: string) => {
if (!formSetting?.updateFormFields) return
const value = formSetting.updateFormFields[oldKey]
delete formSetting.updateFormFields[oldKey]
formSetting.updateFormFields[newKey] = value
}
/** 删除修改字段设置项 */
const deleteFormFieldSetting = (formSetting: FormTriggerSetting, key: string) => {
if (!formSetting?.updateFormFields) return
delete formSetting.updateFormFields[key]
}
/** 保存配置 */
@ -285,10 +447,19 @@ const saveConfig = async () => {
currentNode.value.name = nodeName.value!
currentNode.value.showText = showText
if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
configForm.value.normalFormSetting = undefined
}
if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
configForm.value.formSettings = undefined
} else if (configForm.value.type === TriggerTypeEnum.FORM_UPDATE) {
configForm.value.httpRequestSetting = undefined
//
configForm.value.formSettings?.forEach((setting) => {
setting.deleteFields = undefined
})
} else if (configForm.value.type === TriggerTypeEnum.FORM_DELETE) {
configForm.value.httpRequestSetting = undefined
//
configForm.value.formSettings?.forEach((setting) => {
setting.updateFormFields = undefined
})
}
currentNode.value.triggerSetting = configForm.value
settingVisible.value = false
@ -298,22 +469,35 @@ const saveConfig = async () => {
/** 获取节点展示内容 */
const getShowText = (): string => {
let showText = ''
if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
if (
configForm.value.type === TriggerTypeEnum.HTTP_REQUEST ||
configForm.value.type === TriggerTypeEnum.HTTP_CALLBACK
) {
showText = `${configForm.value.httpRequestSetting?.url}`
} else if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
const updatefields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
if (updatefields.length === 0) {
message.warning('请设置修改表单字段')
} else {
showText = '修改表单数据'
} else if (configForm.value.type === TriggerTypeEnum.FORM_UPDATE) {
for (const [index, setting] of configForm.value.formSettings!.entries()) {
if (!setting.updateFormFields || Object.keys(setting.updateFormFields).length === 0) {
message.warning(`请添加表单设置${index + 1}的修改字段`)
return ''
}
}
showText = '修改表单数据'
} else if (configForm.value.type === TriggerTypeEnum.FORM_DELETE) {
for (const [index, setting] of configForm.value.formSettings!.entries()) {
if (!setting.deleteFields || setting.deleteFields.length === 0) {
message.warning(`请选择表单设置${index + 1}要删除的字段`)
return ''
}
}
showText = '删除表单数据'
}
return showText
}
/** 显示触发器节点配置, 由父组件传过来 */
const showTriggerNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name
originalSetting = node.triggerSetting ? JSON.parse(JSON.stringify(node.triggerSetting)) : {}
if (node.triggerSetting) {
configForm.value = {
type: node.triggerSetting.type,
@ -323,7 +507,13 @@ const showTriggerNodeConfig = (node: SimpleFlowNode) => {
body: [],
response: []
},
normalFormSetting: node.triggerSetting.normalFormSetting || { updateFormFields: {} }
formSettings: node.triggerSetting.formSettings || [
{
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
updateFormFields: {},
deleteFields: []
}
]
}
}
}

View File

@ -25,7 +25,7 @@
<div class="divide-line"></div>
</div>
</template>
<div class="flex flex-items-center mb-3">
<div v-if="currentNode.type === NodeType.USER_TASK_NODE" class="flex flex-items-center mb-3">
<span class="font-size-16px mr-3">审批类型 :</span>
<el-radio-group v-model="approveType">
<el-radio
@ -39,10 +39,10 @@
</el-radio-group>
</div>
<el-tabs type="border-card" v-model="activeTabName" v-if="approveType === ApproveType.USER">
<el-tab-pane label="审批人" name="user">
<el-tab-pane :label="`${nodeTypeName}人`" name="user">
<div>
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
<el-form-item label="审批人设置" prop="candidateStrategy">
<el-form-item :label="`${nodeTypeName}人设置`" prop="candidateStrategy">
<el-radio-group
v-model="configForm.candidateStrategy"
@change="changeCandidateStrategy"
@ -61,7 +61,13 @@
label="指定角色"
prop="roleIds"
>
<el-select filterable v-model="configForm.roleIds" clearable multiple style="width: 100%">
<el-select
filterable
v-model="configForm.roleIds"
clearable
multiple
style="width: 100%"
>
<el-option
v-for="item in roleOptions"
:key="item.id"
@ -99,7 +105,13 @@
prop="postIds"
span="24"
>
<el-select filterable v-model="configForm.postIds" clearable multiple style="width: 100%">
<el-select
filterable
v-model="configForm.postIds"
clearable
multiple
style="width: 100%"
>
<el-option
v-for="item in postOptions"
:key="item.id"
@ -114,7 +126,13 @@
prop="userIds"
span="24"
>
<el-select filterable v-model="configForm.userIds" clearable multiple style="width: 100%">
<el-select
filterable
v-model="configForm.userIds"
clearable
multiple
style="width: 100%"
>
<el-option
v-for="item in userOptions"
:key="item.id"
@ -128,7 +146,13 @@
label="指定用户组"
prop="userGroups"
>
<el-select filterable v-model="configForm.userGroups" clearable multiple style="width: 100%">
<el-select
filterable
v-model="configForm.userGroups"
clearable
multiple
style="width: 100%"
>
<el-option
v-for="item in userGroupOptions"
:key="item.id"
@ -201,7 +225,7 @@
style="width: 100%"
/>
</el-form-item>
<el-form-item label="多人审批方式" prop="approveMethod">
<el-form-item :label="`多人${nodeTypeName}方式`" prop="approveMethod">
<el-radio-group v-model="configForm.approveMethod" @change="approveMethodChanged">
<div class="flex-col">
<div
@ -230,6 +254,7 @@
</el-radio-group>
</el-form-item>
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
<el-divider content-position="left">审批人拒绝时</el-divider>
<el-form-item prop="rejectHandlerType">
<el-radio-group v-model="configForm.rejectHandlerType">
@ -245,7 +270,12 @@
label="驳回节点"
prop="returnNodeId"
>
<el-select filterable v-model="configForm.returnNodeId" clearable style="width: 100%">
<el-select
filterable
v-model="configForm.returnNodeId"
clearable
style="width: 100%"
>
<el-option
v-for="item in returnTaskList"
:key="item.id"
@ -254,7 +284,9 @@
/>
</el-select>
</el-form-item>
</div>
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
<el-divider content-position="left">审批人超时未处理时</el-divider>
<el-form-item label="启用开关" prop="timeoutHandlerEnable">
<el-switch
@ -315,8 +347,9 @@
>
<el-input-number v-model="configForm.maxRemindCount" :min="1" :max="10" />
</el-form-item>
</div>
<el-divider content-position="left">审批人为空时</el-divider>
<el-divider content-position="left">{{ nodeTypeName }}人为空时</el-divider>
<el-form-item prop="assignEmptyHandlerType">
<el-radio-group v-model="configForm.assignEmptyHandlerType">
<div class="flex-col">
@ -348,6 +381,7 @@
</el-select>
</el-form-item>
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
<el-divider content-position="left">审批人与提交人为同一人时</el-divider>
<el-form-item prop="assignStartUserHandlerType">
<el-radio-group v-model="configForm.assignStartUserHandlerType">
@ -358,20 +392,33 @@
</div>
</el-radio-group>
</el-form-item>
</div>
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
<el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable">
<el-switch v-model="configForm.signEnable" active-text="是" inactive-text="否" />
</el-form-item>
</div>
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
<el-divider content-position="left">审批意见</el-divider>
<el-form-item prop="reasonRequire">
<el-switch v-model="configForm.reasonRequire" active-text="必填" inactive-text="非必填" />
<el-switch
v-model="configForm.reasonRequire"
active-text="必填"
inactive-text="非必填"
/>
</el-form-item>
</div>
</el-form>
</div>
</el-tab-pane>
<el-tab-pane label="操作按钮设置" name="buttons">
<el-tab-pane
label="操作按钮设置"
v-if="currentNode.type === NodeType.USER_TASK_NODE"
name="buttons"
>
<div class="button-setting-pane">
<div class="button-setting-desc">操作按钮</div>
<div class="button-setting-title">
@ -407,9 +454,15 @@
<div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles">
<span class="setting-title-label">只读</span>
<span class="setting-title-label">可编辑</span>
<span class="setting-title-label">隐藏</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
只读
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
可编辑
</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
隐藏
</span>
</div>
</div>
<div
@ -448,7 +501,11 @@
</div>
</el-tab-pane>
<el-tab-pane label="监听器" name="listener">
<UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
<UserTaskListener
ref="userTaskListenerRef"
v-model="configForm"
:form-field-options="formFieldOptions"
/>
</el-tab-pane>
</el-tabs>
<template #footer>
@ -485,7 +542,8 @@ import {
ASSIGN_EMPTY_HANDLER_TYPES,
AssignEmptyHandlerType,
FieldPermissionType,
ProcessVariableEnum
ProcessVariableEnum,
TRANSACTOR_DEFAULT_BUTTON_SETTING
} from '../consts'
import {
@ -588,7 +646,7 @@ const {
handleCandidateParam,
parseCandidateParam,
getShowText
} = useNodeForm(NodeType.USER_TASK_NODE)
} = useNodeForm(currentNode.value.type)
const configForm = tempConfigForm as Ref<UserTaskFormType>
//
@ -627,7 +685,12 @@ const {
const userTaskListenerRef = ref()
//
/** 节点类型名称 */
const nodeTypeName = computed(() => {
return currentNode.value.type === NodeType.TRANSACTOR_NODE ? '办理' : '审批'
})
/** 保存配置 */
const saveConfig = async () => {
// activeTabName.value = 'user'
//
@ -713,7 +776,7 @@ const saveConfig = async () => {
return true
}
//
/** 显示审批节点配置, 由父组件传过来 */
const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name
// 1
@ -733,13 +796,13 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
configForm.value.approveRatio = node.approveRatio!
}
// 2.3
configForm.value.rejectHandlerType = node.rejectHandler!.type
configForm.value.rejectHandlerType = node.rejectHandler?.type
configForm.value.returnNodeId = node.rejectHandler?.returnNodeId
const matchNodeList = []
emits('find:returnTaskNodes', matchNodeList)
returnTaskList.value = matchNodeList
// 2.4
configForm.value.timeoutHandlerEnable = node.timeoutHandler!.enable
configForm.value.timeoutHandlerEnable = node.timeoutHandler?.enable
if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) {
const strTimeDuration = node.timeoutHandler.timeDuration
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
@ -755,7 +818,11 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
// 2.6
configForm.value.assignStartUserHandlerType = node.assignStartUserHandlerType
// 3.
buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
buttonsSetting.value =
cloneDeep(node.buttonsSetting) ||
(node.type === NodeType.TRANSACTOR_NODE
? TRANSACTOR_DEFAULT_BUTTON_SETTING
: DEFAULT_BUTTON_SETTING)
// 4.
getNodeConfigFormFields(node.fieldsPermission)
// 5.
@ -788,9 +855,7 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
defineExpose({ openDrawer, showUserTaskNodeConfig }) //
/**
* @description 操作按钮设置
*/
/** 操作按钮设置 */
function useButtonsSetting() {
const buttonsSetting = ref<ButtonSetting[]>()
//
@ -811,9 +876,7 @@ function useButtonsSetting() {
}
}
/**
* @description 审批人超时未处理配置
*/
/** 审批人超时未处理配置 */
function useTimeoutHandler() {
//
const timeUnit = ref(TimeUnitType.HOUR)
@ -896,6 +959,18 @@ function useTimeoutHandler() {
cTimeoutMaxRemindCount
}
}
/** 批量更新权限 */
const updatePermission = (type: string) => {
fieldsPermissionConfig.value.forEach((field) => {
field.permission =
type === 'READ'
? FieldPermissionType.READ
: type === 'WRITE'
? FieldPermissionType.WRITE
: FieldPermissionType.NONE
})
}
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,308 @@
<!-- TODO @jason有可能它里面套 Condition -->
<!-- TODO 怕影响其它节点功能后面看看如何如何复用 Condtion -->
<template>
<Dialog v-model="dialogVisible" title="条件配置" width="600px" :fullscreen="false">
<div class="h-410px">
<el-scrollbar wrap-class="h-full">
<el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
<el-form-item label="配置方式" prop="conditionType">
<el-radio-group v-model="condition.conditionType" @change="changeConditionType">
<el-radio
v-for="(dict, indexConditionType) in conditionConfigTypes"
:key="indexConditionType"
:value="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.RULE && condition.conditionGroups"
label="条件规则"
>
<div class="condition-group-tool">
<div class="flex items-center">
<div class="mr-4">条件组关系</div>
<el-switch
v-model="condition.conditionGroups.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
<el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'">
<el-card
class="condition-group"
style="width: 530px"
v-for="(equation, cIdx) in condition.conditionGroups.conditions"
:key="cIdx"
>
<div
class="condition-group-delete"
v-if="condition.conditionGroups.conditions.length > 1"
>
<Icon
color="#0089ff"
icon="ep:circle-close-filled"
:size="18"
@click="deleteConditionGroup(condition.conditionGroups.conditions, cIdx)"
/>
</div>
<template #header>
<div class="flex items-center justify-between">
<div>条件组</div>
<div class="flex">
<div class="mr-4">规则关系</div>
<el-switch
v-model="equation.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
</template>
<div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
<div class="mr-2">
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
:rules="{
required: true,
message: '左值不能为空',
trigger: 'change'
}"
>
<el-select style="width: 160px" v-model="rule.leftSide">
<el-option
v-for="(field, fIdx) in fieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px">
<el-option
v-for="operator in COMPARISON_OPERATORS"
:key="operator.value"
:label="operator.label"
:value="operator.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.rightSide`"
:rules="{
required: true,
message: '右值不能为空',
trigger: 'blur'
}"
>
<el-input v-model="rule.rightSide" style="width: 160px" />
</el-form-item>
</div>
<div
class="cursor-pointer mr-1 flex items-center"
v-if="equation.rules.length > 1"
>
<Icon
icon="ep:delete"
:size="18"
@click="deleteConditionRule(equation, rIdx)"
/>
</div>
<div class="cursor-pointer flex items-center">
<Icon icon="ep:plus" :size="18" @click="addConditionRule(equation, rIdx)" />
</div>
</div>
</el-card>
</el-space>
<div title="添加条件组" class="mt-4 cursor-pointer">
<Icon
color="#0089ff"
icon="ep:plus"
:size="24"
@click="addConditionGroup(condition.conditionGroups?.conditions)"
/>
</div>
</el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
</el-form>
</el-scrollbar>
</div>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
COMPARISON_OPERATORS,
CONDITION_CONFIG_TYPES,
ConditionType,
ConditionGroup,
DEFAULT_CONDITION_GROUP_VALUE
} from '../../consts'
import { BpmModelFormType } from '@/utils/constants'
import { useFormFieldsAndStartUser } from '../../node'
defineOptions({
name: 'ConditionDialog'
})
const condition = ref<{
conditionType: ConditionType
conditionExpression?: string
conditionGroups?: ConditionGroup
}>({
conditionType: ConditionType.RULE,
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
})
const emit = defineEmits<{
updateCondition: [condition: object]
}>()
const message = useMessage() //
const dialogVisible = ref(false) //
const formType = inject<Ref<number>>('formType') //
const conditionConfigTypes = computed(() => {
return CONDITION_CONFIG_TYPES.filter((item) => {
//
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
return false
} else {
return true
}
})
})
/** 条件规则可选择的表单字段 */
const fieldOptions = useFormFieldsAndStartUser()
//
const formRules = reactive({
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 切换条件配置方式 */
const changeConditionType = () => {
if (condition.value.conditionType === ConditionType.RULE) {
if (!condition.value.conditionGroups) {
condition.value.conditionGroups = DEFAULT_CONDITION_GROUP_VALUE
}
}
}
const deleteConditionGroup = (conditions, index) => {
conditions.splice(index, 1)
}
const deleteConditionRule = (condition, index) => {
condition.rules.splice(index, 1)
}
const addConditionRule = (condition, index) => {
const rule = {
opCode: '==',
leftSide: '',
rightSide: ''
}
condition.rules.splice(index + 1, 0, rule)
}
const addConditionGroup = (conditions) => {
const condition = {
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
conditions.push(condition)
}
/** 保存条件设置 */
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) {
message.warning('请完善条件规则')
return
}
dialogVisible.value = false
//
emit('updateCondition', condition.value)
}
const open = (conditionObj: any | undefined) => {
if (conditionObj) {
condition.value.conditionType = conditionObj.conditionType
condition.value.conditionExpression = conditionObj.conditionExpression
condition.value.conditionGroups = conditionObj.conditionGroups
}
dialogVisible.value = true
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.condition-group-tool {
display: flex;
justify-content: space-between;
width: 500px;
margin-bottom: 20px;
}
.condition-group {
position: relative;
&:hover {
border-color: #0089ff;
.condition-group-delete {
opacity: 1;
}
}
.condition-group-delete {
position: absolute;
top: 0;
left: 0;
display: flex;
cursor: pointer;
opacity: 0;
}
}
::v-deep(.el-card__header) {
padding: 8px var(--el-card-padding);
border-bottom: 1px solid var(--el-card-border-color);
box-sizing: border-box;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-form-item label="请求头">
<el-form-item label-position="top" label="请求头">
<div class="flex pt-2" v-for="(item, index) in props.header" :key="index">
<div class="mr-2">
<el-form-item
@ -69,7 +69,7 @@
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item label="请求体">
<el-form-item label-position="top" label="请求体">
<div class="flex pt-2" v-for="(item, index) in props.body" :key="index">
<div class="mr-2">
<el-form-item
@ -141,7 +141,11 @@
</el-form-item>
</template>
<script setup lang="ts">
import { HttpRequestParam, BPM_HTTP_REQUEST_PARAM_TYPES, BpmHttpRequestParamTypeEnum } from '../../consts'
import {
HttpRequestParam,
BPM_HTTP_REQUEST_PARAM_TYPES,
BpmHttpRequestParamTypeEnum
} from '../../consts'
import { useFormFieldsAndStartUser } from '../../node'
defineOptions({
name: 'HttpRequestParamSetting'

View File

@ -0,0 +1,127 @@
<template>
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<!-- 请求地址-->
<el-form-item
label-position="top"
label="请求地址"
:prop="`${formItemPrefix}.url`"
:rules="{
required: true,
message: '请求地址不能为空',
trigger: 'blur'
}"
>
<el-input v-model="setting.url" />
</el-form-item>
<!-- 请求头请求体设置-->
<HttpRequestParamSetting :header="setting.header" :body="setting.body" :bind="formItemPrefix" />
<!-- 返回值设置-->
<div v-if="responseEnable">
<el-form-item label="返回值" label-position="top">
<el-alert
title="通过请求返回值, 可以修改流程表单的值"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item>
<div class="flex pt-2" v-for="(item, index) in setting.response" :key="index">
<div class="mr-2">
<el-form-item
:prop="`${formItemPrefix}.response.${index}.key`"
:rules="{
required: true,
message: '表单字段不能为空',
trigger: 'blur'
}"
>
<el-select class="w-160px!" v-model="item.key" placeholder="请选择表单字段">
<el-option
v-for="(field, fIdx) in formFields"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-form-item
:prop="`${formItemPrefix}.response.${index}.value`"
:rules="{
required: true,
message: '请求返回字段不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.value" placeholder="请求返回字段" />
</el-form-item>
</div>
<div class="mr-1 pt-1 cursor-pointer">
<Icon
icon="ep:delete"
:size="18"
@click="deleteHttpResponseSetting(setting.response!, index)"
/>
</div>
</div>
<el-button type="primary" text @click="addHttpResponseSetting(setting.response!)">
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</div>
</template>
<script setup lang="ts">
import HttpRequestParamSetting from './HttpRequestParamSetting.vue'
import { useFormFields } from '../../node'
const props = defineProps({
setting: {
type: Object,
required: true
},
responseEnable: {
type: Boolean,
required: true
},
formItemPrefix: {
type: String,
required: true
}
})
const { setting } = toRefs(props)
const emits = defineEmits(['update:setting'])
watch(
() => setting,
(val) => {
emits('update:setting', val)
}
)
/** 流程表单字段 */
const formFields = useFormFields()
/** 添加 HTTP 请求返回值设置项 */
const addHttpResponseSetting = (responseSetting: Record<string, string>[]) => {
responseSetting.push({
key: '',
value: ''
})
}
/** 删除 HTTP 请求返回值设置项 */
const deleteHttpResponseSetting = (responseSetting: Record<string, string>[], index: number) => {
responseSetting.splice(index, 1)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,106 @@
<template>
<div class="node-wrapper">
<div class="node-container">
<div
class="node-box"
:class="[
{ 'node-config-error': !currentNode.showText },
`${useTaskStatusClass(currentNode?.activityStatus)}`
]"
>
<div class="node-title-container">
<div
:class="`node-title-icon ${currentNode.childProcessSetting?.async === true ? 'async-child-process' : 'child-process'}`"
>
<span
:class="`iconfont ${currentNode.childProcessSetting?.async === true ? 'icon-async-child-process' : 'icon-child-process'}`"
>
</span>
</div>
<input
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-mountedFocus
v-model="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">
{{ currentNode.name }}
</div>
</div>
<div class="node-content" @click="openNodeConfig">
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.CHILD_PROCESS_NODE) }}
</div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div>
<div v-if="!readonly" class="node-toolbar">
<div class="toolbar-icon"
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
/></div>
</div>
</div>
<!-- 传递子节点给添加节点组件会在子节点前面添加节点 -->
<NodeHandler
v-if="currentNode"
v-model:child-node="currentNode.childNode"
:current-node="currentNode"
/>
</div>
<ChildProcessNodeConfig
v-if="!readonly && currentNode"
ref="nodeSetting"
:flow-node="currentNode"
/>
</div>
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import ChildProcessNodeConfig from '../nodes-config/ChildProcessNodeConfig.vue'
defineOptions({
name: 'ChildProcessNode'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
//
const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined]
}>()
//
const readonly = inject<Boolean>('readonly')
//
const currentNode = useWatchNode(props)
//
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.CHILD_PROCESS_NODE)
const nodeSetting = ref()
//
const openNodeConfig = () => {
if (readonly) {
return
}
nodeSetting.value.showChildProcessNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
//
const deleteNode = () => {
emits('update:flowNode', currentNode.value.childNode)
}
</script>
<style scoped></style>

View File

@ -9,7 +9,14 @@
]"
>
<div class="node-title-container">
<div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div>
<div
:class="`node-title-icon ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'transactor-task' : 'user-task'}`"
>
<span
:class="`iconfont ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'icon-transactor' : 'icon-approve'}`"
>
</span>
</div>
<input
v-if="!readonly && showInput"
type="text"
@ -28,7 +35,7 @@
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }}
{{ NODE_DEFAULT_TEXT.get(currentNode.type) }}
</div>
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
</div>

View File

@ -177,6 +177,18 @@
color: #ca3a31
}
.transactor {
color: #330099;
}
.child-process {
color: #996633;
}
.async-child-process {
color: #006666;
}
.handler-item-text {
margin-top: 4px;
width: 80px;
@ -294,6 +306,18 @@
&.router-node {
color: #ca3a31
}
&.transactor-task {
color: #330099;
}
&.child-process {
color: #996633;
}
&.async-child-process {
color: #006666;
}
}
.node-title {
@ -777,7 +801,7 @@
content: "\e7eb";
}
.icon-handle:before {
.icon-transactor:before {
content: "\e61c";
}
@ -792,3 +816,11 @@
.icon-parallel:before {
content: "\e688";
}
.icon-async-child-process:before {
content: "\e6f2";
}
.icon-child-process:before {
content: "\e6c1";
}

View File

@ -188,12 +188,8 @@
:scroll="true"
max-height="600px"
>
<!-- append-to-body -->
<div v-highlight>
<code class="hljs">
<!-- 高亮代码块 -->
{{ previewResult }}
</code>
<div>
<pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
</div>
</Dialog>
</div>
@ -237,6 +233,8 @@ import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml'
// const eventName = reactive({
// name: ''
// })
import hljs from 'highlight.js' //
import 'highlight.js/styles/github.css' //
defineOptions({ name: 'MyProcessDesigner' })
@ -308,6 +306,18 @@ const props = defineProps({
}
})
/**
* 代码高亮
*/
const highlightedCode = (code: string) => {
//
if (previewType.value === 'json') {
code = JSON.stringify(code, null, 2)
}
const result = hljs.highlight(code, { language: previewType.value, ignoreIllegals: true })
return result.value || '&nbsp;'
}
provide('configGlobal', props)
let bpmnModeler: any = null
const defaultZoom = ref(1)

View File

@ -123,13 +123,19 @@
</div>
<el-divider content-position="left">字段权限</el-divider>
<div class="field-setting-pane" v-if="formType === 10">
<div class="field-setting-pane" v-if="formType === BpmModelFormType.NORMAL">
<div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles">
<span class="setting-title-label">只读</span>
<span class="setting-title-label">可编辑</span>
<span class="setting-title-label">隐藏</span>
<span class="setting-title-label cursor-pointer" @click="updatePermission('READ')"
>只读</span
>
<span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')"
>可编辑</span
>
<span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')"
>隐藏</span
>
</div>
</div>
<div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index">
@ -140,24 +146,30 @@
:value="FieldPermissionType.READ"
size="large"
:label="FieldPermissionType.READ"
><span></span
></el-radio>
@change="updateElementExtensions"
>
<span></span>
</el-radio>
</div>
<div class="item-radio-wrap">
<el-radio
:value="FieldPermissionType.WRITE"
size="large"
:label="FieldPermissionType.WRITE"
><span></span
></el-radio>
@change="updateElementExtensions"
>
<span></span>
</el-radio>
</div>
<div class="item-radio-wrap">
<el-radio
:value="FieldPermissionType.NONE"
size="large"
:label="FieldPermissionType.NONE"
><span></span
></el-radio>
@change="updateElementExtensions"
>
<span></span>
</el-radio>
</div>
</el-radio-group>
</div>
@ -165,12 +177,22 @@
<el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable">
<el-switch v-model="signEnable.value" active-text="是" inactive-text="否" />
<el-switch
v-model="signEnable.value"
active-text="是"
inactive-text="否"
@change="updateElementExtensions"
/>
</el-form-item>
<el-divider content-position="left">审批意见</el-divider>
<el-form-item prop="reasonRequire">
<el-switch v-model="reasonRequire.value" active-text="必填" inactive-text="非必填" />
<el-switch
v-model="reasonRequire.value"
active-text="必填"
inactive-text="非必填"
@change="updateElementExtensions"
/>
</el-form-item>
</div>
</template>
@ -191,6 +213,7 @@ import {
} from '@/components/SimpleProcessDesignerV2/src/consts'
import * as UserApi from '@/api/system/user'
import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
import { BpmModelFormType } from '@/utils/constants'
defineOptions({ name: 'ElementCustomConfig4UserTask' })
const props = defineProps({
@ -248,7 +271,6 @@ const resetCustomConfigList = () => {
bpmnElement.value.id,
bpmnInstances().modeler
)
//
elExtensionElements.value =
bpmnElement.value.businessObject?.extensionElements ??
@ -311,14 +333,13 @@ const resetCustomConfigList = () => {
}
//
if (formType.value === 10) {
if (formType.value === BpmModelFormType.NORMAL) {
const fieldsPermissionList = elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:FieldsPermission`
)
fieldsPermissionEl.value = []
getNodeConfigFormFields()
//
fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
fieldsPermissionConfig.value = fieldsPermissionConfig.value
fieldsPermissionConfig.value.forEach((element) => {
element.permission =
fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
@ -487,6 +508,19 @@ function useButtonsSetting() {
}
}
/** 批量更新权限 */
// TODO @lesan idea fix
const updatePermission = (type: string) => {
fieldsPermissionEl.value.forEach((field) => {
field.permission =
type === 'READ'
? FieldPermissionType.READ
: type === 'WRITE'
? FieldPermissionType.WRITE
: FieldPermissionType.NONE
})
}
const userOptions = ref<UserApi.UserVO[]>([]) //
onMounted(async () => {
//
@ -497,9 +531,9 @@ onMounted(async () => {
<style lang="scss" scoped>
.button-setting-pane {
display: flex;
flex-direction: column;
font-size: 14px;
margin-top: 8px;
font-size: 14px;
flex-direction: column;
.button-setting-desc {
padding-right: 8px;

View File

@ -254,33 +254,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
activeMenu: '/bpm/manager/form'
}
},
{
path: 'manager/model/edit',
component: () => import('@/views/bpm/model/editor/index.vue'),
name: 'BpmModelEditor',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '设计流程',
activeMenu: '/bpm/manager/model'
}
},
{
path: 'manager/simple/model',
component: () => import('@/views/bpm/simple/SimpleModelDesign.vue'),
name: 'SimpleModelDesign',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '仿钉钉设计流程',
activeMenu: '/bpm/manager/model'
}
},
{
path: 'manager/definition',
component: () => import('@/views/bpm/definition/index.vue'),
component: () => import('@/views/bpm/model/definition/index.vue'),
name: 'BpmProcessDefinition',
meta: {
noCache: true,
@ -356,7 +332,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
// TODO @zws1建议在加一个路由。然后标题是“复制流程”这样体验会好点2复制出来的数据在名字前面加“副本 ”,和钉钉保持一致!
path: 'manager/model/:type/:id',
component: () => import('@/views/bpm/model/form/index.vue'),
name: 'BpmModelUpdate',

View File

@ -511,14 +511,14 @@ export function jsonParse(str: string) {
/**
*
*
* @param name
* @param start
* @param end
* @param str
* @param start
* @param end
*/
export const sliceName = (name: string,start: number, end : number) => {
if (name.length > end) {
return name.slice(start, end)
export const subString = (str: string, start: number, end: number) => {
if (str.length > end) {
return str.slice(start, end)
}
return name
return str
}

View File

@ -89,7 +89,7 @@
</el-tooltip>
<el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
<div v-else class="flow-icon">
<span style="font-size: 12px; color: #fff">{{ sliceName(row.name,0,2) }}</span>
<span style="font-size: 12px; color: #fff">{{ subString(row.name, 0, 2) }}</span>
</div>
{{ row.name }}
</div>
@ -113,6 +113,11 @@
</el-text>
</template>
</el-table-column>
<el-table-column label="流程类型" prop="type" min-width="120">
<template #default="{ row }">
<dict-tag :value="row.type" :type="DICT_TYPE.BPM_MODEL_TYPE" />
</template>
</el-table-column>
<el-table-column label="表单信息" prop="formType" min-width="150">
<template #default="scope">
<el-button
@ -260,6 +265,7 @@
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import Sortable from 'sortablejs'
import { formatDate } from '@/utils/formatTime'
@ -271,9 +277,8 @@ import { checkPermi } from '@/utils/permission'
import { useUserStoreWithOut } from '@/store/modules/user'
import { useAppStore } from '@/store/modules/app'
import { cloneDeep, isEqual } from 'lodash-es'
import { useTagsView } from '@/hooks/web/useTagsView'
import { useDebounceFn } from '@vueuse/core'
import { sliceName } from '@/utils/index'
import { subString } from '@/utils/index'
defineOptions({ name: 'BpmModel' })
@ -583,8 +588,7 @@ const handleDeleteCategory = async () => {
} catch {}
}
/** 添加流程模型弹窗 */
const tagsView = useTagsView()
/** 添加/修改/复制流程模型弹窗 */
const openModelForm = async (type: string, id?: number) => {
if (type === 'create') {
await push({ name: 'BpmModelCreate' })
@ -593,10 +597,6 @@ const openModelForm = async (type: string, id?: number) => {
name: 'BpmModelUpdate',
params: { id, type }
})
//
if (type === 'copy') {
tagsView.setTitle('复制流程')
}
}
}

View File

@ -3,40 +3,60 @@
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="定义编号" align="center" prop="id" width="400" />
<el-table-column label="流程名称" align="center" prop="name" width="200">
<template #default="scope">
<el-button type="primary" link @click="handleBpmnDetail(scope.row)">
<span>{{ scope.row.name }}</span>
</el-button>
<el-table-column label="定义编号" align="center" prop="id" min-width="250" />
<el-table-column label="流程名称" align="center" prop="name" min-width="150" />
<el-table-column label="流程图标" align="center" min-width="50">
<template #default="{ row }">
<el-image v-if="row.icon" :src="row.icon" class="h-24px w-24pxrounded" />
</template>
</el-table-column>
<el-table-column label="定义分类" align="center" prop="categoryName" width="100" />
<el-table-column label="表单信息" align="center" prop="formType" width="200">
<el-table-column label="可见范围" prop="startUserIds" min-width="100">
<template #default="{ row }">
<el-text v-if="!row.startUsers?.length"> 全部可见 </el-text>
<el-text v-else-if="row.startUsers.length === 1">
{{ row.startUsers[0].nickname }}
</el-text>
<el-text v-else>
<el-tooltip
class="box-item"
effect="dark"
placement="top"
:content="row.startUsers.map((user: any) => user.nickname).join('、')"
>
{{ row.startUsers[0].nickname }} {{ row.startUsers.length }} 人可见
</el-tooltip>
</el-text>
</template>
</el-table-column>
<el-table-column label="流程类型" prop="modelType" min-width="120">
<template #default="{ row }">
<dict-tag :value="row.modelType" :type="DICT_TYPE.BPM_MODEL_TYPE" />
</template>
</el-table-column>
<el-table-column label="表单信息" prop="formType" min-width="150">
<template #default="scope">
<el-button
v-if="scope.row.formType === 10"
v-if="scope.row.formType === BpmModelFormType.NORMAL"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formName }}</span>
</el-button>
<el-button v-else type="primary" link @click="handleFormDetail(scope.row)">
<el-button
v-else-if="scope.row.formType === BpmModelFormType.CUSTOM"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formCustomCreatePath }}</span>
</el-button>
<label v-else>暂无表单</label>
</template>
</el-table-column>
<el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80">
<el-table-column label="流程版本" align="center" min-width="80">
<template #default="scope">
<el-tag v-if="scope.row">v{{ scope.row.version }}</el-tag>
<el-tag type="warning" v-else>未部署</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="version" width="80">
<template #default="scope">
<el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
<el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
<el-tag>v{{ scope.row.version }}</el-tag>
</template>
</el-table-column>
<el-table-column
@ -46,13 +66,18 @@
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="定义描述"
align="center"
prop="description"
width="300"
show-overflow-tooltip
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModelForm(scope.row.id)"
v-hasPermi="['bpm:model:update']"
>
恢复
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
@ -67,18 +92,14 @@
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
<!-- 弹窗流程模型图的预览 -->
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
<MyProcessViewer style="height: 700px" key="designer" :xml="bpmnXml" />
</Dialog>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
import * as DefinitionApi from '@/api/bpm/definition'
import { setConfAndFields2 } from '@/utils/formCreate'
import { DICT_TYPE } from '@/utils/dict'
import { BpmModelFormType } from '@/utils/constants'
defineOptions({ name: 'BpmProcessDefinition' })
@ -113,7 +134,7 @@ const formDetailPreview = ref({
option: {}
})
const handleFormDetail = async (row: any) => {
if (row.formType == 10) {
if (row.formType == BpmModelFormType.NORMAL) {
//
setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
//
@ -125,15 +146,12 @@ const handleFormDetail = async (row: any) => {
}
}
/** 流程图的详情按钮操作 */
const bpmnDetailVisible = ref(false)
const bpmnXml = ref('')
const handleBpmnDetail = async (row: any) => {
//
bpmnXml.value = ''
bpmnDetailVisible.value = true
// BPMN XML
bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
/** 恢复流程模型弹窗 */
const openModelForm = async (id?: number) => {
await push({
name: 'BpmModelUpdate',
params: { id, type: 'definition' }
})
}
/** 初始化 **/
@ -141,3 +159,16 @@ onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
.flow-icon {
display: flex;
width: 38px;
height: 38px;
margin-right: 10px;
background-color: var(--el-color-primary);
border-radius: 0.25rem;
align-items: center;
justify-content: center;
}
</style>

View File

@ -140,6 +140,46 @@
</el-select>
</div>
</el-form-item>
<el-form-item class="mb-20px">
<template #label>
<el-text size="large" tag="b">流程前置通知</el-text>
</template>
<div class="flex flex-col w-100%">
<div class="flex">
<el-switch
v-model="preProcessNotifyEnable"
@change="handlePreProcessNotifyEnableChange"
/>
<div class="ml-80px">流程启动后通知</div>
</div>
<HttpRequestSetting
v-if="preProcessNotifyEnable"
v-model:setting="modelData.preProcessNotifySetting"
:responseEnable="true"
:formItemPrefix="'preProcessNotifySetting'"
/>
</div>
</el-form-item>
<el-form-item class="mb-20px">
<template #label>
<el-text size="large" tag="b">流程后置通知</el-text>
</template>
<div class="flex flex-col w-100%">
<div class="flex">
<el-switch
v-model="postProcessNotifyEnable"
@change="handlePostProcessNotifyEnableChange"
/>
<div class="ml-80px">流程启动后通知</div>
</div>
<HttpRequestSetting
v-if="postProcessNotifyEnable"
v-model:setting="modelData.postProcessNotifySetting"
:responseEnable="true"
:formItemPrefix="'postProcessNotifySetting'"
/>
</div>
</el-form-item>
</el-form>
</template>
@ -149,6 +189,7 @@ import { BpmAutoApproveType, BpmModelFormType } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form'
import { parseFormFields } from '@/components/FormCreate/src/utils'
import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts'
import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
const modelData = defineModel<any>()
@ -205,6 +246,36 @@ const numberExample = computed(() => {
}
})
/** 是否开启流程前置通知 */
const preProcessNotifyEnable = ref(false)
const handlePreProcessNotifyEnableChange = (val: boolean | string | number) => {
if (val) {
modelData.value.preProcessNotifySetting = {
url: '',
header: [],
body: [],
response: []
}
} else {
modelData.value.preProcessNotifySetting = null
}
}
/** 是否开启流程后置通知 */
const postProcessNotifyEnable = ref(false)
const handlePostProcessNotifyEnableChange = (val: boolean | string | number) => {
if (val) {
modelData.value.postProcessNotifySetting = {
url: '',
header: [],
body: [],
response: []
}
} else {
modelData.value.postProcessNotifySetting = null
}
}
/** 表单选项 */
const formField = ref<Array<{ field: string; title: string }>>([])
const formFieldOptions4Title = computed(() => {
@ -264,6 +335,12 @@ const initData = () => {
summary: []
}
}
if (modelData.value.preProcessNotifySetting) {
preProcessNotifyEnable.value = true
}
if (modelData.value.postProcessNotifySetting) {
postProcessNotifyEnable.value = true
}
}
defineExpose({ initData })

View File

@ -11,12 +11,12 @@
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="modelData.formType === 10" label="流程表单" prop="formId">
<el-form-item v-if="modelData.formType === BpmModelFormType.NORMAL" label="流程表单" prop="formId">
<el-select v-model="modelData.formId" clearable style="width: 100%">
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
</el-select>
</el-form-item>
<el-form-item v-if="modelData.formType === 20" label="表单提交路由" prop="formCustomCreatePath">
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单提交路由" prop="formCustomCreatePath">
<el-input
v-model="modelData.formCustomCreatePath"
placeholder="请输入表单提交路由"
@ -31,7 +31,7 @@
<Icon icon="ep:question" class="ml-5px" />
</el-tooltip>
</el-form-item>
<el-form-item v-if="modelData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单查看地址" prop="formCustomViewPath">
<el-input
v-model="modelData.formCustomViewPath"
placeholder="请输入表单查看的组件地址"
@ -48,7 +48,7 @@
</el-form-item>
<!-- 表单预览 -->
<div
v-if="modelData.formType === 10 && modelData.formId && formPreview.rule.length > 0"
v-if="modelData.formType === BpmModelFormType.NORMAL && modelData.formId && formPreview.rule.length > 0"
class="mt-20px"
>
<div class="flex items-center mb-15px">
@ -68,6 +68,7 @@
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate'
import { BpmModelFormType } from '@/utils/constants'
const props = defineProps({
formList: {
@ -96,7 +97,7 @@ const formPreview = ref({
watch(
() => modelData.value.formId,
async (newFormId) => {
if (newFormId && modelData.value.formType === 10) {
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
const data = await FormApi.getForm(newFormId)
setConfAndFields2(formPreview.value, data.conf, data.fields)
//

View File

@ -25,7 +25,7 @@
<script lang="ts" setup>
import { BpmModelType } from '@/utils/constants'
import BpmModelEditor from '../editor/index.vue'
import BpmModelEditor from './editor/index.vue'
import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
//

View File

@ -34,10 +34,12 @@ import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/d
//
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
import * as ModelApi from '@/api/bpm/model'
import { BpmModelFormType } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form'
defineOptions({ name: 'BpmModelEditor' })
const props = defineProps<{
defineProps<{
modelId?: string
modelKey: string
modelName: string
@ -49,7 +51,8 @@ const message = useMessage() // 国际化
//
const formFields = ref<string[]>([])
const formType = ref(20)
//
const formType = ref(BpmModelFormType.NORMAL)
provide('formFields', formFields)
provide('formType', formType)
@ -88,6 +91,20 @@ const save = async (bpmnXml: string) => {
}
}
/** 监听表单 ID 变化,加载表单数据 */
watch(
() => modelData.value.formId,
async (newFormId) => {
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
const data = await FormApi.getForm(newFormId)
formFields.value = data.fields
} else {
formFields.value = []
}
},
{ immediate: true }
)
//
onBeforeUnmount(() => {
modeler.value = null

View File

@ -44,8 +44,13 @@
<!-- 右侧按钮 -->
<div class="w-200px flex items-center justify-end gap-2">
<el-button v-if="route.params.id" type="success" @click="handleDeploy"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button>
<el-button v-if="actionType === 'update'" type="success" @click="handleDeploy">
</el-button>
<el-button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'"> </span>
<span v-else> </span>
</el-button>
</div>
</div>
@ -81,20 +86,23 @@
<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router'
import { useMessage } from '@/hooks/web/useMessage'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { useUserStoreWithOut } from '@/store/modules/user'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as UserApi from '@/api/system/user'
import { useUserStoreWithOut } from '@/store/modules/user'
import * as DefinitionApi from '@/api/bpm/definition'
import { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
import BasicInfo from './BasicInfo.vue'
import FormDesign from './FormDesign.vue'
import ProcessDesign from './ProcessDesign.vue'
import { useTagsViewStore } from '@/store/modules/tagsView'
import ExtraSettings from './ExtraSettings.vue'
import { useTagsView } from '@/hooks/web/useTagsView'
const router = useRouter()
const { delView } = useTagsViewStore() //
const tagsView = useTagsView()
const route = useRoute()
const message = useMessage()
const userStore = useUserStoreWithOut()
@ -177,20 +185,36 @@ const categoryList = ref<CategoryVO[]>([])
const userList = ref<UserApi.UserVO[]>([])
/** 初始化数据 */
const actionType = route.params.type as string
const initData = async () => {
if (actionType === 'definition') {
//
const definitionId = route.params.id as string
const data = await DefinitionApi.getProcessDefinition(definitionId)
// definition => model
data.type = data.modelType
delete data.modelType
data.id = data.modelId
delete data.modelId
if (data.simpleModel) {
data.simpleModel = JSON.parse(data.simpleModel)
}
formData.value = data
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
} else if (['update', 'copy'].includes(actionType)) {
// /
const modelId = route.params.id as string
if (modelId) {
//
formData.value = await ModelApi.getModel(modelId)
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
//
if (route.params.type === 'copy') {
//
if (actionType === 'copy') {
delete formData.value.id
formData.value.name += '副本'
formData.value.key += '_copy'
tagsView.setTitle('复制流程')
}
} else {
//
//
formData.value.startUserType = 0 //
formData.value.managerUserIds.push(userStore.getUser.id)
}
@ -271,37 +295,31 @@ const handleSave = async () => {
...formData.value
}
if (formData.value.id) {
if (actionType === 'definition') {
//
await ModelApi.updateModel(modelData)
//
message.success('恢复成功,可点击【发布】按钮,进行发布模型')
} else if (actionType === 'update') {
//
await ModelApi.updateModel(modelData)
//
try {
await message.confirm('修改流程成功,是否发布流程?')
//
await handleDeploy()
} catch {
//
}
} else {
//
//
message.success('修改成功,可点击【发布】按钮,进行发布模型')
} else if (actionType === 'copy') {
//
formData.value.id = await ModelApi.createModel(modelData)
try {
await message.confirm('流程创建成功,是否继续编辑?')
//
await nextTick()
//
delView(unref(router.currentRoute))
//
await router.push({
name: 'BpmModelUpdate',
params: { id: formData.value.id }
})
} catch {
//
delView(unref(router.currentRoute))
//
await router.push({ name: 'BpmModel' })
//
message.success('复制成功,可点击【发布】按钮,进行发布模型')
} else {
//
formData.value.id = await ModelApi.createModel(modelData)
//
message.success('新建成功,可点击【发布】按钮,进行发布模型')
}
//
if (actionType !== 'update') {
await router.push({ name: 'BpmModel' })
}
} catch (error: any) {
console.error('保存失败:', error)
@ -346,7 +364,6 @@ const handleDeploy = async () => {
/** 步骤切换处理 */
const handleStepClick = async (index: number) => {
try {
console.log('index', index)
if (index !== 0) {
await validateBasic()
}

View File

@ -74,7 +74,7 @@
</template>
<script lang="ts" setup>
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
import { BpmModelType } from '@/utils/constants'
import { BpmModelType, BpmModelFormType } from '@/utils/constants'
import {
CandidateStrategy,
NodeId,
@ -108,6 +108,7 @@ const fApi = ref<ApiAttrs>()
//
const startUserSelectTasks: any = ref([]) //
const startUserSelectAssignees = ref({}) //
const tempStartUserSelectAssignees = ref({}) //
const bpmnXML: any = ref(null) // BPMN
const simpleJson = ref<string | undefined>() // Simple json
@ -121,7 +122,7 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
startUserSelectAssignees.value = {}
//
if (row.formType == 10) {
if (row.formType == BpmModelFormType.NORMAL) {
//
// formVariables row.formFields
// formVariables
@ -137,8 +138,11 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
await nextTick()
fApi.value?.btn.show(false) //
//
await getApprovalDetail(row)
// ,
await getApprovalDetail({
id: row.id,
processVariablesStr: JSON.stringify(formVariables)
})
//
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
@ -155,32 +159,61 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
}
}
/** 预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次 */
watch(
detailForm.value,
(newValue) => {
if (newValue && Object.keys(newValue.value).length > 0) {
//
tempStartUserSelectAssignees.value = startUserSelectAssignees.value
startUserSelectAssignees.value = {}
//
getApprovalDetail({
id: props.selectProcessDefinition.id,
processVariablesStr: JSON.stringify(newValue.value) // GET String JSON
})
}
},
{
immediate: true
}
)
/** 获取审批详情 */
const getApprovalDetail = async (row: any) => {
try {
// TODO activityId Simple
// TODO activityId Simple @jason activityId
const data = await ProcessInstanceApi.getApprovalDetail({
processDefinitionId: row.id,
activityId: NodeId.START_USER_NODE_ID
activityId: NodeId.START_USER_NODE_ID,
processVariablesStr: row.processVariablesStr // GET String JSON
})
if (!data) {
message.error('查询不到审批详情信息!')
return
}
// Timeline
activityNodes.value = data.activityNodes
//
startUserSelectTasks.value = data.activityNodes?.filter(
(node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
)
//
if (startUserSelectTasks.value?.length > 0) {
for (const node of startUserSelectTasks.value) {
if (
tempStartUserSelectAssignees.value[node.id] &&
tempStartUserSelectAssignees.value[node.id].length > 0
) {
startUserSelectAssignees.value[node.id] = tempStartUserSelectAssignees.value[node.id]
} else {
startUserSelectAssignees.value[node.id] = []
}
}
}
// Timeline
activityNodes.value = data.activityNodes
//
const formFieldsPermission = data.formFieldsPermission
//

View File

@ -64,9 +64,9 @@
class="w-32px h-32px"
/>
<div v-else class="flow-icon">
<span style="font-size: 12px; color: #fff">{{
sliceName(definition.name,0,2)
}}</span>
<span style="font-size: 12px; color: #fff">
{{ subString(definition.name, 0, 2) }}
</span>
</div>
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
</div>
@ -97,7 +97,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
import { groupBy } from 'lodash-es'
import { sliceName } from '@/utils/index'
import { subString } from '@/utils/index'
defineOptions({ name: 'BpmProcessInstanceCreate' })

View File

@ -1,267 +0,0 @@
<template>
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
<!-- 第一步通过流程定义的列表选择对应的流程 -->
<ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
<el-tabs tab-position="left" v-model="categoryActive">
<el-tab-pane
:label="category.name"
:name="category.code"
:key="category.code"
v-for="category in categoryList"
>
<el-row :gutter="20">
<el-col
:lg="6"
:sm="12"
:xs="24"
v-for="definition in categoryProcessDefinitionList"
:key="definition.id"
>
<el-card
shadow="hover"
class="mb-20px cursor-pointer"
@click="handleSelect(definition)"
>
<template #default>
<div class="flex">
<el-image :src="definition.icon" class="w-32px h-32px" />
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
</div>
</template>
</el-card>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</ContentWrap>
<!-- 第二步填写表单进行流程的提交 -->
<ContentWrap v-else>
<el-card class="box-card">
<div class="clearfix">
<span class="el-icon-document">申请信息{{ selectProcessDefinition.name }}</span>
<el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
<Icon icon="ep:delete" /> 选择其它流程
</el-button>
</div>
<el-col :span="16" :offset="6" style="margin-top: 20px">
<form-create
:rule="detailForm.rule"
v-model:api="fApi"
v-model="detailForm.value"
:option="detailForm.option"
@submit="submitForm"
>
<template #type-startUserSelect>
<el-col :span="24">
<el-card class="mb-10px">
<template #header>指定审批人</template>
<el-form
:model="startUserSelectAssignees"
:rules="startUserSelectAssigneesFormRules"
ref="startUserSelectAssigneesFormRef"
>
<el-form-item
v-for="userTask in startUserSelectTasks"
:key="userTask.id"
:label="`任务【${userTask.name}】`"
:prop="userTask.id"
>
<el-select
v-model="startUserSelectAssignees[userTask.id]"
multiple
placeholder="请选择审批人"
>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-form>
</el-card>
</el-col>
</template>
</form-create>
</el-col>
</el-card>
<!-- 流程图预览 -->
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
</ContentWrap>
</template>
<script lang="ts" setup>
import * as DefinitionApi from '@/api/bpm/definition'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
import { CategoryApi } from '@/api/bpm/category'
import { useTagsViewStore } from '@/store/modules/tagsView'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'BpmProcessInstanceCreate' })
const route = useRoute() //
const { push, currentRoute } = useRouter() //
const message = useMessage() //
const { delView } = useTagsViewStore() //
const processInstanceId = route.query.processInstanceId
const loading = ref(true) //
const categoryList = ref([]) //
const categoryActive = ref('') //
const processDefinitionList = ref([]) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
//
categoryList.value = await CategoryApi.getCategorySimpleList()
if (categoryList.value.length > 0) {
categoryActive.value = categoryList.value[0].code
}
//
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
suspensionState: 1
})
// processInstanceId
if (processInstanceId?.length > 0) {
const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
if (!processInstance) {
message.error('重新发起流程失败,原因:流程实例不存在')
return
}
const processDefinition = processDefinitionList.value.find(
(item) => item.key == processInstance.processDefinition?.key
)
if (!processDefinition) {
message.error('重新发起流程失败,原因:流程定义不存在')
return
}
await handleSelect(processDefinition, processInstance.formVariables)
}
} finally {
loading.value = false
}
}
/** 选中分类对应的流程定义列表 */
const categoryProcessDefinitionList = computed(() => {
return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
})
// ========== ==========
const fApi = ref<ApiAttrs>()
const detailForm = ref({
rule: [],
option: {},
value: {}
}) //
const selectProcessDefinition = ref() //
//
const bpmnXML = ref(null) // BPMN
const startUserSelectTasks = ref([]) //
const startUserSelectAssignees = ref({}) //
const startUserSelectAssigneesFormRef = ref() // Ref
const startUserSelectAssigneesFormRules = ref({}) // Rules
const userList = ref<any[]>([]) //
/** 处理选择流程的按钮操作 **/
const handleSelect = async (row, formVariables) => {
//
selectProcessDefinition.value = row
//
startUserSelectTasks.value = []
startUserSelectAssignees.value = {}
startUserSelectAssigneesFormRules.value = {}
//
if (row.formType == 10) {
//
// formVariables row.formFields
// formVariables
//
const allowedFields = decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field)
for (const key in formVariables) {
if (!allowedFields.includes(key)) {
delete formVariables[key]
}
}
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
//
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
if (processDefinitionDetail) {
bpmnXML.value = processDefinitionDetail.bpmnXml
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
//
if (startUserSelectTasks.value?.length > 0) {
detailForm.value.rule.push({
type: 'startUserSelect',
props: {
title: '指定审批人'
}
})
//
for (const userTask of startUserSelectTasks.value) {
startUserSelectAssignees.value[userTask.id] = []
startUserSelectAssigneesFormRules.value[userTask.id] = [
{ required: true, message: '请选择审批人', trigger: 'blur' }
]
}
//
userList.value = await UserApi.getSimpleUserList()
}
}
//
} else if (row.formCustomCreatePath) {
await push({
path: row.formCustomCreatePath
})
// Tab
}
}
/** 提交按钮 */
const submitForm = async (formData) => {
if (!fApi.value || !selectProcessDefinition.value) {
return
}
//
if (startUserSelectTasks.value?.length > 0) {
await startUserSelectAssigneesFormRef.value.validate()
}
//
fApi.value.btn.loading(true)
try {
await ProcessInstanceApi.createProcessInstance({
processDefinitionId: selectProcessDefinition.value.id,
variables: formData,
startUserSelectAssignees: startUserSelectAssignees.value
})
//
message.success('发起流程成功')
//
delView(unref(currentRoute))
await push({
name: 'BpmProcessInstanceMy'
})
} finally {
fApi.value.btn.loading(false)
}
}
/** 初始化 */
onMounted(() => {
getList()
})
</script>

View File

@ -36,14 +36,27 @@
:rule="approveForm.rule"
/>
</el-card>
<el-form-item label="审批意见" prop="reason">
<el-form-item :label="`${nodeTypeName}意见`" prop="reason">
<el-input
v-model="approveReasonForm.reason"
placeholder="请输入审批意见"
:placeholder="`请输入${nodeTypeName}意见`"
type="textarea"
:rows="4"
/>
</el-form-item>
<el-form-item
label="下一个节点的审批人"
prop="nextAssignees"
v-if="nextAssigneesActivityNode.length > 0"
>
<div class="ml-10px -mt-15px -mb-35px">
<ProcessInstanceTimeline
:activity-nodes="nextAssigneesActivityNode"
:show-status-icon="false"
@select-user-confirm="selectNextAssigneesConfirm"
/>
</div>
</el-form-item>
<el-form-item
v-if="runningTask.signEnable"
label="签名"
@ -66,7 +79,7 @@
>
{{ getButtonDisplayName(OperationButtonType.APPROVE) }}
</el-button>
<el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
<el-button @click="closePopover('approve', approveFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -111,7 +124,7 @@
>
{{ getButtonDisplayName(OperationButtonType.REJECT) }}
</el-button>
<el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
<el-button @click="closePopover('reject', rejectFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -169,7 +182,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleCopy">
{{ getButtonDisplayName(OperationButtonType.COPY) }}
</el-button>
<el-button @click="closePropover('copy', copyFormRef)"> 取消 </el-button>
<el-button @click="closePopover('copy', copyFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -221,7 +234,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
{{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
</el-button>
<el-button @click="closePropover('transfer', transferFormRef)"> 取消 </el-button>
<el-button @click="closePopover('transfer', transferFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -273,7 +286,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
{{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
</el-button>
<el-button @click="closePropover('delegate', delegateFormRef)"> 取消 </el-button>
<el-button @click="closePopover('delegate', delegateFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -328,7 +341,7 @@
<el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
</el-button>
<el-button @click="closePropover('addSign', addSignFormRef)"> 取消 </el-button>
<el-button @click="closePopover('addSign', addSignFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -379,7 +392,7 @@
<el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
减签
</el-button>
<el-button @click="closePropover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
<el-button @click="closePopover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -431,7 +444,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleReturn()">
{{ getButtonDisplayName(OperationButtonType.RETURN) }}
</el-button>
<el-button @click="closePropover('return', returnFormRef)"> 取消 </el-button>
<el-button @click="closePopover('return', returnFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -475,7 +488,7 @@
<el-button :disabled="formLoading" type="primary" @click="handleCancel()">
确认
</el-button>
<el-button @click="closePropover('cancel', cancelFormRef)"> 取消 </el-button>
<el-button @click="closePopover('cancel', cancelFormRef)"> 取消 </el-button>
</el-form-item>
</el-form>
</div>
@ -504,12 +517,16 @@ import * as TaskApi from '@/api/bpm/task'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as UserApi from '@/api/system/user'
import {
NodeType,
OPERATION_BUTTON_NAME,
OperationButtonType
OperationButtonType,
CandidateStrategy
} from '@/components/SimpleProcessDesignerV2/src/consts'
import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
import type { FormInstance, FormRules } from 'element-plus'
import SignDialog from './SignDialog.vue'
import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
import { isEmpty } from '@/utils/is'
defineOptions({ name: 'ProcessInstanceBtnContainer' })
@ -546,22 +563,29 @@ const returnList = ref([] as any) // 退回节点
const runningTask = ref<any>() //
const approveForm = ref<any>({}) //
const approveFormFApi = ref<any>({}) // approveForms fAPi
const nodeTypeName = ref('审批') //
//
const reasonRequire = ref()
const approveFormRef = ref<FormInstance>()
const signRef = ref()
const approveSignFormRef = ref()
const nextAssigneesActivityNode = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) //
const approveReasonForm = reactive({
reason: '',
signPicUrl: ''
signPicUrl: '',
nextAssignees: {}
})
const approveReasonRule = computed(() => {
return {
reason: [{ required: reasonRequire.value, message: '审批意见不能为空', trigger: 'blur' }],
signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }]
reason: [
{ required: reasonRequire.value, message: nodeTypeName + '意见不能为空', trigger: 'blur' }
],
signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }],
nextAssignees: [{ required: true, message: '审批人不能为空', trigger: 'blur' }]
}
})
//
const rejectFormRef = ref<FormInstance>()
const rejectReasonForm = reactive({
@ -668,6 +692,7 @@ const openPopover = async (type: string) => {
message.warning('表单校验不通过,请先完善表单!!')
return
}
initNextAssigneesFormField()
}
if (type === 'return') {
// 退
@ -685,11 +710,58 @@ const openPopover = async (type: string) => {
}
/** 关闭气泡卡 */
const closePropover = (type: string, formRef: FormInstance | undefined) => {
const closePopover = (type: string, formRef: FormInstance | undefined) => {
if (formRef) {
formRef.resetFields()
}
popOverVisible.value[type] = false
nextAssigneesActivityNode.value = []
}
/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
const initNextAssigneesFormField = async () => {
// ,
const variables = getUpdatedProcessInstanceVariables()
const data = await ProcessInstanceApi.getNextApprovalNodes({
processInstanceId: props.processInstance.id,
taskId: runningTask.value.id,
processVariablesStr: JSON.stringify(variables)
})
if (data && data.length > 0) {
data.forEach((node: any) => {
if (
//
(isEmpty(node.tasks) &&
isEmpty(node.candidateUsers) &&
CandidateStrategy.START_USER_SELECT === node.candidateStrategy) ||
//
CandidateStrategy.APPROVE_USER_SELECT === node.candidateStrategy
) {
nextAssigneesActivityNode.value.push(node)
}
})
}
}
/** 选择下一个节点的审批人 */
const selectNextAssigneesConfirm = (id: string, userList: any[]) => {
approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id)
}
/** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */
const validateNextAssignees = () => {
// TODO @ Object.keys(nextAssigneesActivityNode.value).length === 0) return true
//
if (Object.keys(nextAssigneesActivityNode.value).length > 0) {
//
for (const item of nextAssigneesActivityNode.value) {
if (isEmpty(approveReasonForm.nextAssignees[item.id])) {
// TODO @
message.warning('下一个节点的审批人不能为空!')
return false
}
}
}
return true
}
/** 处理审批通过和不通过的操作 */
@ -699,15 +771,24 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
//
if (!formRef) return
await formRef.validate()
//
const valid = await validateNormalForm()
if (!valid) {
message.warning('表单校验不通过,请先完善表单!!')
return
}
if (pass) {
// ,
const nextAssigneesValid = validateNextAssignees()
if (!nextAssigneesValid) return
const variables = getUpdatedProcessInstanceVariables()
//
const data = {
id: runningTask.value.id,
reason: approveReasonForm.reason,
variables // ,
}
variables, // ,
nextAssignees: approveReasonForm.nextAssignees //
} as any
//
if (runningTask.value.signEnable) {
data.signPicUrl = approveReasonForm.signPicUrl
@ -722,6 +803,7 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
}
await TaskApi.approveTask(data)
popOverVisible.value.approve = false
nextAssigneesActivityNode.value = []
message.success('审批通过成功')
} else {
//
@ -969,9 +1051,10 @@ const getButtonDisplayName = (btnType: OperationButtonType) => {
const loadTodoTask = (task: any) => {
approveForm.value = {}
approveFormFApi.value = {}
runningTask.value = task
approveFormFApi.value = {}
reasonRequire.value = task?.reasonRequire ?? false
nodeTypeName.value = task?.nodeType === NodeType.TRANSACTOR_NODE ? '办理' : '审批'
// approve .
if (task && task.formId && task.formConf) {
const tempApproveForm = {}
@ -997,6 +1080,11 @@ const validateNormalForm = async () => {
}
}
/**
* TODO @小北 TO @芋道
* 问题这里存在一种场景会出现问题流程发起后A节点审批完成B节点没有可编辑的流程字段且B节点为自选审批人节点会导致流程审批人为空
* 原因因为没有可编辑的流程字段时props.writableFields为空参数variables传递时也为空
*/
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
const getUpdatedProcessInstanceVariables = () => {
const variables = {}

View File

@ -42,13 +42,13 @@ watch(
const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds
setSimpleModelNodeTaskStatus(
newModelView.simpleModel,
newModelView.processInstance.status,
newModelView.processInstance?.status,
rejectedTaskActivityIds,
unfinishedTaskActivityIds,
finishedActivityIds,
finishedSequenceFlowActivityIds
)
simpleModel.value = newModelView.simpleModel
simpleModel.value = newModelView.simpleModel ? newModelView.simpleModel : {}
}
}
)
@ -84,7 +84,9 @@ const setSimpleModelNodeTaskStatus = (
//
if (
simpleModel.type === NodeType.START_USER_NODE ||
simpleModel.type === NodeType.USER_TASK_NODE
simpleModel.type === NodeType.USER_TASK_NODE ||
simpleModel.type === NodeType.TRANSACTOR_NODE ||
simpleModel.type === NodeType.CHILD_PROCESS_NODE
) {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
if (rejectedTaskActivityIds.includes(simpleModel.id)) {
@ -169,5 +171,4 @@ const setSimpleModelNodeTaskStatus = (
}
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View File

@ -43,7 +43,8 @@
v-if="
isEmpty(activity.tasks) &&
isEmpty(activity.candidateUsers) &&
CandidateStrategy.START_USER_SELECT === activity.candidateStrategy
(CandidateStrategy.START_USER_SELECT === activity.candidateStrategy ||
CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy)
"
>
<!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
@ -121,6 +122,7 @@
"
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
>
<!-- TODO lesan这里如果是办理需要是办理意见 -->
审批意见{{ task.reason }}
</div>
<div
@ -179,6 +181,8 @@ import copySvg from '@/assets/svgs/bpm/copy.svg'
import conditionSvg from '@/assets/svgs/bpm/condition.svg'
import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
import finishSvg from '@/assets/svgs/bpm/finish.svg'
import transactorSvg from '@/assets/svgs/bpm/transactor.svg'
import childProcessSvg from '@/assets/svgs/bpm/child-process.svg'
defineOptions({ name: 'BpmProcessInstanceTimeline' })
withDefaults(
@ -240,12 +244,16 @@ const nodeTypeSvgMap = {
[NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
//
[NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
//
[NodeType.TRANSACTOR_NODE]: { color: '#ff943e', svg: transactorSvg },
//
[NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
//
[NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
//
[NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg }
[NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg },
//
[NodeType.CHILD_PROCESS_NODE]: { color: '#14bb83', svg: childProcessSvg }
}
// -101 icon
@ -264,6 +272,8 @@ const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
if (
nodeType === NodeType.START_USER_NODE ||
nodeType === NodeType.USER_TASK_NODE ||
nodeType === NodeType.TRANSACTOR_NODE ||
nodeType === NodeType.CHILD_PROCESS_NODE ||
nodeType === NodeType.END_EVENT_NODE
) {
return statusIconMap[taskStatus]?.icon

View File

@ -178,8 +178,9 @@ const writableFields: Array<string> = [] // 表单可以编辑的字段
/** 获得详情 */
const getDetail = () => {
//
getApprovalDetail()
//
getProcessModelView()
}

View File

@ -24,9 +24,7 @@
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
</el-form-item>
<!-- TODO @ tuitujistyle 可以使用 unocss -->
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
<!-- TODO @tuituji应该选择好分类就触发搜索啦 RE:done & to check-->
<el-form-item label="" prop="category" class="absolute right-[300px]">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
@ -42,8 +40,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
<el-form-item label="" prop="status" class="absolute right-[130px]">
<el-select
v-model="queryParams.status"
placeholder="请选择流程状态"
@ -61,8 +58,7 @@
</el-form-item>
<!-- 高级筛选 -->
<!-- TODO @ tuitujistyle 可以使用 unocss -->
<el-form-item :style="{ position: 'absolute', right: '0px' }">
<el-form-item class="absolute right-0">
<el-popover
:visible="showPopover"
persistent
@ -75,36 +71,28 @@
<Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button>
</template>
<!-- <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
<el-select
v-model="queryParams.category"
placeholder="请选择流程发起人"
clearable
class="!w-390px"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item> -->
<el-form-item
label="所属流程"
class="bold-label"
class="font-bold"
label-position="top"
prop="processDefinitionKey"
>
<el-input
<el-select
v-model="queryParams.processDefinitionKey"
placeholder="请输入流程定义的标识"
placeholder="请选择流程定义"
clearable
@keyup.enter="handleQuery"
class="!w-390px"
@change="handleQuery"
>
<el-option
v-for="item in processDefinitionList"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-form-item>
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
<el-form-item label="发起时间" class="font-bold" label-position="top" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -115,11 +103,12 @@
class="!w-240px"
/>
</el-form-item>
<!-- TODO tuituiji参考钉钉1按照清空取消确认排序2右对齐3确认增加 primary -->
<el-form-item class="bold-label" label-position="top">
<el-button @click="handleQuery"> 确认</el-button>
<el-button @click="showPopover = false"> 取消</el-button>
<el-form-item class="font-bold" label-position="top">
<div class="flex justify-end w-full">
<el-button @click="resetQuery">清空</el-button>
<el-button @click="showPopover = false">取消</el-button>
<el-button type="primary" @click="handleQuery">确认</el-button>
</div>
</el-form-item>
</el-popover>
</el-form-item>
@ -130,7 +119,7 @@
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
<el-table-column label="摘要" prop="summary" min-width="180" fixed="left">
<el-table-column label="摘要" prop="summary" width="180" fixed="left">
<template #default="scope">
<div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0">
<div v-for="(item, index) in scope.row.summary" :key="index">
@ -146,12 +135,38 @@
min-width="100"
fixed="left"
/>
<!-- TODO @芋艿摘要 -->
<!-- TODO tuituiji参考钉钉1审批中时展示审批任务2非审批中展示状态 -->
<el-table-column label="流程状态" prop="status" width="120">
<el-table-column label="流程状态" prop="status" min-width="200">
<template #default="scope">
<!-- 审批中状态 -->
<template
v-if="
scope.row.status === BpmProcessInstanceStatus.RUNNING && scope.row.tasks?.length > 0
"
>
<!-- 单人审批 -->
<template v-if="scope.row.tasks.length === 1">
<span>
<el-button link type="primary" @click="handleDetail(scope.row)">
{{ scope.row.tasks[0].assigneeUser?.nickname }}
</el-button>
({{ scope.row.tasks[0].name }}) 审批中
</span>
</template>
<!-- 多人审批 -->
<template v-else>
<span>
<el-button link type="primary" @click="handleDetail(scope.row)">
{{ scope.row.tasks[0].assigneeUser?.nickname }}
</el-button>
{{ scope.row.tasks.length }} ({{ scope.row.tasks[0].name }})审批中
</span>
</template>
</template>
<!-- 非审批中状态 -->
<template v-else>
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
</template>
</template>
</el-table-column>
<el-table-column
label="发起时间"
@ -167,19 +182,6 @@
width="180"
:formatter="dateFormatter"
/>
<!--<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
<template #default="scope">
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
</template>
</el-table-column>
<el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
<template #default="scope">
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
<span>{{ task.name }}</span>
</el-button>
</template>
</el-table-column>
-->
<el-table-column label="操作" align="center" fixed="right" width="180">
<template #default="scope">
<el-button
@ -215,7 +217,6 @@
</ContentWrap>
</template>
<script lang="ts" setup>
// TODO @tuitujiList <Icon icon="ep:plus" class="mr-5px" /> RE:done & to check
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { ElMessageBox } from 'element-plus'
@ -223,6 +224,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import { ProcessInstanceVO } from '@/api/bpm/processInstance'
import * as DefinitionApi from '@/api/bpm/definition'
import { BpmProcessInstanceStatus } from '@/utils/constants'
defineOptions({ name: 'BpmProcessInstanceMy' })
@ -233,6 +235,7 @@ const { t } = useI18n() // 国际化
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const processDefinitionList = ref<any[]>([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -244,6 +247,7 @@ const queryParams = reactive({
})
const queryFormRef = ref() //
const categoryList = ref<CategoryVO[]>([]) //
const showPopover = ref(false) //
/** 查询列表 */
const getList = async () => {
@ -257,8 +261,6 @@ const getList = async () => {
}
}
const showPopover = ref(false)
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
@ -325,10 +327,7 @@ onActivated(() => {
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
//
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
})
</script>
<style>
.bold-label .el-form-item__label {
font-weight: bold; /* 将字体加粗 */
}
</style>

View File

@ -79,23 +79,28 @@
<el-button @click="showPopover = !showPopover">
<Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button>
</template>
<!-- <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
<el-form-item
label="所属流程"
class="font-bold"
label-position="top"
prop="processDefinitionKey"
>
<el-select
v-model="queryParams.category"
placeholder="请选择流程发起人"
v-model="queryParams.processDefinitionKey"
placeholder="请选择流程定义"
clearable
@change="handleQuery"
class="!w-390px"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
v-for="item in processDefinitionList"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-form-item> -->
</el-form-item>
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
@ -114,7 +119,6 @@
</el-form-item>
</el-popover>
</el-form-item>
</el-form>
</ContentWrap>
@ -122,9 +126,12 @@
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
<el-table-column label="摘要" prop="processInstance.summary" min-width="180">
<el-table-column label="摘要" prop="processInstance.summary" width="180">
<template #default="scope">
<div class="flex flex-col" v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0">
<div
class="flex flex-col"
v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0"
>
<div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
<el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
</div>
@ -170,7 +177,12 @@
{{ formatPast2(scope.row.durationInMillis) }}
</template>
</el-table-column>
<el-table-column align="center" label="流程编号" prop="processInstanceId" :show-overflow-tooltip="true" />
<el-table-column
align="center"
label="流程编号"
prop="processInstanceId"
:show-overflow-tooltip="true"
/>
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
<el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope">
@ -192,6 +204,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as DefinitionApi from '@/api/bpm/definition'
defineOptions({ name: 'BpmDoneTask' })
@ -200,17 +213,19 @@ const { push } = useRouter() // 路由
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const processDefinitionList = ref<any[]>([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
category: undefined,
status: undefined,
processDefinitionKey: '',
createTime: []
})
const queryFormRef = ref() //
const categoryList = ref<CategoryVO[]>([]) //
const showPopover = ref(false)
const showPopover = ref(false) //
/** 查询任务列表 */
const getList = async () => {
@ -251,5 +266,7 @@ const handleAudit = (row: any) => {
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
//
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
})
</script>

View File

@ -31,8 +31,7 @@
搜索
</el-button>
</el-form-item>
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
<el-form-item label="" prop="category" class="absolute right-130px">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
@ -48,9 +47,8 @@
/>
</el-select>
</el-form-item>
<!-- 高级筛选 -->
<el-form-item :style="{ position: 'absolute', right: '0px' }">
<el-form-item class="absolute right-0">
<el-popover
:visible="showPopover"
persistent
@ -62,24 +60,29 @@
<el-button @click="showPopover = !showPopover">
<Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button>
</template>
<!-- <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
<el-form-item
label="所属流程"
class="font-bold"
label-position="top"
prop="processDefinitionKey"
>
<el-select
v-model="queryParams.category"
placeholder="请选择流程发起人"
v-model="queryParams.processDefinitionKey"
placeholder="请选择流程定义"
clearable
@change="handleQuery"
class="!w-390px"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
v-for="item in processDefinitionList"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-form-item> -->
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
</el-form-item>
<el-form-item label="发起时间" class="font-bold" label-position="top" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -87,17 +90,18 @@
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
class="w-240px!"
/>
</el-form-item>
<el-form-item class="bold-label" label-position="top">
<el-button @click="handleQuery"> 确认</el-button>
<el-button @click="showPopover = false"> 取消</el-button>
<el-form-item class="font-bold" label-position="top">
<div class="flex justify-end w-full">
<el-button @click="resetQuery">清空</el-button>
<el-button @click="showPopover = false">取消</el-button>
<el-button type="primary" @click="handleQuery">确认</el-button>
</div>
</el-form-item>
</el-popover>
</el-form-item>
</el-form>
</ContentWrap>
@ -105,9 +109,12 @@
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
<el-table-column label="摘要" prop="processInstance.summary" min-width="180">
<el-table-column label="摘要" prop="processInstance.summary" width="180">
<template #default="scope">
<div class="flex flex-col" v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0">
<div
class="flex flex-col"
v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0"
>
<div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
<el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
</div>
@ -135,7 +142,12 @@
prop="createTime"
width="180"
/>
<el-table-column align="center" label="流程编号" prop="processInstanceId" :show-overflow-tooltip="true" />
<el-table-column
align="center"
label="流程编号"
prop="processInstanceId"
:show-overflow-tooltip="true"
/>
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
<el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope">
@ -157,6 +169,7 @@
import { dateFormatter } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as DefinitionApi from '@/api/bpm/definition'
defineOptions({ name: 'BpmTodoTask' })
@ -165,15 +178,18 @@ const { push } = useRouter() // 路由
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const processDefinitionList = ref<any[]>([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
category: undefined,
processDefinitionKey: '',
createTime: []
})
const queryFormRef = ref() //
const categoryList = ref<CategoryVO[]>([]) //
const showPopover = ref(false) //
/** 查询任务列表 */
const getList = async () => {
@ -187,8 +203,6 @@ const getList = async () => {
}
}
const showPopover = ref(false)
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
@ -216,5 +230,7 @@ const handleAudit = (row: any) => {
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
//
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
})
</script>

View File

@ -1,151 +0,0 @@
<template>
<div class="upload-container">
<!-- 标题 -->
<div class="title">
<div>选择数据源</div>
</div>
<!-- 数据源选择 -->
<div class="resource-btn" >导入已有文本</div>
<!-- 上传文件区域 -->
<el-form>
<div class="upload-section">
<div class="upload-label">上传文本文件</div>
<el-upload
class="upload-area"
action="#"
:file-list="fileList"
:on-remove="handleRemove"
:before-upload="beforeUpload"
list-type="text"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">拖拽文件至此或者 <em>选择文件</em></div>
<div class="el-upload__tip">
已支持 TXTMARKDOWNPDFHTMLXLSXXLSDOCXCSVEMLMSGPPTXPPTXMLEPUB每个文件不超过 15MB
</div>
</el-upload>
</div>
<!-- 下一步按钮 -->
<div class="next-button">
<el-button type="primary" :disabled="!fileList.length">下一步</el-button>
</div>
</el-form>
<!-- 知识库创建 -->
<div class="create-knowledge">
<el-link type="primary" underline>创建一个空知识库</el-link>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const fileList = ref([])
const handleRemove = (file, fileList) => {
console.log(file, fileList)
}
const beforeUpload = (file) => {
fileList.value.push(file)
return false
}
</script>
<style scoped lang="scss">
.upload-container {
width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
border: 1px solid #ebebeb;
}
.title {
font-size: 22px;
font-weight: bold;
}
.resource-btn {
margin-top: 20px;
border-radius: 10px;
cursor: pointer;
width: 150px;
border: 1.5px solid #528bff;
padding: 10px;
text-align: center;
font-weight: 500;
font-size: 14px;
line-height: 30px;
color: #101828;
}
.upload-section {
margin: 20px 0;
padding-top: 10px;
}
.upload-label {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
color: #303133;
}
.upload-area {
margin-top: 10px;
border: 1px dashed #d9d9d9;
padding: 40px;
text-align: center;
background-color: #f5f7fa;
border-radius: 8px;
}
.el-upload__text em {
color: #409eff;
cursor: pointer;
}
.el-upload__tip {
margin-top: 10px;
font-size: 12px;
color: #909399;
}
.next-button {
text-align: left;
margin-top: 20px;
}
.create-knowledge {
text-align: left;
margin-top: 20px;
}
.el-form-item {
margin-bottom: 0;
}
.source-radio-group {
display: flex;
justify-content: space-between;
}
.el-radio-button {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
padding: 10px 20px;
}
.el-radio-button .el-icon {
margin-right: 8px;
}
</style>

View File

@ -1,168 +0,0 @@
<template>
<el-row>
<!-- Left Section -->
<el-col :span="12">
<el-card>
<!-- 分段设置 -->
<el-form>
<el-form-item label="分段设置">
<el-radio-group v-model="segmentSetting">
<el-radio label="自动分段与清洗">自动分段与清洗</el-radio>
<el-radio label="自定义">自定义</el-radio>
</el-radio-group>
</el-form-item>
<!-- 索引方式 -->
<el-form-item label="索引方式">
<el-radio-group v-model="indexingMethod">
<el-radio label="高质量">高质量</el-radio>
<el-radio label="经济">经济</el-radio>
</el-radio-group>
</el-form-item>
<!-- Embedding 模型 -->
<el-form-item label="Embedding 模型">
<el-select v-model="embeddingModel" placeholder="Select Embedding Model">
<el-option label="text-embedding-3-large" value="text-embedding-3-large"/>
</el-select>
</el-form-item>
<!-- 检索设置 -->
<el-form-item label="检索设置">
<el-card style="width: 400px;">
<div class="card-header">
<span>向量检索</span>
</div>
<el-slider v-model="topK" :min="1" :max="10" label="Top K"/>
<el-slider v-model="scoreThreshold" :min="0" :max="1" step="0.1" label="Score 阈值"/>
</el-card>
<el-card style="width: 400px;">
<div class="card-header">
<span>全文检索</span>
</div>
<el-slider v-model="topK" :min="1" :max="10" label="Top K"/>
<el-slider v-model="scoreThreshold" :min="0" :max="1" step="0.1" label="Score 阈值"/>
</el-card>
<el-card style="width: 400px;">
<div class="card-header">
<span>混合检索</span>
</div>
<el-slider v-model="topK" :min="1" :max="10" label="Top K"/>
<el-slider v-model="scoreThreshold" :min="0" :max="1" step="0.1" label="Score 阈值"/>
</el-card>
</el-form-item>
</el-form>
</el-card>
</el-col>
<!-- Right Section: 分段预览 -->
<el-col :span="9">
<el-card shadow="never">
<div class="previews-title">分段预览</div>
<template v-for="(segment, index) in segmentPreviews" :key="index">
<div class="segment-preview">
<div class="title">
<div class="left">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4.74999 1.5L3.24999 10.5M8.74998 1.5L7.24998 10.5M10.25 4H1.75M9.75 8H1.25"
stroke="#98A2B3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="id">{{ segment.number }}</span>
</div>
<div class="right">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4 3.5H8M6 3.5V8.5M3.9 10.5H8.1C8.94008 10.5 9.36012 10.5 9.68099 10.3365C9.96323 10.1927 10.1927 9.96323 10.3365 9.68099C10.5 9.36012 10.5 8.94008 10.5 8.1V3.9C10.5 3.05992 10.5 2.63988 10.3365 2.31901C10.1927 2.03677 9.96323 1.8073 9.68099 1.66349C9.36012 1.5 8.94008 1.5 8.1 1.5H3.9C3.05992 1.5 2.63988 1.5 2.31901 1.66349C2.03677 1.8073 1.8073 2.03677 1.66349 2.31901C1.5 2.63988 1.5 3.05992 1.5 3.9V8.1C1.5 8.94008 1.5 9.36012 1.66349 9.68099C1.8073 9.96323 2.03677 10.1927 2.31901 10.3365C2.63988 10.5 3.05992 10.5 3.9 10.5Z"
stroke="#667085" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="char-size">7777 字符</span>
</div>
</div>
<div class="content">{{ segment.text }}</div>
</div>
</template>
</el-card>
</el-col>
</el-row>
</template>
<script setup>
import {ref} from 'vue';
// Reactive variables for form control
const segmentSetting = ref('自动分段与清洗');
const indexingMethod = ref('高质量');
const embeddingModel = ref('text-embedding-3-large');
const directionalSearch = ref(true);
const topK = ref(3);
const scoreThreshold = ref(0.5);
// Mock data for segment previews
const segmentPreviews = ref([
{number: '001', text: "同步obs模型...'UAE-large-V1'"},
{number: '002', text: "同步obs模型...'plip'"},
{number: '003', text: "同步obs模型...'phoBERT-base-v2'"},
{number: '004', text: "同步obs模型...'lama3-bb-bnb-4bit'"},
{number: '005', text: "同步obs模型...'t5-base-split-and-rephrase'"}
]);
</script>
<style scoped lang="scss">
/* Add any custom styles here */
.previews-title {
font-size: 18px;
font-weight: 500;
}
.segment-preview {
background-color: rgba(228, 228, 228, 0.38);
border-radius: 10px;
padding: 15px;
margin-top: 15px;
.title {
display: flex;
justify-content: space-between;
.left {
border-right: 5px;
font-size: 13px;
font-style: italic;
font-weight: 500;
color: #676767;
box-sizing: border-box;
align-items: center;
.id {
margin-left: 5px;
}
}
.right {
display: flex;
flex-direction: row;
align-items: center;
.char-size {
margin-left: 5px;
font-size: 13px;
color: rgba(57, 57, 57, 0.66);
}
}
}
.content {
margin-top: 10px;
font-size: 15px;
font-weight: 500;
}
}
</style>

View File

@ -1,152 +0,0 @@
<template>
<div class="knowledge-base-container">
<div class="card-container">
<el-card class="create-card" shadow="hover">
<div class="create-content">
<el-icon class="create-icon"><Plus /></el-icon>
<span class="create-text">创建知识库</span>
</div>
<div class="create-footer">
导入您自己的文本数据或通过 Webhook 实时写入数据以增强 LLM 的上下文
</div>
</el-card>
<el-card class="document-card" shadow="hover" v-for="index in 4" :key="index">
<div class="document-header">
<el-icon><Folder /></el-icon>
<span>接口鉴权示例代码.md</span>
</div>
<div class="document-info">
<el-tag size="small">1 文档</el-tag>
<el-tag size="small" type="info">5 千字符</el-tag>
<el-tag size="small" type="warning">0 关联应用</el-tag>
</div>
<p class="document-description">
useful for when you want to answer queries about the 接口鉴权示例代码.md
</p>
</el-card>
</div>
<div class="pagination-container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30, 40]"
:small="false"
:disabled="false"
:background="true"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Folder, Plus } from '@element-plus/icons-vue'
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(100) // 100
const handleSizeChange = (val) => {
console.log(`每页 ${val}`)
}
const handleCurrentChange = (val) => {
console.log(`当前页: ${val}`)
}
</script>
<style scoped>
.knowledge-base-container {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
position: absolute;
padding: 20px;
margin: 0 auto;
display: flex;
flex-direction: column;
top: 0;
bottom: 40px;
width: 100%;
}
.card-container {
display: flex;
flex-wrap: wrap; /* Enable wrapping */
gap: 20px;
margin-bottom: auto; /* Pushes pagination to the bottom */
}
.create-card, .document-card {
flex: 1 1 360px; /* Allow cards to grow and shrink */
min-width: 0;
max-width: 400px;
border-radius: 10px;
cursor: pointer;
}
.create-card {
background-color: rgba(168, 168, 168, 0.22);
}
.create-card:hover {
background-color: #fff;
}
.create-content {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.create-icon {
font-size: 24px;
color: #409EFF;
}
.create-text {
font-size: 18px;
font-weight: bold;
color: #303133;
}
.create-footer {
font-size: 14px;
color: #909399;
line-height: 1.5;
}
.document-header {
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
}
.document-info {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.document-description {
color: #606266;
font-size: 14px;
line-height: 1.5;
}
.pagination-container {
position: absolute;
width: 100%;
bottom: 0;
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>