From e9f994b5110fedb76aa6489426846e3e438c744b Mon Sep 17 00:00:00 2001 From: Linkon <116425752+Linkon-lcw@users.noreply.github.com> Date: Sun, 18 Jan 2026 21:54:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E7=BC=A9=E6=94=BE=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现窗口缩放功能,允许用户在设置中选择70%-150%的界面缩放比例 添加相关IPC通信和设置项处理逻辑 更新设置界面添加缩放选项控件 --- src/main/index.ts | 1 + src/main/services/SettingsService.ts | 13 +- src/main/services/WindowManager.ts | 10 ++ src/renderer/src/components/ContentArea.tsx | 5 +- src/renderer/src/components/Home.tsx | 141 +++++++++++++++----- src/renderer/src/components/Settings.tsx | 38 +++++- src/renderer/src/components/Sidebar.tsx | 9 +- 7 files changed, 180 insertions(+), 37 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 2b210e6..e647fdd 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -161,6 +161,7 @@ app.whenReady().then(async () => { if (logLevelSetting) { ctx.logger.setLevel(logLevelSetting) } + ctx.logger.info('Application starting...') const mainConsole = console as any diff --git a/src/main/services/SettingsService.ts b/src/main/services/SettingsService.ts index 4a786ae..9c5a6b9 100644 --- a/src/main/services/SettingsService.ts +++ b/src/main/services/SettingsService.ts @@ -1,6 +1,6 @@ import { Service } from '../../shared/kernel' import { MainContext } from '../context' -import { BrowserWindow } from 'electron' +import { BrowserWindow, webContents } from 'electron' import type { IpcMainInvokeEvent } from 'electron' import type { settingsKey, settingsSpec, settingChange } from '../../preload/types' import type { permissionLevel } from './PermissionService' @@ -48,6 +48,17 @@ export class SettingsService extends Service { onChanged: (ctx, next) => { ctx.logger.setLevel(next as any) } + }, + window_zoom: { + kind: 'number', + defaultValue: 1.0, + writePermission: 'admin', + onChanged: (_ctx, next) => { + const zoom = Number(next) || 1.0 + webContents.getAllWebContents().forEach((wc: any) => { + wc.setZoomFactor(zoom) + }) + } } } diff --git a/src/main/services/WindowManager.ts b/src/main/services/WindowManager.ts index 861ab55..4e869d7 100644 --- a/src/main/services/WindowManager.ts +++ b/src/main/services/WindowManager.ts @@ -72,6 +72,9 @@ export class WindowManager extends Service { ...input.options }) + const zoom = Number(this.mainCtx.settings.getValue('window_zoom')) || 1.0 + win.webContents.setZoomFactor(zoom) + this.windows.set(input.key, win) win.on('closed', () => { this.windows.delete(input.key) @@ -182,6 +185,13 @@ export class WindowManager extends Service { return win ? win.isMaximized() : false }) + this.mainCtx.handle('window:set-zoom', (event, zoom: number) => { + const win = BrowserWindow.fromWebContents(event.sender) + if (win && zoom >= 0.5 && zoom <= 2.0) { + win.webContents.setZoomFactor(zoom) + } + }) + this.mainCtx.handle('window:toggle-devtools', (event) => { const win = BrowserWindow.fromWebContents(event.sender) if (win) { diff --git a/src/renderer/src/components/ContentArea.tsx b/src/renderer/src/components/ContentArea.tsx index abca767..11da34a 100644 --- a/src/renderer/src/components/ContentArea.tsx +++ b/src/renderer/src/components/ContentArea.tsx @@ -86,7 +86,10 @@ export function ContentArea({ - } /> + } + /> } /> = ({ canEdit }) => { // 过滤和排序学生 const sortedStudents = useMemo(() => { - let filtered = students.filter((s) => matchStudentName(s.name, searchKeyword)) + const filtered = students.filter((s) => matchStudentName(s.name, searchKeyword)) switch (sortType) { case 'alphabet': @@ -394,7 +394,9 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { }} > {group.key} - + ({group.students.length} 人) @@ -416,24 +418,27 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { const navContainerRef = useRef(null) const isNavDragging = useRef(false) - const handleNavAction = useCallback((clientY: number) => { - if (!navContainerRef.current) return - const rect = navContainerRef.current.getBoundingClientRect() - const y = clientY - rect.top - const items = navContainerRef.current.children - const itemCount = items.length - if (itemCount === 0) return + const handleNavAction = useCallback( + (clientY: number) => { + if (!navContainerRef.current) return + const rect = navContainerRef.current.getBoundingClientRect() + const y = clientY - rect.top + const items = navContainerRef.current.children + const itemCount = items.length + if (itemCount === 0) return - // 计算当前指向第几个项 - const itemHeight = rect.height / itemCount - const index = Math.floor(y / itemHeight) - const safeIndex = Math.max(0, Math.min(itemCount - 1, index)) + // 计算当前指向第几个项 + const itemHeight = rect.height / itemCount + const index = Math.floor(y / itemHeight) + const safeIndex = Math.max(0, Math.min(itemCount - 1, index)) - const targetGroup = groupedStudents[safeIndex] - if (targetGroup) { - scrollToGroup(targetGroup.key) - } - }, [groupedStudents]) + const targetGroup = groupedStudents[safeIndex] + if (targetGroup) { + scrollToGroup(targetGroup.key) + } + }, + [groupedStudents] + ) const onNavMouseDown = (e: React.MouseEvent) => { isNavDragging.current = true @@ -476,7 +481,12 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { // 渲染快速导航 const renderQuickNav = () => { - if (groupedStudents.length <= 1 || sortType === 'score' || (sortType === 'alphabet' && searchKeyword)) return null + if ( + groupedStudents.length <= 1 || + sortType === 'score' || + (sortType === 'alphabet' && searchKeyword) + ) + return null return (
= ({ canEdit }) => { }} >
-

学生积分主页

+

+ 学生积分主页 +

共 {students.length} 名学生,点击卡片进行积分操作

@@ -595,7 +607,12 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { {searchKeyword ? '未找到匹配的学生' : '暂无学生数据,请前往学生管理添加'}
{searchKeyword && ( - )} @@ -649,9 +666,17 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { {selectedStudent.name}
- 当前积分: + + 当前积分: + 0 ? 'success' : selectedStudent.score < 0 ? 'danger' : 'default'} + theme={ + selectedStudent.score > 0 + ? 'success' + : selectedStudent.score < 0 + ? 'danger' + : 'default' + } variant="light" style={{ fontWeight: 'bold' }} > @@ -663,7 +688,14 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { {/* 快捷理由 */} {groupedReasons.length > 0 && (
-
+
快捷选项
@@ -697,14 +729,24 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { size="small" onClick={() => handleReasonSelect(r)} style={{ - borderColor: r.delta > 0 ? 'var(--td-success-color-3)' : r.delta < 0 ? 'var(--td-error-color-3)' : undefined + borderColor: + r.delta > 0 + ? 'var(--td-success-color-3)' + : r.delta < 0 + ? 'var(--td-error-color-3)' + : undefined }} > {r.content}{' '} 0 ? 'var(--td-success-color)' : r.delta < 0 ? 'var(--td-error-color)' : 'inherit', + color: + r.delta > 0 + ? 'var(--td-success-color)' + : r.delta < 0 + ? 'var(--td-error-color)' + : 'inherit', fontWeight: 'bold' }} > @@ -721,7 +763,9 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { {/* 自定义分值 */}
-
+
调整分值
@@ -765,7 +809,9 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { {/* 理由内容 */}
-
+
操作理由
@@ -773,7 +819,14 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { value={reasonContent} onChange={setReasonContent} placeholder="输入加分/扣分的原因(可选)" - suffixIcon={reasonContent ? setReasonContent('')} style={{ cursor: 'pointer' }} /> : undefined} + suffixIcon={ + reasonContent ? ( + setReasonContent('')} + style={{ cursor: 'pointer' }} + /> + ) : undefined + } />
@@ -782,18 +835,40 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
0 ? 'var(--td-success-color-1)' : customScore < 0 ? 'var(--td-error-color-1)' : 'var(--ss-bg-color)', + backgroundColor: + customScore > 0 + ? 'var(--td-success-color-1)' + : customScore < 0 + ? 'var(--td-error-color-1)' + : 'var(--ss-bg-color)', borderRadius: '8px', border: `1px solid ${customScore > 0 ? 'var(--td-success-color-2)' : customScore < 0 ? 'var(--td-error-color-2)' : 'var(--ss-border-color)'}`, marginTop: '4px' }} > -
+
变更预览:
{selectedStudent.name}{' '} - 0 ? 'var(--td-success-color)' : customScore < 0 ? 'var(--td-error-color)' : 'inherit' }}> + 0 + ? 'var(--td-success-color)' + : customScore < 0 + ? 'var(--td-error-color)' + : 'inherit' + }} + > {customScore > 0 ? `+${customScore}` : customScore} {' '} 分 diff --git a/src/renderer/src/components/Settings.tsx b/src/renderer/src/components/Settings.tsx index 0c15db9..a1ddd90 100644 --- a/src/renderer/src/components/Settings.tsx +++ b/src/renderer/src/components/Settings.tsx @@ -18,6 +18,7 @@ type permissionLevel = 'admin' | 'points' | 'view' type appSettings = { is_wizard_completed: boolean log_level: 'debug' | 'info' | 'warn' | 'error' + window_zoom?: string } export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission }) => { @@ -25,7 +26,8 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission const [activeTab, setActiveTab] = useState('appearance') const [settings, setSettings] = useState({ is_wizard_completed: false, - log_level: 'info' + log_level: 'info', + window_zoom: '1.0' }) const [securityStatus, setSecurityStatus] = useState<{ @@ -91,6 +93,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission if (change?.key === 'log_level') return { ...prev, log_level: change.value } if (change?.key === 'is_wizard_completed') return { ...prev, is_wizard_completed: change.value } + if (change?.key === 'window_zoom') return { ...prev, window_zoom: change.value } return prev }) }) @@ -268,6 +271,39 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission ))} + + + +
+ 调节应用界面的整体大小。 +
+
diff --git a/src/renderer/src/components/Sidebar.tsx b/src/renderer/src/components/Sidebar.tsx index 26aca87..9f30b8f 100644 --- a/src/renderer/src/components/Sidebar.tsx +++ b/src/renderer/src/components/Sidebar.tsx @@ -1,5 +1,12 @@ import { Layout, Menu } from 'tdesign-react' -import { UserIcon, SettingIcon, HistoryIcon, RootListIcon, ViewListIcon, HomeIcon } from 'tdesign-icons-react' +import { + UserIcon, + SettingIcon, + HistoryIcon, + RootListIcon, + ViewListIcon, + HomeIcon +} from 'tdesign-icons-react' import appLogo from '../assets/logo.svg' const { Aside } = Layout