feat(web): migrate account and user management to React

Add React account and user administration flows, including TOTP and SSO handling. Route /next SSO callbacks to React while preserving the existing Vue and native app behavior, and cover the routing policy with API tests.
This commit is contained in:
biss committed 2026-10-01 13:28:42 +08:00
1 parent f802598dab
commit 1ab9b76101
17 files changed
+1058 -60

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
import type { AxiosRequestConfig } from 'axios'
import http from './http'
function responseFileName(header: string | undefined, fallback: string) {
if (!header) return fallback
const encoded = header.match(/filename\*=UTF-8''([^;]+)/i)?.[1]
if (encoded) return decodeURIComponent(encoded)
const plain = header.match(/filename="?([^";]+)"?/i)?.[1]
return plain || fallback
}
export async function downloadApiFile(
path: string,
fallbackName: string,
config?: AxiosRequestConfig,
) {
const response = await http.get(path, { ...config, responseType: 'blob' })
const url = URL.createObjectURL(response.data)
try {
const anchor = document.createElement('a')
anchor.href = url
anchor.download = responseFileName(response.headers['content-disposition'], fallbackName)
document.body.appendChild(anchor)
anchor.click()
anchor.remove()
} finally {
URL.revokeObjectURL(url)
}
}
export async function importExcel(path: string, file: File) {
const form = new FormData()
form.append('file', file)
return http.post(path, form, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 60000,
})
}
+14 -7
View File
@@ -1,5 +1,5 @@
import { lazy, Suspense, useEffect } from 'react'
import { ConfigProvider, Skeleton } from 'antd'
import { App as AntdApp, ConfigProvider, Skeleton } from 'antd'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
import zhCN from 'antd/locale/zh_CN'
@@ -7,7 +7,10 @@ import { useAuthStore } from '../stores/auth'
const LoginPage = lazy(() => import('../pages/LoginPage'))
const DashboardPage = lazy(() => import('../pages/DashboardPage'))
const MigrationPlaceholderPage = lazy(() => import('../pages/MigrationPlaceholderPage'))
const AccountPage = lazy(() => import('../pages/AccountPage'))
const UsersPage = lazy(() => import('../pages/UsersPage'))
const SsoCallbackPage = lazy(() => import('../pages/SsoCallbackPage'))
const SsoBindPage = lazy(() => import('../pages/SsoBindPage'))
const AppShell = lazy(() => import('../layout/AppShell'))
const queryClient = new QueryClient({
@@ -43,10 +46,12 @@ function AppRoutes() {
<SessionExpiryBridge />
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/sso/callback" element={<SsoCallbackPage />} />
<Route path="/sso/bind" element={<SsoBindPage />} />
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/account" element={<MigrationPlaceholderPage />} />
<Route path="/users" element={<MigrationPlaceholderPage />} />
<Route path="/account" element={<AccountPage />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/" element={<Navigate to="/dashboard" replace />} />
</Route>
<Route path="*" element={<Navigate to="/dashboard" replace />} />
@@ -79,9 +84,11 @@ export default function App() {
},
}}
>
<QueryClientProvider client={queryClient}>
<BrowserRouter basename="/next"><AppRoutes /></BrowserRouter>
</QueryClientProvider>
<AntdApp>
<QueryClientProvider client={queryClient}>
<BrowserRouter basename="/next"><AppRoutes /></BrowserRouter>
</QueryClientProvider>
</AntdApp>
</ConfigProvider>
)
}
+22
View File
@@ -0,0 +1,22 @@
import type { ReactNode } from 'react'
interface PageHeaderProps {
kicker: string
title: string
description: string
actions?: ReactNode
aside?: ReactNode
}
export default function PageHeader({ kicker, title, description, actions, aside }: PageHeaderProps) {
return (
<header className="page-heading">
<div>
<span className="page-heading__kicker">{kicker}</span>
<h1>{title}</h1>
<p>{description}</p>
</div>
{aside ?? actions}
</header>
)
}
Loaded 3 of 17 files, more files were not shown because too many files have changed in this diff. Show more