diff --git a/src-tauri/src/commands/system.rs b/src-tauri/src/commands/system.rs index fee45c4c..7ce4f39c 100644 --- a/src-tauri/src/commands/system.rs +++ b/src-tauri/src/commands/system.rs @@ -881,9 +881,12 @@ pub fn get_system_info() -> SystemInfo { /// 获取 Web 服务器实际监听端口 /// -/// 若服务器尚未完成绑定,最多等待 5 秒后返回(0 表示超时未启动)。 +/// 若服务器尚未完成绑定,最多等待 5 秒后返回(0 表示超时未启动或用户手动禁用)。 #[tauri::command] pub async fn get_web_server_port() -> u16 { + if !crate::web_server::is_web_server_enabled() { + return 0; + } let port = crate::web_server::get_actual_port(); if port != 0 { return port; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4a76d36e..68172fc3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -102,6 +102,11 @@ pub fn run() { .unwrap(); let settings_obj = settings.get("settings"); + let web_server_enabled = settings_obj + .and_then(|s| s.get("webServerEnabled")) + .and_then(|v| v.as_bool()) + .unwrap_or(true); + let allow_lan_access = settings_obj .and_then(|s| s.get("allowLanAccess")) .and_then(|v| v.as_bool()) @@ -114,19 +119,23 @@ pub fn run() { .filter(|&p| p > 0) .unwrap_or(web_server::DEFAULT_PORT); + web_server::set_web_server_enabled(web_server_enabled); + drop(settings); - tauri::async_runtime::spawn(async move { - web_server::start_web_server( - cfg_clone, - maa_clone, - app_handle, - ws_clone, - web_port, - allow_lan_access, - ) - .await; - }); + if web_server_enabled { + tauri::async_runtime::spawn(async move { + web_server::start_web_server( + cfg_clone, + maa_clone, + app_handle, + ws_clone, + web_port, + allow_lan_access, + ) + .await; + }); + } } // Windows 下移除系统标题栏(使用自定义标题栏) diff --git a/src-tauri/src/web_server.rs b/src-tauri/src/web_server.rs index 54666e28..ce6cf4cc 100644 --- a/src-tauri/src/web_server.rs +++ b/src-tauri/src/web_server.rs @@ -3,7 +3,7 @@ //! 基于 axum 提供 HTTP API,供浏览器客户端(本机/局域网/公网)访问。 //! 与 Tauri invoke IPC 并列,实现同一套后端状态的双通道访问。 -use std::sync::atomic::{AtomicU16, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU16, Ordering}; use std::sync::{Arc, OnceLock}; use axum::{ @@ -41,9 +41,22 @@ pub const DEFAULT_PORT: u16 = 12701; /// 端口搜索范围上限 const MAX_PORT_ATTEMPTS: u16 = 10; +/// 全局存储 Web 服务器是否已启用(由配置控制) +static WEB_SERVER_ENABLED: AtomicBool = AtomicBool::new(true); + /// 全局存储 Web 服务器实际监听端口(供前端查询) static ACTUAL_PORT: AtomicU16 = AtomicU16::new(0); +/// 获取 Web 服务器是否已启用 +pub fn is_web_server_enabled() -> bool { + WEB_SERVER_ENABLED.load(Ordering::Relaxed) +} + +/// 设置 Web 服务器启用状态 +pub fn set_web_server_enabled(value: bool) { + WEB_SERVER_ENABLED.store(value, Ordering::Relaxed); +} + /// 获取 Web 服务器实际监听端口(0 表示尚未启动或启动失败) pub fn get_actual_port() -> u16 { ACTUAL_PORT.load(Ordering::Relaxed) diff --git a/src/components/settings/DebugSection.tsx b/src/components/settings/DebugSection.tsx index 2e0ac203..35f99cd1 100644 --- a/src/components/settings/DebugSection.tsx +++ b/src/components/settings/DebugSection.tsx @@ -10,6 +10,7 @@ import { Globe, ExternalLink, Server, + EthernetPort, } from 'lucide-react'; import { useAppStore } from '@/stores/appStore'; @@ -33,6 +34,8 @@ export function DebugSection() { setTcpCompatMode, allowLanAccess, setAllowLanAccess, + webServerEnabled, + setWebServerEnabled, webServerPort: configuredPort, setWebServerPort: setConfiguredPort, } = useAppStore(); @@ -168,6 +171,16 @@ export function DebugSection() { [setAllowLanAccess], ); + const handleWebServerToggle = useCallback( + (v: boolean) => { + setWebServerEnabled(v); + if (isTauri()) { + setShowRestartPrompt(true); + } + }, + [setWebServerEnabled], + ); + const handlePortBlur = useCallback(() => { const parsed = parseInt(portInput, 10); if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) { @@ -366,10 +379,22 @@ export function DebugSection() { setTcpCompatMode(v)} /> + {/* 启用 Web 服务器 */} +
+
+ +
+ {t('debug.webServerEnabled')} +

{t('debug.webServerEnabledHint')}

+
+
+ +
+ {/* Web 服务器端口 */}
- +
{t('debug.webServerPort')}

{t('debug.webServerPortHint')}

diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index f792556b..7edc9d8b 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -541,6 +541,9 @@ export default { tcpCompatMode: 'Communication Compat Mode', tcpCompatModeHint: 'Try enabling this if the app crashes immediately after starting tasks. Only use in this case, as it may reduce performance', + webServerEnabled: 'Enable Web Server', + webServerEnabledHint: + 'When disabled, the built-in web server will not start (restart required)', webServerPort: 'Web Server Port', webServerPortHint: 'Custom Web server listening port (default 12701, restart required)', allowLanAccess: 'Allow LAN Access', diff --git a/src/i18n/locales/ja-JP.ts b/src/i18n/locales/ja-JP.ts index 2d90031e..b088ab45 100644 --- a/src/i18n/locales/ja-JP.ts +++ b/src/i18n/locales/ja-JP.ts @@ -536,6 +536,9 @@ export default { tcpCompatMode: '通信互換モード', tcpCompatModeHint: 'タスク開始後にアプリがすぐにクラッシュする場合は有効にしてください。この場合のみ使用し、それ以外は性能に影響します', + webServerEnabled: 'Web サーバーを有効化', + webServerEnabledHint: + '無効にすると内蔵 Web サーバーは起動しません(再起動後に反映)', webServerPort: 'Web サーバーポート', webServerPortHint: 'Web サーバーのリッスンポートをカスタマイズ(デフォルト 12701、再起動後に反映)', diff --git a/src/i18n/locales/ko-KR.ts b/src/i18n/locales/ko-KR.ts index bad8eb42..574c45bf 100644 --- a/src/i18n/locales/ko-KR.ts +++ b/src/i18n/locales/ko-KR.ts @@ -533,6 +533,9 @@ export default { tcpCompatMode: '통신 호환 모드', tcpCompatModeHint: '작업 시작 후 앱이 즉시 충돌하면 활성화해 보세요. 이 경우에만 사용하세요, 성능에 영향을 줄 수 있습니다', + webServerEnabled: 'Web 서버 활성화', + webServerEnabledHint: + '비활성화하면 내장 Web 서버가 시작되지 않습니다 (재시작 필요)', webServerPort: 'Web 서버 포트', webServerPortHint: 'Web 서버 수신 포트를 사용자 지정합니다 (기본값 12701, 재시작 필요)', allowLanAccess: 'LAN 접근 허용', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index fd1b9c30..511b0680 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -527,6 +527,8 @@ export default { saveDrawHint: '保存识别和操作的调试图像到日志目录(重启软件后自动关闭)', tcpCompatMode: '通信兼容模式', tcpCompatModeHint: '若启动任务后软件立即闪退,可尝试开启。仅限此情况使用,否则会影响运行效率', + webServerEnabled: '启用 Web 服务', + webServerEnabledHint: '关闭后内置 Web 服务器将不会启动(重启生效)', webServerPort: 'Web 服务端口', webServerPortHint: '自定义 Web 服务器监听端口(默认 12701,重启生效)', allowLanAccess: '允许局域网访问', diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index 27d45a84..dfefa4d1 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -523,6 +523,8 @@ export default { saveDrawHint: '儲存識別和操作的除錯圖像到日誌目錄(重啟軟體後自動關閉)', tcpCompatMode: '通訊相容模式', tcpCompatModeHint: '若啟動任務後軟體立即閃退,可嘗試開啟。僅限此情況使用,否則會影響運行效率', + webServerEnabled: '啟用 Web 服務', + webServerEnabledHint: '關閉後內建 Web 伺服器將不會啟動(重啟生效)', webServerPort: 'Web 服務連接埠', webServerPortHint: '自訂 Web 伺服器監聽連接埠(預設 12701,重啟生效)', allowLanAccess: '允許區域網路存取', diff --git a/src/stores/appStore.ts b/src/stores/appStore.ts index eb3520cd..2c014e15 100644 --- a/src/stores/appStore.ts +++ b/src/stores/appStore.ts @@ -1,6 +1,6 @@ -import i18n, { getInterfaceLangKey, setLanguage as setI18nLanguage } from '@/i18n'; -import { saveConfig } from '@/services/configService'; -import { maaService } from '@/services/maaService'; +import i18n, {getInterfaceLangKey, setLanguage as setI18nLanguage} from '@/i18n'; +import {saveConfig} from '@/services/configService'; +import {maaService} from '@/services/maaService'; import { type AccentColor, applyTheme, @@ -10,15 +10,15 @@ import { resolveThemeMode, unregisterCustomAccent, } from '@/themes'; -import type { MxuConfig, RecentlyClosedInstance, LegacyActionConfig } from '@/types/config'; +import type {LegacyActionConfig, MxuConfig, RecentlyClosedInstance} from '@/types/config'; import { - DEFAULT_MAX_LOGS_PER_INSTANCE, clampAddTaskPanelHeight, + DEFAULT_MAX_LOGS_PER_INSTANCE, defaultAddTaskPanelHeight, defaultMirrorChyanSettings, - normalizeAddTaskPanelHeight, defaultScreenshotFrameRate, defaultWindowSize, + normalizeAddTaskPanelHeight, } from '@/types/config'; import type { ActionConfig, @@ -28,37 +28,37 @@ import type { ProjectInterface, SelectedTask, } from '@/types/interface'; -import type { ConnectionStatus, TaskStatus } from '@/types/maa'; -import { getMxuSpecialTask, isMxuSpecialTask, MXU_SPECIAL_TASKS } from '@/types/specialTasks'; -import { decryptCdk, encryptCdk } from '@/utils/cdkCrypto'; -import { loggers } from '@/utils/logger'; -import { findSwitchCase } from '@/utils/optionHelpers'; -import { create } from 'zustand'; -import { subscribeWithSelector } from 'zustand/middleware'; - -import { logToStdout, pushLogToBackend, clearLogsOnBackend } from '@/utils/logStdout'; +import type {ConnectionStatus, TaskStatus} from '@/types/maa'; +import {getMxuSpecialTask, isMxuSpecialTask, MXU_SPECIAL_TASKS} from '@/types/specialTasks'; +import {decryptCdk, encryptCdk} from '@/utils/cdkCrypto'; +import {loggers} from '@/utils/logger'; +import {findSwitchCase} from '@/utils/optionHelpers'; +import {create} from 'zustand'; +import {subscribeWithSelector} from 'zustand/middleware'; + +import {clearLogsOnBackend, logToStdout, pushLogToBackend} from '@/utils/logStdout'; import { - loadWebUIAppearance, - patchWebUIAppearance, cacheBackendAppearance, + cacheBackendLayout, getBackendAppearance, + getBackendLayout, + loadWebUIAppearance, loadWebUILayout, + patchWebUIAppearance, patchWebUILayout, - cacheBackendLayout, - getBackendLayout, } from '@/services/appearanceStorage'; -import { isTauri } from '@/utils/paths'; +import {isTauri} from '@/utils/paths'; import { - generateId, - initializeAllOptionValues, convertPresetOptionValue, + generateId, getCurrentControllerAndResource, + initializeAllOptionValues, isTaskCompatible, sanitizeOptionValues, } from './helpers'; -import { persistRuntimeLogs } from '@/utils/runtimeLogPersistence'; +import {persistRuntimeLogs} from '@/utils/runtimeLogPersistence'; // 从独立模块导入类型和辅助函数 -import type { AppState, LogEntry, TaskRunStatus } from './types'; +import type {AppState, LogEntry, TaskRunStatus} from './types'; /** 向后兼容:将旧版单个 preAction 迁移为 preActions 数组 */ function migratePreActions(inst: { @@ -1206,6 +1206,7 @@ export const useAppStore = create()( devMode: config.settings.devMode ?? false, tcpCompatMode: config.settings.tcpCompatMode ?? false, allowLanAccess: config.settings.allowLanAccess ?? false, + webServerEnabled: config.settings.webServerEnabled ?? true, webServerPort: config.settings.webServerPort ?? 12701, autoStartInstanceId: config.settings.autoStartInstanceId, autoRunOnLaunch: config.settings.autoRunOnLaunch ?? false, @@ -1668,6 +1669,10 @@ export const useAppStore = create()( allowLanAccess: false, setAllowLanAccess: (enabled) => set({ allowLanAccess: enabled }), + // Web 服务器启用开关(默认 true,需重启生效) + webServerEnabled: true, + setWebServerEnabled: (enabled) => set({webServerEnabled: enabled}), + // Web 服务器端口(默认 12701,需重启生效) webServerPort: 12701, setWebServerPort: (port) => set({ webServerPort: port }), @@ -2084,6 +2089,7 @@ function generateConfig(): MxuConfig { devMode: state.devMode, tcpCompatMode: state.tcpCompatMode, allowLanAccess: state.allowLanAccess, + webServerEnabled: state.webServerEnabled, webServerPort: state.webServerPort, autoStartInstanceId: state.autoStartInstanceId, autoRunOnLaunch: state.autoRunOnLaunch, @@ -2156,6 +2162,7 @@ useAppStore.subscribe( devMode: state.devMode, tcpCompatMode: state.tcpCompatMode, allowLanAccess: state.allowLanAccess, + webServerEnabled: state.webServerEnabled, webServerPort: state.webServerPort, autoStartInstanceId: state.autoStartInstanceId, autoRunOnLaunch: state.autoRunOnLaunch, diff --git a/src/stores/types.ts b/src/stores/types.ts index 2448fc4a..6559cde4 100644 --- a/src/stores/types.ts +++ b/src/stores/types.ts @@ -369,6 +369,10 @@ export interface AppState { tcpCompatMode: boolean; setTcpCompatMode: (enabled: boolean) => void; + /** Web 服务器是否启用(默认 true,重启生效) */ + webServerEnabled: boolean; + setWebServerEnabled: (enabled: boolean) => void; + /** Web UI 允许局域网访问(绑定 0.0.0.0,重启生效) */ allowLanAccess: boolean; setAllowLanAccess: (enabled: boolean) => void; diff --git a/src/types/config.ts b/src/types/config.ts index a44df316..50db8827 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,7 +1,7 @@ // MXU 配置文件结构 (mxu.json) -import type { OptionValue, ActionConfig } from './interface'; -import type { AccentColor, CustomAccent } from '@/themes/types'; +import type {ActionConfig, OptionValue} from './interface'; +import type {AccentColor, CustomAccent} from '@/themes/types'; export const DEFAULT_MAX_LOGS_PER_INSTANCE = 500; @@ -153,6 +153,7 @@ export interface AppSettings { onboardingCompleted?: boolean; // 新用户引导是否已完成 hotkeys?: HotkeySettings; // 快捷键设置 tcpCompatMode?: boolean; // 通信兼容模式,强制使用 TCP 而非 IPC + webServerEnabled?: boolean; // Web 服务器是否启用(默认 true,重启生效) allowLanAccess?: boolean; // Web UI 允许局域网访问(绑定 0.0.0.0,重启生效) webServerPort?: number; // Web 服务器监听端口(默认 12701,重启生效) minimizeToTray?: boolean; // 关闭时最小化到托盘(默认 false)