commit
d11a82a521
|
@ -73,6 +73,7 @@
|
||||||
"vue-i18n": "9.10.2",
|
"vue-i18n": "9.10.2",
|
||||||
"vue-router": "4.4.5",
|
"vue-router": "4.4.5",
|
||||||
"vue-types": "^5.1.1",
|
"vue-types": "^5.1.1",
|
||||||
|
"vue3-signature": "^0.2.4",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"web-storage-cache": "^1.1.1",
|
"web-storage-cache": "^1.1.1",
|
||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
import ESign from './src/ESign.vue'
|
|
||||||
|
|
||||||
export { ESign }
|
|
|
@ -1,289 +0,0 @@
|
||||||
<!-- TODO @lesan:看着没啥问题,哈哈哈,我还搜了下;嘿嘿,有没人封装好了组件库哈?https://github.com/WangShayne/vue3-signature (https://www.npmjs.com/package/vue3-signature) -->
|
|
||||||
<template>
|
|
||||||
<div style="position: relative">
|
|
||||||
<canvas
|
|
||||||
ref="canvasRef"
|
|
||||||
@mousedown="mouseDown"
|
|
||||||
@mousemove="mouseMove"
|
|
||||||
@mouseup="mouseUp"
|
|
||||||
@touchstart="touchStart"
|
|
||||||
@touchmove="touchMove"
|
|
||||||
@touchend="touchEnd"
|
|
||||||
style="border: 1px solid lightgrey; max-width: 100%; display: block"
|
|
||||||
>
|
|
||||||
</canvas>
|
|
||||||
|
|
||||||
<el-button
|
|
||||||
style="position: absolute; bottom: 20px; right: 10px"
|
|
||||||
type="primary"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
@click="reset"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:delete" class="mr-5px" />清除
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { propTypes } from '@/utils/propTypes'
|
|
||||||
|
|
||||||
defineOptions({ name: 'ESign' })
|
|
||||||
|
|
||||||
const emits = defineEmits(['update:bgColor'])
|
|
||||||
const props = defineProps({
|
|
||||||
// 画布宽度,即导出图片的宽度
|
|
||||||
width: propTypes.number.def(900),
|
|
||||||
// 画布高度,即导出图片的高度
|
|
||||||
height: propTypes.number.def(400),
|
|
||||||
// 画笔粗细
|
|
||||||
lineWidth: propTypes.number.def(10),
|
|
||||||
// 画笔颜色
|
|
||||||
lineColor: propTypes.string.def('#000000'),
|
|
||||||
// 画布背景色,为空时画布背景透明
|
|
||||||
bgColor: propTypes.string.def(''),
|
|
||||||
// 是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
|
|
||||||
isCrop: propTypes.bool.def(false),
|
|
||||||
// 清空画布时是否同时清空设置的背景色
|
|
||||||
isClearBgColor: propTypes.bool.def(true),
|
|
||||||
// 生成图片格式
|
|
||||||
format: propTypes.string.def('image/png'),
|
|
||||||
// 生成图片质量,0 到 1
|
|
||||||
quality: propTypes.number.def(1)
|
|
||||||
})
|
|
||||||
const canvasRef = ref()
|
|
||||||
const hasDrew = ref(false)
|
|
||||||
const resultImg = ref('')
|
|
||||||
const points = ref<any>([])
|
|
||||||
const canvasTxt = ref()
|
|
||||||
const startX = ref(0)
|
|
||||||
const startY = ref(0)
|
|
||||||
const isDrawing = ref(false)
|
|
||||||
const sratio = ref(1)
|
|
||||||
|
|
||||||
const ratio = computed(() => {
|
|
||||||
return props.height / props.width
|
|
||||||
})
|
|
||||||
const stageInfo = computed(() => {
|
|
||||||
return canvasRef.value.getBoundingClientRect()
|
|
||||||
})
|
|
||||||
const bgColor = computed(() => {
|
|
||||||
return props.bgColor ? props.bgColor : 'rgba(255, 255, 255, 0)'
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => bgColor.value,
|
|
||||||
() => {
|
|
||||||
if (canvasRef.value) {
|
|
||||||
canvasRef.value.style.background = bgColor.value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const resizeHandler = () => {
|
|
||||||
const canvas = canvasRef.value
|
|
||||||
canvas.style.width = props.width + 'px'
|
|
||||||
const realw = parseFloat(window.getComputedStyle(canvas).width)
|
|
||||||
canvas.style.height = ratio.value * realw + 'px'
|
|
||||||
canvasTxt.value = canvas.getContext('2d')
|
|
||||||
canvasTxt.value.scale(1 * sratio.value, 1 * sratio.value)
|
|
||||||
sratio.value = realw / props.width
|
|
||||||
canvasTxt.value.scale(1 / sratio.value, 1 / sratio.value)
|
|
||||||
}
|
|
||||||
// For PC
|
|
||||||
const mouseDown = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
isDrawing.value = true
|
|
||||||
hasDrew.value = true
|
|
||||||
let obj = {
|
|
||||||
x: e.offsetX,
|
|
||||||
y: e.offsetY
|
|
||||||
}
|
|
||||||
drawStart(obj)
|
|
||||||
}
|
|
||||||
const mouseMove = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (isDrawing.value) {
|
|
||||||
let obj = {
|
|
||||||
x: e.offsetX,
|
|
||||||
y: e.offsetY
|
|
||||||
}
|
|
||||||
drawMove(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mouseUp = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
let obj = {
|
|
||||||
x: e.offsetX,
|
|
||||||
y: e.offsetY
|
|
||||||
}
|
|
||||||
drawEnd(obj)
|
|
||||||
isDrawing.value = false
|
|
||||||
}
|
|
||||||
// For Mobile
|
|
||||||
const touchStart = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
hasDrew.value = true
|
|
||||||
if (e.touches.length === 1) {
|
|
||||||
let obj = {
|
|
||||||
x: e.targetTouches[0].clientX - canvasRef.value.getBoundingClientRect().left,
|
|
||||||
y: e.targetTouches[0].clientY - canvasRef.value.getBoundingClientRect().top
|
|
||||||
}
|
|
||||||
drawStart(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const touchMove = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (e.touches.length === 1) {
|
|
||||||
let obj = {
|
|
||||||
x: e.targetTouches[0].clientX - canvasRef.value.getBoundingClientRect().left,
|
|
||||||
y: e.targetTouches[0].clientY - canvasRef.value.getBoundingClientRect().top
|
|
||||||
}
|
|
||||||
drawMove(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const touchEnd = (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (e.touches.length === 1) {
|
|
||||||
let obj = {
|
|
||||||
x: e.targetTouches[0].clientX - canvasRef.value.getBoundingClientRect().left,
|
|
||||||
y: e.targetTouches[0].clientY - canvasRef.value.getBoundingClientRect().top
|
|
||||||
}
|
|
||||||
drawEnd(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 绘制
|
|
||||||
const drawStart = (obj) => {
|
|
||||||
startX.value = obj.x
|
|
||||||
startY.value = obj.y
|
|
||||||
canvasTxt.value.beginPath()
|
|
||||||
canvasTxt.value.moveTo(startX.value, startY.value)
|
|
||||||
canvasTxt.value.lineTo(obj.x, obj.y)
|
|
||||||
canvasTxt.value.lineCap = 'round'
|
|
||||||
canvasTxt.value.lineJoin = 'round'
|
|
||||||
canvasTxt.value.lineWidth = props.lineWidth * sratio.value
|
|
||||||
canvasTxt.value.stroke()
|
|
||||||
canvasTxt.value.closePath()
|
|
||||||
points.value.push(obj)
|
|
||||||
}
|
|
||||||
const drawMove = (obj) => {
|
|
||||||
canvasTxt.value.beginPath()
|
|
||||||
canvasTxt.value.moveTo(startX.value, startY.value)
|
|
||||||
canvasTxt.value.lineTo(obj.x, obj.y)
|
|
||||||
canvasTxt.value.strokeStyle = props.lineColor
|
|
||||||
canvasTxt.value.lineWidth = props.lineWidth * sratio.value
|
|
||||||
canvasTxt.value.lineCap = 'round'
|
|
||||||
canvasTxt.value.lineJoin = 'round'
|
|
||||||
canvasTxt.value.stroke()
|
|
||||||
canvasTxt.value.closePath()
|
|
||||||
startY.value = obj.y
|
|
||||||
startX.value = obj.x
|
|
||||||
points.value.push(obj)
|
|
||||||
}
|
|
||||||
const drawEnd = (obj) => {
|
|
||||||
canvasTxt.value.beginPath()
|
|
||||||
canvasTxt.value.moveTo(startX.value, startY.value)
|
|
||||||
canvasTxt.value.lineTo(obj.x, obj.y)
|
|
||||||
canvasTxt.value.lineCap = 'round'
|
|
||||||
canvasTxt.value.lineJoin = 'round'
|
|
||||||
canvasTxt.value.stroke()
|
|
||||||
canvasTxt.value.closePath()
|
|
||||||
points.value.push(obj)
|
|
||||||
points.value.push({ x: -1, y: -1 })
|
|
||||||
}
|
|
||||||
// 生成
|
|
||||||
const generate = (options) => {
|
|
||||||
let imgFormat = options && options.format ? options.format : props.format
|
|
||||||
let imgQuality = options && options.quality ? options.quality : props.quality
|
|
||||||
const pm = new Promise((resolve, reject) => {
|
|
||||||
if (!hasDrew.value) {
|
|
||||||
reject(`Warning: Not Signned!`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let resImgData = canvasTxt.value.getImageData(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
canvasRef.value.width,
|
|
||||||
canvasRef.value.height
|
|
||||||
)
|
|
||||||
canvasTxt.value.globalCompositeOperation = 'destination-over'
|
|
||||||
canvasTxt.value.fillStyle = bgColor.value
|
|
||||||
canvasTxt.value.fillRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
|
||||||
resultImg.value = canvasRef.value.toDataURL(imgFormat, imgQuality)
|
|
||||||
canvasTxt.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
|
||||||
canvasTxt.value.putImageData(resImgData, 0, 0)
|
|
||||||
canvasTxt.value.globalCompositeOperation = 'source-over'
|
|
||||||
if (props.isCrop) {
|
|
||||||
const crop_area = getCropArea(resImgData.data)
|
|
||||||
let crop_canvas = document.createElement('canvas')
|
|
||||||
const crop_ctx = crop_canvas.getContext('2d')
|
|
||||||
crop_canvas.width = crop_area[2] - crop_area[0]
|
|
||||||
crop_canvas.height = crop_area[3] - crop_area[1]
|
|
||||||
const crop_imgData = canvasTxt.value.getImageData(...crop_area)
|
|
||||||
crop_ctx.globalCompositeOperation = 'destination-over'
|
|
||||||
crop_ctx.putImageData(crop_imgData, 0, 0)
|
|
||||||
crop_ctx.fillStyle = bgColor.value
|
|
||||||
crop_ctx.fillRect(0, 0, crop_canvas.width, crop_canvas.height)
|
|
||||||
resultImg.value = crop_canvas.toDataURL(imgFormat, imgQuality)
|
|
||||||
}
|
|
||||||
resolve(resultImg.value)
|
|
||||||
})
|
|
||||||
return pm
|
|
||||||
}
|
|
||||||
const reset = () => {
|
|
||||||
canvasTxt.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
|
||||||
if (props.isClearBgColor) {
|
|
||||||
emits('update:bgColor', '')
|
|
||||||
canvasRef.value.style.background = 'rgba(255, 255, 255, 0)'
|
|
||||||
}
|
|
||||||
points.value = []
|
|
||||||
hasDrew.value = false
|
|
||||||
resultImg.value = ''
|
|
||||||
}
|
|
||||||
const getCropArea = (imgData) => {
|
|
||||||
let topX = canvasRef.value.width
|
|
||||||
let btmX = 0
|
|
||||||
let topY = canvasRef.value.height
|
|
||||||
let btnY = 0
|
|
||||||
for (let i = 0; i < canvasRef.value.width; i++) {
|
|
||||||
for (let j = 0; j < canvasRef.value.height; j++) {
|
|
||||||
let pos = (i + canvasRef.value.width * j) * 4
|
|
||||||
if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {
|
|
||||||
btnY = Math.max(j, btnY)
|
|
||||||
btmX = Math.max(i, btmX)
|
|
||||||
topY = Math.min(j, topY)
|
|
||||||
topX = Math.min(i, topX)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
topX++
|
|
||||||
btmX++
|
|
||||||
topY++
|
|
||||||
btnY++
|
|
||||||
const data = [topX, topY, btmX, btnY]
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
generate
|
|
||||||
})
|
|
||||||
onBeforeMount(() => {
|
|
||||||
window.addEventListener('resize', resizeHandler)
|
|
||||||
})
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('resize', resizeHandler)
|
|
||||||
})
|
|
||||||
onMounted(() => {
|
|
||||||
canvasRef.value.height = props.height
|
|
||||||
canvasRef.value.width = props.width
|
|
||||||
canvasRef.value.style.background = bgColor.value
|
|
||||||
resizeHandler()
|
|
||||||
// 在画板以外松开鼠标后冻结画笔
|
|
||||||
document.onmouseup = () => {
|
|
||||||
isDrawing.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
|
@ -242,7 +242,6 @@ const addNode = (type: number) => {
|
||||||
emits('update:childNode', data)
|
emits('update:childNode', data)
|
||||||
}
|
}
|
||||||
if (type === NodeType.ROUTE_BRANCH_NODE) {
|
if (type === NodeType.ROUTE_BRANCH_NODE) {
|
||||||
// TODO @lesan:高亮那边,需要考虑下。
|
|
||||||
const data: SimpleFlowNode = {
|
const data: SimpleFlowNode = {
|
||||||
id: 'GateWay_' + generateUUID(),
|
id: 'GateWay_' + generateUUID(),
|
||||||
name: NODE_DEFAULT_NAME.get(NodeType.ROUTE_BRANCH_NODE) as string,
|
name: NODE_DEFAULT_NAME.get(NodeType.ROUTE_BRANCH_NODE) as string,
|
||||||
|
|
|
@ -116,8 +116,10 @@ export interface SimpleFlowNode {
|
||||||
// 延迟设置
|
// 延迟设置
|
||||||
delaySetting?: DelaySetting
|
delaySetting?: DelaySetting
|
||||||
// 路由分支
|
// 路由分支
|
||||||
routeGroup?: RouteCondition[]
|
routerGroups?: RouteCondition[]
|
||||||
defaultFlowId?: string
|
defaultFlowId?: string
|
||||||
|
// 签名
|
||||||
|
signEnable?: boolean
|
||||||
}
|
}
|
||||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
||||||
export enum CandidateStrategy {
|
export enum CandidateStrategy {
|
||||||
|
@ -241,15 +243,15 @@ export type AssignEmptyHandler = {
|
||||||
export type ListenerHandler = {
|
export type ListenerHandler = {
|
||||||
enable: boolean
|
enable: boolean
|
||||||
path?: string
|
path?: string
|
||||||
header?: ListenerMap[]
|
header?: ListenerParam[]
|
||||||
body?: ListenerMap[]
|
body?: ListenerParam[]
|
||||||
}
|
}
|
||||||
export type ListenerMap = {
|
export type ListenerParam = {
|
||||||
key: string
|
key: string
|
||||||
type: number
|
type: number
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
export enum ListenerMapTypeEnum {
|
export enum ListenerParamTypeEnum {
|
||||||
/**
|
/**
|
||||||
* 固定值
|
* 固定值
|
||||||
*/
|
*/
|
||||||
|
@ -510,8 +512,8 @@ export const APPROVE_METHODS: DictDataVO[] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
export const CONDITION_CONFIG_TYPES: DictDataVO[] = [
|
export const CONDITION_CONFIG_TYPES: DictDataVO[] = [
|
||||||
{ label: '条件表达式', value: ConditionType.EXPRESSION },
|
{ label: '条件规则', value: ConditionType.RULE },
|
||||||
{ label: '条件规则', value: ConditionType.RULE }
|
{ label: '条件表达式', value: ConditionType.EXPRESSION }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 时间单位类型
|
// 时间单位类型
|
||||||
|
@ -660,7 +662,7 @@ export const DELAY_TYPE = [
|
||||||
*/
|
*/
|
||||||
export type RouteCondition = {
|
export type RouteCondition = {
|
||||||
nodeId: string
|
nodeId: string
|
||||||
conditionType: number // TODO @lesan:ConditionType
|
conditionType: ConditionType
|
||||||
conditionExpression: string
|
conditionExpression: string
|
||||||
conditionGroups: ConditionGroup
|
conditionGroups: ConditionGroup
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
AssignStartUserHandlerType,
|
AssignStartUserHandlerType,
|
||||||
AssignEmptyHandlerType,
|
AssignEmptyHandlerType,
|
||||||
FieldPermissionType,
|
FieldPermissionType,
|
||||||
ListenerMap
|
ListenerParam
|
||||||
} from './consts'
|
} from './consts'
|
||||||
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
|
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
|
||||||
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
||||||
|
@ -139,16 +139,17 @@ export type UserTaskFormType = {
|
||||||
buttonsSetting: any[]
|
buttonsSetting: any[]
|
||||||
taskCreateListenerEnable?: boolean
|
taskCreateListenerEnable?: boolean
|
||||||
taskCreateListenerPath?: string
|
taskCreateListenerPath?: string
|
||||||
taskCreateListenerHeader?: ListenerMap[]
|
taskCreateListenerHeader?: ListenerParam[]
|
||||||
taskCreateListenerBody?: ListenerMap[]
|
taskCreateListenerBody?: ListenerParam[]
|
||||||
taskAssignListenerEnable?: boolean
|
taskAssignListenerEnable?: boolean
|
||||||
taskAssignListenerPath?: string
|
taskAssignListenerPath?: string
|
||||||
taskAssignListenerHeader?: ListenerMap[]
|
taskAssignListenerHeader?: ListenerParam[]
|
||||||
taskAssignListenerBody?: ListenerMap[]
|
taskAssignListenerBody?: ListenerParam[]
|
||||||
taskCompleteListenerEnable?: boolean
|
taskCompleteListenerEnable?: boolean
|
||||||
taskCompleteListenerPath?: string
|
taskCompleteListenerPath?: string
|
||||||
taskCompleteListenerHeader?: ListenerMap[]
|
taskCompleteListenerHeader?: ListenerParam[]
|
||||||
taskCompleteListenerBody?: ListenerMap[]
|
taskCompleteListenerBody?: ListenerParam[]
|
||||||
|
signEnable: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CopyTaskFormType = {
|
export type CopyTaskFormType = {
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</template>
|
</template>
|
||||||
<div>
|
<div>
|
||||||
<el-form label-position="top">
|
<el-form label-position="top">
|
||||||
<el-card class="mb-15px" v-for="(item, index) in routeGroup" :key="index">
|
<el-card class="mb-15px" v-for="(item, index) in routerGroups" :key="index">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex flex-items-center">
|
<div class="flex flex-items-center">
|
||||||
<el-text size="large">路由{{ index + 1 }}</el-text>
|
<el-text size="large">路由{{ index + 1 }}</el-text>
|
||||||
|
@ -42,123 +42,7 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-form-item label="配置方式" prop="conditionType">
|
<Condition v-model="routerGroups[index]" />
|
||||||
<el-radio-group v-model="item.conditionType" @change="changeConditionType">
|
|
||||||
<el-radio
|
|
||||||
v-for="(dict, indexConditionType) in conditionConfigTypes"
|
|
||||||
:key="indexConditionType"
|
|
||||||
:value="dict.value"
|
|
||||||
:label="dict.value"
|
|
||||||
>
|
|
||||||
{{ dict.label }}
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<!-- TODO @lesan:1)1、2 使用枚举;2)默认先 条件组关系,再 条件表达式;3)这种可以封装成一个小组件么? -->
|
|
||||||
<el-form-item
|
|
||||||
v-if="item.conditionType === 1"
|
|
||||||
label="条件表达式"
|
|
||||||
prop="conditionExpression"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="item.conditionExpression"
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="item.conditionType === 2" label="条件规则">
|
|
||||||
<div class="condition-group-tool">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="mr-4">条件组关系</div>
|
|
||||||
<el-switch
|
|
||||||
v-model="item.conditionGroups.and"
|
|
||||||
inline-prompt
|
|
||||||
active-text="且"
|
|
||||||
inactive-text="或"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-space direction="vertical" :spacer="item.conditionGroups.and ? '且' : '或'">
|
|
||||||
<el-card
|
|
||||||
class="condition-group"
|
|
||||||
style="width: 530px"
|
|
||||||
v-for="(condition, cIdx) in item.conditionGroups.conditions"
|
|
||||||
:key="cIdx"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="condition-group-delete"
|
|
||||||
v-if="item.conditionGroups.conditions.length > 1"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
color="#0089ff"
|
|
||||||
icon="ep:circle-close-filled"
|
|
||||||
:size="18"
|
|
||||||
@click="deleteConditionGroup(item.conditionGroups.conditions, cIdx)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<template #header>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>条件组</div>
|
|
||||||
<div class="flex">
|
|
||||||
<div class="mr-4">规则关系</div>
|
|
||||||
<el-switch
|
|
||||||
v-model="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="(field, fIdx) in fieldOptions"
|
|
||||||
:key="fIdx"
|
|
||||||
:label="field.title"
|
|
||||||
:value="field.field"
|
|
||||||
:disabled="!field.required"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="mr-2">
|
|
||||||
<el-select v-model="rule.opCode" style="width: 100px">
|
|
||||||
<el-option
|
|
||||||
v-for="operator in COMPARISON_OPERATORS"
|
|
||||||
:key="operator.value"
|
|
||||||
:label="operator.label"
|
|
||||||
:value="operator.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="mr-2">
|
|
||||||
<el-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(item.conditionGroups.conditions)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
@ -177,18 +61,9 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Plus } from '@element-plus/icons-vue'
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
import {
|
import { SimpleFlowNode, NodeType, ConditionType, RouteCondition } from '../consts'
|
||||||
SimpleFlowNode,
|
|
||||||
NodeType,
|
|
||||||
CONDITION_CONFIG_TYPES,
|
|
||||||
ConditionType,
|
|
||||||
COMPARISON_OPERATORS,
|
|
||||||
RouteCondition,
|
|
||||||
ProcessVariableEnum
|
|
||||||
} from '../consts'
|
|
||||||
import { useWatchNode, useDrawer, useNodeName } from '../node'
|
import { useWatchNode, useDrawer, useNodeName } from '../node'
|
||||||
import { BpmModelFormType } from '@/utils/constants'
|
import Condition from './components/Condition.vue'
|
||||||
import { useFormFields } from '../node'
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'RouteNodeConfig'
|
name: 'RouteNodeConfig'
|
||||||
})
|
})
|
||||||
|
@ -206,29 +81,7 @@ const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
const currentNode = useWatchNode(props)
|
const currentNode = useWatchNode(props)
|
||||||
// 节点名称
|
// 节点名称
|
||||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTE_BRANCH_NODE)
|
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTE_BRANCH_NODE)
|
||||||
const formType = inject<Ref<number>>('formType') // 表单类型
|
const routerGroups = ref<RouteCondition[]>([])
|
||||||
const conditionConfigTypes = computed(() => {
|
|
||||||
return CONDITION_CONFIG_TYPES.filter((item) => {
|
|
||||||
// 业务表单暂时去掉条件规则选项
|
|
||||||
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
/** 条件规则可选择的表单字段 */
|
|
||||||
const fieldOptions = computed(() => {
|
|
||||||
const fieldsCopy = useFormFields().slice()
|
|
||||||
// 固定添加发起人 ID 字段
|
|
||||||
fieldsCopy.unshift({
|
|
||||||
field: ProcessVariableEnum.START_USER_ID,
|
|
||||||
title: '发起人',
|
|
||||||
required: true
|
|
||||||
})
|
|
||||||
return fieldsCopy
|
|
||||||
})
|
|
||||||
const routeGroup = ref<RouteCondition[]>([])
|
|
||||||
const nodeOptions = ref()
|
const nodeOptions = ref()
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
|
@ -237,26 +90,26 @@ const saveConfig = async () => {
|
||||||
if (!showText) return false
|
if (!showText) return false
|
||||||
currentNode.value.name = nodeName.value!
|
currentNode.value.name = nodeName.value!
|
||||||
currentNode.value.showText = showText
|
currentNode.value.showText = showText
|
||||||
currentNode.value.routeGroup = routeGroup.value
|
currentNode.value.routerGroups = routerGroups.value
|
||||||
settingVisible.value = false
|
settingVisible.value = false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// 显示路由分支节点配置, 由父组件传过来
|
// 显示路由分支节点配置, 由父组件传过来
|
||||||
const showRouteNodeConfig = (node: SimpleFlowNode) => {
|
const showRouteNodeConfig = (node: SimpleFlowNode) => {
|
||||||
getRoutableNode()
|
getRoutableNode()
|
||||||
routeGroup.value = []
|
routerGroups.value = []
|
||||||
nodeName.value = node.name
|
nodeName.value = node.name
|
||||||
if (node.routeGroup) {
|
if (node.routerGroups) {
|
||||||
routeGroup.value = node.routeGroup
|
routerGroups.value = node.routerGroups
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getShowText = () => {
|
const getShowText = () => {
|
||||||
if (!routeGroup.value || !Array.isArray(routeGroup.value) || routeGroup.value.length <= 0) {
|
if (!routerGroups.value || !Array.isArray(routerGroups.value) || routerGroups.value.length <= 0) {
|
||||||
message.warning('请配置路由!')
|
message.warning('请配置路由!')
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
for (const route of routeGroup.value) {
|
for (const route of routerGroups.value) {
|
||||||
if (!route.nodeId || !route.conditionType) {
|
if (!route.nodeId || !route.conditionType) {
|
||||||
message.warning('请完善路由配置项!')
|
message.warning('请完善路由配置项!')
|
||||||
return ''
|
return ''
|
||||||
|
@ -276,51 +129,13 @@ const getShowText = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${routeGroup.value.length}条路由分支`
|
return `${routerGroups.value.length}条路由分支`
|
||||||
}
|
|
||||||
|
|
||||||
// TODO @lesan:这个需要实现么?
|
|
||||||
const changeConditionType = () => {}
|
|
||||||
|
|
||||||
const deleteConditionGroup = (conditions, index) => {
|
|
||||||
conditions.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteConditionRule = (condition, index) => {
|
|
||||||
condition.rules.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addConditionRule = (condition, index) => {
|
|
||||||
const rule = {
|
|
||||||
type: 1,
|
|
||||||
opName: '等于',
|
|
||||||
opCode: '==',
|
|
||||||
leftSide: '',
|
|
||||||
rightSide: ''
|
|
||||||
}
|
|
||||||
condition.rules.splice(index + 1, 0, rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addConditionGroup = (conditions) => {
|
|
||||||
const condition = {
|
|
||||||
and: true,
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
type: 1,
|
|
||||||
opName: '等于',
|
|
||||||
opCode: '==',
|
|
||||||
leftSide: '',
|
|
||||||
rightSide: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
conditions.push(condition)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const addRouteGroup = () => {
|
const addRouteGroup = () => {
|
||||||
routeGroup.value.push({
|
routerGroups.value.push({
|
||||||
nodeId: '',
|
nodeId: '',
|
||||||
conditionType: ConditionType.EXPRESSION,
|
conditionType: ConditionType.RULE,
|
||||||
conditionExpression: '',
|
conditionExpression: '',
|
||||||
conditionGroups: {
|
conditionGroups: {
|
||||||
and: true,
|
and: true,
|
||||||
|
@ -343,11 +158,11 @@ const addRouteGroup = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRouteGroup = (index) => {
|
const deleteRouteGroup = (index) => {
|
||||||
routeGroup.value.splice(index, 1)
|
routerGroups.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRoutableNode = () => {
|
const getRoutableNode = () => {
|
||||||
// TODO 还需要满足以下要求
|
// TODO @lesan 还需要满足以下要求
|
||||||
// 并行分支、包容分支内部节点不能跳转到外部节点
|
// 并行分支、包容分支内部节点不能跳转到外部节点
|
||||||
// 条件分支节点可以向上跳转到外部节点
|
// 条件分支节点可以向上跳转到外部节点
|
||||||
let node = processNodeTree?.value
|
let node = processNodeTree?.value
|
||||||
|
@ -369,39 +184,3 @@ const getRoutableNode = () => {
|
||||||
|
|
||||||
defineExpose({ openDrawer, showRouteNodeConfig }) // 暴露方法给父组件
|
defineExpose({ openDrawer, showRouteNodeConfig }) // 暴露方法给父组件
|
||||||
</script>
|
</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>
|
|
||||||
|
|
|
@ -356,6 +356,15 @@
|
||||||
</div>
|
</div>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">是否需要签名</el-divider>
|
||||||
|
<el-form-item prop="signEnable">
|
||||||
|
<el-switch
|
||||||
|
v-model="configForm.signEnable"
|
||||||
|
active-text="是"
|
||||||
|
inactive-text="否"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
@ -436,155 +445,161 @@
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="监听器" name="listener">
|
<el-tab-pane label="监听器" name="listener">
|
||||||
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
|
<el-form :model="configForm" label-position="top">
|
||||||
<el-form label-position="top">
|
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
|
||||||
<div>
|
<el-divider content-position="left">
|
||||||
<el-divider content-position="left">
|
<el-text tag="b" size="large">{{ listener.name }}</el-text>
|
||||||
<el-text tag="b" size="large">{{ listener.name }}</el-text>
|
</el-divider>
|
||||||
</el-divider>
|
<el-form-item>
|
||||||
|
<el-switch
|
||||||
|
v-model="configForm[`task${listener.type}ListenerEnable`]"
|
||||||
|
active-text="开启"
|
||||||
|
inactive-text="关闭"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-switch
|
<el-alert
|
||||||
v-model="configForm[`task${listener.type}ListenerEnable`]"
|
title="仅支持 POST 请求,以请求体方式接收参数"
|
||||||
active-text="开启"
|
type="warning"
|
||||||
inactive-text="关闭"
|
show-icon
|
||||||
|
:closable="false"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
|
<el-form-item
|
||||||
<el-form-item>
|
label="请求地址"
|
||||||
<el-alert
|
:prop="`task${listener.type}ListenerPath`"
|
||||||
title="仅支持 POST 请求,以请求体方式接收参数"
|
:rules="{
|
||||||
type="warning"
|
required: true,
|
||||||
show-icon
|
message: '请求地址不能为空',
|
||||||
:closable="false"
|
trigger: 'blur'
|
||||||
/>
|
}"
|
||||||
</el-form-item>
|
>
|
||||||
<el-form-item label="请求地址">
|
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
|
||||||
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="请求头">
|
||||||
<el-form-item label="请求头">
|
<div
|
||||||
<div
|
class="flex pt-2"
|
||||||
class="flex pt-2"
|
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
|
||||||
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
|
:key="index"
|
||||||
:key="index"
|
>
|
||||||
>
|
<div class="mr-2">
|
||||||
<div class="mr-2">
|
<el-input class="w-160px" v-model="item.key" />
|
||||||
<el-input class="w-160px" v-model="item.key" />
|
|
||||||
</div>
|
|
||||||
<div class="mr-2">
|
|
||||||
<el-select class="w-100px!" v-model="item.type">
|
|
||||||
<el-option
|
|
||||||
v-for="types in LISTENER_MAP_TYPES"
|
|
||||||
:key="types.value"
|
|
||||||
:label="types.label"
|
|
||||||
:value="types.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="mr-2">
|
|
||||||
<el-input
|
|
||||||
v-if="item.type === ListenerMapTypeEnum.FIXED_VALUE"
|
|
||||||
class="w-160px"
|
|
||||||
v-model="item.value"
|
|
||||||
/>
|
|
||||||
<el-select
|
|
||||||
v-if="item.type === ListenerMapTypeEnum.FROM_FORM"
|
|
||||||
class="w-160px!"
|
|
||||||
v-model="item.value"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="(field, fIdx) in formFieldOptions"
|
|
||||||
:key="fIdx"
|
|
||||||
:label="field.title"
|
|
||||||
:value="field.field"
|
|
||||||
:disabled="!field.required"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="mr-1 flex items-center">
|
|
||||||
<Icon
|
|
||||||
icon="ep:delete"
|
|
||||||
:size="18"
|
|
||||||
@click="
|
|
||||||
deleteTaskListenerMap(
|
|
||||||
configForm[`task${listener.type}ListenerHeader`],
|
|
||||||
index
|
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
<div class="mr-2">
|
||||||
type="primary"
|
<el-select class="w-100px!" v-model="item.type">
|
||||||
text
|
<el-option
|
||||||
@click="addTaskListenerMap(configForm[`task${listener.type}ListenerHeader`])"
|
v-for="types in LISTENER_MAP_TYPES"
|
||||||
>
|
:key="types.value"
|
||||||
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
:label="types.label"
|
||||||
</el-button>
|
:value="types.value"
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="请求体">
|
|
||||||
<div
|
|
||||||
class="flex pt-2"
|
|
||||||
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<div class="mr-2">
|
|
||||||
<el-input class="w-160px" v-model="item.key" />
|
|
||||||
</div>
|
|
||||||
<div class="mr-2">
|
|
||||||
<el-select class="w-100px!" v-model="item.type">
|
|
||||||
<el-option
|
|
||||||
v-for="types in LISTENER_MAP_TYPES"
|
|
||||||
:key="types.value"
|
|
||||||
:label="types.label"
|
|
||||||
:value="types.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="mr-2">
|
|
||||||
<el-input
|
|
||||||
v-if="item.type === ListenerMapTypeEnum.FIXED_VALUE"
|
|
||||||
class="w-160px"
|
|
||||||
v-model="item.value"
|
|
||||||
/>
|
/>
|
||||||
<el-select
|
</el-select>
|
||||||
v-if="item.type === ListenerMapTypeEnum.FROM_FORM"
|
|
||||||
class="w-160px!"
|
|
||||||
v-model="item.value"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="(field, fIdx) in formFieldOptions"
|
|
||||||
:key="fIdx"
|
|
||||||
:label="field.title"
|
|
||||||
:value="field.field"
|
|
||||||
:disabled="!field.required"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="mr-1 flex items-center">
|
|
||||||
<Icon
|
|
||||||
icon="ep:delete"
|
|
||||||
:size="18"
|
|
||||||
@click="
|
|
||||||
deleteTaskListenerMap(
|
|
||||||
configForm[`task${listener.type}ListenerBody`],
|
|
||||||
index
|
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
<div class="mr-2">
|
||||||
type="primary"
|
<el-input
|
||||||
text
|
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
|
||||||
@click="addTaskListenerMap(configForm[`task${listener.type}ListenerBody`])"
|
class="w-160px"
|
||||||
>
|
v-model="item.value"
|
||||||
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
/>
|
||||||
</el-button>
|
<el-select
|
||||||
</el-form-item>
|
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
|
||||||
</div>
|
class="w-160px!"
|
||||||
|
v-model="item.value"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="(field, fIdx) in formFieldOptions"
|
||||||
|
:key="fIdx"
|
||||||
|
:label="field.title"
|
||||||
|
:value="field.field"
|
||||||
|
:disabled="!field.required"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-1 flex items-center">
|
||||||
|
<Icon
|
||||||
|
icon="ep:delete"
|
||||||
|
:size="18"
|
||||||
|
@click="
|
||||||
|
deleteTaskListenerParam(
|
||||||
|
configForm[`task${listener.type}ListenerHeader`],
|
||||||
|
index
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="请求体">
|
||||||
|
<div
|
||||||
|
class="flex pt-2"
|
||||||
|
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-input class="w-160px" v-model="item.key" />
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-select class="w-100px!" v-model="item.type">
|
||||||
|
<el-option
|
||||||
|
v-for="types in LISTENER_MAP_TYPES"
|
||||||
|
:key="types.value"
|
||||||
|
:label="types.label"
|
||||||
|
:value="types.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-input
|
||||||
|
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
|
||||||
|
class="w-160px"
|
||||||
|
v-model="item.value"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
|
||||||
|
class="w-160px!"
|
||||||
|
v-model="item.value"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="(field, fIdx) in formFieldOptions"
|
||||||
|
:key="fIdx"
|
||||||
|
:label="field.title"
|
||||||
|
:value="field.field"
|
||||||
|
:disabled="!field.required"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-1 flex items-center">
|
||||||
|
<Icon
|
||||||
|
icon="ep:delete"
|
||||||
|
:size="18"
|
||||||
|
@click="
|
||||||
|
deleteTaskListenerParam(
|
||||||
|
configForm[`task${listener.type}ListenerBody`],
|
||||||
|
index
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</div>
|
||||||
</div>
|
</el-form>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
@ -623,7 +638,7 @@ import {
|
||||||
FieldPermissionType,
|
FieldPermissionType,
|
||||||
ProcessVariableEnum,
|
ProcessVariableEnum,
|
||||||
LISTENER_MAP_TYPES,
|
LISTENER_MAP_TYPES,
|
||||||
ListenerMapTypeEnum
|
ListenerParamTypeEnum
|
||||||
} from '../consts'
|
} from '../consts'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -852,6 +867,8 @@ const saveConfig = async () => {
|
||||||
header: configForm.value.taskCompleteListenerHeader,
|
header: configForm.value.taskCompleteListenerHeader,
|
||||||
body: configForm.value.taskCompleteListenerBody
|
body: configForm.value.taskCompleteListenerBody
|
||||||
}
|
}
|
||||||
|
// 签名
|
||||||
|
currentNode.value.signEnable = configForm.value.signEnable
|
||||||
|
|
||||||
currentNode.value.showText = showText
|
currentNode.value.showText = showText
|
||||||
settingVisible.value = false
|
settingVisible.value = false
|
||||||
|
@ -919,6 +936,8 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
|
||||||
configForm.value.taskCompleteListenerPath = node.taskCompleteListener!.path
|
configForm.value.taskCompleteListenerPath = node.taskCompleteListener!.path
|
||||||
configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? []
|
configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? []
|
||||||
configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? []
|
configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? []
|
||||||
|
// 6. 签名
|
||||||
|
configForm.value.signEnable = node.signEnable ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
|
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
|
||||||
|
@ -1032,14 +1051,14 @@ function useTimeoutHandler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addTaskListenerMap = (arr) => {
|
const addTaskListenerParam = (arr) => {
|
||||||
arr.push({
|
arr.push({
|
||||||
key: '',
|
key: '',
|
||||||
type: 1,
|
type: 1,
|
||||||
value: ''
|
value: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const deleteTaskListenerMap = (arr, index) => {
|
const deleteTaskListenerParam = (arr, index) => {
|
||||||
arr.splice(index, 1)
|
arr.splice(index, 1)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,242 @@
|
||||||
|
<template>
|
||||||
|
<el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
|
||||||
|
<el-form-item label="配置方式" prop="conditionType">
|
||||||
|
<el-radio-group v-model="condition.conditionType">
|
||||||
|
<el-radio
|
||||||
|
v-for="(dict, indexConditionType) in conditionConfigTypes"
|
||||||
|
:key="indexConditionType"
|
||||||
|
:value="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="condition.conditionType === ConditionType.EXPRESSION"
|
||||||
|
label="条件表达式"
|
||||||
|
prop="conditionExpression"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="condition.conditionExpression"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="condition.conditionType === ConditionType.RULE" label="条件规则">
|
||||||
|
<div class="condition-group-tool">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="mr-4">条件组关系</div>
|
||||||
|
<el-switch
|
||||||
|
v-model="condition.conditionGroups.and"
|
||||||
|
inline-prompt
|
||||||
|
active-text="且"
|
||||||
|
inactive-text="或"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'">
|
||||||
|
<el-card
|
||||||
|
class="condition-group"
|
||||||
|
style="width: 530px"
|
||||||
|
v-for="(equation, cIdx) in condition.conditionGroups.conditions"
|
||||||
|
:key="cIdx"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="condition-group-delete"
|
||||||
|
v-if="condition.conditionGroups.conditions.length > 1"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
color="#0089ff"
|
||||||
|
icon="ep:circle-close-filled"
|
||||||
|
:size="18"
|
||||||
|
@click="deleteConditionGroup(condition.conditionGroups.conditions, cIdx)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>条件组</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="mr-4">规则关系</div>
|
||||||
|
<el-switch
|
||||||
|
v-model="equation.and"
|
||||||
|
inline-prompt
|
||||||
|
active-text="且"
|
||||||
|
inactive-text="或"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-select style="width: 160px" v-model="rule.leftSide">
|
||||||
|
<el-option
|
||||||
|
v-for="(field, fIdx) in fieldOptions"
|
||||||
|
:key="fIdx"
|
||||||
|
:label="field.title"
|
||||||
|
:value="field.field"
|
||||||
|
:disabled="!field.required"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-select v-model="rule.opCode" style="width: 100px">
|
||||||
|
<el-option
|
||||||
|
v-for="operator in COMPARISON_OPERATORS"
|
||||||
|
:key="operator.value"
|
||||||
|
:label="operator.label"
|
||||||
|
:value="operator.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mr-2">
|
||||||
|
<el-input v-model="rule.rightSide" style="width: 160px" />
|
||||||
|
</div>
|
||||||
|
<div class="mr-1 flex items-center" v-if="equation.rules.length > 1">
|
||||||
|
<Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Icon icon="ep:plus" :size="18" @click="addConditionRule(equation, rIdx)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-space>
|
||||||
|
<div title="添加条件组" class="mt-4 cursor-pointer">
|
||||||
|
<Icon
|
||||||
|
color="#0089ff"
|
||||||
|
icon="ep:plus"
|
||||||
|
:size="24"
|
||||||
|
@click="addConditionGroup(condition.conditionGroups.conditions)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
CONDITION_CONFIG_TYPES,
|
||||||
|
COMPARISON_OPERATORS,
|
||||||
|
ConditionType,
|
||||||
|
ProcessVariableEnum
|
||||||
|
} from '../../consts'
|
||||||
|
import { BpmModelFormType } from '@/utils/constants'
|
||||||
|
import { useFormFields } from '../../node'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const condition = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const formType = inject<Ref<number>>('formType') // 表单类型
|
||||||
|
const conditionConfigTypes = computed(() => {
|
||||||
|
return CONDITION_CONFIG_TYPES.filter((item) => {
|
||||||
|
// 业务表单暂时去掉条件规则选项
|
||||||
|
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/** 条件规则可选择的表单字段 */
|
||||||
|
const fieldOptions = computed(() => {
|
||||||
|
const fieldsCopy = useFormFields().slice()
|
||||||
|
// 固定添加发起人 ID 字段
|
||||||
|
fieldsCopy.unshift({
|
||||||
|
field: ProcessVariableEnum.START_USER_ID,
|
||||||
|
title: '发起人',
|
||||||
|
required: true
|
||||||
|
})
|
||||||
|
return fieldsCopy
|
||||||
|
})
|
||||||
|
// 表单校验规则
|
||||||
|
const formRules = reactive({
|
||||||
|
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
|
||||||
|
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
const deleteConditionGroup = (conditions, index) => {
|
||||||
|
conditions.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteConditionRule = (condition, index) => {
|
||||||
|
condition.rules.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addConditionRule = (condition, index) => {
|
||||||
|
const rule = {
|
||||||
|
type: 1,
|
||||||
|
opName: '等于',
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
condition.rules.splice(index + 1, 0, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addConditionGroup = (conditions) => {
|
||||||
|
const condition = {
|
||||||
|
and: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: 1,
|
||||||
|
opName: '等于',
|
||||||
|
opCode: '==',
|
||||||
|
leftSide: '',
|
||||||
|
rightSide: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
conditions.push(condition)
|
||||||
|
}
|
||||||
|
</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>
|
|
@ -128,7 +128,8 @@ const setSimpleModelNodeTaskStatus = (
|
||||||
if (
|
if (
|
||||||
simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
|
simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
|
||||||
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
|
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
|
||||||
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE
|
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE ||
|
||||||
|
simpleModel.type === NodeType.ROUTE_BRANCH_NODE
|
||||||
) {
|
) {
|
||||||
// 网关节点。只有通过和未执行状态
|
// 网关节点。只有通过和未执行状态
|
||||||
if (finishedActivityIds.includes(simpleModel.id)) {
|
if (finishedActivityIds.includes(simpleModel.id)) {
|
||||||
|
|
Loading…
Reference in New Issue