From e8ccdc7f34891ea31768aea9ebcfc33227d37eb7 Mon Sep 17 00:00:00 2001 From: vben Date: Thu, 26 Nov 2020 22:46:37 +0800 Subject: [PATCH] fix(tree): fix tree style (#99) --- CHANGELOG.zh_CN.md | 4 + src/components/ContextMenu/src/index.tsx | 4 +- src/components/ContextMenu/src/props.ts | 2 +- src/components/Menu/src/BasicMenu.tsx | 4 + src/components/Menu/src/MenuContent.tsx | 13 +- src/components/Menu/src/index.less | 4 +- src/components/Scrollbar/src/index.less | 10 +- src/components/Tree/src/BasicTree.tsx | 174 +++++++--------------- src/components/Tree/src/index.less | 27 +++- src/components/Tree/src/useTree.ts | 97 ++++++++++++ src/layouts/default/menu/useLayoutMenu.ts | 44 +++--- src/views/demo/tree/data.ts | 2 +- 12 files changed, 227 insertions(+), 158 deletions(-) create mode 100644 src/components/Tree/src/useTree.ts diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index 578ba70be92..d849dac46fa 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -19,6 +19,10 @@ - 缓存可以配置是否加密,默认生产环境开启 Aes 加密 - 新增标签页拖拽排序 +### 🐛 Bug Fixes + +- 修复 tree 文本超出挡住操作按钮问题 + ### 🎫 Chores - 更新 antdv 到`2.0.0-rc.2` diff --git a/src/components/ContextMenu/src/index.tsx b/src/components/ContextMenu/src/index.tsx index 5661a51561e..1250b1bbc9f 100644 --- a/src/components/ContextMenu/src/index.tsx +++ b/src/components/ContextMenu/src/index.tsx @@ -8,7 +8,7 @@ import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted import Icon from '/@/components/Icon'; import { Menu, Divider } from 'ant-design-vue'; -import { props } from './props'; +import { contextMenuProps } from './props'; const prefixCls = 'context-menu'; @@ -24,7 +24,7 @@ const ItemContent: FunctionalComponent = (props) => { export default defineComponent({ name: 'ContextMenu', - props, + props: contextMenuProps, setup(props) { const wrapRef = ref(null); const showRef = ref(false); diff --git a/src/components/ContextMenu/src/props.ts b/src/components/ContextMenu/src/props.ts index fb5d7be428e..509b424f6dd 100644 --- a/src/components/ContextMenu/src/props.ts +++ b/src/components/ContextMenu/src/props.ts @@ -1,7 +1,7 @@ import type { PropType } from 'vue'; import type { Axis, ContextMenuItem } from './types'; import { propTypes } from '/@/utils/propTypes'; -export const props = { +export const contextMenuProps = { width: propTypes.number.def(156), customEvent: { type: Object as PropType, diff --git a/src/components/Menu/src/BasicMenu.tsx b/src/components/Menu/src/BasicMenu.tsx index 71691bd9505..eeb8e76db84 100644 --- a/src/components/Menu/src/BasicMenu.tsx +++ b/src/components/Menu/src/BasicMenu.tsx @@ -17,6 +17,7 @@ import { import { Menu } from 'ant-design-vue'; import SearchInput from './SearchInput.vue'; import MenuContent from './MenuContent'; +// import { ScrollContainer } from '/@/components/Container'; import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; import { ThemeEnum } from '/@/enums/appEnum'; @@ -272,7 +273,10 @@ export default defineComponent({ onClick={handleInputClick} collapsed={unref(getCollapsed)} /> + + {/*
*/}
+ {/* {() => renderMenu()} */} {renderMenu()}
diff --git a/src/components/Menu/src/MenuContent.tsx b/src/components/Menu/src/MenuContent.tsx index 29fe81561a5..e5f9a2db545 100644 --- a/src/components/Menu/src/MenuContent.tsx +++ b/src/components/Menu/src/MenuContent.tsx @@ -1,8 +1,9 @@ import type { Menu as MenuType } from '/@/router/types'; -import type { PropType } from 'vue'; +import { computed, PropType, unref } from 'vue'; import { defineComponent } from 'vue'; import Icon from '/@/components/Icon/index'; +import { useI18n } from '/@/hooks/web/useI18n'; export default defineComponent({ name: 'MenuContent', @@ -32,6 +33,13 @@ export default defineComponent({ }, }, setup(props) { + const { t } = useI18n(); + + const getI18nName = computed(() => { + const { name } = props.item; + + return t(name); + }); /** * @description: 渲染图标 */ @@ -61,7 +69,8 @@ export default defineComponent({ return null; } const { showTitle } = props; - const { name, icon } = props.item; + const { icon } = props.item; + const name = unref(getI18nName); const searchValue = props.searchValue || ''; const index = name.indexOf(searchValue); diff --git a/src/components/Menu/src/index.less b/src/components/Menu/src/index.less index 49ca89c857b..04b312562ad 100644 --- a/src/components/Menu/src/index.less +++ b/src/components/Menu/src/index.less @@ -57,8 +57,8 @@ &__content { /* 滚动槽 */ &::-webkit-scrollbar { - width: 4px; - height: 4px; + width: 5px; + height: 5px; } &::-webkit-scrollbar-track { diff --git a/src/components/Scrollbar/src/index.less b/src/components/Scrollbar/src/index.less index 8f688074e59..acdfa78a2dd 100644 --- a/src/components/Scrollbar/src/index.less +++ b/src/components/Scrollbar/src/index.less @@ -38,12 +38,12 @@ z-index: 1; border-radius: 4px; opacity: 0; - -webkit-transition: opacity 120ms ease-out; - transition: opacity 120ms ease-out; + -webkit-transition: opacity 80ms ease; + transition: opacity 80ms ease; &.is-vertical { top: 2px; - width: 6px; + width: 5px; & > div { width: 100%; @@ -52,7 +52,7 @@ &.is-horizontal { left: 2px; - height: 6px; + height: 5px; & > div { height: 100%; @@ -65,5 +65,5 @@ .scrollbar:focus > .scrollbar__bar, .scrollbar:hover > .scrollbar__bar { opacity: 1; - transition: opacity 280ms ease-out; + transition: opacity 180ms ease; } diff --git a/src/components/Tree/src/BasicTree.tsx b/src/components/Tree/src/BasicTree.tsx index 06643f302a5..68f298514f4 100644 --- a/src/components/Tree/src/BasicTree.tsx +++ b/src/components/Tree/src/BasicTree.tsx @@ -1,20 +1,20 @@ -import type { ReplaceFields, TreeItem, Keys, CheckKeys, InsertNodeParams } from './types'; +import './index.less'; + +import type { ReplaceFields, TreeItem, Keys, CheckKeys } from './types'; -import { defineComponent, reactive, computed, unref, ref, watchEffect } from 'vue'; +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, cloneDeep } from 'lodash-es'; -import { forEach } from '/@/utils/helper/treeHelper'; +import { omit } from 'lodash-es'; import { extendSlots } from '/@/utils/helper/tsxHelper'; import { tryTsxEmit } from '/@/utils/helper/vueHelper'; import { basicProps } from './props'; - -import './index.less'; +import { useTree } from './useTree'; interface State { expandedKeys: Keys; @@ -49,17 +49,55 @@ export default defineComponent({ } ); - const getTreeData = computed(() => { - return unref(treeDataRef); + 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; - } + if (!actionList || actionList.length === 0) return; return actionList.map((item, index) => { return ( @@ -81,12 +119,15 @@ export default defineComponent({ const propsData = omit(item, 'title'); const anyItem = item as any; return ( - + {{ title: () => ( - {titleField && anyItem[titleField]} - {renderAction(item)} + + {' '} + {titleField && anyItem[titleField]} + + {renderAction(item)} ), default: () => renderTreeNode({ data: childrenField ? anyItem[childrenField] : [] }), @@ -135,86 +176,6 @@ export default defineComponent({ return state.checkedKeys; } - // 展开指定级别 - function filterByLevel(level = 1, list?: TreeItem[], currentLevel = 1) { - if (!level) { - return []; - } - const res: (string | number)[] = []; - const data = list || props.treeData || []; - for (let index = 0; index < data.length; index++) { - const item = data[index] as any; - - const { key: keyField, children: childrenField } = unref(getReplaceFields); - const key = keyField ? item[keyField] : ''; - const children = childrenField ? item[childrenField] : []; - res.push(key); - if (children && children.length && currentLevel < level) { - currentLevel += 1; - res.push(...filterByLevel(level, children, currentLevel)); - } - } - return res as string[] | number[]; - } - - /** - * 添加节点 - */ - function insertNodeByKey({ parentKey = null, node, push = 'push' }: InsertNodeParams) { - const treeData: any = cloneDeep(unref(treeDataRef)); - if (!parentKey) { - treeData[push](node); - treeDataRef.value = treeData; - return; - } - const { key: keyField, children: childrenField } = unref(getReplaceFields); - forEach(treeData, (treeItem) => { - if (treeItem[keyField] === parentKey) { - treeItem[childrenField] = treeItem[childrenField] || []; - treeItem[childrenField][push](node); - } - }); - treeDataRef.value = treeData; - } - - // 删除节点 - function deleteNodeByKey(key: string, list: TreeItem[]) { - if (!key) return; - const treeData = list || unref(treeDataRef); - const { key: keyField, children: childrenField } = unref(getReplaceFields); - - for (let index = 0; index < treeData.length; index++) { - const element: any = treeData[index]; - const children = element[childrenField]; - - if (element[keyField] === key) { - treeData.splice(index, 1); - break; - } else if (children && children.length) { - deleteNodeByKey(key, element[childrenField]); - } - } - } - - // 更新节点 - function updateNodeByKey(key: string, node: TreeItem, list: TreeItem[]) { - if (!key) return; - const treeData = list || unref(treeDataRef); - const { key: keyField, children: childrenField } = unref(getReplaceFields); - - for (let index = 0; index < treeData.length; index++) { - const element: any = treeData[index]; - const children = element[childrenField]; - - if (element[keyField] === key) { - treeData[index] = { ...treeData[index], ...node }; - break; - } else if (children && children.length) { - updateNodeByKey(key, node, element[childrenField]); - } - } - } - watchEffect(() => { treeDataRef.value = props.treeData as TreeItem[]; state.expandedKeys = props.expandedKeys; @@ -237,31 +198,8 @@ export default defineComponent({ }; }); return () => { - let propsData: any = { - 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 ( - + {{ switcherIcon: () => , default: () => renderTreeNode({ data: unref(getTreeData) }), diff --git a/src/components/Tree/src/index.less b/src/components/Tree/src/index.less index 69a04d1b329..d77e7abd043 100644 --- a/src/components/Tree/src/index.less +++ b/src/components/Tree/src/index.less @@ -2,19 +2,34 @@ position: relative; &-title { + position: relative; display: inline-block; width: 100%; padding-right: 10px; - .basic-tree__action { - display: none; - float: right; - } - &:hover { .basic-tree__action { - display: inline-block; + 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/useTree.ts b/src/components/Tree/src/useTree.ts new file mode 100644 index 00000000000..a7b984122c8 --- /dev/null +++ b/src/components/Tree/src/useTree.ts @@ -0,0 +1,97 @@ +import type { InsertNodeParams, ReplaceFields, TreeItem } from './types'; +import type { Ref, ComputedRef } from 'vue'; + +import { cloneDeep } from 'lodash-es'; +import { unref } from 'vue'; +import { forEach } from '/@/utils/helper/treeHelper'; + +export function useTree( + treeDataRef: Ref, + getReplaceFields: ComputedRef +) { + // 更新节点 + function updateNodeByKey(key: string, node: TreeItem, list: TreeItem[]) { + if (!key) return; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getReplaceFields); + + if (!childrenField || !keyField) return; + + for (let index = 0; index < treeData.length; index++) { + const element: any = treeData[index]; + const children = element[childrenField]; + + if (element[keyField] === key) { + treeData[index] = { ...treeData[index], ...node }; + break; + } else if (children && children.length) { + updateNodeByKey(key, node, element[childrenField]); + } + } + } + + // 展开指定级别 + function filterByLevel(level = 1, list?: TreeItem[], currentLevel = 1) { + if (!level) { + return []; + } + const res: (string | number)[] = []; + const data = list || unref(treeDataRef) || []; + for (let index = 0; index < data.length; index++) { + const item = data[index] as any; + + const { key: keyField, children: childrenField } = unref(getReplaceFields); + const key = keyField ? item[keyField] : ''; + const children = childrenField ? item[childrenField] : []; + res.push(key); + if (children && children.length && currentLevel < level) { + currentLevel += 1; + res.push(...filterByLevel(level, children, currentLevel)); + } + } + return res as string[] | number[]; + } + + /** + * 添加节点 + */ + function insertNodeByKey({ parentKey = null, node, push = 'push' }: InsertNodeParams) { + const treeData: any = cloneDeep(unref(treeDataRef)); + if (!parentKey) { + treeData[push](node); + treeDataRef.value = treeData; + return; + } + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return; + + forEach(treeData, (treeItem) => { + if (treeItem[keyField] === parentKey) { + treeItem[childrenField] = treeItem[childrenField] || []; + treeItem[childrenField][push](node); + } + }); + treeDataRef.value = treeData; + } + + // 删除节点 + function deleteNodeByKey(key: string, list: TreeItem[]) { + if (!key) return; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return; + + for (let index = 0; index < treeData.length; index++) { + const element: any = treeData[index]; + const children = element[childrenField]; + + if (element[keyField] === key) { + treeData.splice(index, 1); + break; + } else if (children && children.length) { + deleteNodeByKey(key, element[childrenField]); + } + } + } + return { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey }; +} diff --git a/src/layouts/default/menu/useLayoutMenu.ts b/src/layouts/default/menu/useLayoutMenu.ts index 42de616d85a..7284cfd069c 100644 --- a/src/layouts/default/menu/useLayoutMenu.ts +++ b/src/layouts/default/menu/useLayoutMenu.ts @@ -17,10 +17,10 @@ import { getShallowMenus, } from '/@/router/menus'; import { permissionStore } from '/@/store/modules/permission'; -import { useI18n } from '/@/hooks/web/useI18n'; -import { cloneDeep } from 'lodash-es'; +// import { useI18n } from '/@/hooks/web/useI18n'; +// import { cloneDeep } from 'lodash-es'; -const { t } = useI18n(); +// const { t } = useI18n(); export function useSplitMenu(splitType: Ref) { // Menu array const menusRef = ref([]); @@ -45,13 +45,13 @@ export function useSplitMenu(splitType: Ref) { return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit); }); - const getI18nFlatMenus = computed(() => { - return setI18nName(flatMenusRef.value, true, false); - }); + // const getI18nFlatMenus = computed(() => { + // return setI18nName(flatMenusRef.value, true, false); + // }); - const getI18nMenus = computed(() => { - return setI18nName(menusRef.value, true, true); - }); + // const getI18nMenus = computed(() => { + // return setI18nName(menusRef.value, true, true); + // }); watch( [() => unref(currentRoute).path, () => unref(splitType)], @@ -83,17 +83,19 @@ export function useSplitMenu(splitType: Ref) { genMenus(); }); - function setI18nName(list: Menu[], clone = false, deep = true) { - const menus = clone ? cloneDeep(list) : list; - menus.forEach((item) => { - if (!item.name.includes('.')) return; - item.name = t(item.name); - if (item.children && deep) { - setI18nName(item.children, false, deep); - } - }); - return menus; - } + // function setI18nName(list: Menu[], clone = false, deep = true) { + // const menus = clone ? cloneDeep(list) : list; + // const arr: Menu[] = []; + // menus.forEach((item) => { + // if (!item.name.includes('.')) return; + // item.name = t(item.name); + + // if (item.children && deep) { + // setI18nName(item.children, false, deep); + // } + // }); + // return menus; + // } // Handle left menu split async function handleSplitLeftMenu(parentPath: string) { @@ -133,5 +135,5 @@ export function useSplitMenu(splitType: Ref) { } } - return { flatMenusRef: getI18nFlatMenus, menusRef: getI18nMenus }; + return { flatMenusRef, menusRef }; } diff --git a/src/views/demo/tree/data.ts b/src/views/demo/tree/data.ts index d55870eea6c..6ae93710dce 100644 --- a/src/views/demo/tree/data.ts +++ b/src/views/demo/tree/data.ts @@ -2,7 +2,7 @@ import { TreeItem } from '/@/components/Tree/index'; export const treeData: TreeItem[] = [ { - title: 'parent 1', + title: 'parent 1parent ', key: '0-0', icon: 'home|svg', children: [