Ver Fonte

tree组件

qianlishi há 7 meses atrás
pai
commit
2abe3b22d6
28 ficheiros alterados com 483 adições e 17 exclusões
  1. 7 0
      Report-V3-TS/pnpm-lock.yaml
  2. 11 0
      Report-V3-TS/src/App.vue
  3. 16 0
      Report-V3-TS/src/api/access/accessAuthority.ts
  4. 8 0
      Report-V3-TS/src/api/login/index.ts
  5. 0 0
      Report-V3-TS/src/components/Base/Jsq-baseTable/index.ts
  6. 0 0
      Report-V3-TS/src/components/Base/Jsq-baseTable/src/Jsq-baseTable.vue
  7. 0 0
      Report-V3-TS/src/components/Base/Jsq-baseTable/src/props.ts
  8. 1 0
      Report-V3-TS/src/components/Base/Jsq-crud/index.ts
  9. 41 0
      Report-V3-TS/src/components/Base/Jsq-crud/src/Jsq-crud.vue
  10. 0 0
      Report-V3-TS/src/components/Base/Jsq-searchForm/index.ts
  11. 0 0
      Report-V3-TS/src/components/Base/Jsq-searchForm/src/Jsq-searchForm.vue
  12. 0 0
      Report-V3-TS/src/components/Base/Jsq-searchForm/src/props.ts
  13. 2 0
      Report-V3-TS/src/components/Base/Jsq-select/index.ts
  14. 60 0
      Report-V3-TS/src/components/Base/Jsq-select/src/Jsq-select.vue
  15. 50 0
      Report-V3-TS/src/components/Base/Jsq-select/src/hooks/useSelect.ts
  16. 17 0
      Report-V3-TS/src/components/Base/Jsq-select/src/props.ts
  17. 20 0
      Report-V3-TS/src/components/Base/Jsq-select/src/types/index.ts
  18. 2 0
      Report-V3-TS/src/components/Base/Jsq-tree/index.ts
  19. 64 0
      Report-V3-TS/src/components/Base/Jsq-tree/src/Jsq-tree.vue
  20. 54 0
      Report-V3-TS/src/components/Base/Jsq-tree/src/hooks/useTree.ts
  21. 8 0
      Report-V3-TS/src/components/Base/Jsq-tree/src/props.ts
  22. 14 0
      Report-V3-TS/src/components/Base/Jsq-tree/src/types/index.ts
  23. 1 0
      Report-V3-TS/src/enums/common.ts
  24. 1 0
      Report-V3-TS/src/utils/http/axios/index.ts
  25. 28 2
      Report-V3-TS/src/utils/index.ts
  26. 9 0
      Report-V3-TS/src/views/access/accessAuthority/components/test.vue
  27. 68 5
      Report-V3-TS/src/views/access/accessAuthority/index.vue
  28. 1 10
      Report-V3-TS/src/views/access/accessRole/index.vue

+ 7 - 0
Report-V3-TS/pnpm-lock.yaml

@@ -37,6 +37,9 @@ dependencies:
   element-resize-detector:
     specifier: ^1.2.4
     version: 1.2.4
+  js-md5:
+    specifier: ^0.8.3
+    version: 0.8.3
   lodash-es:
     specifier: ^4.17.21
     version: 4.17.21
@@ -4096,6 +4099,10 @@ packages:
     hasBin: true
     dev: true
 
+  /js-md5@0.8.3:
+    resolution: {integrity: sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==}
+    dev: false
+
   /js-stringify@1.0.2:
     resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
     dev: true

+ 11 - 0
Report-V3-TS/src/App.vue

@@ -25,6 +25,9 @@
   import { useRoute } from 'vue-router';
   import { useDesignSettingStore } from '@/store/modules/designSetting';
   import { lighten } from '@/utils/index';
+  import { getAllDict } from '@/api/login';
+  import { storage } from '@/utils/Storage';
+  import { GLOBAL_DICT_CODE_NAME } from '@/enums/common'
 
   const route = useRoute();
   const useScreenLock = useScreenLockStore();
@@ -73,7 +76,15 @@
     }, 1000);
   };
 
+  // 初始化字典项
+  const getAllDictData = async () => {
+    const { code, data } = await getAllDict();
+    if (code != 200) return;
+    storage.set(GLOBAL_DICT_CODE_NAME, data);
+  };
+
   onMounted(() => {
+    getAllDictData();
     document.addEventListener('mousedown', timekeeping);
   });
 

+ 16 - 0
Report-V3-TS/src/api/access/accessAuthority.ts

@@ -0,0 +1,16 @@
+/*
+ * @Description:
+ * @Author: qianlishi
+ * @Date: 2024-12-30 01:22:56
+ * @LastEditors: qianlishi
+ * @LastEditTime: 2024-12-30 01:23:21
+ */
+import { http } from '@/utils/http/axios';
+
+export function getAuthorityTree(data = {}) {
+  return http.request({
+    url: 'accessAuthority/menuTree',
+    method: 'get',
+    data,
+  });
+}

+ 8 - 0
Report-V3-TS/src/api/login/index.ts

@@ -23,3 +23,11 @@ export function reqUpdatePassword(data) {
     data,
   });
 }
+
+// 数字字典
+export function getAllDict() {
+  return http.request({
+    url: '/gaeaDict/all',
+    method: 'GET',
+  });
+}

+ 0 - 0
Report-V3-TS/src/components/Base/Jsq-baseTable/index.ts


+ 0 - 0
Report-V3-TS/src/components/Base/Jsq-baseTable/src/Jsq-baseTable.vue


+ 0 - 0
Report-V3-TS/src/components/Base/Jsq-baseTable/src/props.ts


+ 1 - 0
Report-V3-TS/src/components/Base/Jsq-crud/index.ts

@@ -0,0 +1 @@
+export { default as Crud } from './src/Crud.vue';

+ 41 - 0
Report-V3-TS/src/components/Base/Jsq-crud/src/Jsq-crud.vue

@@ -0,0 +1,41 @@
+<template>
+  <div class="view-container">
+    <div class="view-container-left">
+      <n-space vertical :size="12">
+        <n-input v-model:value="pattern" placeholder="搜索" />
+        <h3>所属菜单</h3>
+        <n-tree
+          default-expand-all
+          :show-irrelevant-nodes="showIrrelevantNodes"
+          :pattern="pattern"
+          :data="data"
+          block-line
+        />
+      </n-space>
+    </div>
+    <div class="view-container-right">右</div>
+  </div>
+</template>
+<script lang="ts" setup></script>
+<style lang="less" scoped>
+  .view-container {
+    width: 100%;
+    height: calc(100vh - 118px);
+    display: flex;
+    flex-direction: row;
+    &-left {
+      width: 20%;
+      height: calc(100vh - 118px);
+      overflow-y: auto;
+      background: #fff;
+      padding: 20px 10px;
+      box-sizing: border-box;
+    }
+    &-right {
+      background: #fff;
+      flex: 1;
+      padding: 20px 10px;
+      margin-left: 6px;
+    }
+  }
+</style>

+ 0 - 0
Report-V3-TS/src/components/Base/Jsq-searchForm/index.ts


+ 0 - 0
Report-V3-TS/src/components/Base/Jsq-searchForm/src/Jsq-searchForm.vue


+ 0 - 0
Report-V3-TS/src/components/Base/Jsq-searchForm/src/props.ts


+ 2 - 0
Report-V3-TS/src/components/Base/Jsq-select/index.ts

@@ -0,0 +1,2 @@
+export { default as JsqSelect } from './src/Jsq-select.vue';
+export { useSelect } from './src/hooks/useSelect';

+ 60 - 0
Report-V3-TS/src/components/Base/Jsq-select/src/Jsq-select.vue

@@ -0,0 +1,60 @@
+<template>
+  <n-select v-bind="getBindValue" />
+</template>
+<script lang="ts" setup>
+  import { ref, unref, useAttrs, computed, onMounted, watch } from 'vue';
+  import type { SelectProps } from 'naive-ui/lib/select';
+  import { basicProps } from './props';
+  import { selectActionType } from './types';
+  import { deepMerge, getDictName } from '@/utils';
+
+  const props = defineProps({ ...basicProps });
+  const attrs = useAttrs();
+  const emit = defineEmits(['register']);
+  const selectOptions = ref<any[]>([]);
+
+  const propsRef = ref<Partial<SelectProps>>({});
+  // 将标签传递的属性与hooks传递的参数合并
+  const getProps = computed(() => {
+    return { ...props, ...(unref(propsRef) as any) };
+  });
+
+  const getOptions = computed(() => {
+    const { api, dictCode, localOptions } = unref(getProps);
+    const options = localOptions
+      ? localOptions
+      : dictCode
+      ? getDictName(dictCode)
+      : selectOptions.value;
+    return { options };
+  });
+
+  const getBindValue = computed(
+    () => ({ ...attrs, ...props, ...unref(getProps), ...unref(getOptions) } as Recordable),
+  );
+
+  const setProps = async (selectProps: Partial<SelectProps>): Promise<void> => {
+    propsRef.value = deepMerge(unref(propsRef) || {}, selectProps);
+  };
+
+  const selectMethods: selectActionType = {
+    setProps,
+  };
+
+  watch(
+    () => unref(getProps).api,
+    (old, val) => {
+      console.log(old);
+      console.log(val);
+    },
+  );
+
+  onMounted(() => {
+    emit('register', selectMethods);
+  });
+
+  defineExpose({
+    ...selectMethods,
+  });
+</script>
+<style lang="less" scoped></style>

+ 50 - 0
Report-V3-TS/src/components/Base/Jsq-select/src/hooks/useSelect.ts

@@ -0,0 +1,50 @@
+import { ref, unref, nextTick, onUnmounted, watch } from 'vue';
+import type { SelectProps } from 'naive-ui/lib/select';
+import { getDynamicProps } from '@/utils';
+import { isProdMode } from '@/utils/env';
+import { JsqSelectProps, UseSelectReturnType, selectActionType } from '../types';
+import type { DynamicProps } from '/#/utils';
+
+type props = Partial<DynamicProps<JsqSelectProps>>;
+export const useSelect = (props?: props): UseSelectReturnType => {
+  const selectRef = ref<Nullable<selectActionType>>(null);
+
+  const getSelect = async () => {
+    const select = unref(selectRef);
+    if (!select) {
+      console.error('tree节点尚未获取');
+    }
+    await nextTick();
+    return select as selectActionType;
+  };
+
+  const register = (instance: selectActionType) => {
+    isProdMode() &&
+      onUnmounted(() => {
+        selectRef.value = null;
+      });
+    if (unref(selectRef) && isProdMode() && instance === unref(selectRef)) return;
+
+    selectRef.value = instance;
+
+    watch(
+      () => props,
+      () => {
+        props && instance.setProps(getDynamicProps(props));
+      },
+      {
+        immediate: true,
+        deep: true,
+      },
+    );
+  };
+
+  const methods: selectActionType = {
+    setProps: async (selectProps: Partial<SelectProps>) => {
+      const select = await getSelect();
+      await select.setProps(selectProps);
+    },
+  };
+
+  return [register, methods];
+};

+ 17 - 0
Report-V3-TS/src/components/Base/Jsq-select/src/props.ts

@@ -0,0 +1,17 @@
+import { selectProps } from 'naive-ui';
+
+export const basicProps = {
+  ...selectProps,
+  labelField: {
+    type: String,
+    default: 'text',
+  },
+  valueField: {
+    type: String,
+    default: 'id',
+  },
+  clearable: {
+    type: Boolean,
+    default: true,
+  },
+};

+ 20 - 0
Report-V3-TS/src/components/Base/Jsq-select/src/types/index.ts

@@ -0,0 +1,20 @@
+import type { SelectProps } from 'naive-ui/lib/select';
+
+type options = {
+  [key: string]: string | number;
+};
+
+export type JsqSelectProps = {
+  api?: () => Promise<any>; // api接口
+  dictCode?: String; // 数字字典
+  localOptions?: options[]; // 静态数据
+};
+
+// 对外暴露的方法
+export interface selectActionType {
+  setProps: (JsqSelectProps: Partial<SelectProps>) => Promise<void>;
+}
+
+export type selectRegisterFn = (treeInstance: selectActionType) => void;
+
+export type UseSelectReturnType = [selectRegisterFn, selectActionType];

+ 2 - 0
Report-V3-TS/src/components/Base/Jsq-tree/index.ts

@@ -0,0 +1,2 @@
+export { default as JsqTree } from './src/Jsq-tree.vue';
+export { useTree } from './src/hooks/useTree';

+ 64 - 0
Report-V3-TS/src/components/Base/Jsq-tree/src/Jsq-tree.vue

@@ -0,0 +1,64 @@
+<template>
+  <n-space vertical :size="12">
+    <n-input v-model:value="pattern" placeholder="搜索" clearable />
+    <h3>{{ treeTitle }}</h3>
+    <n-tree
+      v-bind="getBindValue"
+      :show-irrelevant-nodes="showIrrelevantNodes"
+      :pattern="pattern"
+      :data="treeData"
+      block-line
+    />
+  </n-space>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, useAttrs, onMounted, unref } from 'vue';
+  import type { TreeOption } from 'naive-ui';
+  import type { TreeActionType } from './types/index';
+  import type { TreeProps } from 'naive-ui/lib/tree';
+
+  import { basicProps } from './props';
+  import { deepMerge, removeTreeNotValChildren } from '@/utils';
+
+  const props = defineProps({ ...basicProps });
+  const attrs = useAttrs();
+  const emit = defineEmits(['register']);
+
+  const pattern = ref('');
+  const showIrrelevantNodes = ref(false);
+  const treeData = ref<TreeOption[]>([]);
+
+  const propsRef = ref<Partial<TreeProps>>({});
+
+  const getProps = computed(() => {
+    return { ...props, ...(unref(propsRef) as any) };
+  });
+  const getBindValue = computed(() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable));
+
+  const setProps = async (treeProps: Partial<TreeProps>): Promise<void> => {
+    propsRef.value = deepMerge(unref(propsRef) || {}, treeProps);
+  };
+
+  // 查询tree数据
+  const loadData = async (params?: any) => {
+    const { api } = getBindValue.value;
+    if (!api) return;
+    const { code, data } = await api(params);
+    if (code != 200) return;
+    treeData.value = removeTreeNotValChildren(data);
+  };
+
+  const TreeMethods: TreeActionType = {
+    setProps,
+    loadData,
+  };
+
+  onMounted(() => {
+    emit('register', TreeMethods);
+  });
+
+  defineExpose({
+    ...TreeMethods,
+  });
+</script>
+<style lang="less" scoped></style>

+ 54 - 0
Report-V3-TS/src/components/Base/Jsq-tree/src/hooks/useTree.ts

@@ -0,0 +1,54 @@
+import { ref, unref, nextTick, onUnmounted, watch } from 'vue';
+import { getDynamicProps } from '@/utils';
+import { isProdMode } from '@/utils/env';
+import type { JsqTreeProps, TreeActionType, UseTreeReturnType } from '../types/index';
+import type { DynamicProps } from '/#/utils';
+import type { TreeProps } from 'naive-ui/lib/tree';
+
+type Props = Partial<DynamicProps<JsqTreeProps>>;
+export const useTree = (props?: Props): UseTreeReturnType => {
+  const treeRef = ref<Nullable<TreeActionType>>(null);
+
+  const getTree = async () => {
+    const tree = unref(treeRef);
+    if (!tree) {
+      console.error('tree节点尚未获取');
+    }
+    await nextTick();
+    return tree as TreeActionType;
+  };
+
+  const register = (instance: TreeActionType) => {
+    isProdMode() &&
+      onUnmounted(() => {
+        treeRef.value = null;
+      });
+    if (unref(treeRef) && isProdMode() && instance === unref(treeRef)) return;
+
+    treeRef.value = instance;
+
+    watch(
+      () => props,
+      () => {
+        props && instance.setProps(getDynamicProps(props));
+      },
+      {
+        immediate: true,
+        deep: true,
+      },
+    );
+  };
+
+  const methods: TreeActionType = {
+    setProps: async (treeProps: Partial<TreeProps>) => {
+      const tree = await getTree();
+      await tree.setProps(treeProps);
+    },
+    loadData: async (params) => {
+      const tree = await getTree();
+      await tree.loadData(params);
+    },
+  };
+
+  return [register, methods];
+};

+ 8 - 0
Report-V3-TS/src/components/Base/Jsq-tree/src/props.ts

@@ -0,0 +1,8 @@
+import { treeProps } from 'naive-ui';
+export const basicProps = {
+  ...treeProps,
+  treeTitle: {
+    type: String,
+    default: '所属菜单',
+  },
+};

+ 14 - 0
Report-V3-TS/src/components/Base/Jsq-tree/src/types/index.ts

@@ -0,0 +1,14 @@
+import type { TreeProps } from 'naive-ui/lib/tree';
+export type JsqTreeProps = {
+  api?: () => Promise<any>; // api接口
+};
+
+// 对外暴露的方法
+export interface TreeActionType {
+  setProps: (treeProps: Partial<TreeProps>) => Promise<void>;
+  loadData: (params: any) => Promise<void>;
+}
+
+export type TreeRegisterFn = (treeInstance: TreeActionType) => void
+
+export type UseTreeReturnType = [TreeRegisterFn, TreeActionType]

+ 1 - 0
Report-V3-TS/src/enums/common.ts

@@ -2,3 +2,4 @@ export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
 export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
 export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
 export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
+export const GLOBAL_DICT_CODE_NAME = 'REPORTDICT'; // 全局数字字典名称

+ 1 - 0
Report-V3-TS/src/utils/http/axios/index.ts

@@ -63,6 +63,7 @@ const transform: AxiosTransform = {
 
     //  这里 code,result,message为 后台统一的字段,需要修改为项目自己的接口返回格式
     const { code, message } = data;
+
     // 请求成功
     const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
     // 是否显示提示信息

+ 28 - 2
Report-V3-TS/src/utils/index.ts

@@ -4,6 +4,8 @@ import { NIcon, NTag } from 'naive-ui';
 import { PageEnum } from '@/enums/pageEnum';
 import { isObject } from './is/index';
 import { cloneDeep } from 'lodash-es';
+import { storage } from './Storage';
+import { GLOBAL_DICT_CODE_NAME } from '@/enums/common';
 /**
  * render 图标
  * */
@@ -47,7 +49,7 @@ export function renderNew(type = 'warning', text = 'New', color: object = newTag
         size: 'small',
         color,
       },
-      { default: () => text }
+      { default: () => text },
     );
 }
 
@@ -228,7 +230,7 @@ export function lighten(color: string, amount: number) {
   amount = Math.trunc((255 * amount) / 100);
   return `#${addLight(color.substring(0, 2), amount)}${addLight(
     color.substring(2, 4),
-    amount
+    amount,
   )}${addLight(color.substring(4, 6), amount)}`;
 }
 
@@ -238,3 +240,27 @@ export function lighten(color: string, amount: number) {
 export function isUrl(url: string) {
   return /^(http|https):\/\//g.test(url);
 }
+
+/**
+ * 删除树节点 children为 null | [] 字段
+ */
+export function removeTreeNotValChildren(data: any) {
+  for (let i = 0; i < data.length; i++) {
+    const item = data[i];
+    if (item.children && item.children.length > 0) {
+      removeTreeNotValChildren(item.children);
+    } else {
+      delete item.children;
+    }
+  }
+  return data;
+}
+
+// 根据数字字典名称获取数据
+export function getDictName(dictName: string) {
+  if (!dictName) {
+    console.error('必须要传递数字字典名称');
+  }
+  const allDictCode = storage.get(GLOBAL_DICT_CODE_NAME);
+  return allDictCode[dictName];
+}

+ 9 - 0
Report-V3-TS/src/views/access/accessAuthority/components/test.vue

@@ -0,0 +1,9 @@
+<template>
+  <div></div>
+</template>
+<script lang='ts' setup>
+import { useAttrs } from 'vue'
+const attr = useAttrs()
+console.log(attr)
+</script>
+<style lang='less' scoped></style>

+ 68 - 5
Report-V3-TS/src/views/access/accessAuthority/index.vue

@@ -1,15 +1,78 @@
 <!--
  * @Description: 
  * @Author: qianlishi
- * @Date: 2024-12-08 16:34:50
+ * @Date: 2024-12-08 17:38:28
  * @LastEditors: qianlishi
- * @LastEditTime: 2024-12-08 16:36:34
+ * @LastEditTime: 2024-12-30 05:43:45
 -->
 <template>
-  <div>权限管理</div>
+  <div class="view-container">
+    <div class="view-container-left">
+      <JsqTree
+        default-expand-all
+        key-field="id"
+        label-field="label"
+        @register="register"
+        :node-props="nodeProps"
+      />
+    </div>
+    <div class="view-container-right">
+      <JsqSelect @register="register1" />
+      <Test v-bind="obj" />
+    </div>
+  </div>
 </template>
 <script lang="ts" setup>
+  import { ref, onMounted } from 'vue';
+  import JsqTree from '@/components/Base/Jsq-tree/src/Jsq-tree.vue';
+  import JsqSelect from '@/components/Base/Jsq-select/src/Jsq-select.vue';
+  import Test from './components/test.vue';
 
-</script>
-<style lang="less" scoped></style>
+  import { getAuthorityTree } from '@/api/access/accessAuthority';
+
+  import { useTree } from '@/components/Base/Jsq-tree';
+  import { useSelect } from '@/components/Base/Jsq-select';
+
+  const [register1, {}] = useSelect({
+    api: getAuthorityTree,
+  });
 
+  const obj = ref({ a: 1, b: 2, c: 3 });
+
+  const [register, { loadData }] = useTree({
+    api: getAuthorityTree,
+  });
+  const nodeProps = ({ option }: { option: any }) => {
+    return {
+      onClick() {
+        console.log(11, option);
+        console.log(22, option);
+      },
+    };
+  };
+  onMounted(() => {
+    loadData({ a: 1 });
+  });
+</script>
+<style lang="less" scoped>
+  .view-container {
+    width: 100%;
+    height: calc(100vh - 118px);
+    display: flex;
+    flex-direction: row;
+    &-left {
+      width: 20%;
+      height: calc(100vh - 118px);
+      overflow-y: auto;
+      background: #fff;
+      padding: 20px 10px;
+      box-sizing: border-box;
+    }
+    &-right {
+      background: #fff;
+      flex: 1;
+      padding: 20px 10px;
+      margin-left: 6px;
+    }
+  }
+</style>

+ 1 - 10
Report-V3-TS/src/views/access/accessRole/index.vue

@@ -1,14 +1,5 @@
-<!--
- * @Description: 
- * @Author: qianlishi
- * @Date: 2024-12-08 16:36:34
- * @LastEditors: qianlishi
- * @LastEditTime: 2024-12-08 16:36:57
--->
 <template>
   <div>角色管理</div>
 </template>
-<script lang="ts" setup>
-
-</script>
+<script lang="ts" setup></script>
 <style lang="less" scoped></style>