fix(tree): fix tree style (#99)

This commit is contained in:
vben 2020-11-26 22:46:37 +08:00
parent 73c8e0c158
commit e8ccdc7f34
12 changed files with 227 additions and 158 deletions

View File

@ -19,6 +19,10 @@
- 缓存可以配置是否加密,默认生产环境开启 Aes 加密 - 缓存可以配置是否加密,默认生产环境开启 Aes 加密
- 新增标签页拖拽排序 - 新增标签页拖拽排序
### 🐛 Bug Fixes
- 修复 tree 文本超出挡住操作按钮问题
### 🎫 Chores ### 🎫 Chores
- 更新 antdv 到`2.0.0-rc.2` - 更新 antdv 到`2.0.0-rc.2`

View File

@ -8,7 +8,7 @@ import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon';
import { Menu, Divider } from 'ant-design-vue'; import { Menu, Divider } from 'ant-design-vue';
import { props } from './props'; import { contextMenuProps } from './props';
const prefixCls = 'context-menu'; const prefixCls = 'context-menu';
@ -24,7 +24,7 @@ const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
export default defineComponent({ export default defineComponent({
name: 'ContextMenu', name: 'ContextMenu',
props, props: contextMenuProps,
setup(props) { setup(props) {
const wrapRef = ref<ElRef>(null); const wrapRef = ref<ElRef>(null);
const showRef = ref(false); const showRef = ref(false);

View File

@ -1,7 +1,7 @@
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { Axis, ContextMenuItem } from './types'; import type { Axis, ContextMenuItem } from './types';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
export const props = { export const contextMenuProps = {
width: propTypes.number.def(156), width: propTypes.number.def(156),
customEvent: { customEvent: {
type: Object as PropType<Event>, type: Object as PropType<Event>,

View File

@ -17,6 +17,7 @@ import {
import { Menu } from 'ant-design-vue'; import { Menu } from 'ant-design-vue';
import SearchInput from './SearchInput.vue'; import SearchInput from './SearchInput.vue';
import MenuContent from './MenuContent'; import MenuContent from './MenuContent';
// import { ScrollContainer } from '/@/components/Container';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { ThemeEnum } from '/@/enums/appEnum'; import { ThemeEnum } from '/@/enums/appEnum';
@ -272,7 +273,10 @@ export default defineComponent({
onClick={handleInputClick} onClick={handleInputClick}
collapsed={unref(getCollapsed)} collapsed={unref(getCollapsed)}
/> />
{/* <section style={unref(getMenuWrapStyle)}> */}
<section style={unref(getMenuWrapStyle)} class="basic-menu__content"> <section style={unref(getMenuWrapStyle)} class="basic-menu__content">
{/* <ScrollContainer>{() => renderMenu()}</ScrollContainer> */}
{renderMenu()} {renderMenu()}
</section> </section>
</section> </section>

View File

@ -1,8 +1,9 @@
import type { Menu as MenuType } from '/@/router/types'; import type { Menu as MenuType } from '/@/router/types';
import type { PropType } from 'vue'; import { computed, PropType, unref } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import Icon from '/@/components/Icon/index'; import Icon from '/@/components/Icon/index';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({ export default defineComponent({
name: 'MenuContent', name: 'MenuContent',
@ -32,6 +33,13 @@ export default defineComponent({
}, },
}, },
setup(props) { setup(props) {
const { t } = useI18n();
const getI18nName = computed(() => {
const { name } = props.item;
return t(name);
});
/** /**
* @description: * @description:
*/ */
@ -61,7 +69,8 @@ export default defineComponent({
return null; return null;
} }
const { showTitle } = props; const { showTitle } = props;
const { name, icon } = props.item; const { icon } = props.item;
const name = unref(getI18nName);
const searchValue = props.searchValue || ''; const searchValue = props.searchValue || '';
const index = name.indexOf(searchValue); const index = name.indexOf(searchValue);

View File

@ -57,8 +57,8 @@
&__content { &__content {
/* 滚动槽 */ /* 滚动槽 */
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 4px; width: 5px;
height: 4px; height: 5px;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {

View File

@ -38,12 +38,12 @@
z-index: 1; z-index: 1;
border-radius: 4px; border-radius: 4px;
opacity: 0; opacity: 0;
-webkit-transition: opacity 120ms ease-out; -webkit-transition: opacity 80ms ease;
transition: opacity 120ms ease-out; transition: opacity 80ms ease;
&.is-vertical { &.is-vertical {
top: 2px; top: 2px;
width: 6px; width: 5px;
& > div { & > div {
width: 100%; width: 100%;
@ -52,7 +52,7 @@
&.is-horizontal { &.is-horizontal {
left: 2px; left: 2px;
height: 6px; height: 5px;
& > div { & > div {
height: 100%; height: 100%;
@ -65,5 +65,5 @@
.scrollbar:focus > .scrollbar__bar, .scrollbar:focus > .scrollbar__bar,
.scrollbar:hover > .scrollbar__bar { .scrollbar:hover > .scrollbar__bar {
opacity: 1; opacity: 1;
transition: opacity 280ms ease-out; transition: opacity 180ms ease;
} }

View File

@ -1,20 +1,20 @@
import type { ReplaceFields, TreeItem, Keys, CheckKeys, InsertNodeParams } from './types'; import './index.less';
import { defineComponent, reactive, computed, unref, ref, watchEffect } from 'vue'; import type { ReplaceFields, TreeItem, Keys, CheckKeys } from './types';
import { defineComponent, reactive, computed, unref, ref, watchEffect, CSSProperties } from 'vue';
import { Tree } from 'ant-design-vue'; import { Tree } from 'ant-design-vue';
import { DownOutlined } from '@ant-design/icons-vue'; import { DownOutlined } from '@ant-design/icons-vue';
import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu'; import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { omit, cloneDeep } from 'lodash-es'; import { omit } from 'lodash-es';
import { forEach } from '/@/utils/helper/treeHelper';
import { extendSlots } from '/@/utils/helper/tsxHelper'; import { extendSlots } from '/@/utils/helper/tsxHelper';
import { tryTsxEmit } from '/@/utils/helper/vueHelper'; import { tryTsxEmit } from '/@/utils/helper/vueHelper';
import { basicProps } from './props'; import { basicProps } from './props';
import { useTree } from './useTree';
import './index.less';
interface State { interface State {
expandedKeys: Keys; expandedKeys: Keys;
@ -49,17 +49,55 @@ export default defineComponent({
} }
); );
const getTreeData = computed(() => { const getContentStyle = computed(
return unref(treeDataRef); (): 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) { function renderAction(node: TreeItem) {
const { actionList } = props; const { actionList } = props;
if (!actionList || actionList.length === 0) { if (!actionList || actionList.length === 0) return;
return;
}
return actionList.map((item, index) => { return actionList.map((item, index) => {
return ( return (
@ -81,12 +119,15 @@ export default defineComponent({
const propsData = omit(item, 'title'); const propsData = omit(item, 'title');
const anyItem = item as any; const anyItem = item as any;
return ( return (
<Tree.TreeNode {...propsData} key={keyField && anyItem[keyField]}> <Tree.TreeNode {...propsData} key={anyItem?.[keyField]}>
{{ {{
title: () => ( title: () => (
<span class={`${prefixCls}-title`}> <span class={`${prefixCls}-title`}>
{titleField && anyItem[titleField]} <span class={`${prefixCls}__content`} style={unref(getContentStyle)}>
{renderAction(item)} {' '}
{titleField && anyItem[titleField]}
</span>
<span class={`${prefixCls}__actions`}> {renderAction(item)}</span>
</span> </span>
), ),
default: () => renderTreeNode({ data: childrenField ? anyItem[childrenField] : [] }), default: () => renderTreeNode({ data: childrenField ? anyItem[childrenField] : [] }),
@ -135,86 +176,6 @@ export default defineComponent({
return state.checkedKeys; 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(() => { watchEffect(() => {
treeDataRef.value = props.treeData as TreeItem[]; treeDataRef.value = props.treeData as TreeItem[];
state.expandedKeys = props.expandedKeys; state.expandedKeys = props.expandedKeys;
@ -237,31 +198,8 @@ export default defineComponent({
}; };
}); });
return () => { 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 ( return (
<Tree {...propsData} class={prefixCls}> <Tree {...unref(getBindValues)} class={prefixCls}>
{{ {{
switcherIcon: () => <DownOutlined />, switcherIcon: () => <DownOutlined />,
default: () => renderTreeNode({ data: unref(getTreeData) }), default: () => renderTreeNode({ data: unref(getTreeData) }),

View File

@ -2,19 +2,34 @@
position: relative; position: relative;
&-title { &-title {
position: relative;
display: inline-block; display: inline-block;
width: 100%; width: 100%;
padding-right: 10px; padding-right: 10px;
.basic-tree__action {
display: none;
float: right;
}
&:hover { &:hover {
.basic-tree__action { .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;
}
} }

View File

@ -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<TreeItem[]>,
getReplaceFields: ComputedRef<ReplaceFields>
) {
// 更新节点
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 };
}

View File

@ -17,10 +17,10 @@ import {
getShallowMenus, getShallowMenus,
} from '/@/router/menus'; } from '/@/router/menus';
import { permissionStore } from '/@/store/modules/permission'; import { permissionStore } from '/@/store/modules/permission';
import { useI18n } from '/@/hooks/web/useI18n'; // import { useI18n } from '/@/hooks/web/useI18n';
import { cloneDeep } from 'lodash-es'; // import { cloneDeep } from 'lodash-es';
const { t } = useI18n(); // const { t } = useI18n();
export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) { export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
// Menu array // Menu array
const menusRef = ref<Menu[]>([]); const menusRef = ref<Menu[]>([]);
@ -45,13 +45,13 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit); return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit);
}); });
const getI18nFlatMenus = computed(() => { // const getI18nFlatMenus = computed(() => {
return setI18nName(flatMenusRef.value, true, false); // return setI18nName(flatMenusRef.value, true, false);
}); // });
const getI18nMenus = computed(() => { // const getI18nMenus = computed(() => {
return setI18nName(menusRef.value, true, true); // return setI18nName(menusRef.value, true, true);
}); // });
watch( watch(
[() => unref(currentRoute).path, () => unref(splitType)], [() => unref(currentRoute).path, () => unref(splitType)],
@ -83,17 +83,19 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
genMenus(); genMenus();
}); });
function setI18nName(list: Menu[], clone = false, deep = true) { // function setI18nName(list: Menu[], clone = false, deep = true) {
const menus = clone ? cloneDeep(list) : list; // const menus = clone ? cloneDeep(list) : list;
menus.forEach((item) => { // const arr: Menu[] = [];
if (!item.name.includes('.')) return; // menus.forEach((item) => {
item.name = t(item.name); // if (!item.name.includes('.')) return;
if (item.children && deep) { // item.name = t(item.name);
setI18nName(item.children, false, deep);
} // if (item.children && deep) {
}); // setI18nName(item.children, false, deep);
return menus; // }
} // });
// return menus;
// }
// Handle left menu split // Handle left menu split
async function handleSplitLeftMenu(parentPath: string) { async function handleSplitLeftMenu(parentPath: string) {
@ -133,5 +135,5 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
} }
} }
return { flatMenusRef: getI18nFlatMenus, menusRef: getI18nMenus }; return { flatMenusRef, menusRef };
} }

View File

@ -2,7 +2,7 @@ import { TreeItem } from '/@/components/Tree/index';
export const treeData: TreeItem[] = [ export const treeData: TreeItem[] = [
{ {
title: 'parent 1', title: 'parent 1parent ',
key: '0-0', key: '0-0',
icon: 'home|svg', icon: 'home|svg',
children: [ children: [