mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-01-23 09:40:22 +08:00
fix(tree): fix tree style (#99)
This commit is contained in:
parent
73c8e0c158
commit
e8ccdc7f34
@ -19,6 +19,10 @@
|
||||
- 缓存可以配置是否加密,默认生产环境开启 Aes 加密
|
||||
- 新增标签页拖拽排序
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- 修复 tree 文本超出挡住操作按钮问题
|
||||
|
||||
### 🎫 Chores
|
||||
|
||||
- 更新 antdv 到`2.0.0-rc.2`
|
||||
|
@ -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<ItemContentProps> = (props) => {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ContextMenu',
|
||||
props,
|
||||
props: contextMenuProps,
|
||||
setup(props) {
|
||||
const wrapRef = ref<ElRef>(null);
|
||||
const showRef = ref(false);
|
||||
|
@ -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<Event>,
|
||||
|
@ -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)}
|
||||
/>
|
||||
|
||||
{/* <section style={unref(getMenuWrapStyle)}> */}
|
||||
<section style={unref(getMenuWrapStyle)} class="basic-menu__content">
|
||||
{/* <ScrollContainer>{() => renderMenu()}</ScrollContainer> */}
|
||||
{renderMenu()}
|
||||
</section>
|
||||
</section>
|
||||
|
@ -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);
|
||||
|
||||
|
@ -57,8 +57,8 @@
|
||||
&__content {
|
||||
/* 滚动槽 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 { 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 (
|
||||
<Tree.TreeNode {...propsData} key={keyField && anyItem[keyField]}>
|
||||
<Tree.TreeNode {...propsData} key={anyItem?.[keyField]}>
|
||||
{{
|
||||
title: () => (
|
||||
<span class={`${prefixCls}-title`}>
|
||||
{titleField && anyItem[titleField]}
|
||||
{renderAction(item)}
|
||||
<span class={`${prefixCls}__content`} style={unref(getContentStyle)}>
|
||||
{' '}
|
||||
{titleField && anyItem[titleField]}
|
||||
</span>
|
||||
<span class={`${prefixCls}__actions`}> {renderAction(item)}</span>
|
||||
</span>
|
||||
),
|
||||
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 (
|
||||
<Tree {...propsData} class={prefixCls}>
|
||||
<Tree {...unref(getBindValues)} class={prefixCls}>
|
||||
{{
|
||||
switcherIcon: () => <DownOutlined />,
|
||||
default: () => renderTreeNode({ data: unref(getTreeData) }),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
97
src/components/Tree/src/useTree.ts
Normal file
97
src/components/Tree/src/useTree.ts
Normal 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 };
|
||||
}
|
@ -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<MenuSplitTyeEnum>) {
|
||||
// Menu array
|
||||
const menusRef = ref<Menu[]>([]);
|
||||
@ -45,13 +45,13 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
|
||||
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<MenuSplitTyeEnum>) {
|
||||
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<MenuSplitTyeEnum>) {
|
||||
}
|
||||
}
|
||||
|
||||
return { flatMenusRef: getI18nFlatMenus, menusRef: getI18nMenus };
|
||||
return { flatMenusRef, menusRef };
|
||||
}
|
||||
|
@ -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: [
|
||||
|
Loading…
Reference in New Issue
Block a user