Commit cd8e924d4641fc46cacd4a934478d8861e8c3c04

Authored by Vben
1 parent 9a1ba749

perf(tree): strengthen BasicTree function

CHANGELOG.zh_CN.md
@@ -11,8 +11,9 @@ @@ -11,8 +11,9 @@
11 - 新增修改密码界面 11 - 新增修改密码界面
12 - 新增部门管理示例界面 12 - 新增部门管理示例界面
13 - 新增 WebSocket 示例和服务脚本 13 - 新增 WebSocket 示例和服务脚本
14 -- BasicTree 组件新增 `renderIcon` 属性用于控制层级图标显示  
15 -- BasicTree->actionItem 新增 show 属性,用于动态控制按钮显示 14 +- Tree 组件新增 `renderIcon` 属性用于控制层级图标显示
  15 +- Tree->actionItem 新增 show 属性,用于动态控制按钮显示
  16 +- Tree 新增工具栏/title/搜索功能
16 17
17 ### ⚡ Performance Improvements 18 ### ⚡ Performance Improvements
18 19
package.json
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 "dependencies": { 29 "dependencies": {
30 "@iconify/iconify": "^2.0.0-rc.6", 30 "@iconify/iconify": "^2.0.0-rc.6",
31 "@vueuse/core": "^4.3.1", 31 "@vueuse/core": "^4.3.1",
32 - "@zxcvbn-ts/core": "^0.2.0", 32 + "@zxcvbn-ts/core": "^0.3.0",
33 "ant-design-vue": "2.0.1", 33 "ant-design-vue": "2.0.1",
34 "apexcharts": "^3.25.0", 34 "apexcharts": "^3.25.0",
35 "axios": "^0.21.1", 35 "axios": "^0.21.1",
src/components/StrengthMeter/src/index.vue
@@ -23,7 +23,8 @@ @@ -23,7 +23,8 @@
23 23
24 import { Input } from 'ant-design-vue'; 24 import { Input } from 'ant-design-vue';
25 25
26 - import zxcvbn from '@zxcvbn-ts/core'; 26 + // @ts-ignore
  27 + import { zxcvbn } from '@zxcvbn-ts/core';
27 import { useDesign } from '/@/hooks/web/useDesign'; 28 import { useDesign } from '/@/hooks/web/useDesign';
28 import { propTypes } from '/@/utils/propTypes'; 29 import { propTypes } from '/@/utils/propTypes';
29 30
src/components/Tree/src/TreeHeader.vue 0 → 100644
  1 +<template>
  2 + <div class="flex px-2 py-1.5 items-center border-b-1">
  3 + <BasicTitle :helpMessage="helpMessage" v-if="title">{{ title }}</BasicTitle>
  4 +
  5 + <div class="flex flex-1 justify-end items-center cursor-pointer" v-if="search || toolbar">
  6 + <div class="mr-1 w-2/3" v-if="search">
  7 + <InputSearch :placeholder="t('common.searchText')" size="small" @change="handleSearch" />
  8 + </div>
  9 + <Dropdown @click.prevent v-if="toolbar">
  10 + <Icon icon="ion:ellipsis-vertical" />
  11 + <template #overlay>
  12 + <Menu @click="handleMenuClick">
  13 + <MenuItem v-for="item in toolbarList" :key="item.value">
  14 + {{ item.label }}
  15 + </MenuItem>
  16 + </Menu>
  17 + </template>
  18 + </Dropdown>
  19 + </div>
  20 + </div>
  21 +</template>
  22 +<script lang="ts">
  23 + import type { PropType } from 'vue';
  24 + import { defineComponent, ref } from 'vue';
  25 +
  26 + import { Dropdown, Menu, Checkbox, Input } from 'ant-design-vue';
  27 + import { Icon } from '/@/components/Icon';
  28 + import { BasicTitle } from '/@/components/Basic';
  29 +
  30 + import { propTypes } from '/@/utils/propTypes';
  31 +
  32 + import { useI18n } from '/@/hooks/web/useI18n';
  33 + import { useDebounce } from '/@/hooks/core/useDebounce';
  34 +
  35 + import { ToolbarEnum } from './enum';
  36 +
  37 + interface MenuInfo {
  38 + key: ToolbarEnum;
  39 + }
  40 + export default defineComponent({
  41 + name: 'BasicTreeHeader',
  42 + components: {
  43 + BasicTitle,
  44 + Icon,
  45 + Checkbox,
  46 + Dropdown,
  47 + Menu,
  48 + MenuItem: Menu.Item,
  49 + InputSearch: Input.Search,
  50 + },
  51 + props: {
  52 + helpMessage: {
  53 + type: [String, Array] as PropType<string | string[]>,
  54 + default: '',
  55 + },
  56 + title: propTypes.string,
  57 + toolbar: propTypes.bool,
  58 + search: propTypes.bool,
  59 + checkAll: propTypes.func,
  60 + expandAll: propTypes.func,
  61 + },
  62 + emits: ['strictly-change', 'search'],
  63 + setup(props, { emit }) {
  64 + const { t } = useI18n();
  65 + const toolbarList = ref([
  66 + { label: t('component.tree.selectAll'), value: ToolbarEnum.SELECT_ALL },
  67 + { label: t('component.tree.unSelectAll'), value: ToolbarEnum.UN_SELECT_ALL },
  68 + { label: t('component.tree.expandAll'), value: ToolbarEnum.EXPAND_ALL },
  69 + { label: t('component.tree.unExpandAll'), value: ToolbarEnum.UN_EXPAND_ALL },
  70 + { label: t('component.tree.checkStrictly'), value: ToolbarEnum.CHECK_STRICTLY },
  71 + { label: t('component.tree.checkUnStrictly'), value: ToolbarEnum.CHECK_UN_STRICTLY },
  72 + ]);
  73 +
  74 + function handleMenuClick(e: MenuInfo) {
  75 + const { key } = e;
  76 + switch (key) {
  77 + case ToolbarEnum.SELECT_ALL:
  78 + props.checkAll?.(true);
  79 + break;
  80 + case ToolbarEnum.UN_SELECT_ALL:
  81 + props.checkAll?.(false);
  82 + break;
  83 + case ToolbarEnum.EXPAND_ALL:
  84 + props.expandAll?.(true);
  85 + break;
  86 + case ToolbarEnum.UN_EXPAND_ALL:
  87 + props.expandAll?.(false);
  88 + break;
  89 + case ToolbarEnum.CHECK_STRICTLY:
  90 + emit('strictly-change', false);
  91 + break;
  92 + case ToolbarEnum.CHECK_UN_STRICTLY:
  93 + emit('strictly-change', true);
  94 + break;
  95 + }
  96 + }
  97 +
  98 + function emitChange(value?: string): void {
  99 + emit('search', value);
  100 + }
  101 + const [debounceEmitChange] = useDebounce(emitChange, 200);
  102 +
  103 + function handleSearch(e: ChangeEvent): void {
  104 + debounceEmitChange(e.target.value);
  105 + }
  106 +
  107 + return { t, toolbarList, handleMenuClick, handleSearch };
  108 + },
  109 + });
  110 +</script>
src/components/Tree/src/enum.ts 0 → 100644
  1 +export enum ToolbarEnum {
  2 + SELECT_ALL,
  3 + UN_SELECT_ALL,
  4 + EXPAND_ALL,
  5 + UN_EXPAND_ALL,
  6 + CHECK_STRICTLY,
  7 + CHECK_UN_STRICTLY,
  8 +}
src/components/Tree/src/index.vue
1 <script lang="tsx"> 1 <script lang="tsx">
2 import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types'; 2 import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types';
3 3
4 - import {  
5 - defineComponent,  
6 - reactive,  
7 - computed,  
8 - unref,  
9 - ref,  
10 - watchEffect,  
11 - onMounted,  
12 - toRaw,  
13 - } from 'vue'; 4 + import { defineComponent, reactive, computed, unref, ref, watchEffect, toRaw } from 'vue';
14 import { Tree } from 'ant-design-vue'; 5 import { Tree } from 'ant-design-vue';
15 import { TreeIcon } from './TreeIcon'; 6 import { TreeIcon } from './TreeIcon';
  7 + import TreeHeader from './TreeHeader.vue';
16 // import { DownOutlined } from '@ant-design/icons-vue'; 8 // import { DownOutlined } from '@ant-design/icons-vue';
17 9
18 import { omit, get } from 'lodash-es'; 10 import { omit, get } from 'lodash-es';
19 import { isBoolean, isFunction } from '/@/utils/is'; 11 import { isBoolean, isFunction } from '/@/utils/is';
20 import { extendSlots } from '/@/utils/helper/tsxHelper'; 12 import { extendSlots } from '/@/utils/helper/tsxHelper';
  13 + import { filter } from '/@/utils/helper/treeHelper';
21 14
22 import { useTree } from './useTree'; 15 import { useTree } from './useTree';
23 import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu'; 16 import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu';
@@ -30,18 +23,25 @@ @@ -30,18 +23,25 @@
30 expandedKeys: Keys; 23 expandedKeys: Keys;
31 selectedKeys: Keys; 24 selectedKeys: Keys;
32 checkedKeys: CheckKeys; 25 checkedKeys: CheckKeys;
  26 + checkStrictly: boolean;
33 } 27 }
34 export default defineComponent({ 28 export default defineComponent({
35 name: 'BasicTree', 29 name: 'BasicTree',
36 props: basicProps, 30 props: basicProps,
37 - emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'get'], 31 + emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change'],
38 setup(props, { attrs, slots, emit }) { 32 setup(props, { attrs, slots, emit }) {
39 const state = reactive<State>({ 33 const state = reactive<State>({
  34 + checkStrictly: props.checkStrictly,
40 expandedKeys: props.expandedKeys || [], 35 expandedKeys: props.expandedKeys || [],
41 selectedKeys: props.selectedKeys || [], 36 selectedKeys: props.selectedKeys || [],
42 checkedKeys: props.checkedKeys || [], 37 checkedKeys: props.checkedKeys || [],
43 }); 38 });
44 39
  40 + const searchState = reactive({
  41 + startSearch: false,
  42 + searchData: [] as TreeItem[],
  43 + });
  44 +
45 const treeDataRef = ref<TreeItem[]>([]); 45 const treeDataRef = ref<TreeItem[]>([]);
46 46
47 const [createContextMenu] = useContextMenu(); 47 const [createContextMenu] = useContextMenu();
@@ -77,6 +77,7 @@ @@ -77,6 +77,7 @@
77 expandedKeys: state.expandedKeys, 77 expandedKeys: state.expandedKeys,
78 selectedKeys: state.selectedKeys, 78 selectedKeys: state.selectedKeys,
79 checkedKeys: state.checkedKeys, 79 checkedKeys: state.checkedKeys,
  80 + checkStrictly: state.checkStrictly,
80 replaceFields: unref(getReplaceFields), 81 replaceFields: unref(getReplaceFields),
81 'onUpdate:expandedKeys': (v: Keys) => { 82 'onUpdate:expandedKeys': (v: Keys) => {
82 state.expandedKeys = v; 83 state.expandedKeys = v;
@@ -88,21 +89,27 @@ @@ -88,21 +89,27 @@
88 }, 89 },
89 onCheck: (v: CheckKeys) => { 90 onCheck: (v: CheckKeys) => {
90 state.checkedKeys = v; 91 state.checkedKeys = v;
  92 + emit('change', v);
91 emit('update:value', v); 93 emit('update:value', v);
92 }, 94 },
93 onRightClick: handleRightClick, 95 onRightClick: handleRightClick,
94 }; 96 };
95 - propsData = omit(propsData, 'treeData'); 97 + propsData = omit(propsData, 'treeData', 'class');
96 return propsData; 98 return propsData;
97 }); 99 });
98 100
99 - const getTreeData = computed((): TreeItem[] => unref(treeDataRef));  
100 -  
101 - const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey } = useTree(  
102 - treeDataRef,  
103 - getReplaceFields 101 + const getTreeData = computed((): TreeItem[] =>
  102 + searchState.startSearch ? searchState.searchData : unref(treeDataRef)
104 ); 103 );
105 104
  105 + const {
  106 + deleteNodeByKey,
  107 + insertNodeByKey,
  108 + filterByLevel,
  109 + updateNodeByKey,
  110 + getAllKeys,
  111 + } = useTree(treeDataRef, getReplaceFields);
  112 +
106 function getIcon(params: Recordable, icon?: string) { 113 function getIcon(params: Recordable, icon?: string) {
107 if (!icon) { 114 if (!icon) {
108 if (props.renderIcon && isFunction(props.renderIcon)) { 115 if (props.renderIcon && isFunction(props.renderIcon)) {
@@ -112,60 +119,6 @@ @@ -112,60 +119,6 @@
112 return icon; 119 return icon;
113 } 120 }
114 121
115 - function renderAction(node: TreeItem) {  
116 - const { actionList } = props;  
117 - if (!actionList || actionList.length === 0) return;  
118 - return actionList.map((item, index) => {  
119 - if (isFunction(item.show)) {  
120 - return item.show?.(node);  
121 - }  
122 -  
123 - if (isBoolean(item.show)) {  
124 - return item.show;  
125 - }  
126 -  
127 - return (  
128 - <span key={index} class={`${prefixCls}__action`}>  
129 - {item.render(node)}  
130 - </span>  
131 - );  
132 - });  
133 - }  
134 -  
135 - function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) {  
136 - if (!data) {  
137 - return null;  
138 - }  
139 - return data.map((item) => {  
140 - const { title: titleField, key: keyField, children: childrenField } = unref(  
141 - getReplaceFields  
142 - );  
143 -  
144 - const propsData = omit(item, 'title');  
145 - const icon = getIcon({ ...item, level }, item.icon);  
146 - return (  
147 - <Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}>  
148 - {{  
149 - title: () => (  
150 - <span class={`${prefixCls}-title`}>  
151 - {icon && <TreeIcon icon={icon} />}  
152 - <span  
153 - class={`${prefixCls}__content`}  
154 - // style={unref(getContentStyle)}  
155 - >  
156 - {get(item, titleField)}  
157 - </span>  
158 - <span class={`${prefixCls}__actions`}> {renderAction({ ...item, level })}</span>  
159 - </span>  
160 - ),  
161 - default: () =>  
162 - renderTreeNode({ data: get(item, childrenField) || [], level: level + 1 }),  
163 - }}  
164 - </Tree.TreeNode>  
165 - );  
166 - });  
167 - }  
168 -  
169 async function handleRightClick({ event, node }: any) { 122 async function handleRightClick({ event, node }: any) {
170 const { rightMenuList: menuList = [], beforeRightClick } = props; 123 const { rightMenuList: menuList = [], beforeRightClick } = props;
171 let rightMenuList: ContextMenuItem[] = []; 124 let rightMenuList: ContextMenuItem[] = [];
@@ -205,6 +158,32 @@ @@ -205,6 +158,32 @@
205 return state.checkedKeys; 158 return state.checkedKeys;
206 } 159 }
207 160
  161 + function checkAll(checkAll: boolean) {
  162 + state.checkedKeys = checkAll ? getAllKeys() : ([] as Keys);
  163 + }
  164 +
  165 + function expandAll(expandAll: boolean) {
  166 + state.expandedKeys = expandAll ? getAllKeys() : ([] as Keys);
  167 + }
  168 +
  169 + function onStrictlyChange(strictly: boolean) {
  170 + state.checkStrictly = strictly;
  171 + }
  172 +
  173 + function handleSearch(searchValue: string) {
  174 + if (!searchValue) {
  175 + searchState.startSearch = false;
  176 + return;
  177 + }
  178 + searchState.startSearch = true;
  179 +
  180 + searchState.searchData = filter(unref(treeDataRef), (node) => {
  181 + const { title } = node;
  182 + return title?.includes(searchValue) ?? false;
  183 + // || key?.includes(searchValue);
  184 + });
  185 + }
  186 +
208 watchEffect(() => { 187 watchEffect(() => {
209 treeDataRef.value = props.treeData as TreeItem[]; 188 treeDataRef.value = props.treeData as TreeItem[];
210 state.expandedKeys = props.expandedKeys; 189 state.expandedKeys = props.expandedKeys;
@@ -212,6 +191,16 @@ @@ -212,6 +191,16 @@
212 state.checkedKeys = props.checkedKeys; 191 state.checkedKeys = props.checkedKeys;
213 }); 192 });
214 193
  194 + watchEffect(() => {
  195 + if (props.value) {
  196 + state.checkedKeys = props.value;
  197 + }
  198 + });
  199 +
  200 + watchEffect(() => {
  201 + state.checkStrictly = props.checkStrictly;
  202 + });
  203 +
215 const instance: TreeActionType = { 204 const instance: TreeActionType = {
216 setExpandedKeys, 205 setExpandedKeys,
217 getExpandedKeys, 206 getExpandedKeys,
@@ -222,6 +211,8 @@ @@ -222,6 +211,8 @@
222 insertNodeByKey, 211 insertNodeByKey,
223 deleteNodeByKey, 212 deleteNodeByKey,
224 updateNodeByKey, 213 updateNodeByKey,
  214 + checkAll,
  215 + expandAll,
225 filterByLevel: (level: number) => { 216 filterByLevel: (level: number) => {
226 state.expandedKeys = filterByLevel(level); 217 state.expandedKeys = filterByLevel(level);
227 }, 218 },
@@ -229,19 +220,83 @@ @@ -229,19 +220,83 @@
229 220
230 useExpose<TreeActionType>(instance); 221 useExpose<TreeActionType>(instance);
231 222
232 - onMounted(() => {  
233 - emit('get', instance);  
234 - }); 223 + function renderAction(node: TreeItem) {
  224 + const { actionList } = props;
  225 + if (!actionList || actionList.length === 0) return;
  226 + return actionList.map((item, index) => {
  227 + if (isFunction(item.show)) {
  228 + return item.show?.(node);
  229 + }
  230 +
  231 + if (isBoolean(item.show)) {
  232 + return item.show;
  233 + }
  234 +
  235 + return (
  236 + <span key={index} class={`${prefixCls}__action`}>
  237 + {item.render(node)}
  238 + </span>
  239 + );
  240 + });
  241 + }
  242 +
  243 + function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) {
  244 + if (!data) {
  245 + return null;
  246 + }
  247 + return data.map((item) => {
  248 + const { title: titleField, key: keyField, children: childrenField } = unref(
  249 + getReplaceFields
  250 + );
235 251
  252 + const propsData = omit(item, 'title');
  253 + const icon = getIcon({ ...item, level }, item.icon);
  254 + return (
  255 + <Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}>
  256 + {{
  257 + title: () => (
  258 + <span class={`${prefixCls}-title pl-2`}>
  259 + {icon && <TreeIcon icon={icon} />}
  260 + <span
  261 + class={`${prefixCls}__content`}
  262 + // style={unref(getContentStyle)}
  263 + >
  264 + {get(item, titleField)}
  265 + </span>
  266 + <span class={`${prefixCls}__actions`}> {renderAction({ ...item, level })}</span>
  267 + </span>
  268 + ),
  269 + default: () =>
  270 + renderTreeNode({ data: get(item, childrenField) || [], level: level + 1 }),
  271 + }}
  272 + </Tree.TreeNode>
  273 + );
  274 + });
  275 + }
236 return () => { 276 return () => {
  277 + const { title, helpMessage, toolbar, search } = props;
237 return ( 278 return (
238 - <Tree {...unref(getBindValues)} showIcon={false} class={[prefixCls]}>  
239 - {{  
240 - // switcherIcon: () => <DownOutlined />,  
241 - default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),  
242 - ...extendSlots(slots),  
243 - }}  
244 - </Tree> 279 + <div class={[prefixCls, 'h-full bg-white']}>
  280 + {(title || toolbar || search) && (
  281 + <TreeHeader
  282 + checkAll={checkAll}
  283 + expandAll={expandAll}
  284 + title={title}
  285 + search={search}
  286 + toolbar={toolbar}
  287 + helpMessage={helpMessage}
  288 + onStrictlyChange={onStrictlyChange}
  289 + onSearch={handleSearch}
  290 + />
  291 + )}
  292 + <Tree {...unref(getBindValues)} showIcon={false}>
  293 + {{
  294 + // switcherIcon: () => <DownOutlined />,
  295 + default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
  296 + ...extendSlots(slots),
  297 + }}
  298 + </Tree>
  299 + </div>
245 ); 300 );
246 }; 301 };
247 }, 302 },
@@ -251,8 +306,6 @@ @@ -251,8 +306,6 @@
251 @prefix-cls: ~'@{namespace}-basic-tree'; 306 @prefix-cls: ~'@{namespace}-basic-tree';
252 307
253 .@{prefix-cls} { 308 .@{prefix-cls} {
254 - position: relative;  
255 -  
256 .ant-tree-node-content-wrapper { 309 .ant-tree-node-content-wrapper {
257 position: relative; 310 position: relative;
258 311
@@ -278,14 +331,14 @@ @@ -278,14 +331,14 @@
278 } 331 }
279 332
280 &__content { 333 &__content {
281 - display: inline-block; 334 + // display: inline-block;
282 overflow: hidden; 335 overflow: hidden;
283 } 336 }
284 337
285 &__actions { 338 &__actions {
286 position: absolute; 339 position: absolute;
287 top: 2px; 340 top: 2px;
288 - right: 2px; 341 + right: 3px;
289 display: flex; 342 display: flex;
290 } 343 }
291 344
src/components/Tree/src/props.ts
@@ -2,11 +2,26 @@ import type { PropType } from &#39;vue&#39;; @@ -2,11 +2,26 @@ import type { PropType } from &#39;vue&#39;;
2 import type { ReplaceFields, ActionItem, Keys, CheckKeys } from './types'; 2 import type { ReplaceFields, ActionItem, Keys, CheckKeys } from './types';
3 import type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; 3 import type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
4 import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; 4 import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
  5 +import { propTypes } from '/@/utils/propTypes';
5 6
6 export const basicProps = { 7 export const basicProps = {
  8 + value: {
  9 + type: Array as PropType<Keys>,
  10 + },
7 renderIcon: { 11 renderIcon: {
8 type: Function as PropType<(params: Recordable) => string>, 12 type: Function as PropType<(params: Recordable) => string>,
9 }, 13 },
  14 +
  15 + helpMessage: {
  16 + type: [String, Array] as PropType<string | string[]>,
  17 + default: '',
  18 + },
  19 +
  20 + title: propTypes.string,
  21 + toolbar: propTypes.bool,
  22 + search: propTypes.bool,
  23 + checkStrictly: propTypes.bool,
  24 +
10 replaceFields: { 25 replaceFields: {
11 type: Object as PropType<ReplaceFields>, 26 type: Object as PropType<ReplaceFields>,
12 }, 27 },
src/components/Tree/src/types.ts
@@ -21,6 +21,8 @@ export type CheckKeys = @@ -21,6 +21,8 @@ export type CheckKeys =
21 | { checked: string[] | number[]; halfChecked: string[] | number[] }; 21 | { checked: string[] | number[]; halfChecked: string[] | number[] };
22 22
23 export interface TreeActionType { 23 export interface TreeActionType {
  24 + checkAll: (checkAll: boolean) => void;
  25 + expandAll: (expandAll: boolean) => void;
24 setExpandedKeys: (keys: Keys) => void; 26 setExpandedKeys: (keys: Keys) => void;
25 getExpandedKeys: () => Keys; 27 getExpandedKeys: () => Keys;
26 setSelectedKeys: (keys: Keys) => void; 28 setSelectedKeys: (keys: Keys) => void;
src/components/Tree/src/useTree.ts
1 -import type { InsertNodeParams, ReplaceFields } from './types'; 1 +import type { InsertNodeParams, Keys, ReplaceFields } from './types';
2 import type { Ref, ComputedRef } from 'vue'; 2 import type { Ref, ComputedRef } from 'vue';
3 import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; 3 import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
4 4
@@ -10,6 +10,23 @@ export function useTree( @@ -10,6 +10,23 @@ export function useTree(
10 treeDataRef: Ref<TreeDataItem[]>, 10 treeDataRef: Ref<TreeDataItem[]>,
11 getReplaceFields: ComputedRef<ReplaceFields> 11 getReplaceFields: ComputedRef<ReplaceFields>
12 ) { 12 ) {
  13 + function getAllKeys(list?: TreeDataItem[]) {
  14 + const keys: string[] = [];
  15 + const treeData = list || unref(treeDataRef);
  16 + const { key: keyField, children: childrenField } = unref(getReplaceFields);
  17 + if (!childrenField || !keyField) return keys;
  18 +
  19 + for (let index = 0; index < treeData.length; index++) {
  20 + const node = treeData[index];
  21 + keys.push(node[keyField]!);
  22 + const children = node[childrenField];
  23 + if (children && children.length) {
  24 + keys.push(...(getAllKeys(children) as string[]));
  25 + }
  26 + }
  27 + return keys as Keys;
  28 + }
  29 +
13 // Update node 30 // Update node
14 function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) { 31 function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) {
15 if (!key) return; 32 if (!key) return;
@@ -94,5 +111,5 @@ export function useTree( @@ -94,5 +111,5 @@ export function useTree(
94 } 111 }
95 } 112 }
96 } 113 }
97 - return { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey }; 114 + return { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey, getAllKeys };
98 } 115 }
src/locales/lang/en/component/tree.ts 0 → 100644
  1 +export default {
  2 + selectAll: 'Select All',
  3 + unSelectAll: 'Cancel Select',
  4 + expandAll: 'Expand All',
  5 + unExpandAll: 'Collapse all',
  6 +
  7 + checkStrictly: 'Hierarchical association',
  8 + checkUnStrictly: 'Hierarchical independence',
  9 +};
src/locales/lang/en/routes/demo/comp.ts
@@ -12,7 +12,7 @@ export default { @@ -12,7 +12,7 @@ export default {
12 tree: 'Tree', 12 tree: 'Tree',
13 13
14 treeBasic: 'Basic', 14 treeBasic: 'Basic',
15 - editTree: 'Right-click', 15 + editTree: 'Searchable/toolbar',
16 actionTree: 'Function operation', 16 actionTree: 'Function operation',
17 17
18 modal: 'Modal', 18 modal: 'Modal',
src/locales/lang/zh_CN/component/tree.ts 0 → 100644
  1 +export default {
  2 + selectAll: '选择全部',
  3 + unSelectAll: '取消选择',
  4 + expandAll: '展开全部',
  5 + unExpandAll: '折叠全部',
  6 + checkStrictly: '层级关联',
  7 + checkUnStrictly: '层级独立',
  8 +};
src/locales/lang/zh_CN/routes/demo/comp.ts
@@ -11,7 +11,7 @@ export default { @@ -11,7 +11,7 @@ export default {
11 11
12 tree: 'Tree', 12 tree: 'Tree',
13 treeBasic: '基础树', 13 treeBasic: '基础树',
14 - editTree: '右键示例', 14 + editTree: '可搜索/工具栏',
15 actionTree: '函数操作示例', 15 actionTree: '函数操作示例',
16 16
17 modal: '弹窗扩展', 17 modal: '弹窗扩展',
src/router/menus/modules/demo/comp.ts
@@ -6,7 +6,10 @@ const menu: MenuModule = { @@ -6,7 +6,10 @@ const menu: MenuModule = {
6 menu: { 6 menu: {
7 name: t('routes.demo.comp.comp'), 7 name: t('routes.demo.comp.comp'),
8 path: '/comp', 8 path: '/comp',
9 - 9 + tag: {
  10 + dot: true,
  11 + type: 'warn',
  12 + },
10 children: [ 13 children: [
11 { 14 {
12 path: 'basic', 15 path: 'basic',
@@ -154,6 +157,10 @@ const menu: MenuModule = { @@ -154,6 +157,10 @@ const menu: MenuModule = {
154 { 157 {
155 path: 'tree', 158 path: 'tree',
156 name: t('routes.demo.comp.tree'), 159 name: t('routes.demo.comp.tree'),
  160 + tag: {
  161 + dot: true,
  162 + type: 'warn',
  163 + },
157 children: [ 164 children: [
158 { 165 {
159 path: 'basic', 166 path: 'basic',
@@ -162,6 +169,10 @@ const menu: MenuModule = { @@ -162,6 +169,10 @@ const menu: MenuModule = {
162 { 169 {
163 path: 'editTree', 170 path: 'editTree',
164 name: t('routes.demo.comp.editTree'), 171 name: t('routes.demo.comp.editTree'),
  172 + tag: {
  173 + dot: true,
  174 + type: 'warn',
  175 + },
165 }, 176 },
166 { 177 {
167 path: 'actionTree', 178 path: 'actionTree',
@@ -172,9 +183,6 @@ const menu: MenuModule = { @@ -172,9 +183,6 @@ const menu: MenuModule = {
172 { 183 {
173 name: t('routes.demo.editor.editor'), 184 name: t('routes.demo.editor.editor'),
174 path: 'editor', 185 path: 'editor',
175 - tag: {  
176 - content: 'new',  
177 - },  
178 children: [ 186 children: [
179 { 187 {
180 path: 'markdown', 188 path: 'markdown',
src/router/menus/modules/demo/system.ts
@@ -22,21 +22,21 @@ const menu: MenuModule = { @@ -22,21 +22,21 @@ const menu: MenuModule = {
22 path: 'role', 22 path: 'role',
23 name: t('routes.demo.system.role'), 23 name: t('routes.demo.system.role'),
24 tag: { 24 tag: {
25 - content: 'new', 25 + dot: true,
26 }, 26 },
27 }, 27 },
28 { 28 {
29 path: 'menu', 29 path: 'menu',
30 name: t('routes.demo.system.menu'), 30 name: t('routes.demo.system.menu'),
31 tag: { 31 tag: {
32 - content: 'new', 32 + dot: true,
33 }, 33 },
34 }, 34 },
35 { 35 {
36 path: 'dept', 36 path: 'dept',
37 name: t('routes.demo.system.dept'), 37 name: t('routes.demo.system.dept'),
38 tag: { 38 tag: {
39 - content: 'new', 39 + dot: true,
40 }, 40 },
41 }, 41 },
42 42
@@ -44,7 +44,7 @@ const menu: MenuModule = { @@ -44,7 +44,7 @@ const menu: MenuModule = {
44 path: 'changePassword', 44 path: 'changePassword',
45 name: t('routes.demo.system.password'), 45 name: t('routes.demo.system.password'),
46 tag: { 46 tag: {
47 - content: 'new', 47 + dot: true,
48 }, 48 },
49 }, 49 },
50 ], 50 ],
src/store/modules/permission.ts
@@ -18,7 +18,7 @@ import { transformObjToRoute } from &#39;/@/router/helper/routeHelper&#39;; @@ -18,7 +18,7 @@ import { transformObjToRoute } from &#39;/@/router/helper/routeHelper&#39;;
18 import { transformRouteToMenu } from '/@/router/helper/menuHelper'; 18 import { transformRouteToMenu } from '/@/router/helper/menuHelper';
19 19
20 import { useMessage } from '/@/hooks/web/useMessage'; 20 import { useMessage } from '/@/hooks/web/useMessage';
21 -import { useI18n } from '/@/hooks/web/useI18n'; 21 +// import { useI18n } from '/@/hooks/web/useI18n';
22 import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/constant'; 22 import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/constant';
23 23
24 const { createMessage } = useMessage(); 24 const { createMessage } = useMessage();
@@ -84,7 +84,7 @@ class Permission extends VuexModule { @@ -84,7 +84,7 @@ class Permission extends VuexModule {
84 84
85 @Action 85 @Action
86 async buildRoutesAction(id?: number | string): Promise<AppRouteRecordRaw[]> { 86 async buildRoutesAction(id?: number | string): Promise<AppRouteRecordRaw[]> {
87 - const { t } = useI18n(); 87 + // const { t } = useI18n();
88 let routes: AppRouteRecordRaw[] = []; 88 let routes: AppRouteRecordRaw[] = [];
89 const roleList = toRaw(userStore.getRoleListState); 89 const roleList = toRaw(userStore.getRoleListState);
90 90
@@ -101,7 +101,8 @@ class Permission extends VuexModule { @@ -101,7 +101,8 @@ class Permission extends VuexModule {
101 // If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below 101 // If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below
102 } else if (permissionMode === PermissionModeEnum.BACK) { 102 } else if (permissionMode === PermissionModeEnum.BACK) {
103 createMessage.loading({ 103 createMessage.loading({
104 - content: t('sys.app.menuLoading'), 104 + content: 'Loading menu...',
  105 + // content: 't('sys.app.menuLoading')',
105 duration: 1, 106 duration: 1,
106 }); 107 });
107 // Here to get the background routing menu logic to modify by yourself 108 // Here to get the background routing menu logic to modify by yourself
src/views/demo/tree/ActionTree.vue
1 <template> 1 <template>
2 <PageWrapper title="Tree函数操作示例" contentBackground contentClass="p-4"> 2 <PageWrapper title="Tree函数操作示例" contentBackground contentClass="p-4">
3 <div class="mb-4"> 3 <div class="mb-4">
  4 + <a-button @click="expandAll(true)" class="mr-2"> 展开全部 </a-button>
  5 + <a-button @click="expandAll(false)" class="mr-2"> 折叠全部 </a-button>
  6 + <a-button @click="checkAll(true)" class="mr-2"> 全选 </a-button>
  7 + <a-button @click="checkAll(false)" class="mr-2"> 全不选 </a-button>
4 <a-button @click="handleLevel(2)" class="mr-2"> 显示到第2级 </a-button> 8 <a-button @click="handleLevel(2)" class="mr-2"> 显示到第2级 </a-button>
5 <a-button @click="handleLevel(1)" class="mr-2"> 显示到第1级 </a-button> 9 <a-button @click="handleLevel(1)" class="mr-2"> 显示到第1级 </a-button>
  10 + </div>
  11 + <div class="mb-4">
6 <a-button @click="handleSetCheckData" class="mr-2"> 设置勾选数据 </a-button> 12 <a-button @click="handleSetCheckData" class="mr-2"> 设置勾选数据 </a-button>
7 <a-button @click="handleGetCheckData" class="mr-2"> 获取勾选数据 </a-button> 13 <a-button @click="handleGetCheckData" class="mr-2"> 获取勾选数据 </a-button>
8 <a-button @click="handleSetSelectData" class="mr-2"> 设置选中数据 </a-button> 14 <a-button @click="handleSetSelectData" class="mr-2"> 设置选中数据 </a-button>
@@ -17,21 +23,18 @@ @@ -17,21 +23,18 @@
17 <a-button @click="deleteNodeByKey('2-2')" class="mr-2"> 删除parent3节点 </a-button> 23 <a-button @click="deleteNodeByKey('2-2')" class="mr-2"> 删除parent3节点 </a-button>
18 <a-button @click="updateNodeByKey('1-1')" class="mr-2"> 更新parent2节点 </a-button> 24 <a-button @click="updateNodeByKey('1-1')" class="mr-2"> 更新parent2节点 </a-button>
19 </div> 25 </div>
20 - <CollapseContainer title="函数操作" class="mr-4" :canExpan="false" :style="{ width: '33%' }">  
21 - <BasicTree :treeData="treeData" ref="treeRef" :checkable="true" />  
22 - </CollapseContainer> 26 + <BasicTree :treeData="treeData" title="函数操作" ref="treeRef" :checkable="true" />
23 </PageWrapper> 27 </PageWrapper>
24 </template> 28 </template>
25 <script lang="ts"> 29 <script lang="ts">
26 import { defineComponent, ref, unref } from 'vue'; 30 import { defineComponent, ref, unref } from 'vue';
27 import { BasicTree, TreeActionType } from '/@/components/Tree/index'; 31 import { BasicTree, TreeActionType } from '/@/components/Tree/index';
28 import { treeData } from './data'; 32 import { treeData } from './data';
29 - import { CollapseContainer } from '/@/components/Container/index';  
30 import { useMessage } from '/@/hooks/web/useMessage'; 33 import { useMessage } from '/@/hooks/web/useMessage';
31 import { PageWrapper } from '/@/components/Page'; 34 import { PageWrapper } from '/@/components/Page';
32 35
33 export default defineComponent({ 36 export default defineComponent({
34 - components: { BasicTree, CollapseContainer, PageWrapper }, 37 + components: { BasicTree, PageWrapper },
35 setup() { 38 setup() {
36 const treeRef = ref<Nullable<TreeActionType>>(null); 39 const treeRef = ref<Nullable<TreeActionType>>(null);
37 const { createMessage } = useMessage(); 40 const { createMessage } = useMessage();
@@ -75,6 +78,14 @@ @@ -75,6 +78,14 @@
75 createMessage.success(JSON.stringify(keys)); 78 createMessage.success(JSON.stringify(keys));
76 } 79 }
77 80
  81 + function checkAll(checkAll: boolean) {
  82 + getTree().checkAll(checkAll);
  83 + }
  84 +
  85 + function expandAll(checkAll: boolean) {
  86 + getTree().expandAll(checkAll);
  87 + }
  88 +
78 function appendNodeByKey(parentKey: string | null = null) { 89 function appendNodeByKey(parentKey: string | null = null) {
79 getTree().insertNodeByKey({ 90 getTree().insertNodeByKey({
80 parentKey: parentKey, 91 parentKey: parentKey,
@@ -112,6 +123,8 @@ @@ -112,6 +123,8 @@
112 appendNodeByKey, 123 appendNodeByKey,
113 deleteNodeByKey, 124 deleteNodeByKey,
114 updateNodeByKey, 125 updateNodeByKey,
  126 + checkAll,
  127 + expandAll,
115 }; 128 };
116 }, 129 },
117 }); 130 });
src/views/demo/tree/EditTree.vue
1 <template> 1 <template>
2 <PageWrapper title="Tree函数操作示例"> 2 <PageWrapper title="Tree函数操作示例">
3 <div class="flex"> 3 <div class="flex">
4 - <CollapseContainer title="右侧操作按钮/自定义图标" class="mr-4" :style="{ width: '33%' }">  
5 - <BasicTree :treeData="treeData" :actionList="actionList" :renderIcon="createIcon" />  
6 - </CollapseContainer>  
7 -  
8 - <CollapseContainer title="右键菜单" class="mr-4" :style="{ width: '33%' }">  
9 - <BasicTree :treeData="treeData" :beforeRightClick="getRightMenuList" />  
10 - </CollapseContainer> 4 + <BasicTree
  5 + class="w-1/3"
  6 + title="右侧操作按钮/自定义图标"
  7 + helpMessage="帮助信息"
  8 + :treeData="treeData"
  9 + :actionList="actionList"
  10 + :renderIcon="createIcon"
  11 + />
  12 + <BasicTree
  13 + class="w-1/3 mx-4"
  14 + title="右键菜单"
  15 + :treeData="treeData"
  16 + :beforeRightClick="getRightMenuList"
  17 + />
  18 + <BasicTree
  19 + class="w-1/3"
  20 + title="工具栏使用"
  21 + toolbar
  22 + checkable
  23 + search
  24 + :treeData="treeData"
  25 + :beforeRightClick="getRightMenuList"
  26 + />
11 </div> 27 </div>
12 </PageWrapper> 28 </PageWrapper>
13 </template> 29 </template>
@@ -15,12 +31,11 @@ @@ -15,12 +31,11 @@
15 import { defineComponent, h } from 'vue'; 31 import { defineComponent, h } from 'vue';
16 import { BasicTree, ActionItem, ContextMenuItem } from '/@/components/Tree/index'; 32 import { BasicTree, ActionItem, ContextMenuItem } from '/@/components/Tree/index';
17 import { treeData } from './data'; 33 import { treeData } from './data';
18 - import { CollapseContainer } from '/@/components/Container/index';  
19 import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'; 34 import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
20 import { PageWrapper } from '/@/components/Page'; 35 import { PageWrapper } from '/@/components/Page';
21 36
22 export default defineComponent({ 37 export default defineComponent({
23 - components: { BasicTree, CollapseContainer, PageWrapper }, 38 + components: { BasicTree, PageWrapper },
24 setup() { 39 setup() {
25 function handlePlus(node: any) { 40 function handlePlus(node: any) {
26 console.log(node); 41 console.log(node);
src/views/demo/tree/index.vue
1 <template> 1 <template>
2 <PageWrapper title="Tree基础示例"> 2 <PageWrapper title="Tree基础示例">
3 <div class="flex"> 3 <div class="flex">
4 - <CollapseContainer title="基础示例" :style="{ width: '33%' }" class="mr-4">  
5 - <BasicTree :treeData="treeData" />  
6 - </CollapseContainer> 4 + <BasicTree :treeData="treeData" title="基础示例" class="w-1/3" />
7 5
8 - <CollapseContainer title="可勾选" class="mr-4" :style="{ width: '33%' }">  
9 - <BasicTree :treeData="treeData" :checkable="true" />  
10 - </CollapseContainer> 6 + <BasicTree :treeData="treeData" title="可勾选" :checkable="true" class="w-1/3 mx-4" />
11 7
12 - <CollapseContainer title="默认展开/勾选示例" :style="{ width: '33%' }">  
13 - <BasicTree  
14 - :treeData="treeData"  
15 - :checkable="true"  
16 - :expandedKeys="['0-0']"  
17 - :checkedKeys="['0-0']"  
18 - />  
19 - </CollapseContainer> 8 + <BasicTree
  9 + title="默认展开/勾选示例"
  10 + :treeData="treeData"
  11 + :checkable="true"
  12 + :expandedKeys="['0-0']"
  13 + :checkedKeys="['0-0']"
  14 + class="w-1/3"
  15 + />
20 </div> 16 </div>
21 </PageWrapper> 17 </PageWrapper>
22 </template> 18 </template>
@@ -24,11 +20,10 @@ @@ -24,11 +20,10 @@
24 import { defineComponent } from 'vue'; 20 import { defineComponent } from 'vue';
25 import { BasicTree } from '/@/components/Tree/index'; 21 import { BasicTree } from '/@/components/Tree/index';
26 import { treeData } from './data'; 22 import { treeData } from './data';
27 - import { CollapseContainer } from '/@/components/Container/index';  
28 import { PageWrapper } from '/@/components/Page'; 23 import { PageWrapper } from '/@/components/Page';
29 24
30 export default defineComponent({ 25 export default defineComponent({
31 - components: { BasicTree, CollapseContainer, PageWrapper }, 26 + components: { BasicTree, PageWrapper },
32 setup() { 27 setup() {
33 return { treeData }; 28 return { treeData };
34 }, 29 },
yarn.lock
@@ -1791,10 +1791,10 @@ @@ -1791,10 +1791,10 @@
1791 micromatch "^4.0.2" 1791 micromatch "^4.0.2"
1792 windicss "^2.2.3" 1792 windicss "^2.2.3"
1793 1793
1794 -"@zxcvbn-ts/core@^0.2.0":  
1795 - version "0.2.0"  
1796 - resolved "https://registry.npmjs.org/@zxcvbn-ts/core/-/core-0.2.0.tgz#ba3af1fed2213464ae12c0ab565798590afe8ef7"  
1797 - integrity sha512-1NVKw2Tz3Iv3NE4RFTTcF2EQlmHfkNi48U0H80ZR/KLt3ANOFsCDp/mxGawdzCnBrf64E121xI49mpZDAACYZw== 1794 +"@zxcvbn-ts/core@^0.3.0":
  1795 + version "0.3.0"
  1796 + resolved "https://registry.npmjs.org/@zxcvbn-ts/core/-/core-0.3.0.tgz#1a021afef29b97a5f8f72458de005fa149628e32"
  1797 + integrity sha512-H1SOAoC7MbccN/CU9ENZHXwvwTwh6aRt88SOkGROAN9nT88o/qDPJ5B5bElRSbjKLfmmO1LqK2K4u2lUxjbQKQ==
1798 1798
1799 JSONStream@^1.0.4: 1799 JSONStream@^1.0.4:
1800 version "1.3.5" 1800 version "1.3.5"