You've already forked iFlow-Settings-Editor-GUI
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb45ff4512 | |||
| 231a9a686f | |||
| ac87c18e59 |
530
AGENTS.md
530
AGENTS.md
@@ -1,363 +1,233 @@
|
||||
# iFlow Settings Editor - Agent 文档
|
||||
# iFlow Settings Editor - AI Context
|
||||
|
||||
## 项目概述
|
||||
|
||||
iFlow 设置编辑器是一个基于 Electron + Vue 3 的桌面应用程序,用于编辑 `C:\Users\<USER>\.iflow\settings.json` 配置文件。
|
||||
**iFlow 设置编辑器** 是一个用于编辑 iFlow CLI 配置文件 (`~/.iflow/settings.json`) 的桌面应用程序,采用 Electron + Vue 3 技术栈构建。
|
||||
|
||||
## 技术栈
|
||||
### 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| Electron | ^28.0.0 | 桌面应用框架 |
|
||||
| Vue | ^3.4.0 | 前端框架 (组合式 API) |
|
||||
| Vite | ^8.0.8 | 构建工具 |
|
||||
| @icon-park/vue-next | ^1.4.2 | 图标库 |
|
||||
| @vitejs/plugin-vue | ^6.0.6 | Vue 插件 |
|
||||
| concurrently | ^8.2.2 | 并发执行工具 |
|
||||
| electron-builder | ^24.13.3 | 应用打包工具 |
|
||||
| vitest | ^4.1.4 | 单元测试框架 |
|
||||
| @vue/test-utils | ^2.4.6 | Vue 组件测试工具 |
|
||||
| happy-dom | ^20.9.0 | 浏览器环境模拟 |
|
||||
| vue-i18n | ^9.14.5 | 国际化支持 |
|
||||
| less | ^4.6.4 | CSS 预处理器 |
|
||||
| 类别 | 技术 |
|
||||
|------|------|
|
||||
| 框架 | Electron 28 + Vue 3.4 |
|
||||
| 构建工具 | Vite 8 |
|
||||
| CSS 预处理器 | Less |
|
||||
| 国际化 | vue-i18n 9 |
|
||||
| 测试框架 | Vitest 4 + happy-dom |
|
||||
| 打包工具 | electron-builder 24 |
|
||||
| 图标库 | @icon-park/vue-next |
|
||||
|
||||
### 核心架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Electron 主进程 │
|
||||
│ main.js - 窗口管理、系统托盘、IPC 通信、文件读写 │
|
||||
│ preload.js - 安全桥接 (contextBridge) │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
↕ IPC
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Vue 3 渲染进程 │
|
||||
│ src/App.vue - 根组件 │
|
||||
│ ├── TitleBar.vue - 自定义标题栏 │
|
||||
│ ├── SideBar.vue - 侧边导航 │
|
||||
│ ├── Footer.vue - 底部栏 │
|
||||
│ └── views/ │
|
||||
│ ├── GeneralSettings.vue - 基础设置 │
|
||||
│ ├── ApiConfig.vue - API 配置管理 │
|
||||
│ └── McpServers.vue - MCP 服务器管理 │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 配置文件
|
||||
|
||||
- **路径**: `~/.iflow/settings.json`
|
||||
- **编码**: UTF-8 JSON
|
||||
- **备份**: 自动生成 `.bak` 备份文件
|
||||
|
||||
## 开发命令
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 开发模式 (Vite Dev Server)
|
||||
npm run dev
|
||||
|
||||
# Electron 开发模式 (并行启动 Vite + Electron)
|
||||
npm run electron:dev
|
||||
|
||||
# 构建生产版本
|
||||
npm run build
|
||||
|
||||
# 打包 Windows 安装包 (x64)
|
||||
npm run build:win
|
||||
|
||||
# 打包 Windows 便携版
|
||||
npm run build:win-portable
|
||||
|
||||
# 打包 Windows 安装程序 (NSIS)
|
||||
npm run build:win-installer
|
||||
|
||||
# 运行测试
|
||||
npm run test
|
||||
|
||||
# 测试 UI 模式
|
||||
npm run test:ui
|
||||
|
||||
# 测试覆盖率
|
||||
npm run test:coverage
|
||||
|
||||
# 单次运行测试
|
||||
npm run test:run
|
||||
```
|
||||
|
||||
## 设计规范
|
||||
|
||||
### Windows 11 Fluent Design
|
||||
|
||||
项目采用 Windows 11 Fluent Design 设计系统,核心规范:
|
||||
|
||||
| 属性 | 规范 |
|
||||
|------|------|
|
||||
| 字体 | Segoe UI Variable, Segoe UI, system-ui |
|
||||
| 等宽字体 | Cascadia Code, Consolas |
|
||||
| 圆角 | 4px (sm) / 6px / 8px (lg) / 12px (xl) |
|
||||
| 阴影 | 四级层次 (sm///lg/xl) |
|
||||
| 过渡动画 | 0.1-0.2s ease, 0.15s cubic-bezier(0.4, 0, 0.2, 1) |
|
||||
|
||||
### 主题系统
|
||||
|
||||
支持三种主题:`Light` (Xcode) / `Dark` / `Solarized Dark`
|
||||
|
||||
CSS 变量定义在 `src/styles/global.less`,包括:
|
||||
- `--bg-primary/secondary/elevated` - 背景层级
|
||||
- `--text-primary/secondary/tertiary` - 文本层级
|
||||
- `--accent` - 主题色 (Windows Blue)
|
||||
- `--success/warning/danger/info` - 状态色
|
||||
|
||||
### 亚克力效果
|
||||
|
||||
支持可调节透明度的 Mica-inspired 亚克力效果:
|
||||
- 背景透明度随 `acrylicIntensity` (0-100) 变化
|
||||
- 深色/浅色主题有独立的透明度计算逻辑
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
iflow-settings-editor/
|
||||
├── main.js # Electron 主进程 (窗口管理、IPC、文件操作、系统托盘)
|
||||
├── preload.js # 预加载脚本 (contextBridge API)
|
||||
├── index.html # HTML 入口
|
||||
├── package.json # 项目配置
|
||||
├── vite.config.js # Vite 配置
|
||||
├── vitest.config.js # Vitest 测试配置
|
||||
├── src/
|
||||
│ ├── main.js # Vue 入口
|
||||
│ ├── App.vue # 根组件
|
||||
│ ├── components/ # 可复用组件
|
||||
│ │ ├── ApiProfileDialog.vue
|
||||
│ │ ├── Footer.vue
|
||||
│ │ ├── InputDialog.vue
|
||||
│ │ ├── MessageDialog.vue
|
||||
│ │ ├── ServerPanel.vue
|
||||
│ │ ├── SideBar.vue
|
||||
│ │ └── TitleBar.vue
|
||||
│ ├── views/ # 页面视图组件
|
||||
│ │ ├── ApiConfig.vue
|
||||
│ │ ├── GeneralSettings.vue
|
||||
│ │ └── McpServers.vue
|
||||
│ ├── styles/ # 全局样式
|
||||
│ │ └── global.less
|
||||
│ └── locales/ # 国际化资源
|
||||
│ ├── index.js
|
||||
│ ├── en-US.js
|
||||
│ └── ja-JP.js
|
||||
├── build/ # 构建资源 (图标等)
|
||||
├── dist/ # Vite 构建输出
|
||||
├── release/ # 打包输出目录
|
||||
└── screenshots/ # 截图资源
|
||||
src/
|
||||
├── main.js # Vue 应用入口
|
||||
├── App.vue # 根组件
|
||||
├── components/
|
||||
│ ├── TitleBar.vue # 标题栏 (窗口控制按钮)
|
||||
│ ├── SideBar.vue # 侧边导航栏
|
||||
│ ├── Footer.vue # 底部栏
|
||||
│ ├── InputDialog.vue # 输入对话框
|
||||
│ ├── MessageDialog.vue # 消息对话框
|
||||
│ ├── ApiProfileDialog.vue # API 配置弹窗
|
||||
│ └── ServerPanel.vue # 服务器编辑面板
|
||||
├── views/
|
||||
│ ├── GeneralSettings.vue # 常规设置视图
|
||||
│ ├── ApiConfig.vue # API 配置视图
|
||||
│ └── McpServers.vue # MCP 服务器视图
|
||||
├── locales/
|
||||
│ ├── index.js # 中文 (zh-CN)
|
||||
│ ├── en-US.js # 英文
|
||||
│ └── ja-JP.js # 日文
|
||||
└── styles/
|
||||
└── global.less # 全局样式 (Windows Fluent Design)
|
||||
```
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 进程模型
|
||||
- **Main Process (main.js)**: Electron 主进程,处理窗口管理、IPC 通信、文件系统操作、系统托盘
|
||||
- **Preload (preload.js)**: 通过 `contextBridge.exposeInMainWorld` 暴露安全 API
|
||||
- **Renderer (Vue)**: 渲染进程,只通过 preload 暴露的 API 与主进程通信
|
||||
## 关键模块
|
||||
|
||||
### IPC 通信
|
||||
|
||||
**preload.js** 暴露的 API:
|
||||
|
||||
```javascript
|
||||
// preload.js 暴露的 API
|
||||
window.electronAPI = {
|
||||
// 基本设置操作
|
||||
loadSettings: () => ipcRenderer.invoke('load-settings'),
|
||||
saveSettings: (data) => ipcRenderer.invoke('save-settings', data),
|
||||
showMessage: (options) => ipcRenderer.invoke('show-message', options),
|
||||
// 设置操作
|
||||
window.electronAPI.loadSettings()
|
||||
window.electronAPI.saveSettings(data)
|
||||
|
||||
// 窗口控制
|
||||
isMaximized: () => ipcRenderer.invoke('is-maximized'),
|
||||
minimize: () => ipcRenderer.send('window-minimize'),
|
||||
maximize: () => ipcRenderer.send('window-maximize'),
|
||||
close: () => ipcRenderer.send('window-close'),
|
||||
// 窗口控制
|
||||
window.electronAPI.minimize()
|
||||
window.electronAPI.maximize()
|
||||
window.electronAPI.close()
|
||||
window.electronAPI.isMaximized()
|
||||
|
||||
// API 配置管理(单文件内多配置)
|
||||
listApiProfiles: () => ipcRenderer.invoke('list-api-profiles'),
|
||||
switchApiProfile: (profileName) => ipcRenderer.invoke('switch-api-profile', profileName),
|
||||
createApiProfile: (name) => ipcRenderer.invoke('create-api-profile', name),
|
||||
deleteApiProfile: (name) => ipcRenderer.invoke('delete-api-profile', name),
|
||||
renameApiProfile: (oldName, newName) => ipcRenderer.invoke('rename-api-profile', oldName, newName),
|
||||
duplicateApiProfile: (sourceName, newName) => ipcRenderer.invoke('duplicate-api-profile', sourceName, newName),
|
||||
|
||||
// 托盘事件监听
|
||||
onApiProfileSwitched: (callback) => {
|
||||
ipcRenderer.on('api-profile-switched', (event, profileName) => callback(profileName))
|
||||
},
|
||||
|
||||
// 语言切换通知
|
||||
notifyLanguageChanged: () => ipcRenderer.send('language-changed')
|
||||
}
|
||||
// API 配置管理
|
||||
window.electronAPI.listApiProfiles()
|
||||
window.electronAPI.switchApiProfile(name)
|
||||
window.electronAPI.createApiProfile(name)
|
||||
window.electronAPI.deleteApiProfile(name)
|
||||
window.electronAPI.renameApiProfile(oldName, newName)
|
||||
window.electronAPI.duplicateApiProfile(sourceName, newName)
|
||||
```
|
||||
|
||||
### 窗口配置
|
||||
- 窗口尺寸: 1100x750,最小尺寸: 900x600
|
||||
- 无边框窗口 (frame: false),自定义标题栏
|
||||
- 开发模式加载 `http://localhost:5173`,生产模式加载 `dist/index.html`
|
||||
- **关闭窗口时隐藏到系统托盘**,双击托盘图标可重新显示
|
||||
### API 配置管理
|
||||
|
||||
配置文件内使用 `apiProfiles` 对象存储多个配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"currentApiProfile": "default",
|
||||
"apiProfiles": {
|
||||
"default": { "apiKey": "...", "baseUrl": "..." },
|
||||
"production": { "apiKey": "...", "baseUrl": "..." }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 系统托盘
|
||||
- 托盘图标显示应用状态
|
||||
- 右键菜单支持:
|
||||
- 显示主窗口
|
||||
- 切换 API 配置(显示所有配置列表,当前配置带勾选标记)
|
||||
- 退出应用
|
||||
|
||||
- 窗口关闭时隐藏到托盘而非退出
|
||||
- 托盘菜单支持快速切换 API 配置
|
||||
- 双击托盘图标显示主窗口
|
||||
|
||||
### API 配置切换
|
||||
- 支持多环境配置: 默认配置,开发环境、预发布环境、生产环境
|
||||
- 配置文件管理: 支持创建、编辑、复制、删除、重命名
|
||||
- 单独保存每个环境的 API 配置到 `apiProfiles` 对象
|
||||
- 切换配置时直接应用新配置,无需确认
|
||||
- 支持从系统托盘快速切换配置
|
||||
## 代码风格
|
||||
|
||||
## 可用命令
|
||||
### Vue 3 Composition API
|
||||
|
||||
```bash
|
||||
npm install # 安装依赖
|
||||
npm run dev # 启动 Vite 开发服务器
|
||||
npm run build # 构建 Vue 应用到 dist 目录
|
||||
npm start # 运行 Electron (需先build)
|
||||
npm run electron:dev # 同时运行 Vite + Electron (开发模式)
|
||||
npm run electron:start # 构建 + 运行 Electron (生产模式)
|
||||
npm run pack # 打包应用(不生成安装包)
|
||||
npm run build:win # 构建 Windows 安装包
|
||||
npm run build:win64 # 构建 Windows x64 安装包
|
||||
npm run build:win32 # 构建 Windows x86 安装包
|
||||
npm run build:win-portable # 构建可移植版本
|
||||
npm run build:win-installer # 构建 NSIS 安装包
|
||||
npm run dist # 完整构建和打包
|
||||
npm run test # 运行所有测试(监听模式)
|
||||
npm run test:run # 运行测试一次
|
||||
npm run test:ui # 运行测试 UI 界面
|
||||
npm run test:coverage # 生成测试覆盖率报告
|
||||
使用 `<script setup>` 语法:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
|
||||
const settings = ref({})
|
||||
const modified = computed(() => ...)
|
||||
|
||||
const updateSettings = () => { ... }
|
||||
|
||||
watch(settings, () => { ... }, { deep: true })
|
||||
|
||||
onMounted(async () => { ... })
|
||||
</script>
|
||||
```
|
||||
|
||||
## 功能模块
|
||||
### 样式规范
|
||||
|
||||
### 1. 常规设置 (General)
|
||||
- **语言**: zh-CN / en-US / ja-JP
|
||||
- **主题**: Xcode / Dark / Solarized Dark
|
||||
- **启动动画**: 已显示 / 未显示
|
||||
- **检查点保存**: 启用 / 禁用
|
||||
- 使用 Less 预处理器
|
||||
- 通过 CSS 变量 (`var(--xxx)`) 使用主题色
|
||||
- 组件样式使用 BEM-like 命名或直接使用功能类名
|
||||
- 动画使用 `@keyframes` 定义
|
||||
|
||||
### 2. API 配置 (API)
|
||||
- **配置列表**: 显示所有可用的 API 配置文件,带彩色图标和状态标记
|
||||
- **配置切换**: 点击配置卡片直接切换,无需确认
|
||||
- **创建配置**: 新建 API 配置文件(支持设置认证方式、API Key、Base URL 等)
|
||||
- **编辑配置**: 修改现有配置的认证方式、API Key、Base URL 等
|
||||
- **复制配置**: 基于现有配置创建新配置
|
||||
- **删除配置**: 删除非默认配置
|
||||
- **认证方式**: iFlow / API Key / OpenAI 兼容
|
||||
- **API Key**: 密码输入框
|
||||
- **Base URL**: API 端点
|
||||
- **模型名称**: AI 模型标识
|
||||
- **搜索 API Key**: 搜索服务认证
|
||||
- **CNA**: CNA 标识
|
||||
### 测试规范
|
||||
|
||||
### 3. MCP 服务器管理 (MCP)
|
||||
- 服务器列表展示(带描述信息)
|
||||
- 添加/编辑/删除服务器
|
||||
- 侧边面板编辑界面
|
||||
- 服务器配置: 名称、描述、命令、工作目录、参数(每行一个)、环境变量(JSON)
|
||||
- 测试文件命名: `*.test.js` 或 `*.spec.js`
|
||||
- 使用 Vitest + @vue/test-utils
|
||||
- DOM 测试环境: happy-dom
|
||||
- 覆盖范围排除: node_modules, dist, release, build
|
||||
|
||||
## 关键实现细节
|
||||
## 快捷键与交互
|
||||
|
||||
### 设置文件路径
|
||||
```javascript
|
||||
const SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json');
|
||||
```
|
||||
| 操作 | 说明 |
|
||||
|------|------|
|
||||
| 窗口关闭 | 隐藏到系统托盘 |
|
||||
| 双击托盘 | 显示主窗口 |
|
||||
| Ctrl+S | 自动保存设置 (通过 watch 监听) |
|
||||
|
||||
### 保存时自动备份
|
||||
```javascript
|
||||
if (fs.existsSync(SETTINGS_FILE)) {
|
||||
const backupPath = SETTINGS_FILE + '.bak';
|
||||
fs.copyFileSync(SETTINGS_FILE, backupPath);
|
||||
}
|
||||
```
|
||||
## 常见问题
|
||||
|
||||
### 安全配置
|
||||
- `contextIsolation: true` - 隔离上下文
|
||||
- `nodeIntegration: false` - 禁用 Node.js
|
||||
- `webSecurity: false` - 仅开发环境解决 CSP 问题
|
||||
|
||||
### Vue 组件状态管理
|
||||
- `settings` - 当前设置 (ref)
|
||||
- `originalSettings` - 原始设置 (用于检测修改)
|
||||
- `modified` - 是否已修改 (computed/diff)
|
||||
- `currentSection` - 当前显示的板块
|
||||
- `currentServerName` - 当前选中的 MCP 服务器
|
||||
- `currentApiProfile` - 当前使用的 API 配置名称
|
||||
- `apiProfiles` - 可用的 API 配置列表
|
||||
- `isLoading` - 加载状态标志
|
||||
|
||||
### API 配置字段
|
||||
```javascript
|
||||
const API_FIELDS = ['selectedAuthType', 'apiKey', 'baseUrl', 'modelName', 'searchApiKey', 'cna'];
|
||||
```
|
||||
|
||||
### 数据初始化
|
||||
在 `loadSettings` 函数中确保所有字段都有默认值:
|
||||
- `language`: 'zh-CN'
|
||||
- `theme`: 'Xcode'
|
||||
- `bootAnimationShown`: true
|
||||
- `checkpointing`: { enabled: true }
|
||||
- `selectedAuthType`: 'openai-compatible'
|
||||
- `apiKey`: ''
|
||||
- `baseUrl`: ''
|
||||
- `modelName`: ''
|
||||
- `searchApiKey`: ''
|
||||
- `cna`: ''
|
||||
- `apiProfiles`: { default: {} }
|
||||
- `currentApiProfile`: 'default'
|
||||
- `mcpServers`: {}
|
||||
|
||||
## 设计系统
|
||||
|
||||
### Windows UI Kit - Fluent Design
|
||||
|
||||
本项目采用 Windows 11 Fluent Design 设计规范,实现统一的视觉效果。
|
||||
|
||||
#### 主题变量
|
||||
|
||||
**Light 模式:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: rgba(243, 243, 243, 0.85);
|
||||
--bg-secondary: rgba(255, 255, 255, 0.70);
|
||||
--bg-tertiary: #ebebeb;
|
||||
--bg-elevated: rgba(255, 255, 255, 0.95);
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #5d5d5d;
|
||||
--accent: #0078d4;
|
||||
--accent-hover: #106ebe;
|
||||
--border: #e0e0e0;
|
||||
--control-fill: rgba(249, 249, 249, 0.85);
|
||||
}
|
||||
```
|
||||
|
||||
**Dark 模式:**
|
||||
```css
|
||||
.dark {
|
||||
--bg-primary: #1f1f1f;
|
||||
--bg-secondary: #2d2d2d;
|
||||
--text-primary: #ffffff;
|
||||
--accent: #60cdff;
|
||||
--control-fill: #333333;
|
||||
}
|
||||
```
|
||||
|
||||
**Solarized Dark 模式:**
|
||||
```css
|
||||
.solarized-dark {
|
||||
--bg-primary: #002b36;
|
||||
--bg-secondary: #073642;
|
||||
--text-primary: #839496;
|
||||
--accent: #268bd2;
|
||||
}
|
||||
```
|
||||
|
||||
#### 设计原则
|
||||
- **Mica -inspired 层次感**: 使用半透明背景和分层深度
|
||||
- **圆角系统**: 4px / 6px / 8px / 12px 四级圆角
|
||||
- **阴影层次**: sm / md / lg / xl 四级阴影
|
||||
- **过渡动画**: 0.1s-0.2s 流畅曲线
|
||||
- **Segoe UI Variable 字体**: Windows 11 原生字体
|
||||
|
||||
## 测试框架 (Vitest)
|
||||
|
||||
**测试配置**:
|
||||
- 使用 Vitest 作为测试运行器
|
||||
- 使用 `@vue/test-utils` 进行 Vue 组件测试
|
||||
- 使用 `happy-dom` 作为浏览器环境模拟
|
||||
- 配置文件:`vitest.config.js`
|
||||
- 全局变量启用:`globals: true`
|
||||
- 覆盖率工具:`v8`
|
||||
- 覆盖率报告格式:text、json、html
|
||||
|
||||
**测试文件结构**:
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── Footer.test.js # Footer 组件测试
|
||||
│ ├── SideBar.test.js # 侧边栏测试
|
||||
│ └── TitleBar.test.js # 标题栏测试
|
||||
└── views/
|
||||
├── ApiConfig.test.js # API 配置测试
|
||||
├── GeneralSettings.test.js # 常规设置测试
|
||||
└── McpServers.test.js # MCP 服务器测试
|
||||
```
|
||||
|
||||
**测试命令**:
|
||||
```bash
|
||||
npm run test # 运行所有测试(监听模式)
|
||||
npm run test:run # 运行测试一次
|
||||
npm run test:ui # 运行测试 UI 界面 (http://localhost:5174/__vitest__/)
|
||||
npm run test:coverage # 生成测试覆盖率报告
|
||||
```
|
||||
|
||||
## 开发注意事项
|
||||
|
||||
1. **修改检测**: 通过 `watch(settings, () => { modified.value = true }, { deep: true })` 深度监听
|
||||
2. **服务器编辑**: 使用侧边面板 (Side Panel) 收集表单数据
|
||||
3. **MCP 参数**: 每行一个参数,通过换行分割
|
||||
4. **环境变量**: 支持 JSON 格式输入
|
||||
5. **窗口控制**: 通过 IPC 发送指令,主进程处理实际窗口操作
|
||||
6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中
|
||||
7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题
|
||||
8. **默认值处理**: 加载配置时检查 `undefined` 并应用默认值,防止界面显示异常
|
||||
9. **托盘切换事件**: 监听 `onApiProfileSwitched` 处理托盘发起的配置切换
|
||||
10. **样式系统**: 使用 Windows UI Kit 设计系统,所有变量在 `global.less` 中定义
|
||||
|
||||
## 图标使用
|
||||
|
||||
使用 `@icon-park/vue-next` 图标库:
|
||||
```javascript
|
||||
import { Save, Config, Key, Server, Globe, Setting, Add, Edit, Delete, Exchange, Copy } from '@icon-park/vue-next';
|
||||
```
|
||||
|
||||
## 打包配置
|
||||
|
||||
### Windows 平台
|
||||
- **NSIS 安装包**: 支持 x64 架构
|
||||
- **可移植版本**: 无需安装的独立可执行文件
|
||||
- **安装器特性**:
|
||||
- 允许修改安装目录
|
||||
- 允许提升权限
|
||||
- 创建桌面和开始菜单快捷方式
|
||||
- 支持中文和英文界面 (zh_CN, en_US)
|
||||
- 卸载时保留用户数据
|
||||
|
||||
### 输出目录
|
||||
- `release/` - 所有打包输出的根目录
|
||||
- 安装包命名: `iFlow Settings Editor-${version}-${arch}-setup.${ext}`
|
||||
- 可移植版本命名: `iFlow Settings Editor-${version}-portable.${ext}`
|
||||
|
||||
## UI 组件
|
||||
|
||||
### 对话框类型
|
||||
- **输入对话框**: 用于重命名、复制等需要文本输入的场景
|
||||
- **确认对话框**: 用于删除等需要确认的操作
|
||||
- **消息对话框**: 显示 info/success/warning/error 四种类型,带图标
|
||||
|
||||
### 侧边面板
|
||||
- MCP 服务器编辑使用侧边面板 (从右侧滑入)
|
||||
- API 配置编辑使用模态对话框
|
||||
|
||||
## 版本历史
|
||||
|
||||
| 版本 | 日期 | 主要变更 |
|
||||
|------|------|----------|
|
||||
| 1.6.0 | 2026-04-18 | 架构:重构样式系统,采用 Windows 11 Fluent Design 规范 |
|
||||
| 1.5.1 | 2026-04-17 | 新增系统托盘功能,托盘快速切换 API 配置 |
|
||||
| 1.5.0 | 2026-04-16 | 新增自定义消息对话框,API 配置重命名 |
|
||||
| 1.4.0 | 2026-04-14 | 新增多环境配置文件管理 |
|
||||
| 1.0.0 | 2026-04-14 | 项目初始化 |
|
||||
1. **图标不显示**: 检查 `build/icon.ico` 是否存在
|
||||
2. **配置不保存**: 确认 `~/.iflow/settings.json` 目录可写
|
||||
3. **亚克力效果异常**: 检查 `acrylicIntensity` 值是否在 0-100 范围内
|
||||
|
||||
340
README.md
340
README.md
@@ -1,213 +1,161 @@
|
||||
# iFlow Settings Editor
|
||||
|
||||
一个基于 Electron + Vue 3 的桌面应用程序,用于编辑 `C:\Users\<USER>\.iflow\settings.json` 配置文件。
|
||||
一个用于编辑 iFlow CLI 配置文件的桌面应用程序。
|
||||
|
||||

|
||||
|
||||
## 功能特性
|
||||
|
||||
- 📝 **API 配置管理** - 支持多环境配置文件切换、创建、编辑、复制和删除
|
||||
- 🖥️ **MCP 服务器管理** - 便捷的 Model Context Protocol 服务器配置界面
|
||||
- 🎨 **Windows 11 设计风格** - 采用 Fluent Design 设计规范
|
||||
- 🌈 **多主题支持** - Light / Dark / Solarized Dark 三种主题
|
||||
- 🌍 **国际化** - 支持简体中文、English、日語
|
||||
- 💧 **亚克力效果** - 可调节透明度的现代视觉效果
|
||||
- 📦 **系统托盘** - 最小化到托盘,快速切换 API 配置
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| Electron | ^28.0.0 | 桌面应用框架 |
|
||||
| Vue | ^3.4.0 | 前端框架 (组合式 API) |
|
||||
| Vite | ^8.0.8 | 构建工具 |
|
||||
| @icon-park/vue-next | ^1.4.2 | 图标库 |
|
||||
| concurrently | ^8.2.2 | 并发执行工具 |
|
||||
| electron-builder | ^24.13.3 | 应用打包工具 |
|
||||
| vue-i18n | ^9.14.5 | 国际化支持 |
|
||||
| less | ^4.6.4 | CSS 预处理器 |
|
||||
| 技术 | 版本 |
|
||||
|------|------|
|
||||
| Electron | 28.x |
|
||||
| Vue | 3.4.x |
|
||||
| Vite | 8.x |
|
||||
| vue-i18n | 9.x |
|
||||
| Less | 4.x |
|
||||
| Vitest | 4.x |
|
||||
|
||||
## 支持的系统
|
||||
|
||||
- Windows 10 / 11 (x64)
|
||||
|
||||
## 安装
|
||||
|
||||
### 从源码运行
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://git.pandorastudio.cn/product/iFlow-Settings-Editor-GUI.git
|
||||
|
||||
# 进入目录
|
||||
cd iFlow-Settings-Editor-GUI
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 开发模式运行
|
||||
npm run electron:dev
|
||||
```
|
||||
|
||||
### 构建安装包
|
||||
|
||||
```bash
|
||||
# 构建 Windows 安装包 (x64)
|
||||
npm run build:win
|
||||
|
||||
# 构建便携版
|
||||
npm run build:win-portable
|
||||
|
||||
# 构建 NSIS 安装程序
|
||||
npm run build:win-installer
|
||||
```
|
||||
|
||||
构建完成后,安装包位于 `release/` 目录下。
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 基础设置
|
||||
|
||||
在「常规」页面可以设置:
|
||||
|
||||
- **语言** - 界面显示语言
|
||||
- **主题** - 视觉主题风格
|
||||
- **启动动画** - 控制应用启动时的动画显示
|
||||
- **检查点保存** - 开启/关闭自动保存功能
|
||||
- **亚克力效果** - 调节窗口背景透明度
|
||||
|
||||
### API 配置管理
|
||||
|
||||
在「API 配置」页面可以:
|
||||
|
||||
- **切换配置** - 点击不同配置文件快速切换
|
||||
- **新建配置** - 创建新的 API 环境配置
|
||||
- **编辑配置** - 修改现有配置的认证方式、API Key、Base URL 等
|
||||
- **复制配置** - 复制现有配置创建新配置
|
||||
- **删除配置** - 删除不需要的配置(默认配置不可删除)
|
||||
|
||||
支持的认证方式:
|
||||
- iFlow
|
||||
- API Key
|
||||
- OpenAI 兼容
|
||||
|
||||
### MCP 服务器管理
|
||||
|
||||
在「MCP 服务器」页面可以:
|
||||
|
||||
- **添加服务器** - 配置新的 MCP 服务器
|
||||
- **编辑服务器** - 修改服务器的命令、工作目录、参数等
|
||||
- **删除服务器** - 移除不需要的服务器
|
||||
|
||||
### 系统托盘
|
||||
|
||||
- 关闭窗口时,应用会最小化到系统托盘
|
||||
- 双击托盘图标可重新显示主窗口
|
||||
- 右键托盘菜单可快速切换 API 配置
|
||||
|
||||
## 配置文件
|
||||
|
||||
应用配置文件位于:
|
||||
|
||||
```
|
||||
~/.iflow/settings.json
|
||||
```
|
||||
|
||||
每次保存时会自动生成备份文件 `settings.json.bak`。
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
# 运行测试
|
||||
npm run test
|
||||
|
||||
# UI 模式测试
|
||||
npm run test:ui
|
||||
|
||||
# 测试覆盖率
|
||||
npm run test:coverage
|
||||
|
||||
# 单次运行测试
|
||||
npm run test:run
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
iflow-settings-editor/
|
||||
├── main.js # Electron 主进程 (窗口管理、IPC、文件操作、系统托盘)
|
||||
├── preload.js # 预加载脚本 (contextBridge API)
|
||||
├── index.html # HTML 入口
|
||||
├── package.json # 项目配置
|
||||
iFlow-Settings-Editor-GUI/
|
||||
├── main.js # Electron 主进程
|
||||
├── preload.js # 预加载脚本
|
||||
├── index.html # 入口 HTML
|
||||
├── vite.config.js # Vite 配置
|
||||
├── vitest.config.js # Vitest 测试配置
|
||||
├── src/
|
||||
│ ├── main.js # Vue 入口
|
||||
│ ├── components/ # 可复用组件
|
||||
│ │ ├── ApiProfileDialog.vue
|
||||
│ │ ├── Footer.vue
|
||||
│ │ ├── InputDialog.vue
|
||||
│ │ ├── MessageDialog.vue
|
||||
│ │ ├── ServerPanel.vue
|
||||
│ │ ├── SideBar.vue
|
||||
│ │ └── TitleBar.vue
|
||||
│ ├── views/ # 页面视图组件
|
||||
│ │ ├── ApiConfig.vue
|
||||
│ │ ├── GeneralSettings.vue
|
||||
│ │ └── McpServers.vue
|
||||
│ └── App.vue # 主组件 (所有业务逻辑、UI 组件)
|
||||
├── build/ # 构建资源 (图标等)
|
||||
├── vitest.config.js # Vitest 测试配置
|
||||
├── build/ # 构建资源
|
||||
├── dist/ # Vite 构建输出
|
||||
├── release/ # 打包输出目录
|
||||
└── screenshots/ # 截图资源
|
||||
├── release/ # Electron Builder 输出
|
||||
├── screenshots/ # 应用截图
|
||||
└── src/
|
||||
├── main.js # Vue 入口
|
||||
├── App.vue # 根组件
|
||||
├── components/ # 公共组件
|
||||
├── views/ # 页面视图
|
||||
├── locales/ # 国际化语言包
|
||||
└── styles/ # 全局样式
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
npm run dev # 启动 Vite 开发服务器
|
||||
npm run electron:dev # 同时运行 Electron + Vite
|
||||
```
|
||||
|
||||
### 构建与运行
|
||||
|
||||
```bash
|
||||
npm run build # 构建 Vue 应用到 dist 目录
|
||||
npm start # 运行 Electron 应用
|
||||
npm run electron:start # 构建 + 运行 Electron
|
||||
```
|
||||
|
||||
### 打包应用
|
||||
|
||||
```bash
|
||||
npm run pack # 打包应用(不生成安装包)
|
||||
npm run build:win # 构建 Windows 安装包 (NSIS)
|
||||
npm run build:win64 # 构建 Windows x64 安装包
|
||||
npm run build:win32 # 构建 Windows x86 安装包
|
||||
npm run build:win-portable # 构建可移植版本
|
||||
npm run build:win-installer # 构建 NSIS 安装包
|
||||
npm run dist # 完整构建和打包
|
||||
```
|
||||
|
||||
## 功能模块
|
||||
|
||||
### 1. 常规设置 (General)
|
||||
|
||||

|
||||
|
||||
配置应用程序的常规选项:
|
||||
|
||||
- **语言**: zh-CN / en-US / ja-JP
|
||||
- **主题**: Xcode / Dark / Solarized Dark
|
||||
- **启动动画**: 已显示 / 未显示
|
||||
- **检查点保存**: 启用 / 禁用
|
||||
|
||||
### 2. API 配置 (API)
|
||||
|
||||
管理多个环境的 API 配置:
|
||||
|
||||
- **配置列表**: 显示所有可用的 API 配置文件,带彩色图标和状态标记
|
||||
- **配置切换**: 点击配置卡片直接切换
|
||||
- **创建配置**: 新建 API 配置文件
|
||||
- **编辑配置**: 修改现有配置的认证方式、API Key、Base URL 等
|
||||
- **复制配置**: 基于现有配置创建新配置
|
||||
- **删除配置**: 删除非默认配置
|
||||
- **认证方式**: iFlow / API Key / OpenAI 兼容
|
||||
- **API Key**: 密码输入框
|
||||
- **Base URL**: API 端点
|
||||
- **模型名称**: AI 模型标识
|
||||
- **搜索 API Key**: 搜索服务认证
|
||||
- **CNA**: CNA 标识
|
||||
|
||||
### 3. MCP 服务器管理 (MCP)
|
||||
|
||||
管理 Model Context Protocol 服务器配置:
|
||||
|
||||
- **服务器列表**: 显示所有已配置的服务器,带描述信息
|
||||
- **添加服务器**: 创建新的 MCP 服务器配置
|
||||
- **编辑服务器**: 通过侧边面板修改现有服务器配置
|
||||
- **删除服务器**: 移除服务器配置
|
||||
- **服务器配置项**:
|
||||
- 名称
|
||||
- 描述
|
||||
- 命令
|
||||
- 工作目录
|
||||
- 参数 (每行一个)
|
||||
- 环境变量 (JSON 格式)
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 进程模型
|
||||
- **Main Process (main.js)**: Electron 主进程,处理窗口管理、IPC 通信、文件系统操作、系统托盘
|
||||
- **Preload (preload.js)**: 通过 `contextBridge.exposeInMainWorld` 暴露安全 API
|
||||
- **Renderer (Vue)**: 渲染进程,只通过 preload 暴露的 API 与主进程通信
|
||||
|
||||
### 窗口配置
|
||||
- 窗口尺寸: 1100x750,最小尺寸: 900x600
|
||||
- 无边框窗口 (frame: false),自定义标题栏
|
||||
- 开发模式加载 `http://localhost:5173`,生产模式加载 `dist/index.html`
|
||||
- **关闭窗口时隐藏到系统托盘**,双击托盘图标可重新显示
|
||||
|
||||
### 系统托盘
|
||||
- 托盘图标显示应用状态
|
||||
- 右键菜单支持:
|
||||
- 显示主窗口
|
||||
- 切换 API 配置(显示所有配置列表,当前配置带勾选标记)
|
||||
- 退出应用
|
||||
- 双击托盘图标显示主窗口
|
||||
|
||||
### 安全配置
|
||||
- `contextIsolation: true` - 隔离上下文
|
||||
- `nodeIntegration: false` - 禁用 Node.js
|
||||
- `webSecurity: false` - 仅开发环境解决 CSP 问题
|
||||
|
||||
## 设计系统
|
||||
|
||||
本项目采用 **Windows 11 Fluent Design** 设计规范,实现统一的视觉效果。
|
||||
|
||||
### 主题支持
|
||||
- **Xcode**: macOS 风格浅色主题
|
||||
- **Dark**: Windows 11 风格深色主题
|
||||
- **Solarized Dark**: Solarized 配色深色主题
|
||||
|
||||
### 设计特点
|
||||
- **Mica-inspired 层次感**: 使用半透明背景和分层深度
|
||||
- **圆角系统**: 4px / 6px / 8px / 12px 四级圆角
|
||||
- **阴影层次**: sm / md / lg / xl 四级阴影
|
||||
- **过渡动画**: 0.1s-0.2s 流畅曲线
|
||||
- **Segoe UI Variable 字体**: Windows 11 原生字体
|
||||
|
||||
## 打包配置
|
||||
|
||||
### Windows 平台
|
||||
- **NSIS 安装包**: 支持 x64 架构
|
||||
- **可移植版本**: 无需安装的独立可执行文件
|
||||
- **安装器特性**:
|
||||
- 允许修改安装目录
|
||||
- 允许提升权限
|
||||
- 创建桌面和开始菜单快捷方式
|
||||
- 支持中文和英文界面 (zh_CN, en_US)
|
||||
- 卸载时保留用户数据
|
||||
|
||||
### 输出目录
|
||||
- `release/` - 所有打包输出的根目录
|
||||
- 安装包命名: `iFlow Settings Editor-${version}-${arch}-setup.${ext}`
|
||||
- 可移植版本命名: `iFlow Settings Editor-${version}-portable.${ext}`
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `webSecurity: false` 仅用于开发环境解决 CSP 问题
|
||||
- 保存设置时会自动创建备份 (`settings.json.bak`)
|
||||
- MCP 服务器参数每行一个,环境变量支持 JSON 格式
|
||||
- API 配置切换时会直接应用新配置,未保存的更改会被替换
|
||||
- 关闭窗口时应用会隐藏到系统托盘,不会退出应用
|
||||
|
||||
## 开发注意事项
|
||||
|
||||
1. **修改检测**: 通过 `watch(settings, () => { modified.value = true }, { deep: true })` 深度监听
|
||||
2. **服务器编辑**: 使用侧边面板 (Side Panel) 收集表单数据
|
||||
3. **MCP 参数**: 每行一个参数,通过换行分割
|
||||
4. **环境变量**: 支持 JSON 格式输入
|
||||
5. **窗口控制**: 通过 IPC 发送指令,主进程处理实际窗口操作
|
||||
6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中
|
||||
7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题
|
||||
8. **默认值处理**: 加载配置时检查 `undefined` 并应用默认值,防止界面显示异常
|
||||
9. **托盘切换事件**: 监听 `onApiProfileSwitched` 处理托盘发起的配置切换
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright © 2026 上海潘哆呐科技有限公司
|
||||
## 联系方式
|
||||
|
||||
- 公司: 上海潘哆呐科技有限公司
|
||||
- 项目地址: https://git.pandorastudio.cn/product/iFlow-Settings-Editor-GUI
|
||||
|
||||
BIN
screenshots/acrylic-slider-jp.jpg
Normal file
BIN
screenshots/acrylic-slider-jp.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
66
src/App.vue
66
src/App.vue
@@ -62,7 +62,7 @@ const { locale, t } = useI18n()
|
||||
|
||||
const settings = ref({
|
||||
language: 'zh-CN',
|
||||
theme: 'Xcode',
|
||||
uiTheme: 'Light',
|
||||
bootAnimationShown: true,
|
||||
checkpointing: { enabled: true },
|
||||
mcpServers: {},
|
||||
@@ -74,6 +74,7 @@ const settings = ref({
|
||||
cna: '',
|
||||
currentApiProfile: 'default',
|
||||
apiProfiles: { default: {} },
|
||||
acrylicIntensity: 50,
|
||||
})
|
||||
|
||||
const originalSettings = ref({})
|
||||
@@ -262,7 +263,7 @@ const loadSettings = async () => {
|
||||
if (!data.checkpointing) data.checkpointing = { enabled: true }
|
||||
if (!data.mcpServers) data.mcpServers = {}
|
||||
if (data.language === undefined) data.language = 'zh-CN'
|
||||
if (data.theme === undefined) data.theme = 'Xcode'
|
||||
if (data.uiTheme === undefined) data.uiTheme = 'Light'
|
||||
if (data.bootAnimationShown === undefined) data.bootAnimationShown = true
|
||||
if (!data.selectedAuthType) data.selectedAuthType = 'openai-compatible'
|
||||
if (data.apiKey === undefined) data.apiKey = ''
|
||||
@@ -272,6 +273,7 @@ const loadSettings = async () => {
|
||||
if (data.cna === undefined) data.cna = ''
|
||||
if (!data.apiProfiles) data.apiProfiles = { default: {} }
|
||||
if (!data.currentApiProfile) data.currentApiProfile = 'default'
|
||||
if (data.acrylicIntensity === undefined) data.acrylicIntensity = 50
|
||||
settings.value = data
|
||||
originalSettings.value = JSON.parse(JSON.stringify(data))
|
||||
modified.value = false
|
||||
@@ -306,12 +308,37 @@ const showSection = section => {
|
||||
const serverCount = computed(() => (settings.value.mcpServers ? Object.keys(settings.value.mcpServers).length : 0))
|
||||
|
||||
const themeClass = computed(() => {
|
||||
const theme = settings.value.theme
|
||||
const theme = settings.value.uiTheme
|
||||
if (theme === 'Dark') return 'dark'
|
||||
if (theme === 'Solarized Dark') return 'solarized-dark'
|
||||
return ''
|
||||
})
|
||||
|
||||
const acrylicStyle = computed(() => {
|
||||
const intensity = settings.value.acrylicIntensity
|
||||
if (intensity === undefined || intensity === null) return {}
|
||||
const opacity = 1 - intensity / 100
|
||||
const isDark = settings.value.uiTheme === 'Dark'
|
||||
|
||||
if (isDark) {
|
||||
return {
|
||||
'--bg-primary': `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`,
|
||||
'--bg-secondary': `rgba(45, 45, 45, ${Math.max(0.05, opacity * 0.7)})`,
|
||||
'--bg-elevated': `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.95)})`,
|
||||
'--bg-mica': `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`,
|
||||
'--control-fill': `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.85)})`,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'--bg-primary': `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.85)})`,
|
||||
'--bg-secondary': `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.7)})`,
|
||||
'--bg-elevated': `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.95)})`,
|
||||
'--bg-mica': `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.473)})`,
|
||||
'--control-fill': `rgba(249, 249, 249, ${Math.max(0.05, opacity * 0.85)})`,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const selectServer = name => {
|
||||
currentServerName.value = name
|
||||
openEditServerPanel(name)
|
||||
@@ -444,7 +471,7 @@ const closeMessageDialog = () => {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => settings.value.theme,
|
||||
() => settings.value.uiTheme,
|
||||
theme => {
|
||||
const cls = themeClass.value
|
||||
if (cls) {
|
||||
@@ -454,9 +481,39 @@ watch(
|
||||
} else {
|
||||
document.body.classList.remove('dark', 'solarized-dark')
|
||||
}
|
||||
applyAcrylicStyle()
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => settings.value.acrylicIntensity,
|
||||
() => {
|
||||
applyAcrylicStyle()
|
||||
},
|
||||
)
|
||||
|
||||
const applyAcrylicStyle = () => {
|
||||
const intensity = settings.value.acrylicIntensity
|
||||
if (intensity === undefined || intensity === null) return
|
||||
const opacity = 1 - intensity / 100
|
||||
const isDark = settings.value.uiTheme === 'Dark'
|
||||
const root = document.documentElement
|
||||
|
||||
if (isDark) {
|
||||
root.style.setProperty('--bg-primary', `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`)
|
||||
root.style.setProperty('--bg-secondary', `rgba(45, 45, 45, ${Math.max(0.05, opacity * 0.7)})`)
|
||||
root.style.setProperty('--bg-elevated', `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.95)})`)
|
||||
root.style.setProperty('--bg-mica', `rgba(31, 31, 31, ${Math.max(0.05, opacity * 0.85)})`)
|
||||
root.style.setProperty('--control-fill', `rgba(51, 51, 51, ${Math.max(0.05, opacity * 0.85)})`)
|
||||
} else {
|
||||
root.style.setProperty('--bg-primary', `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.85)})`)
|
||||
root.style.setProperty('--bg-secondary', `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.7)})`)
|
||||
root.style.setProperty('--bg-elevated', `rgba(255, 255, 255, ${Math.max(0.05, opacity * 0.95)})`)
|
||||
root.style.setProperty('--bg-mica', `rgba(243, 243, 243, ${Math.max(0.05, opacity * 0.473)})`)
|
||||
root.style.setProperty('--control-fill', `rgba(249, 249, 249, ${Math.max(0.05, opacity * 0.85)})`)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadApiProfiles()
|
||||
await loadSettings()
|
||||
@@ -465,6 +522,7 @@ onMounted(async () => {
|
||||
if (cls) {
|
||||
document.body.classList.add(cls)
|
||||
}
|
||||
applyAcrylicStyle()
|
||||
window.electronAPI.onApiProfileSwitched(async profileName => {
|
||||
currentApiProfile.value = profileName
|
||||
await loadSettings()
|
||||
|
||||
@@ -26,7 +26,10 @@ export default {
|
||||
bootAnimationNotShown: 'Not Shown',
|
||||
checkpointing: 'Checkpointing',
|
||||
enabled: 'Enabled',
|
||||
disabled: 'Disabled'
|
||||
disabled: 'Disabled',
|
||||
acrylicEffect: 'Acrylic Effect',
|
||||
acrylicMin: 'Opaque',
|
||||
acrylicMax: 'Transparent'
|
||||
},
|
||||
theme: {
|
||||
xcode: 'Xcode',
|
||||
|
||||
@@ -26,7 +26,10 @@ export default {
|
||||
bootAnimationNotShown: '未显示',
|
||||
checkpointing: '检查点保存',
|
||||
enabled: '已启用',
|
||||
disabled: '已禁用'
|
||||
disabled: '已禁用',
|
||||
acrylicEffect: '亚克力效果',
|
||||
acrylicMin: '不透明',
|
||||
acrylicMax: '透明'
|
||||
},
|
||||
theme: {
|
||||
xcode: 'Xcode',
|
||||
|
||||
@@ -26,7 +26,10 @@ export default {
|
||||
bootAnimationNotShown: '未表示',
|
||||
checkpointing: 'チェックポイント保存',
|
||||
enabled: '有効',
|
||||
disabled: '無効'
|
||||
disabled: '無効',
|
||||
acrylicEffect: 'アクリリック効果',
|
||||
acrylicMin: '不透明',
|
||||
acrylicMax: '透明'
|
||||
},
|
||||
theme: {
|
||||
xcode: 'Xcode',
|
||||
|
||||
@@ -136,49 +136,6 @@
|
||||
--shadow-xl: 0 16px 32px rgba(0, 0, 0, 0.5), 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
// Solarized Dark Mode
|
||||
.solarized-dark {
|
||||
--bg-primary: #002b36;
|
||||
--bg-secondary: #073642;
|
||||
--bg-tertiary: #094856;
|
||||
--bg-elevated: #0a4a5c;
|
||||
--bg-mica: rgba(0, 43, 54, 0.9);
|
||||
|
||||
--text-primary: #839496;
|
||||
--text-secondary: #93a1a1;
|
||||
--text-tertiary: #586e75;
|
||||
--text-disabled: #3d5a64;
|
||||
|
||||
--accent: #268bd2;
|
||||
--accent-hover: #2d9cdb;
|
||||
--accent-pressed: #1a73c0;
|
||||
--accent-light: rgba(38, 139, 210, 0.15);
|
||||
--accent-text: #268bd2;
|
||||
|
||||
--border: #1d3a47;
|
||||
--border-light: #0d3a47;
|
||||
--border-strong: #2d5a6f;
|
||||
|
||||
--success: #2aa198;
|
||||
--success-bg: rgba(42, 161, 152, 0.15);
|
||||
--danger: #dc322f;
|
||||
--danger-bg: rgba(220, 50, 47, 0.15);
|
||||
--warning: #b58900;
|
||||
--warning-bg: rgba(181, 137, 0, 0.15);
|
||||
--info: #268bd2;
|
||||
--info-bg: rgba(38, 139, 210, 0.15);
|
||||
|
||||
--control-fill: #073642;
|
||||
--control-fill-hover: #0a4a5c;
|
||||
--control-fill-pressed: #0d5a70;
|
||||
--control-fill-disabled: #053845;
|
||||
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
--shadow: 0 4px 8px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5), 0 4px 8px rgba(0, 0, 0, 0.35);
|
||||
--shadow-xl: 0 16px 32px rgba(0, 0, 0, 0.6), 0 8px 16px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Animations
|
||||
// =============================================================================
|
||||
|
||||
@@ -6,9 +6,10 @@ import GeneralSettings from './GeneralSettings.vue';
|
||||
describe('GeneralSettings.vue', () => {
|
||||
const mockSettings = {
|
||||
language: 'zh-CN',
|
||||
theme: 'Xcode',
|
||||
uiTheme: 'Light',
|
||||
bootAnimationShown: true,
|
||||
checkpointing: { enabled: true },
|
||||
acrylicIntensity: 50,
|
||||
};
|
||||
|
||||
it('renders correctly with props', () => {
|
||||
@@ -60,11 +61,9 @@ describe('GeneralSettings.vue', () => {
|
||||
});
|
||||
|
||||
const themeOptions = wrapper.findAll('.form-select')[1].findAll('option');
|
||||
expect(themeOptions.length).toBe(4);
|
||||
expect(themeOptions[0].attributes('value')).toBe('Xcode');
|
||||
expect(themeOptions.length).toBe(2);
|
||||
expect(themeOptions[0].attributes('value')).toBe('Light');
|
||||
expect(themeOptions[1].attributes('value')).toBe('Dark');
|
||||
expect(themeOptions[2].attributes('value')).toBe('Light');
|
||||
expect(themeOptions[3].attributes('value')).toBe('Solarized Dark');
|
||||
});
|
||||
|
||||
it('reflects current settings in form controls', async () => {
|
||||
@@ -82,7 +81,7 @@ describe('GeneralSettings.vue', () => {
|
||||
await nextTick();
|
||||
const selectElements = wrapper.findAll('.form-select');
|
||||
expect(selectElements[0].element.value).toBe('zh-CN');
|
||||
expect(selectElements[1].element.value).toBe('Xcode');
|
||||
expect(selectElements[1].element.value).toBe('Light');
|
||||
expect(selectElements[2].element.value).toBe('true');
|
||||
expect(selectElements[3].element.value).toBe('true');
|
||||
});
|
||||
@@ -149,9 +148,9 @@ describe('GeneralSettings.vue', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAll('.form-row').length).toBe(2);
|
||||
expect(wrapper.findAll('.form-group').length).toBe(4);
|
||||
expect(wrapper.findAll('.form-label').length).toBe(4);
|
||||
expect(wrapper.findAll('.form-row').length).toBe(3);
|
||||
expect(wrapper.findAll('.form-group').length).toBe(5);
|
||||
expect(wrapper.findAll('.form-label').length).toBe(5);
|
||||
expect(wrapper.findAll('.form-select').length).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,11 +20,9 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ $t('general.theme') }}</label>
|
||||
<select class="form-select" v-model="localSettings.theme">
|
||||
<option value="Xcode">{{ $t('theme.xcode') }}</option>
|
||||
<option value="Dark">{{ $t('theme.dark') }}</option>
|
||||
<select class="form-select" v-model="localSettings.uiTheme">
|
||||
<option value="Light">{{ $t('theme.light') }}</option>
|
||||
<option value="Solarized Dark">{{ $t('theme.solarizedDark') }}</option>
|
||||
<option value="Dark">{{ $t('theme.dark') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,6 +48,23 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" v-if="supportsAcrylic">
|
||||
<div class="form-group form-group-full">
|
||||
<label class="form-label">{{ $t('general.acrylicEffect') }}: {{ localSettings.acrylicIntensity }}%</label>
|
||||
<div class="slider-container">
|
||||
<input
|
||||
type="range"
|
||||
class="form-slider"
|
||||
min="0"
|
||||
max="100"
|
||||
:value="localSettings.acrylicIntensity"
|
||||
@input="updateSliderStyle"
|
||||
ref="sliderRef"
|
||||
/>
|
||||
<span class="slider-hint">{{ $t('general.acrylicMin') }} — {{ $t('general.acrylicMax') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -66,12 +81,105 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:settings'])
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref, onMounted, watch, nextTick } from 'vue'
|
||||
|
||||
const localSettings = computed({
|
||||
get: () => props.settings,
|
||||
set: val => emit('update:settings', val),
|
||||
})
|
||||
|
||||
const supportsAcrylic = computed(() => {
|
||||
return typeof document !== 'undefined' && 'backdropFilter' in document.documentElement.style && props.settings.uiTheme !== 'Dark'
|
||||
})
|
||||
|
||||
const sliderRef = ref(null)
|
||||
|
||||
const updateSliderStyle = e => {
|
||||
const value = e.target.value
|
||||
const percent = ((value - 0) / (100 - 0)) * 100
|
||||
e.target.style.backgroundSize = `${percent}% 100%`
|
||||
emit('update:settings', { ...props.settings, acrylicIntensity: Number(value) })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (sliderRef.value) {
|
||||
const percent = ((props.settings.acrylicIntensity - 0) / (100 - 0)) * 100
|
||||
sliderRef.value.style.backgroundSize = `${percent}% 100%`
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.settings.uiTheme,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
if (sliderRef.value && supportsAcrylic.value) {
|
||||
const percent = ((props.settings.acrylicIntensity - 0) / (100 - 0)) * 100
|
||||
sliderRef.value.style.backgroundSize = `${percent}% 100%`
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.form-group-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.slider-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.form-slider {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: var(--border);
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-image: linear-gradient(var(--accent), var(--accent));
|
||||
background-size: var(--slider-progress, 50%) 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.form-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.form-slider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.form-slider::-webkit-slider-thumb:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.form-slider::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.slider-hint {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-tertiary);
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user