瀏覽代碼

fix: BsUi组件-组织选择控件

hanxiaohui 5 月之前
父節點
當前提交
ff57a7723d

+ 13 - 5
src/components/BsUi/OrgUserSelector/DEMO.vue

@@ -1,14 +1,18 @@
 <template>
   <div>
     <a-form name="form">
-      <a-form-item label="组织人员单选选择器" name="userSelector">
+      <a-form-item label="人员单选选择器" name="userSelector">
         <OrgUserSelector  v-model:selected-data="userSelector" :multiple="SELECT_MULTIPLE.ONE" />
       </a-form-item>
-
-      <a-form-item label="组织人员多选选择器" name="userSelectors">
+      <a-form-item label="人员多选选择器" name="userSelectors">
         <OrgUserSelector v-model:selected-data="userSelectors" :multiple="SELECT_MULTIPLE.MORE" />
       </a-form-item>
-
+      <a-form-item label="组织单选选择器" name="orgSelector">
+        <OrgUserSelector  v-model:selected-data="orgSelector" :multiple="SELECT_MULTIPLE.ONE"  :scene-type="SCENE_TYPE.ORG"/>
+      </a-form-item>
+      <a-form-item label="组织多选选择器" name="orgSelectors">
+        <OrgUserSelector v-model:selected-data="orgSelectors" :multiple="SELECT_MULTIPLE.MORE"  :scene-type="SCENE_TYPE.ORG"/>
+      </a-form-item>
     </a-form>
   </div>
 </template>
@@ -16,7 +20,7 @@
 <script setup>
 import OrgUserSelector from '/@/components/BsUi/OrgUserSelector/index.vue';
 import { ref } from 'vue';
-import { SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+import {SCENE_TYPE, SELECT_MULTIPLE} from '/@/components/BsUi/constant.js';
 const userSelectors = ref([
   {
     name: '韩晓辉0',
@@ -49,5 +53,9 @@ const userSelector = ref({
   nodeType: "USER"
 });
 
+const orgSelector = ref({});
+
+const orgSelectors = ref([]);
+
 
 </script>

+ 27 - 8
src/components/BsUi/OrgUserSelector/components/ModalSelector.vue

@@ -9,11 +9,16 @@
   >
     <div class="content">
       <a-input type="text" placeholder="请输入关键字" v-model:value="keyWord" :allow-clear="true" />
+
+      <a-breadcrumb separator=">">
+        <a-breadcrumb-item v-for="(item, idx) in breadcrumbPaths" :key="idx">{{ item }}</a-breadcrumb-item>
+      </a-breadcrumb>
+
       <div class="content-bottom">
         <div class="cb-content">
           <div class="cb-c-top">
             <div class="tree" v-show="isEmpty(keyWord)">
-              <org-tree ref="treeRef"  />
+              <org-tree ref="treeRef" />
             </div>
             <org-user-list
               :keyWord="keyWord"
@@ -38,6 +43,7 @@
               :multiple="multiple"
               @reset="handleReset"
               @remove="handleRemove"
+              :scene-type="sceneType"
             />
           </div>
         </div>
@@ -50,13 +56,15 @@
   import OrgTree from '/@/components/BsUi/OrgUserSelector/components/OrgTree.vue';
   import OrgUserList from '/@/components/BsUi/OrgUserSelector/components/OrgUserList.vue';
   import SelectedData from '/@/components/BsUi/OrgUserSelector/components/SelectedData.vue';
-  import {computed, nextTick, onMounted, ref, watch} from 'vue';
+  import { computed, nextTick, onMounted, ref, watch } from 'vue';
   import { isEmpty } from 'lodash';
-  import {SCENE_TYPE, SELECT_MULTIPLE} from '/@/components/BsUi/constant.js';
+  import { SCENE_TYPE, SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+  import { getPaths } from '/@/components/BsUi/uitl.js';
 
   const treeRef = ref(null);
   const emit = defineEmits(['change', 'ok']);
 
+
   const props = defineProps({
     sceneType: {
       required: false,
@@ -80,8 +88,8 @@
     },
     selectedData: {
       required: true,
-      default: undefined
-    }
+      default: undefined,
+    },
   });
 
   const {
@@ -108,7 +116,6 @@
   const keyWord = ref('');
   const orgUserListRef = ref(null);
 
-
   const handleRemove = (node) => {
     orgUserListRef.value.handleRemove(node);
   };
@@ -130,12 +137,17 @@
 
   const showModal = () => {
     setMVal('width', '1200px');
-    setMVal('title', '人员');
+
+    if (props.sceneType === SCENE_TYPE.USER) {
+      setMVal('title', '人员');
+    } else if (props.sceneType === SCENE_TYPE.ORG) {
+      setMVal('title', '组织');
+    }
     setMVal('visible', true);
     // setMVal('modalExtraProps.okButtonProps.disabled', true);
 
     nextTick(() => {
-      keyWord.value = "";
+      keyWord.value = '';
     });
   };
 
@@ -162,6 +174,13 @@
     (val) => {}
   );
 
+  const breadcrumbPaths = computed(() => {
+    if (treeRef.value) {
+      return treeRef.value?.breadcrumb;
+    }
+    return null;
+  });
+
   defineExpose({
     showModal,
     orgUserListRef,

+ 35 - 7
src/components/BsUi/OrgUserSelector/components/OrgUserList.vue

@@ -24,6 +24,11 @@
               <span>({{ searchData.length }})</span>
             </span>
 
+            <span style="font-size: 12px" v-if="sceneType === SCENE_TYPE.ORG">
+              <span>组织</span>
+              <span>({{ searchData.length }})</span>
+            </span>
+
             <span style="font-size: 12px" v-if="sceneType === SCENE_TYPE.USER">
               <span>人员</span>
               <span>({{ searchData.length }})</span>
@@ -44,7 +49,9 @@
             :checked="allSelectedKeys.findIndex((v) => v === item[idKey]) > -1"
             :disabled="disabled"
           ></a-radio>
-          <a-avatar style="color: #fff; background-color: #1677ff">{{ item[labelKey] && item[labelKey][0] }}</a-avatar>
+          <a-avatar :style="{ color: '#fff', backgroundColor: sceneType === SCENE_TYPE.USER ? '#1677ff' : '#ff4d4f' }">{{
+            item[labelKey] && item[labelKey][0]
+          }}</a-avatar>
           <div class="bot-item">
             <span class="bot-item-top">{{ item[labelKey] }}</span>
             <span class="bot-item-bottom">{{ item.parentName }}</span>
@@ -66,7 +73,7 @@
 </template>
 
 <script setup>
-  import { cloneDeep, isArray, isEmpty } from 'lodash';
+  import { cloneDeep, debounce, isArray, isEmpty } from 'lodash';
   import { queryListByParams } from '/@/api/system/table-api.js';
   import BsEmpty from '/@/components/BsUi/Empty/index.vue';
   import { computed, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
@@ -190,7 +197,7 @@
 
     const params = {
       include: false,
-      nodeType: [props.sceneType],
+      nodeTypes: [props.sceneType],
       ...data,
     };
 
@@ -235,15 +242,36 @@
     });
   };
 
+  // 定义防抖函数 - 关键:在 watch 外部创建防抖实例
+  const debouncedSearch = debounce(
+    async (query) => {
+      if(!isEmpty(query)) {
+        await init(false, {
+          keyword: query,
+          include: false,
+        });
+      } else {
+        await init(false, {
+          keyword: query,
+          include: false,
+          parentId: props.selectedTreeId
+        });
+      }
+    },
+    1000, // 防抖延迟300ms
+    {
+      leading: false, // 首次不立即执行
+      trailing: true, // 延迟结束后执行
+      maxWait: 2000, // 最多等待1秒
+    }
+  );
+
   watch(
     () => props.keyWord,
     (value) => {
       // 监听关键字变化
       isInclude.value = false;
-      init(false, {
-        keyword: value,
-        include: false,
-      });
+      debouncedSearch(value)
     }
   );
   watch(

+ 6 - 2
src/components/BsUi/OrgUserSelector/components/SelectedData.vue

@@ -14,7 +14,7 @@
       <div class="selected-user-item" v-for="(user, idx) in selectedData" :key="user[idKey]">
         <div class="selected-user-icon">
           <CloseCircleOutlined class="close-icon" @click="handleDel(user)"/>
-          <a-avatar shape="square" style="color: #fff; background-color: #1677ff">{{ user[labelKey] && user[labelKey][0] }}</a-avatar>
+          <a-avatar shape="square" :style="{color: '#fff', backgroundColor: sceneType === SCENE_TYPE.USER ? '#1677ff' : '#ff4d4f' }">{{ user[labelKey] && user[labelKey][0] }}</a-avatar>
         </div>
         <div class="title">{{ user[labelKey] }}</div>
       </div>
@@ -24,7 +24,7 @@
 
 <script setup>
   import { UserOutlined, CloseCircleOutlined } from '@ant-design/icons-vue';
-  import { SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+  import {SCENE_TYPE, SELECT_MULTIPLE} from '/@/components/BsUi/constant.js';
   import { ref } from 'vue';
 
   const isView = ref(true);
@@ -52,6 +52,10 @@
       required: false,
       default: 'name',
     },
+    sceneType: {
+      required: false,
+      default: SCENE_TYPE.USER
+    }
   });
 
   const handleReset = () => {

+ 9 - 3
src/components/BsUi/OrgUserSelector/index.vue

@@ -22,7 +22,8 @@
       </a-select>
       <a-button @click="handleClkSelector">
         <template #icon>
-          <UserOutlined />
+          <UserOutlined  v-if="sceneType === SCENE_TYPE.USER" />
+          <TeamOutlined v-if="sceneType === SCENE_TYPE.ORG" />
         </template>
       </a-button>
     </div>
@@ -35,16 +36,17 @@
       @change="selectChange"
       @ok="confirm"
       :selected-data="selectedData"
+      :scene-type="sceneType"
     />
   </div>
 </template>
 
 <script setup>
-  import { SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+import {SCENE_TYPE, SELECT_MULTIPLE} from '/@/components/BsUi/constant.js';
   import ModalSelector from '/@/components/BsUi/OrgUserSelector/components/ModalSelector.vue';
   import { onMounted, ref, watch } from 'vue';
   import { isArray, isEmpty } from 'lodash';
-  import { UserOutlined } from '@ant-design/icons-vue';
+  import { UserOutlined, TeamOutlined } from '@ant-design/icons-vue';
 
   const oldSelectedData = ref([]);
   const selVal = ref([]);
@@ -71,6 +73,10 @@
       required: false,
       default: 'name',
     },
+    sceneType: {
+      required: false,
+      default: SCENE_TYPE.USER
+    }
   });
   const emit = defineEmits(['update:selectedData']);
 

+ 4 - 3
src/components/BsUi/uitl.js

@@ -25,8 +25,9 @@ export function formatPercentage(num, decimalPlaces = 2) {
   return `${percentage}%`;
 }
 
-export const getPathName = (params) => {
-  const { sourceTreeData, labelKey = 'name', idKey = 'id', parentIdKey = 'parentId', childId, joinName = '/' } = params;
+export const getPaths = (params) => {
+
+  const { sourceTreeData, labelKey = 'name', idKey = 'id', parentIdKey = 'parentId', childId } = params;
 
   // 构建映射表
   const map = new Map();
@@ -43,7 +44,7 @@ export const getPathName = (params) => {
       if (node[parentIdKey] === '0') break; // 到达根节点,终止
       currentId = node[parentIdKey]; // 切换到父节点
     }
-    return path.join(joinName); // 拼接为路径字符串
+    return path; // 拼接为路径字符串
   }
 
   return getPath(childId);