Parcourir la source

[Feat 0000] 级联选择器实现方式重构,支持v-model控制及store同步两种模式

houzekong il y a 3 mois
Parent
commit
bf2dbc819f

+ 83 - 99
src/components/Form/src/jeecg/components/MineCascader/MineCascader.vue

@@ -21,116 +21,100 @@
 </template>
 
 <script lang="ts">
-import { last } from 'lodash';
-import { defineComponent, onUnmounted, h, ref, computed, watchEffect } from 'vue';
-// import { useMessage } from '/@/hooks/web/useMessage';
-import { propTypes } from '/@/utils/propTypes';
-import { useMineDepartmentStore } from '/@/store/modules/mine';
-import { storeToRefs } from 'pinia';
-import { Cascader } from 'ant-design-vue';
+  import { last } from 'lodash';
+  import { defineComponent, ref, watch } from 'vue';
+  // import { useMessage } from '/@/hooks/web/useMessage';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useMineDepartmentStore } from '/@/store/modules/mine';
+  import { Cascader } from 'ant-design-vue';
 
-export default defineComponent({
-  name: 'MineCascader',
-  components: { Cascader },
-  props: {
-    value: propTypes.string.def(''),
-    placeholder: propTypes.string.def('全部'),
-    /** 根节点ID,如果传入,组件将过滤该节点下的节点 */
-    rootId: propTypes.string,
-    showSearch: propTypes.bool.def(true),
-    allowClear: propTypes.bool.def(true),
-    changeOnSelect: propTypes.bool.def(true),
-    // clearOnDestroy: propTypes.bool.def(false),
-    mineCode: propTypes.string,//URL传入的矿井ID
-    showDefaultValue: propTypes.bool.def(false) //是否需要显示默认值
-  },
-  emits: ['change', 'update:value'],
-  setup(props, { emit }) {
-    // const { createMessage } = useMessage();
-    const mineStore = useMineDepartmentStore();
-    const { getDepartTree, getMineCode, } = storeToRefs(mineStore);
+  /**
+   * 矿区级联选择器,该组件会根据配置从store中获取初始数据
+   *
+   * 组件数据流梳理:初始化数据及选项 -> 更新数据 -> 同步STORE -> 计算组件内依赖
+   *
+   * 本组件在初始化后不再监听STORE中的数据变化,因为这样可能导致用户通过Tabs切换时出现数据混乱
+   */
+  export default defineComponent({
+    name: 'MineCascader',
+    components: { Cascader },
+    props: {
+      value: propTypes.string.def(''),
+      placeholder: propTypes.string.def('全部'),
+      /** 根节点ID,如果传入,组件将过滤该节点下的节点 */
+      rootId: propTypes.string,
+      /** 是否从已存储的信息中初始化组件值 */
+      initFromStore: propTypes.bool.def(true),
+      /** 是否从将值同步至STORE */
+      syncToStore: propTypes.bool.def(true),
+      showSearch: propTypes.bool.def(true),
+      allowClear: propTypes.bool.def(true),
+      changeOnSelect: propTypes.bool.def(true),
+      // clearOnDestroy: propTypes.bool.def(false),
+    },
+    emits: ['change', 'update:value'],
+    setup(props, { emit }) {
+      // const { createMessage } = useMessage();
+      const mineStore = useMineDepartmentStore();
+      const innerValue = ref<string[]>([]);
+      const options = ref(mineStore.getDepartTree);
 
-    // if (props.clearOnDestroy) {
-    //   const raw = getDepartId.value;
-    //   onUnmounted(() => {
-    //     mineStore.setDepartById(raw);
-    //   });
-    // }
+      // if (props.clearOnDestroy) {
+      //   const raw = getDepartId.value;
+      //   onUnmounted(() => {
+      //     mineStore.setDepartById(raw);
+      //   });
+      // }
 
-    if (props.rootId) {
-      mineStore.clearDepart();
-      mineStore.filterDepartTree((e) => e.parentId === props.rootId);
+      if (props.rootId) {
+        options.value = mineStore.filterDepartTree((e) => e.parentId === props.rootId);
+      }
 
-      onUnmounted(() => {
-        mineStore.restoreDepartTree();
-      });
-    }
+      /**
+       * change事件
+       * @param e
+       */
+      function handleChange(value: any[] = []) {
+        // const dep = mineStore.findDepartById(id, options.value);
+        const val = last(value) ? last(value) : mineStore.calcMineCodeByDepart(options.value);
 
-    // const shownText = computed(() => getDepart.value.departName || '-');
-    const innerValue = ref<string[]>([]);
-    // const innerValue = computed(() => getDepartPath.value.map((e) => e.id));
-    const options = getDepartTree;
+        emit('update:value', val);
+        emit('change', val);
+      }
 
-    //默认显示内容
-    function findNodeWithPath(tree, id, path = []) {
-      for (const node of tree) {
-        const currentPath: any = [...path, node];
-        if (node.id == id) {
-          return currentPath; // 返回包含目标节点及其祖先的数组
+      function handleWatch(id: string = '') {
+        // rootId提供了选项过滤功能,任何传入的值都应该检查
+        if (!mineStore.findDepartById(id, options.value)) {
+          console.warn('The value of MineCascader is not included in MineOptions');
+          innerValue.value = [];
+          return;
         }
-        if (node.childDepart && node.childDepart.length > 0) {
-          const result = findNodeWithPath(node.childDepart, id, currentPath)
-          if (result) {
-            return result.map(el => el.id);
-          }
+        if (props.syncToStore) {
+          mineStore.setDepartById(id);
         }
+
+        const path = mineStore.calcDepartPathById(id, options.value, (e) => e.id);
+        innerValue.value = path;
       }
-      return null;
-    }
-    const defaultValue = computed(() => {
-      if (props.mineCode) {
-        return findNodeWithPath(getDepartTree.value, props.mineCode)
+
+      // 如果从STORE里初始化数据需要触发一次watcher以初始化组件状态
+      if (props.initFromStore) {
+        handleWatch(mineStore.getDepartId);
+        // 为了让使用该组件的各个页面能够第一时间使用到该组件传递的值,手动触发一次emit
+        handleChange([mineStore.getDepartId]);
       } else {
-        const levelData = getDepartTree.value.filter(v => v.childDepart.length)
-        const firstLevel = levelData[0]['id']
-        const secondLevel = levelData[0]['childDepart'][0]['id']
-        const thirdLevel = levelData[0]['childDepart'][0]['childDepart'][0]['id']
-        return [firstLevel, secondLevel, thirdLevel]
+        handleWatch(props.value);
+        // 为了让使用该组件的各个页面能够第一时间使用到该组件传递的值,手动触发一次emit
+        handleChange([props.value]);
       }
-    });
-
-
-    /**
-     * change事件
-     * @param e
-     */
-    function handleChange(value: any[]) {
-      innerValue.value = value;
-      mineStore.setDepartById(last(value));
-      const val = getMineCode.value;
-      emit('update:value', val);
-      emit('change', val);
-    }
-
-
-
-    if (props.showDefaultValue) {
-      handleChange(defaultValue.value);
-    }  else {
-      handleChange([]);
-    }
 
-    onUnmounted(() => {
-      mineStore.clearDepart();
-    });
+      watch(() => props.value, handleWatch);
 
-    return {
-      innerValue,
-      options,
-      getDepartTree,
-      handleChange,
-      h,
-    };
-  },
-});
+      return {
+        innerValue,
+        options,
+        handleChange,
+      };
+    },
+  });
 </script>

+ 23 - 6
src/store/modules/mine.ts

@@ -3,12 +3,14 @@ import { ref, computed } from 'vue';
 
 import { getEnfMineTree } from '/@/api/sys/menu';
 import { getUserMinePermissionData } from '/@/components/Form/src/jeecg/components/MineCascader/mineData.api';
-import { findNode, findNodeAll, findPath, listToTree, treeToList } from '/@/utils/helper/treeHelper';
+import { findNode, findNodeAll, findPath, listToTree, treeToList, forEach } from '/@/utils/helper/treeHelper';
 import { isArray, isFunction, isNil } from 'lodash';
 
 export interface MineDepartment {
   /** 唯一标识 */
   id: string;
+  /** 原本的标识 */
+  rawid: string;
   /** 部门名称 */
   departName: string;
   /** 父部门ID */
@@ -19,6 +21,8 @@ export interface MineDepartment {
   childDepart: MineDepartment[];
   /** 传真,用作矿井编号(矿井编码) */
   fax: string | null;
+  /** 是否禁用,没有子节点的部门即禁用 */
+  disabled?: boolean;
 }
 
 const DEFAULT_CONFIG = {
@@ -101,14 +105,29 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
       ]);
 
       // 标记节点类型
-      r1.forEach((node) => (node.isLeaf = false)); // 非叶子节点
+      r1.forEach((node: any) => {
+        // node.id = ''
+        node.isLeaf = false;
+      }); // 非叶子节点
       r2.forEach((node) => {
+        node.rawid = node.id;
+        node.id = node.fax; // 使用矿井编码作为ID
         node.isLeaf = true; // 叶子节点(矿井)
-        node.id = node.fax || node.id; // 使用矿井编码作为ID
       });
 
       // 合并数据并转换为树形结构
       const tree = listToTree([...r1, ...r2], DEFAULT_CONFIG);
+      forEach(
+        tree,
+        (node) => {
+          // 跳过叶节点
+          if (node.isLeaf) return;
+          node.rawid = node.id;
+          node.id = calcMineCodeByDepart(node);
+          node.disabled = !Boolean(node.childDepart.length);
+        },
+        DEFAULT_CONFIG
+      );
 
       departTree.value = tree;
       // 深拷贝保存原始数据,用于过滤后恢复
@@ -164,10 +183,9 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
     }
     const target = depart.filter((e) => !isNil(e));
     const list = treeToList(target, DEFAULT_CONFIG);
-
     if (isArray(list)) {
       return list
-        .filter((item) => item.isLeaf && item.fax) // 只取叶子节点(矿井)且fax不为空
+        .filter((item) => item.isLeaf) // 只取叶子节点(矿井)且fax不为空
         .map((item) => item.fax!) // 提取矿井编码
         .join(','); // 用逗号分隔多个矿井编码
     }
@@ -208,7 +226,6 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
 
     const target = depart.filter((e) => !isNil(e));
     const node = findNode(target, (e) => e.id === id, DEFAULT_CONFIG);
-
     if (isNil(node)) return;
     return node;
   }