增加了版本号系统

This commit is contained in:
yml2213
2026-06-25 09:38:54 +08:00
parent f21f82cbed
commit e94b37c602
9 changed files with 131 additions and 22 deletions
+6
View File
@@ -0,0 +1,6 @@
import api from './client';
import type { AppInfo } from './types';
export const appApi = {
info: () => api.get<AppInfo, AppInfo>('/app-info'),
};
+1
View File
@@ -1,5 +1,6 @@
export * from './types';
export { accountApi } from './accounts';
export { appApi } from './app';
export { authApi } from './auth';
export { cookieApi } from './cookies';
export { loginApi } from './login';
+4
View File
@@ -13,6 +13,10 @@ export interface MessageDeletedResponse extends MessageResponse {
deleted: number;
}
export interface AppInfo {
version: string;
}
// ==================== Auth ====================
export interface LoginResult {
+25
View File
@@ -0,0 +1,25 @@
import { useEffect, useState } from 'react';
import { appApi } from '../api/modules';
import type { AppInfo } from '../api/modules';
export function useAppInfo() {
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
useEffect(() => {
let ignore = false;
appApi.info()
.then((info) => {
if (!ignore) setAppInfo(info);
})
.catch(() => {
if (!ignore) setAppInfo(null);
});
return () => {
ignore = true;
};
}, []);
return appInfo;
}
+47 -20
View File
@@ -12,6 +12,7 @@ import { authApi } from '../api/modules';
import { type ThemeMode } from '../store/theme';
import { useTheme } from '../store/useTheme';
import { usePermissions } from '../hooks/usePermissions';
import { useAppInfo } from '../hooks/useAppInfo';
const { Sider, Content } = Layout;
const { Text } = Typography;
@@ -35,6 +36,7 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
const [collapsed, setCollapsed] = useState(false);
const { mode, isDark, setMode } = useTheme();
const { can, canAny } = usePermissions(user);
const appInfo = useAppInfo();
useEffect(() => {
if (!user) navigate('/login');
@@ -100,6 +102,8 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
const siderTheme = isDark ? 'dark' : 'light';
const siderTextColor = isDark ? '#fff' : undefined;
const secondaryTextColor = isDark ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0.45)';
const versionText = appInfo?.version ? `v${appInfo.version}` : 'v--';
return (
<Layout style={{ height: '100vh', overflow: 'hidden' }}>
@@ -143,35 +147,58 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
{!collapsed && (
<>
<Text style={{ color: siderTextColor }}>{user.username}</Text>
<Text style={{ color: isDark ? 'rgba(255,255,255,0.65)' : 'rgba(0,0,0,0.45)', fontSize: 12 }}>
<Text style={{ color: secondaryTextColor, fontSize: 12 }}>
({ROLE_LABELS[user.role] || user.role})
</Text>
</>
)}
</Space>
{!collapsed && (
<Text style={{
display: 'block',
color: secondaryTextColor,
fontSize: 12,
lineHeight: '20px',
marginBottom: 8,
}}>
{versionText}
</Text>
)}
{collapsed ? (
<Space direction="vertical" style={{ width: '100%' }}>
<Dropdown
menu={{ items: THEME_OPTIONS.map(o => ({ key: o.key, label: o.label, icon: o.icon, onClick: () => setMode(o.key) })) }}
trigger={['click']}
>
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Dropdown
menu={{ items: THEME_OPTIONS.map(o => ({ key: o.key, label: o.label, icon: o.icon, onClick: () => setMode(o.key) })) }}
trigger={['click']}
>
<Button
block
type="text"
size="small"
icon={isDark ? <MoonOutlined /> : <SunOutlined />}
style={{ color: secondaryTextColor }}
/>
</Dropdown>
<Button
block
type="text"
size="small"
icon={isDark ? <MoonOutlined /> : <SunOutlined />}
style={{ color: isDark ? 'rgba(255,255,255,0.65)' : undefined }}
icon={<LogoutOutlined />}
onClick={handleLogout}
style={{ color: secondaryTextColor }}
/>
</Dropdown>
<Button
block
type="text"
size="small"
icon={<LogoutOutlined />}
onClick={handleLogout}
style={{ color: isDark ? 'rgba(255,255,255,0.65)' : undefined }}
/>
</Space>
</Space>
<Text style={{
display: 'block',
color: secondaryTextColor,
fontSize: 11,
lineHeight: '18px',
textAlign: 'center',
marginTop: 8,
}}>
{versionText}
</Text>
</>
) : (
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
<Dropdown
@@ -182,7 +209,7 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
type="text"
size="small"
icon={isDark ? <MoonOutlined /> : <SunOutlined />}
style={{ color: isDark ? 'rgba(255,255,255,0.65)' : undefined }}
style={{ color: isDark ? secondaryTextColor : undefined }}
>
{THEME_OPTIONS.find(o => o.key === mode)?.label}
</Button>
@@ -192,7 +219,7 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
size="small"
icon={<LogoutOutlined />}
onClick={handleLogout}
style={{ color: isDark ? 'rgba(255,255,255,0.65)' : undefined }}
style={{ color: isDark ? secondaryTextColor : undefined }}
>
退
</Button>
+13 -1
View File
@@ -6,13 +6,16 @@ import { authApi } from '../api/modules';
import { setAuth, type AuthUser } from '../store/auth';
import { getErrorMessage } from '../utils/error';
import { useTheme } from '../store/useTheme';
import { useAppInfo } from '../hooks/useAppInfo';
const { Title } = Typography;
const { Title, Text } = Typography;
export default function LoginPage({ onLogin }: { onLogin?: () => void }) {
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const { isDark } = useTheme();
const appInfo = useAppInfo();
const versionText = appInfo?.version ? `v${appInfo.version}` : 'v--';
const onFinish = async (values: { username: string; password: string }) => {
setLoading(true);
@@ -49,6 +52,15 @@ export default function LoginPage({ onLogin }: { onLogin?: () => void }) {
<Title level={3} style={{ textAlign: 'center', marginBottom: 32 }}>
</Title>
<Text type="secondary" style={{
display: 'block',
textAlign: 'center',
marginTop: -20,
marginBottom: 24,
fontSize: 12,
}}>
{versionText}
</Text>
<Form onFinish={onFinish} size="large">
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
<Input prefix={<UserOutlined />} placeholder="用户名" />