Переглянути джерело

完善wvp相关接口对接以及修改device、field接口前缀

jiuling 10 місяців тому
батько
коміт
d2ffbe5d54
4 змінених файлів з 1564 додано та 1532 видалено
  1. 29 12
      api/services/device.js
  2. 4 4
      api/services/field.js
  3. 1 1
      config/api.js
  4. 1530 1515
      pages/device/device-list/detail-camera.vue

+ 29 - 12
api/services/device.js

@@ -11,6 +11,9 @@ import api from "@/config/api.js";
 import config from "@/config/config.js";
 // const request = http.request;
 const userInfo = storage.getUserInfo()
+/**
+ * 登录wvp获取token
+ */
 export function loginWvp() {
   return uni.request({
     url: `/wvp/api/user/login?username=${config.wvpUsername}&password=${config.wvpPassword}`,
@@ -18,15 +21,24 @@ export function loginWvp() {
     needToken: false,
   });
 }
-
+/**
+ * 获取通道列表
+ */
 export async function getChannels(deviceId) {
   try {
+	  // 登录获取token
     const loginRes = await loginWvp();
     console.log("WVP登录结果:", loginRes);
-
-    if (loginRes.code === 0) {
+	if (loginRes[0]) {
+		  // 处理错误
+		  console.error('登录失败', loginRes[0]);
+		  throw new Error('登录失败');
+		}
+	const response = loginRes[1].data;
+    if (response.code === 0) {
       console.log("WVP登录成功");
-      storage.setWvpAccessToken(loginRes.data.accessToken);
+      storage.setWvpAccessToken(response.data.accessToken);
+	  // 查询通道列表
       const channelsRes = await uni.request({
         url: `/wvp/api/device/query/devices/${deviceId}/channels`,
         method: Method.GET,
@@ -36,7 +48,7 @@ export async function getChannels(deviceId) {
           online: true,
         },
         header: {
-          'Authorization': `access-token ${storage.getWvpAccessToken()}`,
+          'Access-Token': `${storage.getWvpAccessToken()}`,
         },
       });
       console.log("获取通道结果:", channelsRes);
@@ -50,22 +62,27 @@ export async function getChannels(deviceId) {
     throw error;
   }
 }
-
+/**
+ * 开始点播
+ */
 export async function playStart(deviceId, channelId) {
   return await  uni.request({
     url: `/wvp/api/play/start/${deviceId}/${channelId}`,  
     method: Method.GET,
     header: {
-          'Authorization': `access-token ${storage.getWvpAccessToken()}`,
+      'Access-Token': `${storage.getWvpAccessToken()}`,
     },
   })
 }
+/**
+ * 暂停点播
+ */
 export async function pause(deviceId, channelId) {
   return await  uni.request({
-    url: `/wvp/api/play/stop/{deviceId}/{channelId}`,  
+    url: `/wvp/api/play/stop/${deviceId}/${channelId}`,  
     method: Method.GET,
     header: {
-          'Authorization': `access-token ${storage.getWvpAccessToken()}`,
+      'Access-Token': `${storage.getWvpAccessToken()}`,
     },
   })
 }
@@ -81,7 +98,7 @@ export function fetchDeviceOverview(fieldId) {
   if (fieldId) params.fieldId = fieldId;
   
   return http.request({
-    url: 'uniapp/device/overview',
+    url: '/base/device/overview',
     method: Method.POST,
 	needToken: true,
     data: params
@@ -97,7 +114,7 @@ export function fetchDeviceOverview(fieldId) {
 export function fetchDevicesByType(params) {
 if (userInfo.userid) params.userId = userInfo.userid;
   return http.request({
-    url: `uniapp/device/typeList`,
+    url: `/base/device/typeList`,
     method: Method.POST,
 	needToken: true,
     data: params
@@ -144,7 +161,7 @@ export function getDeviceDetail(id) {
  */
 export function getDeviceCollectorDetail(deviceId, code) {
   return http.request({
-    url: `uniapp/device/collector/detail/${deviceId}`,
+    url: `/base/device/collector/detail/${deviceId}`,
     method: Method.GET,
 	needToken: true,
     params: code ? { code } : {}

+ 4 - 4
api/services/field.js

@@ -15,7 +15,7 @@ const request = http.request;
 export function fetchUserFieldList(pageNum = 1, pageSize = 10) {
 	const user = storage.getUserInfo();
 	return http.request({
-		url: 'uniapp/field/list/user',
+		url: '/base/field/list/user',
 		method: Method.POST,
 		needToken: true,
 		data: {
@@ -34,7 +34,7 @@ export function fetchUserFieldList(pageNum = 1, pageSize = 10) {
  */
 export function listFieldName(userId) {
 	return http.request({
-		url: `uniapp/field/name/${userId}`,
+		url: `/base/field/name/${userId}`,
 		method: Method.GET,
 		needToken: true
 	});
@@ -48,7 +48,7 @@ export function searchUserField(data) {
 	console.log("da",data);
 	const user = storage.getUserInfo();
 	return http.request({
-		url: 'uniapp/field/search/user',
+		url: '/base/field/search/user',
 		method: Method.POST,
 		needToken: true,
 		data:{
@@ -68,7 +68,7 @@ export function searchUserField(data) {
 export function countUserPlots() {
 	const user = storage.getUserInfo();
 	return http.request({
-		url: `uniapp/field/count/${user.userid}`,
+		url: `/base/field/count/${user.userid}`,
 		needToken: true,
 		method: Method.GET,
 	});

+ 1 - 1
config/api.js

@@ -4,7 +4,7 @@ const dev = {
 };
 // 生产环境
 const prod = {
-  serve: "http://121.4.16.100:9000/pro-uniapp",
+  serve: "http://nxy.gbdfarm.com:9000/pro-uniapp",
 };
 
 //默认生产环境

+ 1530 - 1515
pages/device/device-list/detail-camera.vue

@@ -1,112 +1,108 @@
 <template>
-  <view class="container">
-    <!-- 设备头部信息区域 -->
-    <view class="device-header">
-      <view class="device-info-row">
-        <view class="device-name-container">
-          <text class="device-name">{{ deviceInfo.name }}</text>
-          <view 
-            class="status-tag" 
-            :class="deviceInfo.status === 1 ? 'status-online' : 'status-offline'"
-          >
-            <view class="status-dot" :class="{'offline-dot': deviceInfo.status === 1}"></view>
-            {{ deviceInfo.status === 1 ? '在线' : '离线' }}
-          </view>
-        </view>
-        
-        <view class="refresh-btn" :class="{'refreshing': isRefreshing}" @tap="refreshData">
-          <image src="/static/icons/refresh_icon.png" mode="aspectFit" style="width: 22px; height: 22px;"></image>
-        </view>
-      </view>
-      
-      <view class="device-meta-row">
-        <view class="device-meta-item">
-          <view class="meta-icon">
-            <image src="/static/icons/device_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
-          </view>
-          <text class="meta-label">设备编号:</text>
-          <text class="meta-value">{{ deviceInfo.deviceId }}</text>
-        </view>
-        
-        <view class="device-meta-item">
-          <view class="meta-icon">
-            <image src="/static/icons/location_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
-          </view>
-          <text class="meta-label">安装位置:</text>
-          <text class="meta-value">{{ deviceInfo.location }}</text>
-        </view>
-        
-        <view class="device-meta-item">
-          <view class="meta-icon">
-            <image src="/static/icons/clock_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
-          </view>
-          <text class="meta-label">最近更新:</text>
-          <text class="meta-value">{{ formatDate(deviceInfo.lastUpdate) }}</text>
-        </view>
-      </view>
-    </view>
-    
-    <!-- 视频预览区域 -->
-    <view class="video-section">
-      <view class="video-container" :class="{'fullscreen-mode': isFullscreen}">
-        <image v-if="!isPlaying" src="/static/images/video-placeholder.jpg" mode="aspectFill" class="video-placeholder"></image>
-        
-        <!-- 使用跨平台视频播放组件 -->
-        <!-- #ifdef H5 -->
-        <view v-if="isPlaying" class="h5-video-wrapper">
-          <Jessibuca ref="jessibucaRef" :videoUrl="getH5StreamUrl" :hasAudio="true" @error="onVideoError" />
-        </view>
-        <!-- #endif -->
-        
-        <!-- #ifdef MP-WEIXIN -->
-        <live-player 
-          v-if="isPlaying"
-          id="videoPlayer"
-          :src="getMiniProgramStreamUrl" 
-          mode="live"
-          :autoplay="true"
-          :muted="isMuted"
-          object-fit="contain"
-          @statechange="onStateChange"
-          @error="onVideoError"
-          @fullscreenchange="onFullscreenChange"
-          class="video-player"
-        ></live-player>
-        <!-- #endif -->
-        
-        <!-- 视频控制层 -->
-        <view class="video-controls">
-          <view class="control-row top-controls">
-            <view class="signal-indicator">
-              <image src="/static/icons/signal_icon.png" mode="aspectFit" style="width: 16px; height: 16px;"></image>
-              <text class="signal-text">信号良好</text>
-            </view>
-            
-            <view class="fullscreen-button" @click="toggleFullscreen">
-              <image src="/static/icons/resize_icon.png" mode="aspectFit" style="width: 20px; height: 20px;"></image>
-            </view>
-          </view>
-          
-          <view class="control-row center-controls">
-            <view v-if="!isPlaying" class="play-button" @click="togglePlayState">
-              <image src="/static/icons/play_icon.png" mode="aspectFit" style="width: 32px; height: 32px;"></image>
-            </view>
-            <view v-else class="center-button-container" @click="togglePlayState">
-              <view class="pause-icon">
-                <image src="/static/icons/pause_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
-              </view>
-            </view>
-          </view>
-          
-          <view class="control-row bottom-controls">
-            <view class="video-time">{{ currentTime }}</view>
-          </view>
-        </view>
-      </view>
-    </view>
-    
-    <!-- 云台控制区域 -->
-    <!-- <view class="ptz-section">
+	<view class="container">
+		<!-- 设备头部信息区域 -->
+		<view class="device-header">
+			<view class="device-info-row">
+				<view class="device-name-container">
+					<text class="device-name">{{ deviceInfo.name }}</text>
+					<view class="status-tag" :class="deviceInfo.status === 1 ? 'status-online' : 'status-offline'">
+						<view class="status-dot" :class="{'offline-dot': deviceInfo.status === 1}"></view>
+						{{ deviceInfo.status === 1 ? '在线' : '离线' }}
+					</view>
+				</view>
+
+				<view class="refresh-btn" :class="{'refreshing': isRefreshing}" @tap="refreshData">
+					<image src="/static/icons/refresh_icon.png" mode="aspectFit" style="width: 22px; height: 22px;">
+					</image>
+				</view>
+			</view>
+
+			<view class="device-meta-row">
+				<view class="device-meta-item">
+					<view class="meta-icon">
+						<image src="/static/icons/device_icon.png" mode="aspectFit"
+							style="width: 36rpx; height: 36rpx;"></image>
+					</view>
+					<text class="meta-label">设备编号:</text>
+					<text class="meta-value">{{ deviceInfo.deviceId }}</text>
+				</view>
+
+				<view class="device-meta-item">
+					<view class="meta-icon">
+						<image src="/static/icons/location_icon.png" mode="aspectFit"
+							style="width: 36rpx; height: 36rpx;"></image>
+					</view>
+					<text class="meta-label">安装位置:</text>
+					<text class="meta-value">{{ deviceInfo.location }}</text>
+				</view>
+
+				<view class="device-meta-item">
+					<view class="meta-icon">
+						<image src="/static/icons/clock_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;">
+						</image>
+					</view>
+					<text class="meta-label">最近更新:</text>
+					<text class="meta-value">{{ formatDate(deviceInfo.lastUpdate) }}</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 视频预览区域 -->
+		<view class="video-section">
+			<view class="video-container" :class="{'fullscreen-mode': isFullscreen}">
+				<image v-if="!isPlaying" src="/static/images/video-placeholder.jpg" mode="aspectFill"
+					class="video-placeholder"></image>
+
+				<!-- 使用跨平台视频播放组件 -->
+				<!-- #ifdef H5 -->
+				<view v-if="isPlaying" class="h5-video-wrapper">
+					<Jessibuca ref="jessibucaRef" :videoUrl="getH5StreamUrl" :hasAudio="true" @error="onVideoError" />
+				</view>
+				<!-- #endif -->
+
+				<!-- #ifdef MP-WEIXIN -->
+				<live-player v-if="isPlaying" id="videoPlayer" :src="getMiniProgramStreamUrl" mode="live"
+					:autoplay="true" :muted="isMuted" object-fit="contain" @statechange="onStateChange"
+					@error="onVideoError" @fullscreenchange="onFullscreenChange" class="video-player"></live-player>
+				<!-- #endif -->
+
+				<!-- 视频控制层 -->
+				<view class="video-controls">
+					<view class="control-row top-controls">
+						<view class="signal-indicator">
+							<image src="/static/icons/signal_icon.png" mode="aspectFit"
+								style="width: 16px; height: 16px;"></image>
+							<text class="signal-text">信号良好</text>
+						</view>
+
+						<view class="fullscreen-button" @click="toggleFullscreen">
+							<image src="/static/icons/resize_icon.png" mode="aspectFit"
+								style="width: 20px; height: 20px;"></image>
+						</view>
+					</view>
+
+					<view class="control-row center-controls">
+						<view v-if="!isPlaying" class="play-button" @click="togglePlayState">
+							<image src="/static/icons/play_icon.png" mode="aspectFit"
+								style="width: 32px; height: 32px;"></image>
+						</view>
+						<view v-else class="center-button-container" @click="togglePlayState">
+							<view class="pause-icon">
+								<image src="/static/icons/pause_icon.png" mode="aspectFit"
+									style="width: 24px; height: 24px;"></image>
+							</view>
+						</view>
+					</view>
+
+					<view class="control-row bottom-controls">
+						<view class="video-time">{{ currentTime }}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 云台控制区域 -->
+		<!-- <view class="ptz-section">
       <view class="section-title">云台控制</view>
       
       <view class="ptz-container">
@@ -154,9 +150,9 @@
         </view>
       </view>
     </view> -->
-    
-    <!-- 快捷功能按钮 -->
-    <!-- <view class="quick-actions">
+
+		<!-- 快捷功能按钮 -->
+		<!-- <view class="quick-actions">
       <view class="action-button" @click="toggleVoiceIntercom">
         <view class="action-icon">
           <image src="/static/icons/Voice_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
@@ -179,1423 +175,1442 @@
         <text class="action-text">视频回看</text>
       </view>
     </view> -->
-    
-    <!-- 告警信息列表 -->
-    <view class="alerts-section">
-      <view class="section-title">
-        <text>告警信息</text>
-        <view class="alert-badge" v-if="getUnhandledAlerts.length > 0">{{ getUnhandledAlerts.length }}</view>
-      </view>
-      
-      <view class="alerts-list" v-if="getUnhandledAlerts.length > 0">
-        <view 
-          v-for="(item, index) in getUnhandledAlerts.slice(0, 3)" 
-          :key="index"
-          class="alert-item"
-          :class="{
+
+		<!-- 告警信息列表 -->
+		<view class="alerts-section">
+			<view class="section-title">
+				<text>告警信息</text>
+				<view class="alert-badge" v-if="getUnhandledAlerts.length > 0">{{ getUnhandledAlerts.length }}</view>
+			</view>
+
+			<view class="alerts-list" v-if="getUnhandledAlerts.length > 0">
+				<view v-for="(item, index) in getUnhandledAlerts.slice(0, 3)" :key="index" class="alert-item" :class="{
             'alert-urgent': item.level === 3, 
             'alert-warning': item.level === 2, 
             'alert-info': item.level === 1
-          }"
-        >
-          <view class="alert-item-icon">
-            <image v-if="item.level === 3" src="/static/icons/warning_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
-            <image v-else-if="item.level === 2" src="/static/icons/info_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
-            <image v-else src="/static/icons/success_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
-          </view>
-          
-          <view class="alert-item-info">
-            <text class="alert-item-type">{{ item.type }}</text>
-            <text class="alert-item-level">
-              {{ item.level === 3 ? '紧急' : item.level === 2 ? '警告' : '提示' }}
-            </text>
-          </view>
-          
-          <view class="alert-item-time">{{ formatSmartTime(item.time) }}</view>
-        </view>
-      </view>
-      
-      <view v-else class="empty-alert">
-        <text class="empty-text">暂无告警信息</text>
-      </view>
-    </view>
-  </view>
+          }">
+					<view class="alert-item-icon">
+						<image v-if="item.level === 3" src="/static/icons/warning_icon.png" mode="aspectFit"
+							style="width: 24px; height: 24px;"></image>
+						<image v-else-if="item.level === 2" src="/static/icons/info_icon.png" mode="aspectFit"
+							style="width: 24px; height: 24px;"></image>
+						<image v-else src="/static/icons/success_icon.png" mode="aspectFit"
+							style="width: 24px; height: 24px;"></image>
+					</view>
+
+					<view class="alert-item-info">
+						<text class="alert-item-type">{{ item.type }}</text>
+						<text class="alert-item-level">
+							{{ item.level === 3 ? '紧急' : item.level === 2 ? '警告' : '提示' }}
+						</text>
+					</view>
+
+					<view class="alert-item-time">{{ formatSmartTime(item.time) }}</view>
+				</view>
+			</view>
+
+			<view v-else class="empty-alert">
+				<text class="empty-text">暂无告警信息</text>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-	import { getDeviceCollectorDetail, getChannels, playStart,pause } from "@/api/services/device.js";
-	import { formatSmartTime, formatDate, getFormattedTime} from '@/utils/dateUtils'
-	import { isPlayableInMiniProgram, buildPlatformStreamUrls } from '@/utils/media-utils'
+	import {
+		getDeviceCollectorDetail,
+		getChannels,
+		playStart,
+		pause
+	} from "@/api/services/device.js";
+	import {
+		formatSmartTime,
+		formatDate,
+		getFormattedTime
+	} from '@/utils/dateUtils'
+	import {
+		isPlayableInMiniProgram,
+		buildPlatformStreamUrls
+	} from '@/utils/media-utils'
 	import config from '@/config/config'
 	// 导入Jessibuca组件
 	// #ifdef H5
 	import Jessibuca from '@/components/common/jessibuca.vue'
 	// #endif
 
-export default {
-	components: {
-	    // #ifdef H5
-	    Jessibuca
-	    // #endif
-	  },
-  data() {
-    return {
-      deviceInfo: {
-        deviceId: '',
-        name: '设备加载中...',
-        status: '',
-        location: '正在获取位置...',
-        lastUpdate: '',
-        deviceType: 'weather' ,// 默认类型,会根据API返回更新
-      	deviceTypeId:null,
-		    streamUrl: '',
-        channelId: null, // 当前通道ID
-        originalStreamUrl: 'ws://121.4.16.100:6080/rtp/34020000001110000001_34020000001320000012.live.flv',
-
-      },
-      isPlaying: false,
-      isMuted: false,
-      isRecording: false,
-      isFullscreen: false,
-      isVoiceActive: false,
-      isGridView: false,
-      isZoomMode: false,
-      currentTime: '14:30:25',
-      // 模拟历史录像数据
-      recordHistory: [
-        { id: 1, startTime: '今天 12:30', duration: '00:15:30', url: '' },
-        { id: 2, startTime: '今天 10:15', duration: '00:05:22', url: '' },
-        { id: 3, startTime: '昨天 18:45', duration: '00:30:10', url: '' },
-        { id: 4, startTime: '昨天 14:20', duration: '00:10:05', url: '' }
-      ],
-      // 模拟告警数据
-      alertHistory: [],
-      livePlayerContext: null, // 小程序视频上下文
-	  isRefreshing: false
-    }
-  },
-  
-  computed: {
-    // 获取所有未处理的告警
-    getUnhandledAlerts() {
-      return this.alertHistory.filter(alert => alert.status === 0);
-    },
-    // 获取H5环境使用的流地址
-    getH5StreamUrl() {
-      // 首先尝试使用设备返回的流URL
-      // if (this.deviceInfo.streamUrl) {
-      //   return this.deviceInfo.streamUrl;
-      // }
-      
-      // 如果没有设备特定的URL,则使用默认原始流
-      return this.deviceInfo.originalStreamUrl || config.streamServer.wsFlvServer;
-    },
-    
-    // 获取小程序环境使用的流地址
-    getMiniProgramStreamUrl() {
-      // 获取合适的小程序流地址
-      const { streamServer } = config;
-      
-      // 如果有设备特定的RTMP流,优先使用
-      if (this.deviceInfo.rtmpUrl) {
-        return this.deviceInfo.rtmpUrl;
-      }
-      
-      // 其次尝试使用HLS流
-      if (this.deviceInfo.hlsUrl) {
-        return this.deviceInfo.hlsUrl;
-      }
-      
-      // 最后使用配置的默认流
-      return streamServer.rtmpServer || streamServer.hlsServer;
-    },
-  },
-  
-  onReady() {
-    // 设置页面标题
-    uni.setNavigationBarTitle({
-      title: this.deviceInfo.name
-    })
-    
-    // 模拟更新时间
-    this.startTimeUpdate()
-    
-    // #ifdef MP-WEIXIN
-    // 创建小程序视频上下文
-    this.livePlayerContext = uni.createLivePlayerContext('videoPlayer', this)
-    // #endif
-    
-    // #ifdef H5
-    // 加载Jessibuca的JS库
-    this.loadJessibucaScript()
-    // #endif
-    
-    // 监听全屏状态变化
-    this.setupFullscreenListener()
-    
-    // 添加键盘事件监听
-    this.setupKeyboardListener()
-    
-    // 初始化流地址
-    this.initStreamUrl()
-  },
-  beforeUnmount() {
-      // 移除全屏状态监听
-      this.removeFullscreenListener()
-      
-      // 移除键盘事件监听
-      this.removeKeyboardListener()
-    },
-  
-  onLoad(options) {
-	  uni.$once('passDeviceData', (data) => {
-	      console.log('接收到数据', data);
-	  		  // 如果有传入设备ID,则获取设备信息
-	  		  if (data && data.deviceId) {
-	  		    this.deviceInfo.deviceId = data.deviceId;
-	  		    this.deviceInfo.location = data.fieldName;
-	  		    this.deviceInfo.name = data.deviceName;
-	  		    this.deviceInfo.status = data.status;
-	  		    this.deviceInfo.deviceTypeId = data.deviceTypeId || '';
-	  		    
-	  		    // 加载设备详情
-	  		    this.fetchDeviceInfo();
-            // this.queryChannels();
-	  		  }
-	    });
-  },
-  
-  methods: {
-	  formatDate,
-	 formatSmartTime,
-	 // 加载Jessibuca脚本
-	     loadJessibucaScript() {
-	       const script = document.createElement('script')
-	       script.src = '/static/js/jessibuca/jessibuca.js'
-	       script.onload = () => {
-	         console.log('Jessibuca 脚本加载成功')
-	       }
-	       script.onerror = (error) => {
-	         console.error('Jessibuca 脚本加载失败:', error)
-	       }
-	       document.head.appendChild(script)
-	     },
-    // 获取设备信息
-    fetchDeviceInfo(deviceId) {
-      // 这里应该是API请求,暂时用模拟数据
-      console.log('获取设备信息:', deviceId)
-	  getDeviceCollectorDetail(this.deviceInfo.deviceId)
-	    .then(res => {
-			if (res.data.data && res.data.code === 200) {
-			  const detail = res.data.data;
-			  this.deviceInfo.lastUpdate = getFormattedTime()
-			  // 更新页面标题
-			  uni.setNavigationBarTitle({
-			    title: this.deviceInfo.name
-			  });
-			  // 更新告警信息
-			  if (detail.alertRecordList && detail.alertRecordList.length > 0) {
-			    this.alertHistory = detail.alertRecordList.map(alert => ({
-			      id: alert.alertId,
-			      time: alert.alertTime,
-			      type: alert.alertContent,
-			      status: alert.processStatus,
-			      level: alert.alertLevel
-			    }));
-			  }
+	export default {
+		components: {
+			// #ifdef H5
+			Jessibuca
+			// #endif
+		},
+		data() {
+			return {
+				deviceInfo: {
+					deviceId: '',
+					name: '设备加载中...',
+					status: '',
+					location: '正在获取位置...',
+					lastUpdate: '',
+					deviceType: 'weather', // 默认类型,会根据API返回更新
+					deviceTypeId: null,
+					streamUrl: '',
+					channelId: null, // 当前通道ID
+					originalStreamUrl: 'ws://121.4.16.100:6080/rtp/34020000001110000001_34020000001320000012.live.flv',
+
+				},
+				isPlaying: false,
+				isMuted: false,
+				isRecording: false,
+				isFullscreen: false,
+				isVoiceActive: false,
+				isGridView: false,
+				isZoomMode: false,
+				currentTime: '14:30:25',
+				// 模拟历史录像数据
+				recordHistory: [{
+						id: 1,
+						startTime: '今天 12:30',
+						duration: '00:15:30',
+						url: ''
+					},
+					{
+						id: 2,
+						startTime: '今天 10:15',
+						duration: '00:05:22',
+						url: ''
+					},
+					{
+						id: 3,
+						startTime: '昨天 18:45',
+						duration: '00:30:10',
+						url: ''
+					},
+					{
+						id: 4,
+						startTime: '昨天 14:20',
+						duration: '00:10:05',
+						url: ''
+					}
+				],
+				// 模拟告警数据
+				alertHistory: [],
+				livePlayerContext: null, // 小程序视频上下文
+				isRefreshing: false
 			}
-		})
-    },
-    
-    // 根据设备id获取通道列表
-    queryChannels(){
-      getChannels(this.deviceInfo.deviceId)
-            .then(res => {
-              console.log('获取通道列表:', res);
-              
-              if (res.code === 0 && res.data.total > 0) {
-                const channels = res.data.list;
-                this.deviceInfo.channelId = channels[0].deviceId; // 默认选择第一个通道
-                playStart(this.deviceInfo.deviceId,this.deviceInfo.channelId).then(res=>{
-                  if (res.code !== 0) {
-                    console.error('播放开始失败:', res.message);
-                    uni.showToast({
-                      title: '播放失败: ' + res.message,
-                      icon: 'none'
-                    });
-                    return;
-                  }
-                  console.log('播放开始:', res);
-                  this.deviceInfo.originalStreamUrl = res.data.ws_flv || this.deviceInfo.originalStreamUrl;
-                }).catch(err => {
-                  console.error('播放开始失败:', err);
-                })
-              } else {
-                uni.showToast({
-                      title: '获取通道列表失败: ' + res.message,
-                      icon: 'none'
-                    });
-                  return;
-              }
-            })
-            .catch(err => {
-              console.error('获取通道列表错误:', err);
-            });
-    },
-    // 播放/暂停切换
-    togglePlayState() {
-      if (!this.isPlaying) {
-        // 从未播放状态切换到播放状态
-        this.isPlaying = true
-        
-        // #ifdef MP-WEIXIN
-        // 小程序环境中,短暂延迟确保组件已加载
-        setTimeout(() => {
-          if (this.livePlayerContext) {
-            this.livePlayerContext.play({
-              success: () => {
-                console.log('小程序视频播放成功')
-                uni.vibrateShort()
-              },
-              fail: (err) => {
-                console.error('小程序视频播放失败:', err)
-                uni.showToast({
-                  title: '视频播放失败',
-                  icon: 'none'
-                })
-              }
-            })
-          }
-        }, 300)
-        // #endif
-        
-        // #ifdef H5
-        // 浏览器环境中使用Jessibuca
-        setTimeout(() => {
-                //    playStart(this.deviceInfo.deviceId,this.deviceInfo.channelId).then(res=>{
-                //   if (res.code !== 0) {
-                //     console.error('播放开始失败:', res.message);
-                //     uni.showToast({
-                //       title: '播放失败: ' + res.message,
-                //       icon: 'none'
-                //     });
-                //     return;
-                //   }
-                //   console.log('播放开始:', res);
-                //   this.deviceInfo.originalStreamUrl = res.data.ws_flv || this.deviceInfo.originalStreamUrl;
-                // }).catch(err => {
-                //   console.error('播放开始失败:', err);
-                // })
-          uni.vibrateShort()
-        }, 300)
-        // #endif
-      } else {
-        // 从播放状态切换到暂停状态
-        // #ifdef H5
-        if (this.$refs.jessibucaRef) {
-          this.$refs.jessibucaRef.pause()
-          pause().then(res => {
-            if (res.code !== 0) {
-              console.error('暂停失败:', res.message)
-              uni.showToast({
-                title: '暂停失败: ' + res.message,
-                icon: 'none'
-              })
-            } else {
-              console.log('视频已暂停')
-              // uni.showToast({
-              //   title: '视频已暂停',
-              //   icon: 'none',
-              //   duration: 1500
-              // })
-            }
-          }).catch(err => {
-            console.error('暂停请求错误:', err)
-          })
-        }
-        // #endif
-        
-        // #ifdef MP-WEIXIN
-        if (this.livePlayerContext) {
-          this.livePlayerContext.pause()
-        }
-        // #endif
-        
-        this.isPlaying = false
-        
-        // 在页面上显示暂停状态
-        uni.showToast({
-          title: '视频已暂停',
-          icon: 'none',
-          duration: 1500
-        })
-      }
-    },
-    
-    // 静音切换
-    toggleMute() {
-      this.isMuted = !this.isMuted
-      
-      // #ifdef H5
-      if (this.$refs.jessibucaRef) {
-        if (this.isMuted) {
-          this.$refs.jessibucaRef.mute()
-        } else {
-          this.$refs.jessibucaRef.cancelMute()
-        }
-      }
-      // #endif
-      
-      // #ifdef MP-WEIXIN
-      // 小程序环境中直接通过属性绑定控制静音
-      // #endif
-    },
-    
-    // 全屏切换
-    toggleFullscreen() {
-      if (!this.isPlaying) {
-        // 如果视频未播放,先开始播放
-        this.togglePlayState()
-        // 延迟执行全屏操作,等待视频元素加载
-        setTimeout(() => {
-          this.setFullscreen(true)
-        }, 500)
-      } else {
-        this.setFullscreen(!this.isFullscreen)
-      }
-    },
-    
-    // 设置全屏状态
-    setFullscreen(fullscreen) {
-      this.isFullscreen = fullscreen
-      
-      // #ifdef H5
-      // 浏览器环境
-      if (this.isFullscreen) {
-        // 如果是移动设备,尝试使用系统全屏
-        const ua = navigator.userAgent.toLowerCase()
-        const isMobile = /mobile|android|iphone|ipad/.test(ua)
-        
-        if (isMobile && this.$refs.jessibucaRef) {
-          // 在移动设备上使用Jessibuca的全屏API
-          this.$refs.jessibucaRef.fullscreenSwich()
-        } else {
-          // 适配屏幕尺寸
-          setTimeout(() => {
-            if (this.$refs.jessibucaRef) {
-              this.$refs.jessibucaRef.resize()
-            }
-          }, 300)
-        }
-        
-        // 锁定屏幕方向为横屏
-        if (window.screen && window.screen.orientation && window.screen.orientation.lock) {
-          window.screen.orientation.lock('landscape').catch(err => {
-            console.error('无法锁定屏幕方向:', err)
-          })
-        }
-      } else {
-        // 退出全屏状态
-        if (this.$refs.jessibucaRef) {
-          // 在某些情况下需要先调用Jessibuca的退出全屏
-          if (this.$refs.jessibucaRef.isFullscreen()) {
-            this.$refs.jessibucaRef.fullscreenSwich()
-          }
-          
-          // 重置视频大小
-          setTimeout(() => {
-            if (this.$refs.jessibucaRef) {
-              this.$refs.jessibucaRef.resize()
-            }
-          }, 300)
-        }
-        
-        // 解除屏幕方向锁定
-        if (window.screen && window.screen.orientation && window.screen.orientation.unlock) {
-          window.screen.orientation.unlock()
-        }
-      }
-      // #endif
-      
-      // #ifdef MP-WEIXIN
-      // 小程序环境
-      if (this.livePlayerContext) {
-        if (this.isFullscreen) {
-          this.livePlayerContext.requestFullScreen({
-            direction: 90, // 横屏
-            success: () => {
-              console.log('进入全屏模式成功')
-            },
-            fail: (err) => {
-              console.error('进入全屏模式失败:', err)
-            }
-          })
-        } else {
-          this.livePlayerContext.exitFullScreen({
-            success: () => {
-              console.log('退出全屏模式成功')
-            },
-            fail: (err) => {
-              console.error('退出全屏模式失败:', err)
-            }
-          })
-        }
-      }
-      // #endif
-    },
-    
-    // 截图
-    takeScreenshot() {
-      // #ifdef H5
-      if (this.$refs.jessibucaRef && this.isPlaying) {
-        this.$refs.jessibucaRef.screenshot()
-        uni.showToast({
-          title: '截图已保存',
-          icon: 'success'
-        })
-      } else {
-        uni.showToast({
-          title: '请先播放视频',
-          icon: 'none'
-        })
-      }
-      // #endif
-      
-      // #ifdef MP-WEIXIN
-      if (this.livePlayerContext && this.isPlaying) {
-        this.livePlayerContext.snapshot({
-          success: (res) => {
-            console.log('截图成功:', res.tempImagePath)
-            // 可以保存到相册或做其他处理
-            uni.saveImageToPhotosAlbum({
-              filePath: res.tempImagePath,
-              success: () => {
-                uni.showToast({
-                  title: '截图已保存到相册',
-                  icon: 'success'
-                })
-              },
-              fail: (err) => {
-                console.error('保存截图失败:', err)
-                uni.showToast({
-                  title: '保存截图失败',
-                  icon: 'none'
-                })
-              }
-            })
-          },
-          fail: (err) => {
-            console.error('截图失败:', err)
-            uni.showToast({
-              title: '截图失败',
-              icon: 'none'
-            })
-          }
-        })
-      } else {
-        uni.showToast({
-          title: '请先播放视频',
-          icon: 'none'
-        })
-      }
-      // #endif
-    },
-    
-    // 小程序播放器状态变化处理
-    onStateChange(e) {
-      console.log('播放器状态变化:', e.detail)
-      const state = e.detail.code
-      // 根据状态码处理
-      switch (state) {
-        case 2001: // 已连接服务器
-          console.log('已连接服务器')
-          break
-        case 2002: // 已连接服务器,开始拉流
-          console.log('开始拉流')
-          break
-        case 2003: // 网络接收到首个视频帧
-          console.log('网络接收到首个视频帧')
-          break
-        case 2004: // 视频播放开始
-          console.log('视频播放开始')
-          break
-        case 2005: // 视频播放进度
-          console.log('视频播放进度')
-          break
-        case 2006: // 视频播放结束
-          console.log('视频播放结束')
-          this.isPlaying = false
-          break
-        case 2007: // 视频播放Loading
-          console.log('视频播放Loading')
-          break
-        case 2008: // 解码器启动
-          console.log('解码器启动')
-          break
-        case 2009: // 视频分辨率改变
-          console.log('视频分辨率改变')
-          break
-        case -2301: // 网络断连,且重新连接亦不能恢复,播放器已停止
-          console.error('网络断连,且重新连接亦不能恢复,播放器已停止')
-          this.isPlaying = false
-          uni.showToast({
-            title: '网络断连,请重试',
-            icon: 'none'
-          })
-          break
-        case -2302: // 获取加速拉流地址失败
-          console.error('获取加速拉流地址失败')
-          break
-        case 2101: // 当前视频帧解码失败
-          console.error('当前视频帧解码失败')
-          break
-        case 2102: // 当前音频帧解码失败
-          console.error('当前音频帧解码失败')
-          break
-        case 2103: // 网络断连, 已启动自动重连
-          console.warn('网络断连, 已启动自动重连')
-          break
-        case 2104: // 网络断连, 重连中...
-          console.warn('网络断连, 重连中...')
-          break
-        case 2105: // 网络断连, 重连成功
-          console.log('网络断连, 重连成功')
-          break
-        case 2106: // 网络断连, 重连失败
-          console.error('网络断连, 重连失败')
-          break
-        case 2107: // 播放器连接超时
-          console.error('播放器连接超时')
-          break
-        case 2108: // 获取点播文件信息失败
-          console.error('获取点播文件信息失败')
-          break
-        default:
-          console.log('其他状态:', state)
-      }
-    },
-    
-    // 小程序全屏状态变化
-    onFullscreenChange(e) {
-      this.isFullscreen = e.detail.fullScreen
-      console.log('全屏状态变化:', this.isFullscreen)
-    },
-    
-    // 初始化流地址
-    initStreamUrl() {
-      const originalUrl = this.deviceInfo.originalStreamUrl
-      // 使用配置中的流服务器信息
-      const streamUrls = buildPlatformStreamUrls(originalUrl, {
-        streamServer: config.streamServer,
-        fallbackToHls: true
-      })
-      
-      // #ifdef H5
-      this.deviceInfo.streamUrl = streamUrls.h5Url
-      // #endif
-      
-      // #ifdef MP-WEIXIN
-      // 检查是否可以在小程序中播放
-      if (streamUrls.miniProgramUrl) {
-        this.deviceInfo.streamUrl = streamUrls.miniProgramUrl
-      } else {
-        // 如果没有可用的小程序播放地址,显示提示
-        uni.showToast({
-          title: '当前视频流不支持小程序播放',
-          icon: 'none',
-          duration: 3000
-        })
-      }
-      // #endif
-      
-      console.log('初始化流地址:', this.deviceInfo.streamUrl)
-    },
-    
-    // 切换九宫格视图
-    toggleGridView() {
-      this.isGridView = !this.isGridView
-      uni.showToast({
-        title: this.isGridView ? '切换到多画面模式' : '切换到单画面模式',
-        icon: 'none'
-      })
-    },
-    
-    // 语音对讲
-    toggleVoiceIntercom() {
-      this.isVoiceActive = !this.isVoiceActive
-      
-      if (this.isVoiceActive) {
-        uni.showToast({
-          title: '语音对讲已开启',
-          icon: 'none'
-        })
-      } else {
-        uni.showToast({
-          title: '语音对讲已关闭',
-          icon: 'none'
-        })
-      }
-    },
-    
-    // 云台控制
-    controlPTZ(direction, isStart) {
-      // 模拟云台控制
-      if (isStart) {
-        console.log(`开始控制云台: ${direction}`)
-        uni.vibrateShort() // 短震动反馈
-      } else {
-        console.log(`停止控制云台: ${direction}`)
-      }
-    },
-    
-    // 云台复位
-    resetPTZ() {
-      console.log('云台复位')
-      uni.vibrateShort() // 短震动反馈
-      uni.showToast({
-        title: '云台已复位',
-        icon: 'none'
-      })
-    },
-    
-    // 变焦控制
-    controlZoom(type, isStart) {
-      // 模拟变焦控制
-      if (isStart) {
-        console.log(`开始控制变焦: ${type}`)
-        uni.vibrateShort() // 短震动反馈
-      } else {
-        console.log(`停止控制变焦: ${type}`)
-      }
-    },
-    
-    // 切换缩放模式
-    toggleZoom() {
-      this.isZoomMode = !this.isZoomMode
-      uni.showToast({
-        title: this.isZoomMode ? '进入缩放模式' : '退出缩放模式',
-        icon: 'none'
-      })
-    },
-    
-    // 添加预设位
-    addPreset() {
-      uni.showModal({
-        title: '添加预设位',
-        content: '是否保存当前位置为预设位?',
-        success: (res) => {
-          if (res.confirm) {
-            uni.showToast({
-              title: '预设位已保存',
-              icon: 'success'
-            })
-          }
-        }
-      })
-    },
-    
-    // 处理告警点击
-    handleAlert(item) {
-      uni.navigateTo({
-        url: `/pages/alerts/alert-detail?alertId=${item.id}&deviceId=${this.deviceInfo.deviceId}`
-      })
-    },
-    
-    // 跳转到历史视频页面
-    navigateToHistory() {
-      uni.navigateTo({
-        url: `/pages/video/history?deviceId=${this.deviceInfo.deviceId}`
-      })
-    },
-    
-    // 视频播放错误处理
-    onVideoError(e) {
-      console.error('视频播放错误:', e);
-      uni.showToast({
-        title: '视频加载失败,请检查网络连接',
-        icon: 'none',
-        duration: 2000
-      });
-      
-      // 在H5环境,尝试重新加载或使用备用流
-      // #ifdef H5
-      setTimeout(() => {
-        if (this.isPlaying && this.$refs.jessibucaRef) {
-          console.log('尝试重新加载视频流');
-          // 可以尝试使用备用流地址
-          if (this.deviceInfo.originalStreamUrl !== config.streamServer.wsFlvServer) {
-            this.deviceInfo.streamUrl = config.streamServer.wsFlvServer;
-            this.$nextTick(() => {
-              if (this.$refs.jessibucaRef) {
-                this.$refs.jessibucaRef.play();
-              }
-            });
-          }
-        }
-      }, 3000);
-      // #endif
-    },
-    
-    // 更新时间
-    startTimeUpdate() {
-      // 模拟时间更新
-      setInterval(() => {
-        const now = new Date()
-        const hours = String(now.getHours()).padStart(2, '0')
-        const minutes = String(now.getMinutes()).padStart(2, '0')
-        const seconds = String(now.getSeconds()).padStart(2, '0')
-        this.currentTime = `${hours}:${minutes}:${seconds}`
-      }, 1000)
-    },
-	// 设置全屏状态监听
-	    setupFullscreenListener() {
-	      this.fullscreenChangeHandler = () => {
-	        const isFullscreen = !!(
-	          document.fullscreenElement ||
-	          document.mozFullScreenElement ||
-	          document.webkitFullscreenElement ||
-	          document.msFullscreenElement
-	        );
-	        
-	        if (this.isFullscreen !== isFullscreen) {
-	          this.isFullscreen = isFullscreen;
-	        }
-	      };
-	      
-	      // document.addEventListener('fullscreenchange', this.fullscreenChangeHandler);
-	      // document.addEventListener('webkitfullscreenchange', this.fullscreenChangeHandler);
-	      // document.addEventListener('mozfullscreenchange', this.fullscreenChangeHandler);
-	      // document.addEventListener('MSFullscreenChange', this.fullscreenChangeHandler);
-	    },
-	    
-	    // 移除全屏状态监听
-	    removeFullscreenListener() {
-	      if (this.fullscreenChangeHandler) {
-	        // document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler);
-	        // document.removeEventListener('webkitfullscreenchange', this.fullscreenChangeHandler);
-	        // document.removeEventListener('mozfullscreenchange', this.fullscreenChangeHandler);
-	        // document.removeEventListener('MSFullscreenChange', this.fullscreenChangeHandler);
-	      }
-	    },
-	    
-	    // 设置键盘监听
-	    setupKeyboardListener() {
-	      this.keydownHandler = (event) => {
-	        // ESC键退出全屏
-	        if (event.key === 'Escape' && this.isFullscreen) {
-	          this.setFullscreen(false);
-	        }
-	        
-	        // F键切换全屏
-	        if (event.key === 'f' || event.key === 'F') {
-	          this.toggleFullscreen();
-	          event.preventDefault();
-	        }
-	      };
-	      
-	      // document.addEventListener('keydown', this.keydownHandler);
-	    },
-	    
-	    // 移除键盘监听
-	    removeKeyboardListener() {
-	      if (this.keydownHandler) {
-	        // document.removeEventListener('keydown', this.keydownHandler);
-	      }
-	    }
-  }
-}
+		},
+
+		computed: {
+			// 获取所有未处理的告警
+			getUnhandledAlerts() {
+				return this.alertHistory.filter(alert => alert.status === 0);
+			},
+			// 获取H5环境使用的流地址
+			getH5StreamUrl() {
+				// 首先尝试使用设备返回的流URL
+				// if (this.deviceInfo.streamUrl) {
+				//   return this.deviceInfo.streamUrl;
+				// }
+
+				// 如果没有设备特定的URL,则使用默认原始流
+				return this.deviceInfo.originalStreamUrl || config.streamServer.wsFlvServer;
+			},
+
+			// 获取小程序环境使用的流地址
+			getMiniProgramStreamUrl() {
+				// 获取合适的小程序流地址
+				const {
+					streamServer
+				} = config;
+
+				// 如果有设备特定的RTMP流,优先使用
+				if (this.deviceInfo.rtmpUrl) {
+					return this.deviceInfo.rtmpUrl;
+				}
+
+				// 其次尝试使用HLS流
+				if (this.deviceInfo.hlsUrl) {
+					return this.deviceInfo.hlsUrl;
+				}
+
+				// 最后使用配置的默认流
+				return streamServer.rtmpServer || streamServer.hlsServer;
+			},
+		},
+
+		onReady() {
+			// 设置页面标题
+			uni.setNavigationBarTitle({
+				title: this.deviceInfo.name
+			})
+
+			// 模拟更新时间
+			this.startTimeUpdate()
+
+			// #ifdef MP-WEIXIN
+			// 创建小程序视频上下文
+			this.livePlayerContext = uni.createLivePlayerContext('videoPlayer', this)
+			// #endif
+
+			// #ifdef H5
+			// 加载Jessibuca的JS库
+			this.loadJessibucaScript()
+			// #endif
+
+			// 监听全屏状态变化
+			this.setupFullscreenListener()
+
+			// 添加键盘事件监听
+			this.setupKeyboardListener()
+
+			// 初始化流地址
+			this.initStreamUrl()
+		},
+		beforeDestroy() {
+			console.log("停止点播");
+			// 停止点播
+			pause(this.deviceInfo.deviceId, this.deviceInfo.channelId).then(res => {
+				if (res[1].data.code !== 0) {
+					console.error('暂停失败:', res.message)
+					// uni.showToast({
+					// 	title: '暂停失败: ' + res.message,
+					// 	icon: 'none'
+					// })
+				} else {
+					console.log('视频已暂停')
+					// uni.showToast({
+					//   title: '视频已暂停',
+					//   icon: 'none',
+					//   duration: 1500
+					// })
+				}
+			}).catch(err => {
+				console.error('暂停请求错误:', err)
+			})
+		},
+
+		onLoad(options) {
+			uni.$once('passDeviceData', (data) => {
+				console.log('接收到数据', data);
+				// 如果有传入设备ID,则获取设备信息
+				if (data && data.deviceId) {
+					this.deviceInfo.deviceId = data.deviceId;
+					this.deviceInfo.location = data.fieldName;
+					this.deviceInfo.name = data.deviceName;
+					this.deviceInfo.status = data.status;
+					this.deviceInfo.deviceTypeId = data.deviceTypeId || '';
+
+					// 加载设备详情
+					this.fetchDeviceInfo();
+					this.queryChannels();
+				}
+			});
+		},
+
+		methods: {
+			formatDate,
+			formatSmartTime,
+			// 加载Jessibuca脚本
+			loadJessibucaScript() {
+				const script = document.createElement('script')
+				script.src = '/static/js/jessibuca/jessibuca.js'
+				script.onload = () => {
+					console.log('Jessibuca 脚本加载成功')
+				}
+				script.onerror = (error) => {
+					console.error('Jessibuca 脚本加载失败:', error)
+				}
+				document.head.appendChild(script)
+			},
+			// 获取设备信息
+			fetchDeviceInfo(deviceId) {
+				// 这里应该是API请求,暂时用模拟数据
+				console.log('获取设备信息:', deviceId)
+				getDeviceCollectorDetail(this.deviceInfo.deviceId)
+					.then(res => {
+						if (res.data.data && res.data.code === 200) {
+							const detail = res.data.data;
+							this.deviceInfo.lastUpdate = getFormattedTime()
+							// 更新页面标题
+							uni.setNavigationBarTitle({
+								title: this.deviceInfo.name
+							});
+							// 更新告警信息
+							if (detail.alertRecordList && detail.alertRecordList.length > 0) {
+								this.alertHistory = detail.alertRecordList.map(alert => ({
+									id: alert.alertId,
+									time: alert.alertTime,
+									type: alert.alertContent,
+									status: alert.processStatus,
+									level: alert.alertLevel
+								}));
+							}
+						}
+					})
+			},
+
+			// 根据设备id获取通道列表
+			queryChannels() {
+				getChannels(this.deviceInfo.deviceId)
+					.then(response => {
+						console.log('获取通道列表:', response);
+						const res = response[1].data
+						if (res.code === 0 && res.data.total > 0) {
+							const channels = res.data.list;
+							this.deviceInfo.channelId = channels[0].deviceId; // 默认选择第一个通道
+							playStart(this.deviceInfo.deviceId, this.deviceInfo.channelId).then(res => {
+								if (res[1].data.code !== 0) {
+									console.error('播放开始失败:', res.message);
+									uni.showToast({
+										title: '播放失败: ' + res.message,
+										icon: 'none'
+									});
+									return;
+								}
+								console.log('播放开始:', res);
+								this.deviceInfo.originalStreamUrl = res[1].data.data.ws_flv || this.deviceInfo
+									.originalStreamUrl;
+							}).catch(err => {
+								console.error('播放开始失败:', err);
+							})
+						} else {
+							uni.showToast({
+								title: '获取通道列表失败: ' + res.message,
+								icon: 'none'
+							});
+							return;
+						}
+					})
+					.catch(err => {
+						console.error('获取通道列表错误:', err);
+					});
+			},
+			// 播放/暂停切换
+			togglePlayState() {
+				if (!this.isPlaying) {
+					// 从未播放状态切换到播放状态
+					this.isPlaying = true
+
+					// #ifdef MP-WEIXIN
+					// 小程序环境中,短暂延迟确保组件已加载
+					setTimeout(() => {
+						if (this.livePlayerContext) {
+							this.livePlayerContext.play({
+								success: () => {
+									console.log('小程序视频播放成功')
+									uni.vibrateShort()
+								},
+								fail: (err) => {
+									console.error('小程序视频播放失败:', err)
+									uni.showToast({
+										title: '视频播放失败',
+										icon: 'none'
+									})
+								}
+							})
+						}
+					}, 300)
+					// #endif
+
+					// #ifdef H5
+					// 浏览器环境中使用Jessibuca
+					setTimeout(() => {
+						playStart(this.deviceInfo.deviceId, this.deviceInfo.channelId).then(res => {
+							if (res[1].data.code !== 0) {
+								console.error('播放开始失败:', res.message);
+								uni.showToast({
+									title: '播放失败: ' + res.message,
+									icon: 'none'
+								});
+								return;
+							}
+							console.log('播放开始:', res[1]);
+							this.deviceInfo.originalStreamUrl = res[1].data.data.ws_flv || this.deviceInfo
+								.originalStreamUrl;
+						}).catch(err => {
+							console.error('播放开始失败:', err);
+						})
+						uni.vibrateShort()
+					}, 300)
+					// #endif
+				} else {
+					// 从播放状态切换到暂停状态
+					// #ifdef H5
+					if (this.$refs.jessibucaRef) {
+						this.$refs.jessibucaRef.pause() // 使用组件的暂定
+					}
+					// #endif
+
+					// #ifdef MP-WEIXIN
+					if (this.livePlayerContext) {
+						this.livePlayerContext.pause()
+					}
+					// #endif
+
+					this.isPlaying = false
+
+					// 在页面上显示暂停状态
+					uni.showToast({
+						title: '视频已暂停',
+						icon: 'none',
+						duration: 1500
+					})
+				}
+			},
+
+			// 静音切换
+			toggleMute() {
+				this.isMuted = !this.isMuted
+
+				// #ifdef H5
+				if (this.$refs.jessibucaRef) {
+					if (this.isMuted) {
+						this.$refs.jessibucaRef.mute()
+					} else {
+						this.$refs.jessibucaRef.cancelMute()
+					}
+				}
+				// #endif
+
+				// #ifdef MP-WEIXIN
+				// 小程序环境中直接通过属性绑定控制静音
+				// #endif
+			},
+
+			// 全屏切换
+			toggleFullscreen() {
+				if (!this.isPlaying) {
+					// 如果视频未播放,先开始播放
+					this.togglePlayState()
+					// 延迟执行全屏操作,等待视频元素加载
+					setTimeout(() => {
+						this.setFullscreen(true)
+					}, 500)
+				} else {
+					this.setFullscreen(!this.isFullscreen)
+				}
+			},
+
+			// 设置全屏状态
+			setFullscreen(fullscreen) {
+				this.isFullscreen = fullscreen
+
+				// #ifdef H5
+				// 浏览器环境
+				if (this.isFullscreen) {
+					// 如果是移动设备,尝试使用系统全屏
+					const ua = navigator.userAgent.toLowerCase()
+					const isMobile = /mobile|android|iphone|ipad/.test(ua)
+
+					if (isMobile && this.$refs.jessibucaRef) {
+						// 在移动设备上使用Jessibuca的全屏API
+						this.$refs.jessibucaRef.fullscreenSwich()
+					} else {
+						// 适配屏幕尺寸
+						setTimeout(() => {
+							if (this.$refs.jessibucaRef) {
+								this.$refs.jessibucaRef.resize()
+							}
+						}, 300)
+					}
+
+					// 锁定屏幕方向为横屏
+					if (window.screen && window.screen.orientation && window.screen.orientation.lock) {
+						window.screen.orientation.lock('landscape').catch(err => {
+							console.error('无法锁定屏幕方向:', err)
+						})
+					}
+				} else {
+					// 退出全屏状态
+					if (this.$refs.jessibucaRef) {
+						// 在某些情况下需要先调用Jessibuca的退出全屏
+						if (this.$refs.jessibucaRef.isFullscreen()) {
+							this.$refs.jessibucaRef.fullscreenSwich()
+						}
+
+						// 重置视频大小
+						setTimeout(() => {
+							if (this.$refs.jessibucaRef) {
+								this.$refs.jessibucaRef.resize()
+							}
+						}, 300)
+					}
+
+					// 解除屏幕方向锁定
+					if (window.screen && window.screen.orientation && window.screen.orientation.unlock) {
+						window.screen.orientation.unlock()
+					}
+				}
+				// #endif
+
+				// #ifdef MP-WEIXIN
+				// 小程序环境
+				if (this.livePlayerContext) {
+					if (this.isFullscreen) {
+						this.livePlayerContext.requestFullScreen({
+							direction: 90, // 横屏
+							success: () => {
+								console.log('进入全屏模式成功')
+							},
+							fail: (err) => {
+								console.error('进入全屏模式失败:', err)
+							}
+						})
+					} else {
+						this.livePlayerContext.exitFullScreen({
+							success: () => {
+								console.log('退出全屏模式成功')
+							},
+							fail: (err) => {
+								console.error('退出全屏模式失败:', err)
+							}
+						})
+					}
+				}
+				// #endif
+			},
+
+			// 截图
+			takeScreenshot() {
+				// #ifdef H5
+				if (this.$refs.jessibucaRef && this.isPlaying) {
+					this.$refs.jessibucaRef.screenshot()
+					uni.showToast({
+						title: '截图已保存',
+						icon: 'success'
+					})
+				} else {
+					uni.showToast({
+						title: '请先播放视频',
+						icon: 'none'
+					})
+				}
+				// #endif
+
+				// #ifdef MP-WEIXIN
+				if (this.livePlayerContext && this.isPlaying) {
+					this.livePlayerContext.snapshot({
+						success: (res) => {
+							console.log('截图成功:', res.tempImagePath)
+							// 可以保存到相册或做其他处理
+							uni.saveImageToPhotosAlbum({
+								filePath: res.tempImagePath,
+								success: () => {
+									uni.showToast({
+										title: '截图已保存到相册',
+										icon: 'success'
+									})
+								},
+								fail: (err) => {
+									console.error('保存截图失败:', err)
+									uni.showToast({
+										title: '保存截图失败',
+										icon: 'none'
+									})
+								}
+							})
+						},
+						fail: (err) => {
+							console.error('截图失败:', err)
+							uni.showToast({
+								title: '截图失败',
+								icon: 'none'
+							})
+						}
+					})
+				} else {
+					uni.showToast({
+						title: '请先播放视频',
+						icon: 'none'
+					})
+				}
+				// #endif
+			},
+
+			// 小程序播放器状态变化处理
+			onStateChange(e) {
+				console.log('播放器状态变化:', e.detail)
+				const state = e.detail.code
+				// 根据状态码处理
+				switch (state) {
+					case 2001: // 已连接服务器
+						console.log('已连接服务器')
+						break
+					case 2002: // 已连接服务器,开始拉流
+						console.log('开始拉流')
+						break
+					case 2003: // 网络接收到首个视频帧
+						console.log('网络接收到首个视频帧')
+						break
+					case 2004: // 视频播放开始
+						console.log('视频播放开始')
+						break
+					case 2005: // 视频播放进度
+						console.log('视频播放进度')
+						break
+					case 2006: // 视频播放结束
+						console.log('视频播放结束')
+						this.isPlaying = false
+						break
+					case 2007: // 视频播放Loading
+						console.log('视频播放Loading')
+						break
+					case 2008: // 解码器启动
+						console.log('解码器启动')
+						break
+					case 2009: // 视频分辨率改变
+						console.log('视频分辨率改变')
+						break
+					case -2301: // 网络断连,且重新连接亦不能恢复,播放器已停止
+						console.error('网络断连,且重新连接亦不能恢复,播放器已停止')
+						this.isPlaying = false
+						uni.showToast({
+							title: '网络断连,请重试',
+							icon: 'none'
+						})
+						break
+					case -2302: // 获取加速拉流地址失败
+						console.error('获取加速拉流地址失败')
+						break
+					case 2101: // 当前视频帧解码失败
+						console.error('当前视频帧解码失败')
+						break
+					case 2102: // 当前音频帧解码失败
+						console.error('当前音频帧解码失败')
+						break
+					case 2103: // 网络断连, 已启动自动重连
+						console.warn('网络断连, 已启动自动重连')
+						break
+					case 2104: // 网络断连, 重连中...
+						console.warn('网络断连, 重连中...')
+						break
+					case 2105: // 网络断连, 重连成功
+						console.log('网络断连, 重连成功')
+						break
+					case 2106: // 网络断连, 重连失败
+						console.error('网络断连, 重连失败')
+						break
+					case 2107: // 播放器连接超时
+						console.error('播放器连接超时')
+						break
+					case 2108: // 获取点播文件信息失败
+						console.error('获取点播文件信息失败')
+						break
+					default:
+						console.log('其他状态:', state)
+				}
+			},
+
+			// 小程序全屏状态变化
+			onFullscreenChange(e) {
+				this.isFullscreen = e.detail.fullScreen
+				console.log('全屏状态变化:', this.isFullscreen)
+			},
+
+			// 初始化流地址
+			initStreamUrl() {
+				const originalUrl = this.deviceInfo.originalStreamUrl
+				// 使用配置中的流服务器信息
+				const streamUrls = buildPlatformStreamUrls(originalUrl, {
+					streamServer: config.streamServer,
+					fallbackToHls: true
+				})
+
+				// #ifdef H5
+				this.deviceInfo.streamUrl = streamUrls.h5Url
+				// #endif
+
+				// #ifdef MP-WEIXIN
+				// 检查是否可以在小程序中播放
+				if (streamUrls.miniProgramUrl) {
+					this.deviceInfo.streamUrl = streamUrls.miniProgramUrl
+				} else {
+					// 如果没有可用的小程序播放地址,显示提示
+					uni.showToast({
+						title: '当前视频流不支持小程序播放',
+						icon: 'none',
+						duration: 3000
+					})
+				}
+				// #endif
+
+				console.log('初始化流地址:', this.deviceInfo.streamUrl)
+			},
+
+			// 切换九宫格视图
+			toggleGridView() {
+				this.isGridView = !this.isGridView
+				uni.showToast({
+					title: this.isGridView ? '切换到多画面模式' : '切换到单画面模式',
+					icon: 'none'
+				})
+			},
+
+			// 语音对讲
+			toggleVoiceIntercom() {
+				this.isVoiceActive = !this.isVoiceActive
+
+				if (this.isVoiceActive) {
+					uni.showToast({
+						title: '语音对讲已开启',
+						icon: 'none'
+					})
+				} else {
+					uni.showToast({
+						title: '语音对讲已关闭',
+						icon: 'none'
+					})
+				}
+			},
+
+			// 云台控制
+			controlPTZ(direction, isStart) {
+				// 模拟云台控制
+				if (isStart) {
+					console.log(`开始控制云台: ${direction}`)
+					uni.vibrateShort() // 短震动反馈
+				} else {
+					console.log(`停止控制云台: ${direction}`)
+				}
+			},
+
+			// 云台复位
+			resetPTZ() {
+				console.log('云台复位')
+				uni.vibrateShort() // 短震动反馈
+				uni.showToast({
+					title: '云台已复位',
+					icon: 'none'
+				})
+			},
+
+			// 变焦控制
+			controlZoom(type, isStart) {
+				// 模拟变焦控制
+				if (isStart) {
+					console.log(`开始控制变焦: ${type}`)
+					uni.vibrateShort() // 短震动反馈
+				} else {
+					console.log(`停止控制变焦: ${type}`)
+				}
+			},
+
+			// 切换缩放模式
+			toggleZoom() {
+				this.isZoomMode = !this.isZoomMode
+				uni.showToast({
+					title: this.isZoomMode ? '进入缩放模式' : '退出缩放模式',
+					icon: 'none'
+				})
+			},
+
+			// 添加预设位
+			addPreset() {
+				uni.showModal({
+					title: '添加预设位',
+					content: '是否保存当前位置为预设位?',
+					success: (res) => {
+						if (res.confirm) {
+							uni.showToast({
+								title: '预设位已保存',
+								icon: 'success'
+							})
+						}
+					}
+				})
+			},
+
+			// 处理告警点击
+			handleAlert(item) {
+				uni.navigateTo({
+					url: `/pages/alerts/alert-detail?alertId=${item.id}&deviceId=${this.deviceInfo.deviceId}`
+				})
+			},
+
+			// 跳转到历史视频页面
+			navigateToHistory() {
+				uni.navigateTo({
+					url: `/pages/video/history?deviceId=${this.deviceInfo.deviceId}`
+				})
+			},
+
+			// 视频播放错误处理
+			onVideoError(e) {
+				console.error('视频播放错误:', e);
+				uni.showToast({
+					title: '视频加载失败,请检查网络连接',
+					icon: 'none',
+					duration: 2000
+				});
+
+				// 在H5环境,尝试重新加载或使用备用流
+				// #ifdef H5
+				setTimeout(() => {
+					if (this.isPlaying && this.$refs.jessibucaRef) {
+						console.log('尝试重新加载视频流');
+						// 可以尝试使用备用流地址
+						if (this.deviceInfo.originalStreamUrl !== config.streamServer.wsFlvServer) {
+							this.deviceInfo.streamUrl = config.streamServer.wsFlvServer;
+							this.$nextTick(() => {
+								if (this.$refs.jessibucaRef) {
+									this.$refs.jessibucaRef.play();
+								}
+							});
+						}
+					}
+				}, 3000);
+				// #endif
+			},
+
+			// 更新时间
+			startTimeUpdate() {
+				// 模拟时间更新
+				setInterval(() => {
+					const now = new Date()
+					const hours = String(now.getHours()).padStart(2, '0')
+					const minutes = String(now.getMinutes()).padStart(2, '0')
+					const seconds = String(now.getSeconds()).padStart(2, '0')
+					this.currentTime = `${hours}:${minutes}:${seconds}`
+				}, 1000)
+			},
+			// 设置全屏状态监听
+			setupFullscreenListener() {
+				this.fullscreenChangeHandler = () => {
+					const isFullscreen = !!(
+						document.fullscreenElement ||
+						document.mozFullScreenElement ||
+						document.webkitFullscreenElement ||
+						document.msFullscreenElement
+					);
+
+					if (this.isFullscreen !== isFullscreen) {
+						this.isFullscreen = isFullscreen;
+					}
+				};
+
+				// document.addEventListener('fullscreenchange', this.fullscreenChangeHandler);
+				// document.addEventListener('webkitfullscreenchange', this.fullscreenChangeHandler);
+				// document.addEventListener('mozfullscreenchange', this.fullscreenChangeHandler);
+				// document.addEventListener('MSFullscreenChange', this.fullscreenChangeHandler);
+			},
+
+			// 设置键盘监听
+			setupKeyboardListener() {
+				this.keydownHandler = (event) => {
+					// ESC键退出全屏
+					if (event.key === 'Escape' && this.isFullscreen) {
+						this.setFullscreen(false);
+					}
+
+					// F键切换全屏
+					if (event.key === 'f' || event.key === 'F') {
+						this.toggleFullscreen();
+						event.preventDefault();
+					}
+				};
+
+				// document.addEventListener('keydown', this.keydownHandler);
+			}
+		}
+	}
 </script>
 
 <style>
-/* 基础样式 */
-.container {
-  display: flex;
-  flex-direction: column;
-  min-height: 100vh;
-  background-color: #F8FCF9;
-  padding-bottom: 30rpx;
-}
-
-/* 设备头部样式 */
-.device-header {
-  background-color: #FFFFFF;
-  border-radius: 20rpx;
-  padding: 26rpx 30rpx 30rpx;
-  margin: 20rpx 30rpx 20rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
-}
-
-.device-info-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-start;
-  margin-bottom: 24rpx;
-}
-
-.device-name-container {
-  display: flex;
-  flex-direction: column;
-  align-items: flex-start;
-}
-
-.device-name {
-  font-size: 34rpx;
-  color: #333333;
-  font-weight: 600;
-  margin-bottom: 10rpx;
-}
-
-.status-tag {
-  padding: 4rpx 12rpx 4rpx 24rpx;
-  border-radius: 30rpx;
-  font-size: 22rpx;
-  font-weight: 500;
-  flex-shrink: 0;
-  position: relative;
-  overflow: hidden;
-}
-
-.status-online {
-  background-color: rgba(76, 175, 80, 0.1);
-  color: #3BB44A;
-}
-
-.status-offline {
-  background-color: rgba(245, 108, 108, 0.1);
-  color: #F56C6C;
-  padding-left: 24rpx;
-}
-
-.status-dot {
-  position: absolute;
-  width: 6rpx;
-  height: 6rpx;
-  background-color: #3BB44A;
-  border-radius: 50%;
-  top: 50%;
-  left: 12rpx;
-  transform: translateY(-50%);
-  box-shadow: 0 0 4rpx rgba(76, 175, 80, 0.8);
-  animation: blink 1.5s infinite;
-  display: inline-block;
-}
-
-.status-dot.offline-dot {
-  background-color: #F56C6C;
-  box-shadow: 0 0 4rpx rgba(245, 108, 108, 0.8);
-}
-
-@keyframes blink {
-  0% {
-    opacity: 0.4;
-  }
-  50% {
-    opacity: 1;
-  }
-  100% {
-    opacity: 0.4;
-  }
-}
-
-.device-meta-row {
-  display: flex;
-  flex-direction: column;
-}
-
-.device-meta-item {
-  display: flex;
-  align-items: center;
-  font-size: 26rpx;
-  margin-top: 18rpx;
-}
-
-.meta-icon {
-  width: 36rpx;
-  height: 36rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 8rpx;
-}
-
-.meta-label {
-  color: #999999;
-  min-width: 140rpx;
-  font-size: 26rpx;
-}
-
-.meta-value {
-  color: #333333;
-  flex: 1;
-  font-size: 26rpx;
-}
-
-/* 视频预览区域 */
-.video-section {
-  margin: 0 30rpx 20rpx;
-  position: relative;
-  z-index: 1;
-}
-
-.video-container {
-  position: relative;
-  width: 100%;
-  height: 420rpx; /* 16:9比例 */
-  background-color: #000000;
-  border-radius: 16rpx;
-  overflow: hidden;
-  box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
-  transition: all 0.3s ease;
-}
-
-.video-container.fullscreen-mode {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100vw;
-  height: 100vh;
-  margin: 0;
-  z-index: 9999;
-  border-radius: 0;
-}
-
-.video-container.fullscreen-mode .video-controls {
-  padding: 30rpx;
-}
-
-.video-container.fullscreen-mode .top-controls,
-.video-container.fullscreen-mode .bottom-controls {
-  opacity: 0;
-  transition: opacity 0.3s ease;
-}
-
-.video-container.fullscreen-mode:hover .top-controls,
-.video-container.fullscreen-mode:hover .bottom-controls {
-  opacity: 1;
-}
-
-.video-player, .video-placeholder {
-  width: 100%;
-  height: 100%;
-  object-fit: contain;
-}
-
-.video-controls {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-  padding: 20rpx;
-  box-sizing: border-box;
-  background: linear-gradient(to bottom, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 70%, rgba(0,0,0,0.4) 100%);
-}
-
-.control-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  width: 100%;
-}
-
-.top-controls {
-  height: 80rpx;
-}
-
-.center-controls {
-  height: 120rpx;
-  justify-content: center;
-  align-items: center;
-}
-
-.bottom-controls {
-  height: 80rpx;
-}
-
-.signal-indicator {
-  display: flex;
-  align-items: center;
-  color: #FFFFFF;
-  font-size: 24rpx;
-  background-color: rgba(0, 0, 0, 0.5);
-  padding: 8rpx 16rpx;
-  border-radius: 30rpx;
-}
-
-.signal-indicator svg {
-  margin-right: 8rpx;
-}
-
-.signal-text {
-  font-weight: 500;
-}
-
-.fullscreen-button {
-  width: 60rpx;
-  height: 60rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: #FFFFFF;
-  background-color: rgba(0, 0, 0, 0.5);
-  border-radius: 50%;
-  transition: all 0.2s;
-}
-
-.fullscreen-button:active {
-  background-color: rgba(76, 175, 80, 0.7);
-  transform: scale(0.9);
-}
-
-.video-time {
-  color: #FFFFFF;
-  font-size: 26rpx;
-  background-color: rgba(0, 0, 0, 0.5);
-  padding: 6rpx 16rpx;
-  border-radius: 30rpx;
-  font-weight: 500;
-}
-
-.play-button {
-  width: 100rpx;
-  height: 100rpx;
-  border-radius: 50%;
-  background-color: rgba(255, 255, 255, 0.9);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.5);
-  transition: all 0.2s;
-  transform: scale(1);
-}
-
-.play-button:active {
-  transform: scale(0.92);
-  background-color: rgba(255, 255, 255, 1);
-}
-
-.center-button-container {
-  width: 100%;
-  height: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.pause-icon {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 50%;
-  background-color: rgba(0, 0, 0, 0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  opacity: 0;
-  transition: opacity 0.3s;
-}
-
-.center-button-container:active .pause-icon {
-  opacity: 1;
-  background-color: rgba(76, 175, 80, 0.7);
-}
-
-/* 云台控制区域 */
-.ptz-section {
-  margin: 0 30rpx 20rpx;
-  background-color: #FFFFFF;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
-}
-
-.section-title {
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333333;
-  margin-bottom: 20rpx;
-  padding: 0 10rpx;
-  display: flex;
-  align-items: center;
-}
-
-.alert-badge {
-  background-color: #F56C6C;
-  color: #FFFFFF;
-  font-size: 22rpx;
-  border-radius: 30rpx;
-  padding: 2rpx 12rpx;
-  margin-left: 12rpx;
-  font-weight: normal;
-  min-width: 32rpx;
-  text-align: center;
-}
-
-.ptz-container {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 20rpx 0;
-}
-
-.ptz-circle-container {
-  position: relative;
-  width: 300rpx;
-  height: 300rpx;
-  margin: 20rpx 0;
-}
-
-.ptz-arrow {
-  position: absolute;
-  width: 70rpx;
-  height: 70rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background-color: #F0F9F0;
-  border-radius: 50%;
-  color: #3BB44A;
-  font-size: 36rpx;
-  transition: all 0.2s;
-  box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.15);
-}
-
-.ptz-up {
-  top: 0;
-  left: 50%;
-  transform: translateX(-50%);
-}
-
-.ptz-down {
-  bottom: 0;
-  left: 50%;
-  transform: translateX(-50%);
-}
-
-.ptz-left {
-  left: 0;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-.ptz-right {
-  right: 0;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-.ptz-center {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  width: 90rpx;
-  height: 90rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background-color: #F0F9F0;
-  border-radius: 50%;
-  color: #3BB44A;
-  font-size: 36rpx;
-  transition: all 0.2s;
-  box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.15);
-}
-
-.ptz-arrow:active, .ptz-center:active {
-  background-color: #3BB44A;
-  transform-origin: center;
-}
-
-.ptz-arrow:active svg path, .ptz-center:active svg path {
-  fill: #FFFFFF;
-}
-
-.ptz-up:active {
-  transform: scale(0.92) translateX(-50%);
-}
-
-.ptz-down:active {
-  transform: scale(0.92) translateX(-50%);
-}
-
-.ptz-left:active {
-  transform: scale(0.92) translateY(-50%);
-}
-
-.ptz-right:active {
-  transform: scale(0.92) translateY(-50%);
-}
-
-.ptz-center:active {
-  transform: translate(-50%, -50%) scale(0.92);
-}
-
-.ptz-bottom-controls {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  width: 300rpx;
-  margin-top: 30rpx;
-}
-
-.ptz-bottom-button {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 50%;
-  background-color: #F0F9F0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: #3BB44A;
-  font-size: 32rpx;
-  font-weight: 500;
-  transition: all 0.2s;
-  box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.1);
-}
-
-.ptz-bottom-button:active {
-  background-color: #3BB44A;
-  color: #FFFFFF;
-  transform: scale(0.92);
-}
-
-.ptz-bottom-button:active svg path {
-  fill: #FFFFFF;
-}
-
-/* 快捷功能按钮 */
-.quick-actions {
-  display: flex;
-  justify-content: space-evenly;
-  margin: 0 30rpx 20rpx;
-  background-color: #FFFFFF;
-  border-radius: 20rpx;
-  padding: 24rpx 30rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
-}
-
-.action-button {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  width: 160rpx;
-}
-
-.action-icon {
-  width: 100rpx;
-  height: 100rpx;
-  border-radius: 50%;
-  background-color: #F0F9F0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: #3BB44A;
-  margin-bottom: 12rpx;
-  transition: all 0.2s;
-  box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.1);
-}
-
-.action-icon:active {
-  background-color: #3BB44A;
-  transform: scale(0.92);
-}
-
-.action-icon:active svg path {
-  fill: #FFFFFF;
-}
-
-.action-text {
-  font-size: 24rpx;
-  color: #666666;
-  text-align: center;
-}
-
-/* 告警信息列表 */
-.alerts-section {
-  margin: 0 30rpx;
-  background-color: #FFFFFF;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
-}
-
-.alerts-list {
-  display: flex;
-  flex-direction: column;
-}
-
-.alert-item {
-  display: flex;
-  align-items: center;
-  padding: 24rpx 20rpx;
-  border-radius: 12rpx;
-  margin-bottom: 16rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
-  position: relative;
-}
-
-.alert-urgent {
-  background-color: #FEF3F3;
-  border-left: 4rpx solid #F56C6C;
-}
-
-.alert-warning {
-  background-color: #FFF8E6;
-  border-left: 4rpx solid #E6A23C;
-}
-
-.alert-info {
-  background-color: #F2FAF5;
-  border-left: 4rpx solid #67C23A;
-}
-
-.alert-item-icon {
-  width: 50rpx;
-  height: 50rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 16rpx;
-}
-
-.alert-item-info {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-}
-
-.alert-item-type {
-  font-size: 28rpx;
-  color: #333333;
-  font-weight: 500;
-  margin-bottom: 8rpx;
-}
-
-.alert-item-level {
-  font-size: 24rpx;
-  color: #999999;
-}
-
-.alert-item-time {
-  font-size: 24rpx;
-  color: #999999;
-  margin-left: 16rpx;
-  min-width: 100rpx;
-  text-align: right;
-}
-
-.empty-alert {
-  padding: 60rpx 0;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.empty-text {
-  font-size: 28rpx;
-  color: #999999;
-}
-</style> 
+	/* 基础样式 */
+	.container {
+		display: flex;
+		flex-direction: column;
+		min-height: 100vh;
+		background-color: #F8FCF9;
+		padding-bottom: 30rpx;
+	}
+
+	/* 设备头部样式 */
+	.device-header {
+		background-color: #FFFFFF;
+		border-radius: 20rpx;
+		padding: 26rpx 30rpx 30rpx;
+		margin: 20rpx 30rpx 20rpx;
+		box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+	}
+
+	.device-info-row {
+		display: flex;
+		justify-content: space-between;
+		align-items: flex-start;
+		margin-bottom: 24rpx;
+	}
+
+	.device-name-container {
+		display: flex;
+		flex-direction: column;
+		align-items: flex-start;
+	}
+
+	.device-name {
+		font-size: 34rpx;
+		color: #333333;
+		font-weight: 600;
+		margin-bottom: 10rpx;
+	}
+
+	.status-tag {
+		padding: 4rpx 12rpx 4rpx 24rpx;
+		border-radius: 30rpx;
+		font-size: 22rpx;
+		font-weight: 500;
+		flex-shrink: 0;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.status-online {
+		background-color: rgba(76, 175, 80, 0.1);
+		color: #3BB44A;
+	}
+
+	.status-offline {
+		background-color: rgba(245, 108, 108, 0.1);
+		color: #F56C6C;
+		padding-left: 24rpx;
+	}
+
+	.status-dot {
+		position: absolute;
+		width: 6rpx;
+		height: 6rpx;
+		background-color: #3BB44A;
+		border-radius: 50%;
+		top: 50%;
+		left: 12rpx;
+		transform: translateY(-50%);
+		box-shadow: 0 0 4rpx rgba(76, 175, 80, 0.8);
+		animation: blink 1.5s infinite;
+		display: inline-block;
+	}
+
+	.status-dot.offline-dot {
+		background-color: #F56C6C;
+		box-shadow: 0 0 4rpx rgba(245, 108, 108, 0.8);
+	}
+
+	@keyframes blink {
+		0% {
+			opacity: 0.4;
+		}
+
+		50% {
+			opacity: 1;
+		}
+
+		100% {
+			opacity: 0.4;
+		}
+	}
+
+	.device-meta-row {
+		display: flex;
+		flex-direction: column;
+	}
+
+	.device-meta-item {
+		display: flex;
+		align-items: center;
+		font-size: 26rpx;
+		margin-top: 18rpx;
+	}
+
+	.meta-icon {
+		width: 36rpx;
+		height: 36rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 8rpx;
+	}
+
+	.meta-label {
+		color: #999999;
+		min-width: 140rpx;
+		font-size: 26rpx;
+	}
+
+	.meta-value {
+		color: #333333;
+		flex: 1;
+		font-size: 26rpx;
+	}
+
+	/* 视频预览区域 */
+	.video-section {
+		margin: 0 30rpx 20rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.video-container {
+		position: relative;
+		width: 100%;
+		height: 420rpx;
+		/* 16:9比例 */
+		background-color: #000000;
+		border-radius: 16rpx;
+		overflow: hidden;
+		box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
+		transition: all 0.3s ease;
+	}
+
+	.video-container.fullscreen-mode {
+		position: fixed;
+		top: 0;
+		left: 0;
+		width: 100vw;
+		height: 100vh;
+		margin: 0;
+		z-index: 9999;
+		border-radius: 0;
+	}
+
+	.video-container.fullscreen-mode .video-controls {
+		padding: 30rpx;
+	}
+
+	.video-container.fullscreen-mode .top-controls,
+	.video-container.fullscreen-mode .bottom-controls {
+		opacity: 0;
+		transition: opacity 0.3s ease;
+	}
+
+	.video-container.fullscreen-mode:hover .top-controls,
+	.video-container.fullscreen-mode:hover .bottom-controls {
+		opacity: 1;
+	}
+
+	.video-player,
+	.video-placeholder {
+		width: 100%;
+		height: 100%;
+		object-fit: contain;
+	}
+
+	.video-controls {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		justify-content: space-between;
+		padding: 20rpx;
+		box-sizing: border-box;
+		background: linear-gradient(to bottom, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, 0) 70%, rgba(0, 0, 0, 0.4) 100%);
+	}
+
+	.control-row {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		width: 100%;
+	}
+
+	.top-controls {
+		height: 80rpx;
+	}
+
+	.center-controls {
+		height: 120rpx;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.bottom-controls {
+		height: 80rpx;
+	}
+
+	.signal-indicator {
+		display: flex;
+		align-items: center;
+		color: #FFFFFF;
+		font-size: 24rpx;
+		background-color: rgba(0, 0, 0, 0.5);
+		padding: 8rpx 16rpx;
+		border-radius: 30rpx;
+	}
+
+	.signal-indicator svg {
+		margin-right: 8rpx;
+	}
+
+	.signal-text {
+		font-weight: 500;
+	}
+
+	.fullscreen-button {
+		width: 60rpx;
+		height: 60rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		color: #FFFFFF;
+		background-color: rgba(0, 0, 0, 0.5);
+		border-radius: 50%;
+		transition: all 0.2s;
+	}
+
+	.fullscreen-button:active {
+		background-color: rgba(76, 175, 80, 0.7);
+		transform: scale(0.9);
+	}
+
+	.video-time {
+		color: #FFFFFF;
+		font-size: 26rpx;
+		background-color: rgba(0, 0, 0, 0.5);
+		padding: 6rpx 16rpx;
+		border-radius: 30rpx;
+		font-weight: 500;
+	}
+
+	.play-button {
+		width: 100rpx;
+		height: 100rpx;
+		border-radius: 50%;
+		background-color: rgba(255, 255, 255, 0.9);
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.5);
+		transition: all 0.2s;
+		transform: scale(1);
+	}
+
+	.play-button:active {
+		transform: scale(0.92);
+		background-color: rgba(255, 255, 255, 1);
+	}
+
+	.center-button-container {
+		width: 100%;
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.pause-icon {
+		width: 80rpx;
+		height: 80rpx;
+		border-radius: 50%;
+		background-color: rgba(0, 0, 0, 0.5);
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		opacity: 0;
+		transition: opacity 0.3s;
+	}
+
+	.center-button-container:active .pause-icon {
+		opacity: 1;
+		background-color: rgba(76, 175, 80, 0.7);
+	}
+
+	/* 云台控制区域 */
+	.ptz-section {
+		margin: 0 30rpx 20rpx;
+		background-color: #FFFFFF;
+		border-radius: 20rpx;
+		padding: 24rpx;
+		box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+	}
+
+	.section-title {
+		font-size: 30rpx;
+		font-weight: 600;
+		color: #333333;
+		margin-bottom: 20rpx;
+		padding: 0 10rpx;
+		display: flex;
+		align-items: center;
+	}
+
+	.alert-badge {
+		background-color: #F56C6C;
+		color: #FFFFFF;
+		font-size: 22rpx;
+		border-radius: 30rpx;
+		padding: 2rpx 12rpx;
+		margin-left: 12rpx;
+		font-weight: normal;
+		min-width: 32rpx;
+		text-align: center;
+	}
+
+	.ptz-container {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 20rpx 0;
+	}
+
+	.ptz-circle-container {
+		position: relative;
+		width: 300rpx;
+		height: 300rpx;
+		margin: 20rpx 0;
+	}
+
+	.ptz-arrow {
+		position: absolute;
+		width: 70rpx;
+		height: 70rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background-color: #F0F9F0;
+		border-radius: 50%;
+		color: #3BB44A;
+		font-size: 36rpx;
+		transition: all 0.2s;
+		box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.15);
+	}
+
+	.ptz-up {
+		top: 0;
+		left: 50%;
+		transform: translateX(-50%);
+	}
+
+	.ptz-down {
+		bottom: 0;
+		left: 50%;
+		transform: translateX(-50%);
+	}
+
+	.ptz-left {
+		left: 0;
+		top: 50%;
+		transform: translateY(-50%);
+	}
+
+	.ptz-right {
+		right: 0;
+		top: 50%;
+		transform: translateY(-50%);
+	}
+
+	.ptz-center {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		width: 90rpx;
+		height: 90rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background-color: #F0F9F0;
+		border-radius: 50%;
+		color: #3BB44A;
+		font-size: 36rpx;
+		transition: all 0.2s;
+		box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.15);
+	}
+
+	.ptz-arrow:active,
+	.ptz-center:active {
+		background-color: #3BB44A;
+		transform-origin: center;
+	}
+
+	.ptz-arrow:active svg path,
+	.ptz-center:active svg path {
+		fill: #FFFFFF;
+	}
+
+	.ptz-up:active {
+		transform: scale(0.92) translateX(-50%);
+	}
+
+	.ptz-down:active {
+		transform: scale(0.92) translateX(-50%);
+	}
+
+	.ptz-left:active {
+		transform: scale(0.92) translateY(-50%);
+	}
+
+	.ptz-right:active {
+		transform: scale(0.92) translateY(-50%);
+	}
+
+	.ptz-center:active {
+		transform: translate(-50%, -50%) scale(0.92);
+	}
+
+	.ptz-bottom-controls {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		width: 300rpx;
+		margin-top: 30rpx;
+	}
+
+	.ptz-bottom-button {
+		width: 80rpx;
+		height: 80rpx;
+		border-radius: 50%;
+		background-color: #F0F9F0;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		color: #3BB44A;
+		font-size: 32rpx;
+		font-weight: 500;
+		transition: all 0.2s;
+		box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.1);
+	}
+
+	.ptz-bottom-button:active {
+		background-color: #3BB44A;
+		color: #FFFFFF;
+		transform: scale(0.92);
+	}
+
+	.ptz-bottom-button:active svg path {
+		fill: #FFFFFF;
+	}
+
+	/* 快捷功能按钮 */
+	.quick-actions {
+		display: flex;
+		justify-content: space-evenly;
+		margin: 0 30rpx 20rpx;
+		background-color: #FFFFFF;
+		border-radius: 20rpx;
+		padding: 24rpx 30rpx;
+		box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+	}
+
+	.action-button {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		width: 160rpx;
+	}
+
+	.action-icon {
+		width: 100rpx;
+		height: 100rpx;
+		border-radius: 50%;
+		background-color: #F0F9F0;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		color: #3BB44A;
+		margin-bottom: 12rpx;
+		transition: all 0.2s;
+		box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.1);
+	}
+
+	.action-icon:active {
+		background-color: #3BB44A;
+		transform: scale(0.92);
+	}
+
+	.action-icon:active svg path {
+		fill: #FFFFFF;
+	}
+
+	.action-text {
+		font-size: 24rpx;
+		color: #666666;
+		text-align: center;
+	}
+
+	/* 告警信息列表 */
+	.alerts-section {
+		margin: 0 30rpx;
+		background-color: #FFFFFF;
+		border-radius: 20rpx;
+		padding: 24rpx;
+		box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+	}
+
+	.alerts-list {
+		display: flex;
+		flex-direction: column;
+	}
+
+	.alert-item {
+		display: flex;
+		align-items: center;
+		padding: 24rpx 20rpx;
+		border-radius: 12rpx;
+		margin-bottom: 16rpx;
+		box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+		position: relative;
+	}
+
+	.alert-urgent {
+		background-color: #FEF3F3;
+		border-left: 4rpx solid #F56C6C;
+	}
+
+	.alert-warning {
+		background-color: #FFF8E6;
+		border-left: 4rpx solid #E6A23C;
+	}
+
+	.alert-info {
+		background-color: #F2FAF5;
+		border-left: 4rpx solid #67C23A;
+	}
+
+	.alert-item-icon {
+		width: 50rpx;
+		height: 50rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 16rpx;
+	}
+
+	.alert-item-info {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+	}
+
+	.alert-item-type {
+		font-size: 28rpx;
+		color: #333333;
+		font-weight: 500;
+		margin-bottom: 8rpx;
+	}
+
+	.alert-item-level {
+		font-size: 24rpx;
+		color: #999999;
+	}
+
+	.alert-item-time {
+		font-size: 24rpx;
+		color: #999999;
+		margin-left: 16rpx;
+		min-width: 100rpx;
+		text-align: right;
+	}
+
+	.empty-alert {
+		padding: 60rpx 0;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.empty-text {
+		font-size: 28rpx;
+		color: #999999;
+	}
+</style>