perf(web): 路由级懒加载,修复失效 useMemo 与 HMR 全页刷新

- 11 个页面改为 React.lazy;Login 与 MainLayout 保持同步加载(未登录用户与
  登录后首帧的必经路径);新增统一占位 RouteFallback,替换权限校验处重复的 div
  首屏静态资源 gzip 497.7 kB → 268.4 kB(-46.1%),>500 kB 体积警告消失
- PlatformUsers 的 columns/memberColumns/merchantColumns 每次渲染都重建,
  使依赖它们的 items 的 useMemo 完全失效;三个回调 useCallback 化、
  三个列定义 useMemo 化,让 memo 真正命中
- useAuth/AuthContext 拆到 store/authContext.ts,使 store/auth.tsx 只导出
  AuthProvider 组件,消除 react(only-export-components) 导致的 HMR 整页刷新
  (同步更新 7 处引用)
- MainLayout 未使用的 catch 参数清理

oxlint:5 warnings → 0 warnings / 0 errors。
This commit is contained in:
yml2213
2026-10-06 19:43:21 +08:00
parent ae71bc99b8
commit c9e35d5d90
9 changed files with 75 additions and 58 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ import { useNavigate } from 'react-router-dom'
import { dashboardApi } from '../api'
import type { DashboardStats } from '../types'
import { PageHeader } from '../components/PageHeader'
import { useAuth } from '../store/auth'
import { useAuth } from '../store/authContext'
const numberText = (value?: number) => (value ?? 0).toLocaleString('zh-CN')
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Form, Input, Button, Typography, message, Space } from 'antd'
import { UserOutlined, LockOutlined, RightOutlined } from '@ant-design/icons'
import { useAuth } from '../store/auth'
import { useAuth } from '../store/authContext'
export default function Login() {
const { login } = useAuth()
+1 -1
View File
@@ -13,7 +13,7 @@ import {
message,
} from 'antd'
import { ReloadOutlined } from '@ant-design/icons'
import { useAuth } from '../store/auth'
import { useAuth } from '../store/authContext'
import { merchantApi } from '../api'
import { PageHeader } from '../components/PageHeader'
import type {
+1 -1
View File
@@ -28,7 +28,7 @@ import type { ColumnsType } from 'antd/es/table'
import { PageHeader } from '../components/PageHeader'
import ImageUploader from '../components/ImageUploader'
import { merchantApi, platformApi } from '../api'
import { useAuth } from '../store/auth'
import { useAuth } from '../store/authContext'
import type { PageResult, RechargeApplication } from '../types'
import { formatDateTime } from '../utils/time'
+13 -13
View File
@@ -6,7 +6,7 @@ import { platformApi, userApi } from '../api'
import { PageHeader } from '../components/PageHeader'
import type { MerchantMember, MerchantMemberGroup, PageResult, User } from '../types'
import { formatDateTime } from '../utils/time'
import { useAuth } from '../store/auth'
import { useAuth } from '../store/authContext'
import { roleText } from './merchantCenterUtils'
import { useNavigate } from 'react-router-dom'
@@ -62,11 +62,11 @@ export default function PlatformUsers() {
setModalOpen(true)
}
const openEdit = (record: User) => {
const openEdit = useCallback((record: User) => {
setEditingAdmin(record)
form.setFieldsValue({ username: record.username, nickname: record.nickname, password: '', status: record.status === 1 })
setModalOpen(true)
}
}, [form])
const submit = async () => {
const values = await form.validateFields()
@@ -94,7 +94,7 @@ export default function PlatformUsers() {
}
}
const remove = async (record: User) => {
const remove = useCallback(async (record: User) => {
try {
await userApi.remove(record.id)
message.success('平台管理员已删除')
@@ -102,9 +102,9 @@ export default function PlatformUsers() {
} catch (e) {
message.error(e instanceof Error ? e.message : '删除失败')
}
}
}, [loadAdmins])
const removeMerchantAccount = async (record: MerchantMember) => {
const removeMerchantAccount = useCallback(async (record: MerchantMember) => {
try {
await platformApi.deleteMerchantAccount(record.user_id)
message.success('商户账号已删除')
@@ -112,9 +112,9 @@ export default function PlatformUsers() {
} catch (e) {
message.error(e instanceof Error ? e.message : '删除失败')
}
}
}, [loadMerchantAccounts])
const columns: ColumnsType<User> = [
const columns: ColumnsType<User> = useMemo(() => [
{ title: '用户名', dataIndex: 'username', width: 260, render: (value) => <Typography.Text strong>{value}</Typography.Text> },
{ title: '昵称', dataIndex: 'nickname', width: 220, render: (value) => value || '-' },
{ title: '状态', dataIndex: 'status', width: 120, render: (value) => value === 1 ? <Tag color="green">启用</Tag> : <Tag>停用</Tag> },
@@ -131,9 +131,9 @@ export default function PlatformUsers() {
</Space>
),
},
]
], [currentUser?.id, openEdit, remove])
const memberColumns: ColumnsType<MerchantMember> = [
const memberColumns: ColumnsType<MerchantMember> = useMemo(() => [
{ title: '员工账号', dataIndex: ['user', 'username'], width: 260, render: (_, record) => <Typography.Text strong>{record.user?.username || `#${record.user_id}`}</Typography.Text> },
{ title: '昵称', dataIndex: ['user', 'nickname'], width: 220, render: (_, record) => record.user?.nickname || '-' },
{ title: '商户角色', dataIndex: 'role', width: 160, render: (role) => <Tag color="blue">{roleText(role)}</Tag> },
@@ -150,9 +150,9 @@ export default function PlatformUsers() {
<Button type="link" danger size="small" icon={<DeleteOutlined />}>删除账号</Button>
</Popconfirm>,
},
]
], [removeMerchantAccount])
const merchantColumns: ColumnsType<MerchantMemberGroup> = [
const merchantColumns: ColumnsType<MerchantMemberGroup> = useMemo(() => [
{
title: '商户',
width: 320,
@@ -174,7 +174,7 @@ export default function PlatformUsers() {
navigate('/merchant-members')
}}>管理商户成员</Button>,
},
]
], [navigate])
const items = useMemo(() => [
{