فهرست منبع

feat(ptz): integrate new PTZ service and update camera API endpoints

- Introduced a new PTZ service with dedicated API endpoints for PTZ control operations.
- Updated camera API endpoints to improve organization and consistency.
- Enhanced camera control functions to utilize the new PTZ service for actions like starting, stopping, and managing presets.
- Improved error handling and response formatting for PTZ service interactions.
- Refactored UI components to support the new PTZ functionalities and streamline user interactions.
yb 3 روز پیش
والد
کامیت
e356a73742
5فایلهای تغییر یافته به همراه115 افزوده شده و 39 حذف شده
  1. 4 1
      .env.development
  2. 3 0
      .env.production
  3. 49 35
      src/api/camera.ts
  4. 57 0
      src/utils/request.ts
  5. 2 3
      src/views/live-stream/index.vue

+ 4 - 1
.env.development

@@ -6,4 +6,7 @@ VITE_APP_TITLE=摄像头管理系统
 VITE_APP_LANG=zh-cn
 
 # API 基础路径 (直接使用远程地址,不使用 Vite proxy)
-VITE_APP_BASE_API=https://tg-live-game.pwtk.cc/api
+VITE_APP_BASE_API=/api
+
+# PTZ 服务地址 (独立服务)
+VITE_APP_PTZ_API=http://localhost:3002

+ 3 - 0
.env.production

@@ -7,3 +7,6 @@ VITE_APP_LANG=en
 
 # API 基础路径 (生产环境使用完整 URL,因为 Cloudflare Pages 没有代理)
 VITE_APP_BASE_API=https://tg-live-game.pwtk.cc/api
+
+# PTZ 服务地址 (独立服务,需要配置生产环境地址)
+VITE_APP_PTZ_API=https://tg-live-game-ptz.pwtk.cc

+ 49 - 35
src/api/camera.ts

@@ -1,4 +1,4 @@
-import { get, post } from '@/utils/request'
+import { get, post, ptzGet, ptzPost } from '@/utils/request'
 import type {
   IBaseResponse,
   IListResponse,
@@ -23,9 +23,9 @@ export function listCameras(machineId?: string): Promise<IListResponse<CameraDTO
   return post('/camera/list', machineId ? { machineId } : {})
 }
 
-// 获取摄像头信息
+// 获取摄像头信息 (主API)
 export function getCamera(cameraId: string): Promise<IBaseResponse<CameraDTO>> {
-  return get(`/camera/control/${cameraId}`)
+  return get(`/cameras/${cameraId}`)
 }
 
 // 切换摄像头通道 (MVP核心)
@@ -38,46 +38,65 @@ export function getCurrentChannel(machineId: string): Promise<IBaseResponse<Chan
   return get('/camera/current', { machineId })
 }
 
-// 开始PTZ控制 (后台专用)
+// 开始PTZ控制 (PTZ服务)
+// 使用 /ptz/control 端点,通过 body 传递 cameraId
 export function ptzStart(cameraId: string, action: PTZAction, speed: number = 50): Promise<BaseResponse> {
-  return post(`/camera/control/${cameraId}/ptz/start`, undefined, {
-    params: { action, speed }
+  // 映射 action 到 PTZ 服务支持的 command
+  const commandMap: Record<PTZAction, string> = {
+    up: 'up',
+    down: 'down',
+    left: 'left',
+    right: 'right',
+    zoom_in: 'zoom_in',
+    zoom_out: 'zoom_out',
+    stop: 'stop'
+  }
+  return ptzPost('/ptz/control', {
+    host: cameraId,
+    command: commandMap[action] || 'stop',
+    speed
   })
 }
 
-// 停止PTZ控制 (后台专用)
+// 停止PTZ控制 (PTZ服务)
 export function ptzStop(cameraId: string): Promise<BaseResponse> {
-  return post(`/camera/control/${cameraId}/ptz/stop`)
+  return ptzPost('/ptz/control', {
+    host: cameraId,
+    command: 'stop'
+  })
 }
 
-// PTZ 直接控制 (pan/tilt/zoom 方式)
+// PTZ 直接控制 (pan/tilt/zoom 方式, PTZ服务)
 export function ptzDirectControl(cameraId: string, data: PTZControlRequest): Promise<BaseResponse> {
-  return post(`/camera/control/${cameraId}/ptz/control`, data)
+  return ptzPost('/ptz/control', {
+    host: cameraId,
+    ...data
+  })
 }
 
-// 获取预置位列表 (PTZ后端)
+// 获取预置位列表 (PTZ服务)
 export function presetList(cameraId: string): Promise<BaseResponse> {
-  return get(`/camera/control/${cameraId}/preset/list`)
+  return ptzGet(`/${cameraId}/preset/list`)
 }
 
-// 获取PTZ能力 (PTZ后端)
+// 获取PTZ能力 (PTZ服务)
 export function getPTZCapabilities(cameraId: string): Promise<BaseResponse> {
-  return get(`/camera/control/${cameraId}/ptz/capabilities`)
+  return ptzGet(`/${cameraId}/ptz/capabilities`)
 }
 
-// 跳转到预置位 (PTZ后端)
+// 跳转到预置位 (PTZ服务)
 export function presetGoto(cameraId: string, presetId: number): Promise<BaseResponse> {
-  return post(`/camera/control/${cameraId}/preset/goto`, { presetId })
+  return ptzPost(`/${cameraId}/preset/goto`, { presetId })
 }
 
-// 设置预置位 (PTZ后端)
+// 设置预置位 (PTZ服务)
 export function presetSet(cameraId: string, presetId: number, presetName?: string): Promise<BaseResponse> {
-  return post(`/camera/control/${cameraId}/preset/set`, { presetId, presetName })
+  return ptzPost(`/${cameraId}/preset/set`, { presetId, presetName })
 }
 
-// 删除预置位 (PTZ后端)
+// 删除预置位 (PTZ服务)
 export function presetRemove(cameraId: string, presetId: number): Promise<BaseResponse> {
-  return post(`/camera/control/${cameraId}/preset/remove`, { presetId })
+  return ptzPost(`/${cameraId}/preset/remove`, { presetId })
 }
 
 // ==================== Admin APIs ====================
@@ -131,7 +150,7 @@ export function adminPTZControl(data: AdminPTZRequest): Promise<IBaseResponse<bo
   return post('/admin/cameras/ptz', data)
 }
 
-// ==================== Preset APIs ====================
+// ==================== Preset APIs (PTZ服务) ====================
 
 // 预置位类型
 export interface PresetInfo {
@@ -139,30 +158,25 @@ export interface PresetInfo {
   name: string
 }
 
-// 获取预置位列表
+// 获取预置位列表 (兼容旧API)
 export function getPresets(cameraId: string): Promise<IListResponse<PresetInfo>> {
-  return get(`/camera/${cameraId}/presets`)
+  return ptzGet(`/${cameraId}/preset/list`)
 }
 
-// 跳转到预置位
+// 跳转到预置位 (兼容旧API)
 export function gotoPreset(cameraId: string, presetToken: string): Promise<BaseResponse> {
-  return post(`/camera/${cameraId}/preset/goto`, undefined, {
-    params: { presetToken }
-  })
+  return ptzPost(`/${cameraId}/preset/goto`, { presetId: parseInt(presetToken) || 1 })
 }
 
-// 设置预置位 (保存当前位置)
+// 设置预置位 (保存当前位置, 兼容旧API)
 export function setPreset(cameraId: string, presetName?: string): Promise<IBaseResponse<PresetInfo>> {
-  return post(`/camera/${cameraId}/preset/set`, undefined, {
-    params: presetName ? { presetName } : undefined
-  })
+  // 旧API不指定presetId,这里使用一个默认值
+  return ptzPost(`/${cameraId}/preset/set`, { presetId: 1, presetName })
 }
 
-// 删除预置位
+// 删除预置位 (兼容旧API)
 export function removePreset(cameraId: string, presetToken: string): Promise<BaseResponse> {
-  return post(`/camera/${cameraId}/preset/remove`, undefined, {
-    params: { presetToken }
-  })
+  return ptzPost(`/${cameraId}/preset/remove`, { presetId: parseInt(presetToken) || 1 })
 }
 
 // ==================== 兼容旧代码的别名 ====================

+ 57 - 0
src/utils/request.ts

@@ -4,6 +4,7 @@ import { getToken } from './auth'
 import { useUserStore } from '@/store/user'
 import type { BaseResponse } from '@/types'
 
+// 主 API 服务
 const service: AxiosInstance = axios.create({
   baseURL: import.meta.env.VITE_APP_BASE_API || '/api',
   timeout: 30000,
@@ -12,6 +13,15 @@ const service: AxiosInstance = axios.create({
   }
 })
 
+// PTZ 服务 (独立服务,不走主后端)
+const ptzService: AxiosInstance = axios.create({
+  baseURL: import.meta.env.VITE_APP_PTZ_API || 'http://localhost:3002',
+  timeout: 10000,
+  headers: {
+    'Content-Type': 'application/json;charset=utf-8'
+  }
+})
+
 // 请求拦截器
 service.interceptors.request.use(
   (config) => {
@@ -93,4 +103,51 @@ export function del<T = any>(url: string, params?: object, config?: AxiosRequest
   return service.delete(url, { params, ...config })
 }
 
+// ==================== PTZ 服务请求方法 ====================
+
+// PTZ 服务响应格式转换
+// PTZ 服务返回: {code, msg, data}
+// 前端期望: {success, errMsg, data}
+interface PTZServiceResponse {
+  code: number
+  msg: string
+  data: unknown
+}
+
+// PTZ 服务响应拦截器
+ptzService.interceptors.response.use(
+  (response: AxiosResponse<PTZServiceResponse>) => {
+    const res = response.data
+    // 转换格式: {code, msg, data} -> {success, errMsg, data}
+    return {
+      success: res.code === 200,
+      errMsg: res.code !== 200 ? res.msg : undefined,
+      data: res.data
+    } as any
+  },
+  (error) => {
+    let { message } = error
+    if (message === 'Network Error') {
+      message = 'PTZ服务连接异常'
+    } else if (message.includes('timeout')) {
+      message = 'PTZ服务请求超时'
+    }
+    console.error('PTZ Service Error:', message)
+    // 返回统一格式的错误响应
+    return {
+      success: false,
+      errMsg: message,
+      data: null
+    }
+  }
+)
+
+export function ptzGet<T = any>(url: string, params?: object, config?: AxiosRequestConfig): Promise<T> {
+  return ptzService.get(url, { params, ...config })
+}
+
+export function ptzPost<T = any>(url: string, data?: object, config?: AxiosRequestConfig): Promise<T> {
+  return ptzService.post(url, data, config)
+}
+
 export default service

+ 2 - 3
src/views/live-stream/index.vue

@@ -340,7 +340,7 @@
               </div>
 
               <!-- 底部播放控制 -->
-              <div class="player-controls">
+              <!-- <div class="player-controls">
                 <el-button type="primary" size="small" @click="handlePlay">{{ t('播放') }}</el-button>
                 <el-button size="small" @click="handlePause">{{ t('暂停') }}</el-button>
                 <el-button type="danger" size="small" @click="handlePlayerStop">{{ t('停止') }}</el-button>
@@ -353,8 +353,7 @@
                   style="margin-left: 16px"
                 />
                 <el-divider direction="vertical" />
-                <!-- 停止推流按钮 -->
-              </div>
+              </div> -->
             </div>
 
             <!-- 右侧:PTZ 控制面板 -->