Commit 72b42d7b3539919a9baa4f1a7316842f85991c1e
1 parent
d67bd496
feat(tree): add renderIcon props close #309
Showing
11 changed files
with
313 additions
and
332 deletions
CHANGELOG.zh_CN.md
src/components/Tree/index.ts
1 | -import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
2 | - | |
3 | -export const BasicTree = createAsyncComponent(() => import('./src/BasicTree')); | |
1 | +import BasicTree from './src/index.vue'; | |
4 | 2 | |
3 | +export { BasicTree }; | |
5 | 4 | export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
6 | 5 | export * from './src/types'; | ... | ... |
src/components/Tree/src/TreeIcon.ts
0 → 100644
1 | +import type { VNode, FunctionalComponent } from 'vue'; | |
2 | + | |
3 | +import { h } from 'vue'; | |
4 | +import { isString } from '/@/utils/is'; | |
5 | +import { Icon } from '/@/components/Icon'; | |
6 | + | |
7 | +export interface ComponentProps { | |
8 | + icon: VNode | string; | |
9 | +} | |
10 | + | |
11 | +export const TreeIcon: FunctionalComponent = ({ icon }: ComponentProps) => { | |
12 | + if (!icon) return null; | |
13 | + if (isString(icon)) { | |
14 | + return h(Icon, { icon, class: 'mr-1' }); | |
15 | + } | |
16 | + return Icon; | |
17 | +}; | ... | ... |
src/components/Tree/src/index.less deleted
100644 → 0
1 | -.basic-tree { | |
2 | - position: relative; | |
3 | - | |
4 | - &-title { | |
5 | - position: relative; | |
6 | - display: inline-block; | |
7 | - width: 100%; | |
8 | - padding-right: 10px; | |
9 | - | |
10 | - &:hover { | |
11 | - .basic-tree__action { | |
12 | - visibility: visible; | |
13 | - } | |
14 | - } | |
15 | - } | |
16 | - | |
17 | - &__content { | |
18 | - display: inline-block; | |
19 | - overflow: hidden; | |
20 | - } | |
21 | - | |
22 | - &__actions { | |
23 | - position: absolute; | |
24 | - top: 0; | |
25 | - right: 0; | |
26 | - display: flex; | |
27 | - } | |
28 | - | |
29 | - &__action { | |
30 | - margin-left: 4px; | |
31 | - // float: right; | |
32 | - // display: none; | |
33 | - visibility: hidden; | |
34 | - } | |
35 | -} |
src/components/Tree/src/BasicTree.tsx renamed to src/components/Tree/src/index.vue
1 | -import './index.less'; | |
2 | - | |
3 | -import type { ReplaceFields, TreeItem, Keys, CheckKeys, TreeActionType } from './types'; | |
4 | - | |
5 | -import { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue'; | |
6 | -import { Tree } from 'ant-design-vue'; | |
7 | -import { DownOutlined } from '@ant-design/icons-vue'; | |
8 | - | |
9 | -import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu'; | |
10 | - | |
11 | -import { isFunction } from '/@/utils/is'; | |
12 | -import { omit } from 'lodash-es'; | |
13 | -import { extendSlots } from '/@/utils/helper/tsxHelper'; | |
14 | - | |
15 | -import { basicProps } from './props'; | |
16 | -import { useTree } from './useTree'; | |
17 | -import { useExpose } from '/@/hooks/core/useExpose'; | |
18 | -import { onMounted } from 'vue'; | |
19 | - | |
20 | -interface State { | |
21 | - expandedKeys: Keys; | |
22 | - selectedKeys: Keys; | |
23 | - checkedKeys: CheckKeys; | |
24 | -} | |
25 | -const prefixCls = 'basic-tree'; | |
26 | -export default defineComponent({ | |
27 | - name: 'BasicTree', | |
28 | - props: basicProps, | |
29 | - emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'get'], | |
30 | - setup(props, { attrs, slots, emit }) { | |
31 | - const state = reactive<State>({ | |
32 | - expandedKeys: props.expandedKeys || [], | |
33 | - selectedKeys: props.selectedKeys || [], | |
34 | - checkedKeys: props.checkedKeys || [], | |
35 | - }); | |
36 | - | |
37 | - const treeDataRef = ref<TreeItem[]>([]); | |
38 | - | |
39 | - const [createContextMenu] = useContextMenu(); | |
40 | - | |
41 | - const getReplaceFields = computed( | |
42 | - (): Required<ReplaceFields> => { | |
43 | - const { replaceFields } = props; | |
44 | - return { | |
45 | - children: 'children', | |
46 | - title: 'title', | |
47 | - key: 'key', | |
48 | - ...replaceFields, | |
1 | +<script lang="tsx"> | |
2 | + import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types'; | |
3 | + | |
4 | + import { defineComponent, reactive, computed, unref, ref, watchEffect, onMounted } from 'vue'; | |
5 | + import { Tree } from 'ant-design-vue'; | |
6 | + import { TreeIcon } from './TreeIcon'; | |
7 | + // import { DownOutlined } from '@ant-design/icons-vue'; | |
8 | + | |
9 | + import { omit, get } from 'lodash-es'; | |
10 | + import { isFunction } from '/@/utils/is'; | |
11 | + import { extendSlots } from '/@/utils/helper/tsxHelper'; | |
12 | + | |
13 | + import { useTree } from './useTree'; | |
14 | + import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu'; | |
15 | + import { useExpose } from '/@/hooks/core/useExpose'; | |
16 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
17 | + | |
18 | + import { basicProps } from './props'; | |
19 | + | |
20 | + interface State { | |
21 | + expandedKeys: Keys; | |
22 | + selectedKeys: Keys; | |
23 | + checkedKeys: CheckKeys; | |
24 | + } | |
25 | + export default defineComponent({ | |
26 | + name: 'BasicTree', | |
27 | + props: basicProps, | |
28 | + emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'get'], | |
29 | + setup(props, { attrs, slots, emit }) { | |
30 | + const state = reactive<State>({ | |
31 | + expandedKeys: props.expandedKeys || [], | |
32 | + selectedKeys: props.selectedKeys || [], | |
33 | + checkedKeys: props.checkedKeys || [], | |
34 | + }); | |
35 | + | |
36 | + const treeDataRef = ref<TreeItem[]>([]); | |
37 | + | |
38 | + const [createContextMenu] = useContextMenu(); | |
39 | + const { prefixCls } = useDesign('basic-tree'); | |
40 | + | |
41 | + const getReplaceFields = computed( | |
42 | + (): Required<ReplaceFields> => { | |
43 | + const { replaceFields } = props; | |
44 | + return { | |
45 | + children: 'children', | |
46 | + title: 'title', | |
47 | + key: 'key', | |
48 | + ...replaceFields, | |
49 | + }; | |
50 | + } | |
51 | + ); | |
52 | + | |
53 | + // const getContentStyle = computed( | |
54 | + // (): CSSProperties => { | |
55 | + // const { actionList } = props; | |
56 | + // const width = actionList.length * 18; | |
57 | + // return { | |
58 | + // width: `calc(100% - ${width}px)`, | |
59 | + // }; | |
60 | + // } | |
61 | + // ); | |
62 | + | |
63 | + const getBindValues = computed(() => { | |
64 | + let propsData = { | |
65 | + blockNode: true, | |
66 | + ...attrs, | |
67 | + ...props, | |
68 | + expandedKeys: state.expandedKeys, | |
69 | + selectedKeys: state.selectedKeys, | |
70 | + checkedKeys: state.checkedKeys, | |
71 | + replaceFields: unref(getReplaceFields), | |
72 | + 'onUpdate:expandedKeys': (v: Keys) => { | |
73 | + state.expandedKeys = v; | |
74 | + emit('update:expandedKeys', v); | |
75 | + }, | |
76 | + 'onUpdate:selectedKeys': (v: Keys) => { | |
77 | + state.selectedKeys = v; | |
78 | + emit('update:selectedKeys', v); | |
79 | + }, | |
80 | + onCheck: (v: CheckKeys, e) => { | |
81 | + state.checkedKeys = v; | |
82 | + console.log(e); | |
83 | + emit('update:value', v); | |
84 | + }, | |
85 | + onRightClick: handleRightClick, | |
49 | 86 | }; |
87 | + propsData = omit(propsData, 'treeData'); | |
88 | + return propsData; | |
89 | + }); | |
90 | + | |
91 | + const getTreeData = computed((): TreeItem[] => unref(treeDataRef)); | |
92 | + | |
93 | + const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey } = useTree( | |
94 | + treeDataRef, | |
95 | + getReplaceFields | |
96 | + ); | |
97 | + | |
98 | + function getIcon(params: Recordable, icon?: string) { | |
99 | + if (!icon) { | |
100 | + if (props.renderIcon && isFunction(props.renderIcon)) { | |
101 | + return props.renderIcon(params); | |
102 | + } | |
103 | + } | |
104 | + return icon; | |
50 | 105 | } |
51 | - ); | |
52 | 106 | |
53 | - const getContentStyle = computed( | |
54 | - (): CSSProperties => { | |
107 | + function renderAction(node: TreeItem) { | |
55 | 108 | const { actionList } = props; |
56 | - const width = actionList.length * 18; | |
57 | - return { | |
58 | - width: `calc(100% - ${width}px)`, | |
59 | - }; | |
109 | + if (!actionList || actionList.length === 0) return; | |
110 | + return actionList.map((item, index) => { | |
111 | + return ( | |
112 | + <span key={index} class={`${prefixCls}__action`}> | |
113 | + {item.render(node)} | |
114 | + </span> | |
115 | + ); | |
116 | + }); | |
60 | 117 | } |
61 | - ); | |
62 | - | |
63 | - const getBindValues = computed(() => { | |
64 | - let propsData = { | |
65 | - blockNode: true, | |
66 | - ...attrs, | |
67 | - ...props, | |
68 | - expandedKeys: state.expandedKeys, | |
69 | - selectedKeys: state.selectedKeys, | |
70 | - checkedKeys: state.checkedKeys, | |
71 | - replaceFields: unref(getReplaceFields), | |
72 | - 'onUpdate:expandedKeys': (v: Keys) => { | |
73 | - state.expandedKeys = v; | |
74 | - emit('update:expandedKeys', v); | |
75 | - }, | |
76 | - 'onUpdate:selectedKeys': (v: Keys) => { | |
77 | - state.selectedKeys = v; | |
78 | - emit('update:selectedKeys', v); | |
79 | - }, | |
80 | - onCheck: (v: CheckKeys) => { | |
81 | - state.checkedKeys = v; | |
82 | - emit('update:value', v); | |
83 | - }, | |
84 | - onRightClick: handleRightClick, | |
85 | - }; | |
86 | - propsData = omit(propsData, 'treeData'); | |
87 | - return propsData; | |
88 | - }); | |
89 | 118 | |
90 | - const getTreeData = computed((): TreeItem[] => unref(treeDataRef)); | |
119 | + function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) { | |
120 | + if (!data) { | |
121 | + return null; | |
122 | + } | |
123 | + return data.map((item) => { | |
124 | + const { title: titleField, key: keyField, children: childrenField } = unref( | |
125 | + getReplaceFields | |
126 | + ); | |
91 | 127 | |
92 | - const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey } = useTree( | |
93 | - treeDataRef, | |
94 | - getReplaceFields | |
95 | - ); | |
128 | + const propsData = omit(item, 'title'); | |
129 | + const icon = getIcon({ ...item, level }, item.icon); | |
130 | + return ( | |
131 | + <Tree.TreeNode {...propsData} key={get(item, keyField)}> | |
132 | + {{ | |
133 | + title: () => ( | |
134 | + <span class={`${prefixCls}-title`}> | |
135 | + {icon && <TreeIcon icon={icon} />} | |
136 | + <span | |
137 | + class={`${prefixCls}__content`} | |
138 | + // style={unref(getContentStyle)} | |
139 | + > | |
140 | + {get(item, titleField)} | |
141 | + </span> | |
142 | + <span class={`${prefixCls}__actions`}> {renderAction(item)}</span> | |
143 | + </span> | |
144 | + ), | |
145 | + default: () => | |
146 | + renderTreeNode({ data: get(item, childrenField) || [], level: level + 1 }), | |
147 | + }} | |
148 | + </Tree.TreeNode> | |
149 | + ); | |
150 | + }); | |
151 | + } | |
96 | 152 | |
97 | - // 渲染操作按钮 | |
98 | - function renderAction(node: TreeItem) { | |
99 | - const { actionList } = props; | |
153 | + async function handleRightClick({ event, node }: any) { | |
154 | + const { rightMenuList: menuList = [], beforeRightClick } = props; | |
155 | + let rightMenuList: ContextMenuItem[] = []; | |
100 | 156 | |
101 | - if (!actionList || actionList.length === 0) return; | |
157 | + if (beforeRightClick && isFunction(beforeRightClick)) { | |
158 | + rightMenuList = await beforeRightClick(node); | |
159 | + } else { | |
160 | + rightMenuList = menuList; | |
161 | + } | |
162 | + if (!rightMenuList.length) return; | |
163 | + createContextMenu({ | |
164 | + event, | |
165 | + items: rightMenuList, | |
166 | + }); | |
167 | + } | |
102 | 168 | |
103 | - return actionList.map((item, index) => { | |
104 | - return ( | |
105 | - <span key={index} class={`${prefixCls}__action`}> | |
106 | - {item.render(node)} | |
107 | - </span> | |
108 | - ); | |
109 | - }); | |
110 | - } | |
111 | - // 渲染树节点 | |
112 | - function renderTreeNode({ data }: { data: TreeItem[] | undefined }) { | |
113 | - if (!data) { | |
114 | - return null; | |
169 | + function setExpandedKeys(keys: string[]) { | |
170 | + state.expandedKeys = keys; | |
115 | 171 | } |
116 | - return data.map((item) => { | |
117 | - const { title: titleField, key: keyField, children: childrenField } = unref( | |
118 | - getReplaceFields | |
119 | - ); | |
120 | - const propsData = omit(item, 'title'); | |
121 | - const anyItem = item as any; | |
172 | + | |
173 | + function getExpandedKeys() { | |
174 | + return state.expandedKeys; | |
175 | + } | |
176 | + function setSelectedKeys(keys: string[]) { | |
177 | + state.selectedKeys = keys; | |
178 | + } | |
179 | + | |
180 | + function getSelectedKeys() { | |
181 | + return state.selectedKeys; | |
182 | + } | |
183 | + | |
184 | + function setCheckedKeys(keys: CheckKeys) { | |
185 | + state.checkedKeys = keys; | |
186 | + } | |
187 | + | |
188 | + function getCheckedKeys() { | |
189 | + return state.checkedKeys; | |
190 | + } | |
191 | + | |
192 | + watchEffect(() => { | |
193 | + treeDataRef.value = props.treeData as TreeItem[]; | |
194 | + state.expandedKeys = props.expandedKeys; | |
195 | + state.selectedKeys = props.selectedKeys; | |
196 | + state.checkedKeys = props.checkedKeys; | |
197 | + }); | |
198 | + | |
199 | + const instance: TreeActionType = { | |
200 | + setExpandedKeys, | |
201 | + getExpandedKeys, | |
202 | + setSelectedKeys, | |
203 | + getSelectedKeys, | |
204 | + setCheckedKeys, | |
205 | + getCheckedKeys, | |
206 | + insertNodeByKey, | |
207 | + deleteNodeByKey, | |
208 | + updateNodeByKey, | |
209 | + filterByLevel: (level: number) => { | |
210 | + state.expandedKeys = filterByLevel(level); | |
211 | + }, | |
212 | + }; | |
213 | + | |
214 | + useExpose<TreeActionType>(instance); | |
215 | + | |
216 | + onMounted(() => { | |
217 | + emit('get', instance); | |
218 | + }); | |
219 | + | |
220 | + return () => { | |
122 | 221 | return ( |
123 | - <Tree.TreeNode {...propsData} key={anyItem?.[keyField]}> | |
222 | + <Tree {...unref(getBindValues)} showIcon={false} class={[prefixCls]}> | |
124 | 223 | {{ |
125 | - title: () => ( | |
126 | - <span class={`${prefixCls}-title`}> | |
127 | - <span class={`${prefixCls}__content`} style={unref(getContentStyle)}> | |
128 | - {titleField && anyItem[titleField]} | |
129 | - </span> | |
130 | - <span class={`${prefixCls}__actions`}> {renderAction(item)}</span> | |
131 | - </span> | |
132 | - ), | |
133 | - default: () => renderTreeNode({ data: childrenField ? anyItem[childrenField] : [] }), | |
224 | + // switcherIcon: () => <DownOutlined />, | |
225 | + default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }), | |
226 | + ...extendSlots(slots), | |
134 | 227 | }} |
135 | - </Tree.TreeNode> | |
228 | + </Tree> | |
136 | 229 | ); |
137 | - }); | |
138 | - } | |
230 | + }; | |
231 | + }, | |
232 | + }); | |
233 | +</script> | |
234 | +<style lang="less"> | |
235 | + @prefix-cls: ~'@{namespace}-basic-tree'; | |
236 | + | |
237 | + .@{prefix-cls} { | |
238 | + position: relative; | |
139 | 239 | |
140 | - // 处理右键事件 | |
141 | - async function handleRightClick({ event, node }: any) { | |
142 | - const { rightMenuList: menuList = [], beforeRightClick } = props; | |
143 | - let rightMenuList: ContextMenuItem[] = []; | |
144 | - if (beforeRightClick && isFunction(beforeRightClick)) { | |
145 | - rightMenuList = await beforeRightClick(node); | |
146 | - } else { | |
147 | - rightMenuList = menuList; | |
240 | + .ant-tree-node-content-wrapper { | |
241 | + position: relative; | |
242 | + | |
243 | + .ant-tree-title { | |
244 | + position: absolute; | |
245 | + left: 0; | |
246 | + width: 100%; | |
148 | 247 | } |
149 | - if (!rightMenuList.length) return; | |
150 | - createContextMenu({ | |
151 | - event, | |
152 | - items: rightMenuList, | |
153 | - }); | |
154 | 248 | } |
155 | 249 | |
156 | - function setExpandedKeys(keys: string[]) { | |
157 | - state.expandedKeys = keys; | |
158 | - } | |
250 | + &-title { | |
251 | + position: relative; | |
252 | + display: flex; | |
253 | + align-items: center; | |
254 | + width: 100%; | |
255 | + padding-right: 10px; | |
159 | 256 | |
160 | - function getExpandedKeys() { | |
161 | - return state.expandedKeys; | |
162 | - } | |
163 | - function setSelectedKeys(keys: string[]) { | |
164 | - state.selectedKeys = keys; | |
257 | + &:hover { | |
258 | + .@{prefix-cls}__action { | |
259 | + visibility: visible; | |
260 | + } | |
261 | + } | |
165 | 262 | } |
166 | 263 | |
167 | - function getSelectedKeys() { | |
168 | - return state.selectedKeys; | |
264 | + &__content { | |
265 | + display: inline-block; | |
266 | + overflow: hidden; | |
169 | 267 | } |
170 | 268 | |
171 | - function setCheckedKeys(keys: CheckKeys) { | |
172 | - state.checkedKeys = keys; | |
269 | + &__actions { | |
270 | + position: absolute; | |
271 | + top: 2px; | |
272 | + right: 2px; | |
273 | + display: flex; | |
173 | 274 | } |
174 | 275 | |
175 | - function getCheckedKeys() { | |
176 | - return state.checkedKeys; | |
276 | + &__action { | |
277 | + margin-left: 4px; | |
278 | + visibility: hidden; | |
177 | 279 | } |
178 | - | |
179 | - watchEffect(() => { | |
180 | - treeDataRef.value = props.treeData as TreeItem[]; | |
181 | - state.expandedKeys = props.expandedKeys; | |
182 | - state.selectedKeys = props.selectedKeys; | |
183 | - state.checkedKeys = props.checkedKeys; | |
184 | - }); | |
185 | - | |
186 | - const instance: TreeActionType = { | |
187 | - setExpandedKeys, | |
188 | - getExpandedKeys, | |
189 | - setSelectedKeys, | |
190 | - getSelectedKeys, | |
191 | - setCheckedKeys, | |
192 | - getCheckedKeys, | |
193 | - insertNodeByKey, | |
194 | - deleteNodeByKey, | |
195 | - updateNodeByKey, | |
196 | - filterByLevel: (level: number) => { | |
197 | - state.expandedKeys = filterByLevel(level); | |
198 | - }, | |
199 | - }; | |
200 | - | |
201 | - useExpose<TreeActionType>(instance); | |
202 | - | |
203 | - onMounted(() => { | |
204 | - emit('get', instance); | |
205 | - }); | |
206 | - | |
207 | - return () => { | |
208 | - return ( | |
209 | - <Tree {...(unref(getBindValues) as any)} class={prefixCls}> | |
210 | - {{ | |
211 | - switcherIcon: () => <DownOutlined />, | |
212 | - default: () => renderTreeNode({ data: unref(getTreeData) }), | |
213 | - ...extendSlots(slots), | |
214 | - }} | |
215 | - </Tree> | |
216 | - ); | |
217 | - }; | |
218 | - }, | |
219 | -}); | |
280 | + } | |
281 | +</style> | ... | ... |
src/components/Tree/src/props.ts
1 | -import { PropType } from 'vue'; | |
2 | -import type { ReplaceFields, TreeItem, ActionItem, Keys, CheckKeys } from './types'; | |
1 | +import type { PropType } from 'vue'; | |
2 | +import type { ReplaceFields, ActionItem, Keys, CheckKeys } from './types'; | |
3 | 3 | import type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
4 | +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; | |
4 | 5 | |
5 | 6 | export const basicProps = { |
7 | + renderIcon: { | |
8 | + type: Function as PropType<(params: Recordable) => string>, | |
9 | + }, | |
6 | 10 | replaceFields: { |
7 | 11 | type: Object as PropType<ReplaceFields>, |
8 | 12 | }, |
9 | 13 | |
10 | 14 | treeData: { |
11 | - type: Array as PropType<TreeItem[]>, | |
15 | + type: Array as PropType<TreeDataItem[]>, | |
12 | 16 | }, |
13 | 17 | |
14 | 18 | actionList: { |
... | ... | @@ -50,7 +54,7 @@ export const treeNodeProps = { |
50 | 54 | type: Object as PropType<ReplaceFields>, |
51 | 55 | }, |
52 | 56 | treeData: { |
53 | - type: Array as PropType<TreeItem[]>, | |
57 | + type: Array as PropType<TreeDataItem[]>, | |
54 | 58 | default: () => [], |
55 | 59 | }, |
56 | 60 | }; | ... | ... |
src/components/Tree/src/types.ts
1 | +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; | |
1 | 2 | export interface ActionItem { |
2 | 3 | render: (record: any) => any; |
3 | 4 | } |
4 | 5 | |
5 | -export interface TreeItem { | |
6 | - /** | |
7 | - * Class | |
8 | - * @description className | |
9 | - * @type string | |
10 | - */ | |
11 | - class?: string; | |
12 | - | |
13 | - /** | |
14 | - * Style | |
15 | - * @description style of tree node | |
16 | - * @type string | object | |
17 | - */ | |
18 | - style?: string | object; | |
19 | - | |
20 | - /** | |
21 | - * Disable Checkbox | |
22 | - * @description Disables the checkbox of the treeNode | |
23 | - * @default false | |
24 | - * @type boolean | |
25 | - */ | |
26 | - disableCheckbox?: boolean; | |
27 | - | |
28 | - /** | |
29 | - * Disabled | |
30 | - * @description Disabled or not | |
31 | - * @default false | |
32 | - * @type boolean | |
33 | - */ | |
34 | - disabled?: boolean; | |
35 | - | |
36 | - /** | |
37 | - * Icon | |
38 | - * @description customize icon. When you pass component, whose render will receive full TreeNode props as component props | |
39 | - * @type any (slot | slot-scope) | |
40 | - */ | |
6 | +export interface TreeItem extends TreeDataItem { | |
41 | 7 | icon?: any; |
42 | - | |
43 | - /** | |
44 | - * Is Leaf? | |
45 | - * @description Leaf node or not | |
46 | - * @default false | |
47 | - * @type boolean | |
48 | - */ | |
49 | - isLeaf?: boolean; | |
50 | - | |
51 | - /** | |
52 | - * Key | |
53 | - * @description Required property, should be unique in the tree | |
54 | - * (In tree: Used with (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys) | |
55 | - * @default internal calculated position of treeNode or undefined | |
56 | - * @type string | number | |
57 | - */ | |
58 | - key: string | number; | |
59 | - | |
60 | - /** | |
61 | - * Selectable | |
62 | - * @description Set whether the treeNode can be selected | |
63 | - * @default true | |
64 | - * @type boolean | |
65 | - */ | |
66 | - selectable?: boolean; | |
67 | - | |
68 | - /** | |
69 | - * Title | |
70 | - * @description Content showed on the treeNodes | |
71 | - * @default '---' | |
72 | - * @type any (string | slot) | |
73 | - */ | |
74 | - title: any; | |
75 | - | |
76 | - /** | |
77 | - * Value | |
78 | - * @description Will be treated as treeNodeFilterProp by default, should be unique in the tree | |
79 | - * @default undefined | |
80 | - * @type string | |
81 | - */ | |
82 | - value?: string; | |
83 | - children?: TreeItem[]; | |
84 | - slots?: any; | |
85 | - scopedSlots?: any; | |
86 | 8 | } |
87 | 9 | |
88 | 10 | export interface ReplaceFields { |
... | ... | @@ -107,12 +29,12 @@ export interface TreeActionType { |
107 | 29 | filterByLevel: (level: number) => void; |
108 | 30 | insertNodeByKey: (opt: InsertNodeParams) => void; |
109 | 31 | deleteNodeByKey: (key: string) => void; |
110 | - updateNodeByKey: (key: string, node: Omit<TreeItem, 'key'>) => void; | |
32 | + updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void; | |
111 | 33 | } |
112 | 34 | |
113 | 35 | export interface InsertNodeParams { |
114 | 36 | parentKey: string | null; |
115 | - node: TreeItem; | |
116 | - list?: TreeItem[]; | |
37 | + node: TreeDataItem; | |
38 | + list?: TreeDataItem[]; | |
117 | 39 | push?: 'push' | 'unshift'; |
118 | 40 | } | ... | ... |
src/components/Tree/src/useTree.ts
1 | -import type { InsertNodeParams, ReplaceFields, TreeItem } from './types'; | |
1 | +import type { InsertNodeParams, ReplaceFields } from './types'; | |
2 | 2 | import type { Ref, ComputedRef } from 'vue'; |
3 | +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; | |
3 | 4 | |
4 | 5 | import { cloneDeep } from 'lodash-es'; |
5 | 6 | import { unref } from 'vue'; |
6 | 7 | import { forEach } from '/@/utils/helper/treeHelper'; |
7 | 8 | |
8 | 9 | export function useTree( |
9 | - treeDataRef: Ref<TreeItem[]>, | |
10 | + treeDataRef: Ref<TreeDataItem[]>, | |
10 | 11 | getReplaceFields: ComputedRef<ReplaceFields> |
11 | 12 | ) { |
12 | - // 更新节点 | |
13 | - function updateNodeByKey(key: string, node: TreeItem, list?: TreeItem[]) { | |
13 | + // Update node | |
14 | + function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) { | |
14 | 15 | if (!key) return; |
15 | 16 | const treeData = list || unref(treeDataRef); |
16 | 17 | const { key: keyField, children: childrenField } = unref(getReplaceFields); |
... | ... | @@ -30,8 +31,8 @@ export function useTree( |
30 | 31 | } |
31 | 32 | } |
32 | 33 | |
33 | - // 展开指定级别 | |
34 | - function filterByLevel(level = 1, list?: TreeItem[], currentLevel = 1) { | |
34 | + // Expand the specified level | |
35 | + function filterByLevel(level = 1, list?: TreeDataItem[], currentLevel = 1) { | |
35 | 36 | if (!level) { |
36 | 37 | return []; |
37 | 38 | } |
... | ... | @@ -74,8 +75,8 @@ export function useTree( |
74 | 75 | treeDataRef.value = treeData; |
75 | 76 | } |
76 | 77 | |
77 | - // 删除节点 | |
78 | - function deleteNodeByKey(key: string, list?: TreeItem[]) { | |
78 | + // Delete node | |
79 | + function deleteNodeByKey(key: string, list?: TreeDataItem[]) { | |
79 | 80 | if (!key) return; |
80 | 81 | const treeData = list || unref(treeDataRef); |
81 | 82 | const { key: keyField, children: childrenField } = unref(getReplaceFields); | ... | ... |
src/views/demo/tree/ActionTree.vue
src/views/demo/tree/EditTree.vue
1 | 1 | <template> |
2 | 2 | <PageWrapper title="Tree函数操作示例"> |
3 | 3 | <div class="flex"> |
4 | - <CollapseContainer title="右侧操作按钮" class="mr-4" :style="{ width: '33%' }"> | |
5 | - <BasicTree :treeData="treeData" :actionList="actionList" /> | |
4 | + <CollapseContainer title="右侧操作按钮/自定义图标" class="mr-4" :style="{ width: '33%' }"> | |
5 | + <BasicTree :treeData="treeData" :actionList="actionList" :renderIcon="createIcon" /> | |
6 | 6 | </CollapseContainer> |
7 | 7 | |
8 | 8 | <CollapseContainer title="右键菜单" class="mr-4" :style="{ width: '33%' }"> |
... | ... | @@ -61,7 +61,19 @@ |
61 | 61 | }, |
62 | 62 | }, |
63 | 63 | ]; |
64 | - return { treeData, actionList, getRightMenuList }; | |
64 | + | |
65 | + function createIcon({ level }) { | |
66 | + if (level === 1) { | |
67 | + return 'ion:git-compare-outline'; | |
68 | + } | |
69 | + if (level === 2) { | |
70 | + return 'ion:home'; | |
71 | + } | |
72 | + if (level === 3) { | |
73 | + return 'ion:airplane'; | |
74 | + } | |
75 | + } | |
76 | + return { treeData, actionList, getRightMenuList, createIcon }; | |
65 | 77 | }, |
66 | 78 | }); |
67 | 79 | </script> | ... | ... |
src/views/demo/tree/data.ts
... | ... | @@ -2,9 +2,8 @@ import { TreeItem } from '/@/components/Tree/index'; |
2 | 2 | |
3 | 3 | export const treeData: TreeItem[] = [ |
4 | 4 | { |
5 | - title: 'parent 1parent ', | |
5 | + title: 'parent ', | |
6 | 6 | key: '0-0', |
7 | - icon: 'home|svg', | |
8 | 7 | children: [ |
9 | 8 | { title: 'leaf', key: '0-0-0' }, |
10 | 9 | { |
... | ... | @@ -20,7 +19,6 @@ export const treeData: TreeItem[] = [ |
20 | 19 | { |
21 | 20 | title: 'parent 2', |
22 | 21 | key: '1-1', |
23 | - icon: 'home|svg', | |
24 | 22 | children: [ |
25 | 23 | { title: 'leaf', key: '1-1-0' }, |
26 | 24 | { title: 'leaf', key: '1-1-1' }, |
... | ... | @@ -29,7 +27,6 @@ export const treeData: TreeItem[] = [ |
29 | 27 | { |
30 | 28 | title: 'parent 3', |
31 | 29 | key: '2-2', |
32 | - icon: 'home|svg', | |
33 | 30 | children: [ |
34 | 31 | { title: 'leaf', key: '2-2-0' }, |
35 | 32 | { title: 'leaf', key: '2-2-1' }, | ... | ... |