feat: 优化侧边栏动画延迟和性能,实现路由懒加载

调整侧边栏动画延迟时间以匹配透明度动画,添加contain: paint优化性能
将路由组件改为懒加载模式并添加加载状态
优化学生搜索功能,预计算拼音数据提升性能
This commit is contained in:
Linkon
2026-01-18 23:26:34 +08:00
parent 1a3f3812d5
commit 8f79653e06
4 changed files with 93 additions and 69 deletions
+3 -2
View File
@@ -114,7 +114,7 @@ html[theme-mode='dark'] .ss-sidebar .t-menu__item.t-is-active svg:not([fill='non
z-index: 10;
flex-shrink: 0;
animation: twinkle-animation 3s infinite ease-in-out;
transition: opacity 0.1s ease, transform 0.1s ease;
transition: opacity 0.07s ease, transform 0.15s ease;
}
.global-sidebar-toggle.hidden {
@@ -134,9 +134,10 @@ html[theme-mode='dark'] .ss-sidebar .t-menu__item.t-is-active svg:not([fill='non
padding: 12px 8px;
gap: 12px;
width: 60px;
transition: opacity 0.15s ease, transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
transition: opacity 0.1s ease, transform 0.1s cubic-bezier(0.34, 1.56, 0.64, 1);
backface-visibility: hidden;
transform: translateZ(0);
contain: paint; /* 性能优化:限制重绘范围 */
}
.sidebar-content-area.hidden {
+41 -24
View File
@@ -1,14 +1,16 @@
import { Layout, Space, Button, Tag } from 'tdesign-react'
import React, { Suspense, lazy } from 'react'
import { Layout, Space, Button, Tag, Loading } from 'tdesign-react'
import { Routes, Route, Navigate } from 'react-router-dom'
import { Home } from './Home'
import { StudentManager } from './StudentManager'
import { Settings } from './Settings'
import { ReasonManager } from './ReasonManager'
import { ScoreManager } from './ScoreManager'
import { Leaderboard } from './Leaderboard'
import { SettlementHistory } from './SettlementHistory'
import { WindowControls } from './WindowControls'
const Home = lazy(() => import('./Home').then(m => ({ default: m.Home })))
const StudentManager = lazy(() => import('./StudentManager').then(m => ({ default: m.StudentManager })))
const Settings = lazy(() => import('./Settings').then(m => ({ default: m.Settings })))
const ReasonManager = lazy(() => import('./ReasonManager').then(m => ({ default: m.ReasonManager })))
const ScoreManager = lazy(() => import('./ScoreManager').then(m => ({ default: m.ScoreManager })))
const Leaderboard = lazy(() => import('./Leaderboard').then(m => ({ default: m.Leaderboard })))
const SettlementHistory = lazy(() => import('./SettlementHistory').then(m => ({ default: m.SettlementHistory })))
const { Content } = Layout
interface ContentAreaProps {
@@ -85,22 +87,37 @@ export function ContentArea({
</div>
<Content style={{ flex: 1, overflowY: 'auto' }}>
<Routes>
<Route
path="/"
element={<Home canEdit={permission === 'admin' || permission === 'points'} />}
/>
<Route path="/students" element={<StudentManager canEdit={permission === 'admin'} />} />
<Route
path="/score"
element={<ScoreManager canEdit={permission === 'admin' || permission === 'points'} />}
/>
<Route path="/leaderboard" element={<Leaderboard />} />
<Route path="/settlements" element={<SettlementHistory />} />
<Route path="/reasons" element={<ReasonManager canEdit={permission === 'admin'} />} />
<Route path="/settings" element={<Settings permission={permission} />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
<Suspense
fallback={
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%'
}}
>
<Loading text="正在载入页面..." />
</div>
}
>
<Routes>
<Route
path="/"
element={<Home canEdit={permission === 'admin' || permission === 'points'} />}
/>
<Route path="/students" element={<StudentManager canEdit={permission === 'admin'} />} />
<Route
path="/score"
element={<ScoreManager canEdit={permission === 'admin' || permission === 'points'} />}
/>
<Route path="/leaderboard" element={<Leaderboard />} />
<Route path="/settlements" element={<SettlementHistory />} />
<Route path="/reasons" element={<ReasonManager canEdit={permission === 'admin'} />} />
<Route path="/settings" element={<Settings permission={permission} />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Suspense>
</Content>
</Layout>
)
@@ -38,10 +38,10 @@ export const GlobalSidebar: React.FC = () => {
if ((window as any).api) {
(window as any).api.windowResize(24, 300)
}
// 3. 最后重新显示三角
// 3. 最后重新显示三角(等待透明度动画完成)
setTimeout(() => {
setShowToggle(true)
}, 20)
}, 150)
}, 150)
}
+47 -41
View File
@@ -18,6 +18,8 @@ interface student {
id: number
name: string
score: number
pinyinName?: string
pinyinFirst?: string
}
interface reason {
@@ -51,33 +53,6 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
window.dispatchEvent(new CustomEvent('ss:data-updated', { detail: { category } }))
}
const fetchData = useCallback(async (silent = false) => {
if (!(window as any).api) return
if (!silent) setLoading(true)
const [stuRes, reaRes] = await Promise.all([
(window as any).api.queryStudents({}),
(window as any).api.queryReasons()
])
if (stuRes.success) setStudents(stuRes.data)
if (reaRes.success) setReasons(reaRes.data)
if (!silent) setLoading(false)
}, [])
useEffect(() => {
fetchData()
const onDataUpdated = (e: any) => {
const category = e?.detail?.category
// 仅在学生名单、理由列表或全量更新时才重新拉取数据
// 积分变动(events)现在由本地状态维护,不再触发全量刷新以防止页面跳动
if (category === 'students' || category === 'reasons' || category === 'all') {
fetchData(true)
}
}
window.addEventListener('ss:data-updated', onDataUpdated as any)
return () => window.removeEventListener('ss:data-updated', onDataUpdated as any)
}, [fetchData])
// 获取姓氏
const getSurname = (name: string) => {
if (!name) return ''
@@ -95,6 +70,38 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
return py ? py.toUpperCase() : '#'
}
const fetchData = useCallback(async (silent = false) => {
if (!(window as any).api) return
if (!silent) setLoading(true)
const [stuRes, reaRes] = await Promise.all([
(window as any).api.queryStudents({}),
(window as any).api.queryReasons()
])
if (stuRes.success) {
const enrichedStudents = (stuRes.data as student[]).map(s => ({
...s,
pinyinName: pinyin(s.name, { toneType: 'none' }).toLowerCase(),
pinyinFirst: getFirstLetter(s.name)
}))
setStudents(enrichedStudents)
}
if (reaRes.success) setReasons(reaRes.data)
if (!silent) setLoading(false)
}, [])
useEffect(() => {
fetchData()
const onDataUpdated = (e: any) => {
const category = e?.detail?.category
if (category === 'students' || category === 'reasons' || category === 'all') {
fetchData(true)
}
}
window.addEventListener('ss:data-updated', onDataUpdated as any)
return () => window.removeEventListener('ss:data-updated', onDataUpdated as any)
}, [fetchData])
// 获取展示用的文字
const getDisplayText = (name: string) => {
if (!name) return ''
@@ -102,23 +109,22 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
}
// 拼音匹配
const matchStudentName = useCallback((name: string, keyword: string) => {
const matchStudentName = useCallback((s: student, keyword: string) => {
const q0 = keyword.trim().toLowerCase()
if (!q0) return true
const nameLower = String(name).toLowerCase()
const nameLower = String(s.name).toLowerCase()
if (nameLower.includes(q0)) return true
const pyLower = s.pinyinName || ''
if (pyLower.includes(q0)) return true
const q1 = q0.replace(/\s+/g, '')
if (q1 && nameLower.replace(/\s+/g, '').includes(q1)) return true
if (q1 && (nameLower.replace(/\s+/g, '').includes(q1) || pyLower.replace(/\s+/g, '').includes(q1))) return true
try {
const m0 = match(name, q0)
const m0 = match(s.name, q0)
if (Array.isArray(m0)) return true
if (q1 && q1 !== q0) {
const m1 = match(name, q1)
if (Array.isArray(m1)) return true
}
} catch {
return false
}
@@ -128,13 +134,13 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
// 过滤和排序学生
const sortedStudents = useMemo(() => {
const filtered = students.filter((s) => matchStudentName(s.name, searchKeyword))
const filtered = students.filter((s) => matchStudentName(s, searchKeyword))
switch (sortType) {
case 'alphabet':
return filtered.sort((a, b) => {
const pyA = pinyin(a.name, { toneType: 'none' })
const pyB = pinyin(b.name, { toneType: 'none' })
const pyA = a.pinyinName || ''
const pyB = b.pinyinName || ''
return pyA.localeCompare(pyB)
})
case 'surname':
@@ -161,7 +167,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
const groups: Record<string, student[]> = {}
sortedStudents.forEach((s) => {
const key = sortType === 'alphabet' ? getFirstLetter(s.name) : getSurname(s.name)
const key = sortType === 'alphabet' ? (s.pinyinFirst || '#') : getSurname(s.name)
if (!groups[key]) groups[key] = []
groups[key].push(s)
})
@@ -822,7 +828,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
suffixIcon={
reasonContent ? (
<DeleteIcon
onClick={() => setReasonContent('')}
onClick={() => setSearchKeyword('')}
style={{ cursor: 'pointer' }}
/>
) : undefined
@@ -883,4 +889,4 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
</Dialog>
</div>
)
}
}