This commit is contained in:
clay
2024-03-04 19:13:43 +08:00
commit e44edd71c0
350 changed files with 52288 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
<template>
<el-button v-if="mode === 'design'" size="small" round class="add-branch-btn-el" @click="emit('addBranch')">{{value}}</el-button>
</template>
<script setup>
import {defineEmits,defineProps,computed} from "vue";
const emit = defineEmits()
const props = defineProps({
value:{
type:String,
default: ""
},
mode: {
type: String,
default: 'design'
}
})
const viewer = computed(() => {
return false;
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,200 @@
<template>
<node :title="config.name" :show-error="showError" :content="content"
:show-avatar="config.props.assignedType === 'ASSIGN_USER'" :user-info="assignedUser"
:error-info="errorInfo"
:select-user="selectUser"
:mode="mode"
@selected="emit('selected')" @delNode="emit('delNode')" @insertNode="type => emit('insertNode', type)"
placeholder="请设置审批人" :header-bgc="headerBgc" :header-icon="Stamp"/>
</template>
<script setup>
import Node from './Node.vue'
import {computed, defineExpose} from 'vue'
import {Stamp} from '@element-plus/icons-vue'
const emit = defineEmits(['insertNode', 'selected', 'delNode'])
const props = defineProps({
config: {
type: Object,
default: () => {
return {}
}
},
mode: {
type: String,
default: 'design'
}
})
const showError = ref(false)
const errorInfo = ref('')
const selectUser = computed(() => {
return {
show: props.config.props.assignedType === 'SELF_SELECT',
multiple: props.config.props.selfSelect.multiple
};
})
const assignedUser = computed(() => {
if (props.config.props.assignedType === 'SELF_SELECT') {
props.config.props.assignedUser = []
}
return props.config.props.assignedUser;
})
const headerBgc = computed(() => {
if (props.mode === 'design' || props.mode === 'view') {
return '#ff943e'
} else {
return props.config.props.headerBgc
}
})
const content = computed(() => {
const config = props.config.props
switch (config.assignedType) {
case "ASSIGN_USER":
if (config.assignedUser.length > 0) {
let texts = []
config.assignedUser.forEach(org => texts.push(org.name))
return String(texts).replaceAll(',', '、')
} else {
return '请指定审批人'
}
case "SELF":
return '发起人自己'
case "SELF_SELECT":
return config.selfSelect.multiple ? '发起人自选多人' : '发起人自选一人'
case "LEADER_TOP":
return '多级主管依次审批'
case "LEADER":
return config.leader.level > 1 ? '发起人的第 ' + config.leader.level + ' 级主管' : '发起人的直接主管'
case "FORM_USER":
if (!config.formUser || config.formUser === '') {
return '表单内联系人(未选择)'
} else {
// let text = getFormItemById(config.formUser)
if (text && text.title) {
return `表单(${text.title})内的人员`
} else {
return '该表单已被移除😥'
}
}
case "ROLE":
if (config.roleList.length > 0) {
return config.roleList.map(role => {
return role.roleName;
}).join("、")
} else {
return '指定角色(未设置)'
}
default:
return '未知设置项😥'
}
})
//校验数据配置的合法性
const validate = (err) => {
try {
console.log(props.config.props.assignedType)
switch (props.config.props.assignedType) {
case "ASSIGN_USER":
showError.value = !validate_ASSIGN_USER(err);
break;
case "SELF":
showError.value = !validate_SELF(err);
break;
case "SELF_SELECT":
showError.value = !validate_SELF_SELECT(err);
console.log(showError.value);
break;
case "LEADER_TOP":
showError.value = !validate_LEADER_TOP(err);
break;
case "LEADER":
showError.value = !validate_LEADER(err);
break;
case "FORM_USER":
showError.value = !validate_FORM_USER(err);
break;
case "ROLE":
showError.value = !validate_ROLE(err);
break;
default:
showError.value = true
err.push("未知设置项😥")
break;
}
if (props.config.props.nobody.handler === 'TO_USER' && props.config.props.nobody.assignedUser.length === 0) {
errorInfo.value = '审批人为空时, 转交给指定人员:【请指定一个具体的人】'
err.push('审批人为空时, 转交给指定人员:【请指定一个具体的人】')
showError.value = true
}
return showError
} catch (e) {
console.log(e)
return false;
}
}
const validate_ASSIGN_USER = (err) => {
if (props.config.props.assignedUser.length > 0) {
return true;
} else {
errorInfo.value = '请指定审批人员'
err.push(`${props.config.name} 未指定审批人员`)
return false
}
}
const validate_SELF_SELECT = (err) => {
// if (!this.viewer) {
// return true
// }
// let userInfo = this.$store.state.selectUserMap.get(this.config.id);
// if (undefined !== userInfo && Array.isArray(userInfo) && userInfo.length > 0) {
// return true;
// }
errorInfo.value = '请指定审批人员'
err.push(`${props.config.name} 未指定审批人员`)
return false;
}
const validate_LEADER_TOP = (err) => {
return true;
}
const validate_LEADER = (err) => {
return true;
}
const validate_ROLE = (err) => {
if (props.config.props.roleList.length <= 0) {
errorInfo.value = '请指定负责审批的系统角色'
err.push(`${props.config.name} 未指定审批角色`)
return false
}
return true;
}
const validate_SELF = (err) => {
return true;
}
const validate_FORM_USER = (err) => {
if (props.config.props.formUser === '') {
errorInfo.value = '请指定表单中的人员组件'
err.push(`${props.config.name} 审批人为表单中人员,但未指定`)
return false
}
return true;
}
const validate_REFUSE = (err) => {
return true;
}
defineExpose({
validate
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,75 @@
<template>
<node :title="config.name" :show-error="showError" :select-user="selectUser" :mode="mode" :content="content" :node-id="config.id"
:error-info="errorInfo" :show-avatar="true" :user-info="config.props.assignedUser"
@selected="emit('selected')" @delNode="emit('delNode')" @insertNode="type => emit('insertNode', type)"
placeholder="请设置抄送人" :header-bgc="headerBgc" :header-icon="Promotion"/>
</template>
<script setup>
import Node from './Node.vue'
import {defineProps, defineEmits, computed, defineExpose} from "vue";
const emit = defineEmits(['insertNode', 'selected', 'delNode'])
const props = defineProps({
config: {
type: Object,
default: () => {
return {}
}
},
mode: {
type: String,
default: 'design'
}
})
const showError = ref(false)
const errorInfo = ref('')
import {Promotion} from '@element-plus/icons-vue'
const headerBgc = computed(() => {
if (props.mode === 'design' || props.mode === 'view') {
return '#3296fa'
} else {
return props.config.props.headerBgc
}
})
const selectUser = computed(() => {
return {
show: props.config.props.assignedType !== 'ASSIGN_USER' && props.config.props.shouldAdd,
multiple: true
};
})
const content = computed(() => {
if (props.config.props.shouldAdd) {
return '由发起人指定'
} else if (props.config.props.assignedUser.length > 0) {
let texts = []
props.config.props.assignedUser.forEach(org => texts.push(org.name))
return String(texts).replaceAll(',', '、')
} else {
return null
}
})
//校验数据配置的合法性
const validate = (err) => {
showError.value = false
if (props.config.props.shouldAdd) {
showError.value = false
} else if (props.config.props.assignedUser.length === 0) {
showError.value = true
errorInfo.value = '请选择需要抄送的人员'
}
if (showError.value) {
err.push(`抄送节点 ${props.config.name} 未设置抄送人`)
}
return !showError.value
}
defineExpose({
validate
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,195 @@
<template>
<div class="node">
<!-- 并行分支选择后右侧出现操作面板,占时不需要 <div class="node-body" @click="emit('selected')">-->
<div v-if="designState" class="node-body" @click="emit('selected')">
<div class="node-body-left" @click.stop="emit('leftMove')" v-if="level > 1">
<el-icon>
<ArrowLeftBold/>
</el-icon>
</div>
<div class="node-body-main">
<div class="node-body-main-header">
<span class="title">
<el-icon :size="15">
<Operation/>
</el-icon>
<ellipsis class="name" hover-tip :content="config.name ? config.name:('并行任务' + level)"/>
</span>
<span class="option">
<el-tooltip effect="dark" content="复制分支" placement="top">
<el-icon @click.stop="emit('copy')" :size="20">
<CopyDocument/>
</el-icon>
</el-tooltip>
<el-icon @click.stop="emit('delNode')" :size="20">
<CloseBold/>
</el-icon>
</span>
</div>
<div class="node-body-main-content">
<span>并行任务同时进行</span>
</div>
</div>
<div class="node-body-right" @click.stop="emit('rightMove')" v-if="level < size && designState ">
<el-icon>
<ArrowRightBold/>
</el-icon>
</div>
</div>
<div class="node-footer">
<div class="btn" :style="(designState ? '' : 'height:0px')">
<insert-button v-if="designState" @insertNode="type => emit('insertNode', type)"></insert-button>
</div>
</div>
</div>
</template>
<!--并行节点-->
<script setup>
import InsertButton from '../common/InsertButton.vue'
import {Operation, ArrowRightBold, ArrowLeftBold, CopyDocument, CloseBold, Warning} from '@element-plus/icons-vue'
import Ellipsis from '../common/Ellipsis.vue'
import {defineProps, defineEmits} from "vue";
const emit = defineEmits(['insertNode'])
const props = defineProps({
config: {
type: Object,
default: () => {
return {}
}
},
level: {
type: Number,
default: 1
},
//条件数
size: {
type: Number,
default: 0
},
mode: {
type: String,
default: 'design'
}
})
const designState = computed(()=>{
return props.mode === 'design'
})
const designStart = () => {
// return this.$store.state.diagramMode === 'design'
return true;
}
</script>
<style lang="scss" scoped>
.node {
padding: 30px 55px 0;
//width: 220px;
width: 320px;
.node-body {
overflow: hidden;
cursor: pointer;
min-height: 80px;
max-height: 120px;
position: relative;
border-radius: 5px;
background-color: white;
box-shadow: 0px 0px 5px 0px #d8d8d8;
&:hover {
.node-body-left, .node-body-right {
i {
display: block !important;
}
}
.node-body-main {
.option {
display: inline-block !important;
}
}
box-shadow: 0px 0px 3px 0px;
}
.node-body-left, .node-body-right {
display: flex;
align-items: center;
position: absolute;
height: 100%;
i {
display: none;
}
&:hover {
background-color: #ececec;
}
}
.node-body-left {
color: #888888;
left: 0;
}
.node-body-right {
color: #888888;
right: 0;
}
.node-body-main {
position: absolute;
width: 188px;
left: 17px;
display: inline-block;
.node-body-main-header {
padding: 10px 0px 5px;
font-size: xx-small;
position: relative;
.title {
color: #718dff;
.name {
display: inline-block;
height: 14px;
width: 130px;
margin-left: 2px;
}
}
.option {
position: absolute;
right: 10px;
display: none;
font-size: medium;
i {
color: #888888;
padding: 0 3px;
}
}
}
.node-body-main-content {
padding: 6px;
color: #656363;
font-size: 14px;
i {
position: absolute;
top: 55%;
right: 10px;
font-size: medium;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,299 @@
<template>
<div :class="{'node': true, 'node-error-state': showError}">
<div :class="{'node-body': true, 'error': showError}">
<div class="node-body-left" @click="emit('leftMove')" v-if="level > 1 && designState">
<el-icon>
<ArrowLeftBold/>
</el-icon>
</div>
<div class="node-body-main" @click="emit('selected')">
<div class="node-body-main-header">
<ellipsis class="title" hover-tip :content="config.name ? config.name : ('条件' + level)"/>
<span class="level">优先级{{ level }}</span>
<span class="option" v-if="designState">
<el-tooltip effect="dark" content="复制条件" placement="top">
<el-icon @click.stop="emit('copy')" :size="20">
<CopyDocument/>
</el-icon>
</el-tooltip>
<el-icon @click.stop="emit('delNode')" :size="20">
<CloseBold/>
</el-icon>
</span>
</div>
<div class="node-body-main-content">
<span class="placeholder" v-if="(content || '').trim() === ''">{{ placeholder }}</span>
<ellipsis hoverTip :row="4" :content="content" v-else/>
</div>
</div>
<div class="node-body-right" @click="emit('rightMove')" v-if="level < size && designState">
<el-icon>
<ArrowRightBold/>
</el-icon>
</div>
<div class="node-error" v-if="showError">
<el-tooltip effect="dark" :content="errorInfo" placement="top-start">
<el-icon>
<Warning/>
</el-icon>
</el-tooltip>
</div>
</div>
<div class="node-footer">
<div class="btn">
<insert-button v-if="designState" @insertNode="type => emit('insertNode', type)"></insert-button>
</div>
</div>
</div>
</template>
<script setup>
import InsertButton from '../common/InsertButton.vue'
import Ellipsis from '../common/Ellipsis.vue'
import {ValueType} from '@/views/workflow/form/ComponentsConfigExport.js'
import {ArrowRightBold,ArrowLeftBold,CopyDocument,CloseBold,Warning} from '@element-plus/icons-vue'
import {defineProps, defineEmits, defineExpose} from "vue";
const emit = defineEmits()
const props = defineProps({
config: {
type: Object,
default: () => {
return {}
}
},
//索引位置
level: {
type: Number,
default: 1
},
//条件数
size: {
type: Number,
default: 0
},
mode: {
type: String,
default: 'design'
}
})
const groupNames = ref(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']);
const placeholder = ref('请设置条件');
const errorInfo = ref('');
const showError = ref(false);
const content = computed(() => {
const groups = props.config.props.groups
let confitions = []
groups.forEach(group => {
let subConditions = []
group.conditions.forEach(subCondition => {
let subConditionStr = ''
switch (subCondition.valueType) {
case ValueType.dept:
case ValueType.user:
subConditionStr = `${subCondition.title}属于[${String(subCondition.value.map(u => u.name)).replaceAll(',', '. ')}]之一`
break;
case ValueType.number:
case ValueType.string:
subConditionStr = getOrdinaryConditionContent(subCondition)
break;
}
subConditions.push(subConditionStr)
})
//根据子条件关系构建描述
let subConditionsStr = String(subConditions)
.replaceAll(',', subConditions.length > 1 ?
(group.groupType === 'AND' ? ') 且 (' : ') 或 (') :
(group.groupType === 'AND' ? ' 且 ' : ' 或 '))
confitions.push(subConditions.length > 1 ? `(${subConditionsStr})` : subConditionsStr)
})
//构建最终描述
return String(confitions).replaceAll(',', (props.config.props.groupsType === 'AND' ? ' 且 ' : ' 或 '))
})
const designState = computed(()=>{
return props.mode === 'design'
})
const getDefault = (val, df) => {
return val && val !== '' ? val : df;
}
const getOrdinaryConditionContent = (subCondition) => {
switch (subCondition.compare) {
case 'IN':
return `${subCondition.title}为[${String(subCondition.value).replaceAll(',', '、')}]中之一`
case 'B':
return `${subCondition.value[0]} < ${subCondition.title} < ${subCondition.value[1]}`
case 'AB':
return `${subCondition.value[0]}${subCondition.title} < ${subCondition.value[1]}`
case 'BA':
return `${subCondition.value[0]} < ${subCondition.title}${subCondition.value[1]}`
case 'ABA':
return `${subCondition.value[0]}${subCondition.title}${subCondition.value[1]}`
case '<=':
return `${subCondition.title}${getDefault(subCondition.value[0], ' ?')}`
case '>=':
return `${subCondition.title}${getDefault(subCondition.value[0], ' ?')}`
default:
return `${subCondition.title}${subCondition.compare}${getDefault(subCondition.value[0], ' ?')}`
}
}
//校验数据配置的合法性
const validate = (err) => {
const defineProps = props.config.props
if (defineProps.groups.length <= 0){
showError.value = true
errorInfo.value = '请设置分支条件'
err.push(`${defineProps.config.name} 未设置条件`)
}else {
for (let i = 0; i < defineProps.groups.length; i++) {
if (defineProps.groups[i].cids.length === 0){
showError.value = true
errorInfo.value = `请设置条件组${groupNames.value[i]}内的条件`
err.push(`条件 ${props.config.name} 条件组${groupNames.value[i]}内未设置条件`)
break
}else {
let conditions = defineProps.groups[i].conditions
for (let ci = 0; ci < conditions.length; ci++) {
let subc = conditions[ci]
showError.value = subc.value.length === 0;
if (showError.value){
errorInfo.value = `请完善条件组${groupNames.value[i]}内的${subc.title}条件`
err.push(`条件 ${props.config.name} 条件组${groupNames.value[i]}${subc.title}条件未完善`)
return false
}
}
}
}
}
return !showError.value
}
defineExpose({
validate
})
</script>
<style lang="scss" scoped>
.node {
padding: 30px 55px 0;
//width: 220px;
width: 320px;
.node-body {
cursor: pointer;
min-height: 80px;
max-height: 120px;
position: relative;
border-radius: 5px;
background-color: white;
box-shadow: 0px 0px 5px 0px #d8d8d8;
&:hover {
.node-body-left, .node-body-right {
i {
display: block !important;
}
}
.node-body-main {
.level {
display: none !important;
}
.option {
display: inline-block !important;
}
}
box-shadow: 0px 0px 3px 0px;
}
.node-body-left, .node-body-right {
display: flex;
align-items: center;
position: absolute;
height: 100%;
i {
display: none;
}
&:hover {
background-color: #ececec;
}
}
.node-body-left {
color: #888888;
left: 0;
}
.node-body-right {
color: #888888;
right: 0;
top: 0;
}
.node-body-main {
//position: absolute;
width: 188px;
margin-left: 17px;
display: inline-block;
.node-body-main-header {
padding: 10px 0px 5px;
font-size: xx-small;
position: relative;
.title {
color: #15bca3;
display: inline-block;
height: 14px;
width: 125px;
}
.level {
position: absolute;
right: 15px;
color: #888888;
}
.option {
position: absolute;
right: 10px;
display: none;
font-size: medium;
i {
color: #888888;
padding: 0 3px;
}
}
}
.node-body-main-content {
padding: 6px;
color: #656363;
font-size: 14px;
i {
position: absolute;
top: 55%;
right: 10px;
font-size: medium;
}
.placeholder {
color: #8c8c8c;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<node :title="config.name" :mode="mode" :show-error="showError" :content="content" :error-info="errorInfo"
@selected="emit('selected')" @delNode="emit('delNode')" @insertNode="type => emit('insertNode', type)"
placeholder="请设置延时时间" :header-bgc="headerBgc" :header-icon="Clock"/>
</template>
<!--延时器节点-->
<script setup>
import Node from './Node.vue'
import {defineProps, defineEmits, computed, defineExpose} from "vue";
const emit = defineEmits()
import {Clock} from '@element-plus/icons-vue'
const props = defineProps({
config: {
type: Object,
default: () => {
return {}
}
},
mode: {
type: String,
default: 'design'
}
})
const showError = ref(false)
const errorInfo = ref('')
const headerBgc = computed(() => {
if (props.mode === 'design' || props.mode === 'view') {
return '#f25643'
} else {
return props.config.props.headerBgc
}
})
const content = computed(() => {
if (props.config.props.type === 'FIXED') {
return `等待 ${props.config.props.time} ${getName(props.config.props.unit)}`
} else if (props.config.props.type === 'AUTO') {
return `至当天 ${props.config.props.dateTime}`
} else {
return null
}
})
//校验数据配置的合法性
const validate = (err) => {
showError.value = false
try {
if (props.config.props.type === "AUTO") {
if ((props.config.props.dateTime || "") === "") {
showError.value = true
errorInfo.value = "请选择时间点"
}
} else {
if (props.config.props.time <= 0) {
showError.value = true
errorInfo.value = "请设置延时时长"
}
}
} catch (e) {
showError.value = true
errorInfo.value = "配置出现问题"
}
if (showError) {
err.push(`${props.config.name} 未设置延时规则`)
}
return !showError
}
const getName = (unit) => {
switch (unit) {
case 'D':
return '天';
case 'H':
return '小时';
case 'M':
return '分钟';
default:
return '未知';
}
}
defineExpose({
validate
})
</script>

View File

@@ -0,0 +1,20 @@
<template>
<node :show="false" :mode="mode" @insertNode="type => emit('insertNode', type)"/>
</template>
<script setup>
import Node from './Node.vue'
import {defineEmits,defineProps} from "vue";
const emit = defineEmits()
const props = defineProps({
mode: {
type: String,
default: 'design'
}
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,20 @@
<template>
<node :show="false" :merge="true" :mode="mode" @insertNode="type => emit('insertNode', type)"/>
</template>
<script setup>
import Node from './Node.vue'
import {defineEmits, defineProps} from "vue";
const emit = defineEmits()
const props = defineProps({
mode: {
type: String,
default: 'design'
}
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,269 @@
<template>
<div :class="{'node': true, 'root': isRoot || !show, 'node-error-state': showError}">
<div v-if="show" @click="emit('selected')" :class="{'node-body': true, 'error': showError}">
<div class="node-body-header" :style="{'background-color': headerBgc}">
<el-icon v-if="headerIcon" size="15">
<component :is="headerIcon"/>
</el-icon>
<ellipsis class="name" hover-tip :content="title"/>
<el-icon v-if="!isRoot && designState" size="15" style="float:right;" @click="emit('delNode')">
<Close/>
</el-icon>
</div>
<div class="node-body-content">
<el-icon v-if="leftIcon">
<component :is="leftIcon"/>
</el-icon>
<template v-if="selectUser.show && mode === 'view'">
<div class="avatar_button">
<avatar-ellipsis :row="3" v-if="userInfo.length > 0" :user-info="userInfo"/>
<el-button type="primary" :icon="Plus" circle/>
</div>
</template>
<template v-else-if="showAvatar">
<span class="placeholder" v-if="userInfo.length === 0">{{ placeholder }}</span>
<avatar-ellipsis :row="3" :user-info="userInfo" v-else/>
</template>
<template v-else>
<span class="placeholder" v-if="(content || '').trim() === ''">{{ placeholder }}</span>
<ellipsis :row="3" :content="content" v-else/>
</template>
</div>
<div class="node-error" v-if="showError">
<el-tooltip effect="dark" :content="errorInfo" placement="top-start">
<el-icon><Warning /></el-icon>
</el-tooltip>
</div>
</div>
<div class="node-footer">
<div v-if="merge" class="branch-merge">
<img data-v-1e7b1da5=""
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAABXlJREFUeF7tmm2IVGUUx3/nLkQUqxmkpAVhSPUllZX8EEGhvVgZQWCU6wcpsqi2mXtnoaBwoqLCufeO9oJYUJQiFZWlZPQCfYiIXkylstTyS1oWWeYWCe6cuLsz7t3r3Ll3du6907oz35bnPM9z/r895zznmWeECf6RCa6fDoBOBExwAp0UmOAB0CmCnRTopECbCZi2nivK1CMD7FpXlH+ydqctKZBz9QyjwkqFG4HzfKK/0gpvl/ulmBWIzAHkVmlRDFY2EihwwLFkRhYQxgzAtHW2CpcBc4CpOsg26eK7yVPYVFwu/9ZzPufq2VLhQBxhAk85lvSF2Y5l/3prjQlAvqR3IjwJTKqz6DeGssQuyLfBsbyjz6PcFgeAZyOwzLFk/QnrjHH/RACYtj6ncHuUCDWYXs7Lz367XEn3inB+1NzauMLGsiW3+u1b2b9lAH22LuiCD2IJED5yTbmiZpt3dAbKT7Hmjhjtdi25oPZnK/uH7dtUCpi2vqVwQ1wRWuHhWkW3StpTEb6IO3fIThhwTemuzWll/0QA5G39FTgrtghhs2vKELA7inra6d38HXvusOEnriWXHo+iFvZvGUDfap3VdYzdzQgIHmd5W7cBc2Ovoax1C3KXZ5/E/i3VgCQciNMD+J1Ug55yXjxo7QfgOdFKCvjyeL/C9MgoEBa5przrt0ti/+C+mRVB/8amrWsU7m2Ql/c5lqwJjre9CDZ1DMFnriXzw0SatvZW4FqBW6o2vwGvCGxyLPmw3rym9g8cwy0XQV8Ix2qEgH0C1ziWNCyceVu1uvZrriVLGqVGsainHO7mRyDynlCvEWupCI7Kxcat6BFg+OwW9ohyfSMIcQF44v/sZovAlRH1I7QVTwyAt1Cjy8hfh9hYvepGQogDoI74HYbSXzHwusTYl7FEAURVcdPWN+NAiAJQTzzCda4p+6N8iDPe1CkQZ8FAtY+E0AhA2uKHszTlT1QkhAHIQnwmAKr1IjQS6gHISnxmABpBCALIUnymAMIgKHxf6wMmH6E3cNTtSLLgZXoKNOgAR6UDyqyq7esKk3znfOriM48AXzc5AmGE1EFgWvXPTMS3DcAJ6TA6XDIT31YAoRCEc5JqcuKc8Kn3AVFO+E4BryvJVHzbI8BzwAdgi2vJ4ihgSY//nyIg8jqctPi2RYBp61wV5qsyR2BFVdhBhBe0wtdisNU15VAagoNrZh4BOVsfFcVCODVUoLBn6E2hIBvShpApgLyt3nvhRXFFCWxwLOmNaz8Wu8wA5G31ntQWNOukwD2OJc80Oy+ufSYAciVdJsJLcZ0K2nV1MbuUk51jnd9oXuoACmW9eHCQj49/T+j3RtiJslPgFxXmoVxe11nhPdeUq8clANPWPoXVQef9D6e1saEHVINHUBYF7Q1lnl2QL5OGkHoE5G19GRhVyBQ+L1tyST0xoa/Iygq3IOvGI4BdwIWjHBeecE15IExM3tYfgJmBOetcU2o9Q2IcsoiAw8Gf0qhyU7kgb4QCcPRTlNGvSspmtzD81J7kJ30AJX0fYeHo2seLjiXLG0TA78CZ/nGFB8uWPJakeG+t9AE4+jjK/XGLWtgTusBix5It4w5ArqRLRTjhl14IW40KD/kre66ki0R4p57IrgozS/2yb9wByDvqhbKX07Xv/oIavMfOP6pFb0pdgcqrbkFuTlp8JingbRIaBfEUHUOYltbtMPUaUNNo2rpeYWk8zSNWqvSmeSvMDIAnybT1boWnY0I4aigL7YJ4bXRqn0wBeCqG7gYVVqFcFapKWXt0gP5nizKQmvLqwpkDqAkaanmhB4MelOkI27XCDjHY7pqyN23htfXbBiArgVH7dABEETrZxzsRcLL/h6P0/Qc1qphfvB2K3wAAAABJRU5ErkJggg=="
alt="">
</div>
<div class="btn">
<insert-button v-if="designState" @insertNode="type => emit('insertNode', type)"/>
</div>
</div>
<!-- <user-picker v-if="selectUser.show" title="请选择系统用户" :multiple="selectUser.multiple" ref="userPicker"-->
<!-- :selected="_userInfo"-->
<!-- @ok="selectedUser"/>-->
</div>
</template>
<script setup>
import InsertButton from '../common/InsertButton.vue'
import Ellipsis from '../common/Ellipsis.vue'
import AvatarEllipsis from '../common/AvatarEllipsis.vue'
import {defineProps,defineEmits} from "vue";
const emit = defineEmits(['insertNode'])
import {Close,Warning,Plus} from '@element-plus/icons-vue'
const props = defineProps({
//是否为根节点
isRoot: {
type: Boolean,
default: false
},
nodeId:{
type:String,
default:()=>{
return "";
}
},
//是否显示节点体
show: {
type: Boolean,
default: true
},
//是否显示节点体
merge: {
type: Boolean,
default: false
},
//节点内容区域文字
content: {
type: String,
default: ""
},
//节点内容区域文字
userInfo: {
type: Array,
default() {
return []
}
},
//节点内容区域文字
showAvatar: {
type: Boolean,
default: false
},
selectUser: {
type: Object,
default() {
return {
show: false,
multiple: false,
}
}
},
title: {
type: String,
default: "标题"
},
placeholder: {
type: String,
default: "请设置"
},
//节点体左侧图标
leftIcon: {
type: Object,
default: null
},
//头部图标
headerIcon: {
type: String,
default: ''
},
//头部背景色
headerBgc: {
type: String,
default: '#576a95'
},
//是否显示错误状态
showError: {
type: Boolean,
default: false
},
errorInfo: {
type: String,
default: '无信息'
},
mode: {
type: String,
default: 'design'
}
})
const designState = computed(()=>{
return props.mode === 'design'
})
const init = () => {
// let userInfo = this.$store.state.selectUserMap.get(this.nodeId);
// if (userInfo){
// let userInfoList = []
// for (let val of userInfo) {
// let userInfo = {
// id: val.id,
// name: val.name,
// avatar: val.avatar,
// }
// userInfoList.push(userInfo)
// }
// // this._userInfo = userInfoList
// }
}
init()
</script>
<style lang="scss" scoped>
.root {
&:before {
display: none !important;
}
}
.node {
padding: 0 50px;
//width: 220px;
width: 320px;
position: relative;
&:before {
content: '';
position: absolute;
top: -12px;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
width: 0;
border-style: solid;
border-width: 8px 6px 4px;
border-color: #CACACA transparent transparent;
background: #F5F5F7;
}
.node-body {
cursor: pointer;
min-height: 63px;
position: relative;
border-radius: 5px;
background-color: white;
box-shadow: 0px 0px 5px 0px #d8d8d8;
&:hover {
box-shadow: 0px 0px 3px 0px;
.node-body-header {
.el-icon-close {
display: inline;
font-size: medium;
}
}
}
.node-body-header {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding: 5px 15px;
color: white;
font-size: xx-small;
.el-icon-close {
display: none;
}
.name {
height: 14px;
width: 150px;
display: inline-block
}
}
.node-body-content {
padding: 18px;
color: #656363;
font-size: 14px;
.avatar_button {
//float: left;
display: flex;
//flex: 1;
flex-wrap: wrap;
button {
margin-top: 3px;
height: 40px;
//flex-shrink: 0;
//flex-grow: 0;
}
}
i {
position: absolute;
top: 55%;
right: 5px;
font-size: medium;
}
.placeholder {
color: #8c8c8c;
}
}
}
}
</style>

View File

@@ -0,0 +1,3 @@
<template>
流程结束
</template>

View File

@@ -0,0 +1,48 @@
<template>
<node title="发起人" :is-root="true" :mode="mode" :content="content" show-avatar :user-info="config.props.assignedUser"
@selected="emit('selected')" @insertNode="type => emit('insertNode', type)"
placeholder="所有人" :header-bgc="config.props.headerBgc" :header-icon="UserFilled"/>
</template>
<script setup>
import Node from './Node.vue'
import {UserFilled} from '@element-plus/icons-vue'
import {defineExpose, defineProps} from "vue";
const emit = defineEmits(['insertNode','selected'])
const props = defineProps({
config:{
type: Object,
default: () => {
return {}
}
},
mode: {
type: String,
default: 'design'
}
})
const content = computed(() => {
if (props.config.props.assignedUser.length > 0){
let texts = []
props.config.props.assignedUser.forEach(org => texts.push(org.name))
return String(texts).replaceAll(',', '、')
} else {
return '所有人'
}
})
const validate = () => {
console.log("调用成功")
return []
}
defineExpose({
validate
})
</script>

View File

@@ -0,0 +1,96 @@
<template>
<node :title="config.name" :mode="mode" :show-error="showError" :content="content" :error-info="errorInfo"
@selected="emit('selected')" @delNode="emit('delNode')" @insertNode="type => emit('insertNode', type)"
placeholder="请设置触发器" :header-bgc="headerBgc" :header-icon="SetUp"/>
</template>
<!--触发器节点-->
<script setup>
import Node from './Node.vue'
const emit = defineEmits()
import {defineProps, defineEmits, defineExpose} from "vue";
import {SetUp} from '@element-plus/icons-vue'
const props = defineProps({
config: {
type: Object,
default: () => {
return {}
}
},
mode: {
type: String,
default: 'design'
}
})
const showError = ref(false)
const errorInfo = ref('')
const viewer = computed(() => {
return false;
})
const preview = computed(() => {
return false;
})
const headerBgc = computed(() => {
return '#47bc82'
// if (preview || !viewer) {
// return '#ff943e'
// } else {
// return props.config.props.headerBgc
// }
})
const content = computed(() => {
return '请设置触发器'
})
//校验数据配置的合法性
const validate = (err) => {
showError.value = false
if (props.config.props.type === 'WEBHOOK') {
if (isNotEmpty(props.config.props.http.url)) {
showError.value = false
} else {
showError.value = true
errorInfo.value = '请设置WEBHOOK的URL地址'
}
if (restfulCheck(props.config.props.http.url)) {
showError.value = false
} else {
showError.value = true
errorInfo.value = 'WEBHOOK的URL地址不符合RESTful标准'
}
} else if (props.config.props.type === 'EMAIL') {
if (!isNotEmpty(props.config.props.email.subject)
|| props.config.props.email.to.length === 0
|| !isNotEmpty(props.config.props.email.content)) {
showError.value = true
errorInfo.value = '请设置邮件发送配置'
} else {
showError.value = false
}
}
if (showError) {
err.push(`${props.config.name} 触发动作未设置完善`)
}
return !showError
}
//url规范性检查
const isNotEmpty = (obj) => {
return (obj !== undefined && obj !== null && obj !== '' && obj !== 'null')
}
const restfulCheck = (url) => {
const httpProtocolPattern = /^http:/;
const httpsProtocolPattern = /^https:/;
const restfulUrlPattern = /\/\w+\/\w+(\/\{[^}]+\})*/;
if (httpProtocolPattern.test(url) || httpsProtocolPattern.test(url)) {
return restfulUrlPattern.test(url);
} else {
return false;
}
}
defineExpose({
validate
})
</script>
<style scoped>
</style>