commit
b94235adf3
|
@ -30,7 +30,7 @@ export const getModelPage = async (params) => {
|
||||||
return await request.get({ url: '/bpm/model/page', params })
|
return await request.get({ url: '/bpm/model/page', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getModel = async (id: number) => {
|
export const getModel = async (id: string) => {
|
||||||
return await request.get({ url: '/bpm/model/get?id=' + id })
|
return await request.get({ url: '/bpm/model/get?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,10 @@ export const updateModel = async (data: ModelVO) => {
|
||||||
return await request.put({ url: '/bpm/model/update', data: data })
|
return await request.put({ url: '/bpm/model/update', data: data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const updateModelBpmn = async (data: ModelVO) => {
|
||||||
|
return await request.put({ url: '/bpm/model/update-bpmn', data: data })
|
||||||
|
}
|
||||||
|
|
||||||
// 任务状态修改
|
// 任务状态修改
|
||||||
export const updateModelState = async (id: number, state: number) => {
|
export const updateModelState = async (id: number, state: number) => {
|
||||||
const data = {
|
const data = {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
import { ProcessDefinitionVO } from '@/api/bpm/model'
|
import { ProcessDefinitionVO } from '@/api/bpm/model'
|
||||||
|
import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
export type Task = {
|
export type Task = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
@ -22,6 +22,35 @@ export type ProcessInstanceVO = {
|
||||||
processDefinition?: ProcessDefinitionVO
|
processDefinition?: ProcessDefinitionVO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
export type User = {
|
||||||
|
id: number,
|
||||||
|
nickname: string,
|
||||||
|
avatar: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批任务信息
|
||||||
|
export type ApprovalTaskInfo = {
|
||||||
|
id: number,
|
||||||
|
ownerUser: User,
|
||||||
|
assigneeUser: User,
|
||||||
|
status: number,
|
||||||
|
reason: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批节点信息
|
||||||
|
export type ApprovalNodeInfo = {
|
||||||
|
id : number
|
||||||
|
name: string
|
||||||
|
nodeType: NodeType
|
||||||
|
status: number
|
||||||
|
startTime?: Date
|
||||||
|
endTime?: Date
|
||||||
|
candidateUserList?: User[]
|
||||||
|
tasks: ApprovalTaskInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
export const getProcessInstanceMyPage = async (params: any) => {
|
export const getProcessInstanceMyPage = async (params: any) => {
|
||||||
return await request.get({ url: '/bpm/process-instance/my-page', params })
|
return await request.get({ url: '/bpm/process-instance/my-page', params })
|
||||||
}
|
}
|
||||||
|
@ -57,3 +86,14 @@ export const getProcessInstance = async (id: string) => {
|
||||||
export const getProcessInstanceCopyPage = async (params: any) => {
|
export const getProcessInstanceCopyPage = async (params: any) => {
|
||||||
return await request.get({ url: '/bpm/process-instance/copy/page', params })
|
return await request.get({ url: '/bpm/process-instance/copy/page', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取审批详情
|
||||||
|
export const getApprovalDetail = async (processInstanceId?:string, processDefinitionId?:string) => {
|
||||||
|
const param = processInstanceId ? '?processInstanceId='+ processInstanceId : '?processDefinitionId='+ processDefinitionId
|
||||||
|
return await request.get({ url: 'bpm/process-instance/get-approval-detail'+ param })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取表单字段权限
|
||||||
|
export const getFormFieldsPermission = async (params: any) => {
|
||||||
|
return await request.get({ url: '/bpm/process-instance/get-form-fields-permission', params })
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
|
||||||
|
export const updateBpmSimpleModel = async (data) => {
|
||||||
|
return await request.post({
|
||||||
|
url: '/bpm/model/simple/update',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBpmSimpleModel = async (id) => {
|
||||||
|
return await request.get({
|
||||||
|
url: '/bpm/model/simple/get?id=' + id
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,5 +1,51 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务状态枚举
|
||||||
|
*/
|
||||||
|
export enum TaskStatusEnum {
|
||||||
|
/**
|
||||||
|
* 未开始
|
||||||
|
*/
|
||||||
|
NOT_START = -1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待审批
|
||||||
|
*/
|
||||||
|
WAIT = 0,
|
||||||
|
/**
|
||||||
|
* 审批中
|
||||||
|
*/
|
||||||
|
RUNNING = 1,
|
||||||
|
/**
|
||||||
|
* 审批通过
|
||||||
|
*/
|
||||||
|
APPROVE = 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批不通过
|
||||||
|
*/
|
||||||
|
REJECT = 3,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已取消
|
||||||
|
*/
|
||||||
|
CANCEL = 4,
|
||||||
|
/**
|
||||||
|
* 已退回
|
||||||
|
*/
|
||||||
|
RETURN = 5,
|
||||||
|
/**
|
||||||
|
* 委派中
|
||||||
|
*/
|
||||||
|
DELEGATE = 6,
|
||||||
|
/**
|
||||||
|
* 审批通过中
|
||||||
|
*/
|
||||||
|
APPROVING = 7,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export type TaskVO = {
|
export type TaskVO = {
|
||||||
id: number
|
id: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
/* stylelint-disable order/properties-order */
|
|
||||||
<template>
|
|
||||||
<div class="add-node-btn-box">
|
|
||||||
<div class="add-node-btn">
|
|
||||||
<el-popover placement="right-start" v-model="visible" width="auto">
|
|
||||||
<div class="add-node-popover-body">
|
|
||||||
<a class="add-node-popover-item approver" @click="addType(1)">
|
|
||||||
<div class="item-wrapper">
|
|
||||||
<span class="iconfont"></span>
|
|
||||||
</div>
|
|
||||||
<p>审批人</p>
|
|
||||||
</a>
|
|
||||||
<a class="add-node-popover-item notifier" @click="addType(2)">
|
|
||||||
<div class="item-wrapper">
|
|
||||||
<span class="iconfont"></span>
|
|
||||||
</div>
|
|
||||||
<p>抄送人</p>
|
|
||||||
</a>
|
|
||||||
<a class="add-node-popover-item condition" @click="addType(4)">
|
|
||||||
<div class="item-wrapper">
|
|
||||||
<span class="iconfont"></span>
|
|
||||||
</div>
|
|
||||||
<p>条件分支</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<template #reference>
|
|
||||||
<button class="btn" type="button">
|
|
||||||
<span class="iconfont"></span>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
let props = defineProps({
|
|
||||||
childNodeP: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let emits = defineEmits(['update:childNodeP'])
|
|
||||||
let visible = ref(false)
|
|
||||||
const addType = (type) => {
|
|
||||||
visible.value = false
|
|
||||||
if (type != 4) {
|
|
||||||
var data
|
|
||||||
if (type == 1) {
|
|
||||||
data = {
|
|
||||||
nodeName: '审核人',
|
|
||||||
error: true,
|
|
||||||
type: 1,
|
|
||||||
settype: 1,
|
|
||||||
selectMode: 0,
|
|
||||||
selectRange: 0,
|
|
||||||
directorLevel: 1,
|
|
||||||
examineMode: 1,
|
|
||||||
noHanderAction: 1,
|
|
||||||
examineEndDirectorLevel: 0,
|
|
||||||
childNode: props.childNodeP,
|
|
||||||
nodeUserList: []
|
|
||||||
}
|
|
||||||
} else if (type == 2) {
|
|
||||||
data = {
|
|
||||||
nodeName: '抄送人',
|
|
||||||
type: 2,
|
|
||||||
ccSelfSelectFlag: 1,
|
|
||||||
childNode: props.childNodeP,
|
|
||||||
nodeUserList: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emits('update:childNodeP', data)
|
|
||||||
} else {
|
|
||||||
emits('update:childNodeP', {
|
|
||||||
nodeName: '路由',
|
|
||||||
type: 4,
|
|
||||||
childNode: null,
|
|
||||||
conditionNodes: [
|
|
||||||
{
|
|
||||||
nodeName: '条件1',
|
|
||||||
error: true,
|
|
||||||
type: 3,
|
|
||||||
priorityLevel: 1,
|
|
||||||
conditionList: [],
|
|
||||||
nodeUserList: [],
|
|
||||||
childNode: props.childNodeP
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodeName: '条件2',
|
|
||||||
type: 3,
|
|
||||||
priorityLevel: 2,
|
|
||||||
conditionList: [],
|
|
||||||
nodeUserList: [],
|
|
||||||
childNode: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.add-node-btn-box {
|
|
||||||
width: 240px;
|
|
||||||
display: inline-flex;
|
|
||||||
-ms-flex-negative: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-ms-flex-positive: 1;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: -1;
|
|
||||||
margin: auto;
|
|
||||||
width: 2px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #cacaca;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-node-btn {
|
|
||||||
user-select: none;
|
|
||||||
width: 240px;
|
|
||||||
padding: 20px 0 32px;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-pack: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
background: #3296fa;
|
|
||||||
border-radius: 50%;
|
|
||||||
position: relative;
|
|
||||||
border: none;
|
|
||||||
line-height: 30px;
|
|
||||||
-webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
||||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.3);
|
|
||||||
box-shadow: 0 13px 27px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: none;
|
|
||||||
background: #1e83e9;
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-node-popover-body {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.add-node-popover-item {
|
|
||||||
margin-right: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
flex: 1;
|
|
||||||
color: #191f25 !important;
|
|
||||||
|
|
||||||
.item-wrapper {
|
|
||||||
user-select: none;
|
|
||||||
display: inline-block;
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #e2e2e2;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
font-size: 35px;
|
|
||||||
line-height: 80px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.approver {
|
|
||||||
.item-wrapper {
|
|
||||||
color: #ff943e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.notifier {
|
|
||||||
.item-wrapper {
|
|
||||||
color: #3296fa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.condition {
|
|
||||||
.item-wrapper {
|
|
||||||
color: #15bc83;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.item-wrapper {
|
|
||||||
background: #3296fa;
|
|
||||||
box-shadow: 0 10px 20px 0 rgba(50, 150, 250, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.item-wrapper {
|
|
||||||
box-shadow: none;
|
|
||||||
background: #eaeaea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconfont {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,297 +0,0 @@
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
|
||||||
<!--
|
|
||||||
* @Date: 2022-09-21 14:41:53
|
|
||||||
* @LastEditors: StavinLi 495727881@qq.com
|
|
||||||
* @LastEditTime: 2023-05-24 15:20:24
|
|
||||||
* @FilePath: /Workflow-Vue3/src/components/nodeWrap.vue
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<div class="node-wrap" v-if="nodeConfig.type < 3">
|
|
||||||
<div class="node-wrap-box" :class="(nodeConfig.type == 0 ? 'start-node ' : '') +(isTried && nodeConfig.error ? 'active error' : '')">
|
|
||||||
<div class="title" :style="`background: rgb(${bgColors[nodeConfig.type]});`">
|
|
||||||
<span v-if="nodeConfig.type == 0">{{ nodeConfig.nodeName }}</span>
|
|
||||||
<template v-else>
|
|
||||||
<span class="iconfont">{{nodeConfig.type == 1?'':''}}</span>
|
|
||||||
<input
|
|
||||||
v-if="isInput"
|
|
||||||
type="text"
|
|
||||||
class="ant-input editable-title-input"
|
|
||||||
@blur="blurEvent()"
|
|
||||||
@focus="$event.currentTarget.select()"
|
|
||||||
v-focus
|
|
||||||
v-model="nodeConfig.nodeName"
|
|
||||||
:placeholder="defaultText"
|
|
||||||
/>
|
|
||||||
<span v-else class="editable-title" @click="clickEvent()">{{ nodeConfig.nodeName }}</span>
|
|
||||||
<i class="anticon anticon-close close" @click="delNode"></i>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="content" @click="setPerson">
|
|
||||||
<div class="text">
|
|
||||||
<span class="placeholder" v-if="!showText">请选择{{defaultText}}</span>
|
|
||||||
{{showText}}
|
|
||||||
</div>
|
|
||||||
<i class="anticon anticon-right arrow"></i>
|
|
||||||
</div>
|
|
||||||
<div class="error_tip" v-if="isTried && nodeConfig.error">
|
|
||||||
<i class="anticon anticon-exclamation-circle"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<addNode v-model:childNodeP="nodeConfig.childNode" />
|
|
||||||
</div>
|
|
||||||
<div class="branch-wrap" v-if="nodeConfig.type == 4">
|
|
||||||
<div class="branch-box-wrap">
|
|
||||||
<div class="branch-box">
|
|
||||||
<button class="add-branch" @click="addTerm">添加条件</button>
|
|
||||||
<div class="col-box" v-for="(item, index) in nodeConfig.conditionNodes" :key="index">
|
|
||||||
<div class="condition-node">
|
|
||||||
<div class="condition-node-box">
|
|
||||||
<div class="auto-judge" :class="isTried && item.error ? 'error active' : ''">
|
|
||||||
<div class="sort-left" v-if="index != 0" @click="arrTransfer(index, -1)"><</div>
|
|
||||||
<div class="title-wrapper">
|
|
||||||
<input
|
|
||||||
v-if="isInputList[index]"
|
|
||||||
type="text"
|
|
||||||
class="ant-input editable-title-input"
|
|
||||||
@blur="blurEvent(index)"
|
|
||||||
@focus="$event.currentTarget.select()"
|
|
||||||
v-model="item.nodeName"
|
|
||||||
/>
|
|
||||||
<span v-else class="editable-title" @click="clickEvent(index)">{{ item.nodeName }}</span>
|
|
||||||
<span class="priority-title" @click="setPerson(item.priorityLevel)">优先级{{ item.priorityLevel }}</span>
|
|
||||||
<i class="anticon anticon-close close" @click="delTerm(index)"></i>
|
|
||||||
</div>
|
|
||||||
<div class="sort-right" v-if="index != nodeConfig.conditionNodes.length - 1" @click="arrTransfer(index)">></div>
|
|
||||||
<div class="content" @click="setPerson(item.priorityLevel)">{{ conditionStr(nodeConfig, index) }}</div>
|
|
||||||
<div class="error_tip" v-if="isTried && item.error">
|
|
||||||
<i class="anticon anticon-exclamation-circle"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<addNode v-model:childNodeP="item.childNode" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nodeWrap v-if="item.childNode" v-model:nodeConfig="item.childNode" />
|
|
||||||
<template v-if="index == 0">
|
|
||||||
<div class="top-left-cover-line"></div>
|
|
||||||
<div class="bottom-left-cover-line"></div>
|
|
||||||
</template>
|
|
||||||
<template v-if="index == nodeConfig.conditionNodes.length - 1">
|
|
||||||
<div class="top-right-cover-line"></div>
|
|
||||||
<div class="bottom-right-cover-line"></div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<addNode v-model:childNodeP="nodeConfig.childNode" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nodeWrap v-if="nodeConfig.childNode" v-model:nodeConfig="nodeConfig.childNode" />
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import addNode from './addNode.vue'
|
|
||||||
import { onMounted, ref, watch, getCurrentInstance, computed } from 'vue'
|
|
||||||
import {
|
|
||||||
arrToStr,
|
|
||||||
conditionStr,
|
|
||||||
setApproverStr,
|
|
||||||
copyerStr,
|
|
||||||
bgColors,
|
|
||||||
placeholderList
|
|
||||||
} from './util'
|
|
||||||
import { useWorkFlowStoreWithOut } from '@/store/modules/simpleWorkflow'
|
|
||||||
let _uid = getCurrentInstance().uid
|
|
||||||
|
|
||||||
let props = defineProps({
|
|
||||||
nodeConfig: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
flowPermission: {
|
|
||||||
type: Object,
|
|
||||||
// eslint-disable-next-line vue/require-valid-default-prop
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let defaultText = computed(() => {
|
|
||||||
return placeholderList[props.nodeConfig.type]
|
|
||||||
})
|
|
||||||
let showText = computed(() => {
|
|
||||||
if (props.nodeConfig.type == 0) return arrToStr(props.flowPermission) || '所有人'
|
|
||||||
if (props.nodeConfig.type == 1) return setApproverStr(props.nodeConfig)
|
|
||||||
return copyerStr(props.nodeConfig)
|
|
||||||
})
|
|
||||||
|
|
||||||
let isInputList = ref([])
|
|
||||||
let isInput = ref(false)
|
|
||||||
const resetConditionNodesErr = () => {
|
|
||||||
for (var i = 0; i < props.nodeConfig.conditionNodes.length; i++) {
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.conditionNodes[i].error =
|
|
||||||
conditionStr(props.nodeConfig, i) == '请设置条件' &&
|
|
||||||
i != props.nodeConfig.conditionNodes.length - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.nodeConfig.type == 1) {
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.error = !setApproverStr(props.nodeConfig)
|
|
||||||
} else if (props.nodeConfig.type == 2) {
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.error = !copyerStr(props.nodeConfig)
|
|
||||||
} else if (props.nodeConfig.type == 4) {
|
|
||||||
resetConditionNodesErr()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let emits = defineEmits(['update:flowPermission', 'update:nodeConfig'])
|
|
||||||
let store = useWorkFlowStoreWithOut()
|
|
||||||
let {
|
|
||||||
setPromoter,
|
|
||||||
setApprover,
|
|
||||||
setCopyer,
|
|
||||||
setCondition,
|
|
||||||
setFlowPermission,
|
|
||||||
setApproverConfig,
|
|
||||||
setCopyerConfig,
|
|
||||||
setConditionsConfig
|
|
||||||
} = store
|
|
||||||
let isTried = computed(() => store.isTried)
|
|
||||||
let flowPermission1 = computed(() => store.flowPermission1)
|
|
||||||
let approverConfig1 = computed(() => store.approverConfig1)
|
|
||||||
let copyerConfig1 = computed(() => store.copyerConfig1)
|
|
||||||
let conditionsConfig1 = computed(() => store.conditionsConfig1)
|
|
||||||
watch(flowPermission1, (flow) => {
|
|
||||||
if (flow.flag && flow.id === _uid) {
|
|
||||||
emits('update:flowPermission', flow.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
watch(approverConfig1, (approver) => {
|
|
||||||
if (approver.flag && approver.id === _uid) {
|
|
||||||
emits('update:nodeConfig', approver.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
watch(copyerConfig1, (copyer) => {
|
|
||||||
if (copyer.flag && copyer.id === _uid) {
|
|
||||||
emits('update:nodeConfig', copyer.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
watch(conditionsConfig1, (condition) => {
|
|
||||||
if (condition.flag && condition.id === _uid) {
|
|
||||||
emits('update:nodeConfig', condition.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const clickEvent = (index) => {
|
|
||||||
if (index || index === 0) {
|
|
||||||
isInputList.value[index] = true
|
|
||||||
} else {
|
|
||||||
isInput.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const blurEvent = (index) => {
|
|
||||||
if (index || index === 0) {
|
|
||||||
isInputList.value[index] = false
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.conditionNodes[index].nodeName =
|
|
||||||
props.nodeConfig.conditionNodes[index].nodeName || '条件'
|
|
||||||
} else {
|
|
||||||
isInput.value = false
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.nodeName = props.nodeConfig.nodeName || defaultText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const delNode = () => {
|
|
||||||
emits('update:nodeConfig', props.nodeConfig.childNode)
|
|
||||||
}
|
|
||||||
const addTerm = () => {
|
|
||||||
let len = props.nodeConfig.conditionNodes.length + 1
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.conditionNodes.push({
|
|
||||||
nodeName: '条件' + len,
|
|
||||||
type: 3,
|
|
||||||
priorityLevel: len,
|
|
||||||
conditionList: [],
|
|
||||||
nodeUserList: [],
|
|
||||||
childNode: null
|
|
||||||
})
|
|
||||||
resetConditionNodesErr()
|
|
||||||
emits('update:nodeConfig', props.nodeConfig)
|
|
||||||
}
|
|
||||||
const delTerm = (index) => {
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.conditionNodes.splice(index, 1)
|
|
||||||
props.nodeConfig.conditionNodes.map((item, index) => {
|
|
||||||
item.priorityLevel = index + 1
|
|
||||||
item.nodeName = `条件${index + 1}`
|
|
||||||
})
|
|
||||||
resetConditionNodesErr()
|
|
||||||
emits('update:nodeConfig', props.nodeConfig)
|
|
||||||
if (props.nodeConfig.conditionNodes.length == 1) {
|
|
||||||
if (props.nodeConfig.childNode) {
|
|
||||||
if (props.nodeConfig.conditionNodes[0].childNode) {
|
|
||||||
reData(props.nodeConfig.conditionNodes[0].childNode, props.nodeConfig.childNode)
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.conditionNodes[0].childNode = props.nodeConfig.childNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emits('update:nodeConfig', props.nodeConfig.conditionNodes[0].childNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const reData = (data, addData) => {
|
|
||||||
if (!data.childNode) {
|
|
||||||
data.childNode = addData
|
|
||||||
} else {
|
|
||||||
reData(data.childNode, addData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const setPerson = (priorityLevel) => {
|
|
||||||
var { type } = props.nodeConfig
|
|
||||||
if (type == 0) {
|
|
||||||
setPromoter(true)
|
|
||||||
setFlowPermission({
|
|
||||||
value: props.flowPermission,
|
|
||||||
flag: false,
|
|
||||||
id: _uid
|
|
||||||
})
|
|
||||||
} else if (type == 1) {
|
|
||||||
setApprover(true)
|
|
||||||
setApproverConfig({
|
|
||||||
value: {
|
|
||||||
...JSON.parse(JSON.stringify(props.nodeConfig)),
|
|
||||||
...{ settype: props.nodeConfig.settype ? props.nodeConfig.settype : 1 }
|
|
||||||
},
|
|
||||||
flag: false,
|
|
||||||
id: _uid
|
|
||||||
})
|
|
||||||
} else if (type == 2) {
|
|
||||||
setCopyer(true)
|
|
||||||
setCopyerConfig({
|
|
||||||
value: JSON.parse(JSON.stringify(props.nodeConfig)),
|
|
||||||
flag: false,
|
|
||||||
id: _uid
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
setCondition(true)
|
|
||||||
setConditionsConfig({
|
|
||||||
value: JSON.parse(JSON.stringify(props.nodeConfig)),
|
|
||||||
priorityLevel,
|
|
||||||
flag: false,
|
|
||||||
id: _uid
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const arrTransfer = (index, type = 1) => {
|
|
||||||
//向左-1,向右1
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.nodeConfig.conditionNodes[index] = props.nodeConfig.conditionNodes.splice(
|
|
||||||
index + type,
|
|
||||||
1,
|
|
||||||
props.nodeConfig.conditionNodes[index]
|
|
||||||
)[0]
|
|
||||||
props.nodeConfig.conditionNodes.map((item, index) => {
|
|
||||||
item.priorityLevel = index + 1
|
|
||||||
})
|
|
||||||
resetConditionNodesErr()
|
|
||||||
emits('update:nodeConfig', props.nodeConfig)
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,165 +0,0 @@
|
||||||
/**
|
|
||||||
* todo
|
|
||||||
*/
|
|
||||||
export const arrToStr = (arr?: [{ name: string }]) => {
|
|
||||||
if (arr) {
|
|
||||||
return arr
|
|
||||||
.map((item) => {
|
|
||||||
return item.name
|
|
||||||
})
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setApproverStr = (nodeConfig: any) => {
|
|
||||||
if (nodeConfig.settype == 1) {
|
|
||||||
if (nodeConfig.nodeUserList.length == 1) {
|
|
||||||
return nodeConfig.nodeUserList[0].name
|
|
||||||
} else if (nodeConfig.nodeUserList.length > 1) {
|
|
||||||
if (nodeConfig.examineMode == 1) {
|
|
||||||
return arrToStr(nodeConfig.nodeUserList)
|
|
||||||
} else if (nodeConfig.examineMode == 2) {
|
|
||||||
return nodeConfig.nodeUserList.length + '人会签'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (nodeConfig.settype == 2) {
|
|
||||||
const level =
|
|
||||||
nodeConfig.directorLevel == 1 ? '直接主管' : '第' + nodeConfig.directorLevel + '级主管'
|
|
||||||
if (nodeConfig.examineMode == 1) {
|
|
||||||
return level
|
|
||||||
} else if (nodeConfig.examineMode == 2) {
|
|
||||||
return level + '会签'
|
|
||||||
}
|
|
||||||
} else if (nodeConfig.settype == 4) {
|
|
||||||
if (nodeConfig.selectRange == 1) {
|
|
||||||
return '发起人自选'
|
|
||||||
} else {
|
|
||||||
if (nodeConfig.nodeUserList.length > 0) {
|
|
||||||
if (nodeConfig.selectRange == 2) {
|
|
||||||
return '发起人自选'
|
|
||||||
} else {
|
|
||||||
return '发起人从' + nodeConfig.nodeUserList[0].name + '中自选'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (nodeConfig.settype == 5) {
|
|
||||||
return '发起人自己'
|
|
||||||
} else if (nodeConfig.settype == 7) {
|
|
||||||
return '从直接主管到通讯录中级别最高的第' + nodeConfig.examineEndDirectorLevel + '个层级主管'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const copyerStr = (nodeConfig: any) => {
|
|
||||||
if (nodeConfig.nodeUserList.length != 0) {
|
|
||||||
return arrToStr(nodeConfig.nodeUserList)
|
|
||||||
} else {
|
|
||||||
if (nodeConfig.ccSelfSelectFlag == 1) {
|
|
||||||
return '发起人自选'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const conditionStr = (nodeConfig, index) => {
|
|
||||||
const { conditionList, nodeUserList } = nodeConfig.conditionNodes[index]
|
|
||||||
if (conditionList.length == 0) {
|
|
||||||
return index == nodeConfig.conditionNodes.length - 1 &&
|
|
||||||
nodeConfig.conditionNodes[0].conditionList.length != 0
|
|
||||||
? '其他条件进入此流程'
|
|
||||||
: '请设置条件'
|
|
||||||
} else {
|
|
||||||
let str = ''
|
|
||||||
for (let i = 0; i < conditionList.length; i++) {
|
|
||||||
const {
|
|
||||||
columnId,
|
|
||||||
columnType,
|
|
||||||
showType,
|
|
||||||
showName,
|
|
||||||
optType,
|
|
||||||
zdy1,
|
|
||||||
opt1,
|
|
||||||
zdy2,
|
|
||||||
opt2,
|
|
||||||
fixedDownBoxValue
|
|
||||||
} = conditionList[i]
|
|
||||||
if (columnId == 0) {
|
|
||||||
if (nodeUserList.length != 0) {
|
|
||||||
str += '发起人属于:'
|
|
||||||
str +=
|
|
||||||
nodeUserList
|
|
||||||
.map((item) => {
|
|
||||||
return item.name
|
|
||||||
})
|
|
||||||
.join('或') + ' 并且 '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (columnType == 'String' && showType == '3') {
|
|
||||||
if (zdy1) {
|
|
||||||
str += showName + '属于:' + dealStr(zdy1, JSON.parse(fixedDownBoxValue)) + ' 并且 '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (columnType == 'Double') {
|
|
||||||
if (optType != 6 && zdy1) {
|
|
||||||
const optTypeStr = ['', '<', '>', '≤', '=', '≥'][optType]
|
|
||||||
str += `${showName} ${optTypeStr} ${zdy1} 并且 `
|
|
||||||
} else if (optType == 6 && zdy1 && zdy2) {
|
|
||||||
str += `${zdy1} ${opt1} ${showName} ${opt2} ${zdy2} 并且 `
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str ? str.substring(0, str.length - 4) : '请设置条件'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dealStr = (str: string, obj) => {
|
|
||||||
const arr = []
|
|
||||||
const list = str.split(',')
|
|
||||||
for (const elem in obj) {
|
|
||||||
list.map((item) => {
|
|
||||||
if (item == elem) {
|
|
||||||
arr.push(obj[elem].value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return arr.join('或')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const removeEle = (arr, elem, key = 'id') => {
|
|
||||||
let includesIndex
|
|
||||||
arr.map((item, index) => {
|
|
||||||
if (item[key] == elem[key]) {
|
|
||||||
includesIndex = index
|
|
||||||
}
|
|
||||||
})
|
|
||||||
arr.splice(includesIndex, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const bgColors = ['87, 106, 149', '255, 148, 62', '50, 150, 250']
|
|
||||||
export const placeholderList = ['发起人', '审核人', '抄送人']
|
|
||||||
export const setTypes = [
|
|
||||||
{ value: 1, label: '指定成员' },
|
|
||||||
{ value: 2, label: '主管' },
|
|
||||||
{ value: 4, label: '发起人自选' },
|
|
||||||
{ value: 5, label: '发起人自己' },
|
|
||||||
{ value: 7, label: '连续多级主管' }
|
|
||||||
]
|
|
||||||
|
|
||||||
export const selectModes = [
|
|
||||||
{ value: 1, label: '选一个人' },
|
|
||||||
{ value: 2, label: '选多个人' }
|
|
||||||
]
|
|
||||||
|
|
||||||
export const selectRanges = [
|
|
||||||
{ value: 1, label: '全公司' },
|
|
||||||
{ value: 2, label: '指定成员' },
|
|
||||||
{ value: 3, label: '指定角色' }
|
|
||||||
]
|
|
||||||
|
|
||||||
export const optTypes = [
|
|
||||||
{ value: '1', label: '小于' },
|
|
||||||
{ value: '2', label: '大于' },
|
|
||||||
{ value: '3', label: '小于等于' },
|
|
||||||
{ value: '4', label: '等于' },
|
|
||||||
{ value: '5', label: '大于等于' },
|
|
||||||
{ value: '6', label: '介于两个数之间' }
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,168 @@
|
||||||
|
<template>
|
||||||
|
<div class="node-handler-wrapper">
|
||||||
|
<div class="node-handler" v-if="props.showAdd">
|
||||||
|
<el-popover
|
||||||
|
trigger="hover"
|
||||||
|
v-model:visible="popoverShow"
|
||||||
|
placement="right-start"
|
||||||
|
width="auto"
|
||||||
|
>
|
||||||
|
<div class="handler-item-wrapper">
|
||||||
|
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||||
|
<div class="approve handler-item-icon">
|
||||||
|
<span class="iconfont icon-approve icon-size"></span>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item-text">审批人</div>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
|
||||||
|
<div class="handler-item-icon copy">
|
||||||
|
<span class="iconfont icon-size icon-copy"></span>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item-text">抄送</div>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item" @click="addNode(NodeType.CONDITION_BRANCH_NODE)">
|
||||||
|
<div class="handler-item-icon condition">
|
||||||
|
<span class="iconfont icon-size icon-exclusive"></span>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item-text">条件分支</div>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item" @click="addNode(NodeType.PARALLEL_BRANCH_NODE)">
|
||||||
|
<div class="handler-item-icon condition">
|
||||||
|
<span class="iconfont icon-size icon-parallel"></span>
|
||||||
|
</div>
|
||||||
|
<div class="handler-item-text">并行分支</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #reference>
|
||||||
|
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ApproveMethodType,
|
||||||
|
AssignEmptyHandlerType,
|
||||||
|
AssignStartUserHandlerType,
|
||||||
|
NODE_DEFAULT_NAME,
|
||||||
|
NodeType,
|
||||||
|
RejectHandlerType,
|
||||||
|
SimpleFlowNode
|
||||||
|
} from './consts'
|
||||||
|
import { generateUUID } from '@/utils'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'NodeHandler'
|
||||||
|
})
|
||||||
|
const popoverShow = ref(false)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
childNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
showAdd: {
|
||||||
|
// 是否显示添加节点
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:childNode'])
|
||||||
|
|
||||||
|
const addNode = (type: number) => {
|
||||||
|
popoverShow.value = false
|
||||||
|
if (type === NodeType.USER_TASK_NODE) {
|
||||||
|
const id = 'Activity_' + generateUUID()
|
||||||
|
const data: SimpleFlowNode = {
|
||||||
|
id: id,
|
||||||
|
name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string,
|
||||||
|
showText: '',
|
||||||
|
type: NodeType.USER_TASK_NODE,
|
||||||
|
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
|
||||||
|
// 超时处理
|
||||||
|
rejectHandler: {
|
||||||
|
type: RejectHandlerType.FINISH_PROCESS
|
||||||
|
},
|
||||||
|
timeoutHandler: {
|
||||||
|
enable: false
|
||||||
|
},
|
||||||
|
assignEmptyHandler: {
|
||||||
|
type: AssignEmptyHandlerType.APPROVE
|
||||||
|
},
|
||||||
|
assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
|
||||||
|
childNode: props.childNode
|
||||||
|
}
|
||||||
|
emits('update:childNode', data)
|
||||||
|
}
|
||||||
|
if (type === NodeType.COPY_TASK_NODE) {
|
||||||
|
const data: SimpleFlowNode = {
|
||||||
|
id: 'Activity_' + generateUUID(),
|
||||||
|
name: NODE_DEFAULT_NAME.get(NodeType.COPY_TASK_NODE) as string,
|
||||||
|
showText: '',
|
||||||
|
type: NodeType.COPY_TASK_NODE,
|
||||||
|
childNode: props.childNode
|
||||||
|
}
|
||||||
|
emits('update:childNode', data)
|
||||||
|
}
|
||||||
|
if (type === NodeType.CONDITION_BRANCH_NODE) {
|
||||||
|
const data: SimpleFlowNode = {
|
||||||
|
name: '条件分支',
|
||||||
|
type: NodeType.CONDITION_BRANCH_NODE,
|
||||||
|
id: 'GateWay_' + generateUUID(),
|
||||||
|
childNode: props.childNode,
|
||||||
|
conditionNodes: [
|
||||||
|
{
|
||||||
|
id: 'Flow_' + generateUUID(),
|
||||||
|
name: '条件1',
|
||||||
|
showText: '',
|
||||||
|
type: NodeType.CONDITION_NODE,
|
||||||
|
childNode: undefined,
|
||||||
|
conditionType: 1,
|
||||||
|
defaultFlow: false
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Flow_' + generateUUID(),
|
||||||
|
name: '其它情况',
|
||||||
|
showText: '其它情况进入此流程',
|
||||||
|
type: NodeType.CONDITION_NODE,
|
||||||
|
childNode: undefined,
|
||||||
|
conditionType: undefined,
|
||||||
|
defaultFlow: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
emits('update:childNode', data)
|
||||||
|
}
|
||||||
|
if (type === NodeType.PARALLEL_BRANCH_NODE) {
|
||||||
|
const data: SimpleFlowNode = {
|
||||||
|
name: '并行分支',
|
||||||
|
type: NodeType.PARALLEL_BRANCH_NODE,
|
||||||
|
id: 'GateWay_' + generateUUID(),
|
||||||
|
childNode: props.childNode,
|
||||||
|
conditionNodes: [
|
||||||
|
{
|
||||||
|
id: 'Flow_' + generateUUID(),
|
||||||
|
name: '并行1',
|
||||||
|
showText: '无需配置条件同时执行',
|
||||||
|
type: NodeType.CONDITION_NODE,
|
||||||
|
childNode: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Flow_' + generateUUID(),
|
||||||
|
name: '并行2',
|
||||||
|
showText: '无需配置条件同时执行',
|
||||||
|
type: NodeType.CONDITION_NODE,
|
||||||
|
childNode: undefined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
emits('update:childNode', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,107 @@
|
||||||
|
<template>
|
||||||
|
<!-- 发起人节点 -->
|
||||||
|
<StartUserNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.START_USER_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
/>
|
||||||
|
<!-- 审批节点 -->
|
||||||
|
<UserTaskNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@update:flow-node="handleModelValueUpdate"
|
||||||
|
@find:parent-node="findFromParentNode"
|
||||||
|
/>
|
||||||
|
<!-- 抄送节点 -->
|
||||||
|
<CopyTaskNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@update:flow-node="handleModelValueUpdate"
|
||||||
|
/>
|
||||||
|
<!-- 条件节点 -->
|
||||||
|
<ExclusiveNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.CONDITION_BRANCH_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@update:model-value="handleModelValueUpdate"
|
||||||
|
@find:parent-node="findFromParentNode"
|
||||||
|
/>
|
||||||
|
<!-- 并行节点 -->
|
||||||
|
<ParallelNode
|
||||||
|
v-if="currentNode && currentNode.type === NodeType.PARALLEL_BRANCH_NODE"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@update:model-value="handleModelValueUpdate"
|
||||||
|
@find:parent-node="findFromParentNode"
|
||||||
|
/>
|
||||||
|
<!-- 递归显示孩子节点 -->
|
||||||
|
<ProcessNodeTree
|
||||||
|
v-if="currentNode && currentNode.childNode"
|
||||||
|
v-model:flow-node="currentNode.childNode"
|
||||||
|
:parent-node="currentNode"
|
||||||
|
@find:recursive-find-parent-node="recursiveFindParentNode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 结束节点 -->
|
||||||
|
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import StartUserNode from './nodes/StartUserNode.vue'
|
||||||
|
import EndEventNode from './nodes/EndEventNode.vue'
|
||||||
|
import UserTaskNode from './nodes/UserTaskNode.vue'
|
||||||
|
import CopyTaskNode from './nodes/CopyTaskNode.vue'
|
||||||
|
import ExclusiveNode from './nodes/ExclusiveNode.vue'
|
||||||
|
import ParallelNode from './nodes/ParallelNode.vue'
|
||||||
|
import { SimpleFlowNode, NodeType } from './consts'
|
||||||
|
import { useWatchNode } from './node'
|
||||||
|
defineOptions({
|
||||||
|
name: 'ProcessNodeTree'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
parentNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
default: () => null
|
||||||
|
},
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
default: () => null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||||
|
'find:recursiveFindParentNode': [
|
||||||
|
nodeList: SimpleFlowNode[],
|
||||||
|
curentNode: SimpleFlowNode,
|
||||||
|
nodeType: number
|
||||||
|
]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
|
||||||
|
// 用于删除节点
|
||||||
|
const handleModelValueUpdate = (updateValue) => {
|
||||||
|
emits('update:flowNode', updateValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const findFromParentNode = (nodeList: SimpleFlowNode[], nodeType: number) => {
|
||||||
|
emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归从父节点中查询匹配的节点
|
||||||
|
const recursiveFindParentNode = (
|
||||||
|
nodeList: SimpleFlowNode[],
|
||||||
|
findNode: SimpleFlowNode,
|
||||||
|
nodeType: number
|
||||||
|
) => {
|
||||||
|
if (!findNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (findNode.type === NodeType.START_USER_NODE) {
|
||||||
|
nodeList.push(findNode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (findNode.type === nodeType) {
|
||||||
|
nodeList.push(findNode)
|
||||||
|
}
|
||||||
|
emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,215 @@
|
||||||
|
<template>
|
||||||
|
<div class="simple-flow-canvas" v-loading="loading">
|
||||||
|
<div class="simple-flow-container" >
|
||||||
|
<div class="top-area-container">
|
||||||
|
<div class="top-actions">
|
||||||
|
<div class="canvas-control">
|
||||||
|
<span class="control-scale-group">
|
||||||
|
<span class="control-scale-button"> <Icon icon="ep:plus" @click="zoomOut()" /></span>
|
||||||
|
<span class="control-scale-label">{{ scaleValue }}%</span>
|
||||||
|
<span class="control-scale-button"><Icon icon="ep:minus" @click="zoomIn()" /></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" @click="saveSimpleFlowModel">保存</el-button>
|
||||||
|
<!-- <el-button type="primary">全局设置</el-button> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
|
||||||
|
<ProcessNodeTree
|
||||||
|
v-if="processNodeTree"
|
||||||
|
v-model:flow-node="processNodeTree"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||||
|
<div class="mb-2">以下节点内容不完善,请修改后保存</div>
|
||||||
|
<div
|
||||||
|
class="mb-3 b-rounded-1 bg-gray-100 p-2 line-height-normal"
|
||||||
|
v-for="(item, index) in errorNodes"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
{{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }}
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="errorDialogVisible = false">知道了</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||||
|
import { updateBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
|
||||||
|
import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts'
|
||||||
|
import { getModel } from '@/api/bpm/model'
|
||||||
|
import { getForm, FormVO } from '@/api/bpm/form'
|
||||||
|
import { handleTree } from '@/utils/tree'
|
||||||
|
import * as RoleApi from '@/api/system/role'
|
||||||
|
import * as DeptApi from '@/api/system/dept'
|
||||||
|
import * as PostApi from '@/api/system/post'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||||
|
import { fa } from 'element-plus/es/locale'
|
||||||
|
defineOptions({
|
||||||
|
name: 'SimpleProcessDesigner'
|
||||||
|
})
|
||||||
|
const router = useRouter() // 路由
|
||||||
|
const props = defineProps({
|
||||||
|
modelId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const loading = ref(true)
|
||||||
|
const formFields = ref<string[]>([])
|
||||||
|
const formType = ref(20)
|
||||||
|
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||||
|
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||||
|
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||||
|
const deptTreeOptions = ref()
|
||||||
|
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||||
|
provide('formFields', formFields)
|
||||||
|
provide('formType', formType)
|
||||||
|
provide('roleList', roleOptions)
|
||||||
|
provide('postList', postOptions)
|
||||||
|
provide('userList', userOptions)
|
||||||
|
provide('deptList', deptOptions)
|
||||||
|
provide('userGroupList', userGroupOptions)
|
||||||
|
provide('deptTree', deptTreeOptions)
|
||||||
|
|
||||||
|
const message = useMessage() // 国际化
|
||||||
|
const processNodeTree = ref<SimpleFlowNode | undefined>()
|
||||||
|
const errorDialogVisible = ref(false)
|
||||||
|
let errorNodes: SimpleFlowNode[] = []
|
||||||
|
const saveSimpleFlowModel = async () => {
|
||||||
|
if (!props.modelId) {
|
||||||
|
message.error('缺少模型 modelId 编号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errorNodes = []
|
||||||
|
validateNode(processNodeTree.value, errorNodes)
|
||||||
|
if (errorNodes.length > 0) {
|
||||||
|
errorDialogVisible.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
id: props.modelId,
|
||||||
|
simpleModel: processNodeTree.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateBpmSimpleModel(data)
|
||||||
|
if (result) {
|
||||||
|
message.success('修改成功')
|
||||||
|
close()
|
||||||
|
} else {
|
||||||
|
message.alert('修改失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
|
||||||
|
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
|
||||||
|
if (node) {
|
||||||
|
const { type, showText, conditionNodes } = node
|
||||||
|
if (type == NodeType.END_EVENT_NODE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (type == NodeType.START_USER_NODE) {
|
||||||
|
validateNode(node.childNode, errorNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === NodeType.USER_TASK_NODE) {
|
||||||
|
if (!showText) {
|
||||||
|
errorNodes.push(node)
|
||||||
|
}
|
||||||
|
validateNode(node.childNode, errorNodes)
|
||||||
|
}
|
||||||
|
if (type === NodeType.COPY_TASK_NODE) {
|
||||||
|
if (!showText) {
|
||||||
|
errorNodes.push(node)
|
||||||
|
}
|
||||||
|
validateNode(node.childNode, errorNodes)
|
||||||
|
}
|
||||||
|
if (type === NodeType.CONDITION_NODE) {
|
||||||
|
if (!showText) {
|
||||||
|
errorNodes.push(node)
|
||||||
|
}
|
||||||
|
validateNode(node.childNode, errorNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == NodeType.CONDITION_BRANCH_NODE) {
|
||||||
|
conditionNodes?.forEach((item) => {
|
||||||
|
validateNode(item, errorNodes)
|
||||||
|
})
|
||||||
|
validateNode(node.childNode, errorNodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
router.push({ path: '/bpm/manager/model' })
|
||||||
|
}
|
||||||
|
let scaleValue = ref(100)
|
||||||
|
const MAX_SCALE_VALUE = 200
|
||||||
|
const MIN_SCALE_VALUE = 50
|
||||||
|
// 放大
|
||||||
|
const zoomOut = () => {
|
||||||
|
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scaleValue.value += 10
|
||||||
|
}
|
||||||
|
// 缩小
|
||||||
|
const zoomIn = () => {
|
||||||
|
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scaleValue.value -= 10
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
// 获取表单字段
|
||||||
|
const bpmnModel = await getModel(props.modelId)
|
||||||
|
if (bpmnModel) {
|
||||||
|
formType.value = bpmnModel.formType
|
||||||
|
if (formType.value === 10) {
|
||||||
|
const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
|
||||||
|
formFields.value = bpmnForm?.fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获得角色列表
|
||||||
|
roleOptions.value = await RoleApi.getSimpleRoleList()
|
||||||
|
// 获得岗位列表
|
||||||
|
postOptions.value = await PostApi.getSimplePostList()
|
||||||
|
// 获得用户列表
|
||||||
|
userOptions.value = await UserApi.getSimpleUserList()
|
||||||
|
// 获得部门列表
|
||||||
|
deptOptions.value = await DeptApi.getSimpleDeptList()
|
||||||
|
|
||||||
|
deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
|
||||||
|
// 获取用户组列表
|
||||||
|
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
|
||||||
|
|
||||||
|
// 获取 SIMPLE 设计器模型
|
||||||
|
const result = await getBpmSimpleModel(props.modelId)
|
||||||
|
if (result) {
|
||||||
|
processNodeTree.value = result
|
||||||
|
} else {
|
||||||
|
// 初始值
|
||||||
|
processNodeTree.value = {
|
||||||
|
name: '发起人',
|
||||||
|
type: NodeType.START_USER_NODE,
|
||||||
|
id: NodeId.START_USER_NODE_ID,
|
||||||
|
childNode: {
|
||||||
|
id: NodeId.END_EVENT_NODE_ID,
|
||||||
|
name: '结束',
|
||||||
|
type: NodeType.END_EVENT_NODE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,544 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { DictDataVO } from '@/api/system/dict/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型
|
||||||
|
*/
|
||||||
|
export enum NodeType {
|
||||||
|
/**
|
||||||
|
* 结束节点
|
||||||
|
*/
|
||||||
|
END_EVENT_NODE = 1,
|
||||||
|
/**
|
||||||
|
* 发起人节点
|
||||||
|
*/
|
||||||
|
START_USER_NODE = 10,
|
||||||
|
/**
|
||||||
|
* 审批人节点
|
||||||
|
*/
|
||||||
|
USER_TASK_NODE = 11,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抄送人节点
|
||||||
|
*/
|
||||||
|
COPY_TASK_NODE = 12,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件节点
|
||||||
|
*/
|
||||||
|
CONDITION_NODE = 50,
|
||||||
|
/**
|
||||||
|
* 条件分支节点 (对应排他网关)
|
||||||
|
*/
|
||||||
|
CONDITION_BRANCH_NODE = 51,
|
||||||
|
/**
|
||||||
|
* 并行分支节点 (对应并行网关)
|
||||||
|
*/
|
||||||
|
PARALLEL_BRANCH_NODE = 52,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包容分支节点 (对应包容网关)
|
||||||
|
*/
|
||||||
|
INCLUSIVE_BRANCH_NODE = 53
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NodeId {
|
||||||
|
/**
|
||||||
|
* 发起人节点 Id
|
||||||
|
*/
|
||||||
|
START_USER_NODE_ID = 'StartUserNode',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起人节点 Id
|
||||||
|
*/
|
||||||
|
END_EVENT_NODE_ID = 'EndEvent'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点结构定义
|
||||||
|
*/
|
||||||
|
export interface SimpleFlowNode {
|
||||||
|
id: string
|
||||||
|
type: NodeType
|
||||||
|
name: string
|
||||||
|
showText?: string
|
||||||
|
// 孩子节点
|
||||||
|
childNode?: SimpleFlowNode
|
||||||
|
// 条件节点
|
||||||
|
conditionNodes?: SimpleFlowNode[]
|
||||||
|
// 审批类型
|
||||||
|
approveType?: ApproveType
|
||||||
|
// 候选人策略
|
||||||
|
candidateStrategy?: number
|
||||||
|
// 候选人参数
|
||||||
|
candidateParam?: string
|
||||||
|
// 多人审批方式
|
||||||
|
approveMethod?: ApproveMethodType
|
||||||
|
//通过比例
|
||||||
|
approveRatio?: number
|
||||||
|
// 审批按钮设置
|
||||||
|
buttonsSetting?: any[]
|
||||||
|
// 表单权限
|
||||||
|
fieldsPermission?: Array<Record<string, string>>
|
||||||
|
// 审批任务超时处理
|
||||||
|
timeoutHandler?: TimeoutHandler
|
||||||
|
// 审批任务拒绝处理
|
||||||
|
rejectHandler?: RejectHandler
|
||||||
|
// 审批人为空的处理
|
||||||
|
assignEmptyHandler?: AssignEmptyHandler
|
||||||
|
// 审批节点的审批人与发起人相同时,对应的处理类型
|
||||||
|
assignStartUserHandlerType?: number
|
||||||
|
// 条件类型
|
||||||
|
conditionType?: ConditionType
|
||||||
|
// 条件表达式
|
||||||
|
conditionExpression?: string
|
||||||
|
// 条件组
|
||||||
|
conditionGroups?: ConditionGroup
|
||||||
|
// 是否默认的条件
|
||||||
|
defaultFlow?: boolean
|
||||||
|
|
||||||
|
}
|
||||||
|
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
||||||
|
export enum CandidateStrategy {
|
||||||
|
/**
|
||||||
|
* 指定角色
|
||||||
|
*/
|
||||||
|
ROLE = 10,
|
||||||
|
/**
|
||||||
|
* 部门成员
|
||||||
|
*/
|
||||||
|
DEPT_MEMBER = 20,
|
||||||
|
/**
|
||||||
|
* 部门的负责人
|
||||||
|
*/
|
||||||
|
DEPT_LEADER = 21,
|
||||||
|
/**
|
||||||
|
* 连续多级部门的负责人
|
||||||
|
*/
|
||||||
|
MULTI_LEVEL_DEPT_LEADER = 23,
|
||||||
|
/**
|
||||||
|
* 指定岗位
|
||||||
|
*/
|
||||||
|
POST = 22,
|
||||||
|
/**
|
||||||
|
* 指定用户
|
||||||
|
*/
|
||||||
|
USER = 30,
|
||||||
|
/**
|
||||||
|
* 发起人自选
|
||||||
|
*/
|
||||||
|
START_USER_SELECT = 35,
|
||||||
|
/**
|
||||||
|
* 发起人自己
|
||||||
|
*/
|
||||||
|
START_USER = 36,
|
||||||
|
/**
|
||||||
|
* 发起人部门负责人
|
||||||
|
*/
|
||||||
|
START_USER_DEPT_LEADER = 37,
|
||||||
|
/**
|
||||||
|
* 发起人连续多级部门的负责人
|
||||||
|
*/
|
||||||
|
START_USER_MULTI_LEVEL_DEPT_LEADER = 38,
|
||||||
|
/**
|
||||||
|
* 指定用户组
|
||||||
|
*/
|
||||||
|
USER_GROUP = 40,
|
||||||
|
/**
|
||||||
|
* 流程表达式
|
||||||
|
*/
|
||||||
|
EXPRESSION = 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多人审批方式类型枚举 ( 用于审批节点 )
|
||||||
|
export enum ApproveMethodType {
|
||||||
|
/**
|
||||||
|
* 随机挑选一人审批
|
||||||
|
*/
|
||||||
|
RANDOM_SELECT_ONE_APPROVE = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多人会签(按通过比例)
|
||||||
|
*/
|
||||||
|
APPROVE_BY_RATIO = 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多人或签(通过只需一人,拒绝只需一人)
|
||||||
|
*/
|
||||||
|
ANY_APPROVE = 3,
|
||||||
|
/**
|
||||||
|
* 多人依次审批
|
||||||
|
*/
|
||||||
|
SEQUENTIAL_APPROVE = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批拒绝结构定义
|
||||||
|
*/
|
||||||
|
export type RejectHandler = {
|
||||||
|
// 审批拒绝类型
|
||||||
|
type: RejectHandlerType
|
||||||
|
// 回退节点 Id
|
||||||
|
returnNodeId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批超时结构定义
|
||||||
|
*/
|
||||||
|
export type TimeoutHandler = {
|
||||||
|
// 是否开启超时处理
|
||||||
|
enable: boolean
|
||||||
|
// 超时执行的动作
|
||||||
|
type?: number
|
||||||
|
// 超时时间设置
|
||||||
|
timeDuration?: string
|
||||||
|
// 执行动作是自动提醒, 最大提醒次数
|
||||||
|
maxRemindCount?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人为空的结构定义
|
||||||
|
*/
|
||||||
|
export type AssignEmptyHandler = {
|
||||||
|
// 审批人为空的处理类型
|
||||||
|
type: AssignEmptyHandlerType
|
||||||
|
// 指定用户的编号数组
|
||||||
|
userIds?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批拒绝类型枚举
|
||||||
|
export enum RejectHandlerType {
|
||||||
|
/**
|
||||||
|
* 结束流程
|
||||||
|
*/
|
||||||
|
FINISH_PROCESS = 1,
|
||||||
|
/**
|
||||||
|
* 驳回到指定节点
|
||||||
|
*/
|
||||||
|
RETURN_USER_TASK = 2
|
||||||
|
}
|
||||||
|
// 用户任务超时处理类型枚举
|
||||||
|
export enum TimeoutHandlerType {
|
||||||
|
/**
|
||||||
|
* 自动提醒
|
||||||
|
*/
|
||||||
|
REMINDER = 1,
|
||||||
|
/**
|
||||||
|
* 自动同意
|
||||||
|
*/
|
||||||
|
APPROVE = 2,
|
||||||
|
/**
|
||||||
|
* 自动拒绝
|
||||||
|
*/
|
||||||
|
REJECT = 3
|
||||||
|
}
|
||||||
|
// 用户任务的审批人为空时,处理类型枚举
|
||||||
|
export enum AssignEmptyHandlerType {
|
||||||
|
/**
|
||||||
|
* 自动通过
|
||||||
|
*/
|
||||||
|
APPROVE = 1,
|
||||||
|
/**
|
||||||
|
* 自动拒绝
|
||||||
|
*/
|
||||||
|
REJECT = 2,
|
||||||
|
/**
|
||||||
|
* 指定人员审批
|
||||||
|
*/
|
||||||
|
ASSIGN_USER,
|
||||||
|
/**
|
||||||
|
* 转交给流程管理员
|
||||||
|
*/
|
||||||
|
ASSIGN_ADMIN = 4
|
||||||
|
}
|
||||||
|
// 用户任务的审批人与发起人相同时,处理类型枚举
|
||||||
|
export enum AssignStartUserHandlerType {
|
||||||
|
/**
|
||||||
|
* 由发起人对自己审批
|
||||||
|
*/
|
||||||
|
START_USER_AUDIT = 1,
|
||||||
|
/**
|
||||||
|
* 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过
|
||||||
|
*/
|
||||||
|
SKIP = 2,
|
||||||
|
/**
|
||||||
|
* 转交给部门负责人审批
|
||||||
|
*/
|
||||||
|
ASSIGN_DEPT_LEADER = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户任务的审批类型。 【参考飞书】
|
||||||
|
export enum ApproveType {
|
||||||
|
/**
|
||||||
|
* 人工审批
|
||||||
|
*/
|
||||||
|
USER = 1,
|
||||||
|
/**
|
||||||
|
* 自动通过
|
||||||
|
*/
|
||||||
|
AUTO_APPROVE = 2,
|
||||||
|
/**
|
||||||
|
* 自动拒绝
|
||||||
|
*/
|
||||||
|
AUTO_REJECT = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间单位枚举
|
||||||
|
export enum TimeUnitType {
|
||||||
|
/**
|
||||||
|
* 分钟
|
||||||
|
*/
|
||||||
|
MINUTE = 1,
|
||||||
|
/**
|
||||||
|
* 小时
|
||||||
|
*/
|
||||||
|
HOUR = 2,
|
||||||
|
/**
|
||||||
|
* 天
|
||||||
|
*/
|
||||||
|
DAY = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 条件配置类型 ( 用于条件节点配置 )
|
||||||
|
export enum ConditionType {
|
||||||
|
/**
|
||||||
|
* 条件表达式
|
||||||
|
*/
|
||||||
|
EXPRESSION = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件规则
|
||||||
|
*/
|
||||||
|
RULE = 2
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 表单权限的枚举
|
||||||
|
*/
|
||||||
|
export enum FieldPermissionType {
|
||||||
|
/**
|
||||||
|
* 只读
|
||||||
|
*/
|
||||||
|
READ = '1',
|
||||||
|
/**
|
||||||
|
* 编辑
|
||||||
|
*/
|
||||||
|
WRITE = '2',
|
||||||
|
/**
|
||||||
|
* 隐藏
|
||||||
|
*/
|
||||||
|
NONE = '3'
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 操作按钮权限结构定义
|
||||||
|
*/
|
||||||
|
export type ButtonSetting = {
|
||||||
|
id: OperationButtonType
|
||||||
|
displayName: string
|
||||||
|
enable: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 操作按钮类型枚举 (用于审批节点)
|
||||||
|
export enum OperationButtonType {
|
||||||
|
/**
|
||||||
|
* 通过
|
||||||
|
*/
|
||||||
|
APPROVE = 1,
|
||||||
|
/**
|
||||||
|
* 拒绝
|
||||||
|
*/
|
||||||
|
REJECT = 2,
|
||||||
|
/**
|
||||||
|
* 转办
|
||||||
|
*/
|
||||||
|
TRANSFER = 3,
|
||||||
|
/**
|
||||||
|
* 委派
|
||||||
|
*/
|
||||||
|
DELEGATE = 4,
|
||||||
|
/**
|
||||||
|
* 加签
|
||||||
|
*/
|
||||||
|
ADD_SIGN = 5,
|
||||||
|
/**
|
||||||
|
* 回退
|
||||||
|
*/
|
||||||
|
RETURN = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件规则结构定义
|
||||||
|
*/
|
||||||
|
export type ConditionRule = {
|
||||||
|
type: number
|
||||||
|
opName: string
|
||||||
|
opCode: string
|
||||||
|
leftSide: string
|
||||||
|
rightSide: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件组结构定义
|
||||||
|
*/
|
||||||
|
export type ConditionGroup = {
|
||||||
|
// 条件组的逻辑关系是否为且
|
||||||
|
and: boolean
|
||||||
|
// 条件数组
|
||||||
|
conditions: Condition[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件结构定义
|
||||||
|
*/
|
||||||
|
export type Condition = {
|
||||||
|
// 条件规则的逻辑关系是否为且
|
||||||
|
and: boolean
|
||||||
|
rules: ConditionRule[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NODE_DEFAULT_TEXT = new Map<number, string>()
|
||||||
|
NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人')
|
||||||
|
NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
|
||||||
|
NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
|
||||||
|
NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
|
||||||
|
|
||||||
|
export const NODE_DEFAULT_NAME = new Map<number, string>()
|
||||||
|
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
|
||||||
|
NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
|
||||||
|
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
|
||||||
|
NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
|
||||||
|
|
||||||
|
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
|
||||||
|
export const CANDIDATE_STRATEGY: DictDataVO[] = [
|
||||||
|
{ label: '指定成员', value: CandidateStrategy.USER },
|
||||||
|
{ label: '指定角色', value: CandidateStrategy.ROLE },
|
||||||
|
{ label: '部门成员', value: CandidateStrategy.DEPT_MEMBER },
|
||||||
|
{ label: '部门负责人', value: CandidateStrategy.DEPT_LEADER },
|
||||||
|
{ label: '连续多级部门负责人', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER },
|
||||||
|
{ label: '发起人自选', value: CandidateStrategy.START_USER_SELECT },
|
||||||
|
{ label: '发起人本人', value: CandidateStrategy.START_USER },
|
||||||
|
{ label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
|
||||||
|
{ label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
|
||||||
|
{ label: '用户组', value: CandidateStrategy.USER_GROUP },
|
||||||
|
{ label: '流程表达式', value: CandidateStrategy.EXPRESSION }
|
||||||
|
]
|
||||||
|
// 审批节点 的审批类型
|
||||||
|
export const APPROVE_TYPE: DictDataVO[] = [
|
||||||
|
{ label: '人工审批', value: ApproveType.USER },
|
||||||
|
{ label: '自动通过', value: ApproveType.AUTO_APPROVE },
|
||||||
|
{ label: '自动拒绝', value: ApproveType.AUTO_REJECT }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const APPROVE_METHODS: DictDataVO[] = [
|
||||||
|
{ label: '按顺序依次审批', value: ApproveMethodType.SEQUENTIAL_APPROVE },
|
||||||
|
{ label: '会签(可同时审批,至少 % 人必须审批通过)', value: ApproveMethodType.APPROVE_BY_RATIO },
|
||||||
|
{ label: '或签(可同时审批,有一人通过即可)', value: ApproveMethodType.ANY_APPROVE },
|
||||||
|
{ label: '随机挑选一人审批', value: ApproveMethodType.RANDOM_SELECT_ONE_APPROVE }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const CONDITION_CONFIG_TYPES: DictDataVO[] = [
|
||||||
|
{ label: '条件表达式', value: ConditionType.EXPRESSION },
|
||||||
|
{ label: '条件规则', value: ConditionType.RULE }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 时间单位类型
|
||||||
|
export const TIME_UNIT_TYPES: DictDataVO[] = [
|
||||||
|
{ label: '分钟', value: TimeUnitType.MINUTE },
|
||||||
|
{ label: '小时', value: TimeUnitType.HOUR },
|
||||||
|
{ label: '天', value: TimeUnitType.DAY }
|
||||||
|
]
|
||||||
|
// 超时处理执行动作类型
|
||||||
|
export const TIMEOUT_HANDLER_TYPES: DictDataVO[] = [
|
||||||
|
{ label: '自动提醒', value: 1 },
|
||||||
|
{ label: '自动同意', value: 2 },
|
||||||
|
{ label: '自动拒绝', value: 3 }
|
||||||
|
]
|
||||||
|
export const REJECT_HANDLER_TYPES: DictDataVO[] = [
|
||||||
|
{ label: '终止流程', value: RejectHandlerType.FINISH_PROCESS },
|
||||||
|
{ label: '驳回到指定节点', value: RejectHandlerType.RETURN_USER_TASK }
|
||||||
|
// { label: '结束任务', value: RejectHandlerType.FINISH_TASK }
|
||||||
|
]
|
||||||
|
export const ASSIGN_EMPTY_HANDLER_TYPES: DictDataVO[] = [
|
||||||
|
{ label: '自动通过', value: 1 },
|
||||||
|
{ label: '自动拒绝', value: 2 },
|
||||||
|
{ label: '指定成员审批', value: 3 },
|
||||||
|
{ label: '转交给流程管理员', value: 4 }
|
||||||
|
]
|
||||||
|
export const ASSIGN_START_USER_HANDLER_TYPES: DictDataVO[] = [
|
||||||
|
{ label: '由发起人对自己审批', value: 1 },
|
||||||
|
{ label: '自动跳过', value: 2 },
|
||||||
|
{ label: '转交给部门负责人审批', value: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 比较运算符
|
||||||
|
export const COMPARISON_OPERATORS: DictDataVO = [
|
||||||
|
{
|
||||||
|
value: '==',
|
||||||
|
label: '等于'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '!=',
|
||||||
|
label: '不等于'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '>',
|
||||||
|
label: '大于'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '>=',
|
||||||
|
label: '大于等于'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '<',
|
||||||
|
label: '小于'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '<=',
|
||||||
|
label: '小于等于'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
// 审批操作按钮名称
|
||||||
|
export const OPERATION_BUTTON_NAME = new Map<number, string>()
|
||||||
|
OPERATION_BUTTON_NAME.set(OperationButtonType.APPROVE, '通过')
|
||||||
|
OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝')
|
||||||
|
OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办')
|
||||||
|
OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派')
|
||||||
|
OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签')
|
||||||
|
OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '回退')
|
||||||
|
|
||||||
|
// 默认的按钮权限设置
|
||||||
|
export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
|
||||||
|
{ id: OperationButtonType.APPROVE, displayName: '通过', enable: true },
|
||||||
|
{ id: OperationButtonType.REJECT, displayName: '拒绝', enable: true },
|
||||||
|
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
|
||||||
|
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
|
||||||
|
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
|
||||||
|
{ id: OperationButtonType.RETURN, displayName: '回退', enable: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 发起人的按钮权限。暂时定死,不可以编辑
|
||||||
|
export const START_USER_BUTTON_SETTING: ButtonSetting[] = [
|
||||||
|
{ id: OperationButtonType.APPROVE, displayName: '提交', enable: true },
|
||||||
|
{ 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 MULTI_LEVEL_DEPT: DictDataVO = [
|
||||||
|
{ label: '第 1 级部门', value: 1 },
|
||||||
|
{ label: '第 2 级部门', value: 2 },
|
||||||
|
{ label: '第 3 级部门', value: 3 },
|
||||||
|
{ label: '第 4 级部门', value: 4 },
|
||||||
|
{ label: '第 5 级部门', value: 5 },
|
||||||
|
{ label: '第 6 级部门', value: 6 },
|
||||||
|
{ label: '第 7 级部门', value: 7 },
|
||||||
|
{ label: '第 8 级部门', value: 8 },
|
||||||
|
{ label: '第 9 级部门', value: 9 },
|
||||||
|
{ label: '第 10 级部门', value: 10 },
|
||||||
|
{ label: '第 11 级部门', value: 11 },
|
||||||
|
{ label: '第 12 级部门', value: 12 },
|
||||||
|
{ label: '第 13 级部门', value: 13 },
|
||||||
|
{ label: '第 14 级部门', value: 14 },
|
||||||
|
{ label: '第 15 级部门', value: 15 }
|
||||||
|
]
|
|
@ -0,0 +1,4 @@
|
||||||
|
import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
|
||||||
|
import '../theme/simple-process-designer.scss'
|
||||||
|
|
||||||
|
export { SimpleProcessDesigner }
|
|
@ -0,0 +1,478 @@
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import * as RoleApi from '@/api/system/role'
|
||||||
|
import * as DeptApi from '@/api/system/dept'
|
||||||
|
import * as PostApi from '@/api/system/post'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||||
|
import {
|
||||||
|
SimpleFlowNode,
|
||||||
|
CandidateStrategy,
|
||||||
|
NodeType,
|
||||||
|
ApproveMethodType,
|
||||||
|
RejectHandlerType,
|
||||||
|
NODE_DEFAULT_NAME,
|
||||||
|
AssignStartUserHandlerType,
|
||||||
|
AssignEmptyHandlerType,
|
||||||
|
FieldPermissionType
|
||||||
|
} from './consts'
|
||||||
|
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
||||||
|
const node = ref<SimpleFlowNode>(props.flowNode)
|
||||||
|
watch(
|
||||||
|
() => props.flowNode,
|
||||||
|
(newValue) => {
|
||||||
|
node.value = newValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
|
||||||
|
*/
|
||||||
|
export function useFormFieldsPermission(defaultPermission: FieldPermissionType) {
|
||||||
|
// 字段权限配置. 需要有 field, title, permissioin 属性
|
||||||
|
const fieldsPermissionConfig = ref<Array<Record<string, string>>>([])
|
||||||
|
|
||||||
|
const formType = inject<Ref<number>>('formType') // 表单类型
|
||||||
|
|
||||||
|
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||||
|
|
||||||
|
const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
|
||||||
|
nodeFormFields = toRaw(nodeFormFields)
|
||||||
|
fieldsPermissionConfig.value =
|
||||||
|
cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields))
|
||||||
|
}
|
||||||
|
// 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
|
||||||
|
const getDefaultFieldsPermission = (formFields?: string[]) => {
|
||||||
|
const defaultFieldsPermission: Array<Record<string, string>> = []
|
||||||
|
if (formFields) {
|
||||||
|
formFields.forEach((fieldStr: string) => {
|
||||||
|
parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return defaultFieldsPermission
|
||||||
|
}
|
||||||
|
// 解析字段。赋给默认权限
|
||||||
|
const parseFieldsSetDefaultPermission = (
|
||||||
|
rule: Record<string, any>,
|
||||||
|
fieldsPermission: Array<Record<string, string>>,
|
||||||
|
parentTitle: string = ''
|
||||||
|
) => {
|
||||||
|
const { /**type,*/ field, title: tempTitle, children } = rule
|
||||||
|
if (field && tempTitle) {
|
||||||
|
let title = tempTitle
|
||||||
|
if (parentTitle) {
|
||||||
|
title = `${parentTitle}.${tempTitle}`
|
||||||
|
}
|
||||||
|
fieldsPermission.push({
|
||||||
|
field,
|
||||||
|
title,
|
||||||
|
permission: defaultPermission
|
||||||
|
})
|
||||||
|
// TODO 子表单 需要处理子表单字段
|
||||||
|
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||||
|
// // 解析子表单的字段
|
||||||
|
// rule.props.rule.forEach((item) => {
|
||||||
|
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
if (children && Array.isArray(children)) {
|
||||||
|
children.forEach((rule) => {
|
||||||
|
parseFieldsSetDefaultPermission(rule, fieldsPermission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formType,
|
||||||
|
fieldsPermissionConfig,
|
||||||
|
getNodeConfigFormFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description 获取表单的字段
|
||||||
|
*/
|
||||||
|
export function useFormFields() {
|
||||||
|
// 解析后的表单字段
|
||||||
|
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||||
|
const parseFormFields = () => {
|
||||||
|
const parsedFormFields: Array<Record<string, string>> = []
|
||||||
|
if (formFields) {
|
||||||
|
formFields.value.forEach((fieldStr: string) => {
|
||||||
|
parseField(JSON.parse(fieldStr), parsedFormFields)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return parsedFormFields
|
||||||
|
}
|
||||||
|
// 解析字段。
|
||||||
|
const parseField = (
|
||||||
|
rule: Record<string, any>,
|
||||||
|
parsedFormFields: Array<Record<string, string>>,
|
||||||
|
parentTitle: string = ''
|
||||||
|
) => {
|
||||||
|
const { field, title: tempTitle, children, type } = rule
|
||||||
|
if (field && tempTitle) {
|
||||||
|
let title = tempTitle
|
||||||
|
if (parentTitle) {
|
||||||
|
title = `${parentTitle}.${tempTitle}`
|
||||||
|
}
|
||||||
|
parsedFormFields.push({
|
||||||
|
field,
|
||||||
|
title,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
// TODO 子表单 需要处理子表单字段
|
||||||
|
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||||
|
// // 解析子表单的字段
|
||||||
|
// rule.props.rule.forEach((item) => {
|
||||||
|
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
if (children && Array.isArray(children)) {
|
||||||
|
children.forEach((rule) => {
|
||||||
|
parseField(rule, parsedFormFields)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFormFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserTaskFormType = {
|
||||||
|
//candidateParamArray: any[]
|
||||||
|
candidateStrategy: CandidateStrategy
|
||||||
|
approveMethod: ApproveMethodType
|
||||||
|
roleIds?: number[] // 角色
|
||||||
|
deptIds?: number[] // 部门
|
||||||
|
deptLevel?: number // 部门层级
|
||||||
|
userIds?: number[] // 用户
|
||||||
|
userGroups?: number[] // 用户组
|
||||||
|
postIds?: number[] // 岗位
|
||||||
|
expression?: string // 流程表达式
|
||||||
|
approveRatio?: number
|
||||||
|
rejectHandlerType?: RejectHandlerType
|
||||||
|
returnNodeId?: string
|
||||||
|
timeoutHandlerEnable?: boolean
|
||||||
|
timeoutHandlerType?: number
|
||||||
|
assignEmptyHandlerType?: AssignEmptyHandlerType
|
||||||
|
assignEmptyHandlerUserIds?: number[]
|
||||||
|
assignStartUserHandlerType?: AssignStartUserHandlerType
|
||||||
|
timeDuration?: number
|
||||||
|
maxRemindCount?: number
|
||||||
|
buttonsSetting: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CopyTaskFormType = {
|
||||||
|
// candidateParamArray: any[]
|
||||||
|
candidateStrategy: CandidateStrategy
|
||||||
|
roleIds?: number[] // 角色
|
||||||
|
deptIds?: number[] // 部门
|
||||||
|
deptLevel?: number // 部门层级
|
||||||
|
userIds?: number[] // 用户
|
||||||
|
userGroups?: number[] // 用户组
|
||||||
|
postIds?: number[] // 岗位
|
||||||
|
expression?: string // 流程表达式
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 节点表单数据。 用于审批节点、抄送节点
|
||||||
|
*/
|
||||||
|
export function useNodeForm(nodeType: NodeType) {
|
||||||
|
const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
|
||||||
|
const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
|
||||||
|
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
|
||||||
|
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
|
||||||
|
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
|
||||||
|
const deptTreeOptions = inject('deptTree') // 部门树
|
||||||
|
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
|
||||||
|
if (nodeType === NodeType.USER_TASK_NODE) {
|
||||||
|
configForm.value = {
|
||||||
|
candidateStrategy: CandidateStrategy.USER,
|
||||||
|
approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
|
||||||
|
approveRatio: 100,
|
||||||
|
rejectHandlerType: RejectHandlerType.FINISH_PROCESS,
|
||||||
|
assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
|
||||||
|
returnNodeId: '',
|
||||||
|
timeoutHandlerEnable: false,
|
||||||
|
timeoutHandlerType: 1,
|
||||||
|
timeDuration: 6, // 默认 6小时
|
||||||
|
maxRemindCount: 1, // 默认 提醒 1次
|
||||||
|
buttonsSetting: []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configForm.value = {
|
||||||
|
candidateStrategy: CandidateStrategy.USER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getShowText = (): string => {
|
||||||
|
let showText = ''
|
||||||
|
// 指定成员
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.USER) {
|
||||||
|
if (configForm.value?.userIds!.length > 0) {
|
||||||
|
const candidateNames: string[] = []
|
||||||
|
userOptions?.value.forEach((item) => {
|
||||||
|
if (configForm.value?.userIds!.includes(item.id)) {
|
||||||
|
candidateNames.push(item.nickname)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
showText = `指定成员:${candidateNames.join(',')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 指定角色
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.ROLE) {
|
||||||
|
if (configForm.value.roleIds!.length > 0) {
|
||||||
|
const candidateNames: string[] = []
|
||||||
|
roleOptions?.value.forEach((item) => {
|
||||||
|
if (configForm.value?.roleIds!.includes(item.id)) {
|
||||||
|
candidateNames.push(item.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
showText = `指定角色:${candidateNames.join(',')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 指定部门
|
||||||
|
if (
|
||||||
|
configForm.value?.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
|
||||||
|
configForm.value?.candidateStrategy === CandidateStrategy.DEPT_LEADER ||
|
||||||
|
configForm.value?.candidateStrategy === CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||||
|
) {
|
||||||
|
if (configForm.value?.deptIds!.length > 0) {
|
||||||
|
const candidateNames: string[] = []
|
||||||
|
deptOptions?.value.forEach((item) => {
|
||||||
|
if (configForm.value?.deptIds!.includes(item.id!)) {
|
||||||
|
candidateNames.push(item.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER) {
|
||||||
|
showText = `部门成员:${candidateNames.join(',')}`
|
||||||
|
} else if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER) {
|
||||||
|
showText = `部门的负责人:${candidateNames.join(',')}`
|
||||||
|
} else {
|
||||||
|
showText = `多级部门的负责人:${candidateNames.join(',')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定岗位
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.POST) {
|
||||||
|
if (configForm.value.postIds!.length > 0) {
|
||||||
|
const candidateNames: string[] = []
|
||||||
|
postOptions?.value.forEach((item) => {
|
||||||
|
if (configForm.value?.postIds!.includes(item.id!)) {
|
||||||
|
candidateNames.push(item.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
showText = `指定岗位: ${candidateNames.join(',')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 指定用户组
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.USER_GROUP) {
|
||||||
|
if (configForm.value?.userGroups!.length > 0) {
|
||||||
|
const candidateNames: string[] = []
|
||||||
|
userGroupOptions?.value.forEach((item) => {
|
||||||
|
if (configForm.value?.userGroups!.includes(item.id)) {
|
||||||
|
candidateNames.push(item.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
showText = `指定用户组: ${candidateNames.join(',')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起人自选
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
|
||||||
|
showText = `发起人自选`
|
||||||
|
}
|
||||||
|
// 发起人自己
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER) {
|
||||||
|
showText = `发起人自己`
|
||||||
|
}
|
||||||
|
// 发起人的部门负责人
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_DEPT_LEADER) {
|
||||||
|
showText = `发起人的部门负责人`
|
||||||
|
}
|
||||||
|
// 发起人的部门负责人
|
||||||
|
if (
|
||||||
|
configForm.value?.candidateStrategy === CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||||
|
) {
|
||||||
|
showText = `发起人连续部门负责人`
|
||||||
|
}
|
||||||
|
// 流程表达式
|
||||||
|
if (configForm.value?.candidateStrategy === CandidateStrategy.EXPRESSION) {
|
||||||
|
showText = `流程表达式:${configForm.value.expression}`
|
||||||
|
}
|
||||||
|
return showText
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理候选人参数的赋值
|
||||||
|
*/
|
||||||
|
const handleCandidateParam = () => {
|
||||||
|
let candidateParam: undefined | string = undefined
|
||||||
|
if (!configForm.value) {
|
||||||
|
return candidateParam
|
||||||
|
}
|
||||||
|
switch (configForm.value.candidateStrategy) {
|
||||||
|
case CandidateStrategy.USER:
|
||||||
|
candidateParam = configForm.value.userIds!.join(',')
|
||||||
|
break
|
||||||
|
case CandidateStrategy.ROLE:
|
||||||
|
candidateParam = configForm.value.roleIds!.join(',')
|
||||||
|
break
|
||||||
|
case CandidateStrategy.POST:
|
||||||
|
candidateParam = configForm.value.postIds!.join(',')
|
||||||
|
break
|
||||||
|
case CandidateStrategy.USER_GROUP:
|
||||||
|
candidateParam = configForm.value.userGroups!.join(',')
|
||||||
|
break
|
||||||
|
case CandidateStrategy.EXPRESSION:
|
||||||
|
candidateParam = configForm.value.expression!
|
||||||
|
break
|
||||||
|
case CandidateStrategy.DEPT_MEMBER:
|
||||||
|
case CandidateStrategy.DEPT_LEADER:
|
||||||
|
candidateParam = configForm.value.deptIds!.join(',')
|
||||||
|
break
|
||||||
|
// 发起人部门负责人
|
||||||
|
case CandidateStrategy.START_USER_DEPT_LEADER:
|
||||||
|
case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER:
|
||||||
|
candidateParam = configForm.value.deptLevel + ''
|
||||||
|
break
|
||||||
|
// 指定连续多级部门的负责人
|
||||||
|
case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
|
||||||
|
// 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
|
||||||
|
const deptIds = configForm.value.deptIds!.join(',')
|
||||||
|
candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return candidateParam
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 解析候选人参数
|
||||||
|
*/
|
||||||
|
const parseCandidateParam = (
|
||||||
|
candidateStrategy: CandidateStrategy,
|
||||||
|
candidateParam: string | undefined
|
||||||
|
) => {
|
||||||
|
if (!configForm.value || !candidateParam) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (candidateStrategy) {
|
||||||
|
case CandidateStrategy.USER: {
|
||||||
|
configForm.value.userIds = candidateParam.split(',').map((item) => +item)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case CandidateStrategy.ROLE:
|
||||||
|
configForm.value.roleIds = candidateParam.split(',').map((item) => +item)
|
||||||
|
break
|
||||||
|
case CandidateStrategy.POST:
|
||||||
|
configForm.value.postIds = candidateParam.split(',').map((item) => +item)
|
||||||
|
break
|
||||||
|
case CandidateStrategy.USER_GROUP:
|
||||||
|
configForm.value.userGroups = candidateParam.split(',').map((item) => +item)
|
||||||
|
break
|
||||||
|
case CandidateStrategy.EXPRESSION:
|
||||||
|
configForm.value.expression = candidateParam
|
||||||
|
break
|
||||||
|
case CandidateStrategy.DEPT_MEMBER:
|
||||||
|
case CandidateStrategy.DEPT_LEADER:
|
||||||
|
configForm.value.deptIds = candidateParam.split(',').map((item) => +item)
|
||||||
|
break
|
||||||
|
// 发起人部门负责人
|
||||||
|
case CandidateStrategy.START_USER_DEPT_LEADER:
|
||||||
|
case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER:
|
||||||
|
configForm.value.deptLevel = +candidateParam
|
||||||
|
break
|
||||||
|
// 指定连续多级部门的负责人
|
||||||
|
case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
|
||||||
|
// 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
|
||||||
|
const paramArray = candidateParam.split('|')
|
||||||
|
configForm.value.deptIds = paramArray[0].split(',').map((item) => +item)
|
||||||
|
configForm.value.deptLevel = +paramArray[1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
configForm,
|
||||||
|
roleOptions,
|
||||||
|
postOptions,
|
||||||
|
userOptions,
|
||||||
|
userGroupOptions,
|
||||||
|
deptTreeOptions,
|
||||||
|
handleCandidateParam,
|
||||||
|
parseCandidateParam,
|
||||||
|
getShowText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 抽屉配置
|
||||||
|
*/
|
||||||
|
export function useDrawer() {
|
||||||
|
// 抽屉配置是否可见
|
||||||
|
const settingVisible = ref(false)
|
||||||
|
// 关闭配置抽屉
|
||||||
|
const closeDrawer = () => {
|
||||||
|
settingVisible.value = false
|
||||||
|
}
|
||||||
|
// 打开配置抽屉
|
||||||
|
const openDrawer = () => {
|
||||||
|
settingVisible.value = true
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
settingVisible,
|
||||||
|
closeDrawer,
|
||||||
|
openDrawer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 节点名称配置
|
||||||
|
*/
|
||||||
|
export function useNodeName(nodeType: NodeType) {
|
||||||
|
// 节点名称
|
||||||
|
const nodeName = ref<string>()
|
||||||
|
// 节点名称输入框
|
||||||
|
const showInput = ref(false)
|
||||||
|
// 点击节点名称编辑图标
|
||||||
|
const clickIcon = () => {
|
||||||
|
showInput.value = true
|
||||||
|
}
|
||||||
|
// 节点名称输入框失去焦点
|
||||||
|
const blurEvent = () => {
|
||||||
|
showInput.value = false
|
||||||
|
nodeName.value = nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
nodeName,
|
||||||
|
showInput,
|
||||||
|
clickIcon,
|
||||||
|
blurEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
||||||
|
// 显示节点名称输入框
|
||||||
|
const showInput = ref(false)
|
||||||
|
// 节点名称输入框失去焦点
|
||||||
|
const blurEvent = () => {
|
||||||
|
showInput.value = false
|
||||||
|
node.value.name = node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string)
|
||||||
|
}
|
||||||
|
// 点击节点标题进行输入
|
||||||
|
const clickTitle = () => {
|
||||||
|
showInput.value = true
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
showInput,
|
||||||
|
clickTitle,
|
||||||
|
blurEvent
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,419 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:append-to-body="true"
|
||||||
|
v-model="settingVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:size="588"
|
||||||
|
:before-close="handleClose"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="config-header">
|
||||||
|
<input
|
||||||
|
v-if="showInput"
|
||||||
|
type="text"
|
||||||
|
class="config-editable-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="currentNode.name"
|
||||||
|
:placeholder="currentNode.name"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-name"
|
||||||
|
>{{ currentNode.name }}
|
||||||
|
<Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()"
|
||||||
|
/></div>
|
||||||
|
|
||||||
|
<div class="divide-line"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">其它条件不满足进入此分支(该分支不可编辑和删除)</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="currentNode"
|
||||||
|
:rules="formRules"
|
||||||
|
label-position="top"
|
||||||
|
>
|
||||||
|
<el-form-item label="配置方式" prop="conditionType">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="currentNode.conditionType"
|
||||||
|
@change="changeConditionType"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
v-for="(dict, index) in conditionConfigTypes"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="currentNode.conditionType === 1"
|
||||||
|
label="条件表达式"
|
||||||
|
prop="conditionExpression"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="currentNode.conditionExpression"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="currentNode.conditionType === 2" label="条件规则">
|
||||||
|
<div class="condition-group-tool">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="mr-4">条件组关系</div>
|
||||||
|
<el-switch
|
||||||
|
v-model="conditionGroups.and"
|
||||||
|
inline-prompt
|
||||||
|
active-text="且"
|
||||||
|
inactive-text="或"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-space direction="vertical" :spacer="conditionGroups.and ? '且' : '或'">
|
||||||
|
<el-card
|
||||||
|
class="condition-group"
|
||||||
|
style="width: 530px"
|
||||||
|
v-for="(condition, cIdx) in conditionGroups.conditions"
|
||||||
|
:key="cIdx"
|
||||||
|
>
|
||||||
|
<div class="condition-group-delete" v-if="conditionGroups.conditions.length > 1">
|
||||||
|
<Icon
|
||||||
|
color="#0089ff"
|
||||||
|
icon="ep:circle-close-filled"
|
||||||
|
:size="18"
|
||||||
|
@click="deleteConditionGroup(cIdx)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>条件组</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="mr-4">规则关系</div>
|
||||||
|
<el-switch
|
||||||
|
v-model="condition.and"
|
||||||
|
inline-prompt
|
||||||
|
active-text="且"
|
||||||
|
inactive-text="或"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="flex pt-2" v-for="(rule, rIdx) in condition.rules" :key="rIdx">
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-select style="width: 160px" v-model="rule.leftSide">
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in fieldsInfo"
|
||||||
|
:key="index"
|
||||||
|
:label="item.title"
|
||||||
|
:value="item.field"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-select v-model="rule.opCode" style="width: 100px">
|
||||||
|
<el-option
|
||||||
|
v-for="item in COMPARISON_OPERATORS"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-input v-model="rule.rightSide" style="width: 160px" />
|
||||||
|
</div>
|
||||||
|
<div class="mr-1 flex items-center" v-if="condition.rules.length > 1">
|
||||||
|
<Icon
|
||||||
|
icon="ep:delete"
|
||||||
|
:size="18"
|
||||||
|
@click="deleteConditionRule(condition, rIdx)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Icon icon="ep:plus" :size="18" @click="addConditionRule(condition, rIdx)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-space>
|
||||||
|
<div title="添加条件组" class="mt-4 cursor-pointer">
|
||||||
|
<Icon color="#0089ff" icon="ep:plus" :size="24" @click="addConditionGroup" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-divider />
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||||
|
<el-button @click="closeDrawer">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
SimpleFlowNode,
|
||||||
|
CONDITION_CONFIG_TYPES,
|
||||||
|
ConditionType,
|
||||||
|
COMPARISON_OPERATORS,
|
||||||
|
ConditionGroup,
|
||||||
|
Condition,
|
||||||
|
ConditionRule
|
||||||
|
} from '../consts'
|
||||||
|
import { getDefaultConditionNodeName } from '../utils'
|
||||||
|
import { useFormFields } from '../node'
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
defineOptions({
|
||||||
|
name: 'ConditionNodeConfig'
|
||||||
|
})
|
||||||
|
const formType = inject<Ref<number>>('formType') // 表单类型
|
||||||
|
const conditionConfigTypes = computed(() => {
|
||||||
|
return CONDITION_CONFIG_TYPES.filter((item) => {
|
||||||
|
// 业务表单暂时去掉条件规则选项
|
||||||
|
if (formType?.value !== 10) {
|
||||||
|
return item.value === ConditionType.RULE
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
conditionNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
nodeIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const settingVisible = ref(false)
|
||||||
|
const open = () => {
|
||||||
|
if (currentNode.value.conditionType === ConditionType.RULE) {
|
||||||
|
if (currentNode.value.conditionGroups) {
|
||||||
|
conditionGroups.value = currentNode.value.conditionGroups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settingVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.conditionNode,
|
||||||
|
(newValue) => {
|
||||||
|
currentNode.value = newValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 显示名称输入框
|
||||||
|
const showInput = ref(false)
|
||||||
|
|
||||||
|
const clickIcon = () => {
|
||||||
|
showInput.value = true
|
||||||
|
}
|
||||||
|
// 输入框失去焦点
|
||||||
|
const blurEvent = () => {
|
||||||
|
showInput.value = false
|
||||||
|
currentNode.value.name =
|
||||||
|
currentNode.value.name ||
|
||||||
|
getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.defaultFlow)
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
|
||||||
|
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
const closeDrawer = () => {
|
||||||
|
settingVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = async (done: (cancel?: boolean) => void) => {
|
||||||
|
const isSuccess = await saveConfig()
|
||||||
|
if (!isSuccess) {
|
||||||
|
done(true) // 传入 true 阻止关闭
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = reactive({
|
||||||
|
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
|
||||||
|
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
const saveConfig = async () => {
|
||||||
|
if (!currentNode.value.defaultFlow) {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return false
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return false
|
||||||
|
const showText = getShowText()
|
||||||
|
if (!showText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
currentNode.value.showText = showText
|
||||||
|
if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
|
||||||
|
currentNode.value.conditionGroups = undefined
|
||||||
|
}
|
||||||
|
if (currentNode.value.conditionType === ConditionType.RULE) {
|
||||||
|
currentNode.value.conditionExpression = undefined
|
||||||
|
currentNode.value.conditionGroups = conditionGroups.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const getShowText = (): string => {
|
||||||
|
let showText = ''
|
||||||
|
if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
|
||||||
|
if (currentNode.value.conditionExpression) {
|
||||||
|
showText = `表达式:${currentNode.value.conditionExpression}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentNode.value.conditionType === ConditionType.RULE) {
|
||||||
|
// 条件组是否为与关系
|
||||||
|
const groupAnd = conditionGroups.value.and
|
||||||
|
let warningMesg: undefined | string = undefined
|
||||||
|
const conditionGroup = conditionGroups.value.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 changeConditionType = () => {}
|
||||||
|
|
||||||
|
const conditionGroups = ref<ConditionGroup>({
|
||||||
|
and: true,
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
and: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: 1,
|
||||||
|
opName: '等于',
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// 添加条件组
|
||||||
|
const addConditionGroup = () => {
|
||||||
|
const condition = {
|
||||||
|
and: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: 1,
|
||||||
|
opName: '等于',
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
conditionGroups.value.conditions.push(condition)
|
||||||
|
}
|
||||||
|
// 删除条件组
|
||||||
|
const deleteConditionGroup = (idx: number) => {
|
||||||
|
conditionGroups.value.conditions.splice(idx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加条件规则
|
||||||
|
const addConditionRule = (condition: Condition, idx: number) => {
|
||||||
|
const rule: ConditionRule = {
|
||||||
|
type: 1,
|
||||||
|
opName: '等于',
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
condition.rules.splice(idx + 1, 0, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteConditionRule = (condition: Condition, idx: number) => {
|
||||||
|
condition.rules.splice(idx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldsInfo = useFormFields()
|
||||||
|
|
||||||
|
const getFieldTitle = (field: string) => {
|
||||||
|
const item = fieldsInfo.find((item) => item.field === field)
|
||||||
|
return item?.title
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOpName = (opCode: string): string => {
|
||||||
|
const opName = COMPARISON_OPERATORS.find((item) => item.value === opCode)
|
||||||
|
return opName?.label
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.condition-group-tool {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 500px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-group {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #0089ff;
|
||||||
|
|
||||||
|
.condition-group-delete {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-group-delete {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-card__header) {
|
||||||
|
padding: 8px var(--el-card-padding);
|
||||||
|
border-bottom: 1px solid var(--el-card-border-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,307 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:append-to-body="true"
|
||||||
|
v-model="settingVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:size="550"
|
||||||
|
:before-close="saveConfig"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="config-header">
|
||||||
|
<input
|
||||||
|
v-if="showInput"
|
||||||
|
type="text"
|
||||||
|
class="config-editable-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="nodeName"
|
||||||
|
:placeholder="nodeName"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-name">
|
||||||
|
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
||||||
|
</div>
|
||||||
|
<div class="divide-line"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-tabs type="border-card" v-model="activeTabName">
|
||||||
|
<el-tab-pane label="抄送人" name="user">
|
||||||
|
<div>
|
||||||
|
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
|
||||||
|
<el-form-item label="抄送人设置" prop="candidateStrategy">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="configForm.candidateStrategy"
|
||||||
|
@change="changeCandidateStrategy"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
v-for="(dict, index) in copyUserStrategies"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
|
||||||
|
label="指定角色"
|
||||||
|
prop="roleIds"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.roleIds" clearable multiple style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in roleOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER
|
||||||
|
"
|
||||||
|
label="指定部门"
|
||||||
|
prop="deptIds"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-tree-select
|
||||||
|
ref="treeRef"
|
||||||
|
v-model="configForm.deptIds"
|
||||||
|
:data="deptTreeOptions"
|
||||||
|
:props="defaultProps"
|
||||||
|
empty-text="加载中,请稍后"
|
||||||
|
multiple
|
||||||
|
node-key="id"
|
||||||
|
style="width: 100%"
|
||||||
|
show-checkbox
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy == CandidateStrategy.POST"
|
||||||
|
label="指定岗位"
|
||||||
|
prop="postIds"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.postIds" clearable multiple style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in postOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id!"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy == CandidateStrategy.USER"
|
||||||
|
label="指定用户"
|
||||||
|
prop="userIds"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in userOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy === CandidateStrategy.USER_GROUP"
|
||||||
|
label="指定用户组"
|
||||||
|
prop="userGroups"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.userGroups" clearable multiple style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in userGroupOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||||
|
label="流程表达式"
|
||||||
|
prop="expression"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="configForm.expression"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
|
||||||
|
<div class="field-setting-pane">
|
||||||
|
<div class="field-setting-desc">字段权限</div>
|
||||||
|
<div class="field-permit-title">
|
||||||
|
<div class="setting-title-label first-title"> 字段名称 </div>
|
||||||
|
<div class="other-titles">
|
||||||
|
<span class="setting-title-label">只读</span>
|
||||||
|
<span class="setting-title-label">可编辑</span>
|
||||||
|
<span class="setting-title-label">隐藏</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="field-setting-item"
|
||||||
|
v-for="(item, index) in fieldsPermissionConfig"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="field-setting-item-label"> {{ item.title }} </div>
|
||||||
|
<el-radio-group class="field-setting-item-group" v-model="item.permission">
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.READ"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.WRITE"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.WRITE"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.WRITE"
|
||||||
|
disabled
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.NONE"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.NONE"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<template #footer>
|
||||||
|
<el-divider />
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||||
|
<el-button @click="closeDrawer">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
SimpleFlowNode,
|
||||||
|
CandidateStrategy,
|
||||||
|
NodeType,
|
||||||
|
CANDIDATE_STRATEGY,
|
||||||
|
FieldPermissionType
|
||||||
|
} from '../consts'
|
||||||
|
import {
|
||||||
|
useWatchNode,
|
||||||
|
useDrawer,
|
||||||
|
useNodeName,
|
||||||
|
useFormFieldsPermission,
|
||||||
|
useNodeForm,
|
||||||
|
CopyTaskFormType
|
||||||
|
} from '../node'
|
||||||
|
import { defaultProps } from '@/utils/tree'
|
||||||
|
defineOptions({
|
||||||
|
name: 'CopyTaskNodeConfig'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 抽屉配置
|
||||||
|
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
|
// 当前节点
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称
|
||||||
|
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_TASK_NODE)
|
||||||
|
// 激活的 Tab 标签页
|
||||||
|
const activeTabName = ref('user')
|
||||||
|
// 表单字段权限配置
|
||||||
|
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||||
|
FieldPermissionType.READ
|
||||||
|
)
|
||||||
|
// 抄送人表单配置
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = reactive({
|
||||||
|
candidateStrategy: [{ required: true, message: '抄送人设置不能为空', trigger: 'change' }],
|
||||||
|
userIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
|
||||||
|
roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
|
||||||
|
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||||
|
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||||
|
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||||
|
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
configForm: tempConfigForm,
|
||||||
|
roleOptions,
|
||||||
|
postOptions,
|
||||||
|
userOptions,
|
||||||
|
userGroupOptions,
|
||||||
|
deptTreeOptions,
|
||||||
|
getShowText,
|
||||||
|
handleCandidateParam,
|
||||||
|
parseCandidateParam
|
||||||
|
} = useNodeForm(NodeType.COPY_TASK_NODE)
|
||||||
|
const configForm = tempConfigForm as Ref<CopyTaskFormType>
|
||||||
|
// 抄送人策略, 去掉发起人自选 和 发起人自己
|
||||||
|
const copyUserStrategies = computed(() => {
|
||||||
|
return CANDIDATE_STRATEGY.filter(
|
||||||
|
(item) =>
|
||||||
|
item.value !== CandidateStrategy.START_USER_SELECT &&
|
||||||
|
item.value !== CandidateStrategy.START_USER
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// 改变抄送人设置策略
|
||||||
|
const changeCandidateStrategy = () => {
|
||||||
|
configForm.value.userIds = []
|
||||||
|
configForm.value.deptIds = []
|
||||||
|
configForm.value.roleIds = []
|
||||||
|
configForm.value.postIds = []
|
||||||
|
configForm.value.userGroups = []
|
||||||
|
configForm.value.deptLevel = 1
|
||||||
|
}
|
||||||
|
// 保存配置
|
||||||
|
const saveConfig = async () => {
|
||||||
|
activeTabName.value = 'user'
|
||||||
|
if (!formRef) return false
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return false
|
||||||
|
const showText = getShowText()
|
||||||
|
if (!showText) return false
|
||||||
|
currentNode.value.name = nodeName.value!
|
||||||
|
currentNode.value.candidateParam = handleCandidateParam()
|
||||||
|
currentNode.value.candidateStrategy = configForm.value.candidateStrategy
|
||||||
|
currentNode.value.showText = showText
|
||||||
|
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 显示抄送节点配置, 由父组件传过来
|
||||||
|
const showCopyTaskNodeConfig = (node: SimpleFlowNode) => {
|
||||||
|
nodeName.value = node.name
|
||||||
|
// 抄送人设置
|
||||||
|
configForm.value.candidateStrategy = node.candidateStrategy!
|
||||||
|
parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
|
||||||
|
// 表单字段权限
|
||||||
|
getNodeConfigFormFields(node.fieldsPermission)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ openDrawer, showCopyTaskNodeConfig }) // 暴露方法给父组件
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,136 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:append-to-body="true"
|
||||||
|
v-model="settingVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:size="550"
|
||||||
|
:before-close="saveConfig"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="config-header">
|
||||||
|
<input
|
||||||
|
v-if="showInput"
|
||||||
|
type="text"
|
||||||
|
class="config-editable-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="nodeName"
|
||||||
|
:placeholder="nodeName"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-name">
|
||||||
|
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
||||||
|
</div>
|
||||||
|
<div class="divide-line"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-tabs type="border-card" v-model="activeTabName">
|
||||||
|
<el-tab-pane label="权限" name="user">
|
||||||
|
<div> 待实现 </div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
|
||||||
|
<div class="field-setting-pane">
|
||||||
|
<div class="field-setting-desc">字段权限</div>
|
||||||
|
<div class="field-permit-title">
|
||||||
|
<div class="setting-title-label first-title"> 字段名称 </div>
|
||||||
|
<div class="other-titles">
|
||||||
|
<span class="setting-title-label">只读</span>
|
||||||
|
<span class="setting-title-label">可编辑</span>
|
||||||
|
<span class="setting-title-label">隐藏</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="field-setting-item"
|
||||||
|
v-for="(item, index) in fieldsPermissionConfig"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="field-setting-item-label"> {{ item.title }} </div>
|
||||||
|
<el-radio-group class="field-setting-item-group" v-model="item.permission">
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.READ"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.READ"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.WRITE"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.WRITE"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.NONE"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.NONE"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<template #footer>
|
||||||
|
<el-divider />
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||||
|
<el-button @click="closeDrawer">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
|
||||||
|
import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'StartUserNodeConfig'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 抽屉配置
|
||||||
|
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
|
// 当前节点
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称
|
||||||
|
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_TASK_NODE)
|
||||||
|
// 激活的 Tab 标签页
|
||||||
|
const activeTabName = ref('user')
|
||||||
|
// 表单字段权限配置
|
||||||
|
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||||
|
FieldPermissionType.WRITE
|
||||||
|
)
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
const saveConfig = async () => {
|
||||||
|
activeTabName.value = 'user'
|
||||||
|
currentNode.value.name = nodeName.value!
|
||||||
|
// TODO 暂时写死。后续可以显示谁有权限可以发起
|
||||||
|
currentNode.value.showText = '已设置'
|
||||||
|
// 设置表单权限
|
||||||
|
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
||||||
|
// 设置发起人的按钮权限
|
||||||
|
currentNode.value.buttonsSetting = START_USER_BUTTON_SETTING
|
||||||
|
console.log('currentNode.value.buttonsSetting==>', currentNode.value.buttonsSetting)
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 显示发起人节点配置, 由父组件传过来
|
||||||
|
const showStartUserNodeConfig = (node: SimpleFlowNode) => {
|
||||||
|
nodeName.value = node.name
|
||||||
|
// 表单字段权限
|
||||||
|
getNodeConfigFormFields(node.fieldsPermission)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ openDrawer, showStartUserNodeConfig }) // 暴露方法给父组件
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,901 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:append-to-body="true"
|
||||||
|
v-model="settingVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:size="550"
|
||||||
|
:before-close="saveConfig"
|
||||||
|
class="justify-start"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="config-header">
|
||||||
|
<input
|
||||||
|
v-if="showInput"
|
||||||
|
type="text"
|
||||||
|
class="config-editable-input"
|
||||||
|
@blur="blurEvent()"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="nodeName"
|
||||||
|
:placeholder="nodeName"
|
||||||
|
/>
|
||||||
|
<div v-else class="node-name">
|
||||||
|
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
|
||||||
|
</div>
|
||||||
|
<div class="divide-line"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-items-center mb-3">
|
||||||
|
<span class="font-size-16px mr-3">审批类型 :</span>
|
||||||
|
<el-radio-group v-model="approveType">
|
||||||
|
<el-radio
|
||||||
|
v-for="(item, index) in APPROVE_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<el-tabs type="border-card" v-model="activeTabName" v-if="approveType === ApproveType.USER">
|
||||||
|
<el-tab-pane label="审批人" name="user">
|
||||||
|
<div>
|
||||||
|
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
|
||||||
|
<el-form-item label="审批人设置" prop="candidateStrategy">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="configForm.candidateStrategy"
|
||||||
|
@change="changeCandidateStrategy"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
v-for="(dict, index) in CANDIDATE_STRATEGY"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
|
||||||
|
label="指定角色"
|
||||||
|
prop="roleIds"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.roleIds" clearable multiple style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in roleOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||||
|
"
|
||||||
|
label="指定部门"
|
||||||
|
prop="deptIds"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-tree-select
|
||||||
|
ref="treeRef"
|
||||||
|
v-model="configForm.deptIds"
|
||||||
|
:data="deptTreeOptions"
|
||||||
|
:props="defaultProps"
|
||||||
|
empty-text="加载中,请稍后"
|
||||||
|
multiple
|
||||||
|
node-key="id"
|
||||||
|
:check-strictly="true"
|
||||||
|
style="width: 100%"
|
||||||
|
show-checkbox
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||||
|
configForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||||
|
"
|
||||||
|
:label="deptLevelLabel!"
|
||||||
|
prop="deptLevel"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.deptLevel" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||||
|
:key="index"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy == CandidateStrategy.POST"
|
||||||
|
label="指定岗位"
|
||||||
|
prop="postIds"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.postIds" clearable multiple style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in postOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id!"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy == CandidateStrategy.USER"
|
||||||
|
label="指定用户"
|
||||||
|
prop="userIds"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-model="configForm.userIds"
|
||||||
|
clearable
|
||||||
|
multiple
|
||||||
|
style="width: 100%"
|
||||||
|
@change="changedCandidateUsers"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in userOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy === CandidateStrategy.USER_GROUP"
|
||||||
|
label="指定用户组"
|
||||||
|
prop="userGroups"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.userGroups" clearable multiple style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in userGroupOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- TODO @jason:后续要支持选择已经存好的表达式 -->
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||||
|
label="流程表达式"
|
||||||
|
prop="expression"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="configForm.expression"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="多人审批方式" prop="approveMethod">
|
||||||
|
<el-radio-group v-model="configForm.approveMethod" @change="approveMethodChanged">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in APPROVE_METHODS"
|
||||||
|
:key="index"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.value"
|
||||||
|
:disabled="
|
||||||
|
item.value !== ApproveMethodType.RANDOM_SELECT_ONE_APPROVE &&
|
||||||
|
notAllowedMultiApprovers
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</el-radio>
|
||||||
|
<el-form-item prop="approveRatio">
|
||||||
|
<el-input-number
|
||||||
|
v-model="configForm.approveRatio"
|
||||||
|
:min="10"
|
||||||
|
:max="100"
|
||||||
|
:step="10"
|
||||||
|
size="small"
|
||||||
|
v-if="
|
||||||
|
item.value === ApproveMethodType.APPROVE_BY_RATIO &&
|
||||||
|
configForm.approveMethod === ApproveMethodType.APPROVE_BY_RATIO
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">审批人拒绝时</el-divider>
|
||||||
|
<el-form-item prop="rejectHandlerType">
|
||||||
|
<el-radio-group v-model="configForm.rejectHandlerType">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
|
||||||
|
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
|
||||||
|
label="驳回节点"
|
||||||
|
prop="returnNodeId"
|
||||||
|
>
|
||||||
|
<el-select v-model="configForm.returnNodeId" clearable style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in returnTaskList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">审批人超时未处理时</el-divider>
|
||||||
|
<el-form-item label="启用开关" prop="timeoutHandlerEnable">
|
||||||
|
<el-switch
|
||||||
|
v-model="configForm.timeoutHandlerEnable"
|
||||||
|
active-text="开启"
|
||||||
|
inactive-text="关闭"
|
||||||
|
@change="timeoutHandlerChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
label="执行动作"
|
||||||
|
prop="timeoutHandlerType"
|
||||||
|
v-if="configForm.timeoutHandlerEnable"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="configForm.timeoutHandlerType"
|
||||||
|
@change="timeoutHandlerTypeChanged"
|
||||||
|
>
|
||||||
|
<el-radio-button
|
||||||
|
v-for="item in TIMEOUT_HANDLER_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
/>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="超时时间设置" v-if="configForm.timeoutHandlerEnable">
|
||||||
|
<span class="mr-2">当超过</span>
|
||||||
|
<el-form-item prop="timeDuration">
|
||||||
|
<el-input-number
|
||||||
|
class="mr-2"
|
||||||
|
:style="{ width: '100px' }"
|
||||||
|
v-model="configForm.timeDuration"
|
||||||
|
:min="1"
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-select
|
||||||
|
v-model="timeUnit"
|
||||||
|
class="mr-2"
|
||||||
|
:style="{ width: '100px' }"
|
||||||
|
@change="timeUnitChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in TIME_UNIT_TYPES"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
未处理
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
label="最大提醒次数"
|
||||||
|
prop="maxRemindCount"
|
||||||
|
v-if="configForm.timeoutHandlerEnable && configForm.timeoutHandlerType === 1"
|
||||||
|
>
|
||||||
|
<el-input-number v-model="configForm.maxRemindCount" :min="1" :max="10" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">审批人为空时</el-divider>
|
||||||
|
<el-form-item prop="assignEmptyHandlerType">
|
||||||
|
<el-radio-group v-model="configForm.assignEmptyHandlerType">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
|
||||||
|
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="configForm.assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
|
||||||
|
label="指定用户"
|
||||||
|
prop="assignEmptyHandlerUserIds"
|
||||||
|
span="24"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-model="configForm.assignEmptyHandlerUserIds"
|
||||||
|
clearable
|
||||||
|
multiple
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in userOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">审批人与提交人为同一人时</el-divider>
|
||||||
|
<el-form-item prop="assignStartUserHandlerType">
|
||||||
|
<el-radio-group v-model="configForm.assignStartUserHandlerType">
|
||||||
|
<div class="flex-col">
|
||||||
|
<div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
|
||||||
|
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="操作按钮设置" name="buttons">
|
||||||
|
<div class="button-setting-pane">
|
||||||
|
<div class="button-setting-desc">操作按钮</div>
|
||||||
|
<div class="button-setting-title">
|
||||||
|
<div class="button-title-label">操作按钮</div>
|
||||||
|
<div class="pl-4 button-title-label">显示名称</div>
|
||||||
|
<div class="button-title-label">启用</div>
|
||||||
|
</div>
|
||||||
|
<div class="button-setting-item" v-for="(item, index) in buttonsSetting" :key="index">
|
||||||
|
<div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div>
|
||||||
|
<div class="button-setting-item-label">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="editable-title-input"
|
||||||
|
@blur="btnDisplayNameBlurEvent(index)"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="item.displayName"
|
||||||
|
:placeholder="item.displayName"
|
||||||
|
v-if="btnDisplayNameEdit[index]"
|
||||||
|
/>
|
||||||
|
<el-button v-else text @click="changeBtnDisplayName(index)"
|
||||||
|
>{{ item.displayName }} <Icon icon="ep:edit"
|
||||||
|
/></el-button>
|
||||||
|
</div>
|
||||||
|
<div class="button-setting-item-label">
|
||||||
|
<el-switch v-model="item.enable" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
|
||||||
|
<div class="field-setting-pane">
|
||||||
|
<div class="field-setting-desc">字段权限</div>
|
||||||
|
<div class="field-permit-title">
|
||||||
|
<div class="setting-title-label first-title"> 字段名称 </div>
|
||||||
|
<div class="other-titles">
|
||||||
|
<span class="setting-title-label">只读</span>
|
||||||
|
<span class="setting-title-label">可编辑</span>
|
||||||
|
<span class="setting-title-label">隐藏</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="field-setting-item"
|
||||||
|
v-for="(item, index) in fieldsPermissionConfig"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="field-setting-item-label"> {{ item.title }} </div>
|
||||||
|
<el-radio-group class="field-setting-item-group" v-model="item.permission">
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.READ"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.READ"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.WRITE"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.WRITE"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
<div class="item-radio-wrap">
|
||||||
|
<el-radio
|
||||||
|
:value="FieldPermissionType.NONE"
|
||||||
|
size="large"
|
||||||
|
:label="FieldPermissionType.NONE"
|
||||||
|
><span></span
|
||||||
|
></el-radio>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<template #footer>
|
||||||
|
<el-divider />
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="saveConfig">确 定</el-button>
|
||||||
|
<el-button @click="closeDrawer">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
SimpleFlowNode,
|
||||||
|
APPROVE_TYPE,
|
||||||
|
ApproveType,
|
||||||
|
APPROVE_METHODS,
|
||||||
|
CandidateStrategy,
|
||||||
|
NodeType,
|
||||||
|
ApproveMethodType,
|
||||||
|
TimeUnitType,
|
||||||
|
RejectHandlerType,
|
||||||
|
TIMEOUT_HANDLER_TYPES,
|
||||||
|
TIME_UNIT_TYPES,
|
||||||
|
REJECT_HANDLER_TYPES,
|
||||||
|
DEFAULT_BUTTON_SETTING,
|
||||||
|
OPERATION_BUTTON_NAME,
|
||||||
|
ButtonSetting,
|
||||||
|
MULTI_LEVEL_DEPT,
|
||||||
|
CANDIDATE_STRATEGY,
|
||||||
|
ASSIGN_START_USER_HANDLER_TYPES,
|
||||||
|
TimeoutHandlerType,
|
||||||
|
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||||
|
AssignEmptyHandlerType,
|
||||||
|
FieldPermissionType
|
||||||
|
} from '../consts'
|
||||||
|
|
||||||
|
import {
|
||||||
|
useWatchNode,
|
||||||
|
useNodeName,
|
||||||
|
useFormFieldsPermission,
|
||||||
|
useNodeForm,
|
||||||
|
UserTaskFormType,
|
||||||
|
useDrawer
|
||||||
|
} from '../node'
|
||||||
|
import { defaultProps } from '@/utils/tree'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import { convertTimeUnit, getApproveTypeText } from '../utils'
|
||||||
|
defineOptions({
|
||||||
|
name: 'UserTaskNodeConfig'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'find:returnTaskNodes': [nodeList: SimpleFlowNode[]]
|
||||||
|
}>()
|
||||||
|
const deptLevelLabel = computed(() => {
|
||||||
|
let label = '部门负责人来源'
|
||||||
|
if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||||
|
label = label + '(指定部门向上)'
|
||||||
|
} else {
|
||||||
|
label = label + '(发起人部门向上)'
|
||||||
|
}
|
||||||
|
return label
|
||||||
|
})
|
||||||
|
// 监控节点的变化
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 抽屉配置
|
||||||
|
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
|
// 节点名称配置
|
||||||
|
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.USER_TASK_NODE)
|
||||||
|
// 激活的 Tab 标签页
|
||||||
|
const activeTabName = ref('user')
|
||||||
|
// 表单字段权限设置
|
||||||
|
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||||
|
FieldPermissionType.READ
|
||||||
|
)
|
||||||
|
// 操作按钮设置
|
||||||
|
const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
|
||||||
|
useButtonsSetting()
|
||||||
|
const approveType = ref(ApproveType.USER)
|
||||||
|
// 审批人表单设置
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = reactive({
|
||||||
|
candidateStrategy: [{ required: true, message: '审批人设置不能为空', trigger: 'change' }],
|
||||||
|
userIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
|
||||||
|
roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
|
||||||
|
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||||
|
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||||
|
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||||
|
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }],
|
||||||
|
approveMethod: [{ required: true, message: '多人审批方式不能为空', trigger: 'change' }],
|
||||||
|
approveRatio: [{ required: true, message: '通过比例不能为空', trigger: 'blur' }],
|
||||||
|
returnNodeId: [{ required: true, message: '驳回节点不能为空', trigger: 'change' }],
|
||||||
|
timeoutHandlerEnable: [{ required: true }],
|
||||||
|
timeoutHandlerType: [{ required: true }],
|
||||||
|
timeDuration: [{ required: true, message: '超时时间不能为空', trigger: 'blur' }],
|
||||||
|
maxRemindCount: [{ required: true, message: '提醒次数不能为空', trigger: 'blur' }],
|
||||||
|
assignEmptyHandlerType: [{ required: true }],
|
||||||
|
assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
|
||||||
|
assignStartUserHandlerType: [{ required: true }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
configForm: tempConfigForm,
|
||||||
|
roleOptions,
|
||||||
|
postOptions,
|
||||||
|
userOptions,
|
||||||
|
userGroupOptions,
|
||||||
|
deptTreeOptions,
|
||||||
|
handleCandidateParam,
|
||||||
|
parseCandidateParam,
|
||||||
|
getShowText
|
||||||
|
} = useNodeForm(NodeType.USER_TASK_NODE)
|
||||||
|
const configForm = tempConfigForm as Ref<UserTaskFormType>
|
||||||
|
// 不允许多人审批
|
||||||
|
const notAllowedMultiApprovers = ref(false)
|
||||||
|
// 改变审批人设置策略
|
||||||
|
const changeCandidateStrategy = () => {
|
||||||
|
configForm.value.userIds = []
|
||||||
|
configForm.value.deptIds = []
|
||||||
|
configForm.value.roleIds = []
|
||||||
|
configForm.value.postIds = []
|
||||||
|
configForm.value.userGroups = []
|
||||||
|
configForm.value.deptLevel = 1
|
||||||
|
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE
|
||||||
|
if (
|
||||||
|
configForm.value.candidateStrategy === CandidateStrategy.START_USER ||
|
||||||
|
configForm.value.candidateStrategy === CandidateStrategy.USER
|
||||||
|
) {
|
||||||
|
notAllowedMultiApprovers.value = true
|
||||||
|
} else {
|
||||||
|
notAllowedMultiApprovers.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 改变审批候选人
|
||||||
|
const changedCandidateUsers = () => {
|
||||||
|
if (
|
||||||
|
configForm.value.userIds &&
|
||||||
|
configForm.value.userIds?.length <= 1 &&
|
||||||
|
configForm.value.candidateStrategy === CandidateStrategy.USER
|
||||||
|
) {
|
||||||
|
configForm.value.approveMethod = ApproveMethodType.RANDOM_SELECT_ONE_APPROVE
|
||||||
|
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||||
|
notAllowedMultiApprovers.value = true
|
||||||
|
} else {
|
||||||
|
notAllowedMultiApprovers.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 审批方式改变
|
||||||
|
const approveMethodChanged = () => {
|
||||||
|
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||||
|
if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
|
||||||
|
configForm.value.approveRatio = 100
|
||||||
|
}
|
||||||
|
formRef.value.clearValidate('approveRatio')
|
||||||
|
}
|
||||||
|
// 审批拒绝 可回退的节点
|
||||||
|
const returnTaskList = ref<SimpleFlowNode[]>([])
|
||||||
|
// 审批人超时未处理设置
|
||||||
|
const {
|
||||||
|
timeoutHandlerChange,
|
||||||
|
cTimeoutType,
|
||||||
|
timeoutHandlerTypeChanged,
|
||||||
|
timeUnit,
|
||||||
|
timeUnitChange,
|
||||||
|
isoTimeDuration,
|
||||||
|
cTimeoutMaxRemindCount
|
||||||
|
} = useTimeoutHandler()
|
||||||
|
|
||||||
|
// 保存配置
|
||||||
|
const saveConfig = async () => {
|
||||||
|
activeTabName.value = 'user'
|
||||||
|
// 设置审批节点名称
|
||||||
|
currentNode.value.name = nodeName.value!
|
||||||
|
// 设置审批类型
|
||||||
|
currentNode.value.approveType = approveType.value
|
||||||
|
// 如果不是人工审批。返回
|
||||||
|
if (approveType.value !== ApproveType.USER) {
|
||||||
|
currentNode.value.showText = getApproveTypeText(approveType.value)
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formRef) return false
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return false
|
||||||
|
const showText = getShowText()
|
||||||
|
if (!showText) return false
|
||||||
|
|
||||||
|
currentNode.value.candidateStrategy = configForm.value.candidateStrategy
|
||||||
|
// 处理 candidateParam 参数
|
||||||
|
currentNode.value.candidateParam = handleCandidateParam()
|
||||||
|
// 设置审批方式
|
||||||
|
currentNode.value.approveMethod = configForm.value.approveMethod
|
||||||
|
if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
|
||||||
|
currentNode.value.approveRatio = configForm.value.approveRatio
|
||||||
|
}
|
||||||
|
// 设置拒绝处理
|
||||||
|
currentNode.value.rejectHandler = {
|
||||||
|
type: configForm.value.rejectHandlerType!,
|
||||||
|
returnNodeId: configForm.value.returnNodeId
|
||||||
|
}
|
||||||
|
// 设置超时处理
|
||||||
|
currentNode.value.timeoutHandler = {
|
||||||
|
enable: configForm.value.timeoutHandlerEnable!,
|
||||||
|
type: cTimeoutType.value,
|
||||||
|
timeDuration: isoTimeDuration.value,
|
||||||
|
maxRemindCount: cTimeoutMaxRemindCount.value
|
||||||
|
}
|
||||||
|
// 设置审批人为空时
|
||||||
|
currentNode.value.assignEmptyHandler = {
|
||||||
|
type: configForm.value.assignEmptyHandlerType!,
|
||||||
|
userIds:
|
||||||
|
configForm.value.assignEmptyHandlerType === AssignEmptyHandlerType.ASSIGN_USER
|
||||||
|
? configForm.value.assignEmptyHandlerUserIds
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
// 设置审批人与发起人相同时
|
||||||
|
currentNode.value.assignStartUserHandlerType = configForm.value.assignStartUserHandlerType
|
||||||
|
// 设置表单权限
|
||||||
|
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
||||||
|
// 设置按钮权限
|
||||||
|
currentNode.value.buttonsSetting = buttonsSetting.value
|
||||||
|
|
||||||
|
currentNode.value.showText = showText
|
||||||
|
settingVisible.value = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示审批节点配置, 由父组件传过来
|
||||||
|
const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
|
||||||
|
nodeName.value = node.name
|
||||||
|
// 1 审批类型
|
||||||
|
approveType.value = node.approveType ? node.approveType : ApproveType.USER
|
||||||
|
// 如果审批类型不是人工审批返回
|
||||||
|
if (approveType.value !== ApproveType.USER) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//2.1 审批人设置
|
||||||
|
configForm.value.candidateStrategy = node.candidateStrategy!
|
||||||
|
// 解析候选人参数
|
||||||
|
parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
|
||||||
|
if (configForm.value.userIds && configForm.value.userIds.length > 1) {
|
||||||
|
notAllowedMultiApprovers.value = true
|
||||||
|
} else {
|
||||||
|
notAllowedMultiApprovers.value = false
|
||||||
|
}
|
||||||
|
// 2.2 设置审批方式
|
||||||
|
configForm.value.approveMethod = node.approveMethod!
|
||||||
|
if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) {
|
||||||
|
configForm.value.approveRatio = node.approveRatio!
|
||||||
|
}
|
||||||
|
// 2.3 设置审批拒绝处理
|
||||||
|
configForm.value.rejectHandlerType = node.rejectHandler!.type
|
||||||
|
configForm.value.returnNodeId = node.rejectHandler?.returnNodeId
|
||||||
|
const matchNodeList = []
|
||||||
|
emits('find:returnTaskNodes', matchNodeList)
|
||||||
|
returnTaskList.value = matchNodeList
|
||||||
|
// 2.4 设置审批超时处理
|
||||||
|
configForm.value.timeoutHandlerEnable = node.timeoutHandler!.enable
|
||||||
|
if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) {
|
||||||
|
const strTimeDuration = node.timeoutHandler.timeDuration
|
||||||
|
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
|
||||||
|
let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
|
||||||
|
configForm.value.timeDuration = parseInt(parseTime)
|
||||||
|
timeUnit.value = convertTimeUnit(parseTimeUnit)
|
||||||
|
}
|
||||||
|
configForm.value.timeoutHandlerType = node.timeoutHandler?.type
|
||||||
|
configForm.value.maxRemindCount = node.timeoutHandler?.maxRemindCount
|
||||||
|
// 2.5 设置审批人为空时
|
||||||
|
configForm.value.assignEmptyHandlerType = node.assignEmptyHandler?.type
|
||||||
|
configForm.value.assignEmptyHandlerUserIds = node.assignEmptyHandler?.userIds
|
||||||
|
// 2.6 设置用户任务的审批人与发起人相同时
|
||||||
|
configForm.value.assignStartUserHandlerType = node.assignStartUserHandlerType
|
||||||
|
// 3. 操作按钮设置
|
||||||
|
buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
|
||||||
|
// 4. 表单字段权限配置
|
||||||
|
getNodeConfigFormFields(node.fieldsPermission)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 操作按钮设置
|
||||||
|
*/
|
||||||
|
function useButtonsSetting() {
|
||||||
|
const buttonsSetting = ref<ButtonSetting[]>()
|
||||||
|
// 操作按钮显示名称可编辑
|
||||||
|
const btnDisplayNameEdit = ref<boolean[]>([])
|
||||||
|
const changeBtnDisplayName = (index: number) => {
|
||||||
|
btnDisplayNameEdit.value[index] = true
|
||||||
|
}
|
||||||
|
const btnDisplayNameBlurEvent = (index: number) => {
|
||||||
|
btnDisplayNameEdit.value[index] = false
|
||||||
|
const buttonItem = buttonsSetting.value![index]
|
||||||
|
buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
buttonsSetting,
|
||||||
|
btnDisplayNameEdit,
|
||||||
|
changeBtnDisplayName,
|
||||||
|
btnDisplayNameBlurEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 审批人超时未处理配置
|
||||||
|
*/
|
||||||
|
function useTimeoutHandler() {
|
||||||
|
// 时间单位
|
||||||
|
const timeUnit = ref(TimeUnitType.HOUR)
|
||||||
|
|
||||||
|
// 超时开关改变
|
||||||
|
const timeoutHandlerChange = () => {
|
||||||
|
if (configForm.value.timeoutHandlerEnable) {
|
||||||
|
timeUnit.value = 2
|
||||||
|
configForm.value.timeDuration = 6
|
||||||
|
configForm.value.timeoutHandlerType = 1
|
||||||
|
configForm.value.maxRemindCount = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 超时执行的动作
|
||||||
|
const cTimeoutType = computed(() => {
|
||||||
|
if (!configForm.value.timeoutHandlerEnable) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return configForm.value.timeoutHandlerType
|
||||||
|
})
|
||||||
|
|
||||||
|
// 超时处理动作改变
|
||||||
|
const timeoutHandlerTypeChanged = () => {
|
||||||
|
if (configForm.value.timeoutHandlerType === TimeoutHandlerType.REMINDER) {
|
||||||
|
configForm.value.maxRemindCount = 1 // 超时提醒次数,默认为1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间单位改变
|
||||||
|
const timeUnitChange = () => {
|
||||||
|
// 分钟,默认是 60 分钟
|
||||||
|
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||||
|
configForm.value.timeDuration = 60
|
||||||
|
}
|
||||||
|
// 小时,默认是 6 个小时
|
||||||
|
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||||
|
configForm.value.timeDuration = 6
|
||||||
|
}
|
||||||
|
// 天, 默认 1天
|
||||||
|
if (timeUnit.value === TimeUnitType.DAY) {
|
||||||
|
configForm.value.timeDuration = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 超时时间的 ISO 表示
|
||||||
|
const isoTimeDuration = computed(() => {
|
||||||
|
if (!configForm.value.timeoutHandlerEnable) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
let strTimeDuration = 'PT'
|
||||||
|
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||||
|
strTimeDuration += configForm.value.timeDuration + 'M'
|
||||||
|
}
|
||||||
|
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||||
|
strTimeDuration += configForm.value.timeDuration + 'H'
|
||||||
|
}
|
||||||
|
if (timeUnit.value === TimeUnitType.DAY) {
|
||||||
|
strTimeDuration += configForm.value.timeDuration + 'D'
|
||||||
|
}
|
||||||
|
return strTimeDuration
|
||||||
|
})
|
||||||
|
|
||||||
|
// 超时最大提醒次数
|
||||||
|
const cTimeoutMaxRemindCount = computed(() => {
|
||||||
|
if (!configForm.value.timeoutHandlerEnable) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (configForm.value.timeoutHandlerType !== TimeoutHandlerType.REMINDER) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return configForm.value.maxRemindCount
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
timeoutHandlerChange,
|
||||||
|
cTimeoutType,
|
||||||
|
timeoutHandlerTypeChanged,
|
||||||
|
timeUnit,
|
||||||
|
timeUnitChange,
|
||||||
|
isoTimeDuration,
|
||||||
|
cTimeoutMaxRemindCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.button-setting-pane {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.button-setting-desc {
|
||||||
|
padding-right: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-setting-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 45px;
|
||||||
|
padding-left: 12px;
|
||||||
|
background-color: #f8fafc0a;
|
||||||
|
border: 1px solid #1f38581a;
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
width: 100px !important;
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :last-child {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-title-label {
|
||||||
|
width: 150px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #000;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-setting-item {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 38px;
|
||||||
|
padding-left: 12px;
|
||||||
|
border: 1px solid #1f38581a;
|
||||||
|
border-top: 0;
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
width: 100px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :last-child {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-setting-item-label {
|
||||||
|
width: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: left;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editable-title-input {
|
||||||
|
height: 24px;
|
||||||
|
max-width: 130px;
|
||||||
|
margin-left: 4px;
|
||||||
|
line-height: 24px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||||
|
<div class="node-title-container">
|
||||||
|
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
|
||||||
|
<input
|
||||||
|
v-if="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.COPY_TASK_NODE) }}
|
||||||
|
</div>
|
||||||
|
<Icon icon="ep:arrow-right-bold" />
|
||||||
|
</div>
|
||||||
|
<div 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" />
|
||||||
|
</div>
|
||||||
|
<CopyTaskNodeConfig v-if="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 } from '../node'
|
||||||
|
import CopyTaskNodeConfig from '../nodes-config/CopyTaskNodeConfig.vue'
|
||||||
|
defineOptions({
|
||||||
|
name: 'CopyTaskNode'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件,更新父组件。
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 监控节点的变化
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称编辑
|
||||||
|
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.COPY_TASK_NODE)
|
||||||
|
|
||||||
|
const nodeSetting = ref()
|
||||||
|
// 打开节点配置
|
||||||
|
const openNodeConfig = () => {
|
||||||
|
nodeSetting.value.showCopyTaskNodeConfig(currentNode.value)
|
||||||
|
nodeSetting.value.openDrawer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除节点。更新当前节点为孩子节点
|
||||||
|
const deleteNode = () => {
|
||||||
|
emits('update:flowNode', currentNode.value.childNode)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<template>
|
||||||
|
<div class="end-node-wrapper">
|
||||||
|
<div class="end-node-box">
|
||||||
|
<span class="node-fixed-name" title="结束">结束</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
name: 'EndEventNode'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,207 @@
|
||||||
|
<template>
|
||||||
|
<div class="branch-node-wrapper">
|
||||||
|
<div class="branch-node-container">
|
||||||
|
<div class="branch-node-add" @click="addCondition">添加条件</div>
|
||||||
|
<div
|
||||||
|
class="branch-node-item"
|
||||||
|
v-for="(item, index) in currentNode.conditionNodes"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<template v-if="index == 0">
|
||||||
|
<div class="branch-line-first-top"> </div>
|
||||||
|
<div class="branch-line-first-bottom"></div>
|
||||||
|
</template>
|
||||||
|
<template v-if="index + 1 == currentNode.conditionNodes?.length">
|
||||||
|
<div class="branch-line-last-top"></div>
|
||||||
|
<div class="branch-line-last-bottom"></div>
|
||||||
|
</template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div class="node-box" :class="{ 'node-config-error': !item.showText }">
|
||||||
|
<div class="branch-node-title-container">
|
||||||
|
<div v-if="showInputs[index]">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input-max-width editable-title-input"
|
||||||
|
@blur="blurEvent(index)"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="item.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
|
||||||
|
<div class="branch-priority"> 优先级{{ index + 1 }} </div>
|
||||||
|
</div>
|
||||||
|
<div class="branch-node-content" @click="conditionNodeConfig(item.id)">
|
||||||
|
<div class="branch-node-text" :title="item.showText" v-if="item.showText">
|
||||||
|
{{ item.showText }}
|
||||||
|
</div>
|
||||||
|
<div class="branch-node-text" v-else>
|
||||||
|
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-toolbar" v-if="index + 1 !== currentNode.conditionNodes?.length">
|
||||||
|
<div class="toolbar-icon">
|
||||||
|
<Icon
|
||||||
|
color="#0089ff"
|
||||||
|
icon="ep:circle-close-filled"
|
||||||
|
:size="18"
|
||||||
|
@click="deleteCondition(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="branch-node-move move-node-left"
|
||||||
|
v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length"
|
||||||
|
@click="moveNode(index, -1)"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:arrow-left" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="branch-node-move move-node-right"
|
||||||
|
v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||||
|
@click="moveNode(index, 1)"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:arrow-right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NodeHandler v-model:child-node="item.childNode" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||||
|
<!-- 递归显示子节点 -->
|
||||||
|
<ProcessNodeTree
|
||||||
|
v-if="item && item.childNode"
|
||||||
|
:parent-node="item"
|
||||||
|
v-model:flow-node="item.childNode"
|
||||||
|
@find:recursive-find-parent-node="recursiveFindParentNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
|
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||||
|
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
|
import { getDefaultConditionNodeName } from '../utils'
|
||||||
|
import { generateUUID } from '@/utils'
|
||||||
|
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
defineOptions({
|
||||||
|
name: 'ExclusiveNode'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
// parentNode : {
|
||||||
|
// type: Object as () => SimpleFlowNode,
|
||||||
|
// required: true
|
||||||
|
// },
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件,更新父组件
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||||
|
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
|
||||||
|
'find:recursiveFindParentNode': [
|
||||||
|
nodeList: SimpleFlowNode[],
|
||||||
|
curentNode: SimpleFlowNode,
|
||||||
|
nodeType: number
|
||||||
|
]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||||
|
// const conditionNodes = computed(() => currentNode.value.conditionNodes);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.flowNode,
|
||||||
|
(newValue) => {
|
||||||
|
currentNode.value = newValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const showInputs = ref<boolean[]>([])
|
||||||
|
// 失去焦点
|
||||||
|
const blurEvent = (index: number) => {
|
||||||
|
showInputs.value[index] = false
|
||||||
|
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||||
|
conditionNode.name =
|
||||||
|
conditionNode.name || getDefaultConditionNodeName(index, conditionNode.defaultFlow)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击条件名称
|
||||||
|
const clickEvent = (index: number) => {
|
||||||
|
showInputs.value[index] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionNodeConfig = (nodeId: string) => {
|
||||||
|
const conditionNode = proxy.$refs[nodeId][0]
|
||||||
|
conditionNode.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增条件
|
||||||
|
const addCondition = () => {
|
||||||
|
const conditionNodes = currentNode.value.conditionNodes
|
||||||
|
if (conditionNodes) {
|
||||||
|
const len = conditionNodes.length
|
||||||
|
let lastIndex = len - 1
|
||||||
|
const conditionData: SimpleFlowNode = {
|
||||||
|
id: 'Flow_' + generateUUID(),
|
||||||
|
name: '条件' + len,
|
||||||
|
showText: '',
|
||||||
|
type: NodeType.CONDITION_NODE,
|
||||||
|
childNode: undefined,
|
||||||
|
conditionNodes: [],
|
||||||
|
conditionType: 1,
|
||||||
|
defaultFlow: false
|
||||||
|
}
|
||||||
|
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除条件
|
||||||
|
const deleteCondition = (index: number) => {
|
||||||
|
const conditionNodes = currentNode.value.conditionNodes
|
||||||
|
if (conditionNodes) {
|
||||||
|
conditionNodes.splice(index, 1)
|
||||||
|
if (conditionNodes.length == 1) {
|
||||||
|
const childNode = currentNode.value.childNode
|
||||||
|
// 更新此节点为后续孩子节点
|
||||||
|
emits('update:modelValue', childNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动节点
|
||||||
|
const moveNode = (index: number, to: number) => {
|
||||||
|
// -1 :向左 1: 向右
|
||||||
|
if (currentNode.value.conditionNodes) {
|
||||||
|
currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
|
||||||
|
index + to,
|
||||||
|
1,
|
||||||
|
currentNode.value.conditionNodes[index]
|
||||||
|
)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 递归从父节点中查询匹配的节点
|
||||||
|
const recursiveFindParentNode = (
|
||||||
|
nodeList: SimpleFlowNode[],
|
||||||
|
node: SimpleFlowNode,
|
||||||
|
nodeType: number
|
||||||
|
) => {
|
||||||
|
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node.type === nodeType) {
|
||||||
|
nodeList.push(node)
|
||||||
|
}
|
||||||
|
// 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.EXCLUSIVE_NODE) 继续查找
|
||||||
|
emits('find:parentNode', nodeList, nodeType)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,181 @@
|
||||||
|
<template>
|
||||||
|
<div class="branch-node-wrapper">
|
||||||
|
<div class="branch-node-container">
|
||||||
|
<div class="branch-node-add" @click="addCondition">添加分支</div>
|
||||||
|
<div
|
||||||
|
class="branch-node-item"
|
||||||
|
v-for="(item, index) in currentNode.conditionNodes"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<template v-if="index == 0">
|
||||||
|
<div class="branch-line-first-top"></div>
|
||||||
|
<div class="branch-line-first-bottom"></div>
|
||||||
|
</template>
|
||||||
|
<template v-if="index + 1 == currentNode.conditionNodes?.length">
|
||||||
|
<div class="branch-line-last-top"></div>
|
||||||
|
<div class="branch-line-last-bottom"></div>
|
||||||
|
</template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div class="node-box">
|
||||||
|
<div class="branch-node-title-container">
|
||||||
|
<div v-if="showInputs[index]">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input-max-width editable-title-input"
|
||||||
|
@blur="blurEvent(index)"
|
||||||
|
v-mountedFocus
|
||||||
|
v-model="item.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
|
||||||
|
<div class="branch-priority">无优先级</div>
|
||||||
|
</div>
|
||||||
|
<div class="branch-node-content" @click="conditionNodeConfig(item.id)">
|
||||||
|
<div class="branch-node-text" :title="item.showText" v-if="item.showText">
|
||||||
|
{{ item.showText }}
|
||||||
|
</div>
|
||||||
|
<div class="branch-node-text" v-else>
|
||||||
|
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-toolbar">
|
||||||
|
<div class="toolbar-icon">
|
||||||
|
<Icon
|
||||||
|
color="#0089ff"
|
||||||
|
icon="ep:circle-close-filled"
|
||||||
|
:size="18"
|
||||||
|
@click="deleteCondition(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div
|
||||||
|
class="branch-node-move move-node-left"
|
||||||
|
v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length" @click="moveNode(index, -1)">
|
||||||
|
<Icon icon="ep:arrow-left" />
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<!-- <div
|
||||||
|
class="branch-node-move move-node-right"
|
||||||
|
v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||||
|
@click="moveNode(index, 1)">
|
||||||
|
<Icon icon="ep:arrow-right" />
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<NodeHandler v-model:child-node="item.childNode" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 递归显示子节点 -->
|
||||||
|
<ProcessNodeTree
|
||||||
|
v-if="item && item.childNode"
|
||||||
|
:parent-node="item"
|
||||||
|
v-model:flow-node="item.childNode"
|
||||||
|
@find:recursive-find-parent-node="recursiveFindParentNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
|
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||||
|
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
|
import { generateUUID } from '@/utils'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
defineOptions({
|
||||||
|
name: 'ParallelNode'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件,更新父组件
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||||
|
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
|
||||||
|
'find:recursiveFindParentNode': [
|
||||||
|
nodeList: SimpleFlowNode[],
|
||||||
|
curentNode: SimpleFlowNode,
|
||||||
|
nodeType: number
|
||||||
|
]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.flowNode,
|
||||||
|
(newValue) => {
|
||||||
|
currentNode.value = newValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const showInputs = ref<boolean[]>([])
|
||||||
|
// 失去焦点
|
||||||
|
const blurEvent = (index: number) => {
|
||||||
|
showInputs.value[index] = false
|
||||||
|
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||||
|
conditionNode.name = conditionNode.name || `并行${index + 1}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击条件名称
|
||||||
|
const clickEvent = (index: number) => {
|
||||||
|
showInputs.value[index] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionNodeConfig = (nodeId: string) => {
|
||||||
|
const conditionNode = proxy.$refs[nodeId][0]
|
||||||
|
conditionNode.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增条件
|
||||||
|
const addCondition = () => {
|
||||||
|
const conditionNodes = currentNode.value.conditionNodes
|
||||||
|
if (conditionNodes) {
|
||||||
|
const len = conditionNodes.length
|
||||||
|
let lastIndex = len - 1
|
||||||
|
const conditionData: SimpleFlowNode = {
|
||||||
|
id: 'Flow_' + generateUUID(),
|
||||||
|
name: '并行' + len,
|
||||||
|
showText: '无需配置条件同时执行',
|
||||||
|
type: NodeType.CONDITION_NODE,
|
||||||
|
childNode: undefined,
|
||||||
|
conditionNodes: []
|
||||||
|
}
|
||||||
|
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除条件
|
||||||
|
const deleteCondition = (index: number) => {
|
||||||
|
const conditionNodes = currentNode.value.conditionNodes
|
||||||
|
if (conditionNodes) {
|
||||||
|
conditionNodes.splice(index, 1)
|
||||||
|
if (conditionNodes.length == 1) {
|
||||||
|
const childNode = currentNode.value.childNode
|
||||||
|
// 更新此节点为后续孩子节点
|
||||||
|
emits('update:modelValue', childNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归从父节点中查询匹配的节点
|
||||||
|
const recursiveFindParentNode = (
|
||||||
|
nodeList: SimpleFlowNode[],
|
||||||
|
node: SimpleFlowNode,
|
||||||
|
nodeType: number
|
||||||
|
) => {
|
||||||
|
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node.type === nodeType) {
|
||||||
|
nodeList.push(node)
|
||||||
|
}
|
||||||
|
// 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点并行节点(NodeType.PARALLEL_NODE) 继续查找
|
||||||
|
emits('find:parentNode', nodeList, nodeType)
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||||
|
<div class="node-title-container">
|
||||||
|
<div class="node-title-icon start-user"
|
||||||
|
><span class="iconfont icon-start-user"></span
|
||||||
|
></div>
|
||||||
|
<input
|
||||||
|
v-if="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.START_USER_NODE) }}
|
||||||
|
</div>
|
||||||
|
<Icon icon="ep:arrow-right-bold" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||||
|
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<StartUserNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
|
import { useWatchNode, useNodeName2 } from '../node'
|
||||||
|
import { SimpleFlowNode, NODE_DEFAULT_TEXT, NodeType } from '../consts'
|
||||||
|
import StartUserNodeConfig from '../nodes-config/StartUserNodeConfig.vue'
|
||||||
|
defineOptions({
|
||||||
|
name: 'StartEventNode'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
default: () => null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 定义事件,更新父组件。
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||||
|
}>()
|
||||||
|
// 监控节点变化
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称编辑
|
||||||
|
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||||
|
|
||||||
|
const nodeSetting = ref()
|
||||||
|
// 打开节点配置
|
||||||
|
const openNodeConfig = () => {
|
||||||
|
// 把当前节点传递给配置组件
|
||||||
|
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||||
|
nodeSetting.value.openDrawer()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<div class="node-wrapper">
|
||||||
|
<div class="node-container">
|
||||||
|
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||||
|
<div class="node-title-container">
|
||||||
|
<div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div>
|
||||||
|
<input
|
||||||
|
v-if="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.USER_TASK_NODE) }}
|
||||||
|
</div>
|
||||||
|
<Icon icon="ep:arrow-right-bold" />
|
||||||
|
</div>
|
||||||
|
<div 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" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UserTaskNodeConfig
|
||||||
|
v-if="currentNode"
|
||||||
|
ref="nodeSetting"
|
||||||
|
:flow-node="currentNode"
|
||||||
|
@find:return-task-nodes="findReturnTaskNodes"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||||
|
import { useWatchNode, useNodeName2 } from '../node'
|
||||||
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
|
import UserTaskNodeConfig from '../nodes-config/UserTaskNodeConfig.vue'
|
||||||
|
defineOptions({
|
||||||
|
name: 'UserTaskNode'
|
||||||
|
})
|
||||||
|
const props = defineProps({
|
||||||
|
flowNode: {
|
||||||
|
type: Object as () => SimpleFlowNode,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||||
|
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
|
||||||
|
}>()
|
||||||
|
// 监控节点变化
|
||||||
|
const currentNode = useWatchNode(props)
|
||||||
|
// 节点名称编辑
|
||||||
|
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||||
|
const nodeSetting = ref()
|
||||||
|
// 打开节点配置
|
||||||
|
const openNodeConfig = () => {
|
||||||
|
// 把当前节点传递给配置组件
|
||||||
|
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||||
|
nodeSetting.value.openDrawer()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteNode = () => {
|
||||||
|
emits('update:flowNode', currentNode.value.childNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找可以驳回用户节点
|
||||||
|
const findReturnTaskNodes = (
|
||||||
|
matchNodeList: SimpleFlowNode[] // 匹配的节点
|
||||||
|
) => {
|
||||||
|
// 从父节点查找
|
||||||
|
emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { TimeUnitType, ApproveType, APPROVE_TYPE } from './consts'
|
||||||
|
|
||||||
|
// 获取条件节点默认的名称
|
||||||
|
export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => {
|
||||||
|
if (defaultFlow) {
|
||||||
|
return '其它情况'
|
||||||
|
}
|
||||||
|
return '条件' + (index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const convertTimeUnit = (strTimeUnit: string) => {
|
||||||
|
if (strTimeUnit === 'M') {
|
||||||
|
return TimeUnitType.MINUTE
|
||||||
|
}
|
||||||
|
if (strTimeUnit === 'H') {
|
||||||
|
return TimeUnitType.HOUR
|
||||||
|
}
|
||||||
|
if (strTimeUnit === 'D') {
|
||||||
|
return TimeUnitType.DAY
|
||||||
|
}
|
||||||
|
return TimeUnitType.HOUR
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getApproveTypeText = (approveType: ApproveType): string => {
|
||||||
|
let approveTypeText = ''
|
||||||
|
APPROVE_TYPE.forEach((item) => {
|
||||||
|
if (item.value === approveType) {
|
||||||
|
approveTypeText = item.label
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return approveTypeText
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,714 @@
|
||||||
|
.simple-flow-canvas {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: #fafafa;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.simple-flow-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.top-area-container {
|
||||||
|
position: sticky;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 42px;
|
||||||
|
z-index: 1;
|
||||||
|
// padding: 4px 0;
|
||||||
|
background-color: #fff;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.top-actions {
|
||||||
|
display: flex;
|
||||||
|
margin: 4px;
|
||||||
|
margin-right: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.canvas-control {
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
.control-scale-group {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
.control-scale-button {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 2px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-scale-label {
|
||||||
|
margin: 0 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
transform-origin: 50% 0 0;
|
||||||
|
transform: scale(1);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
|
||||||
|
// 节点容器 定义节点宽度
|
||||||
|
.node-container {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
// 节点
|
||||||
|
.node-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
min-height: 70px;
|
||||||
|
padding: 5px 10px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #fff;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
// border-color: #0089ff;
|
||||||
|
border-radius: 8px;
|
||||||
|
// border-color: #0089ff;
|
||||||
|
box-shadow: 0 1px 4px 0 rgba(10, 30, 65, 0.16);
|
||||||
|
transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #0089ff;
|
||||||
|
.node-toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-node-move {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通节点标题
|
||||||
|
.node-title-container {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.node-title-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.user-task {
|
||||||
|
color: #ff943e;
|
||||||
|
}
|
||||||
|
&.copy-task {
|
||||||
|
color: #3296fa;
|
||||||
|
}
|
||||||
|
&.start-user {
|
||||||
|
color: #676565;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: #1f1f1f;
|
||||||
|
line-height: 18px;
|
||||||
|
&:hover {
|
||||||
|
border-bottom: 1px dashed #f60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 条件节点标题
|
||||||
|
.branch-node-title-container {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.input-max-width {
|
||||||
|
max-width: 115px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: #f60;
|
||||||
|
&:hover {
|
||||||
|
border-bottom: 1px dashed #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-priority {
|
||||||
|
min-width: 50px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-content {
|
||||||
|
display: flex;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 32px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: #111f2c;
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.node-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
-webkit-line-clamp: 2; /* 这将限制文本显示为两行 */
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//条件节点内容
|
||||||
|
.branch-node-content {
|
||||||
|
display: flex;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 32px;
|
||||||
|
align-items: center;
|
||||||
|
color: #111f2c;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.branch-node-text {
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
-webkit-line-clamp: 2; /* 这将限制文本显示为两行 */
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点操作 :删除
|
||||||
|
.node-toolbar {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: 0px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.toolbar-icon {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 条件节点左右移动
|
||||||
|
.branch-node-move {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-node-left {
|
||||||
|
left: -2px;
|
||||||
|
top: 0px;
|
||||||
|
background: rgba(126, 134, 142, 0.08);
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-node-right {
|
||||||
|
right: -2px;
|
||||||
|
top: 0px;
|
||||||
|
background: rgba(126, 134, 142, 0.08);
|
||||||
|
border-top-right-radius: 6px;
|
||||||
|
border-bottom-right-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-config-error {
|
||||||
|
border-color: #ff5219 !important;
|
||||||
|
}
|
||||||
|
// 普通节点包装
|
||||||
|
.node-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
// 节点连线处理
|
||||||
|
.node-handler-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
height: 70px;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
// bottom: 5px;
|
||||||
|
bottom: 0px;
|
||||||
|
z-index: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
// height: calc(100% - 5px);
|
||||||
|
margin: auto;
|
||||||
|
background-color: #dedede;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-handler {
|
||||||
|
.add-icon {
|
||||||
|
position: relative;
|
||||||
|
top: -5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0089ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-handler-arrow {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
display: flex;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 条件节点包装
|
||||||
|
.branch-node-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
.branch-node-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 4px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
content: '';
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-node-add {
|
||||||
|
position: absolute;
|
||||||
|
top: -18px;
|
||||||
|
left: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 36px;
|
||||||
|
color: #222;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #dedede;
|
||||||
|
border-radius: 18px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
transform-origin: center center;
|
||||||
|
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-node-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 280px;
|
||||||
|
padding: 40px 40px 0;
|
||||||
|
background: transparent;
|
||||||
|
border-top: 2px solid #dedede;
|
||||||
|
border-bottom: 2px solid #dedede;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
inset: 0;
|
||||||
|
background-color: #dedede;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 覆盖条件节点第一个节点左上角的线
|
||||||
|
.branch-line-first-top {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
left: -1px;
|
||||||
|
width: 50%;
|
||||||
|
height: 7px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
// 覆盖条件节点第一个节点左下角的线
|
||||||
|
.branch-line-first-bottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
left: -1px;
|
||||||
|
width: 50%;
|
||||||
|
height: 7px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
// 覆盖条件节点最后一个节点右上角的线
|
||||||
|
.branch-line-last-top {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -1px;
|
||||||
|
width: 50%;
|
||||||
|
height: 7px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
// 覆盖条件节点最后一个节点右下角的线
|
||||||
|
.branch-line-last-bottom {
|
||||||
|
position: absolute;
|
||||||
|
right: -1px;
|
||||||
|
bottom: -5px;
|
||||||
|
width: 50%;
|
||||||
|
height: 7px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-fixed-name {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
padding: 0 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
// 开始节点包装
|
||||||
|
.start-node-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
.start-node-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.start-node-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 90px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 3px 4px;
|
||||||
|
color: #212121;
|
||||||
|
cursor: pointer;
|
||||||
|
// background: #2c2c2c;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 结束节点包装
|
||||||
|
.end-node-wrapper {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.end-node-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 80px;
|
||||||
|
height: 36px;
|
||||||
|
color: #212121;
|
||||||
|
// background: #6e6e6e;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可编辑的 title 输入框
|
||||||
|
.editable-title-input {
|
||||||
|
height: 20px;
|
||||||
|
max-width: 145px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 4px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置节点头部
|
||||||
|
.config-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.node-name {
|
||||||
|
display: flex;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divide-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
margin-top: 16px;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-editable-input {
|
||||||
|
height: 24px;
|
||||||
|
max-width: 510px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单字段权限
|
||||||
|
.field-setting-pane {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.field-setting-desc {
|
||||||
|
padding-right: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-permit-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 45px;
|
||||||
|
padding-left: 12px;
|
||||||
|
line-height: 45px;
|
||||||
|
background-color: #f8fafc0a;
|
||||||
|
border: 1px solid #1f38581a;
|
||||||
|
|
||||||
|
.first-title {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-titles {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-title-label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 110px;
|
||||||
|
padding: 5px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #000;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-setting-item {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 38px;
|
||||||
|
padding-left: 12px;
|
||||||
|
border: 1px solid #1f38581a;
|
||||||
|
border-top: 0;
|
||||||
|
|
||||||
|
.field-setting-item-label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 110px;
|
||||||
|
min-height: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-setting-item-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.item-radio-wrap {
|
||||||
|
display: inline-block;
|
||||||
|
width: 110px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点连线气泡卡片样式
|
||||||
|
.handler-item-wrapper {
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.handler-item {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handler-item-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e2e2e2;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
user-select: none;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e2e2e2;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size {
|
||||||
|
font-size: 35px;
|
||||||
|
line-height: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.approve {
|
||||||
|
color: #ff943e;
|
||||||
|
}
|
||||||
|
.copy {
|
||||||
|
color: #3296fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition {
|
||||||
|
color: #15bc83;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handler-item-text {
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iconfont 样式
|
||||||
|
@font-face {
|
||||||
|
font-family: 'iconfont'; /* Project id 4495938 */
|
||||||
|
src:
|
||||||
|
url('iconfont.woff2?t=1724339470412') format('woff2'),
|
||||||
|
url('iconfont.woff?t=1724339470412') format('woff'),
|
||||||
|
url('iconfont.ttf?t=1724339470412') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-family: 'iconfont' !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-start-user:before {
|
||||||
|
content: '\e679';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-inclusive:before {
|
||||||
|
content: '\e602';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-copy:before {
|
||||||
|
content: '\e7eb';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-handle:before {
|
||||||
|
content: '\e61c';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-exclusive:before {
|
||||||
|
content: '\e717';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-approve:before {
|
||||||
|
content: '\e715';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-parallel:before {
|
||||||
|
content: '\e688';
|
||||||
|
}
|
|
@ -11,3 +11,14 @@ export const setupAuth = (app: App<Element>) => {
|
||||||
hasRole(app)
|
hasRole(app)
|
||||||
hasPermi(app)
|
hasPermi(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出指令:v-mountedFocus
|
||||||
|
*/
|
||||||
|
export const setupMountedFocus = (app: App<Element>) => {
|
||||||
|
app.directive('mountedFocus', {
|
||||||
|
mounted(el) {
|
||||||
|
el.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ import '@/plugins/animate.css'
|
||||||
// 路由
|
// 路由
|
||||||
import router, { setupRouter } from '@/router'
|
import router, { setupRouter } from '@/router'
|
||||||
|
|
||||||
// 权限
|
// 指令
|
||||||
import { setupAuth } from '@/directives'
|
import { setupAuth, setupMountedFocus } from '@/directives'
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
|
@ -58,7 +58,9 @@ const setupAll = async () => {
|
||||||
|
|
||||||
setupRouter(app)
|
setupRouter(app)
|
||||||
|
|
||||||
|
// directives 指令
|
||||||
setupAuth(app)
|
setupAuth(app)
|
||||||
|
setupMountedFocus(app)
|
||||||
|
|
||||||
await router.isReady()
|
await router.isReady()
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'process-instance/detail',
|
path: 'process-instance/detail',
|
||||||
component: () => import('@/views/bpm/processInstance/detail/index.vue'),
|
component: () => import('@/views/bpm/processInstance/detail/index_new.vue'),
|
||||||
name: 'BpmProcessInstanceDetail',
|
name: 'BpmProcessInstanceDetail',
|
||||||
meta: {
|
meta: {
|
||||||
noCache: true,
|
noCache: true,
|
||||||
|
@ -300,7 +300,14 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||||
canTo: true,
|
canTo: true,
|
||||||
title: '流程详情',
|
title: '流程详情',
|
||||||
activeMenu: '/bpm/task/my'
|
activeMenu: '/bpm/task/my'
|
||||||
}
|
},
|
||||||
|
props: route => (
|
||||||
|
{
|
||||||
|
id: route.query.id,
|
||||||
|
taskId: route.query.taskId,
|
||||||
|
activityId: route.query.activityId
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'oa/leave/create',
|
path: 'oa/leave/create',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { store } from '../index'
|
import { store } from '../../index'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
export const useWorkFlowStore = defineStore('simpleWorkflow', {
|
export const useWorkFlowStore = defineStore('simpleWorkflow', {
|
||||||
|
@ -6,15 +6,15 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', {
|
||||||
tableId: '',
|
tableId: '',
|
||||||
isTried: false,
|
isTried: false,
|
||||||
promoterDrawer: false,
|
promoterDrawer: false,
|
||||||
flowPermission1: {},
|
|
||||||
approverDrawer: false,
|
approverDrawer: false,
|
||||||
approverConfig1: {},
|
approverConfig1: {},
|
||||||
copyerDrawer: false,
|
copyerDrawer: false,
|
||||||
copyerConfig1: {},
|
copyerConfig: {},
|
||||||
conditionDrawer: false,
|
conditionDrawer: false,
|
||||||
conditionsConfig1: {
|
conditionsConfig1: {
|
||||||
conditionNodes: []
|
conditionNodes: []
|
||||||
}
|
},
|
||||||
|
userTaskConfig: {}
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
setTableId(payload) {
|
setTableId(payload) {
|
||||||
|
@ -26,26 +26,26 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', {
|
||||||
setPromoter(payload) {
|
setPromoter(payload) {
|
||||||
this.promoterDrawer = payload
|
this.promoterDrawer = payload
|
||||||
},
|
},
|
||||||
setFlowPermission(payload) {
|
setApproverDrawer(payload) {
|
||||||
this.flowPermission1 = payload
|
|
||||||
},
|
|
||||||
setApprover(payload) {
|
|
||||||
this.approverDrawer = payload
|
this.approverDrawer = payload
|
||||||
},
|
},
|
||||||
setApproverConfig(payload) {
|
setApproverConfig(payload) {
|
||||||
this.approverConfig1 = payload
|
this.approverConfig1 = payload
|
||||||
},
|
},
|
||||||
setCopyer(payload) {
|
setCopyerDrawer(payload) {
|
||||||
this.copyerDrawer = payload
|
this.copyerDrawer = payload
|
||||||
},
|
},
|
||||||
setCopyerConfig(payload) {
|
setCopyerConfig(payload) {
|
||||||
this.copyerConfig1 = payload
|
this.copyerConfig = payload
|
||||||
},
|
},
|
||||||
setCondition(payload) {
|
setCondition(payload) {
|
||||||
this.conditionDrawer = payload
|
this.conditionDrawer = payload
|
||||||
},
|
},
|
||||||
setConditionsConfig(payload) {
|
setConditionsConfig(payload) {
|
||||||
this.conditionsConfig1 = payload
|
this.conditionsConfig1 = payload
|
||||||
|
},
|
||||||
|
setUserTaskConfig(payload) {
|
||||||
|
this.userTaskConfig = payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -437,3 +437,15 @@ export const ErpBizType = {
|
||||||
SALE_OUT: 21,
|
SALE_OUT: 21,
|
||||||
SALE_RETURN: 22
|
SALE_RETURN: 22
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== BPM 模块 ==========
|
||||||
|
|
||||||
|
export const BpmModelType = {
|
||||||
|
BPMN: 10, // BPMN 设计器
|
||||||
|
SIMPLE: 20 // 简易设计器
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BpmModelFormType = {
|
||||||
|
NORMAL: 10, // 流程表单
|
||||||
|
CUSTOM: 20 // 业务表单
|
||||||
|
}
|
||||||
|
|
|
@ -143,6 +143,7 @@ export enum DICT_TYPE {
|
||||||
INFRA_OPERATE_TYPE = 'infra_operate_type',
|
INFRA_OPERATE_TYPE = 'infra_operate_type',
|
||||||
|
|
||||||
// ========== BPM 模块 ==========
|
// ========== BPM 模块 ==========
|
||||||
|
BPM_MODEL_TYPE = 'bpm_model_type',
|
||||||
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
|
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
|
||||||
BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',
|
BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',
|
||||||
BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',
|
BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',
|
||||||
|
|
|
@ -8,12 +8,7 @@
|
||||||
label-width="110px"
|
label-width="110px"
|
||||||
>
|
>
|
||||||
<el-form-item label="流程标识" prop="key">
|
<el-form-item label="流程标识" prop="key">
|
||||||
<el-input
|
<el-input v-model="formData.key" :disabled="!!formData.id" placeholder="请输入流标标识" />
|
||||||
v-model="formData.key"
|
|
||||||
:disabled="!!formData.id"
|
|
||||||
placeholder="请输入流标标识"
|
|
||||||
style="width: 330px"
|
|
||||||
/>
|
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="!formData.id"
|
v-if="!formData.id"
|
||||||
class="item"
|
class="item"
|
||||||
|
@ -35,7 +30,7 @@
|
||||||
placeholder="请输入流程名称"
|
placeholder="请输入流程名称"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="formData.id" label="流程分类" prop="category">
|
<el-form-item label="流程分类" prop="category">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="formData.category"
|
v-model="formData.category"
|
||||||
clearable
|
clearable
|
||||||
|
@ -50,73 +45,108 @@
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="formData.id" label="流程图标" prop="icon">
|
<el-form-item label="流程图标" prop="icon">
|
||||||
<UploadImg v-model="formData.icon" :limit="1" height="128px" width="128px" />
|
<UploadImg v-model="formData.icon" :limit="1" height="64px" width="64px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="流程描述" prop="description">
|
<el-form-item label="流程描述" prop="description">
|
||||||
<el-input v-model="formData.description" clearable type="textarea" />
|
<el-input v-model="formData.description" clearable type="textarea" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-if="formData.id">
|
<el-form-item label="流程类型" prop="type">
|
||||||
<el-form-item label="表单类型" prop="formType">
|
<el-radio-group v-model="formData.type">
|
||||||
<el-radio-group v-model="formData.formType">
|
<el-radio
|
||||||
<el-radio
|
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
|
:key="dict.value"
|
||||||
:key="dict.value"
|
:label="dict.value"
|
||||||
:value="dict.value"
|
|
||||||
>
|
|
||||||
{{ dict.label }}
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
|
|
||||||
<el-select v-model="formData.formId" clearable style="width: 100%">
|
|
||||||
<el-option
|
|
||||||
v-for="form in formList"
|
|
||||||
:key="form.id"
|
|
||||||
:label="form.name"
|
|
||||||
:value="form.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item
|
|
||||||
v-if="formData.formType === 20"
|
|
||||||
label="表单提交路由"
|
|
||||||
prop="formCustomCreatePath"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model="formData.formCustomCreatePath"
|
|
||||||
placeholder="请输入表单提交路由"
|
|
||||||
style="width: 330px"
|
|
||||||
/>
|
|
||||||
<el-tooltip
|
|
||||||
class="item"
|
|
||||||
content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create"
|
|
||||||
effect="light"
|
|
||||||
placement="top"
|
|
||||||
>
|
>
|
||||||
<i class="el-icon-question" style="padding-left: 5px"></i>
|
{{ dict.label }}
|
||||||
</el-tooltip>
|
</el-radio>
|
||||||
</el-form-item>
|
</el-radio-group>
|
||||||
<el-form-item
|
</el-form-item>
|
||||||
v-if="formData.formType === 20"
|
<el-form-item label="表单类型" prop="formType">
|
||||||
label="表单查看地址"
|
<el-radio-group v-model="formData.formType">
|
||||||
prop="formCustomViewPath"
|
<el-radio
|
||||||
>
|
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
|
||||||
<el-input
|
:key="dict.value"
|
||||||
v-model="formData.formCustomViewPath"
|
:label="dict.value"
|
||||||
placeholder="请输入表单查看的组件地址"
|
|
||||||
style="width: 330px"
|
|
||||||
/>
|
|
||||||
<el-tooltip
|
|
||||||
class="item"
|
|
||||||
content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail"
|
|
||||||
effect="light"
|
|
||||||
placement="top"
|
|
||||||
>
|
>
|
||||||
<i class="el-icon-question" style="padding-left: 5px"></i>
|
{{ dict.label }}
|
||||||
</el-tooltip>
|
</el-radio>
|
||||||
</el-form-item>
|
</el-radio-group>
|
||||||
</div>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
|
||||||
|
<el-select v-model="formData.formId" clearable style="width: 100%">
|
||||||
|
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="formData.formType === 20"
|
||||||
|
label="表单提交路由"
|
||||||
|
prop="formCustomCreatePath"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="formData.formCustomCreatePath"
|
||||||
|
placeholder="请输入表单提交路由"
|
||||||
|
style="width: 330px"
|
||||||
|
/>
|
||||||
|
<el-tooltip
|
||||||
|
class="item"
|
||||||
|
content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
|
||||||
|
effect="light"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<i class="el-icon-question" style="padding-left: 5px"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="formData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.formCustomViewPath"
|
||||||
|
placeholder="请输入表单查看的组件地址"
|
||||||
|
style="width: 330px"
|
||||||
|
/>
|
||||||
|
<el-tooltip
|
||||||
|
class="item"
|
||||||
|
content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
|
||||||
|
effect="light"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<i class="el-icon-question" style="padding-left: 5px"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否可见" prop="visible">
|
||||||
|
<el-radio-group v-model="formData.visible">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||||
|
:key="dict.value as string"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="谁可以发起" prop="startUserIds">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.startUserIds"
|
||||||
|
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-item label="流程管理员" prop="managerUserIds">
|
||||||
|
<el-select v-model="formData.managerUserIds" 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-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
@ -125,45 +155,62 @@
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
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 } from '@/api/bpm/category'
|
import { CategoryApi } from '@/api/bpm/category'
|
||||||
|
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
|
||||||
|
import { UserVO } from '@/api/system/user'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
defineOptions({ name: 'ModelForm' })
|
defineOptions({ name: 'ModelForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
formType: 10,
|
id: undefined,
|
||||||
name: '',
|
name: '',
|
||||||
|
key: '',
|
||||||
category: undefined,
|
category: undefined,
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
description: '',
|
description: '',
|
||||||
|
type: BpmModelType.BPMN,
|
||||||
|
formType: BpmModelFormType.NORMAL,
|
||||||
formId: '',
|
formId: '',
|
||||||
formCustomCreatePath: '',
|
formCustomCreatePath: '',
|
||||||
formCustomViewPath: ''
|
formCustomViewPath: '',
|
||||||
|
visible: true,
|
||||||
|
startUserIds: [],
|
||||||
|
managerUserIds: []
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
|
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||||
key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
|
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||||
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
|
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
|
||||||
icon: [{ required: true, message: '参数图标不能为空', trigger: 'blur' }],
|
icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
|
||||||
value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],
|
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]
|
formType: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||||
|
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
|
||||||
|
formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
|
||||||
|
formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }],
|
||||||
|
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||||
|
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const formList = ref([]) // 流程表单的下拉框的数据
|
const formList = ref([]) // 流程表单的下拉框的数据
|
||||||
const categoryList = ref([]) // 流程分类列表
|
const categoryList = ref([]) // 流程分类列表
|
||||||
|
const userList = ref<UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: string) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
dialogTitle.value = t('action.' + type)
|
dialogTitle.value = t('action.' + type)
|
||||||
formType.value = type
|
formType.value = type
|
||||||
|
@ -176,11 +223,15 @@ const open = async (type: string, id?: number) => {
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
formData.value.managerUserIds.push(userStore.getUser.id)
|
||||||
}
|
}
|
||||||
// 获得流程表单的下拉框的数据
|
// 获得流程表单的下拉框的数据
|
||||||
formList.value = await FormApi.getFormSimpleList()
|
formList.value = await FormApi.getFormSimpleList()
|
||||||
// 查询流程分类列表
|
// 查询流程分类列表
|
||||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||||
|
// 查询用户列表
|
||||||
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
@ -199,10 +250,9 @@ const submitForm = async () => {
|
||||||
await ModelApi.createModel(data)
|
await ModelApi.createModel(data)
|
||||||
// 提示,引导用户做后续的操作
|
// 提示,引导用户做后续的操作
|
||||||
await ElMessageBox.alert(
|
await ElMessageBox.alert(
|
||||||
'<strong>新建模型成功!</strong>后续需要执行如下 3 个步骤:' +
|
'<strong>新建模型成功!</strong>后续需要执行如下 2 个步骤:' +
|
||||||
'<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' +
|
'<div>1. 点击【设计流程】按钮,绘制流程图</div>' +
|
||||||
'<div>2. 点击【设计流程】按钮,绘制流程图</div>' +
|
'<div>2. 点击【发布流程】按钮,完成流程的最终发布</div>' +
|
||||||
'<div>3. 点击【发布流程】按钮,完成流程的最终发布</div>' +
|
|
||||||
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
|
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
|
||||||
'重要提示',
|
'重要提示',
|
||||||
{
|
{
|
||||||
|
@ -225,14 +275,20 @@ const submitForm = async () => {
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
formType: 10,
|
id: undefined,
|
||||||
name: '',
|
name: '',
|
||||||
|
key: '',
|
||||||
category: undefined,
|
category: undefined,
|
||||||
icon: '',
|
icon: undefined,
|
||||||
description: '',
|
description: '',
|
||||||
|
type: BpmModelType.BPMN,
|
||||||
|
formType: BpmModelFormType.NORMAL,
|
||||||
formId: '',
|
formId: '',
|
||||||
formCustomCreatePath: '',
|
formCustomCreatePath: '',
|
||||||
formCustomViewPath: ''
|
formCustomViewPath: '',
|
||||||
|
visible: true,
|
||||||
|
startUserIds: [],
|
||||||
|
managerUserIds: []
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
<template>
|
|
||||||
<Dialog v-model="dialogVisible" title="导入流程" width="400">
|
|
||||||
<div>
|
|
||||||
<el-upload
|
|
||||||
ref="uploadRef"
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
:action="importUrl"
|
|
||||||
:auto-upload="false"
|
|
||||||
:data="formData"
|
|
||||||
:disabled="formLoading"
|
|
||||||
:headers="uploadHeaders"
|
|
||||||
:limit="1"
|
|
||||||
:on-error="submitFormError"
|
|
||||||
:on-exceed="handleExceed"
|
|
||||||
:on-success="submitFormSuccess"
|
|
||||||
accept=".bpmn, .xml"
|
|
||||||
drag
|
|
||||||
name="bpmnFile"
|
|
||||||
>
|
|
||||||
<Icon class="el-icon--upload" icon="ep:upload-filled" />
|
|
||||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
|
||||||
<template #tip>
|
|
||||||
<div class="el-upload__tip" style="color: red">
|
|
||||||
提示:仅允许导入“bpm”或“xml”格式文件!
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
|
|
||||||
<el-form-item label="流程标识" prop="key">
|
|
||||||
<el-input
|
|
||||||
v-model="formData.key"
|
|
||||||
placeholder="请输入流标标识"
|
|
||||||
style="width: 250px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="流程名称" prop="name">
|
|
||||||
<el-input v-model="formData.name" clearable placeholder="请输入流程名称" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="流程描述" prop="description">
|
|
||||||
<el-input v-model="formData.description" clearable type="textarea" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
|
||||||
|
|
||||||
defineOptions({ name: 'ModelImportForm' })
|
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
|
||||||
const formLoading = ref(false) // 表单的加载中
|
|
||||||
const formData = ref({
|
|
||||||
key: '',
|
|
||||||
name: '',
|
|
||||||
description: ''
|
|
||||||
})
|
|
||||||
const formRules = reactive({
|
|
||||||
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
|
||||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }]
|
|
||||||
})
|
|
||||||
const formRef = ref() // 表单 Ref
|
|
||||||
const uploadRef = ref() // 上传 Ref
|
|
||||||
const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
|
|
||||||
const uploadHeaders = ref() // 上传 Header 头
|
|
||||||
const fileList = ref([]) // 文件列表
|
|
||||||
|
|
||||||
/** 打开弹窗 */
|
|
||||||
const open = async () => {
|
|
||||||
dialogVisible.value = true
|
|
||||||
resetForm()
|
|
||||||
}
|
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
|
||||||
|
|
||||||
/** 提交表单 */
|
|
||||||
const submitForm = async () => {
|
|
||||||
// 校验表单
|
|
||||||
if (!formRef) return
|
|
||||||
const valid = await formRef.value.validate()
|
|
||||||
if (!valid) return
|
|
||||||
if (fileList.value.length == 0) {
|
|
||||||
message.error('请上传文件')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 提交请求
|
|
||||||
uploadHeaders.value = {
|
|
||||||
Authorization: 'Bearer ' + getAccessToken(),
|
|
||||||
'tenant-id': getTenantId()
|
|
||||||
}
|
|
||||||
formLoading.value = true
|
|
||||||
uploadRef.value!.submit()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 文件上传成功 */
|
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
||||||
const submitFormSuccess = async (response: any) => {
|
|
||||||
if (response.code !== 0) {
|
|
||||||
message.error(response.msg)
|
|
||||||
formLoading.value = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 提示成功
|
|
||||||
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
|
|
||||||
dialogVisible.value = false
|
|
||||||
// 发送操作成功的事件
|
|
||||||
emit('success')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 上传错误提示 */
|
|
||||||
const submitFormError = (): void => {
|
|
||||||
message.error('导入流程失败,请您重新上传!')
|
|
||||||
formLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置表单 */
|
|
||||||
const resetForm = () => {
|
|
||||||
// 重置上传状态和文件
|
|
||||||
formLoading.value = false
|
|
||||||
uploadRef.value?.clearFiles()
|
|
||||||
// 重置表单
|
|
||||||
formData.value = {
|
|
||||||
key: '',
|
|
||||||
name: '',
|
|
||||||
description: ''
|
|
||||||
}
|
|
||||||
formRef.value?.resetFields()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 文件数超出提示 */
|
|
||||||
const handleExceed = (): void => {
|
|
||||||
message.error('最多只能上传一个文件!')
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -58,17 +58,17 @@ const initModeler = (item) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改模型 */
|
/** 添加/修改模型 */
|
||||||
const save = async (bpmnXml) => {
|
const save = async (bpmnXml: string) => {
|
||||||
const data = {
|
const data = {
|
||||||
...model.value,
|
...model.value,
|
||||||
bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
|
bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
|
||||||
} as unknown as ModelApi.ModelVO
|
} as unknown as ModelApi.ModelVO
|
||||||
// 提交
|
// 提交
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
await ModelApi.updateModel(data)
|
await ModelApi.updateModelBpmn(data)
|
||||||
message.success('修改成功')
|
message.success('修改成功')
|
||||||
} else {
|
} else {
|
||||||
await ModelApi.createModel(data)
|
await ModelApi.updateModelBpmn(data)
|
||||||
message.success('新增成功')
|
message.success('新增成功')
|
||||||
}
|
}
|
||||||
// 跳转回去
|
// 跳转回去
|
||||||
|
|
|
@ -58,10 +58,7 @@
|
||||||
@click="openForm('create')"
|
@click="openForm('create')"
|
||||||
v-hasPermi="['bpm:model:create']"
|
v-hasPermi="['bpm:model:create']"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新建流程
|
<Icon icon="ep:plus" class="mr-5px" /> 新建
|
||||||
</el-button>
|
|
||||||
<el-button type="success" plain @click="openImportForm" v-hasPermi="['bpm:model:import']">
|
|
||||||
<Icon icon="ep:upload" class="mr-5px" /> 导入流程
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
@ -70,21 +67,34 @@
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list">
|
<el-table v-loading="loading" :data="list">
|
||||||
<el-table-column label="流程标识" align="center" prop="key" width="200" />
|
<el-table-column label="流程名称" align="center" prop="name" min-width="200" />
|
||||||
<el-table-column label="流程名称" align="center" prop="name" width="200">
|
<el-table-column label="流程图标" align="center" prop="icon" min-width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" link @click="handleBpmnDetail(scope.row)">
|
<el-image :src="scope.row.icon" class="h-32px w-32px" />
|
||||||
<span>{{ scope.row.name }}</span>
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="流程图标" align="center" prop="icon" width="100">
|
<el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-image :src="scope.row.icon" class="w-32px h-32px" />
|
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
|
||||||
|
全部可见
|
||||||
|
</el-text>
|
||||||
|
<el-text v-else-if="scope.row.startUsers.length == 1">
|
||||||
|
{{ scope.row.startUsers[0].nickname }}
|
||||||
|
</el-text>
|
||||||
|
<el-text v-else>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
placement="top"
|
||||||
|
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
|
||||||
|
>
|
||||||
|
{{ scope.row.startUsers[0].nickname }}等 {{ scope.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="流程分类" align="center" prop="categoryName" min-width="100" />
|
||||||
<el-table-column label="表单信息" align="center" prop="formType" width="200">
|
<el-table-column label="表单信息" align="center" prop="formType" min-width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="scope.row.formType === 10"
|
v-if="scope.row.formType === 10"
|
||||||
|
@ -105,101 +115,87 @@
|
||||||
<label v-else>暂无表单</label>
|
<label v-else>暂无表单</label>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="最后发布" align="center" prop="deploymentTime" min-width="250">
|
||||||
label="创建时间"
|
<template #default="scope">
|
||||||
align="center"
|
<span v-if="scope.row.processDefinition">
|
||||||
prop="createTime"
|
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
||||||
width="180"
|
</span>
|
||||||
:formatter="dateFormatter"
|
<el-tag v-if="scope.row.processDefinition" class="ml-10px">
|
||||||
/>
|
v{{ scope.row.processDefinition.version }}
|
||||||
<el-table-column label="最新部署的流程定义" align="center">
|
</el-tag>
|
||||||
<el-table-column
|
<el-tag v-else type="warning">未部署</el-tag>
|
||||||
label="流程版本"
|
<el-tag
|
||||||
align="center"
|
v-if="scope.row.processDefinition?.suspensionState === 2"
|
||||||
prop="processDefinition.version"
|
type="warning"
|
||||||
width="100"
|
class="ml-10px"
|
||||||
>
|
>
|
||||||
<template #default="scope">
|
已停用
|
||||||
<el-tag v-if="scope.row.processDefinition">
|
</el-tag>
|
||||||
v{{ scope.row.processDefinition.version }}
|
</template>
|
||||||
</el-tag>
|
|
||||||
<el-tag v-else type="warning">未部署</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="激活状态"
|
|
||||||
align="center"
|
|
||||||
prop="processDefinition.version"
|
|
||||||
width="85"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<el-switch
|
|
||||||
v-if="scope.row.processDefinition"
|
|
||||||
v-model="scope.row.processDefinition.suspensionState"
|
|
||||||
:active-value="1"
|
|
||||||
:inactive-value="2"
|
|
||||||
@change="handleChangeState(scope.row)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="部署时间" align="center" prop="deploymentTime" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<span v-if="scope.row.processDefinition">
|
|
||||||
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" min-width="240" fixed="right">
|
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openForm('update', scope.row.id)"
|
@click="openForm('update', scope.row.id)"
|
||||||
v-hasPermi="['bpm:model:update']"
|
v-hasPermi="['bpm:model:update']"
|
||||||
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
修改流程
|
修改
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
|
class="!ml-5px"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleDesign(scope.row)"
|
@click="handleDesign(scope.row)"
|
||||||
v-hasPermi="['bpm:model:update']"
|
v-hasPermi="['bpm:model:update']"
|
||||||
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
设计流程
|
设计
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="handleSimpleDesign(scope.row.id)"
|
|
||||||
v-hasPermi="['bpm:model:update']"
|
|
||||||
>
|
|
||||||
仿钉钉设计流程
|
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
|
class="!ml-5px"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleDeploy(scope.row)"
|
@click="handleDeploy(scope.row)"
|
||||||
v-hasPermi="['bpm:model:deploy']"
|
v-hasPermi="['bpm:model:deploy']"
|
||||||
|
:disabled="!isManagerUser(scope.row)"
|
||||||
>
|
>
|
||||||
发布流程
|
发布
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-dropdown
|
||||||
link
|
class="!align-middle ml-5px"
|
||||||
type="primary"
|
@command="(command) => handleCommand(command, scope.row)"
|
||||||
v-hasPermi="['bpm:process-definition:query']"
|
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
|
||||||
@click="handleDefinitionList(scope.row)"
|
|
||||||
>
|
>
|
||||||
流程定义
|
<el-button type="primary" link>更多</el-button>
|
||||||
</el-button>
|
<template #dropdown>
|
||||||
<el-button
|
<el-dropdown-menu>
|
||||||
link
|
<el-dropdown-item
|
||||||
type="danger"
|
command="handleDefinitionList"
|
||||||
@click="handleDelete(scope.row.id)"
|
v-if="checkPermi(['bpm:process-definition:query'])"
|
||||||
v-hasPermi="['bpm:model:delete']"
|
>
|
||||||
>
|
历史
|
||||||
删除
|
</el-dropdown-item>
|
||||||
</el-button>
|
<el-dropdown-item
|
||||||
|
command="handleChangeState"
|
||||||
|
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
|
||||||
|
:disabled="!isManagerUser(scope.row)"
|
||||||
|
>
|
||||||
|
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
type="danger"
|
||||||
|
command="handleDelete"
|
||||||
|
v-if="checkPermi(['bpm:model:delete'])"
|
||||||
|
:disabled="!isManagerUser(scope.row)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
@ -215,41 +211,29 @@
|
||||||
<!-- 表单弹窗:添加/修改流程 -->
|
<!-- 表单弹窗:添加/修改流程 -->
|
||||||
<ModelForm ref="formRef" @success="getList" />
|
<ModelForm ref="formRef" @success="getList" />
|
||||||
|
|
||||||
<!-- 表单弹窗:导入流程 -->
|
|
||||||
<ModelImportForm ref="importFormRef" @success="getList" />
|
|
||||||
|
|
||||||
<!-- 弹窗:表单详情 -->
|
<!-- 弹窗:表单详情 -->
|
||||||
<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
|
|
||||||
key="designer"
|
|
||||||
v-model="bpmnXML"
|
|
||||||
:value="bpmnXML as any"
|
|
||||||
v-bind="bpmnControlForm"
|
|
||||||
:prefix="bpmnControlForm.prefix"
|
|
||||||
/>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
|
||||||
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 ModelForm from './ModelForm.vue'
|
import ModelForm from './ModelForm.vue'
|
||||||
import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue'
|
|
||||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||||
import { CategoryApi } from '@/api/bpm/category'
|
import { CategoryApi } from '@/api/bpm/category'
|
||||||
|
import { BpmModelType } from '@/utils/constants'
|
||||||
|
import { checkPermi } from '@/utils/permission'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmModel' })
|
defineOptions({ name: 'BpmModel' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const { push } = useRouter() // 路由
|
const { push } = useRouter() // 路由
|
||||||
|
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
@ -288,25 +272,36 @@ const resetQuery = () => {
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** '更多'操作按钮 */
|
||||||
|
const handleCommand = (command: string, row: any) => {
|
||||||
|
switch (command) {
|
||||||
|
case 'handleDefinitionList':
|
||||||
|
handleDefinitionList(row)
|
||||||
|
break
|
||||||
|
case 'handleDelete':
|
||||||
|
handleDelete(row)
|
||||||
|
break
|
||||||
|
case 'handleChangeState':
|
||||||
|
handleChangeState(row)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
|
||||||
const importFormRef = ref()
|
|
||||||
const openImportForm = () => {
|
|
||||||
importFormRef.value.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (row: any) => {
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
// 发起删除
|
// 发起删除
|
||||||
await ModelApi.deleteModel(id)
|
await ModelApi.deleteModel(row.id)
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await getList()
|
await getList()
|
||||||
|
@ -314,45 +309,45 @@ const handleDelete = async (id: number) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新状态操作 */
|
/** 更新状态操作 */
|
||||||
const handleChangeState = async (row) => {
|
const handleChangeState = async (row: any) => {
|
||||||
const state = row.processDefinition.suspensionState
|
const state = row.processDefinition.suspensionState
|
||||||
|
const newState = state === 1 ? 2 : 1
|
||||||
try {
|
try {
|
||||||
// 修改状态的二次确认
|
// 修改状态的二次确认
|
||||||
const id = row.id
|
const id = row.id
|
||||||
const statusState = state === 1 ? '激活' : '挂起'
|
debugger
|
||||||
|
const statusState = state === 1 ? '停用' : '启用'
|
||||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||||
await message.confirm(content)
|
await message.confirm(content)
|
||||||
// 发起修改状态
|
// 发起修改状态
|
||||||
await ModelApi.updateModelState(id, state)
|
await ModelApi.updateModelState(id, newState)
|
||||||
|
message.success(statusState + '成功')
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await getList()
|
await getList()
|
||||||
} catch {
|
} catch {}
|
||||||
// 取消后,进行恢复按钮
|
|
||||||
row.processDefinition.suspensionState = state === 1 ? 2 : 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设计流程 */
|
/** 设计流程 */
|
||||||
const handleDesign = (row) => {
|
const handleDesign = (row: any) => {
|
||||||
push({
|
if (row.type == BpmModelType.BPMN) {
|
||||||
name: 'BpmModelEditor',
|
push({
|
||||||
query: {
|
name: 'BpmModelEditor',
|
||||||
modelId: row.id
|
query: {
|
||||||
}
|
modelId: row.id
|
||||||
})
|
}
|
||||||
}
|
})
|
||||||
|
} else {
|
||||||
const handleSimpleDesign = (row) => {
|
push({
|
||||||
push({
|
name: 'SimpleWorkflowDesignEditor',
|
||||||
name: 'SimpleWorkflowDesignEditor',
|
query: {
|
||||||
query: {
|
modelId: row.id
|
||||||
modelId: row.id
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发布流程 */
|
/** 发布流程 */
|
||||||
const handleDeploy = async (row) => {
|
const handleDeploy = async (row: any) => {
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.confirm('是否部署该流程!!')
|
await message.confirm('是否部署该流程!!')
|
||||||
|
@ -380,7 +375,7 @@ const formDetailPreview = ref({
|
||||||
rule: [],
|
rule: [],
|
||||||
option: {}
|
option: {}
|
||||||
})
|
})
|
||||||
const handleFormDetail = async (row) => {
|
const handleFormDetail = async (row: any) => {
|
||||||
if (row.formType == 10) {
|
if (row.formType == 10) {
|
||||||
// 设置表单
|
// 设置表单
|
||||||
const data = await FormApi.getForm(row.formId)
|
const data = await FormApi.getForm(row.formId)
|
||||||
|
@ -394,16 +389,10 @@ const handleFormDetail = async (row) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 流程图的详情按钮操作 */
|
/** 判断是否可以操作 */
|
||||||
const bpmnDetailVisible = ref(false)
|
const isManagerUser = (row: any) => {
|
||||||
const bpmnXML = ref(null)
|
const userId = userStore.getUser.id
|
||||||
const bpmnControlForm = ref({
|
return row.managerUserIds && row.managerUserIds.includes(userId)
|
||||||
prefix: 'flowable'
|
|
||||||
})
|
|
||||||
const handleBpmnDetail = async (row) => {
|
|
||||||
const data = await ModelApi.getModel(row.id)
|
|
||||||
bpmnXML.value = data.bpmnXml || ''
|
|
||||||
bpmnDetailVisible.value = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="h-50px position-fixed bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
|
class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
|
||||||
|
v-if="runningTask.id"
|
||||||
>
|
>
|
||||||
<el-popover :visible="passVisible" placement="top-end" :width="500" trigger="click">
|
<!-- 【通过】按钮 -->
|
||||||
|
<el-popover
|
||||||
|
:visible="passVisible"
|
||||||
|
placement="top-end"
|
||||||
|
:width="500"
|
||||||
|
trigger="click"
|
||||||
|
v-if="isShowButton(OperationButtonType.APPROVE)"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button plain type="success" @click="openPopover('1')">
|
<el-button plain type="success" @click="openPopover('1')">
|
||||||
<Icon icon="ep:select" /> 通过
|
<Icon icon="ep:select" /> {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 审批表单 -->
|
||||||
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
||||||
<el-form
|
<el-form
|
||||||
label-position="top"
|
label-position="top"
|
||||||
|
@ -50,19 +59,28 @@
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
|
<el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
|
||||||
通过
|
{{ getButtonDisplayName(OperationButtonType.APPROVE) }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="passVisible = false"> 取消 </el-button>
|
<el-button @click="passVisible = false"> 取消 </el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
<el-popover :visible="rejectVisible" placement="top-end" :width="500" trigger="click">
|
|
||||||
|
<!-- 【拒绝】按钮 -->
|
||||||
|
<el-popover
|
||||||
|
:visible="rejectVisible"
|
||||||
|
placement="top-end"
|
||||||
|
:width="500"
|
||||||
|
trigger="click"
|
||||||
|
v-if="isShowButton(OperationButtonType.REJECT)"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button class="mr-20px" plain type="danger" @click="openPopover('2')">
|
<el-button class="mr-20px" plain type="danger" @click="openPopover('2')">
|
||||||
<Icon icon="ep:close" /> 拒绝
|
<Icon icon="ep:close" /> {{ getButtonDisplayName(OperationButtonType.REJECT) }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 审批表单 -->
|
||||||
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
|
||||||
<el-form
|
<el-form
|
||||||
label-position="top"
|
label-position="top"
|
||||||
|
@ -105,21 +123,46 @@
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
|
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
|
||||||
拒绝
|
{{ getButtonDisplayName(OperationButtonType.REJECT) }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="rejectVisible = false"> 取消 </el-button>
|
<el-button @click="rejectVisible = false"> 取消 </el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
|
||||||
|
<!-- 【抄送】按钮 -->
|
||||||
<div @click="handleSend"> <Icon :size="14" icon="svg-icon:send" /> 抄送 </div>
|
<div @click="handleSend"> <Icon :size="14" icon="svg-icon:send" /> 抄送 </div>
|
||||||
<div @click="openTaskUpdateAssigneeForm">
|
|
||||||
<Icon :size="14" icon="fa:share-square-o" /> 转交
|
<!-- 【转交】按钮 -->
|
||||||
|
<div @click="openTaskUpdateAssigneeForm" v-if="isShowButton(OperationButtonType.TRANSFER)">
|
||||||
|
<Icon :size="14" icon="fa:share-square-o" />
|
||||||
|
{{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
|
||||||
</div>
|
</div>
|
||||||
<div @click="handleDelegate"> <Icon :size="14" icon="ep:position" /> 委派 </div>
|
|
||||||
<div @click="handleSign"> <Icon :size="14" icon="ep:plus" /> 加签 </div>
|
<!-- 【委托】按钮 -->
|
||||||
<div @click="handleBack"> <Icon :size="14" icon="fa:mail-reply" /> 退回 </div>
|
<div @click="handleDelegate" v-if="isShowButton(OperationButtonType.DELEGATE)">
|
||||||
|
<Icon :size="14" icon="ep:position" />
|
||||||
|
{{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 【加签】 -->
|
||||||
|
<div @click="handleSign" v-if="isShowButton(OperationButtonType.ADD_SIGN)">
|
||||||
|
<Icon :size="14" icon="ep:plus" />
|
||||||
|
{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
|
||||||
|
</div>
|
||||||
|
<!-- TODO @jason:减签 -->
|
||||||
|
|
||||||
|
<!-- 【退回】按钮 -->
|
||||||
|
<div @click="handleBack" v-if="isShowButton(OperationButtonType.RETURN)">
|
||||||
|
<Icon :size="14" icon="fa:mail-reply" />
|
||||||
|
{{ getButtonDisplayName(OperationButtonType.RETURN) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--TODO @jason:撤回 -->
|
||||||
|
<!--TODO @jason:再次发起 -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 弹窗:转派审批人 -->
|
<!-- 弹窗:转派审批人 -->
|
||||||
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
|
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
|
||||||
<!-- 弹窗:回退节点 -->
|
<!-- 弹窗:回退节点 -->
|
||||||
|
@ -129,7 +172,6 @@
|
||||||
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||||
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
|
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
@ -140,7 +182,10 @@ import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
|
||||||
import TaskTransferForm from './dialog/TaskTransferForm.vue'
|
import TaskTransferForm from './dialog/TaskTransferForm.vue'
|
||||||
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import {
|
||||||
|
OperationButtonType,
|
||||||
|
OPERATION_BUTTON_NAME
|
||||||
|
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
defineOptions({ name: 'ProcessInstanceBtnConatiner' })
|
defineOptions({ name: 'ProcessInstanceBtnConatiner' })
|
||||||
|
|
||||||
const userId = useUserStore().getUser.id // 当前登录的编号
|
const userId = useUserStore().getUser.id // 当前登录的编号
|
||||||
|
@ -175,15 +220,17 @@ watch(
|
||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO @jaosn:具体的审批任务,要不改成后端返回。让前端弱化下
|
||||||
/**
|
/**
|
||||||
* 设置 runningTasks 中的任务
|
* 设置 runningTasks 中的任务
|
||||||
*/
|
*/
|
||||||
const loadRunningTask = (tasks) => {
|
const loadRunningTask = (tasks: any[]) => {
|
||||||
runningTask.value = {}
|
runningTask.value = {}
|
||||||
auditForm.value = {}
|
auditForm.value = {}
|
||||||
approveForm.value = {}
|
approveForm.value = {}
|
||||||
approveFormFApi.value = {}
|
approveFormFApi.value = {}
|
||||||
tasks.forEach((task) => {
|
tasks.forEach((task: any) => {
|
||||||
if (!isEmpty(task.children)) {
|
if (!isEmpty(task.children)) {
|
||||||
loadRunningTask(task.children)
|
loadRunningTask(task.children)
|
||||||
}
|
}
|
||||||
|
@ -214,7 +261,7 @@ const loadRunningTask = (tasks) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理审批通过和不通过的操作 */
|
/** 处理审批通过和不通过的操作 */
|
||||||
const handleAudit = async (pass) => {
|
const handleAudit = async (pass: any) => {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const auditFormRef = proxy.$refs['formRef']
|
const auditFormRef = proxy.$refs['formRef']
|
||||||
|
@ -254,6 +301,7 @@ const handleAudit = async (pass) => {
|
||||||
/* 抄送 TODO */
|
/* 抄送 TODO */
|
||||||
const handleSend = () => {}
|
const handleSend = () => {}
|
||||||
|
|
||||||
|
// TODO 代码优化:这里 flag 改成 approve: boolean 。因为 flag 目前就只有 1 和 2
|
||||||
const openPopover = (flag) => {
|
const openPopover = (flag) => {
|
||||||
passVisible.value = false
|
passVisible.value = false
|
||||||
rejectVisible.value = false
|
rejectVisible.value = false
|
||||||
|
@ -289,6 +337,24 @@ const getDetail = () => {
|
||||||
emit('success')
|
emit('success')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 是否显示按钮 */
|
||||||
|
const isShowButton = (btnType: OperationButtonType): boolean => {
|
||||||
|
let isShow = true
|
||||||
|
if (runningTask.value.buttonsSetting && runningTask.value.buttonsSetting[btnType]) {
|
||||||
|
isShow = runningTask.value.buttonsSetting[btnType].enable
|
||||||
|
}
|
||||||
|
return isShow
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取按钮的显示名称 */
|
||||||
|
const getButtonDisplayName = (btnType: OperationButtonType) => {
|
||||||
|
let displayName = OPERATION_BUTTON_NAME.get(btnType)
|
||||||
|
if (runningTask.value.buttonsSetting && runningTask.value.buttonsSetting[btnType]) {
|
||||||
|
displayName = runningTask.value.buttonsSetting[btnType].displayName
|
||||||
|
}
|
||||||
|
return displayName
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ loadRunningTask })
|
defineExpose({ loadRunningTask })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -299,10 +365,11 @@ defineExpose({ loadRunningTask })
|
||||||
|
|
||||||
.btn-container {
|
.btn-container {
|
||||||
> div {
|
> div {
|
||||||
|
display: flex;
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #6db5ff;
|
color: #6db5ff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,111 @@
|
||||||
|
<!-- 审批详情的右侧:审批流 -->
|
||||||
<template>
|
<template>
|
||||||
<el-timeline class="pt-20px">
|
<el-timeline class="pt-20px">
|
||||||
<el-timeline-item v-for="(activity, index) in mockData" :key="index" size="large">
|
<!-- 遍历每个审批节点 -->
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="(activity, index) in approveNodes"
|
||||||
|
:key="index"
|
||||||
|
size="large"
|
||||||
|
:icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
|
||||||
|
:color="getApprovalNodeColor(activity.status)"
|
||||||
|
>
|
||||||
<div class="flex flex-col items-start">
|
<div class="flex flex-col items-start">
|
||||||
<div class="font-bold"> {{ activity.name }}</div>
|
<div class="font-bold"> {{ activity.name }}</div>
|
||||||
<div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
|
<div class="flex items-center mt-1">
|
||||||
|
<!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
|
||||||
|
<div v-for="(task, idx) in activity.tasks" :key="idx" class="flex items-center">
|
||||||
|
<div class="flex items-center flex-col pr-2">
|
||||||
|
<div class="position-relative" v-if="task.assigneeUser || task.ownerUser">
|
||||||
|
<!-- 信息:头像 -->
|
||||||
|
<el-avatar
|
||||||
|
:size="36"
|
||||||
|
v-if="task.assigneeUser && task.assigneeUser.avatar"
|
||||||
|
:src="task.assigneeUser.avatar"
|
||||||
|
/>
|
||||||
|
<el-avatar v-else-if="task.assigneeUser && task.assigneeUser.nickname">
|
||||||
|
{{ task.assigneeUser.nickname.substring(0, 1) }}
|
||||||
|
</el-avatar>
|
||||||
|
<el-avatar
|
||||||
|
v-else-if="task.ownerUser && task.ownerUser.avatar"
|
||||||
|
:src="task.ownerUser.avatar"
|
||||||
|
/>
|
||||||
|
<el-avatar v-else-if="task.ownerUser && task.ownerUser.nickname">
|
||||||
|
{{ task.ownerUser.nickname.substring(0, 1) }}
|
||||||
|
</el-avatar>
|
||||||
|
<!-- 信息:任务 ICON -->
|
||||||
|
<div
|
||||||
|
class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:size="12"
|
||||||
|
:icon="statusIconMap2[task.status]?.icon"
|
||||||
|
:color="statusIconMap2[task.status]?.color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-1">
|
||||||
|
<!-- 信息:昵称 -->
|
||||||
|
<div
|
||||||
|
v-if="task.assigneeUser && task.assigneeUser.nickname"
|
||||||
|
class="text-10px text-align-center"
|
||||||
|
>
|
||||||
|
{{ task.assigneeUser.nickname }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="task.ownerUser && task.ownerUser.nickname"
|
||||||
|
class="text-10px text-align-center"
|
||||||
|
>
|
||||||
|
{{ task.ownerUser.nickname }}
|
||||||
|
</div>
|
||||||
|
<!-- TODO @jason:审批意见,要展示哈。 -->
|
||||||
|
<!-- <div v-if="task.reason" :title="task.reason" class="text-13px text-truncate w-150px mt-1"> 审批意见: {{ task.reason }}</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
|
||||||
|
<div
|
||||||
|
v-for="(user, idx1) in activity.candidateUserList"
|
||||||
|
:key="idx1"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<div class="flex items-center flex-col pr-2">
|
||||||
|
<div class="position-relative">
|
||||||
|
<!-- 信息:头像 -->
|
||||||
|
<el-avatar :size="36" v-if="user.avatar" :src="user.avatar" />
|
||||||
|
<el-avatar v-else-if="user.nickname && user.nickname">
|
||||||
|
{{ user.nickname.substring(0, 1) }}
|
||||||
|
</el-avatar>
|
||||||
|
<!-- 信息:任务 ICON -->
|
||||||
|
<div
|
||||||
|
class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:size="12"
|
||||||
|
:icon="statusIconMap2['-1']?.icon"
|
||||||
|
:color="statusIconMap2['-1']?.color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-1">
|
||||||
|
<!-- 信息:昵称 -->
|
||||||
|
<div v-if="user.nickname" class="text-10px text-align-center">
|
||||||
|
{{ user.nickname }}
|
||||||
|
</div>
|
||||||
|
<!-- <div v-if="task.reason" :title="task.reason" class="text-13px text-truncate w-150px mt-1"> 审批意见: {{ task.reason }}</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 信息:时间 -->
|
||||||
|
<div
|
||||||
|
v-if="activity.status !== TaskStatusEnum.NOT_START"
|
||||||
|
class="text-#a5a5a5 text-13px mt-1"
|
||||||
|
>
|
||||||
|
{{ getApprovalNodeTime(activity) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TODO @jason:审批意见,要展示哈。 -->
|
||||||
|
<!-- <div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
|
||||||
<div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
|
<div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
|
||||||
<div class="mb-5px">审批意见:</div>
|
<div class="mb-5px">审批意见:</div>
|
||||||
<div
|
<div
|
||||||
|
@ -14,148 +116,118 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activity.createTime" class="text-#a5a5a5 text-13px">
|
<div v-if="activity.createTime" class="text-#a5a5a5 text-13px">
|
||||||
{{ formatDate(activity.createTime) }}
|
{{ formatDate(activity.createTime) }}
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<!-- 该节点用户的头像 -->
|
|
||||||
<template #dot>
|
|
||||||
<div class="w-35px h-35px position-relative">
|
|
||||||
<img
|
|
||||||
src="@/assets/imgs/avatar.jpg"
|
|
||||||
class="rounded-full w-full h-full position-absolute bottom-6px right-12px"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="position-absolute top-16px left-8px bg-#fff rounded-full flex items-center content-center p-2px"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
:size="12"
|
|
||||||
:icon="optIconMap[activity.status]?.icon"
|
|
||||||
:color="optIconMap[activity.status]?.color"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-timeline-item>
|
</el-timeline-item>
|
||||||
</el-timeline>
|
</el-timeline>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||||
|
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||||
|
import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
|
import { Check, Close, Loading, Clock, Minus, Delete } from '@element-plus/icons-vue'
|
||||||
defineOptions({ name: 'BpmProcessInstanceTimeline' })
|
defineOptions({ name: 'BpmProcessInstanceTimeline' })
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
tasks: propTypes.array // 流程任务的数组
|
// 流程实例编号
|
||||||
|
processInstanceId: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 流程定义编号
|
||||||
|
processDefinitionId: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const optIconMap = {
|
// 审批节点
|
||||||
|
const approveNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
|
||||||
|
|
||||||
|
const statusIconMap2 = {
|
||||||
|
// 未开始
|
||||||
|
'-1': { color: '#e5e7ec', icon: 'ep-clock' },
|
||||||
|
// 待审批
|
||||||
|
'0': { color: '#e5e7ec', icon: 'ep:loading' },
|
||||||
// 审批中
|
// 审批中
|
||||||
'1': {
|
'1': { color: '#448ef7', icon: 'ep:loading' },
|
||||||
color: '#00b32a',
|
|
||||||
icon: 'fa-solid:clock'
|
|
||||||
},
|
|
||||||
// 审批通过
|
// 审批通过
|
||||||
'2': { color: '#00b32a', icon: 'fa-solid:check-circle' },
|
'2': { color: '#00b32a', icon: 'ep:circle-check-filled' },
|
||||||
// 审批不通过
|
// 审批不通过
|
||||||
'3': { color: '#f46b6c', icon: 'fa-solid:times-circle' }
|
'3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
|
||||||
|
// 取消
|
||||||
|
'4': { color: '#cccccc', icon: 'ep:delete-filled' },
|
||||||
|
// 回退
|
||||||
|
'5': { color: '#f46b6c', icon: 'ep:remove-filled' },
|
||||||
|
// 委派中
|
||||||
|
'6': { color: '#448ef7', icon: 'ep:loading' },
|
||||||
|
// 审批通过中
|
||||||
|
'7': { color: '#00b32a', icon: 'ep:circle-check-filled' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockData: any = [
|
const statusIconMap = {
|
||||||
{
|
// 审批未开始
|
||||||
id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
|
'-1': { color: '#e5e7ec', icon: Clock },
|
||||||
name: '发起人',
|
'0': { color: '#e5e7ec', icon: Clock },
|
||||||
createTime: 1725237646192,
|
// 审批中
|
||||||
endTime: null,
|
'1': { color: '#448ef7', icon: Loading },
|
||||||
durationInMillis: null,
|
// 审批通过
|
||||||
status: 1,
|
'2': { color: '#00b32a', icon: Check },
|
||||||
reason: null,
|
// 审批不通过
|
||||||
ownerUser: null,
|
'3': { color: '#f46b6c', icon: Close },
|
||||||
assigneeUser: {
|
// 已取消
|
||||||
id: 104,
|
'4': { color: '#cccccc', icon: Delete },
|
||||||
nickname: '测试号',
|
// 回退
|
||||||
deptId: 107,
|
'5': { color: '#f46b6c', icon: Minus },
|
||||||
deptName: '运维部门'
|
// 委派中
|
||||||
},
|
'6': { color: '#448ef7', icon: Loading },
|
||||||
taskDefinitionKey: 'task-01',
|
// 审批通过中
|
||||||
processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
'7': { color: '#00b32a', icon: Check }
|
||||||
processInstance: {
|
}
|
||||||
id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
|
||||||
name: 'oa_leave',
|
/** 获得审批详情 */
|
||||||
createTime: null,
|
const getApprovalDetail = async () => {
|
||||||
processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
|
const data = await ProcessInstanceApi.getApprovalDetail(
|
||||||
startUser: null
|
props.processInstanceId,
|
||||||
},
|
props.processDefinitionId
|
||||||
parentTaskId: null,
|
)
|
||||||
children: null,
|
approveNodes.value = data.approveNodes
|
||||||
formId: null,
|
}
|
||||||
formName: null,
|
|
||||||
formConf: null,
|
const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
|
||||||
formFields: null,
|
if (taskStatus == TaskStatusEnum.NOT_START) {
|
||||||
formVariables: null
|
return statusIconMap[taskStatus]?.icon
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
|
|
||||||
name: '领导审批',
|
|
||||||
createTime: 1725237646192,
|
|
||||||
endTime: null,
|
|
||||||
durationInMillis: null,
|
|
||||||
status: 2,
|
|
||||||
reason: null,
|
|
||||||
ownerUser: null,
|
|
||||||
assigneeUser: {
|
|
||||||
id: 104,
|
|
||||||
nickname: '领导',
|
|
||||||
deptId: 107,
|
|
||||||
deptName: '运维部门'
|
|
||||||
},
|
|
||||||
taskDefinitionKey: 'task-01',
|
|
||||||
processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
|
||||||
processInstance: {
|
|
||||||
id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
|
||||||
name: 'oa_leave',
|
|
||||||
createTime: null,
|
|
||||||
processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
|
|
||||||
startUser: null
|
|
||||||
},
|
|
||||||
parentTaskId: null,
|
|
||||||
children: null,
|
|
||||||
formId: null,
|
|
||||||
formName: null,
|
|
||||||
formConf: null,
|
|
||||||
formFields: null,
|
|
||||||
formVariables: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
|
|
||||||
name: '财务总监审核',
|
|
||||||
createTime: 1725237646192,
|
|
||||||
endTime: null,
|
|
||||||
durationInMillis: null,
|
|
||||||
status: 3,
|
|
||||||
reason: null,
|
|
||||||
ownerUser: null,
|
|
||||||
assigneeUser: {
|
|
||||||
id: 104,
|
|
||||||
nickname: '财务总监',
|
|
||||||
deptId: 107,
|
|
||||||
deptName: '运维部门'
|
|
||||||
},
|
|
||||||
taskDefinitionKey: 'task-01',
|
|
||||||
processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
|
||||||
processInstance: {
|
|
||||||
id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
|
|
||||||
name: 'oa_leave',
|
|
||||||
createTime: null,
|
|
||||||
processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
|
|
||||||
startUser: null
|
|
||||||
},
|
|
||||||
parentTaskId: null,
|
|
||||||
children: null,
|
|
||||||
formId: null,
|
|
||||||
formName: null,
|
|
||||||
formConf: null,
|
|
||||||
formFields: null,
|
|
||||||
formVariables: null
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
if (nodeType === NodeType.START_USER_NODE || nodeType === NodeType.USER_TASK_NODE) {
|
||||||
|
return statusIconMap[taskStatus]?.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getApprovalNodeColor = (taskStatus: number) => {
|
||||||
|
return statusIconMap[taskStatus]?.color
|
||||||
|
}
|
||||||
|
|
||||||
|
const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
|
||||||
|
if (node.endTime) {
|
||||||
|
return `结束时间:${formatDate(node.endTime)}`
|
||||||
|
}
|
||||||
|
if (node.startTime) {
|
||||||
|
return `创建时间:${formatDate(node.startTime)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重新刷新审批详情 */
|
||||||
|
const refresh = () => {
|
||||||
|
getApprovalDetail()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ refresh })
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getApprovalDetail()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -56,29 +56,73 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
|
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
|
||||||
<el-button type="success" @click="handleAudit(item, true)">
|
<!-- TODO @jason:建议搞个 if 来判断,替代现有的 !item.buttonsSetting || item.buttonsSetting[OpsButtonType.APPROVE]?.enable -->
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.APPROVE]?.enable"
|
||||||
|
@click="handleAudit(item, true)"
|
||||||
|
>
|
||||||
<Icon icon="ep:select" />
|
<Icon icon="ep:select" />
|
||||||
通过
|
<!-- TODO @jason:这个也是类似哈,搞个方法来生成名字 -->
|
||||||
|
{{
|
||||||
|
item.buttonsSetting?.[OperationButtonType.APPROVE]?.displayName ||
|
||||||
|
OPERATION_BUTTON_NAME.get(OperationButtonType.APPROVE)
|
||||||
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" @click="handleAudit(item, false)">
|
<el-button
|
||||||
|
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.REJECT]?.enable"
|
||||||
|
type="danger"
|
||||||
|
@click="handleAudit(item, false)"
|
||||||
|
>
|
||||||
<Icon icon="ep:close" />
|
<Icon icon="ep:close" />
|
||||||
不通过
|
{{
|
||||||
|
item.buttonsSetting?.[OperationButtonType.REJECT].displayName ||
|
||||||
|
OPERATION_BUTTON_NAME.get(OperationButtonType.REJECT)
|
||||||
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)">
|
<el-button
|
||||||
|
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.TRANSFER]?.enable"
|
||||||
|
type="primary"
|
||||||
|
@click="openTaskUpdateAssigneeForm(item.id)"
|
||||||
|
>
|
||||||
<Icon icon="ep:edit" />
|
<Icon icon="ep:edit" />
|
||||||
转办
|
{{
|
||||||
|
item.buttonsSetting?.[OperationButtonType.TRANSFER]?.displayName ||
|
||||||
|
OPERATION_BUTTON_NAME.get(OperationButtonType.TRANSFER)
|
||||||
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" @click="handleDelegate(item)">
|
<el-button
|
||||||
|
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.DELEGATE]?.enable"
|
||||||
|
type="primary"
|
||||||
|
@click="handleDelegate(item)"
|
||||||
|
>
|
||||||
<Icon icon="ep:position" />
|
<Icon icon="ep:position" />
|
||||||
委派
|
{{
|
||||||
|
item.buttonsSetting?.[OperationButtonType.DELEGATE]?.displayName ||
|
||||||
|
OPERATION_BUTTON_NAME.get(OperationButtonType.DELEGATE)
|
||||||
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" @click="handleSign(item)">
|
<el-button
|
||||||
|
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.ADD_SIGN]?.enable"
|
||||||
|
type="primary"
|
||||||
|
@click="handleSign(item)"
|
||||||
|
>
|
||||||
<Icon icon="ep:plus" />
|
<Icon icon="ep:plus" />
|
||||||
加签
|
{{
|
||||||
|
item.buttonsSetting?.[OperationButtonType.ADD_SIGN]?.displayName ||
|
||||||
|
OPERATION_BUTTON_NAME.get(OperationButtonType.ADD_SIGN)
|
||||||
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="warning" @click="handleBack(item)">
|
<el-button
|
||||||
|
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.RETURN]?.enable"
|
||||||
|
type="warning"
|
||||||
|
@click="handleBack(item)"
|
||||||
|
>
|
||||||
<Icon icon="ep:back" />
|
<Icon icon="ep:back" />
|
||||||
回退
|
{{
|
||||||
|
item.buttonsSetting?.[OperationButtonType.RETURN]?.displayName ||
|
||||||
|
OPERATION_BUTTON_NAME.get(OperationButtonType.RETURN)
|
||||||
|
}}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -147,6 +191,10 @@ import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
||||||
import { registerComponent } from '@/utils/routerHelper'
|
import { registerComponent } from '@/utils/routerHelper'
|
||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import {
|
||||||
|
OperationButtonType,
|
||||||
|
OPERATION_BUTTON_NAME
|
||||||
|
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||||
|
|
||||||
|
@ -200,7 +248,11 @@ const handleAudit = async (task, pass) => {
|
||||||
// 1.2 校验表单
|
// 1.2 校验表单
|
||||||
const elForm = unref(auditFormRef)
|
const elForm = unref(auditFormRef)
|
||||||
if (!elForm) return
|
if (!elForm) return
|
||||||
const valid = await elForm.validate()
|
let valid = await elForm.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 校验申请表单
|
||||||
|
if (!fApi.value) return
|
||||||
|
valid = await fApi.value.validate()
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
|
|
||||||
// 2.1 提交审批
|
// 2.1 提交审批
|
||||||
|
@ -216,6 +268,9 @@ const handleAudit = async (task, pass) => {
|
||||||
await formCreateApi.validate()
|
await formCreateApi.validate()
|
||||||
data.variables = approveForms.value[index].value
|
data.variables = approveForms.value[index].value
|
||||||
}
|
}
|
||||||
|
// 获取表单可编辑字段的值
|
||||||
|
data.variables = getWritableValueOfForm(task.fieldsPermission)
|
||||||
|
|
||||||
await TaskApi.approveTask(data)
|
await TaskApi.approveTask(data)
|
||||||
message.success('审批通过成功')
|
message.success('审批通过成功')
|
||||||
} else {
|
} else {
|
||||||
|
@ -251,11 +306,11 @@ const handleSign = async (task: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获得详情 */
|
/** 获得详情 */
|
||||||
const getDetail = () => {
|
const getDetail = async () => {
|
||||||
// 1. 获得流程实例相关
|
// 1. 获得流程任务列表(审批记录)。 需要先获取任务,表单的权限设置需要根据任务来设置
|
||||||
|
await getTaskList()
|
||||||
|
// 2. 获得流程实例相关
|
||||||
getProcessInstance()
|
getProcessInstance()
|
||||||
// 2. 获得流程任务列表(审批记录)
|
|
||||||
getTaskList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载流程实例 */
|
/** 加载流程实例 */
|
||||||
|
@ -273,16 +328,29 @@ const getProcessInstance = async () => {
|
||||||
// 设置表单信息
|
// 设置表单信息
|
||||||
const processDefinition = data.processDefinition
|
const processDefinition = data.processDefinition
|
||||||
if (processDefinition.formType === 10) {
|
if (processDefinition.formType === 10) {
|
||||||
setConfAndFields2(
|
if (detailForm.value.rule.length > 0) {
|
||||||
detailForm,
|
detailForm.value.value = data.formVariables
|
||||||
processDefinition.formConf,
|
} else {
|
||||||
processDefinition.formFields,
|
setConfAndFields2(
|
||||||
data.formVariables
|
detailForm,
|
||||||
)
|
processDefinition.formConf,
|
||||||
|
processDefinition.formFields,
|
||||||
|
data.formVariables
|
||||||
|
)
|
||||||
|
}
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
fApi.value?.btn.show(false)
|
fApi.value?.btn.show(false)
|
||||||
fApi.value?.resetBtn.show(false)
|
fApi.value?.resetBtn.show(false)
|
||||||
fApi.value?.disabled(true)
|
fApi.value?.disabled(true)
|
||||||
|
// 设置表单权限。后续需要改造成。只处理一个运行中的任务
|
||||||
|
if (runningTasks.value.length > 0) {
|
||||||
|
const task = runningTasks.value.at(0)
|
||||||
|
if (task.fieldsPermission) {
|
||||||
|
Object.keys(task.fieldsPermission).forEach((item) => {
|
||||||
|
setFieldPermission(item, task.fieldsPermission[item])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||||
|
@ -353,6 +421,7 @@ const loadRunningTask = (tasks) => {
|
||||||
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.3 添加到处理任务
|
// 2.3 添加到处理任务
|
||||||
runningTasks.value.push({ ...task })
|
runningTasks.value.push({ ...task })
|
||||||
auditForms.value.push({
|
auditForms.value.push({
|
||||||
|
@ -371,6 +440,35 @@ const loadRunningTask = (tasks) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置表单权限
|
||||||
|
*/
|
||||||
|
const setFieldPermission = (field: string, permission: string) => {
|
||||||
|
if (permission === '1') {
|
||||||
|
fApi.value?.disabled(true, field)
|
||||||
|
}
|
||||||
|
if (permission === '2') {
|
||||||
|
fApi.value?.disabled(false, field)
|
||||||
|
}
|
||||||
|
if (permission === '3') {
|
||||||
|
fApi.value?.hidden(true, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取可以编辑字段的值
|
||||||
|
*/
|
||||||
|
const getWritableValueOfForm = (fieldsPermission: Object) => {
|
||||||
|
const fieldsValue = {}
|
||||||
|
if (fieldsPermission && fApi.value) {
|
||||||
|
Object.keys(fieldsPermission).forEach((item) => {
|
||||||
|
if (fieldsPermission[item] === '2') {
|
||||||
|
fieldsValue[item] = fApi.value.getValue(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return fieldsValue
|
||||||
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
|
@ -1,88 +1,106 @@
|
||||||
<template>
|
<template>
|
||||||
<ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
|
<ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
|
||||||
<img
|
<div class="processInstance-wrap-main">
|
||||||
class="position-absolute right-20px"
|
<el-scrollbar>
|
||||||
width="150"
|
<img
|
||||||
:src="auditIcons[processInstance.status]"
|
class="position-absolute right-20px"
|
||||||
alt=""
|
width="150"
|
||||||
/>
|
:src="auditIcons[processInstance.status]"
|
||||||
<div class="text-#878c93">编号:{{ id }}</div>
|
alt=""
|
||||||
<el-divider class="!my-8px" />
|
/>
|
||||||
<div class="flex items-center gap-5 mb-10px">
|
<div class="text-#878c93 h-15px">编号:{{ id }}</div>
|
||||||
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
<el-divider class="!my-8px" />
|
||||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
|
<div class="flex items-center gap-5 mb-10px h-40px">
|
||||||
</div>
|
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
||||||
|
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-5 mb-10px text-13px">
|
<div class="flex items-center gap-5 mb-10px text-13px h-35px">
|
||||||
<div class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600">
|
<div
|
||||||
<img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
|
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
|
||||||
{{ processInstance?.startUser?.nickname }}
|
>
|
||||||
</div>
|
<img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
|
||||||
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
|
{{ processInstance?.startUser?.nickname }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-tabs>
|
<el-tabs v-model="activeTab">
|
||||||
<!-- 表单信息 -->
|
<!-- 表单信息 -->
|
||||||
<el-tab-pane label="表单信息">
|
<el-tab-pane label="审批详情" name="form">
|
||||||
<el-row :gutter="10">
|
<div class="form-scroll-area">
|
||||||
<el-col :span="18" class="!flex !flex-col formCol">
|
<el-scrollbar>
|
||||||
<!-- 表单信息 -->
|
<el-row :gutter="10">
|
||||||
<div v-loading="processInstanceLoading" class="form-box flex flex-col mb-30px flex-1">
|
<el-col :span="18" class="!flex !flex-col formCol">
|
||||||
<!-- 情况一:流程表单 -->
|
<!-- 表单信息 -->
|
||||||
<el-col
|
<div
|
||||||
v-if="processInstance?.processDefinition?.formType === 10"
|
v-loading="processInstanceLoading"
|
||||||
:offset="6"
|
class="form-box flex flex-col mb-30px flex-1"
|
||||||
:span="16"
|
>
|
||||||
>
|
<!-- 情况一:流程表单 -->
|
||||||
<form-create
|
<el-col
|
||||||
v-model="detailForm.value"
|
v-if="processInstance?.processDefinition?.formType === 10"
|
||||||
v-model:api="fApi"
|
:offset="6"
|
||||||
:option="detailForm.option"
|
:span="16"
|
||||||
:rule="detailForm.rule"
|
>
|
||||||
/>
|
<form-create
|
||||||
</el-col>
|
v-model="detailForm.value"
|
||||||
<!-- 情况二:业务表单 -->
|
v-model:api="fApi"
|
||||||
<div v-if="processInstance?.processDefinition?.formType === 20">
|
:option="detailForm.option"
|
||||||
<BusinessFormComponent :id="processInstance.businessKey" />
|
:rule="detailForm.rule"
|
||||||
</div>
|
/>
|
||||||
|
</el-col>
|
||||||
|
<!-- 情况二:业务表单 -->
|
||||||
|
<div v-if="processInstance?.processDefinition?.formType === 20">
|
||||||
|
<BusinessFormComponent :id="processInstance.businessKey" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<!-- 审批记录时间线 -->
|
||||||
|
<ProcessInstanceTimeline ref="timelineRef" :process-instance-id="id" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
<!-- 操作栏按钮 -->
|
<!-- 流程图 -->
|
||||||
<ProcessInstanceOperationButton
|
<el-tab-pane label="流程图" name="diagram">
|
||||||
ref="operationButtonRef"
|
<ProcessInstanceBpmnViewer
|
||||||
:processInstance="processInstance"
|
:id="`${id}`"
|
||||||
:userOptions="userOptions"
|
:bpmn-xml="bpmnXml"
|
||||||
@success="getDetail"
|
:loading="processInstanceLoading"
|
||||||
|
:process-instance="processInstance"
|
||||||
|
:tasks="tasks"
|
||||||
/>
|
/>
|
||||||
</el-col>
|
</el-tab-pane>
|
||||||
<el-col :span="6">
|
<!-- 流转记录 -->
|
||||||
<!-- 审批记录时间线 -->
|
<el-tab-pane label="流转记录" name="record">
|
||||||
<ProcessInstanceTimeline :process-instance="processInstance" :tasks="tasks" />
|
<ProcessInstanceTaskList
|
||||||
</el-col>
|
:loading="tasksLoad"
|
||||||
</el-row>
|
:process-instance="processInstance"
|
||||||
</el-tab-pane>
|
:tasks="tasks"
|
||||||
<!-- 流程图 -->
|
@refresh="getTaskList"
|
||||||
<el-tab-pane label="流程图">
|
/>
|
||||||
<ProcessInstanceBpmnViewer
|
</el-tab-pane>
|
||||||
:id="`${id}`"
|
<!-- 流转评论 TODO 待开发 -->
|
||||||
:bpmn-xml="bpmnXml"
|
<el-tab-pane label="流转评论" name="comment"> 流转评论 </el-tab-pane>
|
||||||
:loading="processInstanceLoading"
|
</el-tabs>
|
||||||
:process-instance="processInstance"
|
|
||||||
:tasks="tasks"
|
<div
|
||||||
/>
|
class="b-t-solid border-t-1px border-[var(--el-border-color)]"
|
||||||
</el-tab-pane>
|
v-if="activeTab === 'form'"
|
||||||
<!-- 流转记录 -->
|
>
|
||||||
<el-tab-pane label="流转记录">
|
<!-- 操作栏按钮 -->
|
||||||
<ProcessInstanceTaskList
|
<ProcessInstanceOperationButton
|
||||||
:loading="tasksLoad"
|
ref="operationButtonRef"
|
||||||
:process-instance="processInstance"
|
:processInstance="processInstance"
|
||||||
:tasks="tasks"
|
:userOptions="userOptions"
|
||||||
@refresh="getTaskList"
|
@success="refresh"
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</div>
|
||||||
<!-- 流转评论 -->
|
</el-scrollbar>
|
||||||
<el-tab-pane label="流转评论"> 流转评论 </el-tab-pane>
|
</div>
|
||||||
</el-tabs>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -99,18 +117,22 @@ import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue
|
||||||
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
|
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
|
||||||
import { registerComponent } from '@/utils/routerHelper'
|
import { registerComponent } from '@/utils/routerHelper'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
import audit1 from '@/assets/svgs/bpm/audit1.svg'
|
import audit1 from '@/assets/svgs/bpm/audit1.svg'
|
||||||
import audit2 from '@/assets/svgs/bpm/audit2.svg'
|
import audit2 from '@/assets/svgs/bpm/audit2.svg'
|
||||||
import audit3 from '@/assets/svgs/bpm/audit3.svg'
|
import audit3 from '@/assets/svgs/bpm/audit3.svg'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||||
|
const props = defineProps<{
|
||||||
const { query } = useRoute() // 查询参数
|
id: string // 流程实例的编号
|
||||||
|
taskId?: string // 任务编号
|
||||||
|
activityId?: string //流程活动编号,用于抄送查看
|
||||||
|
}>()
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const id = query.id as unknown as string // 流程实例的编号
|
|
||||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||||
const processInstance = ref<any>({}) // 流程实例
|
const processInstance = ref<any>({}) // 流程实例
|
||||||
const operationButtonRef = ref()
|
const operationButtonRef = ref()
|
||||||
|
const timelineRef = ref()
|
||||||
const bpmnXml = ref('') // BPMN XML
|
const bpmnXml = ref('') // BPMN XML
|
||||||
const tasksLoad = ref(true) // 任务的加载中
|
const tasksLoad = ref(true) // 任务的加载中
|
||||||
const tasks = ref<any[]>([]) // 任务列表
|
const tasks = ref<any[]>([]) // 任务列表
|
||||||
|
@ -141,7 +163,7 @@ const BusinessFormComponent = ref<any>(null) // 异步组件
|
||||||
const getProcessInstance = async () => {
|
const getProcessInstance = async () => {
|
||||||
try {
|
try {
|
||||||
processInstanceLoading.value = true
|
processInstanceLoading.value = true
|
||||||
const data = await ProcessInstanceApi.getProcessInstance(id)
|
const data = await ProcessInstanceApi.getProcessInstance(props.id)
|
||||||
if (!data) {
|
if (!data) {
|
||||||
message.error('查询不到流程信息!')
|
message.error('查询不到流程信息!')
|
||||||
return
|
return
|
||||||
|
@ -151,6 +173,15 @@ const getProcessInstance = async () => {
|
||||||
// 设置表单信息
|
// 设置表单信息
|
||||||
const processDefinition = data.processDefinition
|
const processDefinition = data.processDefinition
|
||||||
if (processDefinition.formType === 10) {
|
if (processDefinition.formType === 10) {
|
||||||
|
// 获取表单字段权限
|
||||||
|
let fieldsPermission = undefined
|
||||||
|
if (props.taskId || props.activityId) {
|
||||||
|
fieldsPermission = await ProcessInstanceApi.getFormFieldsPermission({
|
||||||
|
processInstanceId: props.id,
|
||||||
|
taskId: props.taskId,
|
||||||
|
activityId: props.activityId
|
||||||
|
})
|
||||||
|
}
|
||||||
setConfAndFields2(
|
setConfAndFields2(
|
||||||
detailForm,
|
detailForm,
|
||||||
processDefinition.formConf,
|
processDefinition.formConf,
|
||||||
|
@ -161,6 +192,11 @@ const getProcessInstance = async () => {
|
||||||
fApi.value?.btn.show(false)
|
fApi.value?.btn.show(false)
|
||||||
fApi.value?.resetBtn.show(false)
|
fApi.value?.resetBtn.show(false)
|
||||||
fApi.value?.disabled(true)
|
fApi.value?.disabled(true)
|
||||||
|
if (fieldsPermission) {
|
||||||
|
Object.keys(fieldsPermission).forEach((item) => {
|
||||||
|
setFieldPermission(item, fieldsPermission[item])
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||||
|
@ -174,15 +210,30 @@ const getProcessInstance = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置表单权限
|
||||||
|
*/
|
||||||
|
const setFieldPermission = (field: string, permission: string) => {
|
||||||
|
if (permission === FieldPermissionType.READ) {
|
||||||
|
fApi.value?.disabled(true, field)
|
||||||
|
}
|
||||||
|
if (permission === FieldPermissionType.WRITE) {
|
||||||
|
fApi.value?.disabled(false, field)
|
||||||
|
}
|
||||||
|
if (permission === FieldPermissionType.NONE) {
|
||||||
|
fApi.value?.hidden(true, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 加载任务列表 */
|
/** 加载任务列表 */
|
||||||
const getTaskList = async () => {
|
const getTaskList = async () => {
|
||||||
try {
|
try {
|
||||||
// 获得未取消的任务
|
// 获得未取消的任务
|
||||||
tasksLoad.value = true
|
tasksLoad.value = true
|
||||||
const data = await TaskApi.getTaskListByProcessInstanceId(id)
|
const data = await TaskApi.getTaskListByProcessInstanceId(props.id)
|
||||||
tasks.value = []
|
tasks.value = []
|
||||||
// 1.1 移除已取消的审批
|
// 1.1 移除已取消的审批
|
||||||
data.forEach((task) => {
|
data.forEach((task: any) => {
|
||||||
if (task.status !== 4) {
|
if (task.status !== 4) {
|
||||||
tasks.value.push(task)
|
tasks.value.push(task)
|
||||||
}
|
}
|
||||||
|
@ -209,6 +260,19 @@ const getTaskList = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作成功后刷新
|
||||||
|
*/
|
||||||
|
const refresh = () => {
|
||||||
|
// 重新获取详情
|
||||||
|
getDetail()
|
||||||
|
// 刷新审批详情 Timeline
|
||||||
|
timelineRef.value?.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前的Tab */
|
||||||
|
const activeTab = ref('form')
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
@ -219,6 +283,33 @@ onMounted(async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
$wrap-padding-height: 30px;
|
||||||
|
$wrap-margin-height: 15px;
|
||||||
|
$button-height: 51px;
|
||||||
|
$process-header-height: 194px;
|
||||||
|
|
||||||
|
.processInstance-wrap-main {
|
||||||
|
height: calc(
|
||||||
|
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px
|
||||||
|
);
|
||||||
|
max-height: calc(
|
||||||
|
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px
|
||||||
|
);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.form-scroll-area {
|
||||||
|
height: calc(
|
||||||
|
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px -
|
||||||
|
$process-header-height - 40px
|
||||||
|
);
|
||||||
|
max-height: calc(
|
||||||
|
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px -
|
||||||
|
$process-header-height - 40px
|
||||||
|
);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-box {
|
.form-box {
|
||||||
:deep(.el-card) {
|
:deep(.el-card) {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
|
@ -1,28 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<SimpleProcessDesigner :model-id="modelId" />
|
||||||
<section class="dingflow-design">
|
|
||||||
<div class="box-scale">
|
|
||||||
<nodeWrap v-model:nodeConfig="nodeConfig" />
|
|
||||||
<div class="end-node">
|
|
||||||
<div class="end-node-circle"></div>
|
|
||||||
<div class="end-node-text">流程结束</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script setup lang="ts">
|
||||||
import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
|
import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/'
|
||||||
defineOptions({ name: 'SimpleWorkflowDesignEditor' })
|
|
||||||
let nodeConfig = ref({
|
defineOptions({
|
||||||
nodeName: '发起人',
|
name: 'SimpleWorkflowDesignEditor'
|
||||||
type: 0,
|
|
||||||
id: 'root',
|
|
||||||
formPerms: {},
|
|
||||||
nodeUserList: [],
|
|
||||||
childNode: {}
|
|
||||||
})
|
})
|
||||||
|
const { query } = useRoute() // 路由的查询
|
||||||
|
const modelId = query.modelId as string
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style lang="scss" scoped></style>
|
||||||
@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -111,11 +111,16 @@ const getList = async () => {
|
||||||
|
|
||||||
/** 处理审批按钮 */
|
/** 处理审批按钮 */
|
||||||
const handleAudit = (row: any) => {
|
const handleAudit = (row: any) => {
|
||||||
|
const query = {
|
||||||
|
id: row.processInstanceId,
|
||||||
|
activityId: undefined
|
||||||
|
}
|
||||||
|
if (row.activityId) {
|
||||||
|
query.activityId = row.activityId
|
||||||
|
}
|
||||||
push({
|
push({
|
||||||
name: 'BpmProcessInstanceDetail',
|
name: 'BpmProcessInstanceDetail',
|
||||||
query: {
|
query: query
|
||||||
id: row.processInstanceId
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,8 @@ const handleAudit = (row: any) => {
|
||||||
push({
|
push({
|
||||||
name: 'BpmProcessInstanceDetail',
|
name: 'BpmProcessInstanceDetail',
|
||||||
query: {
|
query: {
|
||||||
id: row.processInstance.id
|
id: row.processInstance.id,
|
||||||
|
taskId: row.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,8 @@ const handleAudit = (row: any) => {
|
||||||
push({
|
push({
|
||||||
name: 'BpmProcessInstanceDetail',
|
name: 'BpmProcessInstanceDetail',
|
||||||
query: {
|
query: {
|
||||||
id: row.processInstance.id
|
id: row.processInstance.id,
|
||||||
|
taskId: row.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue