From a7f8ce36dff6b653f49d34e96a6fd4c2ce06ba3b Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 29 May 2026 14:04:21 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(font):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=AD=97=E4=BD=93=E6=9E=9A=E4=B8=BE=E4=B8=8E?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E5=AD=97=E4=BD=93=E9=85=8D=E7=BD=AE=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Go 侧已安装字体扫描接口,支持前端读取系统真实字体列表 - 接入 Wails 字体查询导出,补齐 App.d.ts 与 App.js 调用声明 - 新增字体选项构建与匹配工具,区分 UI 字体与等宽字体候选 - 外观设置支持按平台加载字体列表,并支持搜索匹配与默认字体回退 - Store 增加自定义 UI 字体与代码字体配置,持久化全局字体选择 --- frontend/src/App.css | 20 ++ frontend/src/App.tool-center.test.ts | 8 + frontend/src/App.tsx | 110 +++++++++- frontend/src/main.tsx | 2 +- frontend/src/store.test.ts | 22 ++ frontend/src/store.ts | 40 +++- frontend/src/utils/fontFamilies.ts | 316 +++++++++++++++++++++++++++ frontend/wailsjs/go/app/App.d.ts | 2 + frontend/wailsjs/go/app/App.js | 4 + go.mod | 1 + internal/app/methods_fonts.go | 172 +++++++++++++++ 11 files changed, 685 insertions(+), 12 deletions(-) create mode 100644 frontend/src/utils/fontFamilies.ts create mode 100644 internal/app/methods_fonts.go diff --git a/frontend/src/App.css b/frontend/src/App.css index 9896bde..a7c6c19 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,3 +1,8 @@ +:root { + --gn-font-sans: "Inter", "PingFang SC", -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", sans-serif; + --gn-font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace; +} + html, body, #root { height: 100%; margin: 0; @@ -10,6 +15,21 @@ body, #root { border-radius: var(--gonavi-border-radius); /* Slightly rounded app window corners */ } +body, +button, +input, +textarea, +select { + font-family: var(--gn-font-sans); +} + +code, +pre, +kbd, +samp { + font-family: var(--gn-font-mono); +} + /* 侧边栏 Tree 样式优化 */ .ant-tree .ant-tree-treenode { width: 100%; diff --git a/frontend/src/App.tool-center.test.ts b/frontend/src/App.tool-center.test.ts index b52865b..4bfe799 100644 --- a/frontend/src/App.tool-center.test.ts +++ b/frontend/src/App.tool-center.test.ts @@ -179,8 +179,16 @@ describe('global appearance tokens', () => { expect(appSource).toContain("setProperty('--gn-sidebar-tree-font-size'"); expect(appSource).toContain("setProperty('--gn-control-height'"); expect(appSource).toContain("setProperty('--gn-control-height-sm'"); + expect(appSource).toContain('fontFamily: resolvedUiFontFamily'); + expect(appSource).toContain('fontFamilyCode: resolvedMonoFontFamily'); expect(appSource).toContain('数据表字体大小'); expect(appSource).toContain('左侧库表字体大小'); + expect(appSource).toContain('buildFontFamilyOptions(runtimePlatform, \'ui\', installedFontFamilies)'); + expect(appSource).toContain('buildFontFamilyOptions(runtimePlatform, \'mono\', installedFontFamilies)'); + expect(appSource).toContain('ListInstalledFontFamilies()'); + expect(appSource).toContain('const [installedFontFamilies, setInstalledFontFamilies] = useState(EMPTY_INSTALLED_FONT_FAMILIES);'); + expect(appSource).toContain('matchFontFamilyOption'); + expect(appSource).toContain('showSearch'); expect(appSource).toContain('const dataTableFontSizeFollowsGlobal = appearance.dataTableFontSizeFollowGlobal !== false;'); expect(appSource).toContain('const sidebarTreeFontSizeFollowsGlobal = appearance.sidebarTreeFontSizeFollowGlobal !== false;'); expect(appSource).toContain('disabled={dataTableFontSizeFollowsGlobal}'); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e1cfa6d..745f315 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -19,6 +19,7 @@ import SecurityUpdateSettingsModal from './components/SecurityUpdateSettingsModa import { DEFAULT_APPEARANCE, useStore } from './store'; import { SavedConnection, SecurityUpdateIssue, SecurityUpdateStatus } from './types'; import { blurToFilter, isMacLikePlatform, normalizeBlurForPlatform, normalizeOpacityForPlatform, isWindowsPlatform, resolveAppearanceValues } from './utils/appearance'; +import { buildFontFamilyOptions, DEFAULT_MONO_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY, matchFontFamilyOption, resolveMonoFontFamily, resolveUIFontFamily, sanitizeFontFamilyInput, type FontFamilyOption, type InstalledFontFamily } from './utils/fontFamilies'; import { DENSITY_OPTIONS, sanitizeDataTableDensity, @@ -91,7 +92,7 @@ import { } from './utils/aiEntryLayout'; import { DEFAULT_AI_PANEL_WIDTH, resolveOverlayAIPanelWidth, shouldOverlayAIPanel } from './utils/aiPanelLayout'; import { safeWindowRuntimeCall } from './utils/wailsRuntime'; -import { ApplyDataRootDirectory, GetDataRootDirectoryInfo, GetSavedConnections, OpenDataRootDirectory, SelectDataRootDirectory, SetMacNativeWindowControls, SetWindowTranslucency } from '../wailsjs/go/app/App'; +import { ApplyDataRootDirectory, GetDataRootDirectoryInfo, GetSavedConnections, ListInstalledFontFamilies, OpenDataRootDirectory, SelectDataRootDirectory, SetMacNativeWindowControls, SetWindowTranslucency } from '../wailsjs/go/app/App'; import './App.css'; import './v2-theme.css'; @@ -270,6 +271,8 @@ function App() { const effectiveSidebarTreeFontSize = sidebarTreeFontSizeFollowsGlobal ? effectiveFontSize : (sanitizeSidebarTreeFontSize(appearance.sidebarTreeFontSize) ?? effectiveFontSize); + const resolvedUiFontFamily = resolveUIFontFamily(appearance.customUIFontFamily); + const resolvedMonoFontFamily = resolveMonoFontFamily(appearance.customMonoFontFamily); const appComponentSize: 'small' | 'middle' | 'large' = effectiveUiScale <= 0.92 ? 'small' : (effectiveUiScale >= 1.12 ? 'large' : 'middle'); const titleBarHeight = Math.max(28, Math.round(32 * effectiveUiScale)); const titleBarButtonWidth = Math.max(40, Math.round(46 * effectiveUiScale)); @@ -281,6 +284,18 @@ function App() { const [runtimePlatform, setRuntimePlatform] = useState(''); const [runtimeBuildType, setRuntimeBuildType] = useState(''); const [isLinuxRuntime, setIsLinuxRuntime] = useState(false); + const [installedFontFamilies, setInstalledFontFamilies] = useState(EMPTY_INSTALLED_FONT_FAMILIES); + const [isFontFamiliesLoading, setIsFontFamiliesLoading] = useState(false); + const [fontFamiliesLoadError, setFontFamiliesLoadError] = useState(null); + const hasLoadedInstalledFontsRef = useRef(false); + const uiFontOptions = useMemo( + () => buildFontFamilyOptions(runtimePlatform, 'ui', installedFontFamilies), + [installedFontFamilies, runtimePlatform], + ); + const monoFontOptions = useMemo( + () => buildFontFamilyOptions(runtimePlatform, 'mono', installedFontFamilies), + [installedFontFamilies, runtimePlatform], + ); const [isStoreHydrated, setIsStoreHydrated] = useState(() => useStore.persist.hasHydrated()); const [hasLoadedSecureConfig, setHasLoadedSecureConfig] = useState(false); const [viewportWidth, setViewportWidth] = useState(() => (typeof window === 'undefined' ? 1280 : window.innerWidth || 1280)); @@ -2644,6 +2659,9 @@ function App() { darkMode, effectiveDataTableFontSize, effectiveFontSize, + resolvedMonoFontFamily, + resolvedUiFontFamily, + runtimePlatform, effectiveSidebarTreeFontSize, effectiveUiScale, tokenControlHeight, @@ -2923,6 +2941,8 @@ function App() { fontSize: tokenFontSize, fontSizeSM: tokenFontSizeSM, fontSizeLG: tokenFontSizeLG, + fontFamily: resolvedUiFontFamily, + fontFamilyCode: resolvedMonoFontFamily, controlHeight: tokenControlHeight, controlHeightSM: tokenControlHeightSM, controlHeightLG: tokenControlHeightLG, @@ -2973,13 +2993,30 @@ function App() { }), [ darkMode, effectiveOpacity, + isV2Ui, tokenControlHeight, tokenControlHeightLG, tokenControlHeightSM, tokenFontSize, tokenFontSizeLG, tokenFontSizeSM, + resolvedMonoFontFamily, + resolvedUiFontFamily, ]); + const filterFontOption = useCallback((input: string, option?: { value?: string; label?: React.ReactNode }) => ( + matchFontFamilyOption(input, { + value: String(option?.value || ''), + label: String(option?.label || ''), + }) + ), []); + const renderFontOptionLabel = useCallback((option: FontFamilyOption) => ( +
+ {option.label} + + {option.value} + +
+ ), [darkMode]); return ( {effectiveFontSize}px +
+
字体族
+
+
+
界面字体 (UI Font Family)
+ setAppearance({ + customMonoFontFamily: sanitizeFontFamilyInput(value), + })} + onClear={() => setAppearance({ customMonoFontFamily: null })} + options={monoFontOptions.map((option) => ({ + value: option.value, + label: option.label, + }))} + filterOption={filterFontOption} + popupMatchSelectWidth + style={{ width: '100%' }} + optionRender={(option) => renderFontOptionLabel({ + value: String(option.data.value), + label: String(option.data.label), + })} + /> +
+ {fontFamiliesLoadError + ? '当前已回退常见代码字体预置。作用于 SQL 编辑器、AI 代码块、日志、DDL 与数据表等宽内容。' + : '优先展示当前系统已安装字体,名称接近 Mono/Code/Console 的字体会靠前。作用于 SQL 编辑器、AI 代码块、日志、DDL 与数据表等宽内容。'} +
+
+
+
透明与模糊效果
@@ -4277,7 +4383,7 @@ function App() {