仿钉钉设计流程:增加抄送人节点

This commit is contained in:
jason 2024-04-05 13:04:12 +08:00
parent 4f839136c7
commit f79c29d168
10 changed files with 391 additions and 120 deletions

View File

@ -24,6 +24,12 @@
<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>
@ -86,9 +92,13 @@ const addType = (type) => {
data = {
name: '抄送人',
type: 2,
ccSelfSelectFlag: 1,
childNode: props.childNodeP,
nodeUserList: []
error: true,
//
attributes : {
candidateStrategy: undefined,
candidateParam: undefined
},
childNode: props.childNodeP
}
}
emits('update:childNodeP', data)

View File

@ -2,7 +2,6 @@
<el-drawer
:append-to-body="true"
v-model="visible"
class="set_promoter"
:show-close="false"
:size="550"
:before-close="saveConfig"
@ -165,12 +164,7 @@ 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'
defineProps({
directorMaxLevel: {
type: Number,
default: 0
}
})
const roleOptions = ref<RoleApi.RoleVO[]>([]) //
const postOptions = ref<PostApi.PostVO[]>([]) //
const userOptions = ref<UserApi.UserVO[]>([]) //
@ -183,7 +177,7 @@ const candidateConfig = ref({
})
let approverConfig = ref({})
let store = useWorkFlowStoreWithOut()
let { setApprover, setUserTaskConfig } = store
let { setApproverDrawer, setUserTaskConfig } = store
let approverConfig1 = computed(() => store.approverConfig1)
let approverDrawer = computed(() => store.approverDrawer)
const userTaskConfig = computed(() => store.userTaskConfig)
@ -233,32 +227,16 @@ const saveConfig = () => {
// flag: true,
// id: approverConfig1.value.id
// })
const showText = getApproverShowText()
setUserTaskConfig({
value: rawConfig.value,
flag: true,
id: userTaskConfig.value.id,
showText
})
console.log('after is userTaskConfig', userTaskConfig.value)
closeDrawer()
}
const getApproverShowText = () => {
let appoveMethodText = ''
approveMethods.forEach((item) => {
if (item.value == candidateConfig.value.approveMethod) {
appoveMethodText = item.label
}
})
const strategyText = getDictLabel(
DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY,
candidateConfig.value.candidateStrategy
)
return `审批方式:${appoveMethodText} <br/>
审批人规则类型按${strategyText}`
}
const closeDrawer = () => {
setApprover(false)
setApproverDrawer(false)
}
const changecandidateStrategy = () => {
candidateConfig.value.candidateParam = []

View File

@ -0,0 +1,246 @@
<template>
<el-drawer
:append-to-body="true"
v-model="visible"
:show-close="false"
:size="550"
:before-close="saveConfig"
>
<template #header>
<div class="copy-task-header">抄送人设置</div>
</template>
<div>
<el-form label-position="top" label-width="100px">
<el-form-item label="选择抄送人" prop="candidateStrategy">
<el-select v-model="candidateConfig.candidateStrategy" style="width: 100%" clearable @change="changecandidateStrategy">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="candidateConfig.candidateStrategy == 10"
label="指定角色"
prop="candidateParam"
>
<el-select
v-model="candidateConfig.candidateParam"
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="candidateConfig.candidateStrategy == 20 || candidateConfig.candidateStrategy == 21"
label="指定部门"
prop="candidateParam"
span="24"
>
<el-tree-select
ref="treeRef"
v-model="candidateConfig.candidateParam"
:data="deptTreeOptions"
:props="defaultProps"
empty-text="加载中,请稍后"
multiple
node-key="id"
style="width: 100%"
show-checkbox
/>
</el-form-item>
<el-form-item
v-if="candidateConfig.candidateStrategy == 22"
label="指定岗位"
prop="candidateParam"
span="24"
>
<el-select
v-model="candidateConfig.candidateParam"
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="
candidateConfig.candidateStrategy == 30 ||
candidateConfig.candidateStrategy == 31 ||
candidateConfig.candidateStrategy == 32
"
label="指定用户"
prop="candidateParam"
span="24"
>
<el-select
v-model="candidateConfig.candidateParam"
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="candidateConfig.candidateStrategy === 40"
label="指定用户组"
prop="candidateParam"
>
<el-select
v-model="candidateConfig.candidateParam"
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="candidateConfig.candidateStrategy === 60"
label="流程表达式"
prop="candidateParam"
>
<el-input
type="textarea"
v-model="candidateConfig.candidateParam[0]"
clearable
style="width: 100%"
/>
</el-form-item>
</el-form>
</div>
<div class="demo-drawer__footer clear">
<el-button type="primary" @click="saveConfig"> </el-button>
<el-button @click="closeDrawer"> </el-button>
</div>
</el-drawer>
</template>
<script lang="ts" setup>
import { ref, watch, computed, toRaw } from 'vue'
import { useWorkFlowStoreWithOut } from '@/store/modules/simpleWorkflow'
import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
import { defaultProps, 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'
const roleOptions = ref<RoleApi.RoleVO[]>([]) //
const postOptions = ref<PostApi.PostVO[]>([]) //
const userOptions = ref<UserApi.UserVO[]>([]) //
const deptTreeOptions = ref() //
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) //
const candidateConfig = ref({
candidateStrategy: undefined,
candidateParam: [],
})
const store = useWorkFlowStoreWithOut()
const { setCopyerDrawer, setCopyerConfig } = store
const copyerDrawer = computed(() => store.copyerDrawer)
const copyerConfig = computed(() => store.copyerConfig)
const visible = computed({
get() {
return copyerDrawer.value
},
set() {
closeDrawer()
}
})
watch(copyerConfig, (val) => {
if (val.value.attributes) {
console.log('val.value.attributes', val.value.attributes);
candidateConfig.value.candidateStrategy = val.value.attributes.candidateStrategy
const candidateParamStr = val.value.attributes.candidateParam;
if(val.value.attributes.candidateStrategy === 60) {
candidateConfig.value.candidateParam = [candidateParamStr]
} else {
if(candidateParamStr){
candidateConfig.value.candidateParam = candidateParamStr.split(',').map((item) => +item)
}
}
// candidateConfig.value = val.value.attributes
}
})
const saveConfig = () => {
const rawConfig = toRaw(copyerConfig.value)
const { candidateStrategy , candidateParam} = toRaw(candidateConfig.value);
const candidateParamStr = candidateParam.join(',')
rawConfig.value.attributes = {
candidateStrategy,
candidateParam: candidateParamStr
}
rawConfig.flag = true
// TODO
// setApproverConfig({
// value: approverConfig.value,
// flag: true,
// id: approverConfig1.value.id
// })
setCopyerConfig({
value: rawConfig.value,
flag: true,
id: copyerConfig.value.id,
})
console.log('after is copyerConfig', copyerConfig.value)
closeDrawer()
}
const closeDrawer = () => {
setCopyerDrawer(false)
}
const changecandidateStrategy = () => {
candidateConfig.value.candidateParam = []
}
onMounted(async () => {
//
roleOptions.value = await RoleApi.getSimpleRoleList()
postOptions.value = await PostApi.getSimplePostList()
//
userOptions.value = await UserApi.getSimpleUserList()
//
const deptOptions = await DeptApi.getSimpleDeptList()
deptTreeOptions.value = handleTree(deptOptions, 'id')
//
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
})
</script>
<style lang="scss" scoped>
.copy-task-header {
font-size: 16px !important;
}
</style>

View File

@ -22,12 +22,13 @@
v-if="isInput"
type="text"
class="ant-input editable-title-input"
@blur="blurEvent()"
@focus="$event.currentTarget.select()"
@blur="blurEvent(-1)"
@focus="$event.currentTarget?.select()"
v-mountedFoucs
v-model="nodeConfig.name"
:placeholder="defaultText"
/>
<span v-else class="editable-title" @click="clickEvent()">{{ nodeConfig.name }}</span>
<span v-else class="editable-title" @click="clickEvent(-1)">{{ nodeConfig.name }}</span>
<i class="anticon anticon-close close" @click="delNode"></i>
</template>
</div>
@ -60,7 +61,8 @@
type="text"
class="ant-input editable-title-input"
@blur="blurEvent(index)"
@focus="$event.currentTarget.select()"
@focus="$event.currentTarget?.select()"
v-mountedFoucs
v-model="item.name"
/>
<span v-else class="editable-title" @click="clickEvent(index)">{{
@ -135,9 +137,8 @@ let props = defineProps({
let defaultText = computed(() => {
return placeholderList[props.nodeConfig.type]
})
let isInputList = ref([])
let isInput = ref(false)
const isInputList = ref<boolean[]>([])
const isInput = ref<boolean>(false)
const resetConditionNodesErr = () => {
for (var i = 0; i < props.nodeConfig.conditionNodes.length; i++) {
// eslint-disable-next-line vue/no-mutating-props
@ -160,20 +161,20 @@ onMounted(() => {
let emits = defineEmits(['update:nodeConfig'])
let store = useWorkFlowStoreWithOut()
let {
setPromoter,
setApprover,
setCopyer,
setCondition,
setApproverDrawer,
setCopyerDrawer,
// setCondition,
setCopyerConfig,
setConditionsConfig,
// setConditionsConfig,
setUserTaskConfig
} = store
// ???
const isTried = computed(() => store.isTried)
//
const userTaskConfig = computed(() => store.userTaskConfig)
let isTried = computed(() => store.isTried)
let approverConfig1 = computed(() => store.approverConfig1)
let copyerConfig1 = computed(() => store.copyerConfig1)
let conditionsConfig1 = computed(() => store.conditionsConfig1)
//
const copyerConfig = computed(() => store.copyerConfig)
// let conditionsConfig1 = computed(() => store.conditionsConfig1)
const showText = computed(() => {
if (props.nodeConfig.type == 0) return '发起人'
if (props.nodeConfig.type == 1) {
@ -186,42 +187,49 @@ const showText = computed(() => {
return ''
}
}
return copyerStr(props.nodeConfig)
if(props.nodeConfig.type === 2) {
if(props.nodeConfig.attributes) {
return copyerStr( props.nodeConfig.attributes.candidateStrategy)
} else {
return ''
}
}
return ''
})
watch(userTaskConfig, (approver) => {
if (approver.flag && approver.id === _uid) {
emits('update:nodeConfig', approver.value)
}
})
watch(approverConfig1, (approver) => {
if (approver.flag && approver.id === _uid) {
emits('update:nodeConfig', approver.value)
}
})
watch(copyerConfig1, (copyer) => {
watch(copyerConfig, (copyer) => {
console.log('copyer',copyer)
if (copyer.flag && copyer.id === _uid) {
console.log('copyer id is equal',copyer)
emits('update:nodeConfig', copyer.value)
}
})
watch(conditionsConfig1, (condition) => {
if (condition.flag && condition.id === _uid) {
emits('update:nodeConfig', condition.value)
}
})
// watch(conditionsConfig1, (condition) => {
// if (condition.flag && condition.id === _uid) {
// emits('update:nodeConfig', condition.value)
// }
// })
const clickEvent = (index) => {
if (index || index === 0) {
if (index >= 0) {
isInputList.value[index] = true
} else {
isInput.value = true
}
}
const blurEvent = (index) => {
if (index || index === 0) {
if (index >= 0) {
isInputList.value[index] = false
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.conditionNodes[index].name =
props.nodeConfig.conditionNodes[index].name || '条件'
props.nodeConfig.conditionNodes[index].name || '条件'
} else {
isInput.value = false
// eslint-disable-next-line vue/no-mutating-props
@ -278,42 +286,45 @@ const reData = (data, addData) => {
}
}
const setPerson = (priorityLevel) => {
var { type } = props.nodeConfig
console.log('priorityLevel',priorityLevel)
const { type } = props.nodeConfig
console.log('type',type)
if (type == 0) {
setPromoter(true)
// setPromoter(true)
} else if (type == 1) {
setApprover(true)
let showText = undefined
if (_uid === userTaskConfig.value.id) {
showText = userTaskConfig.value.showText
}
setApproverDrawer(true)
// if (_uid === userTaskConfig.value.id) {
// showText = userTaskConfig.value.showText
// }
setUserTaskConfig({
value: {
...JSON.parse(JSON.stringify(props.nodeConfig)),
id: 'Activity_' + _uid
},
flag: false,
id: _uid,
showText
id: _uid
})
} else if (type == 2) {
setCopyer(true)
setCopyerDrawer(true)
setCopyerConfig({
value: JSON.parse(JSON.stringify(props.nodeConfig)),
value: {
...JSON.parse(JSON.stringify(props.nodeConfig)),
id: 'Activity_' + _uid
},
flag: false,
id: _uid
})
} else {
setCondition(true)
setConditionsConfig({
value: {
...JSON.parse(JSON.stringify(props.nodeConfig)),
id: 'Gateway_' + _uid
},
priorityLevel,
flag: false,
id: _uid
})
// setCondition(true)
// setConditionsConfig({
// value: {
// ...JSON.parse(JSON.stringify(props.nodeConfig)),
// id: 'Gateway_' + _uid
// },
// priorityLevel,
// flag: false,
// id: _uid
// })
}
}
// const arrTransfer = (index, type = 1) => {

View File

@ -80,13 +80,24 @@ export const getApproverShowText = (approveMethod :number, candidateStrategy: nu
}
}
export const copyerStr = (nodeConfig: any) => {
if (nodeConfig.nodeUserList.length != 0) {
return arrToStr(nodeConfig.nodeUserList)
export const copyerStr = ( candidateStrategy: number) => {
// if (nodeConfig.nodeUserList.length != 0) {
// return arrToStr(nodeConfig.nodeUserList)
// } else {
// if (nodeConfig.ccSelfSelectFlag == 1) {
// return '发起人自选'
// }
// }
console.log('candidateStrategy', candidateStrategy);
if(candidateStrategy) {
const strategyText = getDictLabel(
DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY,
candidateStrategy
)
return `抄送人类型:按${strategyText}`
} else {
if (nodeConfig.ccSelfSelectFlag == 1) {
return '发起人自选'
}
return ''
}
}
export const conditionStr = (nodeConfig, index) => {

View File

@ -1163,11 +1163,7 @@ html {
box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3)
}
.dingflow-design .auto-judge.active .close,
.dingflow-design .auto-judge:active .close,
.dingflow-design .auto-judge:hover .close {
display: block
}
.dingflow-design .auto-judge.error:after {
border: 1px solid #f25643;
@ -1183,6 +1179,7 @@ html {
line-height: 24px;
width: 258px;
} */
.dingflow-design .title-wrapper {
display: flex;
justify-content: space-between;
@ -1195,18 +1192,39 @@ html {
line-height: 24px;
width: 220px;
color: #fff;
padding-right: 10px;
padding-left: 10px;
}
.dingflow-design .title-wrapper .editable-title {
max-width: 120px;
padding-left: 10px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis
}
.dingflow-design .title-wrapper .close {
padding-right: 10px;
.dingflow-design .title-wrapper .close {
display: none;
position: absolute;
right: 10px;
top: 2px;
width: 20px;
height: 20px;
font-size: 14px;
color:#fff;
border-radius: 50%;
text-align: center;
line-height: 20px;
z-index: 2
}
.dingflow-design .title-wrapper:hover .close {
display: block
}
/* .dingflow-design .title-wrapper .close {
padding-right: 2px;
} */
/* .dingflow-design .title-wrapper .priority-title {
display: inline-block;
float: right;
@ -1233,21 +1251,6 @@ html {
color: #bfbfbf
}
.dingflow-design .auto-judge .close {
display: none;
position: absolute;
right: -10px;
top: -10px;
width: 20px;
height: 20px;
font-size: 14px;
color: rgba(0, 0, 0, .25);
border-radius: 50%;
text-align: center;
line-height: 20px;
z-index: 2
}
.dingflow-design .auto-judge .content {
font-size: 14px;
color: #191f25;

View File

@ -10,4 +10,16 @@ import { hasPermi } from './permission/hasPermi'
export const setupAuth = (app: App<Element>) => {
hasRole(app)
hasPermi(app)
}
/**
* v-mountedFoucs
*/
export const setupMountedFoucs= (app: App<Element>) => {
app.directive('mountedFoucs', {
mounted(el) {
el.focus();
}
})
}

View File

@ -28,8 +28,8 @@ import '@/plugins/animate.css'
// 路由
import router, { setupRouter } from '@/router'
// 权限
import { setupAuth } from '@/directives'
// 其它指令
import { setupAuth, setupMountedFoucs } from '@/directives'
import { createApp } from 'vue'
@ -60,6 +60,8 @@ const setupAll = async () => {
setupAuth(app)
setupMountedFoucs(app)
await router.isReady()
app.use(VueDOMPurifyHTML)

View File

@ -6,11 +6,10 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', {
tableId: '',
isTried: false,
promoterDrawer: false,
flowPermission1: {},
approverDrawer: false,
approverConfig1: {},
copyerDrawer: false,
copyerConfig1: {},
copyerConfig: {},
conditionDrawer: false,
conditionsConfig1: {
conditionNodes: []
@ -27,20 +26,17 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', {
setPromoter(payload) {
this.promoterDrawer = payload
},
setFlowPermission(payload) {
this.flowPermission1 = payload
},
setApprover(payload) {
setApproverDrawer(payload) {
this.approverDrawer = payload
},
setApproverConfig(payload) {
this.approverConfig1 = payload
},
setCopyer(payload) {
setCopyerDrawer(payload) {
this.copyerDrawer = payload
},
setCopyerConfig(payload) {
this.copyerConfig1 = payload
this.copyerConfig = payload
},
setCondition(payload) {
this.conditionDrawer = payload

View File

@ -24,10 +24,12 @@
</section>
</div>
<approverDrawer />
<copyerDrawer />
</template>
<script lang="ts" setup>
import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
import approverDrawer from '@/components/SimpleProcessDesigner/src/drawer/approverDrawer.vue'
import copyerDrawer from '@/components/SimpleProcessDesigner/src/drawer/copyerDrawer.vue'
import { WorkFlowNode } from '@/components/SimpleProcessDesigner/src/consts'
import { ref } from 'vue'
import { saveBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'