Merge branch 'fork' of 124-template-94/d8d-admin-mobile-starter-public into main

This commit is contained in:
2025-05-14 06:04:47 +00:00
committed by Gogs
6 changed files with 559 additions and 523 deletions

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { useRouteError } from 'react-router';
import { Alert, Button } from 'antd';
import { useTheme } from '../hooks_sys.tsx';
export const ErrorPage = () => {
const { isDark } = useTheme();
const error = useRouteError() as any;
const errorMessage = error?.statusText || error?.message || '未知错误';
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4"
style={{ color: isDark ? '#fff' : 'inherit' }}
>
<div className="max-w-3xl w-full">
<h1 className="text-2xl font-bold mb-4"></h1>
<Alert
type="error"
message={error?.message || '未知错误'}
description={
error?.stack ? (
<pre className="text-xs overflow-auto p-2 bg-gray-100 dark:bg-gray-800 rounded">
{error.stack}
</pre>
) : null
}
className="mb-4"
/>
<div className="flex gap-4">
<Button
type="primary"
onClick={() => window.location.reload()}
>
</Button>
<Button
onClick={() => window.location.href = '/admin'}
>
</Button>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,222 @@
import React, { useState, useEffect, useMemo } from 'react';
import {
Outlet,
useLocation,
} from 'react-router';
import {
Layout, Button, Space, Badge, Avatar, Dropdown, Typography, Input, Menu,
} from 'antd';
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
BellOutlined,
VerticalAlignTopOutlined,
UserOutlined
} from '@ant-design/icons';
import { useAuth, useTheme } from '../hooks_sys.tsx';
import { useMenu, useMenuSearch, type MenuItem } from '../menu.tsx';
const { Header, Sider, Content } = Layout;
/**
* 主布局组件
* 包含侧边栏、顶部导航和内容区域
*/
export const MainLayout = () => {
const { user } = useAuth();
const { isDark } = useTheme();
const [showBackTop, setShowBackTop] = useState(false);
const location = useLocation();
// 使用菜单hook
const {
menuItems,
userMenuItems,
openKeys,
collapsed,
setCollapsed,
handleMenuClick: handleRawMenuClick,
onOpenChange
} = useMenu();
// 处理菜单点击
const handleMenuClick = (key: string) => {
const item = findMenuItem(menuItems, key);
if (item && 'label' in item) {
handleRawMenuClick(item);
}
};
// 查找菜单项
const findMenuItem = (items: MenuItem[], key: string): MenuItem | null => {
for (const item of items) {
if (!item) continue;
if (item.key === key) return item;
if (item.children) {
const found = findMenuItem(item.children, key);
if (found) return found;
}
}
return null;
};
// 使用菜单搜索hook
const {
searchText,
setSearchText,
filteredMenuItems
} = useMenuSearch(menuItems);
// 获取当前选中的菜单项
const selectedKey = useMemo(() => {
const findSelectedKey = (items: MenuItem[]): string | null => {
for (const item of items) {
if (!item) continue;
if (item.path === location.pathname) return item.key || null;
if (item.children) {
const childKey = findSelectedKey(item.children);
if (childKey) return childKey;
}
}
return null;
};
return findSelectedKey(menuItems) || '';
}, [location.pathname, menuItems]);
// 检测滚动位置,控制回到顶部按钮显示
useEffect(() => {
const handleScroll = () => {
setShowBackTop(window.pageYOffset > 300);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// 回到顶部
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
// 应用名称 - 从CONFIG中获取或使用默认值
const appName = window.CONFIG?.APP_NAME || '应用Starter';
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider
trigger={null}
collapsible
collapsed={collapsed}
width={240}
className="custom-sider"
style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
zIndex: 100,
}}
>
<div className="p-4">
<Typography.Title level={2} className="text-xl font-bold truncate">
{collapsed ? '应用' : appName}
</Typography.Title>
{/* 菜单搜索框 */}
{!collapsed && (
<div className="mb-4">
<Input.Search
placeholder="搜索菜单..."
allowClear
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
</div>
)}
</div>
{/* 菜单列表 */}
<Menu
theme={isDark ? 'dark' : 'light'}
mode="inline"
items={filteredMenuItems}
openKeys={openKeys}
selectedKeys={[selectedKey]}
onOpenChange={onOpenChange}
onClick={({ key }) => handleMenuClick(key)}
inlineCollapsed={collapsed}
/>
</Sider>
<Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
<Header className="p-0 flex justify-between items-center"
style={{
position: 'sticky',
top: 0,
zIndex: 99,
boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
}}
>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
className="w-16 h-16"
/>
<Space size="middle" className="mr-4">
<Badge count={5} offset={[0, 5]}>
<Button
type="text"
icon={<BellOutlined />}
/>
</Badge>
<Dropdown menu={{ items: userMenuItems }}>
<Space className="cursor-pointer">
<Avatar
src={user?.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=40&auto=format&fit=crop'}
icon={!user?.avatar && !navigator.onLine && <UserOutlined />}
/>
<span>
{user?.nickname || user?.username}
</span>
</Space>
</Dropdown>
</Space>
</Header>
<Content className="m-6" style={{ overflow: 'initial' }}>
<div className="site-layout-content p-6 rounded-lg">
<Outlet />
</div>
{/* 回到顶部按钮 */}
{showBackTop && (
<Button
type="primary"
shape="circle"
icon={<VerticalAlignTopOutlined />}
size="large"
onClick={scrollToTop}
style={{
position: 'fixed',
right: 30,
bottom: 30,
zIndex: 1000,
boxShadow: '0 3px 6px rgba(0,0,0,0.16)',
}}
/>
)}
</Content>
</Layout>
</Layout>
);
};

199
client/admin/menu.tsx Normal file
View File

@@ -0,0 +1,199 @@
import React from 'react';
import { useNavigate } from 'react-router';
import type { MenuProps } from 'antd';
import {
UserOutlined,
DashboardOutlined,
TeamOutlined,
SettingOutlined,
FileOutlined,
MessageOutlined,
InfoCircleOutlined,
BarChartOutlined,
EnvironmentOutlined,
MoonOutlined,
SunOutlined
} from '@ant-design/icons';
import { useTheme } from './hooks_sys.tsx';
export interface MenuItem {
key: string;
label: string;
icon?: React.ReactNode;
children?: MenuItem[];
path?: string;
permission?: string;
}
/**
* 菜单搜索 Hook
* 封装菜单搜索相关逻辑
*/
export const useMenuSearch = (menuItems: MenuItem[]) => {
const [searchText, setSearchText] = React.useState('');
// 过滤菜单项
const filteredMenuItems = React.useMemo(() => {
if (!searchText) return menuItems;
const filterItems = (items: MenuItem[]): MenuItem[] => {
return items
.map(item => {
// 克隆对象避免修改原数据
const newItem = { ...item };
if (newItem.children) {
newItem.children = filterItems(newItem.children);
}
return newItem;
})
.filter(item => {
// 保留匹配项或其子项匹配的项
const match = item.label.toLowerCase().includes(searchText.toLowerCase());
if (match) return true;
if (item.children?.length) return true;
return false;
});
};
return filterItems(menuItems);
}, [menuItems, searchText]);
// 清除搜索
const clearSearch = () => {
setSearchText('');
};
return {
searchText,
setSearchText,
filteredMenuItems,
clearSearch
};
};
export const useMenu = () => {
const { isDark, toggleTheme } = useTheme();
const navigate = useNavigate();
const [collapsed, setCollapsed] = React.useState(false);
const [openKeys, setOpenKeys] = React.useState<string[]>([]);
// 基础菜单项配置
const menuItems: MenuItem[] = [
{
key: 'dashboard',
label: '控制台',
icon: <DashboardOutlined />,
path: '/admin/dashboard'
},
{
key: 'users',
label: '用户管理',
icon: <TeamOutlined />,
path: '/admin/users',
permission: 'user:manage'
},
{
key: 'settings',
label: '系统设置',
icon: <SettingOutlined />,
children: [
{
key: 'theme-settings',
label: '主题设置',
path: '/admin/theme-settings',
permission: 'system:settings'
},
{
key: 'system-settings',
label: '系统配置',
path: '/admin/settings',
permission: 'system:settings'
}
]
},
{
key: 'content',
label: '内容管理',
icon: <FileOutlined />,
children: [
{
key: 'know-info',
label: '知识库',
path: '/admin/know-info',
permission: 'content:manage'
},
{
key: 'file-library',
label: '文件库',
path: '/admin/file-library',
permission: 'content:manage'
}
]
},
{
key: 'messages',
label: '消息中心',
icon: <MessageOutlined />,
path: '/admin/messages',
permission: 'message:view'
},
{
key: 'charts',
label: '数据图表',
icon: <BarChartOutlined />,
path: '/admin/chart-dashboard',
permission: 'chart:view'
},
{
key: 'maps',
label: '地图',
icon: <EnvironmentOutlined />,
path: '/admin/map-dashboard',
permission: 'map:view'
}
];
// 用户菜单项
const userMenuItems: MenuProps['items'] = [
{
key: 'profile',
label: '个人资料',
icon: <UserOutlined />
},
{
key: 'theme',
label: isDark ? '切换到亮色模式' : '切换到暗色模式',
icon: isDark ? <SunOutlined /> : <MoonOutlined />,
onClick: () => toggleTheme()
},
{
key: 'logout',
label: '退出登录',
icon: <InfoCircleOutlined />,
danger: true
}
];
// 处理菜单点击
const handleMenuClick = (item: MenuItem) => {
if (item.path) {
navigate(item.path);
}
};
// 处理菜单展开变化
const onOpenChange = (keys: string[]) => {
const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
};
return {
menuItems,
userMenuItems,
openKeys,
collapsed,
setCollapsed,
handleMenuClick,
onOpenChange
};
};

85
client/admin/routes.tsx Normal file
View File

@@ -0,0 +1,85 @@
import React from 'react';
import { createBrowserRouter, Navigate } from 'react-router';
import { ProtectedRoute } from './components_protected_route.tsx';
import { MainLayout } from './layouts/MainLayout.tsx';
import { ErrorPage } from './components/ErrorPage.tsx';
import { DashboardPage } from './pages_dashboard.tsx';
import { UsersPage } from './pages_users.tsx';
import { FileLibraryPage } from './pages_file_library.tsx';
import { KnowInfoPage } from './pages_know_info.tsx';
import { MessagesPage } from './pages_messages.tsx';
import { SettingsPage } from './pages_settings.tsx';
import { ThemeSettingsPage } from './pages_theme_settings.tsx';
import { ChartDashboardPage } from './pages_chart.tsx';
import { LoginMapPage } from './pages_map.tsx';
import { LoginPage } from './pages_login_reg.tsx';
export const router = createBrowserRouter([
{
path: '/',
element: <Navigate to="/admin" replace />
},
{
path: '/admin/login',
element: <LoginPage />
},
{
path: '/admin',
element: (
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
),
children: [
{
index: true,
element: <Navigate to="/admin/dashboard" />
},
{
path: 'dashboard',
element: <DashboardPage />,
errorElement: <ErrorPage />
},
{
path: 'users',
element: <UsersPage />,
errorElement: <ErrorPage />
},
{
path: 'settings',
element: <SettingsPage />,
errorElement: <ErrorPage />
},
{
path: 'theme-settings',
element: <ThemeSettingsPage />,
errorElement: <ErrorPage />
},
{
path: 'chart-dashboard',
element: <ChartDashboardPage />,
errorElement: <ErrorPage />
},
{
path: 'map-dashboard',
element: <LoginMapPage />,
errorElement: <ErrorPage />
},
{
path: 'know-info',
element: <KnowInfoPage />,
errorElement: <ErrorPage />
},
{
path: 'file-library',
element: <FileLibraryPage />,
errorElement: <ErrorPage />
},
{
path: 'messages',
element: <MessagesPage />,
errorElement: <ErrorPage />
},
],
},
]);

View File

@@ -1,76 +1,16 @@
import React, { useState, useEffect} from 'react';
import React from 'react';
import { createRoot } from 'react-dom/client';
import {
createBrowserRouter,
RouterProvider,
Outlet,
useNavigate,
useLocation,
Navigate,
useParams,
useRouteError
} from 'react-router';
import {
Layout, Menu, Button, Table, Space,
Form, Input, Select, message, Modal,
Card, Spin, Row, Col, Breadcrumb, Avatar,
Dropdown, ConfigProvider, theme, Typography,
Switch, Badge, Image, Upload, Divider, Descriptions,
Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
} from 'antd';
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
UserOutlined,
DashboardOutlined,
TeamOutlined,
SettingOutlined,
LogoutOutlined,
BellOutlined,
BookOutlined,
FileOutlined,
PieChartOutlined,
VerticalAlignTopOutlined,
CloseOutlined,
SearchOutlined
} from '@ant-design/icons';
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { RouterProvider } from 'react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday';
import localeData from 'dayjs/plugin/localeData';
import 'dayjs/locale/zh-cn';
import type {
GlobalConfig
} from '../share/types.ts';
import {
AuthProvider,
useAuth,
ThemeProvider,
useTheme,
} from './hooks_sys.tsx';
import {
DashboardPage
} from './pages_dashboard.tsx';
import {
UsersPage
} from './pages_users.tsx';
import {
FileLibraryPage
} from './pages_file_library.tsx';
import { KnowInfoPage } from './pages_know_info.tsx';
import { MessagesPage } from './pages_messages.tsx';
import {SettingsPage } from './pages_settings.tsx';
import {ThemeSettingsPage} from './pages_theme_settings.tsx'
import { ChartDashboardPage } from './pages_chart.tsx';
import { LoginMapPage } from './pages_map.tsx';
import { LoginPage } from './pages_login_reg.tsx';
import { ProtectedRoute } from './components_protected_route.tsx';
import { AuthProvider } from './hooks_sys.tsx';
import { ThemeProvider } from './hooks_sys.tsx';
import { router } from './routes.tsx';
import type { GlobalConfig } from '../share/types.ts';
// 配置 dayjs 插件
dayjs.extend(weekday);
@@ -79,12 +19,9 @@ dayjs.extend(localeData);
// 设置 dayjs 语言
dayjs.locale('zh-cn');
const { Header, Sider, Content } = Layout;
// 创建QueryClient实例
const queryClient = new QueryClient();
// 声明全局配置对象类型
declare global {
interface Window {
@@ -92,460 +29,8 @@ declare global {
}
}
// 主布局组件
const MainLayout = () => {
const [collapsed, setCollapsed] = useState(false);
const { user, logout } = useAuth();
const { isDark, toggleTheme } = useTheme();
const navigate = useNavigate();
const location = useLocation();
const [openKeys, setOpenKeys] = useState<string[]>([]);
const [showBackTop, setShowBackTop] = useState(false);
const [searchText, setSearchText] = useState('');
const [filteredMenuItems, setFilteredMenuItems] = useState<any[]>([]);
// 检测滚动位置,控制回到顶部按钮显示
useEffect(() => {
const handleScroll = () => {
setShowBackTop(window.pageYOffset > 300);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// 回到顶部
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
// 菜单项配置
const menuItems = [
{
key: '/dashboard',
icon: <DashboardOutlined />,
label: '仪表盘',
},
{
key: '/analysis',
icon: <PieChartOutlined />,
label: '数据分析',
children: [
{
key: '/chart-dashboard',
label: '图表统计',
},
{
key: '/map-dashboard',
label: '地图概览',
},
],
},
{
key: '/files',
icon: <FileOutlined />,
label: '文件管理',
children: [
{
key: '/file-library',
label: '文件库',
},
],
},
{
key: '/know-info',
icon: <BookOutlined />,
label: '知识库',
},
{
key: '/users',
icon: <TeamOutlined />,
label: '用户管理',
},
{
key: '/messages',
icon: <BellOutlined />,
label: '消息管理',
},
{
key: '/settings_group',
icon: <SettingOutlined />,
label: '系统设置',
children: [
{
key: '/theme-settings',
label: '主题设置',
},
{
key: '/settings',
label: '基本设置',
},
],
},
];
// 初始化filteredMenuItems
useEffect(() => {
setFilteredMenuItems(menuItems);
}, []);
// 搜索菜单项
const handleSearch = (value: string) => {
setSearchText(value);
if (!value.trim()) {
setFilteredMenuItems(menuItems);
return;
}
// 搜索功能 - 过滤菜单项
const filtered = menuItems.reduce((acc: any[], item) => {
// 检查主菜单项是否匹配
const mainItemMatch = item.label.toString().toLowerCase().includes(value.toLowerCase());
// 如果有子菜单,检查子菜单中是否有匹配项
if (item.children) {
const matchedChildren = item.children.filter(child =>
child.label.toString().toLowerCase().includes(value.toLowerCase())
);
if (matchedChildren.length > 0) {
// 如果有匹配的子菜单,创建包含匹配子菜单的副本
acc.push({
...item,
children: matchedChildren
});
return acc;
}
}
// 如果主菜单项匹配,添加整个项
if (mainItemMatch) {
acc.push(item);
}
return acc;
}, []);
setFilteredMenuItems(filtered);
};
// 清除搜索
const clearSearch = () => {
setSearchText('');
setFilteredMenuItems(menuItems);
};
const handleMenuClick = ({ key }: { key: string }) => {
navigate(`/admin${key}`);
// 如果有搜索文本,清除搜索
if (searchText) {
clearSearch();
}
};
// 处理登出
const handleLogout = async () => {
await logout();
navigate('/admin/login');
};
// 处理菜单展开/收起
const onOpenChange = (keys: string[]) => {
// 当侧边栏折叠时不保存openKeys状态
if (!collapsed) {
setOpenKeys(keys);
}
};
// 当侧边栏折叠状态改变时,控制菜单打开状态
useEffect(() => {
if (collapsed) {
setOpenKeys([]);
} else {
// 找到当前路径所属的父菜单
const currentPath = location.pathname.replace('/admin', '');
const parentKeys = menuItems
.filter(item => item.children && item.children.some(child => child.key === currentPath))
.map(item => item.key);
// 仅展开当前所在的菜单组
if (parentKeys.length > 0) {
setOpenKeys(parentKeys);
} else {
// 初始时可以根据需要设置要打开的菜单组
setOpenKeys([]);
}
}
}, [collapsed, location.pathname]);
// 用户下拉菜单项
const userMenuItems = [
{
key: 'profile',
label: '个人信息',
icon: <UserOutlined />
},
{
key: 'theme',
label: isDark ? '切换到亮色模式' : '切换到暗色模式',
icon: <SettingOutlined />,
onClick: toggleTheme
},
{
key: 'logout',
label: '退出登录',
icon: <LogoutOutlined />,
onClick: handleLogout
}
];
// 应用名称 - 从CONFIG中获取或使用默认值
const appName = window.CONFIG?.APP_NAME || '应用Starter';
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider
trigger={null}
collapsible
collapsed={collapsed}
width={240}
className="custom-sider"
style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
zIndex: 100,
}}
>
<div className="p-4">
<Typography.Title level={2} className="text-xl font-bold truncate">
{collapsed ? '应用' : appName}
</Typography.Title>
</div>
{/* 搜索框 - 仅在展开状态下显示 */}
{!collapsed && (
<div style={{ padding: '0 16px 16px' }}>
<Input
placeholder="搜索菜单..."
value={searchText}
onChange={(e) => handleSearch(e.target.value)}
suffix={
searchText ?
<Button
type="text"
size="small"
icon={<CloseOutlined />}
onClick={clearSearch}
/> :
<SearchOutlined />
}
/>
</div>
)}
<Menu
theme={isDark ? "light" : "light"}
mode="inline"
selectedKeys={[location.pathname.replace('/admin', '')]}
openKeys={openKeys}
onOpenChange={onOpenChange}
items={filteredMenuItems}
onClick={handleMenuClick}
/>
</Sider>
<Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
<Header className="p-0 flex justify-between items-center"
style={{
position: 'sticky',
top: 0,
zIndex: 99,
boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
}}
>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
className="w-16 h-16"
/>
<Space size="middle" className="mr-4">
<Badge count={5} offset={[0, 5]}>
<Button
type="text"
icon={<BellOutlined />}
/>
</Badge>
<Dropdown menu={{ items: userMenuItems }}>
<Space className="cursor-pointer">
<Avatar
src={user?.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&w=40&auto=format&fit=crop'}
icon={!user?.avatar && !navigator.onLine && <UserOutlined />}
/>
<span>
{user?.nickname || user?.username}
</span>
</Space>
</Dropdown>
</Space>
</Header>
<Content className="m-6" style={{ overflow: 'initial' }}>
<div className="site-layout-content p-6 rounded-lg">
<Outlet />
</div>
{/* 回到顶部按钮 */}
{showBackTop && (
<Button
type="primary"
shape="circle"
icon={<VerticalAlignTopOutlined />}
size="large"
onClick={scrollToTop}
style={{
position: 'fixed',
right: 30,
bottom: 30,
zIndex: 1000,
boxShadow: '0 3px 6px rgba(0,0,0,0.16)',
}}
/>
)}
</Content>
</Layout>
</Layout>
);
};
// 错误页面组件
const ErrorPage = () => {
const { isDark } = useTheme();
const error = useRouteError() as any;
const errorMessage = error?.statusText || error?.message || '未知错误';
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4"
style={{ color: isDark ? '#fff' : 'inherit' }}
>
<div className="max-w-3xl w-full">
<h1 className="text-2xl font-bold mb-4"></h1>
<Alert
type="error"
message={error?.message || '未知错误'}
description={
error?.stack ? (
<pre className="text-xs overflow-auto p-2 bg-gray-100 dark:bg-gray-800 rounded">
{error.stack}
</pre>
) : null
}
className="mb-4"
/>
<div className="flex gap-4">
<Button
type="primary"
onClick={() => window.location.reload()}
>
</Button>
<Button
onClick={() => window.location.href = '/admin'}
>
</Button>
</div>
</div>
</div>
);
};
// 应用入口组件
const App = () => {
// 路由配置
const router = createBrowserRouter([
{
path: '/',
element: <Navigate to="/admin" replace />
},
{
path: '/admin/login',
element: <LoginPage />
},
{
path: '/admin',
element: (
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
),
children: [
{
index: true,
element: <Navigate to="/admin/dashboard" />
},
{
path: 'dashboard',
element: <DashboardPage />,
errorElement: <ErrorPage />
},
{
path: 'users',
element: <UsersPage />,
errorElement: <ErrorPage />
},
{
path: 'settings',
element: <SettingsPage />,
errorElement: <ErrorPage />
},
{
path: 'theme-settings',
element: <ThemeSettingsPage />,
errorElement: <ErrorPage />
},
{
path: 'chart-dashboard',
element: <ChartDashboardPage />,
errorElement: <ErrorPage />
},
{
path: 'map-dashboard',
element: <LoginMapPage />,
errorElement: <ErrorPage />
},
{
path: 'know-info',
element: <KnowInfoPage />,
errorElement: <ErrorPage />
},
{
path: 'file-library',
element: <FileLibraryPage />,
errorElement: <ErrorPage />
},
{
path: 'messages',
element: <MessagesPage />,
errorElement: <ErrorPage />
},
],
},
]);
return <RouterProvider router={router} />
};
@@ -560,4 +45,3 @@ root.render(
</ThemeProvider>
</QueryClientProvider>
);

View File

@@ -33,6 +33,7 @@ export interface User {
role: string;
avatar?: string;
password?: string;
permissions?: string[];
}
export interface MenuItem {