Pārlūkot izejas kodu

Merge pull request #139 from minivv/master

【Simple设计器】流程模型->基本信息->谁可以发起,支持指定多个部门
芋道源码 4 mēneši atpakaļ
vecāks
revīzija
c3ad1ec30a

+ 120 - 0
src/components/DeptSelectForm/index.vue

@@ -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>

+ 6 - 0
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue

@@ -59,6 +59,11 @@ const props = defineProps({
   startUserIds: {
     type: Array,
     required: false
+  },
+  // 可发起流程的部门编号
+  startDeptIds: {
+    type: Array,
+    required: false
   }
 })
 
@@ -82,6 +87,7 @@ provide('deptList', deptOptions)
 provide('userGroupList', userGroupOptions)
 provide('deptTree', deptTreeOptions)
 provide('startUserIds', props.startUserIds)
+provide('startDeptIds', props.startDeptIds)
 provide('tasks', [])
 provide('processInstance', {})
 const message = useMessage() // 国际化

+ 50 - 15
src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue

@@ -25,21 +25,38 @@
     </template>
     <el-tabs type="border-card" v-model="activeTabName">
       <el-tab-pane label="权限" name="user">
-        <el-text v-if="!startUserIds || startUserIds.length === 0"> 全部成员可以发起流程 </el-text>
-        <el-text v-else-if="startUserIds.length == 1">
-          {{ getUserNicknames(startUserIds) }} 可发起流程
-        </el-text>
-        <el-text v-else>
-          <el-tooltip
-            class="box-item"
-            effect="dark"
-            placement="top"
-            :content="getUserNicknames(startUserIds)"
-          >
-            {{ getUserNicknames(startUserIds.slice(0, 2)) }} 等
-            {{ startUserIds.length }} 人可发起流程
-          </el-tooltip>
-        </el-text>
+        <el-text v-if="(!startUserIds || startUserIds.length === 0) && (!startDeptIds || startDeptIds.length === 0)"> 全部成员可以发起流程 </el-text>
+        <div v-else-if="startUserIds && startUserIds.length > 0">
+          <el-text v-if="startUserIds.length == 1">
+            {{ getUserNicknames(startUserIds) }} 可发起流程
+          </el-text>
+          <el-text v-else>
+            <el-tooltip
+              class="box-item"
+              effect="dark"
+              placement="top"
+              :content="getUserNicknames(startUserIds)"
+            >
+              {{ getUserNicknames(startUserIds.slice(0,2)) }} 等 {{ startUserIds.length }} 人可发起流程
+            </el-tooltip>
+          </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 label="表单字段权限" name="fields" v-if="formType === 10">
         <div class="field-setting-pane">
@@ -107,6 +124,7 @@
 import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
 import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
 import * as UserApi from '@/api/system/user'
+import * as DeptApi from '@/api/system/dept'
 defineOptions({
   name: 'StartUserNodeConfig'
 })
@@ -118,8 +136,12 @@ const props = defineProps({
 })
 // 可发起流程的用户编号
 const startUserIds = inject<Ref<any[]>>('startUserIds')
+// 可发起流程的部门编号
+const startDeptIds = inject<Ref<any[]>>('startDeptIds')
 // 用户列表
 const userOptions = inject<Ref<UserApi.UserVO[]>>('userList')
+// 部门列表
+const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList')
 // 抽屉配置
 const { settingVisible, closeDrawer, openDrawer } = useDrawer()
 // 当前节点
@@ -145,6 +167,19 @@ const getUserNicknames = (userIds: number[]): string => {
   })
   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 () => {
   activeTabName.value = 'user'

+ 14 - 1
src/views/bpm/model/CategoryDraggableModel.vue

@@ -97,10 +97,23 @@
         </el-table-column>
         <el-table-column label="可见范围" prop="startUserIds" min-width="150">
           <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">
               {{ row.startUsers[0].nickname }}
             </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-tooltip
                 class="box-item"

+ 73 - 1
src/views/bpm/model/form/BasicInfo.vue

@@ -77,6 +77,7 @@
       >
         <el-option label="全员" :value="0" />
         <el-option label="指定人员" :value="1" />
+        <el-option label="指定部门" :value="2" />
       </el-select>
       <div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
         <div
@@ -99,6 +100,24 @@
           <Icon icon="ep:plus" /> 选择人员
         </el-button>
       </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 label="流程管理员" prop="managerUserIds" class="mb-20px">
       <div class="flex flex-wrap gap-2">
@@ -127,11 +146,19 @@
 
   <!-- 用户选择弹窗 -->
   <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
+  <!-- 部门选择弹窗 -->
+  <DeptSelectForm
+    ref="deptSelectFormRef"
+    :multiple="true"
+    :check-strictly="true"
+    @confirm="handleDeptSelectConfirm"
+  />
 </template>
 
 <script lang="ts" setup>
 import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
 import { UserVO } from '@/api/system/user'
+import { DeptVO } from '@/api/system/dept'
 import { CategoryVO } from '@/api/bpm/category'
 
 const props = defineProps({
@@ -142,13 +169,19 @@ const props = defineProps({
   userList: {
     type: Array,
     required: true
+  },
+  deptList: {
+    type: Array,
+    required: true
   }
 })
 
 const formRef = ref()
 const selectedStartUsers = ref<UserVO[]>([])
+const selectedStartDepts = ref<DeptVO[]>([])
 const selectedManagerUsers = ref<UserVO[]>([])
 const userSelectFormRef = ref()
+const deptSelectFormRef = ref()
 const currentSelectType = ref<'start' | 'manager'>('start')
 
 const rules = {
@@ -174,6 +207,13 @@ watch(
     } else {
       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) {
       selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
         newVal.managerUserIds.includes(user.id)
@@ -193,6 +233,11 @@ const openStartUserSelect = () => {
   userSelectFormRef.value.open(0, selectedStartUsers.value)
 }
 
+/** 打开部门选择 */
+const openStartDeptSelect = () => {
+  deptSelectFormRef.value.open(selectedStartDepts.value)
+}
+
 /** 打开管理员选择 */
 const openManagerUserSelect = () => {
   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) => {
-  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,
       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) => {
   modelData.value = {

+ 1 - 0
src/views/bpm/model/form/ProcessDesign.vue

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

+ 13 - 5
src/views/bpm/model/form/index.vue

@@ -62,6 +62,7 @@
             v-model="formData"
             :categoryList="categoryList"
             :userList="userList"
+            :deptList="deptList"
             ref="basicInfoRef"
           />
         </div>
@@ -92,6 +93,8 @@ import * as ModelApi from '@/api/bpm/model'
 import * as FormApi from '@/api/bpm/form'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 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 { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
 import BasicInfo from './BasicInfo.vue'
@@ -153,6 +156,7 @@ const formData: any = ref({
   visible: true,
   startUserType: undefined,
   startUserIds: [],
+  startDeptIds: [],
   managerUserIds: [],
   allowCancelRunningProcess: true,
   processIdRule: {
@@ -183,6 +187,7 @@ provide('modelData', formData)
 const formList = ref([])
 const categoryList = ref<CategoryVO[]>([])
 const userList = ref<UserApi.UserVO[]>([])
+const deptList = ref<DeptApi.DeptVO[]>([])
 
 /** 初始化数据 */
 const actionType = route.params.type as string
@@ -200,14 +205,15 @@ const initData = async () => {
       data.simpleModel = JSON.parse(data.simpleModel)
     }
     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)) {
     // 情况二:修改场景/复制场景
     const modelId = route.params.id as string
     formData.value = await ModelApi.getModel(modelId)
-    formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
-    // 特殊:复制场景
-    if (actionType === 'copy') {
+    formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : formData.value?.startDeptIds?.length > 0 ? 2 : 0
+
+    // 复制场景
+    if (route.params.type === 'copy') {
       delete formData.value.id
       formData.value.name += '副本'
       formData.value.key += '_copy'
@@ -225,7 +231,9 @@ const initData = async () => {
   categoryList.value = await CategoryApi.getCategorySimpleList()
   // 获取用户列表
   userList.value = await UserApi.getSimpleUserList()
-
+  // 获取部门列表
+  deptList.value = await DeptApi.getSimpleDeptList()
+  
   // 最终,设置 currentStep 切换到第一步
   currentStep.value = 0
 

+ 2 - 0
src/views/bpm/simple/SimpleModelDesign.vue

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