Kaynağa Gözat

feat(oa): tree multi-select for dept condition values

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 ay önce
ebeveyn
işleme
6beb243604

+ 66 - 2
web-admin/src/components/oa-approval/flow-tree/ConditionRuleEditor.vue

@@ -82,6 +82,23 @@
               :value="p.id"
             />
           </el-select>
+          <el-tree-select
+            v-else-if="isDeptLikeField(rule.field)"
+            :model-value="personValueIds(rule.value)"
+            multiple
+            filterable
+            clearable
+            check-strictly
+            collapse-tags
+            collapse-tags-tooltip
+            default-expand-all
+            :data="deptTreeData"
+            :props="{ value: 'value', label: 'label', children: 'children' }"
+            placeholder="选择部门(任一命中)"
+            :disabled="disabled"
+            class="cond-rule-editor__dept-select"
+            @update:model-value="(v) => onValueChange(idx, Array.isArray(v) ? v.map(String) : [])"
+          />
           <el-select
             v-else-if="optionsFor(rule.field)?.length"
             :model-value="rule.value"
@@ -132,7 +149,7 @@
 <script setup lang="ts">
   import { computed, onMounted, ref, watch } from 'vue'
   import { Delete, Plus } from '@element-plus/icons-vue'
-  import { fetchPostList, fetchUserList } from '@/api/admin'
+  import { fetchDeptTree, fetchPostList, fetchUserList } from '@/api/admin'
   import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
   import {
     opsForFieldType,
@@ -166,6 +183,10 @@
   const userOptions = ref<Array<{ id: string; label: string }>>([])
   const postOptions = ref<Array<{ id: string; label: string }>>([])
 
+  type DeptTreeNode = { value: string; label: string; children?: DeptTreeNode[] }
+
+  const deptTreeData = ref<DeptTreeNode[]>([])
+
   function meta(field: string) {
     return props.candidates.find((c) => c.field === field)
   }
@@ -179,6 +200,19 @@
     return meta(field)?.type === 'post'
   }
 
+  function isDeptLikeField(field: string) {
+    const t = meta(field)?.type
+    return t === 'dept' || t === 'ownerDept'
+  }
+
+  function mapDeptTree(nodes: Api.Dept.DeptItem[]): DeptTreeNode[] {
+    return (nodes || []).map((n) => ({
+      value: String(n.id),
+      label: String(n.deptName || n.id),
+      children: n.children?.length ? mapDeptTree(n.children) : undefined
+    }))
+  }
+
   function personValueIds(v: unknown): string[] {
     if (Array.isArray(v)) return v.map(String)
     if (v == null || v === '') return []
@@ -186,7 +220,14 @@
   }
 
   function defaultValueForType(type: string, opts?: ConditionCandidate['options']) {
-    if (type === 'person' || type === 'ownerPerson' || type === 'post' || type === 'initiator') {
+    if (
+      type === 'person' ||
+      type === 'ownerPerson' ||
+      type === 'dept' ||
+      type === 'ownerDept' ||
+      type === 'post' ||
+      type === 'initiator'
+    ) {
       return [] as string[]
     }
     return opts?.[0]?.value ?? ''
@@ -336,6 +377,15 @@
     }
   }
 
+  async function loadDepts() {
+    try {
+      const tree = (await fetchDeptTree()) || []
+      deptTreeData.value = mapDeptTree(tree as Api.Dept.DeptItem[])
+    } catch {
+      deptTreeData.value = []
+    }
+  }
+
   onMounted(() => {
     if (props.candidates.some((c) => c.type === 'person' || c.type === 'ownerPerson' || c.type === 'initiator')) {
       loadUsers()
@@ -343,6 +393,9 @@
     if (props.candidates.some((c) => c.type === 'post')) {
       loadPosts()
     }
+    if (props.candidates.some((c) => c.type === 'dept' || c.type === 'ownerDept')) {
+      loadDepts()
+    }
   })
 
   watch(
@@ -361,6 +414,13 @@
       if (need && !postOptions.value.length) loadPosts()
     }
   )
+
+  watch(
+    () => props.candidates.some((c) => c.type === 'dept' || c.type === 'ownerDept'),
+    (need) => {
+      if (need && !deptTreeData.value.length) loadDepts()
+    }
+  )
 </script>
 
 <style scoped lang="scss">
@@ -404,4 +464,8 @@
   .cond-rule-editor__post-select :deep(.el-select__selection) {
     flex-wrap: wrap;
   }
+
+  .cond-rule-editor__dept-select :deep(.el-select__selection) {
+    flex-wrap: wrap;
+  }
 </style>