Commit d2b99331 by 芋道源码 Committed by Gitee

!735 2.4.2:工作流的更新

Merge pull request !735 from 芋道源码/feature/bpm
parents b63179ef b452b08a
...@@ -114,7 +114,8 @@ const include = [ ...@@ -114,7 +114,8 @@ const include = [
'element-plus/es/components/segmented/style/css', 'element-plus/es/components/segmented/style/css',
'@element-plus/icons-vue', '@element-plus/icons-vue',
'element-plus/es/components/footer/style/css', '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'] const exclude = ['@iconify/json']
......
...@@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => { ...@@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => {
params params
}) })
} }
export const getSimpleProcessDefinitionList = async () => {
return await request.get({
url: '/bpm/process-definition/simple-list'
})
}
...@@ -90,7 +90,12 @@ export const getProcessInstanceCopyPage = async (params: any) => { ...@@ -90,7 +90,12 @@ export const getProcessInstanceCopyPage = async (params: any) => {
// 获取审批详情 // 获取审批详情
export const getApprovalDetail = 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 })
} }
// 获取表单字段权限 // 获取表单字段权限
......
<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>
\ No newline at end of file
<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>
\ No newline at end of file
...@@ -15,6 +15,12 @@ ...@@ -15,6 +15,12 @@
</div> </div>
<div class="handler-item-text">审批人</div> <div class="handler-item-text">审批人</div>
</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" @click="addNode(NodeType.COPY_TASK_NODE)">
<div class="handler-item-icon copy"> <div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span> <span class="iconfont icon-size icon-copy"></span>
...@@ -57,7 +63,13 @@ ...@@ -57,7 +63,13 @@
</div> </div>
<div class="handler-item-text">触发器</div> <div class="handler-item-text">触发器</div>
</div> </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> <template #reference>
<div class="add-icon"><Icon icon="ep:plus" /></div> <div class="add-icon"><Icon icon="ep:plus" /></div>
</template> </template>
...@@ -78,7 +90,7 @@ import { ...@@ -78,7 +90,7 @@ import {
SimpleFlowNode, SimpleFlowNode,
DEFAULT_CONDITION_GROUP_VALUE DEFAULT_CONDITION_GROUP_VALUE
} from './consts' } from './consts'
import {generateUUID} from '@/utils' import { generateUUID } from '@/utils'
defineOptions({ defineOptions({
name: 'NodeHandler' name: 'NodeHandler'
...@@ -114,13 +126,13 @@ const addNode = (type: number) => { ...@@ -114,13 +126,13 @@ const addNode = (type: number) => {
} }
popoverShow.value = false popoverShow.value = false
if (type === NodeType.USER_TASK_NODE) { if (type === NodeType.USER_TASK_NODE || type === NodeType.TRANSACTOR_NODE) {
const id = 'Activity_' + generateUUID() const id = 'Activity_' + generateUUID()
const data: SimpleFlowNode = { const data: SimpleFlowNode = {
id: id, id: id,
name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string, name: NODE_DEFAULT_NAME.get(type) as string,
showText: '', showText: '',
type: NodeType.USER_TASK_NODE, type: type,
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
// 超时处理 // 超时处理
rejectHandler: { rejectHandler: {
...@@ -277,6 +289,31 @@ const addNode = (type: number) => { ...@@ -277,6 +289,31 @@ const addNode = (type: number) => {
} }
emits('update:childNode', data) 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> </script>
......
...@@ -6,7 +6,11 @@ ...@@ -6,7 +6,11 @@
/> />
<!-- 审批节点 --> <!-- 审批节点 -->
<UserTaskNode <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" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
@find:parent-node="findFromParentNode" @find:parent-node="findFromParentNode"
...@@ -50,12 +54,18 @@ ...@@ -50,12 +54,18 @@
:flow-node="currentNode" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
/> />
<!-- 触发器节点 --> <!-- 触发器节点 -->
<TriggerNode <TriggerNode
v-if="currentNode && currentNode.type === NodeType.TRIGGER_NODE" v-if="currentNode && currentNode.type === NodeType.TRIGGER_NODE"
:flow-node="currentNode" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
/> />
<!-- 子流程节点 -->
<ChildProcessNode
v-if="currentNode && currentNode.type === NodeType.CHILD_PROCESS_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 递归显示孩子节点 --> <!-- 递归显示孩子节点 -->
<ProcessNodeTree <ProcessNodeTree
v-if="currentNode && currentNode.childNode" v-if="currentNode && currentNode.childNode"
...@@ -81,6 +91,7 @@ import InclusiveNode from './nodes/InclusiveNode.vue' ...@@ -81,6 +91,7 @@ import InclusiveNode from './nodes/InclusiveNode.vue'
import DelayTimerNode from './nodes/DelayTimerNode.vue' import DelayTimerNode from './nodes/DelayTimerNode.vue'
import RouterNode from './nodes/RouterNode.vue' import RouterNode from './nodes/RouterNode.vue'
import TriggerNode from './nodes/TriggerNode.vue' import TriggerNode from './nodes/TriggerNode.vue'
import ChildProcessNode from './nodes/ChildProcessNode.vue'
import { SimpleFlowNode, NodeType } from './consts' import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
defineOptions({ defineOptions({
......
<template> <template>
<div class="simple-process-model-container position-relative"> <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-row type="flex" justify="end">
<el-button-group key="scale-control" size="default"> <el-button-group key="scale-control" size="default">
<el-button v-if="!readonly" size="default" @click="exportJson"> <el-button v-if="!readonly" size="default" @click="exportJson">
...@@ -23,10 +23,19 @@ ...@@ -23,10 +23,19 @@
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" /> <el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button> <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" /> <el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
<el-button size="default" @click="resetPosition">重置</el-button>
</el-button-group> </el-button-group>
</el-row> </el-row>
</div> </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" /> <ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
</div> </div>
</div> </div>
...@@ -76,11 +85,51 @@ const emits = defineEmits<{ ...@@ -76,11 +85,51 @@ const emits = defineEmits<{
const processNodeTree = useWatchNode(props) const processNodeTree = useWatchNode(props)
provide('readonly', props.readonly) provide('readonly', props.readonly)
// TODO 可优化:拖拽有点卡顿
/** 拖拽、放大缩小等操作 */
let scaleValue = ref(100) let scaleValue = ref(100)
const MAX_SCALE_VALUE = 200 const MAX_SCALE_VALUE = 200
const MIN_SCALE_VALUE = 50 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 = () => { const zoomIn = () => {
if (scaleValue.value == MAX_SCALE_VALUE) { if (scaleValue.value == MAX_SCALE_VALUE) {
return return
...@@ -88,7 +137,6 @@ const zoomIn = () => { ...@@ -88,7 +137,6 @@ const zoomIn = () => {
scaleValue.value += 10 scaleValue.value += 10
} }
// 缩小
const zoomOut = () => { const zoomOut = () => {
if (scaleValue.value == MIN_SCALE_VALUE) { if (scaleValue.value == MIN_SCALE_VALUE) {
return return
...@@ -100,20 +148,15 @@ const processReZoom = () => { ...@@ -100,20 +148,15 @@ const processReZoom = () => {
scaleValue.value = 100 scaleValue.value = 100
} }
const resetPosition = () => {
currentX.value = initialX.value
currentY.value = initialY.value
}
/** 校验节点设置 */
const errorDialogVisible = ref(false) const errorDialogVisible = ref(false)
let errorNodes: SimpleFlowNode[] = [] 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[]) => { const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
if (node) { if (node) {
const { type, showText, conditionNodes } = node const { type, showText, conditionNodes } = node
...@@ -193,6 +236,30 @@ const importLocalFile = () => { ...@@ -193,6 +236,30 @@ const importLocalFile = () => {
} }
} }
} }
// 在组件初始化时记录初始位置
onMounted(() => {
initialX.value = currentX.value
initialY.value = currentY.value
})
</script> </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>
...@@ -45,4 +45,3 @@ watch( ...@@ -45,4 +45,3 @@ watch(
provide('tasks', approveTasks) provide('tasks', approveTasks)
provide('processInstance', currentProcessInstance) provide('processInstance', currentProcessInstance)
</script> </script>
p
...@@ -24,6 +24,11 @@ export enum NodeType { ...@@ -24,6 +24,11 @@ export enum NodeType {
COPY_TASK_NODE = 12, COPY_TASK_NODE = 12,
/** /**
* 办理人节点
*/
TRANSACTOR_NODE = 13,
/**
* 延迟器节点 * 延迟器节点
*/ */
DELAY_TIMER_NODE = 14, DELAY_TIMER_NODE = 14,
...@@ -34,6 +39,11 @@ export enum NodeType { ...@@ -34,6 +39,11 @@ export enum NodeType {
TRIGGER_NODE = 15, TRIGGER_NODE = 15,
/** /**
* 子流程节点
*/
CHILD_PROCESS_NODE = 20,
/**
* 条件节点 * 条件节点
*/ */
CONDITION_NODE = 50, CONDITION_NODE = 50,
...@@ -123,6 +133,8 @@ export interface SimpleFlowNode { ...@@ -123,6 +133,8 @@ export interface SimpleFlowNode {
reasonRequire?: boolean reasonRequire?: boolean
// 触发器设置 // 触发器设置
triggerSetting?: TriggerSetting triggerSetting?: TriggerSetting
// 子流程
childProcessSetting?: ChildProcessSetting
} }
// 候选人策略枚举 ( 用于审批节点。抄送节点 ) // 候选人策略枚举 ( 用于审批节点。抄送节点 )
export enum CandidateStrategy { export enum CandidateStrategy {
...@@ -151,6 +163,10 @@ export enum CandidateStrategy { ...@@ -151,6 +163,10 @@ export enum CandidateStrategy {
*/ */
USER = 30, USER = 30,
/** /**
* 审批人自选
*/
APPROVE_USER_SELECT = 34,
/**
* 发起人自选 * 发起人自选
*/ */
START_USER_SELECT = 35, START_USER_SELECT = 35,
...@@ -506,6 +522,8 @@ NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人') ...@@ -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.DELAY_TIMER_NODE, '请设置延迟器')
NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点') NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点')
NODE_DEFAULT_TEXT.set(NodeType.TRIGGER_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>() export const NODE_DEFAULT_NAME = new Map<number, string>()
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人') NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
...@@ -515,15 +533,20 @@ NODE_DEFAULT_NAME.set(NodeType.START_USER_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.DELAY_TIMER_NODE, '延迟器')
NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支') NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支')
NODE_DEFAULT_NAME.set(NodeType.TRIGGER_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[] = [ export const CANDIDATE_STRATEGY: DictDataVO[] = [
{ label: '指定成员', value: CandidateStrategy.USER }, { label: '指定成员', value: CandidateStrategy.USER },
{ label: '指定角色', value: CandidateStrategy.ROLE }, { label: '指定角色', value: CandidateStrategy.ROLE },
{ label: '指定岗位', value: CandidateStrategy.POST },
{ label: '部门成员', value: CandidateStrategy.DEPT_MEMBER }, { label: '部门成员', value: CandidateStrategy.DEPT_MEMBER },
{ label: '部门负责人', value: CandidateStrategy.DEPT_LEADER }, { label: '部门负责人', value: CandidateStrategy.DEPT_LEADER },
{ label: '连续多级部门负责人', value: CandidateStrategy.MULTI_LEVEL_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.START_USER_SELECT },
{ label: '审批人自选', value: CandidateStrategy.APPROVE_USER_SELECT },
{ label: '发起人本人', value: CandidateStrategy.START_USER }, { label: '发起人本人', value: CandidateStrategy.START_USER },
{ label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER }, { label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
{ label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER }, { label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
...@@ -627,6 +650,16 @@ export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ ...@@ -627,6 +650,16 @@ export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
{ id: OperationButtonType.RETURN, displayName: '退回', enable: true } { 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[] = [ export const START_USER_BUTTON_SETTING: ButtonSetting[] = [
{ id: OperationButtonType.APPROVE, displayName: '提交', enable: true }, { id: OperationButtonType.APPROVE, displayName: '提交', enable: true },
...@@ -717,7 +750,7 @@ export type RouterSetting = { ...@@ -717,7 +750,7 @@ export type RouterSetting = {
export type TriggerSetting = { export type TriggerSetting = {
type: TriggerTypeEnum type: TriggerTypeEnum
httpRequestSetting?: HttpRequestTriggerSetting httpRequestSetting?: HttpRequestTriggerSetting
normalFormSetting?: NormalFormTriggerSetting formSettings?: FormTriggerSetting[]
} }
/** /**
...@@ -729,9 +762,17 @@ export enum TriggerTypeEnum { ...@@ -729,9 +762,17 @@ export enum TriggerTypeEnum {
*/ */
HTTP_REQUEST = 1, HTTP_REQUEST = 1,
/** /**
* 更新流程表单触发器 * 接收 HTTP 回调请求触发器
*/ */
UPDATE_NORMAL_FORM = 2 // TODO @jason:FORM_UPDATE? HTTP_CALLBACK = 2,
/**
* 表单数据更新触发器
*/
FORM_UPDATE = 10,
/**
* 表单数据删除触发器
*/
FORM_DELETE = 11
} }
/** /**
...@@ -751,12 +792,110 @@ export type HttpRequestTriggerSetting = { ...@@ -751,12 +792,110 @@ export type HttpRequestTriggerSetting = {
/** /**
* 流程表单触发器配置结构定义 * 流程表单触发器配置结构定义
*/ */
export type NormalFormTriggerSetting = { export type FormTriggerSetting = {
// 更新表单字段 // 条件类型
conditionType?: ConditionType
// 条件表达式
conditionExpression?: string
// 条件组
conditionGroups?: ConditionGroup
// 更新表单字段配置
updateFormFields?: Record<string, any> updateFormFields?: Record<string, any>
// 删除表单字段配置
deleteFields?: string[]
} }
export const TRIGGER_TYPES: DictDataVO[] = [ export const TRIGGER_TYPES: DictDataVO[] = [
{ label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST }, { label: '发送 HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST },
{ label: '修改表单数据', value: TriggerTypeEnum.UPDATE_NORMAL_FORM } { 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 }
] ]
...@@ -15,7 +15,10 @@ import { ...@@ -15,7 +15,10 @@ import {
AssignEmptyHandlerType, AssignEmptyHandlerType,
FieldPermissionType, FieldPermissionType,
HttpRequestParam, HttpRequestParam,
ProcessVariableEnum ProcessVariableEnum,
ConditionType,
ConditionGroup,
COMPARISON_OPERATORS
} from './consts' } from './consts'
import { parseFormFields } from '@/components/FormCreate/src/utils' import { parseFormFields } from '@/components/FormCreate/src/utils'
...@@ -201,7 +204,7 @@ export function useNodeForm(nodeType: NodeType) { ...@@ -201,7 +204,7 @@ export function useNodeForm(nodeType: NodeType) {
const deptTreeOptions = inject('deptTree', ref()) // 部门树 const deptTreeOptions = inject('deptTree', ref()) // 部门树
const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段 const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
const configForm = ref<UserTaskFormType | CopyTaskFormType>() const configForm = ref<UserTaskFormType | CopyTaskFormType>()
if (nodeType === NodeType.USER_TASK_NODE) { if (nodeType === NodeType.USER_TASK_NODE || nodeType === NodeType.TRANSACTOR_NODE) {
configForm.value = { configForm.value = {
candidateStrategy: CandidateStrategy.USER, candidateStrategy: CandidateStrategy.USER,
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
...@@ -307,6 +310,11 @@ export function useNodeForm(nodeType: NodeType) { ...@@ -307,6 +310,11 @@ export function useNodeForm(nodeType: NodeType) {
showText = `表单内部门负责人` showText = `表单内部门负责人`
} }
// 审批人自选
if (configForm.value?.candidateStrategy === CandidateStrategy.APPROVE_USER_SELECT) {
showText = `审批人自选`
}
// 发起人自选 // 发起人自选
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) { if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
showText = `发起人自选` showText = `发起人自选`
...@@ -543,6 +551,66 @@ export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): stri ...@@ -543,6 +551,66 @@ export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): stri
if (taskStatus === TaskStatusEnum.CANCEL) { if (taskStatus === TaskStatusEnum.CANCEL) {
return 'status-cancel' return 'status-cancel'
} }
return '' 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
}
...@@ -43,15 +43,12 @@ ...@@ -43,15 +43,12 @@
</el-drawer> </el-drawer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { import { SimpleFlowNode, ConditionType } from '../consts'
SimpleFlowNode,
ConditionType,
COMPARISON_OPERATORS,
} from '../consts'
import { getDefaultConditionNodeName } from '../utils' import { getDefaultConditionNodeName } from '../utils'
import { useFormFieldsAndStartUser } from '../node' import { useFormFieldsAndStartUser, getConditionShowText } from '../node'
import Condition from './components/Condition.vue' import Condition from './components/Condition.vue'
const message = useMessage() // 消息弹窗 import { cloneDeep } from 'lodash-es'
defineOptions({ defineOptions({
name: 'ConditionNodeConfig' name: 'ConditionNodeConfig'
}) })
...@@ -67,9 +64,51 @@ const props = defineProps({ ...@@ -67,9 +64,51 @@ const props = defineProps({
}) })
const settingVisible = ref(false) const settingVisible = ref(false)
const currentNode = ref<SimpleFlowNode>(props.conditionNode) 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 = () => { 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 settingVisible.value = true
} }
...@@ -93,8 +132,6 @@ const blurEvent = () => { ...@@ -93,8 +132,6 @@ const blurEvent = () => {
getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow) getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow)
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
// 关闭 // 关闭
...@@ -111,84 +148,41 @@ const handleClose = async (done: (cancel?: boolean) => void) => { ...@@ -111,84 +148,41 @@ const handleClose = async (done: (cancel?: boolean) => void) => {
} }
} }
/** 保存配置 */
const fieldOptions = useFormFieldsAndStartUser() // 流程表单字段和发起人字段
const conditionRef = ref() const conditionRef = ref()
// 保存配置
const saveConfig = async () => { const saveConfig = async () => {
if (!currentNode.value.conditionSetting?.defaultFlow) { if (!currentNode.value.conditionSetting?.defaultFlow) {
// 校验表单 // 校验表单
const valid = await conditionRef.value.validate() const valid = await conditionRef.value.validate()
if (!valid) return false if (!valid) return false
const showText = getShowText() const showText = getConditionShowText(
condition.value?.conditionType,
condition.value?.conditionExpression,
condition.value.conditionGroups,
fieldOptions
)
if (!showText) { if (!showText) {
return false return false
} }
currentNode.value.showText = showText currentNode.value.showText = showText
currentNode.value.conditionSetting!.conditionType = condition.value?.conditionType // 使用 cloneDeep 进行深拷贝
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) { currentNode.value.conditionSetting = cloneDeep({
currentNode.value.conditionSetting.conditionGroups = undefined ...currentNode.value.conditionSetting,
currentNode.value.conditionSetting.conditionExpression = condition.value?.conditionExpression conditionType: condition.value?.conditionType,
} conditionExpression:
if (currentNode.value.conditionSetting!.conditionType === ConditionType.RULE) { condition.value?.conditionType === ConditionType.EXPRESSION
currentNode.value.conditionSetting!.conditionExpression = undefined ? condition.value?.conditionExpression
currentNode.value.conditionSetting!.conditionGroups = condition.value?.conditionGroups : undefined,
} conditionGroups:
condition.value?.conditionType === ConditionType.RULE
? condition.value?.conditionGroups
: undefined
})
} }
settingVisible.value = false settingVisible.value = false
return true 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
:key="idx" :key="idx"
:label="item.title" :label="item.title"
:value="item.field" :value="item.field"
:disabled ="!item.required" :disabled="!item.required"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -149,7 +149,7 @@ ...@@ -149,7 +149,7 @@
:key="idx" :key="idx"
:label="item.title" :label="item.title"
:value="item.field" :value="item.field"
:disabled ="!item.required" :disabled="!item.required"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -195,9 +195,15 @@ ...@@ -195,9 +195,15 @@
<div class="field-permit-title"> <div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div> <div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles"> <div class="other-titles">
<span class="setting-title-label">只读</span> <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
<span class="setting-title-label">可编辑</span> 只读
<span class="setting-title-label">隐藏</span> </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> </div>
<div <div
...@@ -368,6 +374,18 @@ const showCopyTaskNodeConfig = (node: SimpleFlowNode) => { ...@@ -368,6 +374,18 @@ const showCopyTaskNodeConfig = (node: SimpleFlowNode) => {
getNodeConfigFormFields(node.fieldsPermission) 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 }) // 暴露方法给父组件 defineExpose({ openDrawer, showCopyTaskNodeConfig }) // 暴露方法给父组件
</script> </script>
......
...@@ -36,7 +36,8 @@ ...@@ -36,7 +36,8 @@
placement="top" placement="top"
:content="getUserNicknames(startUserIds)" :content="getUserNicknames(startUserIds)"
> >
{{ getUserNicknames(startUserIds.slice(0,2)) }} 等 {{ startUserIds.length }} 人可发起流程 {{ getUserNicknames(startUserIds.slice(0, 2)) }} 等
{{ startUserIds.length }} 人可发起流程
</el-tooltip> </el-tooltip>
</el-text> </el-text>
</el-tab-pane> </el-tab-pane>
...@@ -46,9 +47,15 @@ ...@@ -46,9 +47,15 @@
<div class="field-permit-title"> <div class="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div> <div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles"> <div class="other-titles">
<span class="setting-title-label">只读</span> <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
<span class="setting-title-label">可编辑</span> 只读
<span class="setting-title-label">隐藏</span> </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> </div>
<div <div
...@@ -157,6 +164,17 @@ const showStartUserNodeConfig = (node: SimpleFlowNode) => { ...@@ -157,6 +164,17 @@ const showStartUserNodeConfig = (node: SimpleFlowNode) => {
getNodeConfigFormFields(node.fieldsPermission) 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 }) // 暴露方法给父组件 defineExpose({ openDrawer, showStartUserNodeConfig }) // 暴露方法给父组件
</script> </script>
......
<template> <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="flex pt-2" v-for="(item, index) in props.header" :key="index">
<div class="mr-2"> <div class="mr-2">
<el-form-item <el-form-item
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
<Icon icon="ep:plus" class="mr-5px" />添加一行 <Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button> </el-button>
</el-form-item> </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="flex pt-2" v-for="(item, index) in props.body" :key="index">
<div class="mr-2"> <div class="mr-2">
<el-form-item <el-form-item
...@@ -141,7 +141,11 @@ ...@@ -141,7 +141,11 @@
</el-form-item> </el-form-item>
</template> </template>
<script setup lang="ts"> <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' import { useFormFieldsAndStartUser } from '../../node'
defineOptions({ defineOptions({
name: 'HttpRequestParamSetting' name: 'HttpRequestParamSetting'
......
<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>
<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>
...@@ -9,7 +9,14 @@ ...@@ -9,7 +9,14 @@
]" ]"
> >
<div class="node-title-container"> <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 <input
v-if="!readonly && showInput" v-if="!readonly && showInput"
type="text" type="text"
...@@ -28,7 +35,7 @@ ...@@ -28,7 +35,7 @@
{{ currentNode.showText }} {{ currentNode.showText }}
</div> </div>
<div class="node-text" v-else> <div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }} {{ NODE_DEFAULT_TEXT.get(currentNode.type) }}
</div> </div>
<Icon icon="ep:arrow-right-bold" v-if="!readonly" /> <Icon icon="ep:arrow-right-bold" v-if="!readonly" />
</div> </div>
......
...@@ -177,6 +177,18 @@ ...@@ -177,6 +177,18 @@
color: #ca3a31 color: #ca3a31
} }
.transactor {
color: #330099;
}
.child-process {
color: #996633;
}
.async-child-process {
color: #006666;
}
.handler-item-text { .handler-item-text {
margin-top: 4px; margin-top: 4px;
width: 80px; width: 80px;
...@@ -290,10 +302,22 @@ ...@@ -290,10 +302,22 @@
&.trigger-node { &.trigger-node {
color: #3373d2; color: #3373d2;
} }
&.router-node { &.router-node {
color: #ca3a31 color: #ca3a31
} }
&.transactor-task {
color: #330099;
}
&.child-process {
color: #996633;
}
&.async-child-process {
color: #006666;
}
} }
.node-title { .node-title {
...@@ -777,7 +801,7 @@ ...@@ -777,7 +801,7 @@
content: "\e7eb"; content: "\e7eb";
} }
.icon-handle:before { .icon-transactor:before {
content: "\e61c"; content: "\e61c";
} }
...@@ -792,3 +816,11 @@ ...@@ -792,3 +816,11 @@
.icon-parallel:before { .icon-parallel:before {
content: "\e688"; content: "\e688";
} }
.icon-async-child-process:before {
content: "\e6f2";
}
.icon-child-process:before {
content: "\e6c1";
}
...@@ -188,12 +188,8 @@ ...@@ -188,12 +188,8 @@
:scroll="true" :scroll="true"
max-height="600px" max-height="600px"
> >
<!-- append-to-body --> <div>
<div v-highlight> <pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
<code class="hljs">
<!-- 高亮代码块 -->
{{ previewResult }}
</code>
</div> </div>
</Dialog> </Dialog>
</div> </div>
...@@ -237,6 +233,8 @@ import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml' ...@@ -237,6 +233,8 @@ import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml'
// const eventName = reactive({ // const eventName = reactive({
// name: '' // name: ''
// }) // })
import hljs from 'highlight.js' // 导入代码高亮文件
import 'highlight.js/styles/github.css' // 导入代码高亮样式
defineOptions({ name: 'MyProcessDesigner' }) defineOptions({ name: 'MyProcessDesigner' })
...@@ -308,6 +306,18 @@ const props = defineProps({ ...@@ -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) provide('configGlobal', props)
let bpmnModeler: any = null let bpmnModeler: any = null
const defaultZoom = ref(1) const defaultZoom = ref(1)
......
...@@ -123,13 +123,19 @@ ...@@ -123,13 +123,19 @@
</div> </div>
<el-divider content-position="left">字段权限</el-divider> <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="field-permit-title">
<div class="setting-title-label first-title"> 字段名称 </div> <div class="setting-title-label first-title"> 字段名称 </div>
<div class="other-titles"> <div class="other-titles">
<span class="setting-title-label">只读</span> <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')"
<span class="setting-title-label">可编辑</span> >只读</span
<span class="setting-title-label">隐藏</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> </div>
<div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index"> <div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index">
...@@ -140,24 +146,30 @@ ...@@ -140,24 +146,30 @@
:value="FieldPermissionType.READ" :value="FieldPermissionType.READ"
size="large" size="large"
:label="FieldPermissionType.READ" :label="FieldPermissionType.READ"
><span></span @change="updateElementExtensions"
></el-radio> >
<span></span>
</el-radio>
</div> </div>
<div class="item-radio-wrap"> <div class="item-radio-wrap">
<el-radio <el-radio
:value="FieldPermissionType.WRITE" :value="FieldPermissionType.WRITE"
size="large" size="large"
:label="FieldPermissionType.WRITE" :label="FieldPermissionType.WRITE"
><span></span @change="updateElementExtensions"
></el-radio> >
<span></span>
</el-radio>
</div> </div>
<div class="item-radio-wrap"> <div class="item-radio-wrap">
<el-radio <el-radio
:value="FieldPermissionType.NONE" :value="FieldPermissionType.NONE"
size="large" size="large"
:label="FieldPermissionType.NONE" :label="FieldPermissionType.NONE"
><span></span @change="updateElementExtensions"
></el-radio> >
<span></span>
</el-radio>
</div> </div>
</el-radio-group> </el-radio-group>
</div> </div>
...@@ -165,12 +177,22 @@ ...@@ -165,12 +177,22 @@
<el-divider content-position="left">是否需要签名</el-divider> <el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable"> <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-form-item>
<el-divider content-position="left">审批意见</el-divider> <el-divider content-position="left">审批意见</el-divider>
<el-form-item prop="reasonRequire"> <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> </el-form-item>
</div> </div>
</template> </template>
...@@ -191,6 +213,7 @@ import { ...@@ -191,6 +213,7 @@ import {
} from '@/components/SimpleProcessDesignerV2/src/consts' } from '@/components/SimpleProcessDesignerV2/src/consts'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node' import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
import { BpmModelFormType } from '@/utils/constants'
defineOptions({ name: 'ElementCustomConfig4UserTask' }) defineOptions({ name: 'ElementCustomConfig4UserTask' })
const props = defineProps({ const props = defineProps({
...@@ -248,7 +271,6 @@ const resetCustomConfigList = () => { ...@@ -248,7 +271,6 @@ const resetCustomConfigList = () => {
bpmnElement.value.id, bpmnElement.value.id,
bpmnInstances().modeler bpmnInstances().modeler
) )
// 获取元素扩展属性 或者 创建扩展属性 // 获取元素扩展属性 或者 创建扩展属性
elExtensionElements.value = elExtensionElements.value =
bpmnElement.value.businessObject?.extensionElements ?? bpmnElement.value.businessObject?.extensionElements ??
...@@ -311,14 +333,13 @@ const resetCustomConfigList = () => { ...@@ -311,14 +333,13 @@ const resetCustomConfigList = () => {
} }
// 字段权限 // 字段权限
if (formType.value === 10) { if (formType.value === BpmModelFormType.NORMAL) {
const fieldsPermissionList = elExtensionElements.value.values?.filter( const fieldsPermissionList = elExtensionElements.value.values?.filter(
(ex) => ex.$type === `${prefix}:FieldsPermission` (ex) => ex.$type === `${prefix}:FieldsPermission`
) )
fieldsPermissionEl.value = [] fieldsPermissionEl.value = []
getNodeConfigFormFields() getNodeConfigFormFields()
// 由于默认添加了发起人元素,这里需要删掉 fieldsPermissionConfig.value = fieldsPermissionConfig.value
fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
fieldsPermissionConfig.value.forEach((element) => { fieldsPermissionConfig.value.forEach((element) => {
element.permission = element.permission =
fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1' fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
...@@ -487,6 +508,19 @@ function useButtonsSetting() { ...@@ -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[]>([]) // 用户列表 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
onMounted(async () => { onMounted(async () => {
// 获得用户列表 // 获得用户列表
...@@ -497,9 +531,9 @@ onMounted(async () => { ...@@ -497,9 +531,9 @@ onMounted(async () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.button-setting-pane { .button-setting-pane {
display: flex; display: flex;
flex-direction: column;
font-size: 14px;
margin-top: 8px; margin-top: 8px;
font-size: 14px;
flex-direction: column;
.button-setting-desc { .button-setting-desc {
padding-right: 8px; padding-right: 8px;
......
...@@ -255,32 +255,8 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -255,32 +255,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
}, },
{ {
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', path: 'manager/definition',
component: () => import('@/views/bpm/definition/index.vue'), component: () => import('@/views/bpm/model/definition/index.vue'),
name: 'BpmProcessDefinition', name: 'BpmProcessDefinition',
meta: { meta: {
noCache: true, noCache: true,
...@@ -356,7 +332,6 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -356,7 +332,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
}, },
{ {
// TODO @zws:1)建议,在加一个路由。然后标题是“复制流程”,这样体验会好点;2)复制出来的数据,在名字前面,加“副本 ”,和钉钉保持一致!
path: 'manager/model/:type/:id', path: 'manager/model/:type/:id',
component: () => import('@/views/bpm/model/form/index.vue'), component: () => import('@/views/bpm/model/form/index.vue'),
name: 'BpmModelUpdate', name: 'BpmModelUpdate',
......
...@@ -511,14 +511,14 @@ export function jsonParse(str: string) { ...@@ -511,14 +511,14 @@ export function jsonParse(str: string) {
/** /**
* 截取字符串 * 截取字符串
* *
* @param name * @param str 字符串
* @param start * @param start 开始位置
* @param end * @param end 结束位置
*/ */
export const sliceName = (name: string,start: number, end : number) => { export const subString = (str: string, start: number, end: number) => {
if (name.length > end) { if (str.length > end) {
return name.slice(start, end) return str.slice(start, end)
} }
return name return str
} }
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
</el-tooltip> </el-tooltip>
<el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" /> <el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
<div v-else class="flow-icon"> <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> </div>
{{ row.name }} {{ row.name }}
</div> </div>
...@@ -113,6 +113,11 @@ ...@@ -113,6 +113,11 @@
</el-text> </el-text>
</template> </template>
</el-table-column> </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"> <el-table-column label="表单信息" prop="formType" min-width="150">
<template #default="scope"> <template #default="scope">
<el-button <el-button
...@@ -260,6 +265,7 @@ ...@@ -260,6 +265,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
...@@ -271,9 +277,8 @@ import { checkPermi } from '@/utils/permission' ...@@ -271,9 +277,8 @@ import { checkPermi } from '@/utils/permission'
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { cloneDeep, isEqual } from 'lodash-es' import { cloneDeep, isEqual } from 'lodash-es'
import { useTagsView } from '@/hooks/web/useTagsView'
import { useDebounceFn } from '@vueuse/core' import { useDebounceFn } from '@vueuse/core'
import { sliceName } from '@/utils/index' import { subString } from '@/utils/index'
defineOptions({ name: 'BpmModel' }) defineOptions({ name: 'BpmModel' })
...@@ -583,8 +588,7 @@ const handleDeleteCategory = async () => { ...@@ -583,8 +588,7 @@ const handleDeleteCategory = async () => {
} catch {} } catch {}
} }
/** 添加流程模型弹窗 */ /** 添加/修改/复制流程模型弹窗 */
const tagsView = useTagsView()
const openModelForm = async (type: string, id?: number) => { const openModelForm = async (type: string, id?: number) => {
if (type === 'create') { if (type === 'create') {
await push({ name: 'BpmModelCreate' }) await push({ name: 'BpmModelCreate' })
...@@ -593,10 +597,6 @@ const openModelForm = async (type: string, id?: number) => { ...@@ -593,10 +597,6 @@ const openModelForm = async (type: string, id?: number) => {
name: 'BpmModelUpdate', name: 'BpmModelUpdate',
params: { id, type } params: { id, type }
}) })
// 设置标题
if (type === 'copy') {
tagsView.setTitle('复制流程')
}
} }
} }
......
...@@ -3,40 +3,60 @@ ...@@ -3,40 +3,60 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="定义编号" align="center" prop="id" width="400" /> <el-table-column label="定义编号" align="center" prop="id" min-width="250" />
<el-table-column label="流程名称" align="center" prop="name" width="200"> <el-table-column label="流程名称" align="center" prop="name" min-width="150" />
<template #default="scope"> <el-table-column label="流程图标" align="center" min-width="50">
<el-button type="primary" link @click="handleBpmnDetail(scope.row)"> <template #default="{ row }">
<span>{{ scope.row.name }}</span> <el-image v-if="row.icon" :src="row.icon" class="h-24px w-24pxrounded" />
</el-button> </template>
</el-table-column>
<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> </template>
</el-table-column> </el-table-column>
<el-table-column label="定义分类" align="center" prop="categoryName" width="100" /> <el-table-column label="流程类型" prop="modelType" min-width="120">
<el-table-column label="表单信息" align="center" prop="formType" width="200"> <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"> <template #default="scope">
<el-button <el-button
v-if="scope.row.formType === 10" v-if="scope.row.formType === BpmModelFormType.NORMAL"
type="primary" type="primary"
link link
@click="handleFormDetail(scope.row)" @click="handleFormDetail(scope.row)"
> >
<span>{{ scope.row.formName }}</span> <span>{{ scope.row.formName }}</span>
</el-button> </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> <span>{{ scope.row.formCustomCreatePath }}</span>
</el-button> </el-button>
<label v-else>暂无表单</label>
</template> </template>
</el-table-column> </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"> <template #default="scope">
<el-tag v-if="scope.row">v{{ scope.row.version }}</el-tag> <el-tag>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>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
...@@ -46,13 +66,18 @@ ...@@ -46,13 +66,18 @@
width="180" width="180"
:formatter="dateFormatter" :formatter="dateFormatter"
/> />
<el-table-column <el-table-column label="操作" align="center">
label="定义描述" <template #default="scope">
align="center" <el-button
prop="description" link
width="300" type="primary"
show-overflow-tooltip @click="openModelForm(scope.row.id)"
/> v-hasPermi="['bpm:model:update']"
>
恢复
</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
...@@ -67,18 +92,14 @@ ...@@ -67,18 +92,14 @@
<Dialog title="表单详情" v-model="formDetailVisible" width="800"> <Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" /> <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog> </Dialog>
<!-- 弹窗:流程模型图的预览 -->
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
<MyProcessViewer style="height: 700px" key="designer" :xml="bpmnXml" />
</Dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
import * as DefinitionApi from '@/api/bpm/definition' import * as DefinitionApi from '@/api/bpm/definition'
import { setConfAndFields2 } from '@/utils/formCreate' import { setConfAndFields2 } from '@/utils/formCreate'
import { DICT_TYPE } from '@/utils/dict'
import { BpmModelFormType } from '@/utils/constants'
defineOptions({ name: 'BpmProcessDefinition' }) defineOptions({ name: 'BpmProcessDefinition' })
...@@ -113,7 +134,7 @@ const formDetailPreview = ref({ ...@@ -113,7 +134,7 @@ const formDetailPreview = ref({
option: {} option: {}
}) })
const handleFormDetail = async (row: any) => { const handleFormDetail = async (row: any) => {
if (row.formType == 10) { if (row.formType == BpmModelFormType.NORMAL) {
// 设置表单 // 设置表单
setConfAndFields2(formDetailPreview, row.formConf, row.formFields) setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
// 弹窗打开 // 弹窗打开
...@@ -125,15 +146,12 @@ const handleFormDetail = async (row: any) => { ...@@ -125,15 +146,12 @@ const handleFormDetail = async (row: any) => {
} }
} }
/** 流程图的详情按钮操作 */ /** 恢复流程模型弹窗 */
const bpmnDetailVisible = ref(false) const openModelForm = async (id?: number) => {
const bpmnXml = ref('') await push({
const handleBpmnDetail = async (row: any) => { name: 'BpmModelUpdate',
// 设置可见 params: { id, type: 'definition' }
bpmnXml.value = '' })
bpmnDetailVisible.value = true
// 加载 BPMN XML
bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
} }
/** 初始化 **/ /** 初始化 **/
...@@ -141,3 +159,16 @@ onMounted(() => { ...@@ -141,3 +159,16 @@ onMounted(() => {
getList() getList()
}) })
</script> </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>
...@@ -140,6 +140,46 @@ ...@@ -140,6 +140,46 @@
</el-select> </el-select>
</div> </div>
</el-form-item> </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> </el-form>
</template> </template>
...@@ -149,6 +189,7 @@ import { BpmAutoApproveType, BpmModelFormType } from '@/utils/constants' ...@@ -149,6 +189,7 @@ import { BpmAutoApproveType, BpmModelFormType } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form' import * as FormApi from '@/api/bpm/form'
import { parseFormFields } from '@/components/FormCreate/src/utils' import { parseFormFields } from '@/components/FormCreate/src/utils'
import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts' import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts'
import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
const modelData = defineModel<any>() const modelData = defineModel<any>()
...@@ -205,6 +246,36 @@ const numberExample = computed(() => { ...@@ -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 formField = ref<Array<{ field: string; title: string }>>([])
const formFieldOptions4Title = computed(() => { const formFieldOptions4Title = computed(() => {
...@@ -264,6 +335,12 @@ const initData = () => { ...@@ -264,6 +335,12 @@ const initData = () => {
summary: [] summary: []
} }
} }
if (modelData.value.preProcessNotifySetting) {
preProcessNotifyEnable.value = true
}
if (modelData.value.postProcessNotifySetting) {
postProcessNotifyEnable.value = true
}
} }
defineExpose({ initData }) defineExpose({ initData })
......
...@@ -11,12 +11,12 @@ ...@@ -11,12 +11,12 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </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-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-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
</el-select> </el-select>
</el-form-item> </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 <el-input
v-model="modelData.formCustomCreatePath" v-model="modelData.formCustomCreatePath"
placeholder="请输入表单提交路由" placeholder="请输入表单提交路由"
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<Icon icon="ep:question" class="ml-5px" /> <Icon icon="ep:question" class="ml-5px" />
</el-tooltip> </el-tooltip>
</el-form-item> </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 <el-input
v-model="modelData.formCustomViewPath" v-model="modelData.formCustomViewPath"
placeholder="请输入表单查看的组件地址" placeholder="请输入表单查看的组件地址"
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
</el-form-item> </el-form-item>
<!-- 表单预览 --> <!-- 表单预览 -->
<div <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" class="mt-20px"
> >
<div class="flex items-center mb-15px"> <div class="flex items-center mb-15px">
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as FormApi from '@/api/bpm/form' import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate' import { setConfAndFields2 } from '@/utils/formCreate'
import { BpmModelFormType } from '@/utils/constants'
const props = defineProps({ const props = defineProps({
formList: { formList: {
...@@ -96,7 +97,7 @@ const formPreview = ref({ ...@@ -96,7 +97,7 @@ const formPreview = ref({
watch( watch(
() => modelData.value.formId, () => modelData.value.formId,
async (newFormId) => { async (newFormId) => {
if (newFormId && modelData.value.formType === 10) { if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
const data = await FormApi.getForm(newFormId) const data = await FormApi.getForm(newFormId)
setConfAndFields2(formPreview.value, data.conf, data.fields) setConfAndFields2(formPreview.value, data.conf, data.fields)
// 设置只读 // 设置只读
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { BpmModelType } from '@/utils/constants' import { BpmModelType } from '@/utils/constants'
import BpmModelEditor from '../editor/index.vue' import BpmModelEditor from './editor/index.vue'
import SimpleModelDesign from '../../simple/SimpleModelDesign.vue' import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
// 创建本地数据副本 // 创建本地数据副本
......
...@@ -34,10 +34,12 @@ import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/d ...@@ -34,10 +34,12 @@ import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/d
// 自定义左侧菜单(修改 默认任务 为 用户任务) // 自定义左侧菜单(修改 默认任务 为 用户任务)
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette' import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
import * as ModelApi from '@/api/bpm/model' import * as ModelApi from '@/api/bpm/model'
import { BpmModelFormType } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form'
defineOptions({ name: 'BpmModelEditor' }) defineOptions({ name: 'BpmModelEditor' })
const props = defineProps<{ defineProps<{
modelId?: string modelId?: string
modelKey: string modelKey: string
modelName: string modelName: string
...@@ -49,7 +51,8 @@ const message = useMessage() // 国际化 ...@@ -49,7 +51,8 @@ const message = useMessage() // 国际化
// 表单信息 // 表单信息
const formFields = ref<string[]>([]) const formFields = ref<string[]>([])
const formType = ref(20) // 表单类型,暂仅限流程表单
const formType = ref(BpmModelFormType.NORMAL)
provide('formFields', formFields) provide('formFields', formFields)
provide('formType', formType) provide('formType', formType)
...@@ -72,7 +75,7 @@ const model = ref<ModelApi.ModelVO>() // 流程模型的信息 ...@@ -72,7 +75,7 @@ const model = ref<ModelApi.ModelVO>() // 流程模型的信息
/** 初始化 modeler */ /** 初始化 modeler */
const initModeler = async (item: any) => { const initModeler = async (item: any) => {
//先初始化模型数据 // 先初始化模型数据
model.value = modelData.value model.value = modelData.value
modeler.value = item modeler.value = item
} }
...@@ -88,6 +91,20 @@ const save = async (bpmnXml: string) => { ...@@ -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(() => { onBeforeUnmount(() => {
modeler.value = null modeler.value = null
......
...@@ -44,8 +44,13 @@ ...@@ -44,8 +44,13 @@
<!-- 右侧按钮 --> <!-- 右侧按钮 -->
<div class="w-200px flex items-center justify-end gap-2"> <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 v-if="actionType === 'update'" type="success" @click="handleDeploy">
<el-button type="primary" @click="handleSave">保 存</el-button> 发 布
</el-button>
<el-button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'">恢 复</span>
<span v-else>保 存</span>
</el-button>
</div> </div>
</div> </div>
...@@ -81,20 +86,23 @@ ...@@ -81,20 +86,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useMessage } from '@/hooks/web/useMessage' 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 ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form' import * as FormApi from '@/api/bpm/form'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as UserApi from '@/api/system/user' 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 { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
import BasicInfo from './BasicInfo.vue' import BasicInfo from './BasicInfo.vue'
import FormDesign from './FormDesign.vue' import FormDesign from './FormDesign.vue'
import ProcessDesign from './ProcessDesign.vue' import ProcessDesign from './ProcessDesign.vue'
import { useTagsViewStore } from '@/store/modules/tagsView'
import ExtraSettings from './ExtraSettings.vue' import ExtraSettings from './ExtraSettings.vue'
import { useTagsView } from '@/hooks/web/useTagsView'
const router = useRouter() const router = useRouter()
const { delView } = useTagsViewStore() // 视图操作 const { delView } = useTagsViewStore() // 视图操作
const tagsView = useTagsView()
const route = useRoute() const route = useRoute()
const message = useMessage() const message = useMessage()
const userStore = useUserStoreWithOut() const userStore = useUserStoreWithOut()
...@@ -165,7 +173,7 @@ const formData: any = ref({ ...@@ -165,7 +173,7 @@ const formData: any = ref({
} }
}) })
//流程数据 // 流程数据
const processData = ref<any>() const processData = ref<any>()
provide('processData', processData) provide('processData', processData)
...@@ -177,20 +185,36 @@ const categoryList = ref<CategoryVO[]>([]) ...@@ -177,20 +185,36 @@ const categoryList = ref<CategoryVO[]>([])
const userList = ref<UserApi.UserVO[]>([]) const userList = ref<UserApi.UserVO[]>([])
/** 初始化数据 */ /** 初始化数据 */
const actionType = route.params.type as string
const initData = async () => { const initData = async () => {
const modelId = route.params.id as string if (actionType === 'definition') {
if (modelId) { // 情况一:流程定义场景(恢复)
// 修改场景 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
formData.value = await ModelApi.getModel(modelId) formData.value = await ModelApi.getModel(modelId)
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0 formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
// 复制场景 // 特殊:复制场景
if (route.params.type === 'copy') { if (actionType === 'copy') {
delete formData.value.id delete formData.value.id
formData.value.name += '副本' formData.value.name += '副本'
formData.value.key += '_copy' formData.value.key += '_copy'
tagsView.setTitle('复制流程')
} }
} else { } else {
// 新增场景 // 情况三:新增场景
formData.value.startUserType = 0 // 全体 formData.value.startUserType = 0 // 全体
formData.value.managerUserIds.push(userStore.getUser.id) formData.value.managerUserIds.push(userStore.getUser.id)
} }
...@@ -271,37 +295,31 @@ const handleSave = async () => { ...@@ -271,37 +295,31 @@ const handleSave = async () => {
...formData.value ...formData.value
} }
if (formData.value.id) { if (actionType === 'definition') {
// 情况一:流程定义场景(恢复)
await ModelApi.updateModel(modelData)
// 提示成功
message.success('恢复成功,可点击【发布】按钮,进行发布模型')
} else if (actionType === 'update') {
// 修改场景 // 修改场景
await ModelApi.updateModel(modelData) await ModelApi.updateModel(modelData)
// 询问是否发布流程 // 提示成功
try { message.success('修改成功,可点击【发布】按钮,进行发布模型')
await message.confirm('修改流程成功,是否发布流程?') } else if (actionType === 'copy') {
// 用户点击确认,执行发布 // 情况三:复制场景
await handleDeploy() formData.value.id = await ModelApi.createModel(modelData)
} catch { // 提示成功
// 用户点击取消,停留在当前页面 message.success('复制成功,可点击【发布】按钮,进行发布模型')
}
} else { } else {
// 新增场景 // 情况四:新增场景
formData.value.id = await ModelApi.createModel(modelData) formData.value.id = await ModelApi.createModel(modelData)
try { // 提示成功
await message.confirm('流程创建成功,是否继续编辑?') message.success('新建成功,可点击【发布】按钮,进行发布模型')
// 用户点击继续编辑,跳转到编辑页面 }
await nextTick()
// 先删除当前页签 // 返回列表页(排除更新的情况)
delView(unref(router.currentRoute)) if (actionType !== 'update') {
// 跳转到编辑页面 await router.push({ name: 'BpmModel' })
await router.push({
name: 'BpmModelUpdate',
params: { id: formData.value.id }
})
} catch {
// 先删除当前页签
delView(unref(router.currentRoute))
// 用户点击返回列表
await router.push({ name: 'BpmModel' })
}
} }
} catch (error: any) { } catch (error: any) {
console.error('保存失败:', error) console.error('保存失败:', error)
...@@ -346,7 +364,6 @@ const handleDeploy = async () => { ...@@ -346,7 +364,6 @@ const handleDeploy = async () => {
/** 步骤切换处理 */ /** 步骤切换处理 */
const handleStepClick = async (index: number) => { const handleStepClick = async (index: number) => {
try { try {
console.log('index', index)
if (index !== 0) { if (index !== 0) {
await validateBasic() await validateBasic()
} }
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { decodeFields, setConfAndFields2 } from '@/utils/formCreate' import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
import { BpmModelType } from '@/utils/constants' import { BpmModelType, BpmModelFormType } from '@/utils/constants'
import { import {
CandidateStrategy, CandidateStrategy,
NodeId, NodeId,
...@@ -108,6 +108,7 @@ const fApi = ref<ApiAttrs>() ...@@ -108,6 +108,7 @@ const fApi = ref<ApiAttrs>()
// 指定审批人 // 指定审批人
const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人或抄送人的任务列表 const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人或抄送人的任务列表
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据 const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
const tempStartUserSelectAssignees = ref({}) // 历史发起人选择审批人的数据,用于每次表单变更时,临时保存
const bpmnXML: any = ref(null) // BPMN 数据 const bpmnXML: any = ref(null) // BPMN 数据
const simpleJson = ref<string | undefined>() // Simple 设计器数据 json 格式 const simpleJson = ref<string | undefined>() // Simple 设计器数据 json 格式
...@@ -121,7 +122,7 @@ const initProcessInfo = async (row: any, formVariables?: any) => { ...@@ -121,7 +122,7 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
startUserSelectAssignees.value = {} startUserSelectAssignees.value = {}
// 情况一:流程表单 // 情况一:流程表单
if (row.formType == 10) { if (row.formType == BpmModelFormType.NORMAL) {
// 设置表单 // 设置表单
// 注意:需要从 formVariables 中,移除不在 row.formFields 的值。 // 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
// 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。 // 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
...@@ -137,8 +138,11 @@ const initProcessInfo = async (row: any, formVariables?: any) => { ...@@ -137,8 +138,11 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
await nextTick() await nextTick()
fApi.value?.btn.show(false) // 隐藏提交按钮 fApi.value?.btn.show(false) // 隐藏提交按钮
// 获取流程审批信息 // 获取流程审批信息,当再次发起时,流程审批节点要根据原始表单参数预测出来
await getApprovalDetail(row) await getApprovalDetail({
id: row.id,
processVariablesStr: JSON.stringify(formVariables)
})
// 加载流程图 // 加载流程图
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id) const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
...@@ -155,32 +159,61 @@ const initProcessInfo = async (row: any, formVariables?: any) => { ...@@ -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) => { const getApprovalDetail = async (row: any) => {
try { try {
// TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效) // TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效);@jason:这里可以去掉 activityId 么?
const data = await ProcessInstanceApi.getApprovalDetail({ const data = await ProcessInstanceApi.getApprovalDetail({
processDefinitionId: row.id, processDefinitionId: row.id,
activityId: NodeId.START_USER_NODE_ID activityId: NodeId.START_USER_NODE_ID,
processVariablesStr: row.processVariablesStr // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
}) })
if (!data) { if (!data) {
message.error('查询不到审批详情信息!') message.error('查询不到审批详情信息!')
return return
} }
// 获取审批节点,显示 Timeline 的数据
activityNodes.value = data.activityNodes
// 获取发起人自选的任务 // 获取发起人自选的任务
startUserSelectTasks.value = data.activityNodes?.filter( startUserSelectTasks.value = data.activityNodes?.filter(
(node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy (node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
) )
// 恢复之前的选择审批人
if (startUserSelectTasks.value?.length > 0) { if (startUserSelectTasks.value?.length > 0) {
for (const node of startUserSelectTasks.value) { for (const node of startUserSelectTasks.value) {
startUserSelectAssignees.value[node.id] = [] 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 const formFieldsPermission = data.formFieldsPermission
// 设置表单字段权限 // 设置表单字段权限
......
...@@ -64,9 +64,9 @@ ...@@ -64,9 +64,9 @@
class="w-32px h-32px" class="w-32px h-32px"
/> />
<div v-else class="flow-icon"> <div v-else class="flow-icon">
<span style="font-size: 12px; color: #fff">{{ <span style="font-size: 12px; color: #fff">
sliceName(definition.name,0,2) {{ subString(definition.name, 0, 2) }}
}}</span> </span>
</div> </div>
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text> <el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
</div> </div>
...@@ -97,7 +97,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance' ...@@ -97,7 +97,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue' import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
import { groupBy } from 'lodash-es' import { groupBy } from 'lodash-es'
import { sliceName } from '@/utils/index' import { subString } from '@/utils/index'
defineOptions({ name: 'BpmProcessInstanceCreate' }) defineOptions({ name: 'BpmProcessInstanceCreate' })
......
<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>
...@@ -42,13 +42,13 @@ watch( ...@@ -42,13 +42,13 @@ watch(
const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds
setSimpleModelNodeTaskStatus( setSimpleModelNodeTaskStatus(
newModelView.simpleModel, newModelView.simpleModel,
newModelView.processInstance.status, newModelView.processInstance?.status,
rejectedTaskActivityIds, rejectedTaskActivityIds,
unfinishedTaskActivityIds, unfinishedTaskActivityIds,
finishedActivityIds, finishedActivityIds,
finishedSequenceFlowActivityIds finishedSequenceFlowActivityIds
) )
simpleModel.value = newModelView.simpleModel simpleModel.value = newModelView.simpleModel ? newModelView.simpleModel : {}
} }
} }
) )
...@@ -84,7 +84,9 @@ const setSimpleModelNodeTaskStatus = ( ...@@ -84,7 +84,9 @@ const setSimpleModelNodeTaskStatus = (
// 审批节点 // 审批节点
if ( if (
simpleModel.type === NodeType.START_USER_NODE || 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 simpleModel.activityStatus = TaskStatusEnum.NOT_START
if (rejectedTaskActivityIds.includes(simpleModel.id)) { if (rejectedTaskActivityIds.includes(simpleModel.id)) {
...@@ -169,5 +171,4 @@ const setSimpleModelNodeTaskStatus = ( ...@@ -169,5 +171,4 @@ const setSimpleModelNodeTaskStatus = (
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>
...@@ -43,7 +43,8 @@ ...@@ -43,7 +43,8 @@
v-if=" v-if="
isEmpty(activity.tasks) && isEmpty(activity.tasks) &&
isEmpty(activity.candidateUsers) && 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 --> <!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
...@@ -121,6 +122,7 @@ ...@@ -121,6 +122,7 @@
" "
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md" class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
> >
<!-- TODO lesan:这里如果是办理,需要是办理意见 -->
审批意见:{{ task.reason }} 审批意见:{{ task.reason }}
</div> </div>
<div <div
...@@ -179,6 +181,8 @@ import copySvg from '@/assets/svgs/bpm/copy.svg' ...@@ -179,6 +181,8 @@ import copySvg from '@/assets/svgs/bpm/copy.svg'
import conditionSvg from '@/assets/svgs/bpm/condition.svg' import conditionSvg from '@/assets/svgs/bpm/condition.svg'
import parallelSvg from '@/assets/svgs/bpm/parallel.svg' import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
import finishSvg from '@/assets/svgs/bpm/finish.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' }) defineOptions({ name: 'BpmProcessInstanceTimeline' })
withDefaults( withDefaults(
...@@ -240,12 +244,16 @@ const nodeTypeSvgMap = { ...@@ -240,12 +244,16 @@ const nodeTypeSvgMap = {
[NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg }, [NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
// 审批人节点 // 审批人节点
[NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg }, [NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
// 办理人节点
[NodeType.TRANSACTOR_NODE]: { color: '#ff943e', svg: transactorSvg },
// 抄送人节点 // 抄送人节点
[NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg }, [NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
// 条件分支节点 // 条件分支节点
[NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg }, [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 }
} }
// 只有只有状态是 -1、0、1 才展示头像右小角状态小icon // 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
...@@ -264,6 +272,8 @@ const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => { ...@@ -264,6 +272,8 @@ const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
if ( if (
nodeType === NodeType.START_USER_NODE || nodeType === NodeType.START_USER_NODE ||
nodeType === NodeType.USER_TASK_NODE || nodeType === NodeType.USER_TASK_NODE ||
nodeType === NodeType.TRANSACTOR_NODE ||
nodeType === NodeType.CHILD_PROCESS_NODE ||
nodeType === NodeType.END_EVENT_NODE nodeType === NodeType.END_EVENT_NODE
) { ) {
return statusIconMap[taskStatus]?.icon return statusIconMap[taskStatus]?.icon
......
...@@ -178,8 +178,9 @@ const writableFields: Array<string> = [] // 表单可以编辑的字段 ...@@ -178,8 +178,9 @@ const writableFields: Array<string> = [] // 表单可以编辑的字段
/** 获得详情 */ /** 获得详情 */
const getDetail = () => { const getDetail = () => {
// 获得审批详情
getApprovalDetail() getApprovalDetail()
// 获得流程模型视图
getProcessModelView() getProcessModelView()
} }
......
...@@ -24,9 +24,7 @@ ...@@ -24,9 +24,7 @@
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
</el-form-item> </el-form-item>
<!-- TODO @ tuituji:style 可以使用 unocss --> <el-form-item label="" prop="category" class="absolute right-[300px]">
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
<!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 RE:done & to check-->
<el-select <el-select
v-model="queryParams.category" v-model="queryParams.category"
placeholder="请选择流程分类" placeholder="请选择流程分类"
...@@ -42,8 +40,7 @@ ...@@ -42,8 +40,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="" prop="status" class="absolute right-[130px]">
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
<el-select <el-select
v-model="queryParams.status" v-model="queryParams.status"
placeholder="请选择流程状态" placeholder="请选择流程状态"
...@@ -61,8 +58,7 @@ ...@@ -61,8 +58,7 @@
</el-form-item> </el-form-item>
<!-- 高级筛选 --> <!-- 高级筛选 -->
<!-- TODO @ tuituji:style 可以使用 unocss --> <el-form-item class="absolute right-0">
<el-form-item :style="{ position: 'absolute', right: '0px' }">
<el-popover <el-popover
:visible="showPopover" :visible="showPopover"
persistent persistent
...@@ -75,36 +71,28 @@ ...@@ -75,36 +71,28 @@
<Icon icon="ep:plus" class="mr-5px" />高级筛选 <Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button> </el-button>
</template> </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 <el-form-item
label="所属流程" label="所属流程"
class="bold-label" class="font-bold"
label-position="top" label-position="top"
prop="processDefinitionKey" prop="processDefinitionKey"
> >
<el-input <el-select
v-model="queryParams.processDefinitionKey" v-model="queryParams.processDefinitionKey"
placeholder="请输入流程定义的标识" placeholder="请选择流程定义"
clearable clearable
@keyup.enter="handleQuery"
class="!w-390px" 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>
<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 <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
...@@ -115,11 +103,12 @@ ...@@ -115,11 +103,12 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<!-- TODO tuituiji:参考钉钉,1)按照清空、取消、确认排序。2)右对齐。3)确认增加 primary --> <el-form-item class="font-bold" label-position="top">
<el-form-item class="bold-label" label-position="top"> <div class="flex justify-end w-full">
<el-button @click="handleQuery"> 确认</el-button> <el-button @click="resetQuery">清空</el-button>
<el-button @click="showPopover = false"> 取消</el-button> <el-button @click="showPopover = false">取消</el-button>
<el-button @click="resetQuery"> 清空</el-button> <el-button type="primary" @click="handleQuery">确认</el-button>
</div>
</el-form-item> </el-form-item>
</el-popover> </el-popover>
</el-form-item> </el-form-item>
...@@ -130,7 +119,7 @@ ...@@ -130,7 +119,7 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" /> <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"> <template #default="scope">
<div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0"> <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"> <div v-for="(item, index) in scope.row.summary" :key="index">
...@@ -146,11 +135,37 @@ ...@@ -146,11 +135,37 @@
min-width="100" min-width="100"
fixed="left" fixed="left"
/> />
<!-- TODO @芋艿:摘要 --> <el-table-column label="流程状态" prop="status" min-width="200">
<!-- TODO tuituiji:参考钉钉;1)审批中时,展示审批任务;2)非审批中,展示状态 -->
<el-table-column label="流程状态" prop="status" width="120">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" /> <!-- 审批中状态 -->
<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> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
...@@ -167,19 +182,6 @@ ...@@ -167,19 +182,6 @@
width="180" width="180"
:formatter="dateFormatter" :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"> <el-table-column label="操作" align="center" fixed="right" width="180">
<template #default="scope"> <template #default="scope">
<el-button <el-button
...@@ -215,7 +217,6 @@ ...@@ -215,7 +217,6 @@
</ContentWrap> </ContentWrap>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。 RE:done & to check
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
...@@ -223,6 +224,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance' ...@@ -223,6 +224,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import { ProcessInstanceVO } from '@/api/bpm/processInstance' import { ProcessInstanceVO } from '@/api/bpm/processInstance'
import * as DefinitionApi from '@/api/bpm/definition' import * as DefinitionApi from '@/api/bpm/definition'
import { BpmProcessInstanceStatus } from '@/utils/constants'
defineOptions({ name: 'BpmProcessInstanceMy' }) defineOptions({ name: 'BpmProcessInstanceMy' })
...@@ -233,6 +235,7 @@ const { t } = useI18n() // 国际化 ...@@ -233,6 +235,7 @@ const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据 const list = ref([]) // 列表的数据
const processDefinitionList = ref<any[]>([]) // 流程定义列表
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
...@@ -244,6 +247,7 @@ const queryParams = reactive({ ...@@ -244,6 +247,7 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表 const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
const showPopover = ref(false) // 高级筛选是否展示
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
...@@ -257,8 +261,6 @@ const getList = async () => { ...@@ -257,8 +261,6 @@ const getList = async () => {
} }
} }
const showPopover = ref(false)
/** 搜索按钮操作 */ /** 搜索按钮操作 */
const handleQuery = () => { const handleQuery = () => {
queryParams.pageNo = 1 queryParams.pageNo = 1
...@@ -325,10 +327,7 @@ onActivated(() => { ...@@ -325,10 +327,7 @@ onActivated(() => {
onMounted(async () => { onMounted(async () => {
await getList() await getList()
categoryList.value = await CategoryApi.getCategorySimpleList() categoryList.value = await CategoryApi.getCategorySimpleList()
// 获取流程定义列表
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
}) })
</script> </script>
<style>
.bold-label .el-form-item__label {
font-weight: bold; /* 将字体加粗 */
}
</style>
...@@ -76,26 +76,31 @@ ...@@ -76,26 +76,31 @@
placement="bottom-end" placement="bottom-end"
> >
<template #reference> <template #reference>
<el-button @click="showPopover = !showPopover" > <el-button @click="showPopover = !showPopover">
<Icon icon="ep:plus" class="mr-5px" />高级筛选 <Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button> </el-button>
</template> </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 <el-select
v-model="queryParams.category" v-model="queryParams.processDefinitionKey"
placeholder="请选择流程发起人" placeholder="请选择流程定义"
clearable clearable
@change="handleQuery"
class="!w-390px" class="!w-390px"
> >
<el-option <el-option
v-for="category in categoryList" v-for="item in processDefinitionList"
:key="category.code" :key="item.key"
:label="category.name" :label="item.name"
:value="category.code" :value="item.key"
/> />
</el-select> </el-select>
</el-form-item> --> </el-form-item>
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime"> <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
<el-date-picker <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
...@@ -111,10 +116,9 @@ ...@@ -111,10 +116,9 @@
<el-button @click="handleQuery"> 确认</el-button> <el-button @click="handleQuery"> 确认</el-button>
<el-button @click="showPopover = false"> 取消</el-button> <el-button @click="showPopover = false"> 取消</el-button>
<el-button @click="resetQuery"> 清空</el-button> <el-button @click="resetQuery"> 清空</el-button>
</el-form-item> </el-form-item>
</el-popover> </el-popover>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
...@@ -122,9 +126,12 @@ ...@@ -122,9 +126,12 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" /> <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"> <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"> <div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
<el-text type="info"> {{ item.key }} : {{ item.value }} </el-text> <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
</div> </div>
...@@ -170,7 +177,12 @@ ...@@ -170,7 +177,12 @@
{{ formatPast2(scope.row.durationInMillis) }} {{ formatPast2(scope.row.durationInMillis) }}
</template> </template>
</el-table-column> </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="任务编号" prop="id" :show-overflow-tooltip="true" />
<el-table-column align="center" label="操作" fixed="right" width="80"> <el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope"> <template #default="scope">
...@@ -192,6 +204,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' ...@@ -192,6 +204,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatPast2 } from '@/utils/formatTime' import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task' import * as TaskApi from '@/api/bpm/task'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as DefinitionApi from '@/api/bpm/definition'
defineOptions({ name: 'BpmDoneTask' }) defineOptions({ name: 'BpmDoneTask' })
...@@ -200,17 +213,19 @@ const { push } = useRouter() // 路由 ...@@ -200,17 +213,19 @@ const { push } = useRouter() // 路由
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据 const list = ref([]) // 列表的数据
const processDefinitionList = ref<any[]>([]) // 流程定义列表
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: '', name: '',
category: undefined, category: undefined,
status: undefined, status: undefined,
processDefinitionKey: '',
createTime: [] createTime: []
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表 const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
const showPopover = ref(false) const showPopover = ref(false) // 高级筛选是否展示
/** 查询任务列表 */ /** 查询任务列表 */
const getList = async () => { const getList = async () => {
...@@ -251,5 +266,7 @@ const handleAudit = (row: any) => { ...@@ -251,5 +266,7 @@ const handleAudit = (row: any) => {
onMounted(async () => { onMounted(async () => {
await getList() await getList()
categoryList.value = await CategoryApi.getCategorySimpleList() categoryList.value = await CategoryApi.getCategorySimpleList()
// 获取流程定义列表
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
}) })
</script> </script>
...@@ -31,8 +31,7 @@ ...@@ -31,8 +31,7 @@
搜索 搜索
</el-button> </el-button>
</el-form-item> </el-form-item>
<el-form-item label="" prop="category" class="absolute right-130px">
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
<el-select <el-select
v-model="queryParams.category" v-model="queryParams.category"
placeholder="请选择流程分类" placeholder="请选择流程分类"
...@@ -48,9 +47,8 @@ ...@@ -48,9 +47,8 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 高级筛选 --> <!-- 高级筛选 -->
<el-form-item :style="{ position: 'absolute', right: '0px' }"> <el-form-item class="absolute right-0">
<el-popover <el-popover
:visible="showPopover" :visible="showPopover"
persistent persistent
...@@ -59,27 +57,32 @@ ...@@ -59,27 +57,32 @@
placement="bottom-end" placement="bottom-end"
> >
<template #reference> <template #reference>
<el-button @click="showPopover = !showPopover" > <el-button @click="showPopover = !showPopover">
<Icon icon="ep:plus" class="mr-5px" />高级筛选 <Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button> </el-button>
</template> </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 <el-select
v-model="queryParams.category" v-model="queryParams.processDefinitionKey"
placeholder="请选择流程发起人" placeholder="请选择流程定义"
clearable clearable
@change="handleQuery"
class="!w-390px" class="!w-390px"
> >
<el-option <el-option
v-for="category in categoryList" v-for="item in processDefinitionList"
:key="category.code" :key="item.key"
:label="category.name" :label="item.name"
:value="category.code" :value="item.key"
/> />
</el-select> </el-select>
</el-form-item> --> </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 <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
...@@ -87,17 +90,18 @@ ...@@ -87,17 +90,18 @@
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期" end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" :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>
<el-form-item class="bold-label" label-position="top"> <el-form-item class="font-bold" label-position="top">
<el-button @click="handleQuery"> 确认</el-button> <div class="flex justify-end w-full">
<el-button @click="showPopover = false"> 取消</el-button> <el-button @click="resetQuery">清空</el-button>
<el-button @click="resetQuery"> 清空</el-button> <el-button @click="showPopover = false">取消</el-button>
</el-form-item> <el-button type="primary" @click="handleQuery">确认</el-button>
</div>
</el-form-item>
</el-popover> </el-popover>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
...@@ -105,9 +109,12 @@ ...@@ -105,9 +109,12 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" /> <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"> <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"> <div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
<el-text type="info"> {{ item.key }} : {{ item.value }} </el-text> <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
</div> </div>
...@@ -135,7 +142,12 @@ ...@@ -135,7 +142,12 @@
prop="createTime" prop="createTime"
width="180" 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="任务编号" prop="id" :show-overflow-tooltip="true" />
<el-table-column align="center" label="操作" fixed="right" width="80"> <el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope"> <template #default="scope">
...@@ -157,6 +169,7 @@ ...@@ -157,6 +169,7 @@
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task' import * as TaskApi from '@/api/bpm/task'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as DefinitionApi from '@/api/bpm/definition'
defineOptions({ name: 'BpmTodoTask' }) defineOptions({ name: 'BpmTodoTask' })
...@@ -165,15 +178,18 @@ const { push } = useRouter() // 路由 ...@@ -165,15 +178,18 @@ const { push } = useRouter() // 路由
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据 const list = ref([]) // 列表的数据
const processDefinitionList = ref<any[]>([]) // 流程定义列表
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: '', name: '',
category: undefined, category: undefined,
processDefinitionKey: '',
createTime: [] createTime: []
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表 const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
const showPopover = ref(false) // 高级筛选是否展示
/** 查询任务列表 */ /** 查询任务列表 */
const getList = async () => { const getList = async () => {
...@@ -187,8 +203,6 @@ const getList = async () => { ...@@ -187,8 +203,6 @@ const getList = async () => {
} }
} }
const showPopover = ref(false)
/** 搜索按钮操作 */ /** 搜索按钮操作 */
const handleQuery = () => { const handleQuery = () => {
queryParams.pageNo = 1 queryParams.pageNo = 1
...@@ -216,5 +230,7 @@ const handleAudit = (row: any) => { ...@@ -216,5 +230,7 @@ const handleAudit = (row: any) => {
onMounted(async () => { onMounted(async () => {
await getList() await getList()
categoryList.value = await CategoryApi.getCategorySimpleList() categoryList.value = await CategoryApi.getCategorySimpleList()
// 获取流程定义列表
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
}) })
</script> </script>
<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">
已支持 TXT、MARKDOWN、PDF、HTML、XLSX、XLS、DOCX、CSV、EML、MSG、PPTX、PPT、XML、EPUB,每个文件不超过 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>
<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>
<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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment