From 58cb1baca6ed464042b3108732ad63f8f00b294d Mon Sep 17 00:00:00 2001
From: Cmochance <3216202644@qq.com>
Date: Wed, 17 Jun 2026 23:41:09 +0800
Subject: [PATCH] =?UTF-8?q?feat(frontend):=20settings=20=E9=A1=B5=E6=8E=A5?=
=?UTF-8?q?=20/api/settings(13=20=E8=AE=BE=E7=BD=AE=20+=20=E4=B8=BB?=
=?UTF-8?q?=E9=A2=98/=E8=AF=AD=E8=A8=80=E6=8C=81=E4=B9=85=E5=8C=96)?=
=?UTF-8?q?=E2=80=94=20Stage=205?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
SettingsPage 从 stub 升级为综合设置页,逐字复刻旧 renderSettings 的读写:
- 13 项设置(autoApplyOnStart/restoreCodexOnExit/autoUnlockCodexPlugins/autoWakeCodexPet/
codexQuotaEnabled/codexNetworkAccess/exposeAllProviderModels/showGrayProviders/
mcpCredentialsPortableStore + webFetchBackend + proxyPort/adminPort + updateUrl)
- 默认值语义复刻(!==false / ===true);partial PUT 浅合并;乐观更新 + 失败回滚
- theme/language 双向持久化(改后 PUT /api/settings);App 启动从后端 hydrate(无 echo)
- 新增 api/settings.ts + stores/settings.ts;webFetchBackend 默认 auto(对齐后端 schema)
注:删旧 app.js 推迟 — 新前端尚有 4 个 stub 页(Dashboard/Guide/CodexSkin/Desktop)
+ settings 三复杂子 UI(residual scan/snapshot/diagnostic)未迁,需保留旧代码作移植参考。
Refs MOC-254
---
frontend/src/App.vue | 14 ++
frontend/src/api/settings.ts | 18 +++
frontend/src/pages/SettingsPage.vue | 204 ++++++++++++++++++++++++----
frontend/src/stores/settings.ts | 46 +++++++
4 files changed, 259 insertions(+), 23 deletions(-)
create mode 100644 frontend/src/api/settings.ts
create mode 100644 frontend/src/stores/settings.ts
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index f56c012e..cbe00f2c 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,5 +1,19 @@
diff --git a/frontend/src/api/settings.ts b/frontend/src/api/settings.ts
new file mode 100644
index 00000000..c4ee6a17
--- /dev/null
+++ b/frontend/src/api/settings.ts
@@ -0,0 +1,18 @@
+import { api } from './http'
+
+// /api/settings 是一个自由 key-value 对象(theme/language/各开关/端口/webFetchBackend/updateUrl…)。
+// GET 返裸 settings 对象;PUT 浅合并传入的 partial,返 {success, settings(合并后), webFetchSyncWarning?}。
+export type Settings = Record
+
+export const getSettings = () => api('GET', '/api/settings')
+
+export async function saveSettings(
+ partial: Settings,
+): Promise<{ settings: Settings; webFetchSyncWarning?: string }> {
+ const data = await api<{ settings?: Settings; webFetchSyncWarning?: string }>(
+ 'PUT',
+ '/api/settings',
+ partial,
+ )
+ return { settings: data.settings || {}, webFetchSyncWarning: data.webFetchSyncWarning }
+}
diff --git a/frontend/src/pages/SettingsPage.vue b/frontend/src/pages/SettingsPage.vue
index b63b4395..0e2db3e7 100644
--- a/frontend/src/pages/SettingsPage.vue
+++ b/frontend/src/pages/SettingsPage.vue
@@ -1,14 +1,65 @@
+
+
diff --git a/frontend/src/stores/settings.ts b/frontend/src/stores/settings.ts
new file mode 100644
index 00000000..4fd4d426
--- /dev/null
+++ b/frontend/src/stores/settings.ts
@@ -0,0 +1,46 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import * as settingsApi from '@/api/settings'
+import type { Settings } from '@/api/settings'
+
+export const useSettingsStore = defineStore('settings', () => {
+ const settings = ref({})
+ const loaded = ref(false)
+
+ async function load() {
+ settings.value = await settingsApi.getSettings()
+ loaded.value = true
+ return settings.value
+ }
+
+ // PUT partial(浅合并)→ 后端返合并后 settings;返回可选 webFetchSyncWarning 供 UI toast。
+ // 乐观更新(开关即时响应)+ 失败回滚(防 UI 与服务端不一致)。
+ async function save(partial: Settings): Promise {
+ const prev = { ...settings.value }
+ settings.value = { ...settings.value, ...partial }
+ try {
+ const { settings: merged, webFetchSyncWarning } = await settingsApi.saveSettings(partial)
+ settings.value = merged
+ return webFetchSyncWarning
+ } catch (e) {
+ settings.value = prev
+ throw e
+ }
+ }
+
+ // 带默认值的 typed getter(旧 app.js renderSettings 的 `!== false` / `=== true` 默认语义)
+ function bool(key: string, def: boolean): boolean {
+ const v = settings.value[key]
+ return typeof v === 'boolean' ? v : def
+ }
+ function str(key: string, def = ''): string {
+ const v = settings.value[key]
+ return typeof v === 'string' ? v : def
+ }
+ function num(key: string, def = 0): number {
+ const v = settings.value[key]
+ return typeof v === 'number' ? v : def
+ }
+
+ return { settings, loaded, load, save, bool, str, num }
+})