mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
feat(projects): 菜单数据及组件接入
This commit is contained in:
@ -1,8 +1,10 @@
|
||||
import type { Component } from 'vue';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import type { MenuOption } from 'naive-ui';
|
||||
import { EnumRoutePath, EnumLoginModule } from '@/enum';
|
||||
|
||||
/** 路由描述 */
|
||||
export interface RouteMeta {
|
||||
interface RouteMeta {
|
||||
/** 路由名称 */
|
||||
title?: string;
|
||||
/** 页面100%视高 */
|
||||
@ -10,11 +12,16 @@ export interface RouteMeta {
|
||||
/** 作为菜单 */
|
||||
asMenu?: boolean;
|
||||
/** 菜单和面包屑对应的图标 */
|
||||
icon?: string;
|
||||
icon?: Component;
|
||||
}
|
||||
|
||||
export type CustomRoute = RouteRecordRaw & { meta: RouteMeta };
|
||||
|
||||
export type RoutePathKey = keyof typeof EnumRoutePath;
|
||||
|
||||
export type GlobalMenuOption = MenuOption & {
|
||||
routeName: string;
|
||||
routePath: string;
|
||||
};
|
||||
|
||||
export type LoginModuleType = keyof typeof EnumLoginModule;
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export { UserInfo } from './business';
|
||||
export { ThemeSettings, NavMode, AnimateType } from './theme';
|
||||
export { CustomRoute, RoutePathKey, LoginModuleType } from './common';
|
||||
export { CustomRoute, RoutePathKey, GlobalMenuOption, LoginModuleType } from './common';
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<n-dropdown :options="options" @select="handleDropdown">
|
||||
<header-item class="px-12px">
|
||||
<n-avatar :src="avatar" :round="true" />
|
||||
<span class="pl-8px text-16px font-medium">Soybean</span>
|
||||
</header-item>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NDropdown, NAvatar } from 'naive-ui';
|
||||
import { UserAvatar, Logout } from '@vicons/carbon';
|
||||
import { dynamicIconRender } from '@/utils';
|
||||
import HeaderItem from './HeaderItem.vue';
|
||||
import avatar from '@/assets/img/common/logo-fill.png';
|
||||
import { useAuthStore } from '@/store';
|
||||
|
||||
type DropdownKey = 'user-center' | 'logout';
|
||||
|
||||
const { resetAuthState } = useAuthStore();
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '用户中心',
|
||||
key: 'user-center',
|
||||
icon: dynamicIconRender(UserAvatar)
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'divider'
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout',
|
||||
icon: dynamicIconRender(Logout)
|
||||
}
|
||||
];
|
||||
|
||||
function handleDropdown(key: DropdownKey) {
|
||||
if (key === 'logout') {
|
||||
resetAuthState();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
@ -1,3 +1,4 @@
|
||||
import UserAvatar from './UserAvatar.vue';
|
||||
import HeaderItem from './HeaderItem.vue';
|
||||
|
||||
export { HeaderItem };
|
||||
export { UserAvatar, HeaderItem };
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
<icon-gridicons-fullscreen-exit v-if="isFullscreen" class="text-16px" />
|
||||
<icon-gridicons-fullscreen v-else class="text-16px" />
|
||||
</header-item>
|
||||
<user-avatar />
|
||||
<header-item class="w-40px h-full" @click="openSettingDrawer">
|
||||
<icon-mdi-light-cog class="text-16px" />
|
||||
</header-item>
|
||||
@ -23,7 +24,7 @@ import { computed } from 'vue';
|
||||
import { NLayoutHeader } from 'naive-ui';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { useAppStore, useThemeStore } from '@/store';
|
||||
import { HeaderItem } from './components';
|
||||
import { UserAvatar, HeaderItem } from './components';
|
||||
import { GlobalLogo } from '../common';
|
||||
|
||||
defineProps({
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<router-link to="/" class="nowrap-hidden flex-center h-64px">
|
||||
<a :href="homePath" class="nowrap-hidden flex-center h-64px cursor-pointer">
|
||||
<img src="@/assets/img/common/logo.png" alt="" class="w-32px h-32px" />
|
||||
<h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold">{{ title }}</h2>
|
||||
</router-link>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useAppStore, useThemeStore } from '@/store';
|
||||
import { useAppTitle } from '@/hooks';
|
||||
import { EnumRoutePath } from '@/enum';
|
||||
|
||||
const app = useAppStore();
|
||||
const theme = useThemeStore();
|
||||
const showTitle = computed(() => !app.menu.collapsed && theme.navStyle.mode !== 'vertical-mix');
|
||||
const title = useAppTitle();
|
||||
|
||||
const showTitle = computed(() => !app.menu.collapsed && theme.navStyle.mode !== 'vertical-mix');
|
||||
const homePath = EnumRoutePath['dashboard-analysis'];
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@ -1,11 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="text-center text-18px text-error">菜单</h3>
|
||||
<div class="flex-center h-48px">
|
||||
<router-link to="/login" class="text-primary text-18px">登录页</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<n-menu
|
||||
:value="activeKey"
|
||||
:collapsed="app.menu.collapsed"
|
||||
:collapsed-width="theme.menuStyle.collapsedWidth"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menus"
|
||||
@update:value="handleUpdateMenu"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { NMenu } from 'naive-ui';
|
||||
import { useThemeStore, useAppStore } from '@/store';
|
||||
import { menus } from '@/router';
|
||||
import { GlobalMenuOption } from '@/interface';
|
||||
|
||||
const theme = useThemeStore();
|
||||
const app = useAppStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const activeKey = computed(() => getActiveKey());
|
||||
|
||||
function getActiveKey() {
|
||||
return route.name as string;
|
||||
}
|
||||
|
||||
function handleUpdateMenu(key: string, menuItem: GlobalMenuOption) {
|
||||
router.push(menuItem.routePath);
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@ -3,7 +3,7 @@ import App from './App.vue';
|
||||
import AppProvider from './AppProvider.vue';
|
||||
import { setupStore } from './store';
|
||||
import { setupRouter } from './router';
|
||||
import { setupSmoothScroll, setupWindicssDarkMode } from './plugins';
|
||||
import { setupWindicssDarkMode } from './plugins';
|
||||
import 'virtual:windi.css';
|
||||
import './styles/css/global.css';
|
||||
|
||||
@ -27,5 +27,4 @@ async function setupApp() {
|
||||
setupWindicssDarkMode();
|
||||
}
|
||||
|
||||
setupSmoothScroll();
|
||||
setupApp();
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import setupSmoothScroll from './smooth-scroll';
|
||||
import setupWindicssDarkMode from './dark-mode';
|
||||
|
||||
export { setupSmoothScroll, setupWindicssDarkMode };
|
||||
export { setupWindicssDarkMode };
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import smoothscroll from 'smoothscroll-polyfill';
|
||||
|
||||
/** 平滑滚动插件(兼容主流浏览器) */
|
||||
export default function setupSmoothScroll(): void {
|
||||
smoothscroll.polyfill();
|
||||
}
|
||||
@ -21,3 +21,4 @@ export async function setupRouter(app: App) {
|
||||
}
|
||||
|
||||
export { RouteNameMap };
|
||||
export { menus } from './menus';
|
||||
|
||||
@ -1,5 +1,49 @@
|
||||
import { CustomRoute } from '@/interface';
|
||||
import type { Component } from 'vue';
|
||||
import { customRoutes } from './routes';
|
||||
import { CustomRoute, GlobalMenuOption } from '@/interface';
|
||||
import { dynamicIconRender } from '@/utils';
|
||||
|
||||
export function transformRouteToMenu(routes: CustomRoute[]) {
|
||||
return routes;
|
||||
const globalMenu: GlobalMenuOption[] = [];
|
||||
routes.forEach(route => {
|
||||
if (asMenu(route)) {
|
||||
const { name, path, meta } = route;
|
||||
const routeName = name as string;
|
||||
let menuChildren: GlobalMenuOption[] | undefined;
|
||||
if (route.children) {
|
||||
menuChildren = transformRouteToMenu(route.children as CustomRoute[]);
|
||||
}
|
||||
const menuItem: GlobalMenuOption = addPartialProps(
|
||||
{
|
||||
key: routeName,
|
||||
label: meta?.title ?? routeName,
|
||||
routeName,
|
||||
routePath: path
|
||||
},
|
||||
meta?.icon,
|
||||
menuChildren
|
||||
);
|
||||
globalMenu.push(menuItem);
|
||||
}
|
||||
});
|
||||
return globalMenu;
|
||||
}
|
||||
|
||||
/** 判断路由是否作为菜单 */
|
||||
function asMenu(route: CustomRoute) {
|
||||
return Boolean(route.meta?.asMenu);
|
||||
}
|
||||
|
||||
/** 给菜单添加可选属性 */
|
||||
function addPartialProps(menuItem: GlobalMenuOption, icon?: Component, children?: GlobalMenuOption[]) {
|
||||
const item = { ...menuItem };
|
||||
if (icon) {
|
||||
Object.assign(item, { icon: dynamicIconRender(icon) });
|
||||
}
|
||||
if (children) {
|
||||
Object.assign(item, { children });
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
export const menus = transformRouteToMenu(customRoutes);
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { Dashboard } from '@vicons/carbon';
|
||||
import { ExceptionOutlined } from '@vicons/antd';
|
||||
import { BasicLayout, BlankLayout } from '@/layouts';
|
||||
import { EnumRoutePath, EnumRouteTitle } from '@/enum';
|
||||
import type { CustomRoute, RoutePathKey, LoginModuleType } from '@/interface';
|
||||
@ -100,7 +102,7 @@ export const customRoutes: CustomRoute[] = [
|
||||
meta: {
|
||||
title: EnumRouteTitle.dashboard,
|
||||
asMenu: true,
|
||||
icon: 'mdi:view-dashboard'
|
||||
icon: Dashboard
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@ -130,7 +132,7 @@ export const customRoutes: CustomRoute[] = [
|
||||
meta: {
|
||||
title: EnumRouteTitle.exception,
|
||||
asMenu: true,
|
||||
icon: 'ant-design:exception-outlined'
|
||||
icon: ExceptionOutlined
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@ -157,7 +159,7 @@ export const customRoutes: CustomRoute[] = [
|
||||
component: () => import('@/views/system/exception/500.vue'),
|
||||
meta: {
|
||||
title: EnumRouteTitle['exception-500'],
|
||||
asMenu: true
|
||||
asMenu: false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { store } from '@/store';
|
||||
import { removeToken } from '@/utils';
|
||||
import type { UserInfo } from '@/interface';
|
||||
|
||||
interface AuthState {
|
||||
@ -30,7 +31,9 @@ const authStore = defineStore({
|
||||
actions: {
|
||||
/** 重置auth状态 */
|
||||
resetAuthState() {
|
||||
removeToken();
|
||||
this.$reset();
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export { getToken, setToken, getUserInfo, getLoginModuleRegExp } from './user';
|
||||
export { getToken, setToken, removeToken, getUserInfo, getLoginModuleRegExp } from './user';
|
||||
export { getLoginRedirectUrl, toLoginRedirectUrl, toHomeByLocation } from './location';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { EnumStorageKey } from '@/enum';
|
||||
import type { LoginModuleType } from '@/interface';
|
||||
import { setLocal, getLocal } from '../storage';
|
||||
import { setLocal, getLocal, removeLocal } from '../storage';
|
||||
|
||||
/** 设置token */
|
||||
export function getToken() {
|
||||
@ -12,6 +12,10 @@ export function setToken(token: string) {
|
||||
setLocal(EnumStorageKey.token, token);
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
removeLocal(EnumStorageKey.token);
|
||||
}
|
||||
|
||||
export function getUserInfo() {}
|
||||
|
||||
/** 获取登录模块的正则字符串 */
|
||||
|
||||
12
src/utils/common/icon.ts
Normal file
12
src/utils/common/icon.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { h } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { NIcon } from 'naive-ui';
|
||||
|
||||
/** 动态渲染vicon */
|
||||
export function dynamicIconRender(icon: Component) {
|
||||
return () => {
|
||||
return h(NIcon, null, {
|
||||
default: () => h(icon)
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -13,3 +13,5 @@ export {
|
||||
} from './typeof';
|
||||
|
||||
export { brightenColor, darkenColor } from './color';
|
||||
|
||||
export { dynamicIconRender } from './icon';
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export {
|
||||
setToken,
|
||||
getToken,
|
||||
removeToken,
|
||||
getUserInfo,
|
||||
getLoginModuleRegExp,
|
||||
getLoginRedirectUrl,
|
||||
@ -21,7 +22,8 @@ export {
|
||||
isSet,
|
||||
isMap,
|
||||
brightenColor,
|
||||
darkenColor
|
||||
darkenColor,
|
||||
dynamicIconRender
|
||||
} from './common';
|
||||
|
||||
export { setLocal, getLocal, setSession, getSession } from './storage';
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export { setLocal, getLocal } from './local';
|
||||
export { setLocal, getLocal, removeLocal } from './local';
|
||||
export { setSession, getSession } from './session';
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
export function setLocal(key: string, value: unknown) {
|
||||
const json = JSON.stringify(value);
|
||||
localStorage.setItem(key, json);
|
||||
window.localStorage.setItem(key, json);
|
||||
}
|
||||
|
||||
export function getLocal<T>(key: string) {
|
||||
const json = localStorage.getItem(key);
|
||||
const json = window.localStorage.getItem(key);
|
||||
if (json) {
|
||||
return JSON.parse(json) as T;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
export function removeLocal(key: string) {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
@ -2,20 +2,10 @@
|
||||
<div>
|
||||
<h2>工作台</h2>
|
||||
<router-link :to="EnumRoutePath['dashboard-analysis']">analysis</router-link>
|
||||
<n-button @click="removeCurrent">去除</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NButton } from 'naive-ui';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { EnumRoutePath } from '@/enum';
|
||||
import { RouteNameMap } from '@/router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function removeCurrent() {
|
||||
router.removeRoute(RouteNameMap.get('dashboard-workbench')!);
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div class="relative flex-center w-full h-full bg-[#DBE0F9]">
|
||||
<login-bg />
|
||||
<div class="w-400px p-40px bg-white rounded-20px z-10">
|
||||
<header class="flex-y-center justify-between">
|
||||
<div class="w-70px h-70px rounded-35px overflow-hidden">
|
||||
@ -15,6 +14,7 @@
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<login-bg />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user