index5.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. <template>
  2. <div class="app-container">
  3. <el-tabs v-model="activeTab">
  4. <!-- 广播分区管理标签页 -->
  5. <el-tab-pane label="广播分区管理" name="zones">
  6. <el-form :model="zoneQuery" ref="zoneQueryRef" :inline="true" label-width="80px">
  7. <el-form-item label="分区名称" prop="zoneName">
  8. <el-input v-model="zoneQuery.zoneName" placeholder="请输入分区名称" clearable />
  9. </el-form-item>
  10. <el-form-item label="分区类型" prop="zoneType">
  11. <el-select v-model="zoneQuery.zoneType" placeholder="请选择分区类型" clearable>
  12. <el-option label="公共区域" value="公共区域" />
  13. <el-option label="办公区域" value="办公区域" />
  14. <el-option label="会议室" value="会议室" />
  15. <el-option label="走廊" value="走廊" />
  16. <el-option label="停车场" value="停车场" />
  17. </el-select>
  18. </el-form-item>
  19. <el-form-item label="楼栋" prop="buildingName">
  20. <el-select v-model="zoneQuery.buildingName" placeholder="请选择楼栋" clearable>
  21. <el-option label="A栋" value="A栋" />
  22. <el-option label="B栋" value="B栋" />
  23. <el-option label="C栋" value="C栋" />
  24. <el-option label="D栋" value="D栋" />
  25. <el-option label="E栋" value="E栋" />
  26. <el-option label="室外" value="室外" />
  27. <el-option label="地下" value="地下" />
  28. </el-select>
  29. </el-form-item>
  30. <el-form-item label="广播状态" prop="broadcastStatus">
  31. <el-select v-model="zoneQuery.broadcastStatus" placeholder="请选择状态" clearable>
  32. <el-option label="空闲" :value="0" />
  33. <el-option label="广播中" :value="1" />
  34. <el-option label="紧急广播" :value="2" />
  35. </el-select>
  36. </el-form-item>
  37. <el-form-item>
  38. <el-button type="primary" icon="Search" @click="getZoneList">搜索</el-button>
  39. <el-button icon="Refresh" @click="resetZoneQuery">重置</el-button>
  40. </el-form-item>
  41. </el-form>
  42. <!-- 快捷操作按钮 -->
  43. <el-row :gutter="10" class="mb8">
  44. <el-col :span="1.5">
  45. <el-button
  46. type="danger"
  47. plain
  48. icon="Notification"
  49. @click="handleEmergencyBroadcast"
  50. v-hasPermi="['broadcast:zone:emergency']"
  51. >紧急广播</el-button>
  52. </el-col>
  53. <el-col :span="1.5">
  54. <el-button
  55. type="primary"
  56. plain
  57. icon="Microphone"
  58. @click="handleQuickBroadcast"
  59. v-hasPermi="['broadcast:zone:broadcast']"
  60. >快速广播</el-button>
  61. </el-col>
  62. <el-col :span="1.5">
  63. <el-button
  64. type="warning"
  65. plain
  66. icon="Phone"
  67. @click="handlePaging"
  68. v-hasPermi="['broadcast:zone:paging']"
  69. >寻呼找人</el-button>
  70. </el-col>
  71. </el-row>
  72. <el-table
  73. v-loading="zoneLoading"
  74. :data="zoneList"
  75. stripe
  76. border
  77. @selection-change="handleSelectionChange"
  78. :row-class-name="getRowClassName"
  79. >
  80. <el-table-column type="selection" width="55" align="center" />
  81. <el-table-column label="分区编码" prop="zoneCode" width="150" />
  82. <el-table-column label="分区名称" prop="zoneName" width="180" />
  83. <el-table-column label="分区类型" prop="zoneType" width="100" />
  84. <el-table-column label="位置" width="180">
  85. <template #default="scope">
  86. <span>{{ scope.row.spaceLocation }}</span>
  87. </template>
  88. </el-table-column>
  89. <el-table-column label="在线状态" prop="isOnline" width="90" align="center">
  90. <template #default="scope">
  91. <el-tag :type="scope.row.isOnline === 1 ? 'success' : 'danger'" effect="dark" size="small">
  92. {{ scope.row.isOnline === 1 ? '在线' : '离线' }}
  93. </el-tag>
  94. </template>
  95. </el-table-column>
  96. <el-table-column label="广播状态" prop="broadcastStatus" width="120" align="center">
  97. <template #default="scope">
  98. <el-tag
  99. :type="getBroadcastStatusType(scope.row.broadcastStatus)"
  100. effect="dark"
  101. >
  102. <el-icon v-if="scope.row.broadcastStatus === 1" class="is-loading">
  103. <Loading />
  104. </el-icon>
  105. {{ getBroadcastStatusText(scope.row.broadcastStatus) }}
  106. </el-tag>
  107. </template>
  108. </el-table-column>
  109. <el-table-column label="当前播放" prop="currentContent" min-width="150" show-overflow-tooltip />
  110. <el-table-column label="音量控制" width="200" align="center">
  111. <template #default="scope">
  112. <div class="volume-control">
  113. <el-button
  114. :icon="scope.row.isMute ? 'MuteNotification' : 'Notification'"
  115. circle
  116. size="small"
  117. @click="toggleMute(scope.row)"
  118. :type="scope.row.isMute ? 'danger' : 'primary'"
  119. />
  120. <el-slider
  121. v-model="scope.row.volume"
  122. :disabled="scope.row.isMute || scope.row.isOnline === 0"
  123. @change="handleVolumeChange(scope.row)"
  124. style="width: 120px; margin: 0 10px;"
  125. />
  126. <span>{{ scope.row.volume }}%</span>
  127. </div>
  128. </template>
  129. </el-table-column>
  130. <el-table-column label="操作" width="150" fixed="right">
  131. <template #default="scope">
  132. <el-button
  133. link
  134. type="primary"
  135. icon="VideoPlay"
  136. @click="handleControl(scope.row)"
  137. :disabled="scope.row.isOnline === 0"
  138. >控制</el-button>
  139. <el-button link type="primary" icon="View" @click="handleZoneDetail(scope.row)">详情</el-button>
  140. </template>
  141. </el-table-column>
  142. </el-table>
  143. <pagination
  144. v-show="zoneTotal > 0"
  145. :total="zoneTotal"
  146. v-model:page="zoneQuery.pageNum"
  147. v-model:limit="zoneQuery.pageSize"
  148. @pagination="getZoneList"
  149. />
  150. </el-tab-pane>
  151. <!-- 设备监控标签页 -->
  152. <el-tab-pane label="设备监控" name="devices">
  153. <el-form :model="deviceQuery" ref="deviceQueryRef" :inline="true" label-width="80px">
  154. <el-form-item label="设备名称" prop="deviceName">
  155. <el-input v-model="deviceQuery.deviceName" placeholder="请输入设备名称" clearable />
  156. </el-form-item>
  157. <el-form-item label="设备类型" prop="deviceType">
  158. <el-select v-model="deviceQuery.deviceType" placeholder="请选择设备类型" clearable>
  159. <el-option label="功放" value="功放" />
  160. <el-option label="音箱" value="音箱" />
  161. <el-option label="话筒" value="话筒" />
  162. <el-option label="控制器" value="控制器" />
  163. </el-select>
  164. </el-form-item>
  165. <el-form-item label="设备状态" prop="status">
  166. <el-select v-model="deviceQuery.status" placeholder="请选择状态" clearable>
  167. <el-option label="正常" :value="1" />
  168. <el-option label="故障" :value="0" />
  169. <el-option label="维护中" :value="2" />
  170. </el-select>
  171. </el-form-item>
  172. <el-form-item label="所属分区" prop="zoneName">
  173. <el-input v-model="deviceQuery.zoneName" placeholder="请输入分区名称" clearable />
  174. </el-form-item>
  175. <el-form-item>
  176. <el-button type="primary" icon="Search" @click="getDeviceList">搜索</el-button>
  177. <el-button icon="Refresh" @click="resetDeviceQuery">重置</el-button>
  178. </el-form-item>
  179. </el-form>
  180. <!-- 设备统计卡片 -->
  181. <el-row :gutter="20" class="mb20">
  182. <el-col :span="6">
  183. <el-card class="stat-card">
  184. <div class="stat-item">
  185. <div class="stat-title">设备总数</div>
  186. <div class="stat-value total">{{ deviceStats.total }}</div>
  187. </div>
  188. </el-card>
  189. </el-col>
  190. <el-col :span="6">
  191. <el-card class="stat-card">
  192. <div class="stat-item">
  193. <div class="stat-title">在线设备</div>
  194. <div class="stat-value online">{{ deviceStats.online }}</div>
  195. </div>
  196. </el-card>
  197. </el-col>
  198. <el-col :span="6">
  199. <el-card class="stat-card">
  200. <div class="stat-item">
  201. <div class="stat-title">故障设备</div>
  202. <div class="stat-value fault">{{ deviceStats.fault }}</div>
  203. </div>
  204. </el-card>
  205. </el-col>
  206. <el-col :span="6">
  207. <el-card class="stat-card">
  208. <div class="stat-item">
  209. <div class="stat-title">维护中</div>
  210. <div class="stat-value maintenance">{{ deviceStats.maintenance }}</div>
  211. </div>
  212. </el-card>
  213. </el-col>
  214. </el-row>
  215. <el-table v-loading="deviceLoading" :data="deviceList" stripe border>
  216. <el-table-column label="设备编码" prop="deviceCode" width="150" />
  217. <el-table-column label="设备名称" prop="deviceName" width="180" />
  218. <el-table-column label="设备类型" prop="deviceType" width="100" align="center">
  219. <template #default="scope">
  220. <el-tag>{{ scope.row.deviceType }}</el-tag>
  221. </template>
  222. </el-table-column>
  223. <el-table-column label="所属分区" prop="zoneName" width="180" />
  224. <el-table-column label="品牌型号" width="150">
  225. <template #default="scope">
  226. <div>{{ scope.row.brand }}</div>
  227. <div class="text-small">{{ scope.row.model }}</div>
  228. </template>
  229. </el-table-column>
  230. <el-table-column label="设备状态" prop="status" width="100" align="center">
  231. <template #default="scope">
  232. <el-tag :type="getDeviceStatusType(scope.row.status)" effect="dark">
  233. {{ getDeviceStatusText(scope.row.status) }}
  234. </el-tag>
  235. </template>
  236. </el-table-column>
  237. <el-table-column label="在线状态" prop="isOnline" width="100" align="center">
  238. <template #default="scope">
  239. <el-tag :type="scope.row.isOnline === 1 ? 'success' : 'danger'">
  240. {{ scope.row.isOnline === 1 ? '在线' : '离线' }}
  241. </el-tag>
  242. </template>
  243. </el-table-column>
  244. <el-table-column label="故障信息" width="200" show-overflow-tooltip>
  245. <template #default="scope">
  246. <span v-if="scope.row.faultCode" class="fault-info">
  247. {{ scope.row.faultCode }}: {{ scope.row.faultDesc }}
  248. </span>
  249. <span v-else>-</span>
  250. </template>
  251. </el-table-column>
  252. <el-table-column label="最后心跳" prop="lastHeartbeat" width="160">
  253. <template #default="scope">
  254. <span>{{ parseTime(scope.row.lastHeartbeat) }}</span>
  255. </template>
  256. </el-table-column>
  257. </el-table>
  258. <pagination
  259. v-show="deviceTotal > 0"
  260. :total="deviceTotal"
  261. v-model:page="deviceQuery.pageNum"
  262. v-model:limit="deviceQuery.pageSize"
  263. @pagination="getDeviceList"
  264. />
  265. </el-tab-pane>
  266. </el-tabs>
  267. <!-- 分区详情抽屉 -->
  268. <el-drawer
  269. v-model="detailVisible"
  270. title="分区详情"
  271. direction="rtl"
  272. size="45%"
  273. >
  274. <el-descriptions :column="2" border>
  275. <el-descriptions-item label="分区编码">{{ currentZone.zoneCode }}</el-descriptions-item>
  276. <el-descriptions-item label="分区名称">{{ currentZone.zoneName }}</el-descriptions-item>
  277. <el-descriptions-item label="分区类型">{{ currentZone.zoneType }}</el-descriptions-item>
  278. <el-descriptions-item label="楼栋">{{ currentZone.buildingName }}</el-descriptions-item>
  279. <el-descriptions-item label="楼层">{{ currentZone.floorName }}</el-descriptions-item>
  280. <el-descriptions-item label="设备数量">{{ currentZone.deviceCount }}</el-descriptions-item>
  281. <el-descriptions-item label="空间位置" :span="2">{{ currentZone.spaceLocation }}</el-descriptions-item>
  282. <el-descriptions-item label="音量">{{ currentZone.volume }}%</el-descriptions-item>
  283. <el-descriptions-item label="静音状态">
  284. <el-tag :type="currentZone.isMute === 1 ? 'danger' : 'success'">
  285. {{ currentZone.isMute === 1 ? '已静音' : '正常' }}
  286. </el-tag>
  287. </el-descriptions-item>
  288. <el-descriptions-item label="在线状态">
  289. <el-tag :type="currentZone.isOnline === 1 ? 'success' : 'danger'">
  290. {{ currentZone.isOnline === 1 ? '在线' : '离线' }}
  291. </el-tag>
  292. </el-descriptions-item>
  293. <el-descriptions-item label="广播状态">
  294. <el-tag :type="getBroadcastStatusType(currentZone.broadcastStatus)">
  295. {{ getBroadcastStatusText(currentZone.broadcastStatus) }}
  296. </el-tag>
  297. </el-descriptions-item>
  298. <el-descriptions-item label="当前播放内容" :span="2">
  299. {{ currentZone.currentContent || '无' }}
  300. </el-descriptions-item>
  301. <el-descriptions-item label="内容代号">{{ currentZone.contentCode || '-' }}</el-descriptions-item>
  302. <el-descriptions-item label="优先级">{{ currentZone.priority }}</el-descriptions-item>
  303. </el-descriptions>
  304. <!-- 分区内设备列表 -->
  305. <div class="zone-devices" v-if="zoneDevices && zoneDevices.length > 0">
  306. <h4>分区设备列表</h4>
  307. <el-table :data="zoneDevices" stripe size="small">
  308. <el-table-column label="设备编码" prop="deviceCode" />
  309. <el-table-column label="设备名称" prop="deviceName" />
  310. <el-table-column label="设备类型" prop="deviceType" />
  311. <el-table-column label="状态" prop="status" align="center">
  312. <template #default="scope">
  313. <el-tag :type="getDeviceStatusType(scope.row.status)" size="small">
  314. {{ getDeviceStatusText(scope.row.status) }}
  315. </el-tag>
  316. </template>
  317. </el-table-column>
  318. </el-table>
  319. </div>
  320. </el-drawer>
  321. <!-- 广播控制对话框 -->
  322. <el-dialog v-model="controlDialogVisible" title="广播控制" width="600px" append-to-body>
  323. <el-form ref="controlFormRef" :model="controlForm" :rules="controlRules" label-width="100px">
  324. <el-form-item label="分区名称">
  325. <el-input v-model="controlForm.zoneName" disabled />
  326. </el-form-item>
  327. <el-form-item label="广播内容" prop="contentCode">
  328. <el-select v-model="controlForm.contentCode" placeholder="请选择广播内容" @change="handleContentChange">
  329. <el-option
  330. v-for="item in contentList"
  331. :key="item.contentCode"
  332. :label="item.contentName"
  333. :value="item.contentCode"
  334. >
  335. <span style="float: left">{{ item.contentName }}</span>
  336. <span style="float: right; color: #8492a6; font-size: 13px">{{ item.contentType }}</span>
  337. </el-option>
  338. </el-select>
  339. </el-form-item>
  340. <el-form-item label="播放模式" prop="playMode">
  341. <el-radio-group v-model="controlForm.playMode">
  342. <el-radio :label="1">单次播放</el-radio>
  343. <el-radio :label="2">循环播放</el-radio>
  344. <el-radio :label="3">定时播放</el-radio>
  345. </el-radio-group>
  346. </el-form-item>
  347. <el-form-item label="音量调节">
  348. <el-slider v-model="controlForm.volume" :min="0" :max="100" show-input />
  349. </el-form-item>
  350. <el-form-item label="优先级" prop="priority">
  351. <el-rate v-model="controlForm.priority" :max="10" show-text />
  352. </el-form-item>
  353. </el-form>
  354. <template #footer>
  355. <span class="dialog-footer">
  356. <el-button @click="controlDialogVisible = false">取 消</el-button>
  357. <el-button type="primary" @click="submitControl">开始播放</el-button>
  358. <el-button type="danger" @click="stopBroadcast">停止播放</el-button>
  359. </span>
  360. </template>
  361. </el-dialog>
  362. <!-- 紧急广播对话框 -->
  363. <el-dialog v-model="emergencyDialogVisible" title="紧急广播" width="700px" append-to-body :close-on-click-modal="false">
  364. <el-alert
  365. title="紧急广播将中断所有正在播放的内容,请谨慎操作!"
  366. type="warning"
  367. show-icon
  368. :closable="false"
  369. style="margin-bottom: 20px"
  370. />
  371. <el-form ref="emergencyFormRef" :model="emergencyForm" :rules="emergencyRules" label-width="100px">
  372. <el-form-item label="广播范围" prop="broadcastRange">
  373. <el-radio-group v-model="emergencyForm.broadcastRange">
  374. <el-radio :label="1">全部分区</el-radio>
  375. <el-radio :label="2">选择分区</el-radio>
  376. </el-radio-group>
  377. </el-form-item>
  378. <el-form-item label="选择分区" v-if="emergencyForm.broadcastRange === 2" prop="selectedZones">
  379. <el-select
  380. v-model="emergencyForm.selectedZones"
  381. multiple
  382. placeholder="请选择分区"
  383. style="width: 100%"
  384. >
  385. <el-option
  386. v-for="zone in zoneList"
  387. :key="zone.id"
  388. :label="zone.zoneName"
  389. :value="zone.id"
  390. :disabled="zone.isOnline === 0"
  391. />
  392. </el-select>
  393. </el-form-item>
  394. <el-form-item label="广播方式" prop="broadcastType">
  395. <el-radio-group v-model="emergencyForm.broadcastType">
  396. <el-radio :label="1">预设内容</el-radio>
  397. <el-radio :label="2">实时喊话</el-radio>
  398. </el-radio-group>
  399. </el-form-item>
  400. <el-form-item label="预设内容" v-if="emergencyForm.broadcastType === 1" prop="contentCode">
  401. <el-select v-model="emergencyForm.contentCode" placeholder="请选择紧急广播内容">
  402. <el-option
  403. v-for="item in emergencyContentList"
  404. :key="item.contentCode"
  405. :label="item.contentName"
  406. :value="item.contentCode"
  407. />
  408. </el-select>
  409. </el-form-item>
  410. <el-form-item label="喊话内容" v-if="emergencyForm.broadcastType === 2" prop="message">
  411. <el-input
  412. v-model="emergencyForm.message"
  413. type="textarea"
  414. :rows="4"
  415. placeholder="请输入紧急广播内容"
  416. maxlength="200"
  417. show-word-limit
  418. />
  419. </el-form-item>
  420. <el-form-item label="循环次数" prop="loopCount">
  421. <el-input-number v-model="emergencyForm.loopCount" :min="1" :max="10" />
  422. </el-form-item>
  423. </el-form>
  424. <template #footer>
  425. <span class="dialog-footer">
  426. <el-button @click="emergencyDialogVisible = false">取 消</el-button>
  427. <el-button type="danger" @click="submitEmergency">立即广播</el-button>
  428. </span>
  429. </template>
  430. </el-dialog>
  431. </div>
  432. </template>
  433. <script setup>
  434. import { ref, reactive, computed } from 'vue'
  435. import {
  436. listBroadcastZone,
  437. listBroadcastDevice,
  438. updateZoneVolume,
  439. controlBroadcast,
  440. emergencyBroadcast,
  441. getZoneDevices,
  442. getBroadcastContent
  443. } from '@/api/publicBroadcasting/broadcast'
  444. const { proxy } = getCurrentInstance()
  445. const activeTab = ref('zones')
  446. // 分区查询相关
  447. const zoneQuery = reactive({
  448. pageNum: 1,
  449. pageSize: 10,
  450. zoneName: null,
  451. zoneType: null,
  452. buildingName: null,
  453. broadcastStatus: null
  454. })
  455. const zoneList = ref([])
  456. const zoneTotal = ref(0)
  457. const zoneLoading = ref(false)
  458. const selectedZones = ref([])
  459. // 设备查询相关
  460. const deviceQuery = reactive({
  461. pageNum: 1,
  462. pageSize: 10,
  463. deviceName: null,
  464. deviceType: null,
  465. status: null,
  466. zoneName: null
  467. })
  468. const deviceList = ref([])
  469. const deviceTotal = ref(0)
  470. const deviceLoading = ref(false)
  471. // 设备统计
  472. const deviceStats = ref({
  473. total: 0,
  474. online: 0,
  475. fault: 0,
  476. maintenance: 0
  477. })
  478. // 详情相关
  479. const detailVisible = ref(false)
  480. const currentZone = ref({})
  481. const zoneDevices = ref([])
  482. // 广播控制相关
  483. const controlDialogVisible = ref(false)
  484. const controlForm = ref({
  485. zoneId: null,
  486. zoneName: '',
  487. contentCode: '',
  488. playMode: 1,
  489. volume: 50,
  490. priority: 5
  491. })
  492. const contentList = ref([])
  493. const controlRules = {
  494. contentCode: [
  495. { required: true, message: '请选择广播内容', trigger: 'change' }
  496. ]
  497. }
  498. // 紧急广播相关
  499. const emergencyDialogVisible = ref(false)
  500. const emergencyForm = ref({
  501. broadcastRange: 1,
  502. selectedZones: [],
  503. broadcastType: 1,
  504. contentCode: '',
  505. message: '',
  506. loopCount: 3
  507. })
  508. const emergencyContentList = ref([])
  509. const emergencyRules = {
  510. selectedZones: [
  511. { required: true, message: '请选择分区', trigger: 'change' }
  512. ],
  513. contentCode: [
  514. { required: true, message: '请选择广播内容', trigger: 'change' }
  515. ],
  516. message: [
  517. { required: true, message: '请输入喊话内容', trigger: 'blur' }
  518. ]
  519. }
  520. // 获取广播状态类型
  521. const getBroadcastStatusType = (status) => {
  522. const types = ['info', 'success', 'danger']
  523. return types[status] || 'info'
  524. }
  525. // 获取广播状态文本
  526. const getBroadcastStatusText = (status) => {
  527. const texts = ['空闲', '广播中', '紧急广播']
  528. return texts[status] || '未知'
  529. }
  530. // 获取设备状态类型
  531. const getDeviceStatusType = (status) => {
  532. const types = ['danger', 'success', 'warning']
  533. return types[status] || 'info'
  534. }
  535. // 获取设备状态文本
  536. const getDeviceStatusText = (status) => {
  537. const texts = ['故障', '正常', '维护中']
  538. return texts[status] || '未知'
  539. }
  540. // 获取行样式
  541. const getRowClassName = ({ row }) => {
  542. if (row.broadcastStatus === 2) {
  543. return 'emergency-row'
  544. } else if (row.broadcastStatus === 1) {
  545. return 'broadcasting-row'
  546. }
  547. return ''
  548. }
  549. // 查询分区列表
  550. function getZoneList() {
  551. zoneLoading.value = true
  552. listBroadcastZone(zoneQuery).then(response => {
  553. zoneList.value = response.rows
  554. zoneTotal.value = response.total
  555. zoneLoading.value = false
  556. })
  557. }
  558. // 查询设备列表
  559. function getDeviceList() {
  560. deviceLoading.value = true
  561. listBroadcastDevice(deviceQuery).then(response => {
  562. deviceList.value = response.rows
  563. deviceTotal.value = response.total
  564. // 更新统计数据
  565. if (response.extData) {
  566. deviceStats.value = response.extData
  567. }
  568. deviceLoading.value = false
  569. })
  570. }
  571. // 重置分区查询
  572. function resetZoneQuery() {
  573. proxy.resetForm('zoneQueryRef')
  574. zoneQuery.pageNum = 1
  575. getZoneList()
  576. }
  577. // 重置设备查询
  578. function resetDeviceQuery() {
  579. proxy.resetForm('deviceQueryRef')
  580. deviceQuery.pageNum = 1
  581. getDeviceList()
  582. }
  583. // 多选框选中数据
  584. function handleSelectionChange(selection) {
  585. selectedZones.value = selection
  586. }
  587. // 切换静音
  588. function toggleMute(row) {
  589. const newMuteStatus = row.isMute === 1 ? 0 : 1
  590. updateZoneVolume({
  591. id: row.id,
  592. isMute: newMuteStatus,
  593. volume: row.volume
  594. }).then(() => {
  595. row.isMute = newMuteStatus
  596. proxy.$modal.msgSuccess(newMuteStatus === 1 ? '已静音' : '已取消静音')
  597. })
  598. }
  599. // 音量调节
  600. function handleVolumeChange(row) {
  601. updateZoneVolume({
  602. id: row.id,
  603. volume: row.volume,
  604. isMute: row.isMute
  605. }).then(() => {
  606. proxy.$modal.msgSuccess('音量调节成功')
  607. })
  608. }
  609. // 广播控制
  610. function handleControl(row) {
  611. controlForm.value = {
  612. zoneId: row.id,
  613. zoneName: row.zoneName,
  614. contentCode: '',
  615. playMode: 1,
  616. volume: row.volume,
  617. priority: 5
  618. }
  619. // 获取广播内容列表
  620. getBroadcastContent({ contentType: '背景音乐,通知' }).then(response => {
  621. contentList.value = response.rows
  622. })
  623. controlDialogVisible.value = true
  624. }
  625. // 内容选择变化
  626. function handleContentChange(value) {
  627. const content = contentList.value.find(item => item.contentCode === value)
  628. if (content) {
  629. controlForm.value.priority = content.priority
  630. }
  631. }
  632. // 提交广播控制
  633. function submitControl() {
  634. proxy.$refs.controlFormRef.validate(valid => {
  635. if (valid) {
  636. controlBroadcast(controlForm.value).then(() => {
  637. proxy.$modal.msgSuccess('广播开始播放')
  638. controlDialogVisible.value = false
  639. getZoneList()
  640. })
  641. }
  642. })
  643. }
  644. // 停止广播
  645. function stopBroadcast() {
  646. proxy.$modal.confirm('确认停止当前广播吗?').then(() => {
  647. controlBroadcast({
  648. zoneId: controlForm.value.zoneId,
  649. action: 'stop'
  650. }).then(() => {
  651. proxy.$modal.msgSuccess('广播已停止')
  652. controlDialogVisible.value = false
  653. getZoneList()
  654. })
  655. })
  656. }
  657. // 紧急广播
  658. function handleEmergencyBroadcast() {
  659. emergencyForm.value = {
  660. broadcastRange: 1,
  661. selectedZones: [],
  662. broadcastType: 1,
  663. contentCode: '',
  664. message: '',
  665. loopCount: 3
  666. }
  667. // 获取紧急广播内容
  668. getBroadcastContent({ contentType: '紧急广播' }).then(response => {
  669. emergencyContentList.value = response.rows
  670. })
  671. emergencyDialogVisible.value = true
  672. }
  673. // 提交紧急广播
  674. function submitEmergency() {
  675. proxy.$refs.emergencyFormRef.validate(valid => {
  676. if (valid) {
  677. proxy.$modal.confirm('确认发送紧急广播吗?这将中断所有正在播放的内容!').then(() => {
  678. emergencyBroadcast(emergencyForm.value).then(() => {
  679. proxy.$modal.msgSuccess('紧急广播已发送')
  680. emergencyDialogVisible.value = false
  681. getZoneList()
  682. })
  683. })
  684. }
  685. })
  686. }
  687. // 快速广播
  688. function handleQuickBroadcast() {
  689. if (selectedZones.value.length === 0) {
  690. proxy.$modal.msgWarning('请先选择要广播的分区')
  691. return
  692. }
  693. // TODO: 实现快速广播功能
  694. proxy.$modal.msgWarning('快速广播功能开发中')
  695. }
  696. // 寻呼找人
  697. function handlePaging() {
  698. // TODO: 实现寻呼功能
  699. proxy.$modal.msgWarning('寻呼找人功能开发中')
  700. }
  701. // 查看分区详情
  702. function handleZoneDetail(row) {
  703. currentZone.value = row
  704. // 获取分区内设备
  705. getZoneDevices(row.id).then(response => {
  706. zoneDevices.value = response.rows
  707. })
  708. detailVisible.value = true
  709. }
  710. // 初始化
  711. getZoneList()
  712. </script>
  713. <style scoped>
  714. .volume-control {
  715. display: flex;
  716. align-items: center;
  717. }
  718. .stat-card {
  719. height: 100px;
  720. margin-bottom: 20px;
  721. }
  722. .stat-item {
  723. text-align: center;
  724. padding: 10px;
  725. }
  726. .stat-title {
  727. font-size: 14px;
  728. color: #909399;
  729. margin-bottom: 10px;
  730. }
  731. .stat-value {
  732. font-size: 28px;
  733. font-weight: bold;
  734. }
  735. .stat-value.total {
  736. color: #409EFF;
  737. }
  738. .stat-value.online {
  739. color: #67C23A;
  740. }
  741. .stat-value.fault {
  742. color: #F56C6C;
  743. }
  744. .stat-value.maintenance {
  745. color: #E6A23C;
  746. }
  747. .text-small {
  748. font-size: 12px;
  749. color: #909399;
  750. }
  751. .fault-info {
  752. color: #F56C6C;
  753. font-size: 12px;
  754. }
  755. .zone-devices {
  756. margin: 20px;
  757. }
  758. .zone-devices h4 {
  759. margin-bottom: 15px;
  760. }
  761. .mb20 {
  762. margin-bottom: 20px;
  763. }
  764. /* 表格行样式 */
  765. :deep(.emergency-row) {
  766. background-color: #fef0f0 !important;
  767. }
  768. :deep(.broadcasting-row) {
  769. background-color: #f0f9ff !important;
  770. }
  771. /* 加载动画 */
  772. .is-loading {
  773. animation: rotating 2s linear infinite;
  774. }
  775. @keyframes rotating {
  776. 0% {
  777. transform: rotate(0deg);
  778. }
  779. 100% {
  780. transform: rotate(360deg);
  781. }
  782. }
  783. </style>