From 8f79653e06787fb4eecdfa534333deab61bf75db Mon Sep 17 00:00:00 2001 From: Linkon <116425752+Linkon-lcw@users.noreply.github.com> Date: Sun, 18 Jan 2026 23:26:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E5=8A=A8=E7=94=BB=E5=BB=B6=E8=BF=9F=E5=92=8C=E6=80=A7?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=AE=9E=E7=8E=B0=E8=B7=AF=E7=94=B1=E6=87=92?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整侧边栏动画延迟时间以匹配透明度动画,添加contain: paint优化性能 将路由组件改为懒加载模式并添加加载状态 优化学生搜索功能,预计算拼音数据提升性能 --- src/renderer/src/assets/main.css | 5 +- src/renderer/src/components/ContentArea.tsx | 65 +++++++++----- src/renderer/src/components/GlobalSidebar.tsx | 4 +- src/renderer/src/components/Home.tsx | 88 ++++++++++--------- 4 files changed, 93 insertions(+), 69 deletions(-) diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index 28e027f..37f2d7e 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -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 { diff --git a/src/renderer/src/components/ContentArea.tsx b/src/renderer/src/components/ContentArea.tsx index 11da34a..a811a6d 100644 --- a/src/renderer/src/components/ContentArea.tsx +++ b/src/renderer/src/components/ContentArea.tsx @@ -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({ - - } - /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - + + + + } + > + + } + /> + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + + ) diff --git a/src/renderer/src/components/GlobalSidebar.tsx b/src/renderer/src/components/GlobalSidebar.tsx index ee978f3..d7bc00d 100644 --- a/src/renderer/src/components/GlobalSidebar.tsx +++ b/src/renderer/src/components/GlobalSidebar.tsx @@ -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) } diff --git a/src/renderer/src/components/Home.tsx b/src/renderer/src/components/Home.tsx index 494e027..d274829 100644 --- a/src/renderer/src/components/Home.tsx +++ b/src/renderer/src/components/Home.tsx @@ -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 = {} 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 ? ( setReasonContent('')} + onClick={() => setSearchKeyword('')} style={{ cursor: 'pointer' }} /> ) : undefined @@ -883,4 +889,4 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { ) -} +} \ No newline at end of file