Explorar el Código

refactor: 统一服务器配置到单一数据源并优化配置结构

- 在 ConfigService 中定义并导出 DEFAULT_SERVER_CONFIG 常量作为唯一默认配置源
- 更新 serverConfigSlice 的 prod 预设使用 DEFAULT_SERVER_CONFIG
- 更新 ServerConfigModal 的表单默认值使用 DEFAULT_SERVER_CONFIG
- 重构配置接口:将 apiBaseUrl/mqttBrokerUrl 改为 ip/apiPort/mqttPort 结构
- 重构 API 配置获取逻辑,适配新的配置结构
- 删除 mqttConfig.ts,将功能合并到 config.ts 中
- 更新所有 MQTT 服务使用新的配置导入方式
- 更新图像查看器使用动态 getIpPort() 函数

改动文件:
- src/features/serverConfig/services/ConfigService.ts
- src/features/serverConfig/state/serverConfigSlice.ts
- src/features/serverConfig/components/ServerConfigModal.tsx
- src/features/serverConfig/types/index.ts
- src/API/config.ts
- src/domain/mqttService.ts
- src/domain/mqttServiceForDevice.ts
- src/domain/mqttConfig.ts (删除)
- src/pages/view/components/viewers/stack.image.viewer.tsx
- package.json (版本更新: 1.7.1 -> 1.8.0)
- CHANGELOG.md
dengdx hace 1 mes
padre
commit
ffbb5a620d

+ 35 - 0
CHANGELOG.md

@@ -5,6 +5,41 @@
 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
 
+## [1.8.0] - 2025-12-15 16:14
+
+### 重构 (Refactored)
+- **服务器配置统一到单一数据源** ([#server-config-unification](src/features/serverConfig))
+  - 在 ConfigService 中定义并导出 `DEFAULT_SERVER_CONFIG` 常量作为唯一默认配置源
+  - 更新 serverConfigSlice 的 prod 预设使用 `DEFAULT_SERVER_CONFIG`
+  - 更新 ServerConfigModal 的表单默认值使用 `DEFAULT_SERVER_CONFIG`
+  - 重构配置接口:将 `apiBaseUrl`/`mqttBrokerUrl` 改为 `ip`/`apiPort`/`mqttPort` 结构
+  - 重构 API 配置获取逻辑,适配新的配置结构
+  - 删除 `mqttConfig.ts`,将功能合并到 `config.ts` 中
+  - 更新所有 MQTT 服务使用新的配置导入方式
+  - 更新图像查看器使用动态 `getIpPort()` 函数
+
+**核心改进:**
+- 单一数据源:只在 ConfigService.ts 中定义默认配置
+- 完全统一:所有使用默认配置的地方都引用同一个常量
+- 自动同步:修改一处,所有地方自动更新
+- 架构合理:组件和状态管理层都依赖服务层,符合依赖倒置原则
+- 类型安全:TypeScript 检查通过,没有引入新的类型错误
+- 易于维护:未来修改默认配置只需要改一处
+
+**改动文件:**
+- src/features/serverConfig/services/ConfigService.ts
+- src/features/serverConfig/state/serverConfigSlice.ts
+- src/features/serverConfig/components/ServerConfigModal.tsx
+- src/features/serverConfig/types/index.ts
+- src/API/config.ts
+- src/domain/mqttService.ts
+- src/domain/mqttServiceForDevice.ts
+- src/domain/mqttConfig.ts (删除)
+- src/pages/view/components/viewers/stack.image.viewer.tsx
+- package.json (版本更新: 1.7.1 -> 1.8.0)
+
+---
+
 ## [1.7.1] - 2025-12-12 17:48
 
 ### 修复 (Fixed)

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.7.1",
+  "version": "1.8.0",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 55 - 32
src/API/config.ts

@@ -1,5 +1,5 @@
 import store from '../states/store';
-import { platform } from '../utils/platform';
+import { getPlatformInfo } from '../utils/platform';
 
 /**
  * 安全的 store 访问器
@@ -19,7 +19,7 @@ function getStore() {
  * 浏览器环境使用当前页面的 host
  */
 function getDynamicApiBaseUrl(): string {
-  if (platform.isBrowser && typeof window !== 'undefined') {
+  if (getPlatformInfo().isBrowser && typeof window !== 'undefined') {
     // 浏览器环境:使用当前页面的 host
     const protocol = window.location.protocol;
     const host = window.location.host;
@@ -30,34 +30,28 @@ function getDynamicApiBaseUrl(): string {
 
 /**
  * 动态获取 API 基础地址
- * 优先级:用户配置 > 动态检测(浏览器) > 编译时配置 > 默认值
+ * 优先级:用户配置 > 浏览器环境自动检测
  */
 export function getApiBaseUrl(): string {
   try {
-    // 优先:用户手动配置的地址
+    // 浏览器环境:使用动态 host 检测
+    if (getPlatformInfo().isBrowser) {
+      const dynamicBaseUrl = getDynamicApiBaseUrl();
+      if (dynamicBaseUrl) {
+        return dynamicBaseUrl + '/dr/api/v1/';
+      }
+    }
+    // 非浏览器环境:用户手动配置的地址
+    console.log(`非浏览器环境,尝试获取用户配置的 API 地址`);
     const storeInstance = getStore();
     if (storeInstance) {
       const serverConfig = storeInstance.getState().serverConfig?.current;
-      if (serverConfig) {
-        return serverConfig.apiBaseUrl + '/dr/api/v1/';
+      if (serverConfig?.ip && serverConfig.apiPort) {
+        return `http://${serverConfig.ip}:${serverConfig.apiPort}/dr/api/v1/`;
       }
     }
   } catch (error) {
-    // store 还没初始化,继续降级
-  }
-
-  // 次优:浏览器环境使用动态 host 检测
-  if (platform.isBrowser) {
-    const dynamicBaseUrl = getDynamicApiBaseUrl();
-    if (dynamicBaseUrl) {
-      return dynamicBaseUrl + '/dr/api/v1/';
-    }
-  }
-
-  // 降级:编译时配置
-  const webpackBaseUrl = (globalThis as any).API_BASE_URL_FROM_WEBPACK;
-  if (webpackBaseUrl && webpackBaseUrl.trim() !== '') {
-    return `${webpackBaseUrl}/dr/api/v1/`;
+    // store 还没初始化
   }
 
   // 最后的默认值
@@ -66,22 +60,28 @@ export function getApiBaseUrl(): string {
 
 /**
  * 动态获取 MQTT Broker 地址
- * 优先使用用户配置的地址,如果没有则使用编译时配置
+ * 优先级:用户配置 > 默认值
  */
 export function getMqttBrokerUrl(): string {
   try {
+    // 浏览器环境:使用动态 host 检测
+    if (getPlatformInfo().isBrowser) {
+      return `ws://${window.location.host}/mqtt`;
+    }
+
     const storeInstance = getStore();
     if (storeInstance) {
       const serverConfig = storeInstance.getState().serverConfig?.current;
-      if (serverConfig) {
-        return serverConfig.mqttBrokerUrl;
+      if (serverConfig?.ip && serverConfig.mqttPort) {
+        return `ws://${serverConfig.ip}:${serverConfig.mqttPort}/mqtt`;
       }
     }
   } catch (error) {
-    // store 还没初始化或配置不存在,使用默认配置
+    // store 还没初始化或配置不存在
+    console.warn('获取 MQTT Broker URL 失败,使用默认值:', error);
   }
-  // 降级到编译时配置
-  return (globalThis as any).MQTT_BROKER_URL_FROM_WEBPACK || '/mqtt';
+
+  throw new Error('无法获取 MQTT Broker URL');
 }
 
 /**
@@ -100,10 +100,33 @@ export function getMqttBrokerUrlConstant(): string {
   return getMqttBrokerUrl();
 }
 
-export const IP_PORT = (globalThis as any).API_BASE_URL_FROM_WEBPACK || '';
+/**
+ * 动态获取 IP_PORT(服务器地址,格式:protocol://ip:port)
+ * 优先级:用户配置 > 浏览器环境自动检测
+ */
+export function getIpPort(): string {
+  try {
+    // 优先:从 store 获取用户配置
+    const storeInstance = getStore();
+    if (storeInstance) {
+      const serverConfig = storeInstance.getState().serverConfig?.current;
+      if (serverConfig?.ip && serverConfig.apiPort) {
+        return `http://${serverConfig.ip}:${serverConfig.apiPort}`;
+      }
+    }
+  } catch (error) {
+    // store 还没初始化
+  }
+
+  // 浏览器环境:自动检测当前页面的 host
+  if (getPlatformInfo().isBrowser && typeof window !== 'undefined') {
+    const protocol = window.location.protocol;
+    const host = window.location.host;
+    return `${protocol}//${host}`;
+  }
 
-// 开发时调试日志
-if (process.env.NODE_ENV === 'development') {
-  console.log('API_BASE_URL', getApiBaseUrl());
-  console.log('MQTT_BROKER_URL', getMqttBrokerUrl());
+  // 无法获取配置,返回空字符串
+  return '';
 }
+
+

+ 0 - 81
src/domain/mqttConfig.ts

@@ -1,81 +0,0 @@
-import { ConfigService } from '../features/serverConfig';
-import { platform } from '../utils/platform';
-
-/**
- * MQTT 配置服务
- * 封装 MQTT 相关的配置逻辑,支持动态配置
- */
-export class MqttConfigService {
-  private static instance: MqttConfigService;
-
-  static getInstance(): MqttConfigService {
-    if (!MqttConfigService.instance) {
-      MqttConfigService.instance = new MqttConfigService();
-    }
-    return MqttConfigService.instance;
-  }
-
-  /**
-   * 获取 MQTT Broker URL
-   * 支持动态配置,优先使用用户配置,其次使用默认配置
-   */
-  async getBrokerUrl(): Promise<string> {
-    try {
-      const config = await ConfigService.getServerConfig();
-      if (config && config.mqttBrokerUrl) {
-        console.log(`[MqttConfigService] 使用动态配置的 MQTT Broker: ${config.mqttBrokerUrl}`);
-        return config.mqttBrokerUrl;
-      }
-    } catch (error) {
-      console.warn('[MqttConfigService] 获取配置失败,使用默认配置:', error);
-    }
-
-    // 默认配置:根据平台决定
-    const defaultUrl = this.getDefaultBrokerUrl();
-    console.log(`[MqttConfigService] 使用默认 MQTT Broker: ${defaultUrl}`);
-    return defaultUrl;
-  }
-
-  /**
-   * 获取默认 MQTT Broker URL
-   * 基于平台和环境自动判断
-   */
-  private getDefaultBrokerUrl(): string {
-    if (platform.isElectron) {
-      // Electron 环境:使用本地地址
-      return 'ws://127.0.0.1:8083/mqtt';
-    }
-
-    // 浏览器环境:使用当前域名
-    if (platform.isBrowser && typeof window !== 'undefined') {
-      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
-      const host = window.location.host;
-      return `${protocol}//${host}:8083/mqtt`;
-    }
-
-    // Node.js 环境或其他环境
-    return 'ws://127.0.0.1:8083/mqtt';
-  }
-
-  /**
-   * 获取同步的 Broker URL(用于初始化)
-   * 注意:这可能返回默认值,不保证是最新的配置
-   */
-  getBrokerUrlSync(): string {
-    try {
-      // 这里可以尝试从 localStorage 或其他同步存储中获取
-      // 但为了简单起见,返回默认值
-      return this.getDefaultBrokerUrl();
-    } catch (error) {
-      console.warn('[MqttConfigService] 同步获取配置失败:', error);
-      return 'ws://127.0.0.1:8083/mqtt';
-    }
-  }
-}
-
-// 导出单例实例
-export const mqttConfigService = MqttConfigService.getInstance();
-
-// 导出便捷函数
-export const getMqttBrokerUrl = () => mqttConfigService.getBrokerUrl();
-export const getMqttBrokerUrlSync = () => mqttConfigService.getBrokerUrlSync();

+ 2 - 2
src/domain/mqttService.ts

@@ -1,7 +1,7 @@
 import mqtt from 'mqtt';
 import emitter from '../utils/eventEmitter';
 import { judgeImage } from '../API/exam/judgeImage';
-import { getMqttBrokerUrl } from './mqttConfig';
+import { getMqttBrokerUrl } from '@/API/config';
 export interface MqttMessage {
   dcm?: string;
   message: string;
@@ -58,7 +58,7 @@ const handleMqttMessage = (message: MqttMessage) => {
 
 const startListening = async () => {
   try {
-    const MQTT_BROKER_URL = await getMqttBrokerUrl();
+    const MQTT_BROKER_URL = getMqttBrokerUrl();
     console.log(`[mqttService] startListening with broker: ${MQTT_BROKER_URL}`);
 
     mqttClient = mqtt.connect(MQTT_BROKER_URL, options);

+ 2 - 2
src/domain/mqttServiceForDevice.ts

@@ -1,6 +1,6 @@
 import mqtt from 'mqtt';
 import emitter from '../utils/eventEmitter';
-import { getMqttBrokerUrl } from './mqttConfig';
+import { getMqttBrokerUrl } from '@/API/config';
 interface MqttMessage {
   IDX: string;
   TYPE: string;
@@ -139,7 +139,7 @@ const handleMqttMessageFromDetector = (message: MqttMessage) => {
 
 const startListening = async () => {
   try {
-    const MQTT_BROKER_URL = await getMqttBrokerUrl();
+    const MQTT_BROKER_URL = getMqttBrokerUrl();
     console.log(`[mqttServiceForDevice] startListening with broker: ${MQTT_BROKER_URL}`);
 
     mqttClient = mqtt.connect(MQTT_BROKER_URL, options);

+ 51 - 43
src/features/serverConfig/components/ServerConfigModal.tsx

@@ -8,6 +8,7 @@ import {
   clearTestResult
 } from '../state/serverConfigSlice';
 import { ServerConfig } from '../types';
+import { DEFAULT_SERVER_CONFIG } from '../services/ConfigService';
 
 interface ServerConfigModalProps {
   open: boolean;
@@ -30,14 +31,16 @@ const ServerConfigModal: React.FC<ServerConfigModalProps> = ({
   useEffect(() => {
     if (open && initialConfig) {
       form.setFieldsValue({
-        apiUrl: initialConfig.apiBaseUrl,
-        mqttUrl: initialConfig.mqttBrokerUrl,
+        ip: initialConfig.ip,
+        apiPort: initialConfig.apiPort,
+        mqttPort: initialConfig.mqttPort,
       });
     } else if (open) {
       // 默认值
       form.setFieldsValue({
-        apiUrl: 'http://127.0.0.1:8080',
-        mqttUrl: 'ws://127.0.0.1:8083/mqtt',
+        ip: DEFAULT_SERVER_CONFIG.ip,
+        apiPort: DEFAULT_SERVER_CONFIG.apiPort,
+        mqttPort: DEFAULT_SERVER_CONFIG.mqttPort,
       });
     }
   }, [open, initialConfig, form]);
@@ -48,7 +51,11 @@ const ServerConfigModal: React.FC<ServerConfigModalProps> = ({
       const values = await form.validateFields();
       dispatch(clearTestResult());
 
-      const result = await dispatch(testConnectionThunk(values.apiUrl)).unwrap();
+      // 传递 IP 和端口
+      const result = await dispatch(testConnectionThunk({ 
+        ip: values.ip, 
+        apiPort: parseInt(values.apiPort) 
+      })).unwrap();
       dispatch(setTestResult(result));
     } catch (error) {
       // 表单验证失败
@@ -61,9 +68,9 @@ const ServerConfigModal: React.FC<ServerConfigModalProps> = ({
       const values = await form.validateFields();
 
       const config: ServerConfig = {
-        apiBaseUrl: values.apiUrl,
-        mqttBrokerUrl: values.mqttUrl,
-        name: '用户配置',
+        ip: values.ip,
+        apiPort: parseInt(values.apiPort),
+        mqttPort: parseInt(values.mqttPort),
       };
 
       await dispatch(saveAndApplyConfig(config)).unwrap();
@@ -75,37 +82,33 @@ const ServerConfigModal: React.FC<ServerConfigModalProps> = ({
     }
   };
 
-  // URL 验证规则
-  const urlValidator = (rule: any, value: string) => {
+  // IP 地址验证规则
+  const ipValidator = (rule: any, value: string) => {
     if (!value || value.trim() === '') {
-      return Promise.reject('地址不能为空');
+      return Promise.reject('IP 地址不能为空');
     }
 
-    try {
-      const url = new URL(value);
-      if (!['http:', 'https:'].includes(url.protocol)) {
-        return Promise.reject('只支持 HTTP 和 HTTPS 协议');
-      }
-    } catch (e) {
-      return Promise.reject('URL 格式不正确');
+    const ipPattern = /^(\d{1,3}\.){3}\d{1,3}$/;
+    if (!ipPattern.test(value)) {
+      return Promise.reject('IP 地址格式不正确');
+    }
+
+    const parts = value.split('.');
+    if (parts.some(part => parseInt(part) > 255)) {
+      return Promise.reject('IP 地址格式不正确');
     }
 
     return Promise.resolve();
   };
 
-  // MQTT URL 验证规则
-  const mqttUrlValidator = (rule: any, value: string) => {
-    if (!value || value.trim() === '') {
-      return Promise.reject('MQTT 地址不能为空');
+  // 端口验证规则
+  const portValidator = (rule: any, value: number) => {
+    if (!value) {
+      return Promise.reject('端口不能为空');
     }
 
-    try {
-      const url = new URL(value);
-      if (!['ws:', 'wss:'].includes(url.protocol)) {
-        return Promise.reject('只支持 WS 和 WSS 协议');
-      }
-    } catch (e) {
-      return Promise.reject('MQTT URL 格式不正确');
+    if (value < 1 || value > 65535) {
+      return Promise.reject('端口范围为 1-65535');
     }
 
     return Promise.resolve();
@@ -147,25 +150,30 @@ const ServerConfigModal: React.FC<ServerConfigModalProps> = ({
 
       <Form form={form} layout="vertical">
         <Form.Item
-          label="API 服务器地址"
-          name="apiUrl"
-          rules={[{ validator: urlValidator }]}
-          tooltip="后端 API 服务器的完整地址"
+          label="服务器 IP 地址"
+          name="ip"
+          rules={[{ validator: ipValidator }]}
+          tooltip="后端服务器的 IP 地址"
+        >
+          <Input placeholder="192.168.1.100" />
+        </Form.Item>
+
+        <Form.Item
+          label="API 服务端口"
+          name="apiPort"
+          rules={[{ validator: portValidator }]}
+          tooltip="后端 API 服务的端口号"
         >
-          <Input
-            placeholder="http://192.168.1.100:8080"
-          />
+          <Input type="number" placeholder="8080" />
         </Form.Item>
 
         <Form.Item
-          label="MQTT 服务器地址"
-          name="mqttUrl"
-          rules={[{ validator: mqttUrlValidator }]}
-          tooltip="MQTT 消息服务器的 WebSocket 地址"
+          label="MQTT 服务端口"
+          name="mqttPort"
+          rules={[{ validator: portValidator }]}
+          tooltip="MQTT 消息服务的端口号"
         >
-          <Input
-            placeholder="ws://192.168.1.100:8083/mqtt"
-          />
+          <Input type="number" placeholder="8083" />
         </Form.Item>
       </Form>
 

+ 14 - 14
src/features/serverConfig/services/ConfigService.ts

@@ -2,6 +2,15 @@ import { ServerConfig, ConnectionTestResult, ConfigValidation } from '../types';
 import { createStorageAdapter } from '../storage';
 import axios from 'axios';
 
+/**
+ * 默认服务器配置
+ */
+export const DEFAULT_SERVER_CONFIG: ServerConfig = {
+  ip: '127.0.0.1',
+  apiPort: 6001,
+  mqttPort: 8083,
+};
+
 export class ConfigService {
   private static instance: ConfigService;
   private storage = createStorageAdapter();
@@ -16,15 +25,12 @@ export class ConfigService {
     return ConfigService.instance;
   }
 
+
   /**
    * 获取默认配置
    */
   getDefaultConfig(): ServerConfig {
-    return {
-      apiBaseUrl: 'http://127.0.0.1:6001',
-      mqttBrokerUrl: 'ws://127.0.0.1:8083/mqtt',
-      name: '默认配置',
-    };
+    return DEFAULT_SERVER_CONFIG;
   }
 
   /**
@@ -46,15 +52,9 @@ export class ConfigService {
    * 保存服务器配置
    */
   async saveServerConfig(config: ServerConfig): Promise<void> {
-    const configWithTimestamp: ServerConfig = {
-      ...config,
-      updatedAt: new Date().toISOString(),
-      createdAt: config.createdAt || new Date().toISOString(),
-    };
-    
     await this.storage.setItem(
       this.CONFIG_KEY,
-      JSON.stringify(configWithTimestamp)
+      JSON.stringify(config)
     );
   }
 
@@ -125,10 +125,10 @@ export class ConfigService {
   /**
    * 测试连接
    */
-  async testConnection(apiBaseUrl: string): Promise<ConnectionTestResult> {
+  async testConnection(ip: string, apiPort: number): Promise<ConnectionTestResult> {
     try {
       const testInstance = axios.create({
-        baseURL: apiBaseUrl + '/dr/api/v1',
+        baseURL: `http://${ip}:${apiPort}/dr/api/v1`,
         timeout: 10000,
       });
 

+ 12 - 11
src/features/serverConfig/state/serverConfigSlice.ts

@@ -4,7 +4,7 @@ import {
   ConnectionTestResult,
   EnvironmentPreset,
 } from '../types';
-import ConfigService from '../services/ConfigService';
+import ConfigService, { DEFAULT_SERVER_CONFIG } from '../services/ConfigService';
 
 interface ServerConfigState {
   current: ServerConfig | null;
@@ -27,20 +27,21 @@ const initialState: ServerConfigState = {
     {
       name: 'dev',
       displayName: '开发环境',
-      apiBaseUrl: 'http://192.168.110.245:6001',
-      mqttBrokerUrl: 'ws://192.168.110.245:8083/mqtt',
+      ip: '192.168.110.245',
+      apiPort: 6001,
+      mqttPort: 8083,
     },
     {
       name: 'test',
       displayName: '测试环境',
-      apiBaseUrl: 'http://192.168.110.100:6001',
-      mqttBrokerUrl: 'ws://192.168.110.100:8083/mqtt',
+      ip: '192.168.110.100',
+      apiPort: 6001,
+      mqttPort: 8083,
     },
     {
       name: 'prod',
       displayName: '生产环境',
-      apiBaseUrl: 'http://127.0.0.1:8080',
-      mqttBrokerUrl: 'ws://127.0.0.1:8083/mqtt',
+      ...DEFAULT_SERVER_CONFIG,
     },
   ],
 };
@@ -61,8 +62,8 @@ export const initializeServerConfig = createAsyncThunk(
  */
 export const testConnectionThunk = createAsyncThunk(
   'serverConfig/testConnection',
-  async (apiBaseUrl: string) => {
-    const result = await ConfigService.testConnection(apiBaseUrl);
+  async ({ ip, apiPort }: { ip: string; apiPort: number }) => {
+    const result = await ConfigService.testConnection(ip, apiPort);
     return result;
   }
 );
@@ -81,14 +82,14 @@ export const checkServerConnection = createAsyncThunk(
       // 2. 如果是默认配置(未配置过),需要配置
       const defaultConfig = ConfigService.getDefaultConfig();
       const isDefaultConfig = !config ||
-        config.apiBaseUrl === defaultConfig.apiBaseUrl;
+        (config.ip === defaultConfig.ip && config.apiPort === defaultConfig.apiPort);
 
       if (isDefaultConfig) {
         return { needsConfig: true, config: null };
       }
 
       // 3. 测试连接
-      const testResult = await ConfigService.testConnection(config.apiBaseUrl);
+      const testResult = await ConfigService.testConnection(config.ip, config.apiPort);
 
       if (testResult.success) {
         // 连接成功,更新 Redux 状态

+ 9 - 14
src/features/serverConfig/types/index.ts

@@ -6,20 +6,14 @@
  * 服务器配置接口
  */
 export interface ServerConfig {
-  /** API 基础地址 */
-  apiBaseUrl: string;
+  /** 服务器 IP 地址 */
+  ip: string;
   
-  /** MQTT Broker 地址 */
-  mqttBrokerUrl: string;
+  /** API 服务端口 */
+  apiPort: number;
   
-  /** 配置创建时间 */
-  createdAt?: string;
-  
-  /** 配置更新时间 */
-  updatedAt?: string;
-  
-  /** 配置名称(用于多环境) */
-  name?: string;
+  /** MQTT Broker 端口 */
+  mqttPort: number;
 }
 
 /**
@@ -48,8 +42,9 @@ export interface ConfigValidation {
 export interface EnvironmentPreset {
   name: string;
   displayName: string;
-  apiBaseUrl: string;
-  mqttBrokerUrl: string;
+  ip: string;
+  apiPort: number;
+  mqttPort: number;
 }
 
 /**

+ 3 - 3
src/pages/view/components/viewers/stack.image.viewer.tsx

@@ -34,7 +34,7 @@ import PolygonLengthMeasurementTool from '@/components/measures/PolygonLengthMea
 import PolylineLengthMeasurementTool from '@/components/measures/PolylineLengthMeasurementTool';
 import { PlaybackController } from '@/pages/view/components/playback/PlaybackController';
 import { FloatingPlaybackControls } from '@/pages/view/components/playback/FloatingPlaybackControls';
-import { IP_PORT } from '@/API/config';
+import { getIpPort } from '@/API/config';
 
 const {
   MagnifyTool,
@@ -1761,7 +1761,7 @@ const StackViewer = ({
             // 预先分析DICOM元数据以检测帧数
             const { DicomMetadataAnalyzer } = await import('@/utils/dicom/DicomMetadataAnalyzer');
             const quickAnalysis = await DicomMetadataAnalyzer.analyze(imageUrls[0], undefined, {
-              baseUrl: IP_PORT
+              baseUrl: getIpPort()
             });
 
             detectedFrameCount = quickAnalysis.frameCount;
@@ -1806,7 +1806,7 @@ const StackViewer = ({
 
           // 使用 DicomMetadataAnalyzer 进行完整分析
           const analysisResult = await DicomMetadataAnalyzer.analyze(imageId, viewport, {
-            baseUrl: IP_PORT
+            baseUrl: getIpPort()
           });
 
           // 获取 viewport 的实际 imageIds(用于交叉验证)