2026-01-19 11:03:08 +00:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
2026-03-26 06:55:12 +00:00
|
|
|
|
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom';
|
|
|
|
|
|
import { ConfigProvider, theme, App as AntdApp } from 'antd';
|
|
|
|
|
|
import zhCN from 'antd/locale/zh_CN';
|
2026-01-19 11:03:08 +00:00
|
|
|
|
import apiClient from './utils/apiClient';
|
|
|
|
|
|
import { buildApiUrl, API_ENDPOINTS } from './config/api';
|
|
|
|
|
|
import HomePage from './pages/HomePage';
|
|
|
|
|
|
import Dashboard from './pages/Dashboard';
|
|
|
|
|
|
import AdminDashboard from './pages/AdminDashboard';
|
|
|
|
|
|
import MeetingDetails from './pages/MeetingDetails';
|
|
|
|
|
|
import MeetingPreview from './pages/MeetingPreview';
|
|
|
|
|
|
import AdminManagement from './pages/AdminManagement';
|
|
|
|
|
|
import PromptManagementPage from './pages/PromptManagementPage';
|
2026-03-26 06:55:12 +00:00
|
|
|
|
import PromptConfigPage from './pages/PromptConfigPage';
|
2026-01-19 11:03:08 +00:00
|
|
|
|
import KnowledgeBasePage from './pages/KnowledgeBasePage';
|
|
|
|
|
|
import EditKnowledgeBase from './pages/EditKnowledgeBase';
|
|
|
|
|
|
import ClientDownloadPage from './pages/ClientDownloadPage';
|
|
|
|
|
|
import AccountSettings from './pages/AccountSettings';
|
2026-03-26 06:55:12 +00:00
|
|
|
|
import MeetingCenterPage from './pages/MeetingCenterPage';
|
|
|
|
|
|
import MainLayout from './components/MainLayout';
|
|
|
|
|
|
import menuService from './services/menuService';
|
2026-01-19 11:03:08 +00:00
|
|
|
|
import './App.css';
|
2026-03-26 06:55:12 +00:00
|
|
|
|
import './styles/console-theme.css';
|
|
|
|
|
|
|
|
|
|
|
|
// Layout Wrapper to inject user and handleLogout
|
|
|
|
|
|
const AuthenticatedLayout = ({ user, handleLogout }) => {
|
|
|
|
|
|
// 如果还在加载中或用户不存在,不渲染,避免闪烁
|
|
|
|
|
|
if (!user) return null;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<MainLayout user={user} onLogout={handleLogout}>
|
|
|
|
|
|
<Outlet />
|
|
|
|
|
|
</MainLayout>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const DefaultMenuRedirect = ({ user }) => {
|
|
|
|
|
|
const [targetPath, setTargetPath] = useState(null);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
let active = true;
|
|
|
|
|
|
|
|
|
|
|
|
const resolveDefaultPath = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const path = await menuService.getDefaultPath();
|
|
|
|
|
|
if (active) {
|
|
|
|
|
|
setTargetPath(path || '/dashboard');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Resolve default menu path failed:', error);
|
|
|
|
|
|
if (active) {
|
|
|
|
|
|
setTargetPath('/dashboard');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (user) {
|
|
|
|
|
|
resolveDefaultPath();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
active = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [user]);
|
|
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
|
return <Navigate to="/" replace />;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!targetPath) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="app-loading">
|
|
|
|
|
|
<div className="loading-spinner"></div>
|
|
|
|
|
|
<p>加载菜单中...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return <Navigate to={targetPath} replace />;
|
|
|
|
|
|
};
|
2026-01-19 11:03:08 +00:00
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
|
const [user, setUser] = useState(null);
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
|
|
// Load user from localStorage on app start
|
|
|
|
|
|
useEffect(() => {
|
2026-03-26 06:55:12 +00:00
|
|
|
|
const savedAuth = localStorage.getItem('iMeetingUser');
|
|
|
|
|
|
if (savedAuth && savedAuth !== "undefined" && savedAuth !== "null") {
|
2026-01-19 11:03:08 +00:00
|
|
|
|
try {
|
2026-03-26 06:55:12 +00:00
|
|
|
|
const authData = JSON.parse(savedAuth);
|
|
|
|
|
|
// 如果数据包含 user 字段,则提取 user 字段(适应新结构)
|
|
|
|
|
|
// 否则使用整个对象(兼容旧结构)
|
|
|
|
|
|
const userData = authData.user || authData;
|
|
|
|
|
|
|
|
|
|
|
|
if (userData && typeof userData === 'object' && (userData.user_id || userData.id)) {
|
|
|
|
|
|
setUser(userData);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
localStorage.removeItem('iMeetingUser');
|
|
|
|
|
|
}
|
2026-01-19 11:03:08 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error parsing saved user:', error);
|
|
|
|
|
|
localStorage.removeItem('iMeetingUser');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-03-26 06:55:12 +00:00
|
|
|
|
const handleLogin = (authData) => {
|
|
|
|
|
|
if (authData) {
|
|
|
|
|
|
menuService.clearCache();
|
|
|
|
|
|
// 提取用户信息用于 UI 展示
|
|
|
|
|
|
const userData = authData.user || authData;
|
|
|
|
|
|
setUser(userData);
|
|
|
|
|
|
// 存入完整 auth 数据(包含 token)供拦截器使用
|
|
|
|
|
|
localStorage.setItem('iMeetingUser', JSON.stringify(authData));
|
|
|
|
|
|
}
|
2026-01-19 11:03:08 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleLogout = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await apiClient.post(buildApiUrl(API_ENDPOINTS.AUTH.LOGOUT));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Logout API error:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setUser(null);
|
|
|
|
|
|
localStorage.removeItem('iMeetingUser');
|
2026-03-26 06:55:12 +00:00
|
|
|
|
menuService.clearCache();
|
|
|
|
|
|
window.location.href = '/';
|
2026-01-19 11:03:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="app-loading">
|
|
|
|
|
|
<div className="loading-spinner"></div>
|
|
|
|
|
|
<p>加载中...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-03-26 06:55:12 +00:00
|
|
|
|
<ConfigProvider
|
|
|
|
|
|
locale={zhCN}
|
|
|
|
|
|
theme={{
|
|
|
|
|
|
token: {
|
|
|
|
|
|
colorPrimary: '#1d4ed8',
|
|
|
|
|
|
colorSuccess: '#0f766e',
|
|
|
|
|
|
colorWarning: '#d97706',
|
|
|
|
|
|
colorError: '#c2410c',
|
|
|
|
|
|
borderRadius: 12,
|
|
|
|
|
|
borderRadiusLG: 16,
|
|
|
|
|
|
wireframe: false,
|
|
|
|
|
|
fontFamily: '"MiSans", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif',
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
colorTextBase: '#112b4e',
|
|
|
|
|
|
colorBgLayout: 'transparent',
|
|
|
|
|
|
},
|
|
|
|
|
|
algorithm: theme.defaultAlgorithm,
|
|
|
|
|
|
components: {
|
|
|
|
|
|
Layout: {
|
|
|
|
|
|
bodyBg: 'transparent',
|
|
|
|
|
|
siderBg: 'rgba(255,255,255,0.82)',
|
|
|
|
|
|
headerBg: 'transparent',
|
|
|
|
|
|
},
|
|
|
|
|
|
Card: {
|
|
|
|
|
|
paddingLG: 18,
|
|
|
|
|
|
},
|
|
|
|
|
|
Table: {
|
|
|
|
|
|
headerBorderRadius: 14,
|
|
|
|
|
|
},
|
|
|
|
|
|
Button: {
|
|
|
|
|
|
controlHeight: 40,
|
|
|
|
|
|
controlHeightLG: 46,
|
|
|
|
|
|
borderRadius: 12,
|
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
|
paddingInline: 18,
|
|
|
|
|
|
defaultBorderColor: 'rgba(148, 163, 184, 0.24)',
|
|
|
|
|
|
defaultColor: '#274365',
|
|
|
|
|
|
defaultBg: 'rgba(255,255,255,0.92)',
|
|
|
|
|
|
defaultHoverBg: '#ffffff',
|
|
|
|
|
|
defaultHoverBorderColor: 'rgba(59, 130, 246, 0.3)',
|
|
|
|
|
|
defaultHoverColor: '#1d4ed8',
|
|
|
|
|
|
defaultActiveBg: '#eff6ff',
|
|
|
|
|
|
primaryShadow: '0 12px 24px rgba(29, 78, 216, 0.18)',
|
|
|
|
|
|
dangerShadow: '0 12px 24px rgba(220, 38, 38, 0.16)',
|
|
|
|
|
|
},
|
|
|
|
|
|
Switch: {
|
|
|
|
|
|
trackMinWidth: 40,
|
|
|
|
|
|
trackHeight: 22,
|
|
|
|
|
|
trackPadding: 2,
|
|
|
|
|
|
handleSize: 18,
|
|
|
|
|
|
innerMinMargin: 4,
|
|
|
|
|
|
innerMaxMargin: 26,
|
|
|
|
|
|
borderRadius: 100,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<AntdApp message={{ top: 64, maxCount: 3 }}>
|
|
|
|
|
|
<Router>
|
|
|
|
|
|
<div className="app">
|
|
|
|
|
|
<Routes>
|
|
|
|
|
|
{/* Public Routes */}
|
|
|
|
|
|
<Route path="/" element={
|
|
|
|
|
|
user ? <DefaultMenuRedirect user={user} /> : <HomePage onLogin={handleLogin} />
|
|
|
|
|
|
} />
|
|
|
|
|
|
|
|
|
|
|
|
<Route path="/meetings/preview/:meeting_id" element={<MeetingPreview />} />
|
|
|
|
|
|
<Route path="/downloads" element={<ClientDownloadPage />} />
|
|
|
|
|
|
|
|
|
|
|
|
{/* Authenticated Routes */}
|
|
|
|
|
|
<Route element={user ? <AuthenticatedLayout user={user} handleLogout={handleLogout} /> : <Navigate to="/" replace />}>
|
|
|
|
|
|
<Route path="/dashboard" element={
|
|
|
|
|
|
user?.role_id === 1
|
|
|
|
|
|
? <AdminDashboard user={user} onLogout={handleLogout} />
|
|
|
|
|
|
: <Dashboard user={user} onLogout={handleLogout} />
|
|
|
|
|
|
} />
|
|
|
|
|
|
<Route path="/meetings/center" element={
|
|
|
|
|
|
user?.role_id === 1
|
|
|
|
|
|
? <Navigate to="/dashboard" replace />
|
|
|
|
|
|
: <MeetingCenterPage user={user} />
|
|
|
|
|
|
} />
|
|
|
|
|
|
<Route path="/meetings/history" element={<Navigate to="/meetings/center" replace />} />
|
|
|
|
|
|
<Route path="/meetings/:meeting_id" element={<MeetingDetails user={user} />} />
|
|
|
|
|
|
<Route path="/meetings/create" element={<Navigate to="/meetings/center" replace />} />
|
|
|
|
|
|
<Route path="/meetings/edit/:meeting_id" element={<Navigate to="/meetings/center" replace />} />
|
|
|
|
|
|
<Route path="/admin/management" element={
|
|
|
|
|
|
user?.role_id === 1
|
|
|
|
|
|
? <Navigate to="/admin/management/system-overview" replace />
|
|
|
|
|
|
: <Navigate to="/dashboard" replace />
|
|
|
|
|
|
} />
|
|
|
|
|
|
<Route path="/admin/management/:moduleKey" element={
|
|
|
|
|
|
user?.role_id === 1 ? <AdminManagement user={user} /> : <Navigate to="/dashboard" replace />
|
|
|
|
|
|
} />
|
|
|
|
|
|
<Route path="/prompt-management" element={
|
|
|
|
|
|
user?.role_id === 1 ? <PromptManagementPage user={user} /> : <Navigate to="/dashboard" replace />
|
|
|
|
|
|
} />
|
|
|
|
|
|
<Route path="/prompt-config" element={<PromptConfigPage user={user} />} />
|
|
|
|
|
|
<Route path="/personal-prompts" element={
|
|
|
|
|
|
user?.role_id === 1 ? <Navigate to="/dashboard" replace /> : <Navigate to="/prompt-config" replace />
|
|
|
|
|
|
} />
|
|
|
|
|
|
<Route path="/knowledge-base" element={<KnowledgeBasePage user={user} />} />
|
|
|
|
|
|
<Route path="/knowledge-base/edit/:kb_id" element={<EditKnowledgeBase user={user} />} />
|
|
|
|
|
|
<Route path="/account-settings" element={<AccountSettings user={user} onUpdateUser={handleLogin} />} />
|
|
|
|
|
|
</Route>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Catch all */}
|
|
|
|
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
|
|
|
|
</Routes>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Router>
|
|
|
|
|
|
</AntdApp>
|
|
|
|
|
|
</ConfigProvider>
|
2026-01-19 11:03:08 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 06:55:12 +00:00
|
|
|
|
export default App;
|