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 | export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; | 4 | export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
6 | export * from './src/types'; | 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 | const { actionList } = props; | 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 | return ( | 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 | 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 | 5 | ||
5 | export const basicProps = { | 6 | export const basicProps = { |
7 | + renderIcon: { | ||
8 | + type: Function as PropType<(params: Recordable) => string>, | ||
9 | + }, | ||
6 | replaceFields: { | 10 | replaceFields: { |
7 | type: Object as PropType<ReplaceFields>, | 11 | type: Object as PropType<ReplaceFields>, |
8 | }, | 12 | }, |
9 | 13 | ||
10 | treeData: { | 14 | treeData: { |
11 | - type: Array as PropType<TreeItem[]>, | 15 | + type: Array as PropType<TreeDataItem[]>, |
12 | }, | 16 | }, |
13 | 17 | ||
14 | actionList: { | 18 | actionList: { |
@@ -50,7 +54,7 @@ export const treeNodeProps = { | @@ -50,7 +54,7 @@ export const treeNodeProps = { | ||
50 | type: Object as PropType<ReplaceFields>, | 54 | type: Object as PropType<ReplaceFields>, |
51 | }, | 55 | }, |
52 | treeData: { | 56 | treeData: { |
53 | - type: Array as PropType<TreeItem[]>, | 57 | + type: Array as PropType<TreeDataItem[]>, |
54 | default: () => [], | 58 | default: () => [], |
55 | }, | 59 | }, |
56 | }; | 60 | }; |
src/components/Tree/src/types.ts
1 | +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; | ||
1 | export interface ActionItem { | 2 | export interface ActionItem { |
2 | render: (record: any) => any; | 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 | icon?: any; | 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 | export interface ReplaceFields { | 10 | export interface ReplaceFields { |
@@ -107,12 +29,12 @@ export interface TreeActionType { | @@ -107,12 +29,12 @@ export interface TreeActionType { | ||
107 | filterByLevel: (level: number) => void; | 29 | filterByLevel: (level: number) => void; |
108 | insertNodeByKey: (opt: InsertNodeParams) => void; | 30 | insertNodeByKey: (opt: InsertNodeParams) => void; |
109 | deleteNodeByKey: (key: string) => void; | 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 | export interface InsertNodeParams { | 35 | export interface InsertNodeParams { |
114 | parentKey: string | null; | 36 | parentKey: string | null; |
115 | - node: TreeItem; | ||
116 | - list?: TreeItem[]; | 37 | + node: TreeDataItem; |
38 | + list?: TreeDataItem[]; | ||
117 | push?: 'push' | 'unshift'; | 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 | 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 | 4 | ||
4 | import { cloneDeep } from 'lodash-es'; | 5 | import { cloneDeep } from 'lodash-es'; |
5 | import { unref } from 'vue'; | 6 | import { unref } from 'vue'; |
6 | import { forEach } from '/@/utils/helper/treeHelper'; | 7 | import { forEach } from '/@/utils/helper/treeHelper'; |
7 | 8 | ||
8 | export function useTree( | 9 | export function useTree( |
9 | - treeDataRef: Ref<TreeItem[]>, | 10 | + treeDataRef: Ref<TreeDataItem[]>, |
10 | getReplaceFields: ComputedRef<ReplaceFields> | 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 | if (!key) return; | 15 | if (!key) return; |
15 | const treeData = list || unref(treeDataRef); | 16 | const treeData = list || unref(treeDataRef); |
16 | const { key: keyField, children: childrenField } = unref(getReplaceFields); | 17 | const { key: keyField, children: childrenField } = unref(getReplaceFields); |
@@ -30,8 +31,8 @@ export function useTree( | @@ -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 | if (!level) { | 36 | if (!level) { |
36 | return []; | 37 | return []; |
37 | } | 38 | } |
@@ -74,8 +75,8 @@ export function useTree( | @@ -74,8 +75,8 @@ export function useTree( | ||
74 | treeDataRef.value = treeData; | 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 | if (!key) return; | 80 | if (!key) return; |
80 | const treeData = list || unref(treeDataRef); | 81 | const treeData = list || unref(treeDataRef); |
81 | const { key: keyField, children: childrenField } = unref(getReplaceFields); | 82 | const { key: keyField, children: childrenField } = unref(getReplaceFields); |
src/views/demo/tree/ActionTree.vue
@@ -35,6 +35,7 @@ | @@ -35,6 +35,7 @@ | ||
35 | setup() { | 35 | setup() { |
36 | const treeRef = ref<Nullable<TreeActionType>>(null); | 36 | const treeRef = ref<Nullable<TreeActionType>>(null); |
37 | const { createMessage } = useMessage(); | 37 | const { createMessage } = useMessage(); |
38 | + | ||
38 | function getTree() { | 39 | function getTree() { |
39 | const tree = unref(treeRef); | 40 | const tree = unref(treeRef); |
40 | if (!tree) { | 41 | if (!tree) { |
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" /> | 4 | + <CollapseContainer title="右侧操作按钮/自定义图标" class="mr-4" :style="{ width: '33%' }"> |
5 | + <BasicTree :treeData="treeData" :actionList="actionList" :renderIcon="createIcon" /> | ||
6 | </CollapseContainer> | 6 | </CollapseContainer> |
7 | 7 | ||
8 | <CollapseContainer title="右键菜单" class="mr-4" :style="{ width: '33%' }"> | 8 | <CollapseContainer title="右键菜单" class="mr-4" :style="{ width: '33%' }"> |
@@ -61,7 +61,19 @@ | @@ -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 | </script> | 79 | </script> |
src/views/demo/tree/data.ts
@@ -2,9 +2,8 @@ import { TreeItem } from '/@/components/Tree/index'; | @@ -2,9 +2,8 @@ import { TreeItem } from '/@/components/Tree/index'; | ||
2 | 2 | ||
3 | export const treeData: TreeItem[] = [ | 3 | export const treeData: TreeItem[] = [ |
4 | { | 4 | { |
5 | - title: 'parent 1parent ', | 5 | + title: 'parent ', |
6 | key: '0-0', | 6 | key: '0-0', |
7 | - icon: 'home|svg', | ||
8 | children: [ | 7 | children: [ |
9 | { title: 'leaf', key: '0-0-0' }, | 8 | { title: 'leaf', key: '0-0-0' }, |
10 | { | 9 | { |
@@ -20,7 +19,6 @@ export const treeData: TreeItem[] = [ | @@ -20,7 +19,6 @@ export const treeData: TreeItem[] = [ | ||
20 | { | 19 | { |
21 | title: 'parent 2', | 20 | title: 'parent 2', |
22 | key: '1-1', | 21 | key: '1-1', |
23 | - icon: 'home|svg', | ||
24 | children: [ | 22 | children: [ |
25 | { title: 'leaf', key: '1-1-0' }, | 23 | { title: 'leaf', key: '1-1-0' }, |
26 | { title: 'leaf', key: '1-1-1' }, | 24 | { title: 'leaf', key: '1-1-1' }, |
@@ -29,7 +27,6 @@ export const treeData: TreeItem[] = [ | @@ -29,7 +27,6 @@ export const treeData: TreeItem[] = [ | ||
29 | { | 27 | { |
30 | title: 'parent 3', | 28 | title: 'parent 3', |
31 | key: '2-2', | 29 | key: '2-2', |
32 | - icon: 'home|svg', | ||
33 | children: [ | 30 | children: [ |
34 | { title: 'leaf', key: '2-2-0' }, | 31 | { title: 'leaf', key: '2-2-0' }, |
35 | { title: 'leaf', key: '2-2-1' }, | 32 | { title: 'leaf', key: '2-2-1' }, |