|
@@ -0,0 +1,844 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container">
|
|
|
+ <el-tabs v-model="activeTab">
|
|
|
+ <!-- 广播分区管理标签页 -->
|
|
|
+ <el-tab-pane label="广播分区管理" name="zones">
|
|
|
+ <el-form :model="zoneQuery" ref="zoneQueryRef" :inline="true" label-width="80px">
|
|
|
+ <el-form-item label="分区名称" prop="zoneName">
|
|
|
+ <el-input v-model="zoneQuery.zoneName" placeholder="请输入分区名称" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="分区类型" prop="zoneType">
|
|
|
+ <el-select v-model="zoneQuery.zoneType" placeholder="请选择分区类型" clearable>
|
|
|
+ <el-option label="公共区域" value="公共区域" />
|
|
|
+ <el-option label="办公区域" value="办公区域" />
|
|
|
+ <el-option label="会议室" value="会议室" />
|
|
|
+ <el-option label="走廊" value="走廊" />
|
|
|
+ <el-option label="停车场" value="停车场" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="楼栋" prop="buildingName">
|
|
|
+ <el-select v-model="zoneQuery.buildingName" placeholder="请选择楼栋" clearable>
|
|
|
+ <el-option label="A栋" value="A栋" />
|
|
|
+ <el-option label="B栋" value="B栋" />
|
|
|
+ <el-option label="C栋" value="C栋" />
|
|
|
+ <el-option label="D栋" value="D栋" />
|
|
|
+ <el-option label="E栋" value="E栋" />
|
|
|
+ <el-option label="室外" value="室外" />
|
|
|
+ <el-option label="地下" value="地下" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="广播状态" prop="broadcastStatus">
|
|
|
+ <el-select v-model="zoneQuery.broadcastStatus" placeholder="请选择状态" clearable>
|
|
|
+ <el-option label="空闲" :value="0" />
|
|
|
+ <el-option label="广播中" :value="1" />
|
|
|
+ <el-option label="紧急广播" :value="2" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="Search" @click="getZoneList">搜索</el-button>
|
|
|
+ <el-button icon="Refresh" @click="resetZoneQuery">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- 快捷操作按钮 -->
|
|
|
+ <el-row :gutter="10" class="mb8">
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ plain
|
|
|
+ icon="Notification"
|
|
|
+ @click="handleEmergencyBroadcast"
|
|
|
+ v-hasPermi="['broadcast:zone:emergency']"
|
|
|
+ >紧急广播</el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ plain
|
|
|
+ icon="Microphone"
|
|
|
+ @click="handleQuickBroadcast"
|
|
|
+ v-hasPermi="['broadcast:zone:broadcast']"
|
|
|
+ >快速广播</el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button
|
|
|
+ type="warning"
|
|
|
+ plain
|
|
|
+ icon="Phone"
|
|
|
+ @click="handlePaging"
|
|
|
+ v-hasPermi="['broadcast:zone:paging']"
|
|
|
+ >寻呼找人</el-button>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-table
|
|
|
+ v-loading="zoneLoading"
|
|
|
+ :data="zoneList"
|
|
|
+ stripe
|
|
|
+ border
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ :row-class-name="getRowClassName"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="55" align="center" />
|
|
|
+ <el-table-column label="分区编码" prop="zoneCode" width="150" />
|
|
|
+ <el-table-column label="分区名称" prop="zoneName" width="180" />
|
|
|
+ <el-table-column label="分区类型" prop="zoneType" width="100" />
|
|
|
+ <el-table-column label="位置" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <span>{{ scope.row.spaceLocation }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="在线状态" prop="isOnline" width="90" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="scope.row.isOnline === 1 ? 'success' : 'danger'" effect="dark" size="small">
|
|
|
+ {{ scope.row.isOnline === 1 ? '在线' : '离线' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="广播状态" prop="broadcastStatus" width="120" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag
|
|
|
+ :type="getBroadcastStatusType(scope.row.broadcastStatus)"
|
|
|
+ effect="dark"
|
|
|
+ >
|
|
|
+ <el-icon v-if="scope.row.broadcastStatus === 1" class="is-loading">
|
|
|
+ <Loading />
|
|
|
+ </el-icon>
|
|
|
+ {{ getBroadcastStatusText(scope.row.broadcastStatus) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="当前播放" prop="currentContent" min-width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column label="音量控制" width="200" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="volume-control">
|
|
|
+ <el-button
|
|
|
+ :icon="scope.row.isMute ? 'MuteNotification' : 'Notification'"
|
|
|
+ circle
|
|
|
+ size="small"
|
|
|
+ @click="toggleMute(scope.row)"
|
|
|
+ :type="scope.row.isMute ? 'danger' : 'primary'"
|
|
|
+ />
|
|
|
+ <el-slider
|
|
|
+ v-model="scope.row.volume"
|
|
|
+ :disabled="scope.row.isMute || scope.row.isOnline === 0"
|
|
|
+ @change="handleVolumeChange(scope.row)"
|
|
|
+ style="width: 120px; margin: 0 10px;"
|
|
|
+ />
|
|
|
+ <span>{{ scope.row.volume }}%</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" fixed="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ icon="VideoPlay"
|
|
|
+ @click="handleControl(scope.row)"
|
|
|
+ :disabled="scope.row.isOnline === 0"
|
|
|
+ >控制</el-button>
|
|
|
+ <el-button link type="primary" icon="View" @click="handleZoneDetail(scope.row)">详情</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <pagination
|
|
|
+ v-show="zoneTotal > 0"
|
|
|
+ :total="zoneTotal"
|
|
|
+ v-model:page="zoneQuery.pageNum"
|
|
|
+ v-model:limit="zoneQuery.pageSize"
|
|
|
+ @pagination="getZoneList"
|
|
|
+ />
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <!-- 设备监控标签页 -->
|
|
|
+ <el-tab-pane label="设备监控" name="devices">
|
|
|
+ <el-form :model="deviceQuery" ref="deviceQueryRef" :inline="true" label-width="80px">
|
|
|
+ <el-form-item label="设备名称" prop="deviceName">
|
|
|
+ <el-input v-model="deviceQuery.deviceName" placeholder="请输入设备名称" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="设备类型" prop="deviceType">
|
|
|
+ <el-select v-model="deviceQuery.deviceType" placeholder="请选择设备类型" clearable>
|
|
|
+ <el-option label="功放" value="功放" />
|
|
|
+ <el-option label="音箱" value="音箱" />
|
|
|
+ <el-option label="话筒" value="话筒" />
|
|
|
+ <el-option label="控制器" value="控制器" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="设备状态" prop="status">
|
|
|
+ <el-select v-model="deviceQuery.status" placeholder="请选择状态" clearable>
|
|
|
+ <el-option label="正常" :value="1" />
|
|
|
+ <el-option label="故障" :value="0" />
|
|
|
+ <el-option label="维护中" :value="2" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="所属分区" prop="zoneName">
|
|
|
+ <el-input v-model="deviceQuery.zoneName" placeholder="请输入分区名称" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="Search" @click="getDeviceList">搜索</el-button>
|
|
|
+ <el-button icon="Refresh" @click="resetDeviceQuery">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- 设备统计卡片 -->
|
|
|
+ <el-row :gutter="20" class="mb20">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-title">设备总数</div>
|
|
|
+ <div class="stat-value total">{{ deviceStats.total }}</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-title">在线设备</div>
|
|
|
+ <div class="stat-value online">{{ deviceStats.online }}</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-title">故障设备</div>
|
|
|
+ <div class="stat-value fault">{{ deviceStats.fault }}</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-title">维护中</div>
|
|
|
+ <div class="stat-value maintenance">{{ deviceStats.maintenance }}</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-table v-loading="deviceLoading" :data="deviceList" stripe border>
|
|
|
+ <el-table-column label="设备编码" prop="deviceCode" width="150" />
|
|
|
+ <el-table-column label="设备名称" prop="deviceName" width="180" />
|
|
|
+ <el-table-column label="设备类型" prop="deviceType" width="100" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag>{{ scope.row.deviceType }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="所属分区" prop="zoneName" width="180" />
|
|
|
+ <el-table-column label="品牌型号" width="150">
|
|
|
+ <template #default="scope">
|
|
|
+ <div>{{ scope.row.brand }}</div>
|
|
|
+ <div class="text-small">{{ scope.row.model }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="设备状态" prop="status" width="100" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="getDeviceStatusType(scope.row.status)" effect="dark">
|
|
|
+ {{ getDeviceStatusText(scope.row.status) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="在线状态" prop="isOnline" width="100" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="scope.row.isOnline === 1 ? 'success' : 'danger'">
|
|
|
+ {{ scope.row.isOnline === 1 ? '在线' : '离线' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="故障信息" width="200" show-overflow-tooltip>
|
|
|
+ <template #default="scope">
|
|
|
+ <span v-if="scope.row.faultCode" class="fault-info">
|
|
|
+ {{ scope.row.faultCode }}: {{ scope.row.faultDesc }}
|
|
|
+ </span>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="最后心跳" prop="lastHeartbeat" width="160">
|
|
|
+ <template #default="scope">
|
|
|
+ <span>{{ parseTime(scope.row.lastHeartbeat) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <pagination
|
|
|
+ v-show="deviceTotal > 0"
|
|
|
+ :total="deviceTotal"
|
|
|
+ v-model:page="deviceQuery.pageNum"
|
|
|
+ v-model:limit="deviceQuery.pageSize"
|
|
|
+ @pagination="getDeviceList"
|
|
|
+ />
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
+ <!-- 分区详情抽屉 -->
|
|
|
+ <el-drawer
|
|
|
+ v-model="detailVisible"
|
|
|
+ title="分区详情"
|
|
|
+ direction="rtl"
|
|
|
+ size="45%"
|
|
|
+ >
|
|
|
+ <el-descriptions :column="2" border>
|
|
|
+ <el-descriptions-item label="分区编码">{{ currentZone.zoneCode }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="分区名称">{{ currentZone.zoneName }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="分区类型">{{ currentZone.zoneType }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="楼栋">{{ currentZone.buildingName }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="楼层">{{ currentZone.floorName }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="设备数量">{{ currentZone.deviceCount }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="空间位置" :span="2">{{ currentZone.spaceLocation }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="音量">{{ currentZone.volume }}%</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="静音状态">
|
|
|
+ <el-tag :type="currentZone.isMute === 1 ? 'danger' : 'success'">
|
|
|
+ {{ currentZone.isMute === 1 ? '已静音' : '正常' }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="在线状态">
|
|
|
+ <el-tag :type="currentZone.isOnline === 1 ? 'success' : 'danger'">
|
|
|
+ {{ currentZone.isOnline === 1 ? '在线' : '离线' }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="广播状态">
|
|
|
+ <el-tag :type="getBroadcastStatusType(currentZone.broadcastStatus)">
|
|
|
+ {{ getBroadcastStatusText(currentZone.broadcastStatus) }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="当前播放内容" :span="2">
|
|
|
+ {{ currentZone.currentContent || '无' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="内容代号">{{ currentZone.contentCode || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="优先级">{{ currentZone.priority }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <!-- 分区内设备列表 -->
|
|
|
+ <div class="zone-devices" v-if="zoneDevices && zoneDevices.length > 0">
|
|
|
+ <h4>分区设备列表</h4>
|
|
|
+ <el-table :data="zoneDevices" stripe size="small">
|
|
|
+ <el-table-column label="设备编码" prop="deviceCode" />
|
|
|
+ <el-table-column label="设备名称" prop="deviceName" />
|
|
|
+ <el-table-column label="设备类型" prop="deviceType" />
|
|
|
+ <el-table-column label="状态" prop="status" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="getDeviceStatusType(scope.row.status)" size="small">
|
|
|
+ {{ getDeviceStatusText(scope.row.status) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <!-- 广播控制对话框 -->
|
|
|
+ <el-dialog v-model="controlDialogVisible" title="广播控制" width="600px" append-to-body>
|
|
|
+ <el-form ref="controlFormRef" :model="controlForm" :rules="controlRules" label-width="100px">
|
|
|
+ <el-form-item label="分区名称">
|
|
|
+ <el-input v-model="controlForm.zoneName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="广播内容" prop="contentCode">
|
|
|
+ <el-select v-model="controlForm.contentCode" placeholder="请选择广播内容" @change="handleContentChange">
|
|
|
+ <el-option
|
|
|
+ v-for="item in contentList"
|
|
|
+ :key="item.contentCode"
|
|
|
+ :label="item.contentName"
|
|
|
+ :value="item.contentCode"
|
|
|
+ >
|
|
|
+ <span style="float: left">{{ item.contentName }}</span>
|
|
|
+ <span style="float: right; color: #8492a6; font-size: 13px">{{ item.contentType }}</span>
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="播放模式" prop="playMode">
|
|
|
+ <el-radio-group v-model="controlForm.playMode">
|
|
|
+ <el-radio :label="1">单次播放</el-radio>
|
|
|
+ <el-radio :label="2">循环播放</el-radio>
|
|
|
+ <el-radio :label="3">定时播放</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="音量调节">
|
|
|
+ <el-slider v-model="controlForm.volume" :min="0" :max="100" show-input />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="优先级" prop="priority">
|
|
|
+ <el-rate v-model="controlForm.priority" :max="10" show-text />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="controlDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="submitControl">开始播放</el-button>
|
|
|
+ <el-button type="danger" @click="stopBroadcast">停止播放</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 紧急广播对话框 -->
|
|
|
+ <el-dialog v-model="emergencyDialogVisible" title="紧急广播" width="700px" append-to-body :close-on-click-modal="false">
|
|
|
+ <el-alert
|
|
|
+ title="紧急广播将中断所有正在播放的内容,请谨慎操作!"
|
|
|
+ type="warning"
|
|
|
+ show-icon
|
|
|
+ :closable="false"
|
|
|
+ style="margin-bottom: 20px"
|
|
|
+ />
|
|
|
+ <el-form ref="emergencyFormRef" :model="emergencyForm" :rules="emergencyRules" label-width="100px">
|
|
|
+ <el-form-item label="广播范围" prop="broadcastRange">
|
|
|
+ <el-radio-group v-model="emergencyForm.broadcastRange">
|
|
|
+ <el-radio :label="1">全部分区</el-radio>
|
|
|
+ <el-radio :label="2">选择分区</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="选择分区" v-if="emergencyForm.broadcastRange === 2" prop="selectedZones">
|
|
|
+ <el-select
|
|
|
+ v-model="emergencyForm.selectedZones"
|
|
|
+ multiple
|
|
|
+ placeholder="请选择分区"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="zone in zoneList"
|
|
|
+ :key="zone.id"
|
|
|
+ :label="zone.zoneName"
|
|
|
+ :value="zone.id"
|
|
|
+ :disabled="zone.isOnline === 0"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="广播方式" prop="broadcastType">
|
|
|
+ <el-radio-group v-model="emergencyForm.broadcastType">
|
|
|
+ <el-radio :label="1">预设内容</el-radio>
|
|
|
+ <el-radio :label="2">实时喊话</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="预设内容" v-if="emergencyForm.broadcastType === 1" prop="contentCode">
|
|
|
+ <el-select v-model="emergencyForm.contentCode" placeholder="请选择紧急广播内容">
|
|
|
+ <el-option
|
|
|
+ v-for="item in emergencyContentList"
|
|
|
+ :key="item.contentCode"
|
|
|
+ :label="item.contentName"
|
|
|
+ :value="item.contentCode"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="喊话内容" v-if="emergencyForm.broadcastType === 2" prop="message">
|
|
|
+ <el-input
|
|
|
+ v-model="emergencyForm.message"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入紧急广播内容"
|
|
|
+ maxlength="200"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="循环次数" prop="loopCount">
|
|
|
+ <el-input-number v-model="emergencyForm.loopCount" :min="1" :max="10" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="emergencyDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button type="danger" @click="submitEmergency">立即广播</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, computed } from 'vue'
|
|
|
+import {
|
|
|
+ listBroadcastZone,
|
|
|
+ listBroadcastDevice,
|
|
|
+ updateZoneVolume,
|
|
|
+ controlBroadcast,
|
|
|
+ emergencyBroadcast,
|
|
|
+ getZoneDevices,
|
|
|
+ getBroadcastContent
|
|
|
+} from '@/api/publicBroadcasting/broadcast'
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance()
|
|
|
+const activeTab = ref('zones')
|
|
|
+
|
|
|
+// 分区查询相关
|
|
|
+const zoneQuery = reactive({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ zoneName: null,
|
|
|
+ zoneType: null,
|
|
|
+ buildingName: null,
|
|
|
+ broadcastStatus: null
|
|
|
+})
|
|
|
+const zoneList = ref([])
|
|
|
+const zoneTotal = ref(0)
|
|
|
+const zoneLoading = ref(false)
|
|
|
+const selectedZones = ref([])
|
|
|
+
|
|
|
+// 设备查询相关
|
|
|
+const deviceQuery = reactive({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ deviceName: null,
|
|
|
+ deviceType: null,
|
|
|
+ status: null,
|
|
|
+ zoneName: null
|
|
|
+})
|
|
|
+const deviceList = ref([])
|
|
|
+const deviceTotal = ref(0)
|
|
|
+const deviceLoading = ref(false)
|
|
|
+
|
|
|
+// 设备统计
|
|
|
+const deviceStats = ref({
|
|
|
+ total: 0,
|
|
|
+ online: 0,
|
|
|
+ fault: 0,
|
|
|
+ maintenance: 0
|
|
|
+})
|
|
|
+
|
|
|
+// 详情相关
|
|
|
+const detailVisible = ref(false)
|
|
|
+const currentZone = ref({})
|
|
|
+const zoneDevices = ref([])
|
|
|
+
|
|
|
+// 广播控制相关
|
|
|
+const controlDialogVisible = ref(false)
|
|
|
+const controlForm = ref({
|
|
|
+ zoneId: null,
|
|
|
+ zoneName: '',
|
|
|
+ contentCode: '',
|
|
|
+ playMode: 1,
|
|
|
+ volume: 50,
|
|
|
+ priority: 5
|
|
|
+})
|
|
|
+const contentList = ref([])
|
|
|
+
|
|
|
+const controlRules = {
|
|
|
+ contentCode: [
|
|
|
+ { required: true, message: '请选择广播内容', trigger: 'change' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+// 紧急广播相关
|
|
|
+const emergencyDialogVisible = ref(false)
|
|
|
+const emergencyForm = ref({
|
|
|
+ broadcastRange: 1,
|
|
|
+ selectedZones: [],
|
|
|
+ broadcastType: 1,
|
|
|
+ contentCode: '',
|
|
|
+ message: '',
|
|
|
+ loopCount: 3
|
|
|
+})
|
|
|
+const emergencyContentList = ref([])
|
|
|
+
|
|
|
+const emergencyRules = {
|
|
|
+ selectedZones: [
|
|
|
+ { required: true, message: '请选择分区', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ contentCode: [
|
|
|
+ { required: true, message: '请选择广播内容', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ message: [
|
|
|
+ { required: true, message: '请输入喊话内容', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+// 获取广播状态类型
|
|
|
+const getBroadcastStatusType = (status) => {
|
|
|
+ const types = ['info', 'success', 'danger']
|
|
|
+ return types[status] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+// 获取广播状态文本
|
|
|
+const getBroadcastStatusText = (status) => {
|
|
|
+ const texts = ['空闲', '广播中', '紧急广播']
|
|
|
+ return texts[status] || '未知'
|
|
|
+}
|
|
|
+
|
|
|
+// 获取设备状态类型
|
|
|
+const getDeviceStatusType = (status) => {
|
|
|
+ const types = ['danger', 'success', 'warning']
|
|
|
+ return types[status] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+// 获取设备状态文本
|
|
|
+const getDeviceStatusText = (status) => {
|
|
|
+ const texts = ['故障', '正常', '维护中']
|
|
|
+ return texts[status] || '未知'
|
|
|
+}
|
|
|
+
|
|
|
+// 获取行样式
|
|
|
+const getRowClassName = ({ row }) => {
|
|
|
+ if (row.broadcastStatus === 2) {
|
|
|
+ return 'emergency-row'
|
|
|
+ } else if (row.broadcastStatus === 1) {
|
|
|
+ return 'broadcasting-row'
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+}
|
|
|
+
|
|
|
+// 查询分区列表
|
|
|
+function getZoneList() {
|
|
|
+ zoneLoading.value = true
|
|
|
+ listBroadcastZone(zoneQuery).then(response => {
|
|
|
+ zoneList.value = response.rows
|
|
|
+ zoneTotal.value = response.total
|
|
|
+ zoneLoading.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 查询设备列表
|
|
|
+function getDeviceList() {
|
|
|
+ deviceLoading.value = true
|
|
|
+ listBroadcastDevice(deviceQuery).then(response => {
|
|
|
+ deviceList.value = response.rows
|
|
|
+ deviceTotal.value = response.total
|
|
|
+ // 更新统计数据
|
|
|
+ if (response.extData) {
|
|
|
+ deviceStats.value = response.extData
|
|
|
+ }
|
|
|
+ deviceLoading.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 重置分区查询
|
|
|
+function resetZoneQuery() {
|
|
|
+ proxy.resetForm('zoneQueryRef')
|
|
|
+ zoneQuery.pageNum = 1
|
|
|
+ getZoneList()
|
|
|
+}
|
|
|
+
|
|
|
+// 重置设备查询
|
|
|
+function resetDeviceQuery() {
|
|
|
+ proxy.resetForm('deviceQueryRef')
|
|
|
+ deviceQuery.pageNum = 1
|
|
|
+ getDeviceList()
|
|
|
+}
|
|
|
+
|
|
|
+// 多选框选中数据
|
|
|
+function handleSelectionChange(selection) {
|
|
|
+ selectedZones.value = selection
|
|
|
+}
|
|
|
+
|
|
|
+// 切换静音
|
|
|
+function toggleMute(row) {
|
|
|
+ const newMuteStatus = row.isMute === 1 ? 0 : 1
|
|
|
+ updateZoneVolume({
|
|
|
+ id: row.id,
|
|
|
+ isMute: newMuteStatus,
|
|
|
+ volume: row.volume
|
|
|
+ }).then(() => {
|
|
|
+ row.isMute = newMuteStatus
|
|
|
+ proxy.$modal.msgSuccess(newMuteStatus === 1 ? '已静音' : '已取消静音')
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 音量调节
|
|
|
+function handleVolumeChange(row) {
|
|
|
+ updateZoneVolume({
|
|
|
+ id: row.id,
|
|
|
+ volume: row.volume,
|
|
|
+ isMute: row.isMute
|
|
|
+ }).then(() => {
|
|
|
+ proxy.$modal.msgSuccess('音量调节成功')
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 广播控制
|
|
|
+function handleControl(row) {
|
|
|
+ controlForm.value = {
|
|
|
+ zoneId: row.id,
|
|
|
+ zoneName: row.zoneName,
|
|
|
+ contentCode: '',
|
|
|
+ playMode: 1,
|
|
|
+ volume: row.volume,
|
|
|
+ priority: 5
|
|
|
+ }
|
|
|
+ // 获取广播内容列表
|
|
|
+ getBroadcastContent({ contentType: '背景音乐,通知' }).then(response => {
|
|
|
+ contentList.value = response.rows
|
|
|
+ })
|
|
|
+ controlDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 内容选择变化
|
|
|
+function handleContentChange(value) {
|
|
|
+ const content = contentList.value.find(item => item.contentCode === value)
|
|
|
+ if (content) {
|
|
|
+ controlForm.value.priority = content.priority
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 提交广播控制
|
|
|
+function submitControl() {
|
|
|
+ proxy.$refs.controlFormRef.validate(valid => {
|
|
|
+ if (valid) {
|
|
|
+ controlBroadcast(controlForm.value).then(() => {
|
|
|
+ proxy.$modal.msgSuccess('广播开始播放')
|
|
|
+ controlDialogVisible.value = false
|
|
|
+ getZoneList()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 停止广播
|
|
|
+function stopBroadcast() {
|
|
|
+ proxy.$modal.confirm('确认停止当前广播吗?').then(() => {
|
|
|
+ controlBroadcast({
|
|
|
+ zoneId: controlForm.value.zoneId,
|
|
|
+ action: 'stop'
|
|
|
+ }).then(() => {
|
|
|
+ proxy.$modal.msgSuccess('广播已停止')
|
|
|
+ controlDialogVisible.value = false
|
|
|
+ getZoneList()
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 紧急广播
|
|
|
+function handleEmergencyBroadcast() {
|
|
|
+ emergencyForm.value = {
|
|
|
+ broadcastRange: 1,
|
|
|
+ selectedZones: [],
|
|
|
+ broadcastType: 1,
|
|
|
+ contentCode: '',
|
|
|
+ message: '',
|
|
|
+ loopCount: 3
|
|
|
+ }
|
|
|
+ // 获取紧急广播内容
|
|
|
+ getBroadcastContent({ contentType: '紧急广播' }).then(response => {
|
|
|
+ emergencyContentList.value = response.rows
|
|
|
+ })
|
|
|
+ emergencyDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 提交紧急广播
|
|
|
+function submitEmergency() {
|
|
|
+ proxy.$refs.emergencyFormRef.validate(valid => {
|
|
|
+ if (valid) {
|
|
|
+ proxy.$modal.confirm('确认发送紧急广播吗?这将中断所有正在播放的内容!').then(() => {
|
|
|
+ emergencyBroadcast(emergencyForm.value).then(() => {
|
|
|
+ proxy.$modal.msgSuccess('紧急广播已发送')
|
|
|
+ emergencyDialogVisible.value = false
|
|
|
+ getZoneList()
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 快速广播
|
|
|
+function handleQuickBroadcast() {
|
|
|
+ if (selectedZones.value.length === 0) {
|
|
|
+ proxy.$modal.msgWarning('请先选择要广播的分区')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // TODO: 实现快速广播功能
|
|
|
+ proxy.$modal.msgWarning('快速广播功能开发中')
|
|
|
+}
|
|
|
+
|
|
|
+// 寻呼找人
|
|
|
+function handlePaging() {
|
|
|
+ // TODO: 实现寻呼功能
|
|
|
+ proxy.$modal.msgWarning('寻呼找人功能开发中')
|
|
|
+}
|
|
|
+
|
|
|
+// 查看分区详情
|
|
|
+function handleZoneDetail(row) {
|
|
|
+ currentZone.value = row
|
|
|
+ // 获取分区内设备
|
|
|
+ getZoneDevices(row.id).then(response => {
|
|
|
+ zoneDevices.value = response.rows
|
|
|
+ })
|
|
|
+ detailVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化
|
|
|
+getZoneList()
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.volume-control {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card {
|
|
|
+ height: 100px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-item {
|
|
|
+ text-align: center;
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value.total {
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value.online {
|
|
|
+ color: #67C23A;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value.fault {
|
|
|
+ color: #F56C6C;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value.maintenance {
|
|
|
+ color: #E6A23C;
|
|
|
+}
|
|
|
+
|
|
|
+.text-small {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.fault-info {
|
|
|
+ color: #F56C6C;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.zone-devices {
|
|
|
+ margin: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.zone-devices h4 {
|
|
|
+ margin-bottom: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.mb20 {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格行样式 */
|
|
|
+:deep(.emergency-row) {
|
|
|
+ background-color: #fef0f0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.broadcasting-row) {
|
|
|
+ background-color: #f0f9ff !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 加载动画 */
|
|
|
+.is-loading {
|
|
|
+ animation: rotating 2s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes rotating {
|
|
|
+ 0% {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|