From 72b42d7b3539919a9baa4f1a7316842f85991c1e Mon Sep 17 00:00:00 2001 From: Vben Date: Tue, 2 Mar 2021 23:14:36 +0800 Subject: [PATCH] feat(tree): add renderIcon props close #309 --- CHANGELOG.zh_CN.md | 1 + src/components/Tree/index.ts | 5 +- src/components/Tree/src/BasicTree.tsx | 219 -------------------- src/components/Tree/src/TreeIcon.ts | 17 ++ src/components/Tree/src/index.less | 35 ---- src/components/Tree/src/index.vue | 281 ++++++++++++++++++++++++++ src/components/Tree/src/props.ts | 12 +- src/components/Tree/src/types.ts | 88 +------- src/components/Tree/src/useTree.ts | 17 +- src/views/demo/tree/ActionTree.vue | 1 + src/views/demo/tree/EditTree.vue | 18 +- src/views/demo/tree/data.ts | 5 +- 12 files changed, 340 insertions(+), 359 deletions(-) delete mode 100644 src/components/Tree/src/BasicTree.tsx create mode 100644 src/components/Tree/src/TreeIcon.ts delete mode 100644 src/components/Tree/src/index.less create mode 100644 src/components/Tree/src/index.vue diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index 89049734..6c534fc9 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -11,6 +11,7 @@ - 新增修改密码界面 - 新增部门管理示例界面 - 新增 WebSocket 示例和服务脚本 +- BasicTree 组件新增 `renderIcon` 属性用于控制层级图标显示 ### ⚡ Performance Improvements diff --git a/src/components/Tree/index.ts b/src/components/Tree/index.ts index 61eb3e89..d07f74ba 100644 --- a/src/components/Tree/index.ts +++ b/src/components/Tree/index.ts @@ -1,6 +1,5 @@ -import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; - -export const BasicTree = createAsyncComponent(() => import('./src/BasicTree')); +import BasicTree from './src/index.vue'; +export { BasicTree }; export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; export * from './src/types'; diff --git a/src/components/Tree/src/BasicTree.tsx b/src/components/Tree/src/BasicTree.tsx deleted file mode 100644 index 064e3088..00000000 --- a/src/components/Tree/src/BasicTree.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import './index.less'; - -import type { ReplaceFields, TreeItem, Keys, CheckKeys, TreeActionType } from './types'; - -import { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue'; -import { Tree } from 'ant-design-vue'; -import { DownOutlined } from '@ant-design/icons-vue'; - -import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu'; - -import { isFunction } from '/@/utils/is'; -import { omit } from 'lodash-es'; -import { extendSlots } from '/@/utils/helper/tsxHelper'; - -import { basicProps } from './props'; -import { useTree } from './useTree'; -import { useExpose } from '/@/hooks/core/useExpose'; -import { onMounted } from 'vue'; - -interface State { - expandedKeys: Keys; - selectedKeys: Keys; - checkedKeys: CheckKeys; -} -const prefixCls = 'basic-tree'; -export default defineComponent({ - name: 'BasicTree', - props: basicProps, - emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'get'], - setup(props, { attrs, slots, emit }) { - const state = reactive({ - expandedKeys: props.expandedKeys || [], - selectedKeys: props.selectedKeys || [], - checkedKeys: props.checkedKeys || [], - }); - - const treeDataRef = ref([]); - - const [createContextMenu] = useContextMenu(); - - const getReplaceFields = computed( - (): Required => { - const { replaceFields } = props; - return { - children: 'children', - title: 'title', - key: 'key', - ...replaceFields, - }; - } - ); - - const getContentStyle = computed( - (): CSSProperties => { - const { actionList } = props; - const width = actionList.length * 18; - return { - width: `calc(100% - ${width}px)`, - }; - } - ); - - const getBindValues = computed(() => { - let propsData = { - blockNode: true, - ...attrs, - ...props, - expandedKeys: state.expandedKeys, - selectedKeys: state.selectedKeys, - checkedKeys: state.checkedKeys, - replaceFields: unref(getReplaceFields), - 'onUpdate:expandedKeys': (v: Keys) => { - state.expandedKeys = v; - emit('update:expandedKeys', v); - }, - 'onUpdate:selectedKeys': (v: Keys) => { - state.selectedKeys = v; - emit('update:selectedKeys', v); - }, - onCheck: (v: CheckKeys) => { - state.checkedKeys = v; - emit('update:value', v); - }, - onRightClick: handleRightClick, - }; - propsData = omit(propsData, 'treeData'); - return propsData; - }); - - const getTreeData = computed((): TreeItem[] => unref(treeDataRef)); - - const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey } = useTree( - treeDataRef, - getReplaceFields - ); - - // 渲染操作按钮 - function renderAction(node: TreeItem) { - const { actionList } = props; - - if (!actionList || actionList.length === 0) return; - - return actionList.map((item, index) => { - return ( - - {item.render(node)} - - ); - }); - } - // 渲染树节点 - function renderTreeNode({ data }: { data: TreeItem[] | undefined }) { - if (!data) { - return null; - } - return data.map((item) => { - const { title: titleField, key: keyField, children: childrenField } = unref( - getReplaceFields - ); - const propsData = omit(item, 'title'); - const anyItem = item as any; - return ( - - {{ - title: () => ( - - - {titleField && anyItem[titleField]} - - {renderAction(item)} - - ), - default: () => renderTreeNode({ data: childrenField ? anyItem[childrenField] : [] }), - }} - - ); - }); - } - - // 处理右键事件 - async function handleRightClick({ event, node }: any) { - const { rightMenuList: menuList = [], beforeRightClick } = props; - let rightMenuList: ContextMenuItem[] = []; - if (beforeRightClick && isFunction(beforeRightClick)) { - rightMenuList = await beforeRightClick(node); - } else { - rightMenuList = menuList; - } - if (!rightMenuList.length) return; - createContextMenu({ - event, - items: rightMenuList, - }); - } - - function setExpandedKeys(keys: string[]) { - state.expandedKeys = keys; - } - - function getExpandedKeys() { - return state.expandedKeys; - } - function setSelectedKeys(keys: string[]) { - state.selectedKeys = keys; - } - - function getSelectedKeys() { - return state.selectedKeys; - } - - function setCheckedKeys(keys: CheckKeys) { - state.checkedKeys = keys; - } - - function getCheckedKeys() { - return state.checkedKeys; - } - - watchEffect(() => { - treeDataRef.value = props.treeData as TreeItem[]; - state.expandedKeys = props.expandedKeys; - state.selectedKeys = props.selectedKeys; - state.checkedKeys = props.checkedKeys; - }); - - const instance: TreeActionType = { - setExpandedKeys, - getExpandedKeys, - setSelectedKeys, - getSelectedKeys, - setCheckedKeys, - getCheckedKeys, - insertNodeByKey, - deleteNodeByKey, - updateNodeByKey, - filterByLevel: (level: number) => { - state.expandedKeys = filterByLevel(level); - }, - }; - - useExpose(instance); - - onMounted(() => { - emit('get', instance); - }); - - return () => { - return ( - - {{ - switcherIcon: () => , - default: () => renderTreeNode({ data: unref(getTreeData) }), - ...extendSlots(slots), - }} - - ); - }; - }, -}); diff --git a/src/components/Tree/src/TreeIcon.ts b/src/components/Tree/src/TreeIcon.ts new file mode 100644 index 00000000..69e7cd06 --- /dev/null +++ b/src/components/Tree/src/TreeIcon.ts @@ -0,0 +1,17 @@ +import type { VNode, FunctionalComponent } from 'vue'; + +import { h } from 'vue'; +import { isString } from '/@/utils/is'; +import { Icon } from '/@/components/Icon'; + +export interface ComponentProps { + icon: VNode | string; +} + +export const TreeIcon: FunctionalComponent = ({ icon }: ComponentProps) => { + if (!icon) return null; + if (isString(icon)) { + return h(Icon, { icon, class: 'mr-1' }); + } + return Icon; +}; diff --git a/src/components/Tree/src/index.less b/src/components/Tree/src/index.less deleted file mode 100644 index d77e7abd..00000000 --- a/src/components/Tree/src/index.less +++ /dev/null @@ -1,35 +0,0 @@ -.basic-tree { - position: relative; - - &-title { - position: relative; - display: inline-block; - width: 100%; - padding-right: 10px; - - &:hover { - .basic-tree__action { - visibility: visible; - } - } - } - - &__content { - display: inline-block; - overflow: hidden; - } - - &__actions { - position: absolute; - top: 0; - right: 0; - display: flex; - } - - &__action { - margin-left: 4px; - // float: right; - // display: none; - visibility: hidden; - } -} diff --git a/src/components/Tree/src/index.vue b/src/components/Tree/src/index.vue new file mode 100644 index 00000000..578582b9 --- /dev/null +++ b/src/components/Tree/src/index.vue @@ -0,0 +1,281 @@ + + diff --git a/src/components/Tree/src/props.ts b/src/components/Tree/src/props.ts index 0a924ab1..280a86e8 100644 --- a/src/components/Tree/src/props.ts +++ b/src/components/Tree/src/props.ts @@ -1,14 +1,18 @@ -import { PropType } from 'vue'; -import type { ReplaceFields, TreeItem, ActionItem, Keys, CheckKeys } from './types'; +import type { PropType } from 'vue'; +import type { ReplaceFields, ActionItem, Keys, CheckKeys } from './types'; import type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; export const basicProps = { + renderIcon: { + type: Function as PropType<(params: Recordable) => string>, + }, replaceFields: { type: Object as PropType, }, treeData: { - type: Array as PropType, + type: Array as PropType, }, actionList: { @@ -50,7 +54,7 @@ export const treeNodeProps = { type: Object as PropType, }, treeData: { - type: Array as PropType, + type: Array as PropType, default: () => [], }, }; diff --git a/src/components/Tree/src/types.ts b/src/components/Tree/src/types.ts index 8fae9fa3..54105b3e 100644 --- a/src/components/Tree/src/types.ts +++ b/src/components/Tree/src/types.ts @@ -1,88 +1,10 @@ +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; export interface ActionItem { render: (record: any) => any; } -export interface TreeItem { - /** - * Class - * @description className - * @type string - */ - class?: string; - - /** - * Style - * @description style of tree node - * @type string | object - */ - style?: string | object; - - /** - * Disable Checkbox - * @description Disables the checkbox of the treeNode - * @default false - * @type boolean - */ - disableCheckbox?: boolean; - - /** - * Disabled - * @description Disabled or not - * @default false - * @type boolean - */ - disabled?: boolean; - - /** - * Icon - * @description customize icon. When you pass component, whose render will receive full TreeNode props as component props - * @type any (slot | slot-scope) - */ +export interface TreeItem extends TreeDataItem { icon?: any; - - /** - * Is Leaf? - * @description Leaf node or not - * @default false - * @type boolean - */ - isLeaf?: boolean; - - /** - * Key - * @description Required property, should be unique in the tree - * (In tree: Used with (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys) - * @default internal calculated position of treeNode or undefined - * @type string | number - */ - key: string | number; - - /** - * Selectable - * @description Set whether the treeNode can be selected - * @default true - * @type boolean - */ - selectable?: boolean; - - /** - * Title - * @description Content showed on the treeNodes - * @default '---' - * @type any (string | slot) - */ - title: any; - - /** - * Value - * @description Will be treated as treeNodeFilterProp by default, should be unique in the tree - * @default undefined - * @type string - */ - value?: string; - children?: TreeItem[]; - slots?: any; - scopedSlots?: any; } export interface ReplaceFields { @@ -107,12 +29,12 @@ export interface TreeActionType { filterByLevel: (level: number) => void; insertNodeByKey: (opt: InsertNodeParams) => void; deleteNodeByKey: (key: string) => void; - updateNodeByKey: (key: string, node: Omit) => void; + updateNodeByKey: (key: string, node: Omit) => void; } export interface InsertNodeParams { parentKey: string | null; - node: TreeItem; - list?: TreeItem[]; + node: TreeDataItem; + list?: TreeDataItem[]; push?: 'push' | 'unshift'; } diff --git a/src/components/Tree/src/useTree.ts b/src/components/Tree/src/useTree.ts index 6245d9fc..706f2f90 100644 --- a/src/components/Tree/src/useTree.ts +++ b/src/components/Tree/src/useTree.ts @@ -1,16 +1,17 @@ -import type { InsertNodeParams, ReplaceFields, TreeItem } from './types'; +import type { InsertNodeParams, ReplaceFields } from './types'; import type { Ref, ComputedRef } from 'vue'; +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; import { cloneDeep } from 'lodash-es'; import { unref } from 'vue'; import { forEach } from '/@/utils/helper/treeHelper'; export function useTree( - treeDataRef: Ref, + treeDataRef: Ref, getReplaceFields: ComputedRef ) { - // 更新节点 - function updateNodeByKey(key: string, node: TreeItem, list?: TreeItem[]) { + // Update node + function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) { if (!key) return; const treeData = list || unref(treeDataRef); const { key: keyField, children: childrenField } = unref(getReplaceFields); @@ -30,8 +31,8 @@ export function useTree( } } - // 展开指定级别 - function filterByLevel(level = 1, list?: TreeItem[], currentLevel = 1) { + // Expand the specified level + function filterByLevel(level = 1, list?: TreeDataItem[], currentLevel = 1) { if (!level) { return []; } @@ -74,8 +75,8 @@ export function useTree( treeDataRef.value = treeData; } - // 删除节点 - function deleteNodeByKey(key: string, list?: TreeItem[]) { + // Delete node + function deleteNodeByKey(key: string, list?: TreeDataItem[]) { if (!key) return; const treeData = list || unref(treeDataRef); const { key: keyField, children: childrenField } = unref(getReplaceFields); diff --git a/src/views/demo/tree/ActionTree.vue b/src/views/demo/tree/ActionTree.vue index 463c246d..7dc8f5b7 100644 --- a/src/views/demo/tree/ActionTree.vue +++ b/src/views/demo/tree/ActionTree.vue @@ -35,6 +35,7 @@ setup() { const treeRef = ref>(null); const { createMessage } = useMessage(); + function getTree() { const tree = unref(treeRef); if (!tree) { diff --git a/src/views/demo/tree/EditTree.vue b/src/views/demo/tree/EditTree.vue index 4de656c9..30624bdd 100644 --- a/src/views/demo/tree/EditTree.vue +++ b/src/views/demo/tree/EditTree.vue @@ -1,8 +1,8 @@