DeviceView.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <template>
  2. <!-- 设备画面组件:右侧面板上方,包含操作按钮和 2×4 监控画面网格 -->
  3. <div class="device-view">
  4. <!-- 顶部操作按钮:最新热源 / 最新闯入 / 最新烟雾 / 关闭声光报警 -->
  5. <div class="view-actions">
  6. <button v-for="btn in actions" :key="btn.key" type="button"
  7. :class="['action-btn', { active: activeAction === btn.key }]" @click="onAction(btn.key)">
  8. {{ btn.label }}
  9. </button>
  10. </div>
  11. <!-- 2×4 监控画面网格 -->
  12. <div class="view-grid">
  13. <div class="view-cell" @click="selectChannel">
  14. <div class="media-wrap">
  15. <div v-if="renderPlayer" ref="playerRef" style="
  16. display: flex;
  17. width: 100%;
  18. height: 100%;
  19. overflow-y: auto;
  20. pointer-events: none;
  21. ">
  22. </div>
  23. </div>
  24. </div>
  25. </div>
  26. </div>
  27. </template>
  28. <script setup lang="ts">
  29. import { onBeforeUnmount, onMounted, ref } from 'vue'
  30. import { useCamera } from '/@/hooks/system/useCamera';
  31. /** 操作按钮数据结构 */
  32. interface ActionItem {
  33. key: string
  34. label: string
  35. }
  36. let props = defineProps({
  37. deviceId: {
  38. type: String,
  39. default: ''
  40. },
  41. });
  42. /** 组件自定义事件 */
  43. const emit = defineEmits<{
  44. (e: 'action', key: string): void
  45. (e: 'select', channel: any): void
  46. }>()
  47. const { getCamera, removeCamera } = useCamera();
  48. const playerRef = ref();
  49. const renderPlayer = ref(true);
  50. /** 顶部操作按钮列表 */
  51. const actions: ActionItem[] = [
  52. { key: 'heat', label: '最新热源' },
  53. { key: 'intrusion', label: '最新闯入' },
  54. { key: 'smoke', label: '最新烟雾' },
  55. { key: 'alarmOff', label: '关闭声光报警' },
  56. ]
  57. /** 当前激活的操作按钮 */
  58. const activeAction = ref('')
  59. /** 当前选中的通道 ID */
  60. const selectedId = ref('')
  61. /** 点击操作按钮,触发 action 事件 */
  62. function onAction(key: string) {
  63. activeAction.value = key
  64. emit('action', key)
  65. }
  66. /** 选中监控通道,触发 select 事件 */
  67. function selectChannel(item) {
  68. selectedId.value = item.id
  69. emit('select', item)
  70. }
  71. onMounted(async () => {
  72. await getCamera(props.deviceId, playerRef, renderPlayer);
  73. });
  74. onBeforeUnmount(() => {
  75. removeCamera(playerRef);
  76. });
  77. </script>
  78. <style lang="less" scoped>
  79. @import '/@/design/theme.less';
  80. // 设备画面主容器
  81. .device-view {
  82. --image-bg: url('@/assets/images/home-container/configurable/hsq/7-5.png');
  83. --dv-accent: #00d4ff;
  84. --dv-accent-soft: rgba(0, 212, 255, 0.45);
  85. --dv-text: #d8f4ff;
  86. width: 100%;
  87. height: 100%;
  88. display: flex;
  89. flex-direction: column;
  90. overflow: hidden;
  91. color: var(--dv-text);
  92. user-select: none;
  93. padding: 15px;
  94. box-sizing: border-box;
  95. }
  96. // 顶部操作按钮栏
  97. .view-actions {
  98. display: flex;
  99. flex-wrap: wrap;
  100. align-items: center;
  101. gap: 12px;
  102. margin-bottom: 14px;
  103. flex-shrink: 0;
  104. .action-btn {
  105. min-width: 96px;
  106. height: 30px;
  107. padding: 0 14px;
  108. border: 1px solid #269ee1;
  109. border-radius: 2px;
  110. background: #185f8e;
  111. color: #fff;
  112. font-size: 13px;
  113. letter-spacing: 0.5px;
  114. cursor: pointer;
  115. box-shadow: 0 0 6px rgba(0, 200, 255, 0.25), inset 0 0 8px rgba(0, 120, 180, 0.12);
  116. transition: color 0.2s, background 0.2s, box-shadow 0.2s;
  117. &:hover,
  118. &.active {
  119. color: #fff;
  120. background: rgba(0, 90, 140, 0.45);
  121. }
  122. &:active {
  123. transform: translateY(1px);
  124. }
  125. }
  126. }
  127. // 2×4 监控画面网格
  128. .view-grid {
  129. flex: 1;
  130. display: flex;
  131. // flex-wrap: wrap;
  132. // gap: 15px 10px;
  133. // justify-content: flex-start;
  134. // align-items: flex-start;
  135. overflow-y: auto;
  136. }
  137. // // 单个监控画面单元格
  138. // .view-cell {
  139. // width: calc(25% - 10px);
  140. // height: 220px;
  141. // cursor: pointer;
  142. // // background: var(--image-bg) no-repeat center;
  143. // // background-size: 100% 100%;
  144. // }
  145. // // 媒体内容包裹层
  146. // .media-wrap {
  147. // height: 100%;
  148. // padding: 10px 10px 12px 10px;
  149. // box-sizing: border-box;
  150. // }
  151. // 无画面占位提示
  152. .media-empty {
  153. height: 100%;
  154. display: flex;
  155. align-items: center;
  156. justify-content: center;
  157. }
  158. :deep(#LivePlayerBox) {
  159. display: flex;
  160. flex-direction: row;
  161. justify-content: flex-start;
  162. flex-wrap: wrap;
  163. pointer-events: none;
  164. gap: 10px;
  165. .liveVideo {
  166. width: 349px !important;
  167. height: 252px !important;
  168. padding: 12px 8px !important;
  169. align-self: auto !important;
  170. // margin: 8px !important;
  171. background-size: 100% 100% !important;
  172. }
  173. .video-parent {
  174. pointer-events: auto !important;
  175. align-self: auto !important;
  176. }
  177. }
  178. ::-webkit-scrollbar {
  179. display: none;
  180. }
  181. </style>