Files
dyf-vue-ui/src/views/controlCenter/100J/index.vue
2026-03-10 18:03:33 +08:00

887 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="device-page p-2">
<!-- 头部信息栏 -->
<div class="header-bar">
<div>设备名称{{ deviceDetail.deviceName }}</div>
<div>设备型号{{ deviceDetail.deviceImei }}</div>
<div class="device-status">设备状态
<span :class="{ online: deviceDetail.onlineStatus === 1, offline: deviceDetail?.onlineStatus === 0 }">
{{ deviceDetail.onlineStatus === 1 ? '在线' : '离线' }}
</span>
</div>
<div>电量{{ deviceDetail.batteryPercentage || 0 }}%</div>
<div>续航{{ deviceDetail.batteryRemainingTime || "0" }} 分钟</div>
</div>
<!-- 主体内容区域 -->
<div class="content-wrapper">
<el-row :gutter="20" class="content-row" :class="deviceDetail.alarmStatus == 1 ? '' : 'displayNone'">
<el-col :lg="24" :xs="24">
<div class="staticRwo" :class="deviceDetail.alarmStatus == 1 ? '' : 'displayNone'"
@click="showClose()">
设备强制报警中!
</div>
</el-col>
</el-row>
<!-- 第一行灯光模式 + 灯光亮度强制报警位置信息 -->
<el-row :gutter="20" class="content-row">
<el-col :lg="16" :xs="24">
<div class="content-card">
<h4 class="section-title">报警模式</h4>
<div class="light-mode">
<!-- 使用v-for循环渲染灯光模式卡片 -->
<div class="mode-card" :class="{ 'active': mode.active }"
@click.stop="handleVoiceType(mode.id)" v-for="mode in sta_VoiceType" :key="mode.id">
<img :src="mode.active ? mode.activeIcon : mode.icon" :alt="mode.name"
class="mode-icon" />
<div class="mode-name">{{ mode.name }}</div>
<el-switch v-model="mode.switchStatusVioice" />
</div>
</div>
</div>
</el-col>
<el-col :lg="8" :xs="24">
<div class="brightness-alarm">
<el-button type="danger" class="alarm-btn" @click="forceAlarm" :loading="forceAlarmLoading"
v-if="deviceDetail.alarmStatus === 0 || deviceDetail.alarmStatus === null"
:loading-text="forceAlarmLoading ? '报警中...' : '强制报警'"> {{
forceAlarmLoading ? '报警中' : '强制报警' }}</el-button>
</div>
<div class="content-card_gps">
<h4 class="section-title">位置信息</h4>
<div class="location-info">
<div class="location-item">
<span class="location-icon"></span>
<span>经纬度 {{ deviceDetail && deviceDetail.longitude ?
Number(deviceDetail.longitude).toFixed(4) : '无' }}
{{ deviceDetail && deviceDetail.latitude ? Number(deviceDetail.latitude).toFixed(4)
: '无' }} </span>
</div>
<div class="location-item">
<div>地址 <span class="lacatin_gps">{{ deviceDetail.address || "未获取到地址" }}</span></div>
<el-button link type="primary" class="view-btn"
@click="lookMap(deviceDetail)">查看</el-button>
</div>
</div>
</div>
</el-col>
</el-row>
<!-- 第二行人员信息登记 + 发送信息 -->
<el-row :gutter="20" class="content-row">
<el-col :lg="16" :xs="24">
<div class="content-card">
<h4 class="section-title">警示灯模式</h4>
<div class="light-mode">
<!-- 使用v-for循环渲染灯光模式卡片 -->
<div class="mode-card" :class="{ 'active': mode.active }"
@click.stop="handleModeClick(mode.id)" v-for="mode in lightModes" :key="mode.id">
<img :src="mode.active ? mode.activeIcon : mode.icon" :alt="mode.name"
class="mode-icon" />
<div class="mode-name">{{ mode.name }}</div>
<el-switch v-model="mode.switchStatus" />
</div>
</div>
</div>
</el-col>
<el-col :lg="8" :xs="24">
<div class="content-card ">
<div class="brightness-alarm">
<div class="brightness-control">
<span class="brightness-label">亮度</span>
<el-input class="inputTFT" v-model="deviceDetail.lightBrightness" :min="10" :max="100"
:step="1" size="small" />
<span class="brightness-value">%</span>
<el-button type="primary" class="save-btn" v-loading="lightModesLoading"
@click="saveBtnlight">保存 </el-button>
</div>
</div>
<div class="brightness-alarm">
<div class="brightness-control">
<span class="brightness-label">频率</span>
<el-input class="inputTFT" v-model="deviceDetail.strobeFrequency" :min="0.5" :max="10"
:step="1" size="small" />
<span class="brightness-value">HZ</span>
<el-button type="primary" class="save-btn" @click="saveBtnstrobe">保存 </el-button>
</div>
</div>
<div class="brightness-alarm">
<div class="brightness-control">
<span class="brightness-label">音量</span>
<el-input class="inputTFT" v-model="deviceDetail.volume" :min="10" :max="100" :step="1"
size="small" />
<span class="brightness-value">%</span>
<el-button type="primary" class="save-btn" @click="saveBtnVolume">保存 </el-button>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
<!-- ===========充电提示框====== -->
<el-dialog title="充电提示" v-model="centerDialogVisible" width="15%">
<div style="display: flex; align-items: center;">
<h3 style="color: rgba(224, 52, 52, 1)">设备电量低于20%</h3>
</div>
<div>请及时充电</div>
<span slot="footer" class="dialog-footer" style="text-align: right;display: block;">
<el-button type="primary" @click="centerDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script setup name="DeviceControl" lang="ts">
const route = useRoute();
import { useMqtt } from '@/utils/mqtt';
import api from '@/api/controlCenter/controlPanel/100J'
import { DeviceDetail, LightMode } from '@/api/controlCenter/controlPanel/types';
import {getDeviceStatus } from '@/utils/function';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
// 导入图片资源(确保路径正确)
import closeDefault from '@/assets/images/close.png';
import closeActive from '@/assets/images/close_HL.png';
import rb from '@/assets/images/rb.png';
import rbAc from '@/assets/images/rbAc.png';
import sg from '@/assets/images/sg.png';
import sgAc from '@/assets/images/sgAc.png';
const forceAlarmLoading = ref(false) //强制报警
const lightModesLoading = ref(false)
const centerDialogVisible = ref(false)
const {
connected,
connect,
subscribe,
onConnect,
onError,
onMessage,
disconnect
} = useMqtt();
// 报警模式
const sta_VoiceType = ref([
{
id: 'fire',
name: '消防',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
switchStatusVioice: true,
active: true
},
{
id: '0',
name: '公安',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
active: false,
switchStatusVioice: false,
},
{
id: '3',
name: '交警',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
switchStatusVioice: false,
active: false
},
{
id: '4',
name: '市政',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
switchStatusVioice: false,
active: false
},
{
id: '2',
name: '应急',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
switchStatusVioice: false,
active: false
},
{
id: '6',
name: '医疗',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
switchStatusVioice: false,
active: false
},
{
id: '5',
name: '铁道',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
switchStatusVioice: false,
active: false
},
{
id: '7',
name: 'app语音',
icon: sg, // 直接使用导入的变量
activeIcon: sgAc,
switchStatusVioice: false,
active: false
},
{
id: '-1',
name: '关闭',
icon: closeDefault, // 直接使用导入的变量
activeIcon: closeActive,
switchStatusVioice: false,
active: false
},
])
// 警示灯灯光模式数据(引用导入的图片)
const lightModes = ref<LightMode[]>([
{
id: 'redBlueAlternate', // 红蓝交替
name: '红蓝交替',
icon: rb,
activeIcon: rbAc,
switchStatus: true,
instructValue: '6',
active: true,
},
{
id: 'redFlash', // 红闪
name: '红闪',
icon: rb,
activeIcon: rbAc,
switchStatus: false,
instructValue: '0',
active: false,
},
{
id: 'yellowFlash', // 黄闪
name: '黄闪',
icon: rb,
activeIcon: rbAc,
switchStatus: false,
instructValue: '2',
active: false
},
{
id: 'blueFlash', // 蓝闪
name: '蓝闪',
icon: rb,
activeIcon: rbAc,
switchStatus: false,
instructValue: '1',
active: false
},
{
id: 'redClockwise', // 红色顺时针
name: '红色顺时针',
icon: rb,
activeIcon: rbAc,
switchStatus: false,
instructValue: '3',
active: false
},
{
id: 'yellowClockwise', // 黄色顺时针
name: '黄色顺时针',
icon: rb,
activeIcon: rbAc,
switchStatus: false,
instructValue: '4',
active: false
},
{
id: 'redBlueClockwise', // 红蓝顺时针
name: '红蓝顺时针',
icon: rb,
activeIcon: rbAc,
switchStatus: false,
instructValue: '5',
active: false
},
{
id: 'close', // 关闭
name: '关闭',
icon: closeDefault,
activeIcon: closeActive,
switchStatus: false,
instructValue: '-1',
active: false
},
]);
const deviceDetail = ref<DeviceDetail & { typeName: string }>({
lightBrightness: '',
deviceName: '',
deviceImei: '',
onlineStatus: 0,
batteryPercentage: 0,
batteryRemainingTime: '',
longitude: '',
latitude: '',
address: '',
sendMsg: '',
chargeState: '0',
typeName: '',
alarmStatus: 0,
strobeFrequency: '0.5',
volume: '10'
});
const isUpdatingStatus = ref(false);
// 报警模式// 2. 点击卡片事件
const handleVoiceType = async (targetId: string) => {
const deviceId = route.params.deviceId as string;
if (!deviceId) return;
const targetMode = sta_VoiceType.value.find(mode => mode.id === targetId);
if (!targetMode) return;
if (targetMode.active) return;
sta_VoiceType.value.forEach(mode => {
mode.active = mode.id === targetId;
if (mode.active) {
mode.switchStatusVioice = true;
} else {
mode.switchStatusVioice = false;
}
});
// 2. 调用强制报警接口
const params = {
deviceIds: [deviceId],
voiceStrobeAlarm: targetId === '-1' ? 0 : 1,
mode: targetId == '-1' ? 0 : targetId
};
try {
const res = await api.SosSetting(params);
if (res.code == 200) {
proxy?.$modal.msgSuccess(res.msg);
}
} catch (error) {
await getList()
}
};
// 警示灯模式
const handleModeClick = async (modeId: string) => {
if (isUpdatingStatus.value || isSyncingStatus.value) return;
try {
const deviceId = route.params.deviceId as string;
if (!deviceId) return;
const targetMode = lightModes.value.find(m => m.id === modeId);
if (!targetMode || !targetMode.instructValue) return;
isUpdatingStatus.value = true;
const res = await api.lightModeSettings({
deviceId,
instructValue: targetMode.instructValue,
deviceImei: deviceDetail.value.deviceImei,
typeName: deviceDetail.value.typeName,
});
if (res.code === 200) {
ElMessage.closeAll();
proxy?.$modal.msgSuccess(res.msg);
setActiveLightMode(modeId);
} else {
proxy?.$modal.msgError(res.msg);
const prevActiveMode = lightModes.value.find(m => m.active);
if (prevActiveMode) {
setActiveLightMode(prevActiveMode.id);
}
}
} catch (error) {
// 异常时恢复状态
const prevActiveMode = lightModes.value.find(m => m.active);
if (prevActiveMode) {
setActiveLightMode(prevActiveMode.id);
}
} finally {
isUpdatingStatus.value = false;
}
};
const isSyncingStatus = ref(false);
// 警示灯接口
const setActiveLightMode = (targetModeId: string) => {
isSyncingStatus.value = true;
lightModes.value.forEach(mode => {
const isActive = mode.id === targetModeId;
mode.active = isActive;
mode.switchStatus = isActive;
});
isSyncingStatus.value = false;
};
const getList = async () => {
try {
const deviceId = route.params.deviceId;
if (!deviceId) return;
const res = await api.deviceDeatil(deviceId as string);
deviceDetail.value = res.data;
// ==========灯光模式逻辑==========
let targetModeId = "redBlueAlternate";
const mainLightMode = String(res.data.strobeMode);
const matchedMode = lightModes.value.find(
mode => mode.instructValue === mainLightMode
);
if (matchedMode) {
targetModeId = matchedMode.id;
}
setActiveLightMode(targetModeId);
// 报警模式
const alarmMode = String(res.data.alarmMode); // 报警模式ID
const voiceStrobeAlarm = String(res.data.voiceStrobeAlarm); // 报警开关状态1开启0关闭
// 先重置所有报警模式的状态
sta_VoiceType.value.forEach(mode => {
mode.active = false;
mode.switchStatusVioice = false;
});
if (voiceStrobeAlarm === '1') {
const matchedVoiceMode = sta_VoiceType.value.find(
mode => mode.id === alarmMode
);
if (matchedVoiceMode) {
matchedVoiceMode.active = true;
matchedVoiceMode.switchStatusVioice = true;
} else {
console.warn('未找到对应的报警模式:', alarmMode);
}
} else {
const closeMode = sta_VoiceType.value.find(mode => mode.id === '-1');
if (closeMode) {
closeMode.active = true;
closeMode.switchStatusVioice = true;
}
}
} catch (error) {
console.error('获取设备详情失败:', error);
}
};
// 灯光亮度
const saveBtnlight = () => {
lightModesLoading.value = true
let data = {
deviceId: route.params.deviceId,
brightness: deviceDetail.value.lightBrightness,
}
api.lightModeSettings(data).then((res) => {
if (res.code === 200) {
lightModesLoading.value = false
proxy?.$modal.msgSuccess(res.msg);
} else {
lightModesLoading.value = false
proxy?.$modal.msgError(res.msg);
}
}).catch((error) => {
lightModesLoading.value = false
});
}
// 爆闪频率
const saveBtnstrobe = () => {
let data = {
deviceId: route.params.deviceId,
frequency: deviceDetail.value.strobeFrequency,
}
api.staticPowerSetting(data).then((res) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess(res.msg);
}
})
}
// 修改音量
const saveBtnVolume = () => {
let data = {
deviceId: route.params.deviceId,
volume: deviceDetail.value.volume,
}
api.settingUpdateVolume(data).then((res) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess(res.msg);
}
})
}
// 解除报警
const showClose = async () => {
try {
await proxy?.$modal.confirm('确定要对该设备解除报警?', '提示');
// 2. 准备请求数据
let data = {
deviceIds: [route.params.deviceId],
typeName: deviceDetail.value.typeName,
deviceImeiList: [deviceDetail.value.deviceImei],
instructValue: '0', //强制报警1解除报警0
}
} catch (error: any) {
}
}
// 强制报警
const forceAlarm = async () => {
try {
await proxy?.$modal.confirm('确定要对该设备开启强制报警?', '提示');
forceAlarmLoading.value = true
// 2. 准备请求数据
let data = {
deviceIds: [route.params.deviceId],
typeName: deviceDetail.value.typeName,
deviceImeiList: [deviceDetail.value.deviceImei],
instructValue: '1', //强制报警1解除报警0
}
} catch (error: any) {
// proxy?.$modal.msgWarning(error.msg)
forceAlarmLoading.value = false;
} finally {
forceAlarmLoading.value = false;
}
}
const lookMap = (row: any) => {
console.log(row, 'rowrowrowrowrworowrowrowrowrowrow');
router.push({
path: '/controlCenter/controlPanel',
query: {
view: 'map',
deviceId: row.deviceId
}
});
}
onMounted(async () => {
await getList();
});
onUnmounted(() => {
});
</script>
<style lang="scss" scoped>
.p-2 {
background: rgba(247, 248, 252, 1);
min-height: 100vh;
box-sizing: border-box;
padding: 15px;
}
.device-page {
.header-bar {
border-radius: 8px;
background: linear-gradient(135deg, #3400e7, #009bff);
color: white;
padding: 20px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
.device-status {
.online {
color: #00ff00;
}
.offline {
color: rgb(224, 52, 52);
}
}
}
.content-wrapper {
.content-row {
margin-bottom: 10px;
}
.content-card {
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 34, 96, 0.1);
background: white;
padding: 0px 20px 50px;
border: 1px solid #ebeef5;
height: 289px;
position: relative;
}
.content-card_gps {
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 34, 96, 0.1);
background: white;
padding: 0px 20px 50px;
border: 1px solid #ebeef5;
height: 78%;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 20px;
color: #303133;
}
.light-mode {
display: flex;
justify-content: space-between;
}
.lacatin_gps {
height: 70px;
border-radius: 4px;
background: rgba(247, 248, 252, 1);
display: inline-block;
width: 400px;
padding: 10px;
}
.mode-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 15px;
border: 1px solid #dcdfe6;
border-radius: 8px;
width: 107px;
cursor: pointer;
transition: all 0.3s ease;
&.active {
border-color: #409eff;
background-color: rgba(64, 158, 255, 0.05);
}
.mode-icon {
width: 48px;
margin-bottom: 10px;
transition: all 0.3s ease;
object-fit: scale-down;
height: 50px;
}
.mode-name {
font-size: 14px;
margin-bottom: 12px;
color: #606266;
}
.el-switch {
--el-switch-on-color: #409eff;
--el-switch-off-color: #dcdfe6;
}
}
.brightness-alarm {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.brightness-control {
display: flex;
align-items: center;
gap: 10px;
border-radius: 39px;
// box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
background: rgba(255, 255, 255, 1);
width: 100%;
// height: 54px;
padding: 10px 20px;
margin-top: 20px;
.brightness-label {
font-size: 14px;
color: #606266;
min-width: 70px;
}
.brightness-value {
font-size: 14px;
color: #409eff;
font-weight: 500;
}
.save-btn {
padding: 6px 20px;
border-radius: 29px;
background: rgba(2, 124, 251, 1);
border: none
}
.inputTFT {
width: 130px;
height: 40px;
}
}
.alarm-btn {
background-color: rgba(224, 52, 52, 1);
border-color: rgba(224, 52, 52, 1);
padding: 8px 20px;
border-radius: 27px;
}
}
.location-info {
.location-item {
display: flex;
align-items: center;
margin-bottom: 15px;
font-size: 14px;
color: #606266;
.location-icon {
margin-right: 8px;
font-weight: bold;
color: #409eff;
}
.view-btn {
margin: 0 8px;
padding: 0;
font-size: 13px;
}
}
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
text-align: right;
.form-item {
display: flex;
align-items: center;
.form-label {
min-width: 35px;
font-size: 14px;
color: #606266;
margin-right: 10px;
}
.el-input {
flex: 1;
}
}
.register-btn {
grid-column: 1 / 3;
justify-self: start;
padding: 6px 20px;
position: absolute;
right: 19px;
bottom: 30px;
border-radius: 29px;
background: rgba(2, 124, 251, 1);
border: none
}
}
.message-content {
// display: flex;
// flex-direction: column;
// gap: 10px;
.el-textarea {
border: 1px solid rgba(29, 111, 255, 1);
border-radius: 4px;
background: rgba(247, 248, 252, 1);
border-radius: 6px;
padding: 10px;
}
.el-textarea__inner {
background: rgba(247, 248, 252, 1);
}
.send-btn {
align-self: flex-end;
padding: 10px 20px;
border-radius: 29px;
background: rgba(2, 124, 251, 1);
border: none;
margin: 20px 0px 30px 0;
}
}
.upload-area {
display: flex;
justify-content: center;
align-items: center;
height: 100px;
border: 1px dashed #dcdfe6;
border-radius: 6px;
cursor: pointer;
}
.video-input {
display: flex;
gap: 10px;
align-items: center;
.el-input {
flex: 1;
}
.save-video-btn {
padding: 6px 12px;
}
}
}
// 响应式调整
@media (max-width: 768px) {
.header-bar {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.form-grid {
grid-template-columns: 1fr;
}
.form-grid .register-btn {
grid-column: 1;
}
.light-mode .mode-group {
gap: 10px;
}
.light-mode .mode-item {
width: 60px;
}
.light-mode .mode-icon {
width: 30px;
height: 30px;
}
}
}
.path-img {
width: 52px;
height: 28px;
}
.staticRwo {
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 34, 96, 0.1);
background: white;
border: 1px solid #ebeef5;
height: auto;
line-height: 36px;
box-sizing: border-box;
text-indent: 15px;
color: #ff0000;
font-weight: bold;
font-size: 17px;
margin-bottom: 5px;
}
.displayNone {
display: none !important;
}
</style>