【Simple设计器】流程模型->基本信息->谁可以发起,支持指定多个部门

指定部门可以在部门新增成员后无需重新修改相关流程

# Conflicts:
#	src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
#	src/views/bpm/model/form/index.vue
This commit is contained in:
郑威 2025-03-17 15:33:11 +08:00
parent 41f2c7199e
commit 1b3cbfc881
8 changed files with 279 additions and 22 deletions

View File

@ -0,0 +1,120 @@
<template>
<Dialog v-model="dialogVisible" title="部门选择" width="600">
<el-row v-loading="formLoading">
<el-col :span="24">
<ContentWrap class="h-1/1">
<el-tree
ref="treeRef"
:data="deptTree"
:props="defaultProps"
show-checkbox
:check-strictly="checkStrictly"
check-on-click-node
default-expand-all
highlight-current
node-key="id"
@check="handleCheck"
/>
</ContentWrap>
</el-col>
</el-row>
<template #footer>
<el-button
:disabled="formLoading || !selectedDeptIds?.length"
type="primary"
@click="submitForm"
>
</el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { defaultProps, handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept'
defineOptions({ name: 'DeptSelectForm' })
const emit = defineEmits<{
confirm: [deptList: any[]]
}>()
const { t } = useI18n() //
const message = useMessage() //
const props = defineProps({
//
checkStrictly: {
type: Boolean,
default: false
},
//
multiple: {
type: Boolean,
default: true
}
})
const treeRef = ref()
const deptTree = ref<Tree[]>([]) //
const selectedDeptIds = ref<number[]>([]) // ID
const dialogVisible = ref(false) //
const formLoading = ref(false) //
/** 打开弹窗 */
const open = async (selectedList?: DeptApi.DeptVO[]) => {
resetForm()
formLoading.value = true
try {
//
const deptData = await DeptApi.getSimpleDeptList()
deptTree.value = handleTree(deptData)
} finally {
formLoading.value = false
}
dialogVisible.value = true
//
if (selectedList?.length) {
await nextTick()
const selectedIds = selectedList.map(dept => dept.id).filter((id): id is number => id !== undefined)
selectedDeptIds.value = selectedIds
treeRef.value?.setCheckedKeys(selectedIds)
}
}
/** 处理选中状态变化 */
const handleCheck = (data: any, checked: any) => {
selectedDeptIds.value = treeRef.value.getCheckedKeys()
if (!props.multiple && selectedDeptIds.value.length > 1) {
//
const lastSelectedId = selectedDeptIds.value[selectedDeptIds.value.length - 1]
selectedDeptIds.value = [lastSelectedId]
treeRef.value.setCheckedKeys([lastSelectedId])
}
}
/** 提交选择 */
const submitForm = async () => {
try {
//
const checkedNodes = treeRef.value.getCheckedNodes()
message.success(t('common.updateSuccess'))
dialogVisible.value = false
emit('confirm', checkedNodes)
} finally {
}
}
/** 重置表单 */
const resetForm = () => {
deptTree.value = []
selectedDeptIds.value = []
if (treeRef.value) {
treeRef.value.setCheckedKeys([])
}
}
defineExpose({ open }) // open
</script>

View File

@ -59,6 +59,11 @@ const props = defineProps({
startUserIds: { startUserIds: {
type: Array, type: Array,
required: false required: false
},
//
startDeptIds: {
type: Array,
required: false
} }
}) })
@ -82,6 +87,7 @@ provide('deptList', deptOptions)
provide('userGroupList', userGroupOptions) provide('userGroupList', userGroupOptions)
provide('deptTree', deptTreeOptions) provide('deptTree', deptTreeOptions)
provide('startUserIds', props.startUserIds) provide('startUserIds', props.startUserIds)
provide('startDeptIds', props.startDeptIds)
provide('tasks', []) provide('tasks', [])
provide('processInstance', {}) provide('processInstance', {})
const message = useMessage() // const message = useMessage() //

View File

@ -25,8 +25,9 @@
</template> </template>
<el-tabs type="border-card" v-model="activeTabName"> <el-tabs type="border-card" v-model="activeTabName">
<el-tab-pane label="权限" name="user"> <el-tab-pane label="权限" name="user">
<el-text v-if="!startUserIds || startUserIds.length === 0"> 全部成员可以发起流程 </el-text> <el-text v-if="(!startUserIds || startUserIds.length === 0) && (!startDeptIds || startDeptIds.length === 0)"> 全部成员可以发起流程 </el-text>
<el-text v-else-if="startUserIds.length == 1"> <div v-else-if="startUserIds && startUserIds.length > 0">
<el-text v-if="startUserIds.length == 1">
{{ getUserNicknames(startUserIds) }} 可发起流程 {{ getUserNicknames(startUserIds) }} 可发起流程
</el-text> </el-text>
<el-text v-else> <el-text v-else>
@ -36,10 +37,26 @@
placement="top" placement="top"
:content="getUserNicknames(startUserIds)" :content="getUserNicknames(startUserIds)"
> >
{{ getUserNicknames(startUserIds.slice(0, 2)) }} {{ getUserNicknames(startUserIds.slice(0,2)) }} {{ startUserIds.length }} 人可发起流程
{{ startUserIds.length }} 人可发起流程
</el-tooltip> </el-tooltip>
</el-text> </el-text>
</div>
<div v-else-if="startDeptIds && startDeptIds.length > 0">
<el-text v-if="startDeptIds.length == 1">
{{ getDeptNames(startDeptIds) }} 可发起流程
</el-text>
<el-text v-else>
<el-tooltip
class="box-item"
effect="dark"
placement="top"
:content="getDeptNames(startDeptIds)"
>
{{ getDeptNames(startDeptIds.slice(0,2)) }} {{ startDeptIds.length }} 个部门的人可发起流程
</el-tooltip>
</el-text>
</div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10"> <el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
<div class="field-setting-pane"> <div class="field-setting-pane">
@ -107,6 +124,7 @@
import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts' import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node' import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import * as DeptApi from '@/api/system/dept'
defineOptions({ defineOptions({
name: 'StartUserNodeConfig' name: 'StartUserNodeConfig'
}) })
@ -118,8 +136,12 @@ const props = defineProps({
}) })
// //
const startUserIds = inject<Ref<any[]>>('startUserIds') const startUserIds = inject<Ref<any[]>>('startUserIds')
//
const startDeptIds = inject<Ref<any[]>>('startDeptIds')
// //
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') const userOptions = inject<Ref<UserApi.UserVO[]>>('userList')
//
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList')
// //
const { settingVisible, closeDrawer, openDrawer } = useDrawer() const { settingVisible, closeDrawer, openDrawer } = useDrawer()
// //
@ -145,6 +167,19 @@ const getUserNicknames = (userIds: number[]): string => {
}) })
return nicknames.join(',') return nicknames.join(',')
} }
const getDeptNames = (deptIds: number[]): string => {
if (!deptIds || deptIds.length === 0) {
return ''
}
const deptNames: string[] = []
deptIds.forEach((deptId) => {
const found = deptOptions?.value.find((item) => item.id === deptId)
if (found && found.name) {
deptNames.push(found.name)
}
})
return deptNames.join(',')
}
// //
const saveConfig = async () => { const saveConfig = async () => {
activeTabName.value = 'user' activeTabName.value = 'user'

View File

@ -97,10 +97,23 @@
</el-table-column> </el-table-column>
<el-table-column label="可见范围" prop="startUserIds" min-width="150"> <el-table-column label="可见范围" prop="startUserIds" min-width="150">
<template #default="{ row }"> <template #default="{ row }">
<el-text v-if="!row.startUsers?.length"> 全部可见 </el-text> <el-text v-if="!row.startUsers?.length && !row.startDepts?.length"> 全部可见 </el-text>
<el-text v-else-if="row.startUsers.length === 1"> <el-text v-else-if="row.startUsers.length === 1">
{{ row.startUsers[0].nickname }} {{ row.startUsers[0].nickname }}
</el-text> </el-text>
<el-text v-else-if="row.startDepts?.length === 1">
{{ row.startDepts[0].name }}
</el-text>
<el-text v-else-if="row.startDepts?.length > 1">
<el-tooltip
class="box-item"
effect="dark"
placement="top"
:content="row.startDepts.map((dept: any) => dept.name).join('、')"
>
{{ row.startDepts[0].name }} {{ row.startDepts.length }} 个部门可见
</el-tooltip>
</el-text>
<el-text v-else> <el-text v-else>
<el-tooltip <el-tooltip
class="box-item" class="box-item"

View File

@ -77,6 +77,7 @@
> >
<el-option label="全员" :value="0" /> <el-option label="全员" :value="0" />
<el-option label="指定人员" :value="1" /> <el-option label="指定人员" :value="1" />
<el-option label="指定部门" :value="2" />
</el-select> </el-select>
<div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2"> <div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
<div <div
@ -99,6 +100,24 @@
<Icon icon="ep:plus" /> 选择人员 <Icon icon="ep:plus" /> 选择人员
</el-button> </el-button>
</div> </div>
<div v-if="modelData.startUserType === 2" class="mt-2 flex flex-wrap gap-2">
<div
v-for="dept in selectedStartDepts"
:key="dept.id"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<Icon icon="ep:office-building" class="!m-5px text-20px" />
{{ dept.name }}
<Icon
icon="ep:close"
class="ml-2 cursor-pointer hover:text-red-500"
@click="handleRemoveStartDept(dept)"
/>
</div>
<el-button type="primary" link @click="openStartDeptSelect">
<Icon icon="ep:plus" /> 选择部门
</el-button>
</div>
</el-form-item> </el-form-item>
<el-form-item label="流程管理员" prop="managerUserIds" class="mb-20px"> <el-form-item label="流程管理员" prop="managerUserIds" class="mb-20px">
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@ -127,11 +146,19 @@
<!-- 用户选择弹窗 --> <!-- 用户选择弹窗 -->
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" /> <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
<!-- 部门选择弹窗 -->
<DeptSelectForm
ref="deptSelectFormRef"
:multiple="true"
:check-strictly="true"
@confirm="handleDeptSelectConfirm"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
import { UserVO } from '@/api/system/user' import { UserVO } from '@/api/system/user'
import { DeptVO } from '@/api/system/dept'
import { CategoryVO } from '@/api/bpm/category' import { CategoryVO } from '@/api/bpm/category'
const props = defineProps({ const props = defineProps({
@ -142,13 +169,19 @@ const props = defineProps({
userList: { userList: {
type: Array, type: Array,
required: true required: true
},
deptList: {
type: Array,
required: true
} }
}) })
const formRef = ref() const formRef = ref()
const selectedStartUsers = ref<UserVO[]>([]) const selectedStartUsers = ref<UserVO[]>([])
const selectedStartDepts = ref<DeptVO[]>([])
const selectedManagerUsers = ref<UserVO[]>([]) const selectedManagerUsers = ref<UserVO[]>([])
const userSelectFormRef = ref() const userSelectFormRef = ref()
const deptSelectFormRef = ref()
const currentSelectType = ref<'start' | 'manager'>('start') const currentSelectType = ref<'start' | 'manager'>('start')
const rules = { const rules = {
@ -174,6 +207,13 @@ watch(
} else { } else {
selectedStartUsers.value = [] selectedStartUsers.value = []
} }
if (newVal.startDeptIds?.length) {
selectedStartDepts.value = props.deptList.filter((dept: DeptVO) =>
newVal.startDeptIds.includes(dept.id)
) as DeptVO[]
} else {
selectedStartDepts.value = []
}
if (newVal.managerUserIds?.length) { if (newVal.managerUserIds?.length) {
selectedManagerUsers.value = props.userList.filter((user: UserVO) => selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
newVal.managerUserIds.includes(user.id) newVal.managerUserIds.includes(user.id)
@ -193,6 +233,11 @@ const openStartUserSelect = () => {
userSelectFormRef.value.open(0, selectedStartUsers.value) userSelectFormRef.value.open(0, selectedStartUsers.value)
} }
/** 打开部门选择 */
const openStartDeptSelect = () => {
deptSelectFormRef.value.open(selectedStartDepts.value)
}
/** 打开管理员选择 */ /** 打开管理员选择 */
const openManagerUserSelect = () => { const openManagerUserSelect = () => {
currentSelectType.value = 'manager' currentSelectType.value = 'manager'
@ -214,9 +259,28 @@ const handleUserSelectConfirm = (_, users: UserVO[]) => {
} }
} }
/** 处理部门选择确认 */
const handleDeptSelectConfirm = (depts: DeptVO[]) => {
modelData.value = {
...modelData.value,
startDeptIds: depts.map((d) => d.id)
}
}
/** 处理发起人类型变化 */ /** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: number) => { const handleStartUserTypeChange = (value: number) => {
if (value !== 1) { if (value === 0) {
modelData.value = {
...modelData.value,
startUserIds: [],
startDeptIds: []
}
} else if (value === 1) {
modelData.value = {
...modelData.value,
startDeptIds: []
}
} else if (value === 2) {
modelData.value = { modelData.value = {
...modelData.value, ...modelData.value,
startUserIds: [] startUserIds: []
@ -232,6 +296,14 @@ const handleRemoveStartUser = (user: UserVO) => {
} }
} }
/** 移除部门 */
const handleRemoveStartDept = (dept: DeptVO) => {
modelData.value = {
...modelData.value,
startDeptIds: modelData.value.startDeptIds.filter((id: number) => id !== dept.id)
}
}
/** 移除管理员 */ /** 移除管理员 */
const handleRemoveManagerUser = (user: UserVO) => { const handleRemoveManagerUser = (user: UserVO) => {
modelData.value = { modelData.value = {

View File

@ -18,6 +18,7 @@
:model-key="modelData.key" :model-key="modelData.key"
:model-name="modelData.name" :model-name="modelData.name"
:start-user-ids="modelData.startUserIds" :start-user-ids="modelData.startUserIds"
:start-dept-ids="modelData.startDeptIds"
@success="handleDesignSuccess" @success="handleDesignSuccess"
/> />
</template> </template>

View File

@ -62,6 +62,7 @@
v-model="formData" v-model="formData"
:categoryList="categoryList" :categoryList="categoryList"
:userList="userList" :userList="userList"
:deptList="deptList"
ref="basicInfoRef" ref="basicInfoRef"
/> />
</div> </div>
@ -92,6 +93,8 @@ import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form' import * as FormApi from '@/api/bpm/form'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import * as DeptApi from '@/api/system/dept'
import { useUserStoreWithOut } from '@/store/modules/user'
import * as DefinitionApi from '@/api/bpm/definition' import * as DefinitionApi from '@/api/bpm/definition'
import { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants' import { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
import BasicInfo from './BasicInfo.vue' import BasicInfo from './BasicInfo.vue'
@ -153,6 +156,7 @@ const formData: any = ref({
visible: true, visible: true,
startUserType: undefined, startUserType: undefined,
startUserIds: [], startUserIds: [],
startDeptIds: [],
managerUserIds: [], managerUserIds: [],
allowCancelRunningProcess: true, allowCancelRunningProcess: true,
processIdRule: { processIdRule: {
@ -183,6 +187,7 @@ provide('modelData', formData)
const formList = ref([]) const formList = ref([])
const categoryList = ref<CategoryVO[]>([]) const categoryList = ref<CategoryVO[]>([])
const userList = ref<UserApi.UserVO[]>([]) const userList = ref<UserApi.UserVO[]>([])
const deptList = ref<DeptApi.DeptVO[]>([])
/** 初始化数据 */ /** 初始化数据 */
const actionType = route.params.type as string const actionType = route.params.type as string
@ -200,14 +205,15 @@ const initData = async () => {
data.simpleModel = JSON.parse(data.simpleModel) data.simpleModel = JSON.parse(data.simpleModel)
} }
formData.value = data formData.value = data
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0 formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : formData.value?.startDeptIds?.length > 0 ? 2 : 0
} else if (['update', 'copy'].includes(actionType)) { } else if (['update', 'copy'].includes(actionType)) {
// / // /
const modelId = route.params.id as string const modelId = route.params.id as string
formData.value = await ModelApi.getModel(modelId) formData.value = await ModelApi.getModel(modelId)
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0 formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : formData.value?.startDeptIds?.length > 0 ? 2 : 0
//
if (actionType === 'copy') { //
if (route.params.type === 'copy') {
delete formData.value.id delete formData.value.id
formData.value.name += '副本' formData.value.name += '副本'
formData.value.key += '_copy' formData.value.key += '_copy'
@ -225,6 +231,8 @@ const initData = async () => {
categoryList.value = await CategoryApi.getCategorySimpleList() categoryList.value = await CategoryApi.getCategorySimpleList()
// //
userList.value = await UserApi.getSimpleUserList() userList.value = await UserApi.getSimpleUserList()
//
deptList.value = await DeptApi.getSimpleDeptList()
// currentStep // currentStep
currentStep.value = 0 currentStep.value = 0

View File

@ -6,6 +6,7 @@
:model-name="modelName" :model-name="modelName"
@success="handleSuccess" @success="handleSuccess"
:start-user-ids="startUserIds" :start-user-ids="startUserIds"
:start-dept-ids="startDeptIds"
ref="designerRef" ref="designerRef"
/> />
</ContentWrap> </ContentWrap>
@ -22,6 +23,7 @@ defineProps<{
modelKey?: string modelKey?: string
modelName?: string modelName?: string
startUserIds?: number[] startUserIds?: number[]
startDeptIds?: number[]
}>() }>()
const emit = defineEmits(['success']) const emit = defineEmits(['success'])