Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: vue2新增动态路由&多级菜单&页签模式&按钮级权限 #167

4 changes: 2 additions & 2 deletions packages/toolkits/pro/template/tinyvue2/src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export function getAllUser(page?: number, limit?: number) {
}

// 获取单个用户
export function getUserInfo(email: string) {
return axios.get<User>(`/api/user/info/${email}`);
export function getUserInfo(email?: string) {
return axios.get<User>(`/api/user/info/${email ?? ''}`);
}

export function deleteUser(email: string) {
Expand Down
334 changes: 79 additions & 255 deletions packages/toolkits/pro/template/tinyvue2/src/components/menu/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,31 @@
<div class="menu-router">
<tiny-tree-menu
ref="tree"
:data="treeNodes"
@current-change="currentChange"
:default-expanded-keys="expandArr"
:data="MenuData"
:show-filter="false"
node-key="id"
wrap
only-check-children
:default-expanded-keys="expandeArr"
@current-change="currentChange"
>
<template #default="slotScope">
<div v-for="(item, index) in routerTitle" :key="index">
<span v-if="slotScope.label === item.label" class="menu-title">
<component :is="item.customIcon"></component>
<span>{{ $t(item.locale) }}</span>
</span>
</div>
</template>
</tiny-tree-menu>
</div>
</template>

<script setup lang="ts">
import { computed, watch, ref, onMounted, getCurrentInstance } from 'vue';
import type { Component, Ref } from 'vue';
import {
IconDownloadCloud,
IconFiles,
IconSetting,
IconSuccessful,
IconCueL,
IconUser,
IconFiletext,
IconApplication,
} from '@opentiny/vue-icon';
<script lang="ts" setup>
import { ref, onMounted, watch, computed, reactive } from 'vue';
import { TreeMenu as tinyTreeMenu } from '@opentiny/vue';
import { useRoute, useRouter } from '@/router';
import { useUserStore } from '@/stores/user';
import type { RouteConfig } from 'vue-router';
import { useI18n } from 'vue-i18n-composable';

const { locale, t } = useI18n();
import { useMenuStore } from '@/stores/modules/router';
import router from '@/router';
import * as icons from '@opentiny/vue-icon';

interface ITreeNodeData {
// node-key='id' 设置节点的唯一标识
Expand All @@ -43,251 +36,82 @@ interface ITreeNodeData {
// 子节点
children?: ITreeNodeData[];
// 链接
url?: string;
// show-number 时展示的字段
number?: number | string;
meta?: {
locale: string;
};
// 自定义每个节点的图标
// "customIcon": Component,
}
const route = useRoute();
const routes = route.sort(
(a, b) => (a.meta?.order ?? 0) - (b.meta?.order ?? 0)
);
const toNodeData = (route: RouteConfig, parent: RouteConfig | null) => {
return {
id: route.meta?.id ?? (route as any).id ?? '',
label: t(route.meta?.locale) || route.meta?.locale,
url: parent
? [parent.path.replace(/\\/, ''), route.path.replace(/\\/, '')].join('/')
: route.path,
children: [],
meta: route.meta,
} as ITreeNodeData;
url: string;
// 组件
component: string;
// 图标
customIcon: string;
// 类型
menuType: string;
// 父节点
parentId: number;
// 排序
order: number;
// 国际化
locale: string;
}

const menuStore = useMenuStore();
const rawMenuData = menuStore.menuList;

type SideMenuData = (ITreeNodeData & { meta: { url: string } })[];

const routerTitle = [] as any;

const filtter = (treeNodeDatas: ITreeNodeData[]) => {
const menus: SideMenuData = [];
for (let i = 0; i < treeNodeDatas.length; i += 1) {
const treeNodeData = treeNodeDatas[i];
const url = treeNodeData.url!;
delete treeNodeData.url;
const temp = {} as any;
temp.label = treeNodeData.label;
temp.locale = treeNodeData.locale;
if (treeNodeData.customIcon) {
temp.customIcon = icons[treeNodeData.customIcon]();
}
routerTitle.push(temp);
menus.push({
...treeNodeData,
meta: {
url,
},
children: [...filtter(treeNodeData.children ?? [])],
});
}
return menus;
};

const useTreeNodeData = (routes: RouteConfig[], parent: RouteConfig | null) => {
const nodes: ITreeNodeData[] = [];
for (const route of routes) {
const node = toNodeData(route, parent);
nodes.push(node);
node.url = '';
node.children = useTreeNodeData(route.children ?? [], route);
const MenuData = ref<SideMenuData>(filtter(rawMenuData));

const currentChange = (data: any) => {
let filter = [];
for (let i = 0; i < rawMenuData.length; i += 1) {
filter.push(rawMenuData[i].label);
}
return nodes;
};
const patchLabel = (nodes: ITreeNodeData[]) => {
for (const node of nodes) {
if (node.children) {
patchLabel(node.children);
}
node.label = String(t(node.meta?.locale ?? '') ?? node.meta?.locale ?? '');
if (filter.indexOf(data.label) === -1) {
router.replace({ name: data.label });
}
};

const treeNodes = ref(useTreeNodeData(routes, null));
const router = useRouter();
const expandArr: Ref<string[]> = ref([]);
const tree = ref();

watch(locale, () => {
patchLabel(treeNodes.value);
});

const expandeArr = ref();
/**
* 监听路由变化高亮当前菜单
*/
onMounted(() => {
watch(
() => router.currentRoute.path,
() => {
if (!expandArr.value.includes(router.currentRoute.meta?.id)) {
expandArr.value.push(router.currentRoute.meta?.id);
}
tree.value.setCurrentKey(router.currentRoute.meta?.id);
(newValue: string) => {
const menuKey = newValue
.replace(/^.*\//, '')
.replace(/^[a-z]/, (s: string) => s.toUpperCase());
expandeArr.value = [menuKey];
tree.value.setCurrentKey(menuKey);
},
{ immediate: true }
);
});
const currentChange = (data: any) => {
const filter = [
'Exception',
'Form',
'Board',
'List',
'Profile',
'Result',
'User',
'Cloud',
'Permission',
];
if (!data.children.length) {
router.push({ name: data.id });
}
};
</script>

<style lang="less" scoped>
.main-title {
height: 20px;
font-size: 14px;
line-height: 20px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
color: #000;
}

.title {
height: 20px;
font-size: 14px;
line-height: 20px;
text-align: left;
}

.menu-title {
display: flex;
gap: 10px;
align-items: center;
height: 20px;

> svg {
width: 1.3em;
height: 1.3em;
}
}
:deep(.tiny-tree-node__wrapper > .is-current > .tiny-tree-node__content) {
color: #000 !important;
background: none !important;
margin-left: 0 !important;
&:hover {
background: #fff !important;
color: #fff !important;
}
}

:deep(
.tiny-tree-node__wrapper
> .is-expanded
> .tiny-tree-node__children
> .tiny-tree-node__wrapper
> .is-current
> .tiny-tree-node__content
) {
background-color: var(--ti-tree-menu-node-hover-bg-color) !important;
margin-left: 0 !important;
&:hover {
background: var(--ti-tree-menu-node-hover-bg-color) !important;
}
}

:deep(
.tiny-tree-node__wrapper
> .is-expanded
> .tiny-tree-node__children
.tiny-tree-node__wrapper
.is-current
.tiny-tree-node__content
.tree-node-name
) {
border-left: 2px solid var(--ti-tree-menu-square-left-border-color, '#fff') !important;
}

:deep(
.tiny-tree-node__wrapper
> .is-expanded
> .tiny-tree-node__children
.tiny-tree-node__wrapper
.is-current
.tiny-tree-node__content
.tiny-tree-node__content-right
) {
background-color: var(--ti-tree-menu-node-hover-bg-color) !important;
}

:deep(
.tiny-tree-node__wrapper
> .is-expanded
> .tiny-tree-node__children
.tiny-tree-node__wrapper
.is-current
.tiny-tree-node__content
.tiny-tree-node__content-left
.tiny-tree-node__content-box
) {
background-color: var(--ti-tree-menu-node-hover-bg-color) !important;
}

:deep(
.tiny-tree-node__wrapper
> .is-expanded
> .tiny-tree-node__children
.tiny-tree-node__wrapper
.is-current
.tiny-tree-node__content
.tiny-tree-node__content-left
.tiny-tree-node__content-box:before
) {
display: none !important;
}

:deep(
.tiny-tree-node__wrapper
> .is-expanded
> .tiny-tree-node__children
.tiny-tree-node__wrapper
.is-current
.tiny-tree-node__content
.tiny-tree-node__content-left:before
) {
display: none !important;
}

:deep(.tiny-tree-node__wrapper > .is-current > .tiny-tree-node__content) {
background-color: #fff !important;
}
:deep(.tiny-tree-node.is-current.is-root > .tiny-tree-node__content a) {
color: unset !important;
}
:deep(.tiny-tree-node.is-current.is-root > .tiny-tree-node__content a) {
color: var(--ti-tree-menu-node-hover-bg-color) !important;
}

:deep(
.tiny-tree-node__wrapper
> .is-current
> .tiny-tree-node__content
.tiny-tree-node__content-box
) {
background-color: #fff !important;
}

:deep(.tiny-tree-node__content:hover) {
background-color: var(--ti-tree-node-content-hover-bg-color) !important;
}

:deep(
.tiny-tree-menu__wrap
> .tiny-tree-node__wrapper
> .is-root
> .tiny-tree-node__content
> .tiny-tree-node__content-left
.tiny-tree-node__content-box
.tree-node-name
) {
padding: 0 8px !important;
}

:deep(.tiny-tree-node > .tiny-tree-node__content) {
margin-left: 0 !important;
}

.tiny-tree-menu
.tiny-tree
.tiny-tree-node.is-current
> .tiny-tree-node__content
.tree-node-name
.tiny-svg {
fill: var(--ti-base-icon-color);
}
</style>
<style scoped></style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type App from '../App.vue';
import permission from './permission';

export default {
install(Vue: App) {
Vue.directive('permission', permission);
},
};
Loading