提交 | 用户 | age
|
72b42d
|
1 |
<script lang="tsx"> |
V |
2 |
import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types'; |
|
3 |
|
4628d9
|
4 |
import { defineComponent, reactive, computed, unref, ref, watchEffect, toRaw, watch } from 'vue'; |
72b42d
|
5 |
import { Tree } from 'ant-design-vue'; |
V |
6 |
import { TreeIcon } from './TreeIcon'; |
cd8e92
|
7 |
import TreeHeader from './TreeHeader.vue'; |
72b42d
|
8 |
// import { DownOutlined } from '@ant-design/icons-vue'; |
V |
9 |
|
|
10 |
import { omit, get } from 'lodash-es'; |
8b62fa
|
11 |
import { isBoolean, isFunction } from '/@/utils/is'; |
72b42d
|
12 |
import { extendSlots } from '/@/utils/helper/tsxHelper'; |
cd8e92
|
13 |
import { filter } from '/@/utils/helper/treeHelper'; |
72b42d
|
14 |
|
V |
15 |
import { useTree } from './useTree'; |
|
16 |
import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
|
17 |
import { useExpose } from '/@/hooks/core/useExpose'; |
|
18 |
import { useDesign } from '/@/hooks/web/useDesign'; |
|
19 |
|
|
20 |
import { basicProps } from './props'; |
|
21 |
|
|
22 |
interface State { |
|
23 |
expandedKeys: Keys; |
|
24 |
selectedKeys: Keys; |
|
25 |
checkedKeys: CheckKeys; |
cd8e92
|
26 |
checkStrictly: boolean; |
72b42d
|
27 |
} |
V |
28 |
export default defineComponent({ |
|
29 |
name: 'BasicTree', |
4628d9
|
30 |
inheritAttrs: false, |
72b42d
|
31 |
props: basicProps, |
cd8e92
|
32 |
emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change'], |
72b42d
|
33 |
setup(props, { attrs, slots, emit }) { |
V |
34 |
const state = reactive<State>({ |
cd8e92
|
35 |
checkStrictly: props.checkStrictly, |
72b42d
|
36 |
expandedKeys: props.expandedKeys || [], |
V |
37 |
selectedKeys: props.selectedKeys || [], |
|
38 |
checkedKeys: props.checkedKeys || [], |
cd8e92
|
39 |
}); |
V |
40 |
|
|
41 |
const searchState = reactive({ |
|
42 |
startSearch: false, |
|
43 |
searchData: [] as TreeItem[], |
72b42d
|
44 |
}); |
V |
45 |
|
|
46 |
const treeDataRef = ref<TreeItem[]>([]); |
|
47 |
|
|
48 |
const [createContextMenu] = useContextMenu(); |
|
49 |
const { prefixCls } = useDesign('basic-tree'); |
|
50 |
|
|
51 |
const getReplaceFields = computed( |
|
52 |
(): Required<ReplaceFields> => { |
|
53 |
const { replaceFields } = props; |
|
54 |
return { |
|
55 |
children: 'children', |
|
56 |
title: 'title', |
|
57 |
key: 'key', |
|
58 |
...replaceFields, |
|
59 |
}; |
|
60 |
} |
|
61 |
); |
|
62 |
|
|
63 |
// const getContentStyle = computed( |
|
64 |
// (): CSSProperties => { |
|
65 |
// const { actionList } = props; |
|
66 |
// const width = actionList.length * 18; |
|
67 |
// return { |
|
68 |
// width: `calc(100% - ${width}px)`, |
|
69 |
// }; |
|
70 |
// } |
|
71 |
// ); |
|
72 |
|
|
73 |
const getBindValues = computed(() => { |
|
74 |
let propsData = { |
|
75 |
blockNode: true, |
|
76 |
...attrs, |
|
77 |
...props, |
|
78 |
expandedKeys: state.expandedKeys, |
|
79 |
selectedKeys: state.selectedKeys, |
|
80 |
checkedKeys: state.checkedKeys, |
cd8e92
|
81 |
checkStrictly: state.checkStrictly, |
72b42d
|
82 |
replaceFields: unref(getReplaceFields), |
V |
83 |
'onUpdate:expandedKeys': (v: Keys) => { |
|
84 |
state.expandedKeys = v; |
|
85 |
emit('update:expandedKeys', v); |
|
86 |
}, |
|
87 |
'onUpdate:selectedKeys': (v: Keys) => { |
|
88 |
state.selectedKeys = v; |
|
89 |
emit('update:selectedKeys', v); |
|
90 |
}, |
b6bb81
|
91 |
onCheck: (v: CheckKeys) => { |
72b42d
|
92 |
state.checkedKeys = v; |
4628d9
|
93 |
const rawVal = toRaw(v); |
V |
94 |
emit('change', rawVal); |
|
95 |
emit('update:value', rawVal); |
72b42d
|
96 |
}, |
V |
97 |
onRightClick: handleRightClick, |
|
98 |
}; |
cd8e92
|
99 |
propsData = omit(propsData, 'treeData', 'class'); |
72b42d
|
100 |
return propsData; |
V |
101 |
}); |
|
102 |
|
cd8e92
|
103 |
const getTreeData = computed((): TreeItem[] => |
V |
104 |
searchState.startSearch ? searchState.searchData : unref(treeDataRef) |
72b42d
|
105 |
); |
cd8e92
|
106 |
|
V |
107 |
const { |
|
108 |
deleteNodeByKey, |
|
109 |
insertNodeByKey, |
|
110 |
filterByLevel, |
|
111 |
updateNodeByKey, |
|
112 |
getAllKeys, |
|
113 |
} = useTree(treeDataRef, getReplaceFields); |
72b42d
|
114 |
|
V |
115 |
function getIcon(params: Recordable, icon?: string) { |
|
116 |
if (!icon) { |
|
117 |
if (props.renderIcon && isFunction(props.renderIcon)) { |
|
118 |
return props.renderIcon(params); |
|
119 |
} |
|
120 |
} |
|
121 |
return icon; |
|
122 |
} |
|
123 |
|
|
124 |
async function handleRightClick({ event, node }: any) { |
|
125 |
const { rightMenuList: menuList = [], beforeRightClick } = props; |
|
126 |
let rightMenuList: ContextMenuItem[] = []; |
|
127 |
|
|
128 |
if (beforeRightClick && isFunction(beforeRightClick)) { |
|
129 |
rightMenuList = await beforeRightClick(node); |
|
130 |
} else { |
|
131 |
rightMenuList = menuList; |
|
132 |
} |
|
133 |
if (!rightMenuList.length) return; |
|
134 |
createContextMenu({ |
|
135 |
event, |
|
136 |
items: rightMenuList, |
|
137 |
}); |
|
138 |
} |
|
139 |
|
|
140 |
function setExpandedKeys(keys: string[]) { |
|
141 |
state.expandedKeys = keys; |
|
142 |
} |
|
143 |
|
|
144 |
function getExpandedKeys() { |
|
145 |
return state.expandedKeys; |
|
146 |
} |
|
147 |
function setSelectedKeys(keys: string[]) { |
|
148 |
state.selectedKeys = keys; |
|
149 |
} |
|
150 |
|
|
151 |
function getSelectedKeys() { |
|
152 |
return state.selectedKeys; |
|
153 |
} |
|
154 |
|
|
155 |
function setCheckedKeys(keys: CheckKeys) { |
|
156 |
state.checkedKeys = keys; |
|
157 |
} |
|
158 |
|
|
159 |
function getCheckedKeys() { |
|
160 |
return state.checkedKeys; |
|
161 |
} |
|
162 |
|
cd8e92
|
163 |
function checkAll(checkAll: boolean) { |
V |
164 |
state.checkedKeys = checkAll ? getAllKeys() : ([] as Keys); |
|
165 |
} |
|
166 |
|
|
167 |
function expandAll(expandAll: boolean) { |
|
168 |
state.expandedKeys = expandAll ? getAllKeys() : ([] as Keys); |
|
169 |
} |
|
170 |
|
|
171 |
function onStrictlyChange(strictly: boolean) { |
|
172 |
state.checkStrictly = strictly; |
|
173 |
} |
|
174 |
|
|
175 |
function handleSearch(searchValue: string) { |
|
176 |
if (!searchValue) { |
|
177 |
searchState.startSearch = false; |
|
178 |
return; |
|
179 |
} |
|
180 |
searchState.startSearch = true; |
|
181 |
|
|
182 |
searchState.searchData = filter(unref(treeDataRef), (node) => { |
|
183 |
const { title } = node; |
|
184 |
return title?.includes(searchValue) ?? false; |
|
185 |
// || key?.includes(searchValue); |
|
186 |
}); |
|
187 |
} |
|
188 |
|
72b42d
|
189 |
watchEffect(() => { |
V |
190 |
treeDataRef.value = props.treeData as TreeItem[]; |
|
191 |
state.expandedKeys = props.expandedKeys; |
|
192 |
state.selectedKeys = props.selectedKeys; |
|
193 |
state.checkedKeys = props.checkedKeys; |
cd8e92
|
194 |
}); |
V |
195 |
|
4628d9
|
196 |
watch( |
V |
197 |
() => props.value, |
|
198 |
() => { |
|
199 |
state.checkedKeys = toRaw(props.value || []); |
cd8e92
|
200 |
} |
4628d9
|
201 |
); |
V |
202 |
|
|
203 |
// watchEffect(() => { |
|
204 |
// console.log('======================'); |
|
205 |
// console.log(props.value); |
|
206 |
// console.log('======================'); |
|
207 |
// if (props.value) { |
|
208 |
// state.checkedKeys = props.value; |
|
209 |
// } |
|
210 |
// }); |
cd8e92
|
211 |
|
V |
212 |
watchEffect(() => { |
|
213 |
state.checkStrictly = props.checkStrictly; |
72b42d
|
214 |
}); |
V |
215 |
|
|
216 |
const instance: TreeActionType = { |
|
217 |
setExpandedKeys, |
|
218 |
getExpandedKeys, |
|
219 |
setSelectedKeys, |
|
220 |
getSelectedKeys, |
|
221 |
setCheckedKeys, |
|
222 |
getCheckedKeys, |
|
223 |
insertNodeByKey, |
|
224 |
deleteNodeByKey, |
|
225 |
updateNodeByKey, |
cd8e92
|
226 |
checkAll, |
V |
227 |
expandAll, |
72b42d
|
228 |
filterByLevel: (level: number) => { |
V |
229 |
state.expandedKeys = filterByLevel(level); |
|
230 |
}, |
|
231 |
}; |
|
232 |
|
|
233 |
useExpose<TreeActionType>(instance); |
|
234 |
|
cd8e92
|
235 |
function renderAction(node: TreeItem) { |
V |
236 |
const { actionList } = props; |
|
237 |
if (!actionList || actionList.length === 0) return; |
|
238 |
return actionList.map((item, index) => { |
80b47c
|
239 |
let nodeShow = true; |
cd8e92
|
240 |
if (isFunction(item.show)) { |
80b47c
|
241 |
nodeShow = item.show?.(node); |
V |
242 |
} else if (isBoolean(item.show)) { |
|
243 |
nodeShow = item.show; |
cd8e92
|
244 |
} |
72b42d
|
245 |
|
80b47c
|
246 |
if (!nodeShow) return null; |
cd8e92
|
247 |
|
V |
248 |
return ( |
|
249 |
<span key={index} class={`${prefixCls}__action`}> |
|
250 |
{item.render(node)} |
|
251 |
</span> |
|
252 |
); |
|
253 |
}); |
|
254 |
} |
|
255 |
|
|
256 |
function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) { |
|
257 |
if (!data) { |
|
258 |
return null; |
|
259 |
} |
|
260 |
return data.map((item) => { |
|
261 |
const { title: titleField, key: keyField, children: childrenField } = unref( |
|
262 |
getReplaceFields |
|
263 |
); |
|
264 |
|
|
265 |
const propsData = omit(item, 'title'); |
|
266 |
const icon = getIcon({ ...item, level }, item.icon); |
|
267 |
return ( |
|
268 |
<Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}> |
|
269 |
{{ |
|
270 |
title: () => ( |
|
271 |
<span class={`${prefixCls}-title pl-2`}> |
|
272 |
{icon && <TreeIcon icon={icon} />} |
|
273 |
<span |
|
274 |
class={`${prefixCls}__content`} |
|
275 |
// style={unref(getContentStyle)} |
|
276 |
> |
|
277 |
{get(item, titleField)} |
|
278 |
</span> |
|
279 |
<span class={`${prefixCls}__actions`}> {renderAction({ ...item, level })}</span> |
|
280 |
</span> |
|
281 |
), |
|
282 |
default: () => |
|
283 |
renderTreeNode({ data: get(item, childrenField) || [], level: level + 1 }), |
|
284 |
}} |
|
285 |
</Tree.TreeNode> |
|
286 |
); |
|
287 |
}); |
|
288 |
} |
72b42d
|
289 |
return () => { |
cd8e92
|
290 |
const { title, helpMessage, toolbar, search } = props; |
72b42d
|
291 |
return ( |
cd8e92
|
292 |
<div class={[prefixCls, 'h-full bg-white']}> |
V |
293 |
{(title || toolbar || search) && ( |
|
294 |
<TreeHeader |
|
295 |
checkAll={checkAll} |
|
296 |
expandAll={expandAll} |
|
297 |
title={title} |
|
298 |
search={search} |
|
299 |
toolbar={toolbar} |
|
300 |
helpMessage={helpMessage} |
|
301 |
onStrictlyChange={onStrictlyChange} |
|
302 |
onSearch={handleSearch} |
|
303 |
/> |
|
304 |
)} |
|
305 |
<Tree {...unref(getBindValues)} showIcon={false}> |
|
306 |
{{ |
|
307 |
// switcherIcon: () => <DownOutlined />, |
|
308 |
default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }), |
|
309 |
...extendSlots(slots), |
|
310 |
}} |
|
311 |
</Tree> |
|
312 |
</div> |
72b42d
|
313 |
); |
V |
314 |
}; |
|
315 |
}, |
|
316 |
}); |
|
317 |
</script> |
|
318 |
<style lang="less"> |
|
319 |
@prefix-cls: ~'@{namespace}-basic-tree'; |
|
320 |
|
|
321 |
.@{prefix-cls} { |
|
322 |
.ant-tree-node-content-wrapper { |
|
323 |
position: relative; |
|
324 |
|
|
325 |
.ant-tree-title { |
|
326 |
position: absolute; |
|
327 |
left: 0; |
|
328 |
width: 100%; |
|
329 |
} |
|
330 |
} |
|
331 |
|
|
332 |
&-title { |
|
333 |
position: relative; |
|
334 |
display: flex; |
|
335 |
align-items: center; |
|
336 |
width: 100%; |
|
337 |
padding-right: 10px; |
|
338 |
|
|
339 |
&:hover { |
|
340 |
.@{prefix-cls}__action { |
|
341 |
visibility: visible; |
|
342 |
} |
|
343 |
} |
|
344 |
} |
|
345 |
|
|
346 |
&__content { |
|
347 |
overflow: hidden; |
|
348 |
} |
|
349 |
|
|
350 |
&__actions { |
|
351 |
position: absolute; |
|
352 |
top: 2px; |
cd8e92
|
353 |
right: 3px; |
72b42d
|
354 |
display: flex; |
V |
355 |
} |
|
356 |
|
|
357 |
&__action { |
|
358 |
margin-left: 4px; |
|
359 |
visibility: hidden; |
|
360 |
} |
|
361 |
} |
|
362 |
</style> |