2
0

2 Commits 510720cb2a ... 5cb6242e57

Autor SHA1 Mensagem Data
  lxh 5cb6242e57 Merge branch 'master' of http://39.97.59.228:8013/hrx/hsq-pro 1 mês atrás
  lxh ff3f3e511a 视频预览-界面提交 1 mês atrás

+ 9 - 0
src/assets/icons/jg-icon.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="17.346" height="18.604" viewBox="0 0 17.346 18.604">
+  <defs>
+    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
+      <stop offset="0" stop-color="#3df6ff"/>
+      <stop offset="1" stop-color="#fff"/>
+    </linearGradient>
+  </defs>
+  <path id="路径_58037" data-name="路径 58037" d="M101.428,64h5.507v4.4h-5.507ZM96,78.3h3.3v3.3H96Zm6.531,0h3.3v3.3h-3.3Zm6.511,0h3.3v3.3h-3.3ZM98.2,72.782h5.352V77.1h1.257V72.782h5.352V77.1h1.257v-5.58h-6.609V69.4h-1.257v2.122H96.945V77.1H98.2Z" transform="translate(-95.5 -63.5)" stroke="rgba(0,0,0,0)" stroke-width="1" fill="url(#linear-gradient)"/>
+</svg>

+ 9 - 0
src/assets/icons/video-icon.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="19.538" height="17.649" viewBox="0 0 19.538 17.649">
+  <defs>
+    <linearGradient id="linear-gradient" x1="0.5" x2="0.5" y2="1" gradientUnits="objectBoundingBox">
+      <stop offset="0" stop-color="#28a6ff"/>
+      <stop offset="1" stop-color="#fff"/>
+    </linearGradient>
+  </defs>
+  <path id="路径_58036" data-name="路径 58036" d="M3.841,62.552H1.892V60.087L0,59.743v7.912l1.892-.631V64.272H4.931l1.663-2.236L4.415,60.889Zm9.861.516,4.415-2.809L4.357,51.2H2.752L.573,54.64v1.147l12.1,7.339H13.7v-.057Zm-1.663.573L1.605,56.876.516,58.023l12.1,7.339H13.7l.573-.573.573-1.663s-1.605,1.147-1.663,1.147a9.788,9.788,0,0,0-1.147-.631Z" transform="translate(0.5 -50.7)" stroke="rgba(0,0,0,0)" stroke-width="1" fill="url(#linear-gradient)"/>
+</svg>

BIN
src/assets/images/home-container/configurable/hsq/1-1.png


BIN
src/assets/images/home-container/configurable/hsq/7-1.png


BIN
src/assets/images/home-container/configurable/hsq/7-2.png


BIN
src/assets/images/home-container/configurable/hsq/7-3.png


BIN
src/assets/images/home-container/configurable/hsq/7-4.png


BIN
src/assets/images/home-container/configurable/hsq/7-5.png


BIN
src/assets/images/home-container/configurable/hsq/login-bg.png


+ 149 - 0
src/views/sys/login/Login copy.vue

@@ -0,0 +1,149 @@
+<template>
+  <div class="login-container">
+    <!-- 登录中加载态:Ant Design Spin + 自定义青色点阵,风格对齐设计稿 -->
+    <div class="login-loading">
+      <Spin :spinning="loading" size="large">
+        <template #indicator>
+          <div class="dot-spinner" aria-hidden="true">
+            <span
+              v-for="i in 8"
+              :key="i"
+              class="dot"
+              :style="dotStyle(i)"
+            />
+          </div>
+        </template>
+        <div class="spin-holder" />
+      </Spin>
+      <div v-show="loading" class="loading-text">登录中...</div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref,onMounted, watch } from 'vue'
+import { Spin } from 'ant-design-vue'
+import { useRoute, useRouter } from 'vue-router'
+
+/** 登录加载状态,对接登录接口后可在请求前后切换 */
+const loading = ref(true)
+const router = useRouter()
+/** 监听 URL 地址栏中的 type 查询参数 */
+const route = useRoute()
+
+function dotStyle(i: number) {
+  const index = i - 1
+  const size = 10 - index * 0.75
+  return {
+    transform: `rotate(${index * 45}deg) translateY(-22px)`,
+    opacity: Math.max(0.25, 1 - index * 0.1),
+    width: `${size}px`,
+    height: `${size}px`,
+    margin: `${-size / 2}px 0 0 ${-size / 2}px`,
+  }
+}
+
+watch(
+  () => route.query.type,
+  (type) => {
+    const typeValue = Array.isArray(type) ? type[0] : type
+    if (typeValue == null || typeValue === '') return
+    // 根据 type 处理业务逻辑
+    if (typeValue === '1') {
+     //向192.168.247.84:80请求
+    } else {
+     //192.168.247.17:81请求
+    }
+  },
+  { immediate: true }
+)
+
+
+onMounted(() => {
+  setTimeout(() => {
+    loading.value = false
+    router.push('/configurable/default/hsq/home')
+  }, 3000)
+})
+</script>
+
+<style lang="less" scoped>
+.login-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background: url('@/assets/images/home-container/configurable/hsq/login-bg.png') no-repeat;
+  background-size: 100% 100%;
+  overflow: hidden;
+}
+
+.login-loading {
+  position: absolute;
+  inset: 0;
+  z-index: 10;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+
+  :deep(.ant-spin-nested-loading) {
+    width: auto;
+  }
+
+  :deep(.ant-spin) {
+    max-height: none;
+    color: #00e5ff;
+  }
+
+  :deep(.ant-spin-dot) {
+    display: none !important;
+  }
+
+  :deep(.ant-spin-container) {
+    transition: none;
+  }
+
+  // .spin-holder {
+  //   width: 56px;
+  //   height: 56px;
+  // }
+}
+
+.dot-spinner {
+  position: relative;
+  width: 56px;
+  height: 56px;
+  animation: spin-rotate 1s linear infinite;
+
+  .dot {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    border-radius: 50%;
+    background: #00e5ff;
+    box-shadow:
+      0 0 4px rgba(0, 229, 255, 1),
+      0 0 10px rgba(0, 200, 255, 0.7);
+  }
+}
+
+.loading-text {
+  margin-top: 50px;
+  margin-left: 18px;
+  color: #fff;
+  font-size: 16px;
+  line-height: 1;
+  letter-spacing: 2px;
+  font-weight: 400;
+  user-select: none;
+}
+
+@keyframes spin-rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+</style>

+ 769 - 0
src/views/vent/monitorManager/hsqHome/components/DeviceControl.vue

@@ -0,0 +1,769 @@
+<template>
+  <!-- 设备控制组件:左侧面板下方,包含设备选择、云台控制、扫描/调色模式、红外选项等 -->
+  <div class="device-control">
+    <!-- 控制设备下拉选择 -->
+    <div class="device-select-row">
+      <span class="select-label">控制设备</span>
+      <div class="device-select" @click.stop="toggleDeviceMenu">
+        <span class="select-text" :title="currentDevice">{{ currentDevice }}</span>
+        <span :class="['select-arrow', { open: deviceMenuOpen }]">
+          <svg viewBox="0 0 10 10" width="10" height="10">
+            <path d="M1 3.2 L5 7.2 L9 3.2" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"
+              stroke-linejoin="round" />
+          </svg>
+        </span>
+        <ul v-if="deviceMenuOpen" class="select-dropdown" @click.stop>
+          <li v-for="item in deviceOptions" :key="item.value" :class="{ active: item.label=== currentDevice }"
+            @click="selectDevice(item)">
+            {{ item.label }}
+          </li>
+        </ul>
+      </div>
+    </div>
+
+    <!-- 功能 Tab -->
+    <div class="control-tabs">
+      <div v-for="tab in tabsPtz" :key="tab.key" :class="['tab-item', { active: activeTab === tab.key }]"
+        @click="activeTab = tab.key">
+        {{ tab.label }}
+      </div>
+    </div>
+
+    <!-- 云台控制主体 -->
+    <div class="ptz-panel">
+      <!-- 左侧:变焦 / 聚焦 / 光圈 -->
+      <div class="lens-controls">
+        <div v-for="row in lensControls" :key="row.key" class="lens-row">
+          <button class="lens-btn" type="button" @mousedown="emitAction(row.key, 'plus')"
+            @mouseup="emitAction(row.key, 'stop')" @mouseleave="emitAction(row.key, 'stop')">+</button>
+          <span class="lens-icon" v-html="row.icon"></span>
+          <button class="lens-btn" type="button" @mousedown="emitAction(row.key, 'minus')"
+            @mouseup="emitAction(row.key, 'stop')" @mouseleave="emitAction(row.key, 'stop')">−</button>
+        </div>
+      </div>
+
+      <!-- 右侧:方向盘 -->
+      <div class="direction-pad">
+        <button class="dir-btn up" type="button" @mousedown="emitAction('pan', 'up')"
+          @mouseup="emitAction('pan', 'stop')" @mouseleave="emitAction('pan', 'stop')" aria-label="上">
+          <span class="dir-arrow"></span>
+        </button>
+        <button class="dir-btn left" type="button" @mousedown="emitAction('pan', 'left')"
+          @mouseup="emitAction('pan', 'stop')" @mouseleave="emitAction('pan', 'stop')" aria-label="左">
+          <span class="dir-arrow"></span>
+        </button>
+        <button class="dir-btn right" type="button" @mousedown="emitAction('pan', 'right')"
+          @mouseup="emitAction('pan', 'stop')" @mouseleave="emitAction('pan', 'stop')" aria-label="右">
+          <span class="dir-arrow"></span>
+        </button>
+        <button class="dir-btn down" type="button" @mousedown="emitAction('pan', 'down')"
+          @mouseup="emitAction('pan', 'stop')" @mouseleave="emitAction('pan', 'stop')" aria-label="下">
+          <span class="dir-arrow"></span>
+        </button>
+        <div class="pad-center"></div>
+      </div>
+    </div>
+
+    <!-- 速度 -->
+    <div class="param-row speed-row">
+      <span class="param-label">速度:</span>
+      <div class="speed-slider">
+        <input v-model.number="speed" type="range" min="0" max="100" />
+        <div class="slider-track">
+          <div class="slider-fill" :style="{ width: speed + '%' }"></div>
+          <div class="slider-thumb" :style="{ left: speed + '%' }"></div>
+        </div>
+      </div>
+      <span class="speed-value">{{ speed }}%</span>
+    </div>
+
+    <!-- 扫描模式 / 调色模式 -->
+    <div class="param-row mode-row">
+      <span class="param-label">扫描模式:</span>
+      <div class="mode-select" @click.stop="toggleMenu('scan')">
+        <span class="mode-value">{{ scanMode || '' }}</span>
+        <span class="mode-line"></span>
+        <span :class="['mode-arrow', { open: openMenu === 'scan' }]">▼</span>
+        <ul v-if="openMenu === 'scan'" class="mode-dropdown" @click.stop>
+          <li v-for="item in scanModes" :key="item" @click="pickMode('scan', item)">{{ item }}</li>
+        </ul>
+      </div>
+    </div>
+    <div class="param-row mode-row">
+      <span class="param-label">调色模式:</span>
+      <div class="mode-select" @click.stop="toggleMenu('color')">
+        <span class="mode-value">{{ colorMode || '' }}</span>
+        <span class="mode-line"></span>
+        <span :class="['mode-arrow', { open: openMenu === 'color' }]">▼</span>
+        <ul v-if="openMenu === 'color'" class="mode-dropdown" @click.stop>
+          <li v-for="item in colorModes" :key="item" @click="pickMode('color', item)">{{ item }}</li>
+        </ul>
+      </div>
+    </div>
+
+    <!-- 底部勾选 -->
+    <div class="footer-checks">
+      <label class="check-item">
+        <input v-model="irZoom" type="checkbox" />
+        <span class="check-box">
+          <svg v-if="irZoom" viewBox="0 0 12 12" width="10" height="10">
+            <path d="M2 6.2 L4.8 9 L10 3.2" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round"
+              stroke-linejoin="round" />
+          </svg>
+        </span>
+        <span class="check-label">红外变倍</span>
+      </label>
+      <label class="check-item">
+        <input v-model="irControl" type="checkbox" />
+        <span class="check-box">
+          <svg v-if="irControl" viewBox="0 0 12 12" width="10" height="10">
+            <path d="M2 6.2 L4.8 9 L10 3.2" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round"
+              stroke-linejoin="round" />
+          </svg>
+        </span>
+        <span class="check-label">红外控制</span>
+      </label>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, onUnmounted, ref, watch } from 'vue'
+import { tabsPtz, lensControls } from '../hsqHome.data'
+
+let props=defineProps({
+  deviceOptions: {
+    type: Array as PropType<any[]>,
+    default: () => []
+  }
+})
+
+/** 组件自定义事件 */
+const emit = defineEmits<{
+  (e: 'deviceChange', device: string): void
+  (e: 'tabChange', key: string): void
+  (e: 'action', payload: { type: string; value: string }): void
+  (e: 'speedChange', value: number): void
+  (e: 'scanModeChange', value: string): void
+  (e: 'colorModeChange', value: string): void
+  (e: 'optionChange', payload: { irZoom: boolean; irControl: boolean }): void
+}>()
+
+/** 当前选中的设备 */
+const currentDevice = ref('')
+/** 设备下拉菜单是否展开 */
+const deviceMenuOpen = ref(false)
+/** 当前激活的功能 Tab */
+const activeTab = ref('ptz')
+/** 云台控制速度(0-100) */
+const speed = ref(28)
+/** 当前扫描模式 */
+const scanMode = ref('')
+/** 当前调色模式 */
+const colorMode = ref('')
+/** 当前展开的下拉菜单(扫描/调色/无) */
+const openMenu = ref<'scan' | 'color' | ''>('')
+/** 红外变倍开关 */
+const irZoom = ref(true)
+/** 红外控制开关 */
+const irControl = ref(false)
+
+/** 扫描模式选项 */
+const scanModes = ['自动扫描', '水平扫描', '垂直扫描', '关闭']
+/** 调色模式选项 */
+const colorModes = ['白热', '黑热', '彩虹', '铁红']
+
+/** 发送操作指令(云台方向/镜头控制等) */
+function emitAction(type: string, value: string) {
+  emit('action', { type, value })
+}
+
+/** 切换设备下拉菜单的展开/收起 */
+function toggleDeviceMenu() {
+  deviceMenuOpen.value = !deviceMenuOpen.value
+  openMenu.value = ''
+}
+
+/** 选择设备,关闭菜单并触发 deviceChange 事件 */
+function selectDevice(item) {
+  currentDevice.value = item.label
+  deviceMenuOpen.value = false
+  emit('deviceChange', item)
+}
+
+/** 切换扫描/调色模式下拉菜单(互斥展开) */
+function toggleMenu(key: 'scan' | 'color') {
+  openMenu.value = openMenu.value === key ? '' : key
+  deviceMenuOpen.value = false
+}
+
+/** 选择扫描/调色模式并触发对应事件 */
+function pickMode(type: 'scan' | 'color', value: string) {
+  if (type === 'scan') {
+    scanMode.value = value
+    emit('scanModeChange', value)
+  } else {
+    colorMode.value = value
+    emit('colorModeChange', value)
+  }
+  openMenu.value = ''
+}
+
+/** 点击页面其他区域时关闭所有下拉菜单 */
+function onDocClick() {
+  deviceMenuOpen.value = false
+  openMenu.value = ''
+}
+
+/** 监听 Tab 切换 */
+watch(activeTab, (key) => emit('tabChange', key))
+/** 监听速度变化 */
+watch(speed, (val) => emit('speedChange', val))
+/** 监听红外选项变化 */
+watch([irZoom, irControl], () => {
+  emit('optionChange', { irZoom: irZoom.value, irControl: irControl.value })
+})
+watch(() => props.deviceOptions, (newVal) => {
+  if(newVal.length > 0){
+    currentDevice.value = newVal[0].label
+  }
+},{immediate: true})
+
+/** 挂载时注册全局点击关闭事件 */
+onMounted(() => {
+  document.addEventListener('click', onDocClick)
+})
+/** 卸载时移除全局点击事件 */
+onUnmounted(() => {
+  document.removeEventListener('click', onDocClick)
+})
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+// 设备控制主容器
+.device-control {
+  --image-bg: url('@/assets/images/home-container/configurable/hsq/2-6.png');
+  --image-bg1: url('@/assets/images/home-container/configurable/hsq/7-3.png');
+  --image-bg2: url('@/assets/images/home-container/configurable/hsq/7-4.png');
+  --dc-accent: #00b7ff;
+  --dc-accent-soft: rgba(0, 183, 255, 0.35);
+  --dc-text: #eaf6ff;
+  --dc-muted: #8ea8c0;
+  --dc-panel: rgba(4, 28, 55, 0.55);
+  --dc-glow: 0 0 8px rgba(0, 183, 255, 0.45);
+
+  height: 100%;
+  color: var(--dc-text);
+  font-size: 13px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  user-select: none;
+  padding: 15px;
+  box-sizing: border-box;
+}
+
+// 设备下拉选择行
+.device-select-row {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  margin-bottom: 5px;
+  flex-shrink: 0;
+
+  .select-label {
+    white-space: nowrap;
+    color: #fff;
+  }
+
+  // 自定义下拉选择框
+  .device-select {
+    position: relative;
+    flex: 1;
+    height: 38px;
+    display: flex;
+    align-items: center;
+    padding: 0 28px 0 14px;
+    background: var(--image-bg) no-repeat;
+    background-size: 100% 100%;
+    cursor: pointer;
+
+    .select-text {
+      flex: 1;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      font-size: 12px;
+      color: #fff;
+    }
+
+    // 下拉箭头
+    .select-arrow {
+      position: absolute;
+      right: 20px;
+      color: #fff;
+      display: inline-flex;
+      transition: transform 0.2s;
+
+      &.open {
+        transform: rotate(180deg);
+      }
+    }
+
+    // 下拉菜单
+    .select-dropdown {
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: calc(100% + 2px);
+      z-index: 20;
+      margin: 0;
+      padding: 4px 0;
+      list-style: none;
+      background: #0a1f3a;
+      border: 1px solid var(--dc-accent);
+      box-shadow: var(--dc-glow);
+      max-height: 140px;
+      overflow: auto;
+
+      li {
+        padding: 6px 10px;
+        font-size: 12px;
+        color: #cfe9ff;
+        cursor: pointer;
+
+        &:hover,
+        &.active {
+          background: rgba(0, 183, 255, 0.2);
+          color: #fff;
+        }
+      }
+    }
+  }
+}
+
+// 功能 Tab 切换栏(云台控制/红外控制/激光/透雾)
+.control-tabs {
+  display: flex;
+  align-items: stretch;
+  margin-bottom: 5px;
+  flex-shrink: 0;
+  background-color: #15517b;
+
+  .tab-item {
+    position: relative;
+    flex: 1;
+    height: 26px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #7f9bb5;
+    cursor: pointer;
+    font-size: 12px;
+    transition: color 0.2s, background 0.2s;
+
+    &:hover {
+      color: #d7efff;
+    }
+
+    &.active {
+      color: #fff;
+      background-color: #1d73a8;
+
+      &::after {
+        content: '';
+        position: absolute;
+        left: 0;
+        right: 0;
+        bottom: -1px;
+        height: 2px;
+        background: #36c7ff;
+        box-shadow: 0 0 8px rgba(78, 200, 255, 0.85);
+      }
+    }
+  }
+}
+
+// 云台控制面板(镜头控制 + 方向键)
+.ptz-panel {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  padding: 10px 14px;
+  margin-bottom: 8px;
+  flex-shrink: 0;
+  background: var(--image-bg1) no-repeat;
+  background-size: 100% 100%;
+}
+
+// 镜头控制区域(变焦/聚焦/光圈)
+.lens-controls {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  flex: 1;
+  max-width: 180px;
+}
+
+// 单行镜头控制(+ 按钮 / 图标 / - 按钮)
+.lens-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 28px;
+  padding: 0 10px;
+  background: var(--image-bg2) no-repeat;
+  background-size: 100% 100%;
+
+  .lens-btn {
+    width: 22px;
+    height: 22px;
+    border: none;
+    background: transparent;
+    color: #fff;
+    font-size: 18px;
+    line-height: 1;
+    cursor: pointer;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    text-shadow: 0 0 6px rgba(0, 200, 255, 0.6);
+
+    &:hover {
+      color: var(--dc-accent);
+    }
+
+    &:active {
+      transform: scale(0.92);
+    }
+  }
+
+  .lens-icon {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    filter: drop-shadow(0 0 3px rgba(0, 200, 255, 0.55));
+  }
+}
+
+// 云台方向键(圆形方向盘)
+.direction-pad {
+  position: relative;
+  width: 108px;
+  height: 108px;
+  flex-shrink: 0;
+  border-radius: 50%;
+  background: radial-gradient(circle at center, rgba(10, 40, 75, 0.95) 0%, rgba(4, 22, 48, 0.98) 70%);
+  border: 1px solid rgba(0, 183, 255, 0.35);
+  box-shadow: inset 0 0 18px rgba(0, 80, 140, 0.45), 0 0 8px rgba(0, 183, 255, 0.2);
+
+  // 方向盘中心圆点
+  .pad-center {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    width: 28px;
+    height: 28px;
+    margin: -14px 0 0 -14px;
+    border-radius: 50%;
+    background: rgba(0, 40, 70, 0.8);
+    border: 1px solid rgba(0, 183, 255, 0.25);
+  }
+
+  // 方向按钮通用样式
+  .dir-btn {
+    position: absolute;
+    width: 36px;
+    height: 36px;
+    border: none;
+    background: transparent;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0;
+
+    .dir-arrow {
+      width: 0;
+      height: 0;
+      opacity: 0.92;
+      filter: drop-shadow(0 0 5px rgba(160, 220, 255, 0.95));
+      transition: opacity 0.15s, filter 0.15s;
+    }
+
+    &:hover .dir-arrow {
+      opacity: 1;
+      filter: drop-shadow(0 0 8px rgba(0, 220, 255, 1));
+    }
+
+    &:active .dir-arrow {
+      opacity: 0.7;
+    }
+
+    // 上方向键
+    &.up {
+      left: 50%;
+      top: 6px;
+      margin-left: -18px;
+
+      .dir-arrow {
+        border-left: 10px solid transparent;
+        border-right: 10px solid transparent;
+        border-bottom: 14px solid rgba(230, 245, 255, 0.95);
+      }
+    }
+
+    // 下方向键
+    &.down {
+      left: 50%;
+      bottom: 6px;
+      margin-left: -18px;
+
+      .dir-arrow {
+        border-left: 10px solid transparent;
+        border-right: 10px solid transparent;
+        border-top: 14px solid rgba(230, 245, 255, 0.95);
+      }
+    }
+
+    // 左方向键
+    &.left {
+      left: 6px;
+      top: 50%;
+      margin-top: -18px;
+
+      .dir-arrow {
+        border-top: 10px solid transparent;
+        border-bottom: 10px solid transparent;
+        border-right: 14px solid rgba(230, 245, 255, 0.95);
+      }
+    }
+
+    // 右方向键
+    &.right {
+      right: 6px;
+      top: 50%;
+      margin-top: -18px;
+
+      .dir-arrow {
+        border-top: 10px solid transparent;
+        border-bottom: 10px solid transparent;
+        border-left: 14px solid rgba(230, 245, 255, 0.95);
+      }
+    }
+  }
+}
+
+// 参数行通用样式(速度/扫描模式/调色模式)
+.param-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  flex-shrink: 0;
+
+  .param-label {
+    width: 68px;
+    flex-shrink: 0;
+    color: #fff;
+    white-space: nowrap;
+  }
+}
+
+// 速度滑块行
+.speed-row {
+  .speed-slider {
+    position: relative;
+    flex: 1;
+    height: 18px;
+    display: flex;
+    align-items: center;
+    margin-right: 10px;
+
+    // 隐藏原生 range 控件,显示自定义滑块
+    input[type='range'] {
+      position: absolute;
+      inset: 0;
+      width: 100%;
+      height: 100%;
+      opacity: 0;
+      cursor: pointer;
+      z-index: 2;
+      margin: 0;
+    }
+
+    // 滑块轨道
+    .slider-track {
+      position: relative;
+      width: 100%;
+      height: 4px;
+      border-radius: 2px;
+      background: rgba(20, 60, 100, 0.9);
+      border: 1px solid rgba(0, 183, 255, 0.25);
+    }
+
+    // 滑块已填充部分
+    .slider-fill {
+      position: absolute;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      background: linear-gradient(90deg, #0090e0, #00d0ff);
+      border-radius: 2px;
+      box-shadow: 0 0 6px rgba(0, 200, 255, 0.5);
+    }
+
+    // 滑块拖拽圆点
+    .slider-thumb {
+      position: absolute;
+      top: 50%;
+      width: 14px;
+      height: 14px;
+      margin: -7px 0 0 -7px;
+      border-radius: 50%;
+      background: radial-gradient(circle at center, #fff 0 35%, #1a9fe0 40% 100%);
+      border: 1px solid #7ad8ff;
+      box-shadow: 0 0 8px rgba(0, 200, 255, 0.85);
+      pointer-events: none;
+    }
+  }
+
+  .speed-value {
+    width: 40px;
+    text-align: right;
+    color: #fff;
+    font-size: 12px;
+  }
+}
+
+// 模式选择行(扫描模式/调色模式)
+.mode-row {
+  .mode-select {
+    position: relative;
+    flex: 1;
+    height: 22px;
+    display: flex;
+    align-items: center;
+    cursor: pointer;
+
+    .mode-value {
+      position: absolute;
+      left: 0;
+      right: 16px;
+      top: 0;
+      font-size: 12px;
+      color: #cfe9ff;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      line-height: 16px;
+    }
+
+    // 下划线装饰
+    .mode-line {
+      position: absolute;
+      left: 0;
+      right: 14px;
+      bottom: 2px;
+      height: 1px;
+      background: var(--dc-accent);
+      box-shadow: 0 0 4px rgba(0, 183, 255, 0.5);
+    }
+
+    // 下拉箭头
+    .mode-arrow {
+      position: absolute;
+      right: 0;
+      bottom: 0;
+      font-size: 9px;
+      color: var(--dc-accent);
+      line-height: 1;
+      transition: transform 0.2s;
+
+      &.open {
+        transform: rotate(180deg);
+      }
+    }
+
+    // 模式下拉菜单
+    .mode-dropdown {
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: calc(100% + 2px);
+      z-index: 20;
+      margin: 0;
+      padding: 4px 0;
+      list-style: none;
+      background: #0a1f3a;
+      border: 1px solid var(--dc-accent);
+      box-shadow: var(--dc-glow);
+
+      li {
+        padding: 5px 8px;
+        font-size: 12px;
+        color: #cfe9ff;
+        cursor: pointer;
+
+        &:hover {
+          background: rgba(0, 183, 255, 0.2);
+          color: #fff;
+        }
+      }
+    }
+  }
+}
+
+// 底部勾选框区域(红外变倍/红外控制)
+.footer-checks {
+  display: flex;
+  align-items: center;
+  gap: 28px;
+  margin-top: auto;
+  padding-top: 4px;
+  flex-shrink: 0;
+
+  .check-item {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    cursor: pointer;
+    margin: 0;
+
+    input {
+      display: none;
+    }
+
+    // 自定义复选框
+    .check-box {
+      width: 14px;
+      height: 14px;
+      box-sizing: border-box;
+      border: 1px solid rgba(0, 183, 255, 0.85);
+      border-radius: 2px;
+      background: rgba(0, 30, 55, 0.85);
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    input:checked+.check-box {
+      background: #1aa0e0;
+      border-color: #4ec8ff;
+      box-shadow: 0 0 6px rgba(0, 183, 255, 0.55);
+    }
+
+    .check-label {
+      color: #fff;
+      font-size: 12px;
+    }
+  }
+}
+
+// 隐藏滚动条
+::-webkit-scrollbar {
+    display: none;
+  }
+</style>

+ 417 - 0
src/views/vent/monitorManager/hsqHome/components/DeviceTree.vue

@@ -0,0 +1,417 @@
+<template>
+  <!-- 设备树组件:左侧面板,包含设备分组树、Tab切换、角度保存等功能 -->
+  <div class="device-tree">
+    <!-- 顶部 Tab -->
+    <div class="tree-tabs">
+      <div v-for="tab in tabs" :key="tab.key" :class="['tab-item', { active: activeTab === tab.key }]"
+        @click="activeTab = tab.key">
+        {{ tab.label }}
+      </div>
+    </div>
+
+    <!-- 角度栏 + 保存 -->
+    <div class="tree-toolbar">
+      <span class="toolbar-label">角度</span>
+      <div class="save-btn" @click="handleSave">
+        <div class="btn-item">保存</div>
+      </div>
+    </div>
+
+    <!-- 设备树列表 -->
+    <div class="tree-body">
+      <div v-for="group in treeData" :key="group.id" class="tree-group">
+        <div class="group-row" @click="toggleGroup(group.id)">
+          <span :class="['expand-icon', { collapsed: !expandedMap[group.id] }]">
+            <svg viewBox="0 0 10 10" width="10" height="10">
+              <path d="M1 3.2 L5 7.2 L9 3.2" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"
+                stroke-linejoin="round" />
+            </svg>
+          </span>
+          <span class="group-icon">
+            <SvgIcon class="icon-style2" size="18" name="jg-icon" />
+          </span>
+          <span class="group-title">{{ group.title }}</span>
+        </div>
+
+        <div v-show="expandedMap[group.id]" class="group-children">
+          <div v-for="(device, index) in group.children" :key="device.id"
+            :class="['device-row', { active: selectedId === device.id, 'is-last': index === group.children.length - 1 }]"
+            @click="selectDevice(device)">
+            <span class="tree-line"></span>
+            <span class="device-icon">
+              <SvgIcon class="icon-style2" size="18" name="video-icon" />
+            </span>
+            <span class="device-name" :title="device.name">{{ device.name }}</span>
+            <div class="device-checks" @click.stop>
+              <label v-for="(checked, cIndex) in device.checks" :key="cIndex" class="check-item">
+                <input type="checkbox" :checked="checked"
+                  @change="toggleCheck(device, cIndex, ($event.target as HTMLInputElement).checked)" />
+                <span class="check-box">
+                  <svg v-if="checked" viewBox="0 0 12 12" width="10" height="10">
+                    <path d="M2 6.2 L4.8 9 L10 3.2" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round"
+                      stroke-linejoin="round" />
+                  </svg>
+                </span>
+              </label>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, reactive, ref, watch } from 'vue'
+import { SvgIcon } from '/@/components/Icon';
+import { tabs } from '../hsqHome.data'
+
+/** 设备节点数据结构 */
+interface DeviceNode {
+  id: string
+  name: string
+  /** 三个勾选状态:如显示/联动/告警等 */
+  checks: [boolean, boolean, boolean]
+}
+/** 设备分组节点数据结构 */
+interface GroupNode {
+  id: string
+  title: string
+  children: DeviceNode[]
+}
+
+let props=defineProps({
+  treeData: {
+    type: Array as PropType<GroupNode[]>,
+    default: () => [],
+  },
+})
+
+/** 组件自定义事件 */
+const emit = defineEmits<{
+  // (e: 'save', data: GroupNode[]): void
+  (e: 'select', device: DeviceNode): void
+  (e: 'checkChange', payload: { device: DeviceNode; index: number; checked: boolean }): void
+  (e: 'tabChange', key: string): void
+}>()
+
+
+
+/** 当前激活的 Tab */
+const activeTab = ref('device')
+/** 当前选中的设备 ID */
+const selectedId = ref('d2')
+
+/** 设备分组展开/折叠状态映射 */
+const expandedMap = reactive<Record<string, boolean>>({
+  g1: true,
+  g2: true,
+})
+
+
+
+/** 切换设备分组的展开/折叠状态 */
+function toggleGroup(id: string) {
+  expandedMap[id] = !expandedMap[id]
+}
+
+/** 选中设备,触发 select 事件 */
+function selectDevice(device: DeviceNode) {
+  selectedId.value = device.id
+  emit('select', device)
+}
+
+/** 切换设备勾选状态,触发 checkChange 事件 */
+function toggleCheck(device: DeviceNode, index: number, checked: boolean) {
+  device.checks[index] = checked
+  emit('checkChange', { device, index, checked })
+}
+
+/** 保存设备树数据,触发 save 事件 */
+function handleSave() {
+  // emit('save', treeData.value)
+}
+
+/** 监听 Tab 切换,触发 tabChange 事件 */
+watch(activeTab, (key) => {
+  emit('tabChange', key)
+})
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+// 设备树主容器
+.device-tree {
+  --image-bg: url('@/assets/images/home-container/configurable/hsq/7-2.png');
+  --dt-bg: #071528;
+  --dt-bg-row: transparent;
+  --dt-bg-active: rgba(22, 90, 150, 0.55);
+  --dt-accent: #00b7ff;
+  --dt-text: #e8f4ff;
+  --dt-text-muted: #8aa0b8;
+  --dt-border: rgba(40, 120, 180, 0.35);
+  --dt-line: rgba(120, 150, 180, 0.55);
+
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  color: var(--dt-text);
+  font-size: 13px;
+  overflow: hidden;
+  padding: 15px;
+  box-sizing: border-box;
+}
+
+// 顶部 Tab 切换栏
+.tree-tabs {
+  display: flex;
+  align-items: stretch;
+  height: 30px;
+  flex-shrink: 0;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+  background: #15517b;
+  margin-bottom: 10px;
+
+  .tab-item {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    color: var(--dt-text-muted);
+    position: relative;
+    transition: color 0.2s;
+
+    &:hover {
+      color: #cfe9ff;
+    }
+
+    &.active {
+      color: #fff;
+      font-weight: 600;
+      background-color: #1d73a8;
+
+      &::after {
+        content: '';
+        position: absolute;
+        left: 0%;
+        right: 0%;
+        bottom: 0;
+        height: 2px;
+        background: #36c7ff;
+        border-radius: 1px;
+        box-shadow: 0 0 6px rgba(0, 183, 255, 0.6);
+      }
+    }
+  }
+}
+
+// 工具栏:角度标签 + 保存按钮
+.tree-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 28px;
+  padding: 0 10px;
+  flex-shrink: 0;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+  background: var(--image-bg) no-repeat;
+  background-size: 100% 100%;
+
+  .toolbar-label {
+    color: var(--dt-text);
+    font-size: 13px;
+  }
+
+  // 保存按钮
+  .save-btn {
+    min-width: 52px;
+    height: 24px;
+    padding: 2px;
+    border: 1px solid #29e7db;
+    border-radius: 3px;
+    background: transparent;
+    color: #fff;
+    font-size: 12px;
+    cursor: pointer;
+    line-height: 24px;
+    transition: background 0.2s, box-shadow 0.2s;
+
+    .btn-item {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 100%;
+      background: linear-gradient(to bottom, rgba(41, 231, 219, 1) 0%, rgba(41, 231, 219, 0.5) 100%);
+    }
+
+  }
+}
+
+// 设备树列表主体(可滚动区域)
+.tree-body {
+  flex: 1;
+  overflow: auto;
+  padding: 6px 0px;
+  box-sizing: border-box;
+
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: rgba(0, 183, 255, 0.35);
+    border-radius: 2px;
+  }
+}
+
+// 设备分组行(煤场/采场等)
+.group-row {
+  display: flex;
+  align-items: center;
+  height: 30px;
+  padding: 0 10px 0 8px;
+  cursor: pointer;
+  user-select: none;
+
+  &:hover {
+    background: rgba(255, 255, 255, 0.03);
+  }
+
+  // 展开/折叠箭头图标
+  .expand-icon {
+    width: 14px;
+    height: 14px;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    color: #d8e6f5;
+    margin-right: 4px;
+    transition: transform 0.2s;
+
+    &.collapsed {
+      transform: rotate(-90deg);
+    }
+  }
+
+  .group-icon {
+    display: inline-flex;
+    margin-right: 6px;
+  }
+
+  .group-title {
+    font-size: 13px;
+    color: var(--dt-text);
+  }
+}
+
+// 分组子节点容器
+.group-children {
+  position: relative;
+}
+
+// 单个设备行
+.device-row {
+  position: relative;
+  display: flex;
+  align-items: center;
+  height: 30px;
+  padding: 0 10px 0 28px;
+  cursor: pointer;
+  background: var(--dt-bg-row);
+
+  &:hover {
+    background: rgba(255, 255, 255, 0.04);
+  }
+
+  &.active {
+    background: var(--dt-bg-active);
+  }
+
+  /* 竖向虚线树连接线 */
+  .tree-line {
+    position: absolute;
+    left: 14px;
+    top: 0;
+    bottom: 0;
+    width: 0;
+    border-left: 1px dashed var(--dt-line);
+
+    &::after {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 15px;
+      width: 10px;
+      border-top: 1px dashed var(--dt-line);
+    }
+  }
+
+  // 最后一个子节点只显示一半高度的连接线
+  &.is-last .tree-line {
+    bottom: 50%;
+  }
+
+  .device-icon {
+    display: inline-flex;
+    margin-right: 6px;
+    flex-shrink: 0;
+  }
+
+  // 设备名称(溢出省略)
+  .device-name {
+    flex: 1;
+    min-width: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    color: #d7e9fa;
+    font-size: 12px;
+  }
+
+  // 设备勾选框容器
+  .device-checks {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    margin-left: 8px;
+    flex-shrink: 0;
+  }
+}
+
+// 自定义勾选框样式
+.check-item {
+  display: inline-flex;
+  cursor: pointer;
+  margin: 0;
+
+  input {
+    display: none;
+  }
+
+  .check-box {
+    width: 14px;
+    height: 14px;
+    box-sizing: border-box;
+    border: 1px solid rgba(0, 183, 255, 0.75);
+    border-radius: 2px;
+    background: rgba(0, 30, 55, 0.85);
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  input:checked+.check-box {
+    background: var(--dt-accent);
+    border-color: var(--dt-accent);
+  }
+}
+
+// 隐藏滚动条
+::-webkit-scrollbar {
+  display: none;
+}
+</style>

+ 190 - 0
src/views/vent/monitorManager/hsqHome/components/DeviceView.vue

@@ -0,0 +1,190 @@
+<template>
+  <!-- 设备画面组件:右侧面板上方,包含操作按钮和 2×4 监控画面网格 -->
+  <div class="device-view">
+    <!-- 顶部操作按钮:最新热源 / 最新闯入 / 最新烟雾 / 关闭声光报警 -->
+    <div class="view-actions">
+      <button v-for="btn in actions" :key="btn.key" type="button"
+        :class="['action-btn', { active: activeAction === btn.key }]" @click="onAction(btn.key)">
+        {{ btn.label }}
+      </button>
+    </div>
+
+    <!-- 2×4 监控画面网格 -->
+    <div class="view-grid">
+      <div v-for="item in channels" :key="item.id" class="view-cell" @click="selectChannel(item)">
+        <div class="media-wrap">
+          <video v-if="item.poster" class="media-image" ref="video" controls></video>
+          <div v-else class="media-empty">暂无画面</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+// 监控通道封面图导入
+import poster1 from '/@/assets/images/home-container/configurable/hsq/video/1-1.png'
+import poster2 from '/@/assets/images/home-container/configurable/hsq/video/1-2.png'
+import poster3 from '/@/assets/images/home-container/configurable/hsq/video/1-4.png'
+import poster4 from '/@/assets/images/home-container/configurable/hsq/video/1-5.png'
+import poster5 from '/@/assets/images/home-container/configurable/hsq/video/2-2.png'
+import poster6 from '/@/assets/images/home-container/configurable/hsq/video/2-6.png'
+import poster7 from '/@/assets/images/home-container/configurable/hsq/video/2-7.png'
+import poster8 from '/@/assets/images/home-container/configurable/hsq/video/7-3.png'
+
+/** 操作按钮数据结构 */
+interface ActionItem {
+  key: string
+  label: string
+}
+
+/** 监控通道数据结构 */
+interface ChannelItem {
+  id: string
+  name: string
+  poster: string
+}
+
+/** 组件自定义事件 */
+const emit = defineEmits<{
+  (e: 'action', key: string): void
+  (e: 'select', channel: ChannelItem): void
+}>()
+
+/** 顶部操作按钮列表 */
+const actions: ActionItem[] = [
+  { key: 'heat', label: '最新热源' },
+  { key: 'intrusion', label: '最新闯入' },
+  { key: 'smoke', label: '最新烟雾' },
+  { key: 'alarmOff', label: '关闭声光报警' },
+]
+
+/** 8 个通道封面图资源 */
+const posters = [poster1, poster2, poster3, poster4, poster5, poster6, poster7, poster8]
+
+/** 监控通道列表(2×4 网格,共 8 个通道) */
+const channels = ref<ChannelItem[]>(
+  posters.map((poster, index) => ({
+    id: `ch-${index + 1}`,
+    name: `通道${index + 1}`,
+    poster,
+  })),
+)
+
+/** 当前激活的操作按钮 */
+const activeAction = ref('')
+/** 当前选中的通道 ID */
+const selectedId = ref('')
+
+/** 点击操作按钮,触发 action 事件 */
+function onAction(key: string) {
+  activeAction.value = key
+  emit('action', key)
+}
+
+/** 选中监控通道,触发 select 事件 */
+function selectChannel(item: ChannelItem) {
+  selectedId.value = item.id
+  emit('select', item)
+}
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+// 设备画面主容器
+.device-view {
+  --image-bg: url('@/assets/images/home-container/configurable/hsq/7-5.png');
+  --dv-accent: #00d4ff;
+  --dv-accent-soft: rgba(0, 212, 255, 0.45);
+  --dv-text: #d8f4ff;
+
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  color: var(--dv-text);
+  user-select: none;
+  padding: 15px;
+  box-sizing: border-box;
+}
+
+// 顶部操作按钮栏
+.view-actions {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 14px;
+  flex-shrink: 0;
+
+  .action-btn {
+    min-width: 96px;
+    height: 30px;
+    padding: 0 14px;
+    border: 1px solid #269ee1;
+    border-radius: 2px;
+    background: #185f8e;
+    color: #fff;
+    font-size: 13px;
+    letter-spacing: 0.5px;
+    cursor: pointer;
+    box-shadow: 0 0 6px rgba(0, 200, 255, 0.25), inset 0 0 8px rgba(0, 120, 180, 0.12);
+    transition: color 0.2s, background 0.2s, box-shadow 0.2s;
+
+    &:hover,
+    &.active {
+      color: #fff;
+      background: rgba(0, 90, 140, 0.45);
+    }
+
+    &:active {
+      transform: translateY(1px);
+    }
+  }
+}
+
+// 2×4 监控画面网格
+.view-grid {
+  flex: 1;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 15px 10px;
+  justify-content: flex-start;
+  align-items: flex-start;
+  overflow-y: auto;
+
+}
+
+// 单个监控画面单元格
+.view-cell {
+  width: calc(25% - 10px);
+  height: 220px;
+  cursor: pointer;
+  background: var(--image-bg) no-repeat center;
+  background-size: 100% 100%;
+}
+
+// 媒体内容包裹层
+.media-wrap{
+  height: 100%;
+  padding: 10px 10px 12px 10px;
+  box-sizing: border-box;
+}
+
+// 监控视频画面
+.media-image{
+  height: 100%;
+  width: 100%;
+}
+
+// 无画面占位提示
+.media-empty {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 109 - 0
src/views/vent/monitorManager/hsqHome/components/DeviceWarnList.vue

@@ -0,0 +1,109 @@
+<template>
+  <!-- 设备告警列表组件:右侧面板下方,展示告警信息的表格 -->
+  <div class="device-warn-list">
+    <basicBorder title="告警">
+      <div class="basic-history">
+        <!-- 表头 -->
+        <div class="history-title">
+          <div class="title-item" v-for="(item, index) in videoColumns" :key="index">{{ item.name }}</div>
+        </div>
+        <!-- 表格内容区域 -->
+        <div class="history-content">
+          <div class="content-item" v-for="(item, index) in warnList" :key="index">
+            <div class="item-text" v-for="(ite, ind) in videoColumns" :key="ind">
+              <div >{{ item[ite.prop] }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </basicBorder>
+  </div>
+</template>
+
+<script setup lang="ts">
+import basicBorder from './basicBorder.vue'
+// 表格列配置(从数据文件导入)
+import {videoColumns} from '../hsqHome.data.js'
+
+let props = defineProps({
+  warnList: {
+    type: Array as PropType<any[]>,
+    default: () => [],
+  },
+})
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+// 设备告警列表主容器
+.device-warn-list {
+  --image-title-bg: url('@/assets/images/home-container/configurable/hsq/2-24.png');
+  height: 100%;
+  padding: 15px;
+  box-sizing: border-box;
+}
+
+// 告警历史记录区域
+.basic-history {
+  height: 100%;
+}
+
+// 表头行
+.history-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 34px;
+  background: var(--image-title-bg) no-repeat;
+  background-size: 100% 100%;
+  margin-bottom: 6px;
+}
+
+// 表头单元格
+.title-item {
+  display: flex;
+  flex: 1;
+  justify-content: center;
+  align-items: center;
+  color: #01fefc;
+  font-size: 13px;
+}
+
+// 表格内容可滚动区域
+.history-content {
+  height: calc(100% - 40px);
+  overflow-y: auto;
+}
+
+// 表格数据行
+.content-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 30px;
+
+  // 奇数行与偶数行交替背景色
+  &:nth-child(odd) {
+    background-color: #0e3455;
+  }
+
+  &:nth-child(even) {
+    background-color: #114268;
+  }
+}
+
+// 表格数据单元格
+.item-text {
+  display: flex;
+  flex: 1;
+  justify-content: center;
+  align-items: center;
+  font-size: 13px;
+}
+
+// 隐藏滚动条
+::-webkit-scrollbar {
+    display: none;
+  }
+</style>

+ 1 - 1
src/views/vent/monitorManager/hsqHome/components/navMenu.vue

@@ -13,10 +13,10 @@ let activeIndex = ref(0);
 let menuList = ref<any[]>([
   { label: '一张图', value: 'yzt' },
   { label: '监测预警', value: 'jcyj' },
+  { label: '视频预览', value: 'spyl' },
   { label: '设备管理', value: 'sbgl' },
   { label: '联动配置', value: 'ldpz' },
   { label: '系统管理', value: 'xtgl' },
-  // { label: '大屏展示', value: 'dpzs' },
 ]);
 let $emit = defineEmits(['changeMenu']);
 

+ 7 - 0
src/views/vent/monitorManager/hsqHome/hsqHome.api.ts

@@ -15,6 +15,7 @@ enum Api {
   getWarningDeviceList = '/safety/managesysAlarm/list', //获取配置预警设备数据
   getAlarmLogList = '/monitor/groupCompany/getAlarmLogList',  // 设备预警历史查询
   warningList = '/safety/managesysAlarmInfo/list',//预警条目list
+  dscAlarmLogList = '/safety/dscAlarmLog/list',//设备预警历史查询
   
 }
 /**
@@ -82,3 +83,9 @@ export const queryRoleAndUserNum = (params) => defHttp.get({ url: Api.queryRoleA
 export const getWarningDeviceList = (params) => defHttp.get({ url: Api.getWarningDeviceList, params });
 export const getAlarmLogList = (params) => defHttp.post({ url: Api.getAlarmLogList, params });
 export const warningList = (params) => defHttp.get({ url: Api.warningList, params });
+
+/**
+* 双光谱摄像机报警记录-分页列表查询
+* @param params
+*/
+export const dscAlarmLogList = (params) => defHttp.get({ url: Api.dscAlarmLogList, params });

+ 91 - 0
src/views/vent/monitorManager/hsqHome/hsqHome.data.ts

@@ -1,3 +1,15 @@
+/** Tab 标签项数据结构 */
+interface TabItem {
+  key: string
+  label: string
+}
+/** 镜头控制项(变焦/聚焦/光圈)数据结构 */
+interface LensControl {
+  key: string
+  icon: string
+}
+
+
 //设备状态总览
 export let option = [
   {
@@ -223,3 +235,82 @@ export let warnTableConfigD: any = {
   readFrom: '',
   type: 'D',
 }
+
+//视频预览-报警列表表头
+export let videoColumns: any[] = [
+  {
+    name: '设备号',
+    prop: 'preset',
+  },
+  {
+    name: '设备名称',
+    prop: 'deviceName',
+  },
+  {
+    name: '设备安装位置',
+    prop: 'devicePos',
+  },
+  {
+    name: '报警类型',
+    prop: 'eventType',
+  },
+  {
+    name: '红外类型',
+    prop: 'irModel',
+  },
+  {
+    name: '模式',
+    prop: 'mode',
+  },
+  {
+    name: '红外水平像素数量',
+    prop: 'numOfIrHorPixs',
+  },
+  {
+    name: '红外垂直像素数量',
+    prop: 'numOfIrVerPixs',
+  },
+  {
+    name: '云台水平角',
+    prop: 'ptzHorAngle',
+  },
+  {
+    name: '云台垂直角',
+    prop: 'ptzVerAngle',
+  },
+  {
+    name: '测温标记',
+    prop: 'tempFlag',
+  },
+]
+
+/**视频预览-Tab 标签列表 */
+export const tabs: TabItem[] = [
+  { key: 'device', label: '设备' },
+  { key: 'stats', label: '统计' },
+  { key: 'advanced', label: '高级' },
+  { key: 'preset', label: '预置位' },
+]
+
+/**云台控制-功能 Tab 列表:云台控制 / 红外控制 / 激光 / 透雾 */
+export const tabsPtz: TabItem[] = [
+  { key: 'ptz', label: '云台控制' },
+  { key: 'ir', label: '红外控制' },
+  { key: 'laser', label: '激光' },
+  { key: 'defog', label: '透雾' },
+]
+/**云台控制 - 镜头控制项:变焦、聚焦、光圈 */
+export const lensControls: LensControl[] = [
+  {
+    key: 'zoom',
+    icon: `<svg viewBox="0 0 24 24" width="18" height="18"><circle cx="10" cy="10" r="6" fill="none" stroke="#fff" stroke-width="1.6"/><path d="M14.5 14.5 L20 20" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/><path d="M10 7.5 V12.5 M7.5 10 H12.5" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/></svg>`,
+  },
+  {
+    key: 'focus',
+    icon: `<svg viewBox="0 0 24 24" width="18" height="18"><rect x="3" y="7" width="12" height="10" rx="1.5" fill="none" stroke="#fff" stroke-width="1.5"/><circle cx="9" cy="12" r="2.5" fill="none" stroke="#fff" stroke-width="1.4"/><path d="M15 9.5 L21 7 V17 L15 14.5 Z" fill="none" stroke="#fff" stroke-width="1.4" stroke-linejoin="round"/></svg>`,
+  },
+  {
+    key: 'iris',
+    icon: `<svg viewBox="0 0 24 24" width="18" height="18"><circle cx="12" cy="12" r="8" fill="none" stroke="#fff" stroke-width="1.4"/><path d="M12 4 L14.5 10.5 L21 12 L14.5 13.5 L12 20 L9.5 13.5 L3 12 L9.5 10.5 Z" fill="none" stroke="#fff" stroke-width="1.2" stroke-linejoin="round"/></svg>`,
+  },
+]

+ 3 - 0
src/views/vent/monitorManager/hsqHome/index.vue

@@ -21,6 +21,7 @@ import warnMonitor from './warnMonitor.vue'
 import systemManger from './systemManger.vue'
 import largeScreen from './largeScreen.vue'
 import linkConfiguration from './linkConfiguration.vue'
+import videoPlayer from './videoPlayer.vue'
 
 
 /** 菜单键值映射接口,定义各导航菜单项对应的 key 标识 */
@@ -31,6 +32,7 @@ interface IMenuKeyMap {
   ldpz: 'ldpz'
   xtgl: 'xtgl'
   dpzs: 'dpzs'
+  spyl: 'spyl'
 }
 /** 菜单 key 类型,从 IMenuKeyMap 中提取所有键的联合类型 */
 type MenuKey = IMenuKeyMap[keyof IMenuKeyMap]
@@ -51,6 +53,7 @@ const menuComponentMap: Record<MenuKey, Component> = {
   ldpz: linkConfiguration,
   xtgl: systemManger,
   dpzs: largeScreen,
+  spyl: videoPlayer,
 }
 
 /**

+ 214 - 0
src/views/vent/monitorManager/hsqHome/videoPlayer.vue

@@ -0,0 +1,214 @@
+<template>
+  <!-- 视频播放器页面 - 左右双栏布局,左侧展示设备树和设备控制,右侧展示设备画面和告警列表 -->
+  <div class="video-player">
+    <!-- 左侧面板:设备树 + 设备控制 -->
+    <div class="left-box">
+      <!-- 左上:设备树组件 -->
+      <div class="basic-top-left">
+        <DeviceTree :treeData="treeData" />
+      </div>
+      <!-- 左下:设备控制组件 -->
+      <div class="basic-bottom-left">
+        <DeviceControl :deviceOptions="deviceOptions" />
+      </div>
+    </div>
+    <!-- 右侧面板:设备画面 + 告警列表 -->
+    <div class="right-box">
+      <!-- 右上:设备画面组件 -->
+      <div class="basic-top-right">
+        <DeviceView />
+      </div>
+      <!-- 右下:设备告警列表组件 -->
+      <div class="basic-bottom-right">
+        <DeviceWarnList :warnList="warnList" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+
+// 子组件导入
+import DeviceTree from './components/DeviceTree.vue' // 设备树
+import DeviceControl from './components/DeviceControl.vue' // 设备控制
+import DeviceView from './components/DeviceView.vue' // 设备画面
+import DeviceWarnList from './components/DeviceWarnList.vue' // 设备告警列表
+import { managesysList, monitorSystem, dscAlarmLogList } from './hsqHome.api'
+
+/** 设备分组节点数据结构 */
+interface GroupNode {
+  id: string
+  title: string
+  children: DeviceNode[]
+}
+/** 设备节点数据结构 */
+interface DeviceNode {
+  id: string
+  name: string
+  /** 三个勾选状态:如显示/联动/告警等 */
+  checks: [boolean, boolean, boolean]
+}
+
+//系统id
+const systemId = ref('')
+/** 设备树数据(包含分组和设备列表) */
+const treeData = ref<GroupNode[]>([])
+// 设备选项
+const deviceOptions = ref<any[]>([])
+// 双光谱摄像机报警记录-分页列表查询
+const warnList = ref<any[]>([])
+/** 定时获取监测数据定时器 */
+let timer: null | NodeJS.Timeout = null;
+
+/**
+ * 定时获取监测数据
+ * 通过定时器循环调用数据获取方法,更新表格数据
+ *
+ * @param flag - 是否立即执行(首次调用时传true跳过延时)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    await getTableData();
+    await getDscAlarmLogList();
+    if (timer) {
+      timer = null;
+    }
+    getMonitor();
+  }, flag ? 0 : 30000);
+}
+
+/**
+ * 获取管理系统列表
+ * 调用 managesysList 接口获取户外火灾相关系统列表,并取第一个系统ID作为当前系统
+ */
+async function getManagesysList() {
+  const res = await managesysList({ strtype: 'sys_openair_fire', pagetype: 'normal' });
+  if (res && res.records) {
+    systemId.value = res.records[0].id
+  }
+}
+
+/**
+ * 获取设备列表数据
+ *
+ * 调用 deviceList 接口获取双光谱相机设备列表,
+ * 并将返回数据赋值给 tableData
+ */
+async function getTableData() {
+  const res = await monitorSystem({ type: 'all', systemID: systemId.value, devicetype: '' });
+  if (res.deviceInfo.duaSpeCamera.datalist.length > 0 && res.deviceInfo.imgFireDet.datalist.length > 0) {
+    let data = [...res.deviceInfo.duaSpeCamera.datalist, ...res.deviceInfo.imgFireDet.datalist]
+    treeData.value = [
+      {
+        id: 'g1',
+        title: '双光谱摄像机',
+        children: res.deviceInfo.duaSpeCamera.datalist.map(d => {
+          return {
+            id: d.deviceID,
+            name: d.strname,
+            checks: [false, false, false]
+          }
+        })
+      },
+      {
+        id: 'g2',
+        title: '火灾探测器',
+        children: res.deviceInfo.imgFireDet.datalist.map(i => {
+          return {
+            id: i.deviceID,
+            name: i.strname,
+            checks: [false, false, false]
+          }
+        })
+      },
+    ]
+    console.log(treeData.value, 'treeData.value')
+    deviceOptions.value = data.map(d => ({
+      value: d.deviceID,
+      label: d.strname,
+    }))
+  }
+
+}
+
+/**
+ * 获取双光谱摄像机报警记录-分页列表查询
+ * 调用 dscAlarmLogList 接口获取双光谱摄像机报警记录分页列表,
+ * 并将返回数据赋值给 warnList
+ */
+async function getDscAlarmLogList() {
+  const res = await dscAlarmLogList({ pageNo: 1, pageSize: 1000 });
+  if (res && res.records) {
+    warnList.value = res.records
+  }
+}
+
+onMounted(async () => {
+  await getManagesysList();
+  getMonitor(true);
+})
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+// 视频播放器主容器 - 左右双栏布局
+.video-player {
+  --image-bg: url('@/assets/images/home-container/configurable/hsq/1-1.png');
+  --image-bg1: url('@/assets/images/home-container/configurable/hsq/5-4.png');
+  --image-bg2: url('@/assets/images/home-container/configurable/hsq/3-3.png');
+  --image-bg3: url('@/assets/images/home-container/configurable/hsq/7-1.png');
+  position: relative;
+  width: 100%;
+  height: calc(100% - 73px);
+  color: @white;
+  margin-bottom: 10px;
+  margin-top: 63px;
+  display: flex;
+  justify-content: space-between;
+
+  // 左侧面板:固定宽度 430px
+  .left-box {
+    width: 430px;
+    height: 100%;
+    margin-right: 10px;
+  }
+
+  // 左上区域:设备树,占左侧面板 53% 高度
+  .basic-top-left {
+    height: 53%;
+    margin-bottom: 10px;
+    background: var(--image-bg) no-repeat;
+    background-size: 100% 100%;
+  }
+
+  // 左下区域:设备控制,占剩余高度
+  .basic-bottom-left {
+    height: calc(100% - 53% - 10px);
+    background: var(--image-bg1) no-repeat;
+    background-size: 100% 100%;
+  }
+
+  // 右侧面板:自适应宽度
+  .right-box {
+    width: calc(100% - 440px);
+    height: 100%;
+  }
+
+  // 右上区域:设备画面,占右侧面板 72% 高度
+  .basic-top-right {
+    height: 72%;
+    margin-bottom: 10px;
+    background: var(--image-bg3) no-repeat;
+    background-size: 100% 100%;
+  }
+
+  // 右下区域:设备告警列表,占剩余高度
+  .basic-bottom-right {
+    height: calc(100% - 72% - 10px);
+    background: var(--image-bg2) no-repeat;
+    background-size: 100% 100%;
+  }
+}
+</style>