diff --git a/.github/workflows/sync-main-to-dev.yml b/.github/workflows/sync-main-to-dev.yml
deleted file mode 100644
index 18f047a..0000000
--- a/.github/workflows/sync-main-to-dev.yml
+++ /dev/null
@@ -1,180 +0,0 @@
-name: main 回灌 dev
-
-on:
- push:
- branches:
- - main
- workflow_dispatch:
-
-permissions:
- contents: write
- pull-requests: write
-
-concurrency:
- group: sync-main-to-dev
- cancel-in-progress: true
-
-jobs:
- sync-main-to-dev:
- name: 执行回灌同步
- runs-on: ubuntu-latest
- steps:
- - name: 检出代码
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: 检查是否需要同步
- id: diff_check
- shell: bash
- run: |
- set -euo pipefail
- echo "开始检查 main 与 dev 的分支差异..."
- git fetch origin main dev
- ahead_count="$(git rev-list --count origin/dev..origin/main)"
- echo "ahead_count=${ahead_count}" >> "$GITHUB_OUTPUT"
- if [ "${ahead_count}" -eq 0 ]; then
- echo "无需同步,dev 已包含 main 的最新提交。"
- echo "has_changes=false" >> "$GITHUB_OUTPUT"
- else
- echo "检测到 ${ahead_count} 个待同步提交,准备创建或复用同步 PR。"
- echo "has_changes=true" >> "$GITHUB_OUTPUT"
- fi
-
- - name: 创建或复用同步 PR
- id: sync_pr
- if: steps.diff_check.outputs.has_changes == 'true'
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- shell: bash
- run: |
- set -euo pipefail
- echo "permission_blocked=false" >> "$GITHUB_OUTPUT"
- existing_number="$(gh pr list --base dev --head main --state open --json number --jq '.[0].number // empty')"
-
- if [ -n "${existing_number}" ]; then
- pr_number="${existing_number}"
- pr_url="$(gh pr view "${pr_number}" --json url --jq '.url')"
- echo "复用已有同步 PR:#${pr_number}"
- echo "created=false" >> "$GITHUB_OUTPUT"
- else
- body_file="$(mktemp)"
- error_file="$(mktemp)"
- {
- echo "## 自动回灌:\`main -> dev\`"
- echo
- echo "- 触发条件:\`main\` 分支出现新提交(含贡献者直接合并到 \`main\` 的 PR)"
- echo "- 目标:让 \`dev\` 持续吸收 \`main\` 的更新,避免发布前集中冲突"
- echo
- echo "### 合并建议"
- echo "- 无冲突:直接合并该 PR(建议 \`Merge commit\`)"
- echo "- 有冲突:在该 PR 内解决冲突后再合并"
- } > "${body_file}"
-
- if pr_url="$(gh pr create \
- --base dev \
- --head main \
- --title "🔁 chore(sync): 回灌 main 到 dev" \
- --body-file "${body_file}" 2>"${error_file}")"; then
- pr_number="${pr_url##*/}"
- echo "已创建同步 PR:#${pr_number}"
- echo "created=true" >> "$GITHUB_OUTPUT"
- else
- error_message="$(tr '\n' ' ' < "${error_file}")"
- if printf '%s' "${error_message}" | grep -Fq "GitHub Actions is not permitted to create or approve pull requests"; then
- echo "::warning::仓库未开启“Allow GitHub Actions to create and approve pull requests”,已跳过自动创建同步 PR。"
- echo "permission_blocked=true" >> "$GITHUB_OUTPUT"
- echo "created=false" >> "$GITHUB_OUTPUT"
- echo "pr_number=" >> "$GITHUB_OUTPUT"
- echo "pr_url=" >> "$GITHUB_OUTPUT"
- exit 0
- fi
- echo "::error::创建同步 PR 失败:${error_message}"
- exit 1
- fi
- fi
-
- echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT"
- echo "pr_url=${pr_url}" >> "$GITHUB_OUTPUT"
-
- - name: 检查合并状态
- id: merge_state
- if: steps.diff_check.outputs.has_changes == 'true' && steps.sync_pr.outputs.permission_blocked != 'true'
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- shell: bash
- run: |
- set -euo pipefail
- pr_number="${{ steps.sync_pr.outputs.pr_number }}"
- mergeable="UNKNOWN"
- merge_state_status="UNKNOWN"
-
- for attempt in 1 2 3 4 5 6; do
- mergeable="$(gh pr view "${pr_number}" --json mergeable --jq '.mergeable')"
- merge_state_status="$(gh pr view "${pr_number}" --json mergeStateStatus --jq '.mergeStateStatus')"
- echo "第 ${attempt} 次检查 PR #${pr_number} 合并状态:mergeable=${mergeable}, mergeStateStatus=${merge_state_status}"
- if [ "${mergeable}" != "UNKNOWN" ]; then
- break
- fi
- if [ "${attempt}" -lt 6 ]; then
- echo "GitHub 仍在计算可合并状态,3 秒后重试..."
- sleep 3
- fi
- done
-
- if [ "${mergeable}" = "UNKNOWN" ]; then
- echo "::warning::PR 合并状态仍在计算中,本次未开启自动合并,可稍后重跑 workflow 或手动开启。"
- echo "merge_state_pending=true" >> "$GITHUB_OUTPUT"
- else
- echo "merge_state_pending=false" >> "$GITHUB_OUTPUT"
- fi
- echo "mergeable=${mergeable}" >> "$GITHUB_OUTPUT"
- echo "merge_state_status=${merge_state_status}" >> "$GITHUB_OUTPUT"
-
- - name: 可合并时开启自动合并
- id: auto_merge
- if: steps.diff_check.outputs.has_changes == 'true' && steps.sync_pr.outputs.permission_blocked != 'true' && steps.merge_state.outputs.mergeable == 'MERGEABLE'
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- shell: bash
- run: |
- set -euo pipefail
- pr_number="${{ steps.sync_pr.outputs.pr_number }}"
- if gh pr merge "${pr_number}" --merge --auto; then
- echo "已为 PR #${pr_number} 开启自动合并。"
- echo "result=enabled" >> "$GITHUB_OUTPUT"
- else
- echo "::warning::自动合并开启失败,请手动处理并合并该 PR。"
- echo "result=failed" >> "$GITHUB_OUTPUT"
- fi
-
- - name: 写入执行摘要
- if: always()
- shell: bash
- run: |
- {
- echo "## main 回灌 dev 执行结果"
- if [ "${{ steps.diff_check.outputs.has_changes }}" != "true" ]; then
- echo "- 状态:无需同步(dev 已包含 main 最新提交)"
- exit 0
- fi
- if [ "${{ steps.sync_pr.outputs.permission_blocked }}" = "true" ]; then
- echo "- 状态:已跳过自动创建同步 PR"
- echo "- 原因:仓库未开启 GitHub Actions 创建与审批 Pull Request 权限"
- echo "- 处理:前往 Settings -> Actions -> General -> Workflow permissions,开启 Allow GitHub Actions to create and approve pull requests"
- echo "- 兜底:由维护者手动执行 main 到 dev 合并,或开启该设置后重新运行 workflow"
- exit 0
- fi
- echo "- PR:${{ steps.sync_pr.outputs.pr_url }}"
- echo "- 可合并状态:${{ steps.merge_state.outputs.mergeable }}"
- echo "- 合并状态详情:${{ steps.merge_state.outputs.merge_state_status }}"
- if [ "${{ steps.merge_state.outputs.mergeable }}" = "CONFLICTING" ]; then
- echo "- 结论:检测到冲突,需要手动处理后合并"
- elif [ "${{ steps.merge_state.outputs.merge_state_pending }}" = "true" ]; then
- echo "- 结论:GitHub 仍在计算合并状态,本次未开启自动合并;可稍后重跑 workflow 或手动开启 auto-merge"
- elif [ "${{ steps.auto_merge.outputs.result }}" = "enabled" ]; then
- echo "- 结论:已启用自动合并(满足保护规则后将自动入 dev)"
- else
- echo "- 结论:PR 已创建/复用,请按分支策略人工合并"
- fi
- } >> "$GITHUB_STEP_SUMMARY"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b89e554..162357f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -79,14 +79,8 @@ Because external pull requests are merged directly into `main`, maintainers must
### 1. Sync `main` -> `dev` (required)
-This repository provides automatic sync via GitHub Actions workflow:
-
-- `.github/workflows/sync-main-to-dev.yml`
-- Trigger: every push to `main`
-- Behavior: create/reuse a PR from `main` to `dev`; if mergeable, it tries to enable auto-merge
-- Prerequisite: in `Settings -> Actions -> General -> Workflow permissions`, enable `Allow GitHub Actions to create and approve pull requests`; otherwise the workflow will skip PR creation and only emit a warning summary
-
-Manual fallback (when conflicts or automation is unavailable):
+The automatic GitHub Actions sync workflow has been removed.
+Maintainers should sync `main` back to `dev` manually when needed:
```bash
git checkout dev
diff --git a/CONTRIBUTING.zh-CN.md b/CONTRIBUTING.zh-CN.md
index 3e79997..a2d3983 100644
--- a/CONTRIBUTING.zh-CN.md
+++ b/CONTRIBUTING.zh-CN.md
@@ -79,14 +79,8 @@ feature/* / fix/* -> dev -> release/* -> main -> tag(vX.Y.Z)
### 1. main → dev 同步(必做)
-仓库已提供 GitHub Actions 自动同步机制:
-
-- `.github/workflows/sync-main-to-dev.yml`
-- 触发时机:每次 `main` 分支有新的 push
-- 行为:自动创建或复用 `main` 到 `dev` 的同步 PR;若可合并,则尝试开启自动合并
-- 前置条件:需在 `Settings -> Actions -> General -> Workflow permissions` 中开启 `Allow GitHub Actions to create and approve pull requests`,否则 workflow 只会输出告警摘要并跳过建 PR
-
-当出现冲突,或自动化暂不可用时,使用以下手动兜底方式:
+仓库已移除 GitHub Actions 自动回灌 workflow。
+当前统一采用手动方式将 `main` 同步回 `dev`:
```bash
git checkout dev
diff --git a/frontend/package.json.md5 b/frontend/package.json.md5
index a7661c0..0f8f4fe 100755
--- a/frontend/package.json.md5
+++ b/frontend/package.json.md5
@@ -1 +1 @@
-d0f9366af59a6367ad3c7e2d4185ead4
\ No newline at end of file
+5b8157374dae5f9340e31b2d0bd2c00e
\ No newline at end of file
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index be49c41..3aa2f01 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,7 +1,7 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import { Layout, Button, ConfigProvider, theme, Dropdown, MenuProps, message, Modal, Spin, Slider, Progress, Switch, Input, InputNumber, Select } from 'antd';
import zhCN from 'antd/locale/zh_CN';
-import { PlusOutlined, ConsoleSqlOutlined, UploadOutlined, DownloadOutlined, CloudDownloadOutlined, BugOutlined, ToolOutlined, GlobalOutlined, InfoCircleOutlined, GithubOutlined, SkinOutlined, CheckOutlined, MinusOutlined, BorderOutlined, CloseOutlined, SettingOutlined, LinkOutlined } from '@ant-design/icons';
+import { PlusOutlined, ConsoleSqlOutlined, UploadOutlined, DownloadOutlined, CloudDownloadOutlined, BugOutlined, ToolOutlined, GlobalOutlined, InfoCircleOutlined, GithubOutlined, SkinOutlined, CheckOutlined, MinusOutlined, BorderOutlined, CloseOutlined, SettingOutlined, LinkOutlined, BgColorsOutlined, AppstoreOutlined } from '@ant-design/icons';
import { BrowserOpenURL, Environment, EventsOn, Quit, WindowFullscreen, WindowGetSize, WindowIsFullscreen, WindowIsMaximised, WindowMaximise, WindowMinimise, WindowSetSize, WindowToggleMaximise } from '../wailsjs/runtime';
import Sidebar from './components/Sidebar';
import TabManager from './components/TabManager';
@@ -11,7 +11,7 @@ import DriverManagerModal from './components/DriverManagerModal';
import LogPanel from './components/LogPanel';
import { useStore } from './store';
import { SavedConnection } from './types';
-import { blurToFilter, normalizeBlurForPlatform, normalizeOpacityForPlatform, isWindowsPlatform } from './utils/appearance';
+import { blurToFilter, normalizeBlurForPlatform, normalizeOpacityForPlatform, isWindowsPlatform, resolveAppearanceValues } from './utils/appearance';
import {
SHORTCUT_ACTION_META,
SHORTCUT_ACTION_ORDER,
@@ -78,11 +78,11 @@ function App() {
const tokenControlHeightLG = Math.max(30, Math.round(40 * effectiveUiScale));
const appComponentSize: 'small' | 'middle' | 'large' = effectiveUiScale <= 0.92 ? 'small' : (effectiveUiScale >= 1.12 ? 'large' : 'middle');
const titleBarHeight = Math.max(28, Math.round(32 * effectiveUiScale));
- const toolbarHeight = Math.max(32, Math.round(36 * effectiveUiScale));
const titleBarButtonWidth = Math.max(40, Math.round(46 * effectiveUiScale));
const floatingLogButtonHeight = Math.max(30, Math.round(34 * effectiveUiScale));
- const effectiveOpacity = normalizeOpacityForPlatform(appearance.opacity);
- const effectiveBlur = normalizeBlurForPlatform(appearance.blur);
+ const resolvedAppearance = resolveAppearanceValues(appearance);
+ const effectiveOpacity = normalizeOpacityForPlatform(resolvedAppearance.opacity);
+ const effectiveBlur = normalizeBlurForPlatform(resolvedAppearance.blur);
const blurFilter = blurToFilter(effectiveBlur);
const windowCornerRadius = 14;
const [runtimePlatform, setRuntimePlatform] = useState('');
@@ -93,8 +93,8 @@ function App() {
// 同步 macOS 窗口透明度:opacity=1.0 且 blur=0 时关闭 NSVisualEffectView,
// 避免 GPU 持续计算窗口背后的模糊合成
useEffect(() => {
- void SetWindowTranslucency(appearance.opacity, appearance.blur).catch(() => undefined);
- }, [appearance.opacity, appearance.blur]);
+ void SetWindowTranslucency(resolvedAppearance.opacity, resolvedAppearance.blur).catch(() => undefined);
+ }, [resolvedAppearance.blur, resolvedAppearance.opacity]);
useEffect(() => {
let cancelled = false;
@@ -370,6 +370,141 @@ function App() {
const floatingLogButtonShadow = darkMode
? '0 8px 22px rgba(0,0,0,0.38)'
: '0 8px 20px rgba(0,0,0,0.16)';
+
+ const isOpaqueUtilityMode = resolvedAppearance.opacity >= 0.999 && resolvedAppearance.blur <= 0;
+ const utilityButtonBgAlpha = darkMode
+ ? Math.max(0.28, Math.min(0.76, effectiveOpacity * 0.72))
+ : Math.max(0.52, Math.min(0.92, effectiveOpacity * 0.9));
+ const utilityButtonBgColor = isOpaqueUtilityMode
+ ? 'transparent'
+ : (darkMode
+ ? `rgba(20, 26, 38, ${utilityButtonBgAlpha})`
+ : `rgba(255, 255, 255, ${utilityButtonBgAlpha})`);
+ const utilityButtonBorderColor = isOpaqueUtilityMode
+ ? (darkMode ? 'rgba(255,255,255,0.12)' : 'rgba(16,24,40,0.10)')
+ : (darkMode
+ ? `rgba(255,255,255,${Math.max(0.08, Math.min(0.18, effectiveOpacity * 0.16))})`
+ : `rgba(16,24,40,${Math.max(0.06, Math.min(0.14, effectiveOpacity * 0.12))})`);
+ const utilityButtonShadow = isOpaqueUtilityMode
+ ? 'none'
+ : (darkMode
+ ? `0 8px 18px rgba(0,0,0,${Math.max(0.10, Math.min(0.22, effectiveOpacity * 0.24))})`
+ : `0 8px 18px rgba(15,23,42,${Math.max(0.04, Math.min(0.12, effectiveOpacity * 0.12))})`);
+ const utilityButtonStyle = useMemo(() => ({
+ height: Math.max(30, Math.round(32 * effectiveUiScale)),
+ width: '100%',
+ paddingInline: Math.max(10, Math.round(12 * effectiveUiScale)),
+ borderRadius: 10,
+ border: `1px solid ${utilityButtonBorderColor}`,
+ background: utilityButtonBgColor,
+ color: darkMode ? 'rgba(255,255,255,0.94)' : '#162033',
+ boxShadow: utilityButtonShadow,
+ backdropFilter: isOpaqueUtilityMode ? 'none' : blurFilter,
+ WebkitBackdropFilter: isOpaqueUtilityMode ? 'none' : blurFilter,
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 6,
+ }), [blurFilter, darkMode, effectiveUiScale, isOpaqueUtilityMode, utilityButtonBgColor, utilityButtonBorderColor, utilityButtonShadow]);
+ const utilityDropdownShellStyle = useMemo(() => ({
+ borderRadius: 14,
+ padding: 6,
+ background: darkMode ? 'linear-gradient(180deg, rgba(20,26,38,0.96) 0%, rgba(13,17,26,0.98) 100%)' : 'linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(246,248,252,0.98) 100%)',
+ border: darkMode ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(16,24,40,0.08)',
+ boxShadow: darkMode ? '0 20px 48px rgba(0,0,0,0.32)' : '0 16px 36px rgba(15,23,42,0.12)',
+ backdropFilter: darkMode ? 'blur(16px)' : 'none',
+ overflow: 'hidden',
+ }), [darkMode]);
+
+ const sidebarQuickActionBaseStyle = useMemo(() => ({
+ height: Math.max(34, Math.round(36 * effectiveUiScale)),
+ borderRadius: 12,
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 8,
+ paddingInline: Math.max(12, Math.round(14 * effectiveUiScale)),
+ fontWeight: 700,
+ boxShadow: darkMode ? '0 8px 18px rgba(0,0,0,0.16)' : '0 8px 16px rgba(15,23,42,0.08)',
+ backdropFilter: blurFilter,
+ WebkitBackdropFilter: blurFilter,
+ minWidth: 0,
+ whiteSpace: 'nowrap',
+ }), [blurFilter, darkMode, effectiveUiScale]);
+ const sidebarQueryActionStyle = useMemo(() => ({
+ ...sidebarQuickActionBaseStyle,
+ flex: '1 1 0',
+ border: `1px solid ${darkMode ? 'rgba(255,255,255,0.12)' : 'rgba(16,24,40,0.10)'}`,
+ background: darkMode ? `rgba(255,255,255,0.05)` : 'rgba(255,255,255,0.88)',
+ color: darkMode ? 'rgba(255,255,255,0.92)' : '#162033',
+ }), [darkMode, sidebarQuickActionBaseStyle]);
+ const sidebarCreateConnectionActionStyle = useMemo(() => ({
+ ...sidebarQuickActionBaseStyle,
+ flex: '1 1 0',
+ border: 'none',
+ background: 'linear-gradient(135deg, rgba(255,214,102,0.96) 0%, rgba(240,183,39,0.92) 100%)',
+ color: '#2a1f00',
+ }), [sidebarQuickActionBaseStyle]);
+
+ const utilityMenuTheme = useMemo(() => ({
+ components: {
+ Menu: {
+ popupBg: 'transparent',
+ darkPopupBg: 'transparent',
+ itemBg: 'transparent',
+ darkItemBg: 'transparent',
+ subMenuItemBg: 'transparent',
+ itemColor: darkMode ? 'rgba(255,255,255,0.88)' : '#162033',
+ itemHoverColor: darkMode ? '#fff7d6' : '#0f172a',
+ itemHoverBg: darkMode ? 'rgba(255,214,102,0.10)' : 'rgba(24,144,255,0.08)',
+ itemSelectedColor: darkMode ? '#ffd666' : '#1677ff',
+ itemSelectedBg: darkMode ? 'rgba(255,214,102,0.14)' : 'rgba(24,144,255,0.12)',
+ itemBorderRadius: 10,
+ itemMarginBlock: 4,
+ itemMarginInline: 0,
+ itemPaddingInline: 12,
+ itemHeight: 40,
+ groupTitleColor: darkMode ? 'rgba(255,255,255,0.48)' : 'rgba(16,24,40,0.48)',
+ },
+ },
+ }), [darkMode]);
+ const renderUtilityDropdown = (menu: React.ReactNode) => (
+
+
+ {menu}
+
+
+ );
+ const utilityModalShellStyle = useMemo(() => ({
+ background: darkMode ? 'linear-gradient(180deg, rgba(20,26,38,0.96) 0%, rgba(13,17,26,0.98) 100%)' : 'linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(246,248,252,0.98) 100%)',
+ border: darkMode ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(16,24,40,0.08)',
+ boxShadow: darkMode ? '0 24px 56px rgba(0,0,0,0.32)' : '0 18px 42px rgba(15,23,42,0.12)',
+ backdropFilter: darkMode ? 'blur(18px)' : 'none',
+ }), [darkMode]);
+ const utilityPanelStyle = useMemo(() => ({
+ padding: 16,
+ borderRadius: 14,
+ border: darkMode ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(16,24,40,0.08)',
+ background: darkMode ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.84)',
+ }), [darkMode]);
+ const utilityMutedTextStyle = useMemo(() => ({
+ color: darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(16,24,40,0.55)',
+ fontSize: 12,
+ lineHeight: 1.6,
+ }), [darkMode]);
+ const renderUtilityModalTitle = (icon: React.ReactNode, title: string, description: string) => (
+
+
+ {icon}
+
+
+
{title}
+
{description}
+
+
+ );
+
+ const sidebarHorizontalPadding = 10;
const addTab = useStore(state => state.addTab);
const activeContext = useStore(state => state.activeContext);
@@ -786,37 +921,18 @@ function App() {
label: '驱动管理',
icon: ,
onClick: () => setIsDriverModalOpen(true)
- }
- ];
-
- const themeMenu: MenuProps['items'] = [
- {
- key: 'light',
- label: '亮色主题',
- icon: themeMode === 'light' ? : undefined,
- onClick: () => setTheme('light')
- },
- {
- key: 'dark',
- label: '暗色主题',
- icon: themeMode === 'dark' ? : undefined,
- onClick: () => setTheme('dark')
},
{ type: 'divider' },
- {
- key: 'settings',
- label: '外观设置...',
- icon: ,
- onClick: () => setIsAppearanceModalOpen(true)
- },
{
key: 'shortcut-settings',
- label: '快捷键管理...',
+ label: '快捷键管理',
icon: ,
onClick: () => setIsShortcutModalOpen(true)
}
];
+ const [isThemeModalOpen, setIsThemeModalOpen] = useState(false);
+ const [themeModalSection, setThemeModalSection] = useState<'theme' | 'appearance'>('theme');
const [isAppearanceModalOpen, setIsAppearanceModalOpen] = useState(false);
const [isShortcutModalOpen, setIsShortcutModalOpen] = useState(false);
const [capturingShortcutAction, setCapturingShortcutAction] = useState(null);
@@ -1190,8 +1306,6 @@ function App() {
},
components: {
Layout: {
- colorBgBody: 'transparent',
- colorBgHeader: 'transparent',
bodyBg: 'transparent',
headerBg: 'transparent',
siderBg: 'transparent',
@@ -1272,28 +1386,6 @@ function App() {
-
-
- } title="工具">工具
-
- } title="代理" onClick={() => setIsProxyModalOpen(true)}>代理
-
- } title="主题">主题
-
- } title="关于" onClick={() => setIsAboutOpen(true)}>关于
-
-
-
-
-
} onClick={handleNewQuery} title="新建查询" />
-
} onClick={() => setIsModalOpen(true)} title="新建连接" />
+
+
+
+ } title="工具" style={utilityButtonStyle}>工具
+
+ } title="代理" style={utilityButtonStyle} onClick={() => setIsProxyModalOpen(true)}>代理
+ } title="主题" style={utilityButtonStyle} onClick={() => setIsThemeModalOpen(true)}>主题
+ } title="关于" style={utilityButtonStyle} onClick={() => setIsAboutOpen(true)}>关于
+
+
+
+
+ } onClick={handleNewQuery} title="新建查询" style={sidebarQueryActionStyle}>
+ 新建查询
+
+ } onClick={() => setIsModalOpen(true)} title="新建连接" style={sidebarCreateConnectionActionStyle}>
+ 新建连接
+
+
-
@@ -1370,8 +1475,8 @@ function App() {
title="拖动调整宽度"
/>
-
-
+
+
{isLogPanelOpen && (
@@ -1399,9 +1504,10 @@ function App() {
onOpenGlobalProxySettings={() => setIsProxyModalOpen(true)}
/>
, '关于 GoNavi', '查看版本信息、仓库地址、更新状态与下载入口。')}
open={isAboutOpen}
onCancel={() => setIsAboutOpen(false)}
+ styles={{ content: utilityModalShellStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 8 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 10 } }}
footer={[
canShowProgressEntry ? (
} onClick={showUpdateDownloadProgress}>下载进度
@@ -1421,150 +1527,274 @@ function App() {
) : (
-
-
版本:{aboutInfo?.version || '未知'}
-
作者:{aboutInfo?.author || '未知'}
- {aboutInfo?.communityUrl ? (
-
- ) : null}
-
更新状态:{aboutUpdateStatus || '未检查'}
-
)}
setIsAppearanceModalOpen(false)}
+ title={renderUtilityModalTitle(
+ themeModalSection === 'theme' ? : ,
+ themeModalSection === 'theme' ? '主题设置' : '外观设置',
+ themeModalSection === 'theme'
+ ? '切换亮暗主题,保持整体视觉风格统一。'
+ : '统一调整缩放、字体、透明度与模糊效果。'
+ )}
+ open={isThemeModalOpen}
+ onCancel={() => { setIsThemeModalOpen(false); setThemeModalSection('theme'); }}
footer={null}
- width={460}
+ width={820}
+ styles={{ content: utilityModalShellStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 8 }, body: { paddingTop: 8, height: 620, overflow: 'hidden' }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 10 } }}
>
-
-
-
界面缩放 (UI Scale)
-
- setUiScale(Number(v))}
- style={{ flex: 1 }}
- />
- {Math.round(effectiveUiScale * 100)}%
-
-
- * 建议小屏设备设置为 85%-95%
+
+
+
设置导航
+
+ {[
+ { key: 'theme', title: '主题模式', description: '亮色与暗色切换', icon:
},
+ { key: 'appearance', title: '外观参数', description: '缩放、字体与透明度', icon:
},
+ ].map((item) => {
+ const active = themeModalSection === item.key;
+ return (
+
+ );
+ })}
-
-
基础字体大小 (Font Size)
-
- setFontSize(Number(v))}
- style={{ flex: 1 }}
- />
- {effectiveFontSize}px
-
-
-
-
背景不透明度 (Opacity)
-
- setAppearance({ opacity: v })}
- style={{ flex: 1 }}
- />
- {Math.round((appearance.opacity ?? 1.0) * 100)}%
-
-
-
-
高斯模糊 (Blur)
- {isWindowsPlatform() ? (
-
- Windows 使用系统 Acrylic 效果,模糊程度由系统控制
+
+ {themeModalSection === 'theme' ? (
+
+
+
主题模式
+
+ {[
+ { key: 'light', label: '亮色主题', description: '适合明亮环境,层次更轻。' },
+ { key: 'dark', label: '暗色主题', description: '适合低光环境,视觉更沉稳。' },
+ ].map((item) => {
+ const active = themeMode === item.key;
+ return (
+
+ );
+ })}
+
+
) : (
- <>
-
-
setAppearance({ blur: v })}
- style={{ flex: 1 }}
- />
- {appearance.blur}px
+
+
+
界面缩放 (UI Scale)
+
+ setUiScale(Number(v))}
+ style={{ flex: 1 }}
+ />
+ {Math.round(effectiveUiScale * 100)}%
+
+
+ * 建议小屏设备设置为 85%-95%
+
-
- * 仅控制应用内覆盖层的模糊效果
+
+
基础字体大小 (Font Size)
+
+ setFontSize(Number(v))}
+ style={{ flex: 1 }}
+ />
+ {effectiveFontSize}px
+
- >
+
+
透明与模糊效果
+
+
+
启用透明与模糊
+
关闭后保留当前阈值,重新开启时直接恢复之前的设置。
+
+
setAppearance({ enabled: checked })} />
+
+
+
+
背景不透明度 (Opacity)
+
+ setAppearance({ opacity: v })}
+ style={{ flex: 1 }}
+ />
+ {Math.round((appearance.opacity ?? 1.0) * 100)}%
+
+
+
+
高斯模糊 (Blur)
+ {isWindowsPlatform() ? (
+
+ Windows 使用系统 Acrylic 效果,模糊程度由系统控制
+
+ ) : (
+ <>
+
+ setAppearance({ blur: v })}
+ style={{ flex: 1 }}
+ />
+ {appearance.blur}px
+
+
+ * 仅控制应用内覆盖层的模糊效果
+
+ >
+ )}
+
+
+
+
+
启动窗口
+
+ 启动时全屏
+ setStartupFullscreen(checked)} />
+
+
+ * 修改后下次启动生效
+
+
+
+
+
+
)}
-
-
启动窗口
-
- 启动时全屏
- setStartupFullscreen(checked)} />
-
-
- * 修改后下次启动生效
-
-
-
-
-
, '快捷键管理', '统一查看、录制与启停常用快捷键,保持操作习惯一致。')}
open={isShortcutModalOpen}
onCancel={() => {
setIsShortcutModalOpen(false);
setCapturingShortcutAction(null);
}}
- width={720}
+ width={760}
+ styles={{ content: utilityModalShellStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 8 }, body: { paddingTop: 8 }, footer: { background: 'transparent', borderTop: 'none', paddingTop: 10 } }}
footer={[