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:
1 parent
24d2a96144
commit
93ab6a7b9e
17 files changed
+1058
-60
No files matched your search
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user