Vben
2021-03-04 80b47c84cd490388c6db659921f1103c443d7b9d
提交 | 用户 | 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>