控制中心,设备控制页面功能开发
@ -5,7 +5,7 @@ VITE_APP_TITLE = 云平台管理系统
|
||||
VITE_APP_ENV = 'development'
|
||||
|
||||
# 开发环境
|
||||
VITE_APP_BASE_API = 'http://192.168.2.23:8000'
|
||||
VITE_APP_BASE_API = 'http://192.168.2.34:8000'
|
||||
|
||||
# 应用访问路径 例如使用前缀 /admin/
|
||||
VITE_APP_CONTEXT_PATH = '/'
|
||||
|
@ -6,7 +6,7 @@
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>星汉研创科技</title>
|
||||
<title>物联网管理平台</title>
|
||||
<!--[if lt IE 11
|
||||
]><script>
|
||||
window.location.href = '/html/ie.html';
|
||||
|
24
src/api/controlCenter/controlPanel/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { deviceQuery, deviceVO } from './types';
|
||||
// 设备分组
|
||||
export const devicegroupList = (params) => {
|
||||
return request({
|
||||
url: '/api/device/group/list',
|
||||
method: 'get',
|
||||
params: params
|
||||
});
|
||||
};
|
||||
// 设备列表
|
||||
export const deviceControlCenterList = (params) => {
|
||||
return request({
|
||||
url: '/api/device/controlCenter/list',
|
||||
method: 'get',
|
||||
params: params
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
devicegroupList,
|
||||
deviceControlCenterList
|
||||
};
|
19
src/api/controlCenter/controlPanel/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface deviceQuery {
|
||||
groupId: string;
|
||||
pageNum: number;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
deviceStatus: string;
|
||||
deviceMac:string;
|
||||
deviceImei:string;
|
||||
currentOwnerId:string;
|
||||
communicationMode:string;
|
||||
queryParams:string;
|
||||
pageSize:Number;
|
||||
|
||||
}
|
||||
export interface deviceVO {
|
||||
user: UserVO;
|
||||
roles: string[];
|
||||
permissions: string[];
|
||||
}
|
@ -6,7 +6,7 @@ import { AxiosPromise } from 'axios';
|
||||
*/
|
||||
export const userList = (params: any): AxiosPromise => {
|
||||
return request({
|
||||
url: '/app/user/list',
|
||||
url: '/WebApp/user/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
BIN
src/assets/images/flood-light.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/flood-light_HL.png
Normal file
After Width: | Height: | Size: 304 B |
BIN
src/assets/images/laser-light.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/images/laser-light_HL.png
Normal file
After Width: | Height: | Size: 289 B |
BIN
src/assets/images/strobe-light.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/strobe-light_HL.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
src/assets/images/strong-light.png
Normal file
After Width: | Height: | Size: 294 B |
BIN
src/assets/images/strong-light_HL.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/weak-light.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/weak-light_HL.png
Normal file
After Width: | Height: | Size: 235 B |
@ -88,6 +88,12 @@ div:focus {
|
||||
.pl-5 {
|
||||
padding-left: 5px;
|
||||
}
|
||||
.p-2{
|
||||
background: rgba(247, 248, 252, 1);
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
|
490
src/views/controlCenter/6170/index.vue
Normal file
@ -0,0 +1,490 @@
|
||||
<template>
|
||||
<div class="device-page p-2">
|
||||
<!-- 头部信息栏 -->
|
||||
<div class="header-bar">
|
||||
<div>设备名称:6170零零一</div>
|
||||
<div>设备型号:BJQ6170</div>
|
||||
<div class="device-status">设备状态:<span class="online">在线</span></div>
|
||||
<div>电量:80%</div>
|
||||
<div>续航:1小时55分钟</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体内容区域 -->
|
||||
<div class="content-wrapper">
|
||||
<!-- 第一行:灯光模式 + 灯光亮度、强制报警、位置信息 -->
|
||||
<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="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="brightness-alarm">
|
||||
<div class="brightness-control">
|
||||
<span class="brightness-label">灯光亮度</span>
|
||||
<el-input class="inputTFT" v-model="brightness" :min="0" :max="100" :step="1"
|
||||
size="small" />
|
||||
<span class="brightness-value">%</span>
|
||||
<el-button type="primary" class="save-btn">保存</el-button>
|
||||
</div>
|
||||
<el-button type="danger" class="alarm-btn">强制报警</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>经纬度 114°7'E 30°28'N</span>
|
||||
</div>
|
||||
<div class="location-item">
|
||||
|
||||
<div>地址 <span class="lacatin_gps">ksjkjwekrnjewrnjewrnjwerjweb</span></div>
|
||||
<el-button link type="primary" class="view-btn">查看</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="form-grid">
|
||||
<div class="form-item">
|
||||
<span class="form-label">单位:</span>
|
||||
<el-input placeholder="请输入设备名称" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<span class="form-label">职位:</span>
|
||||
<el-input placeholder="请输入设备名称" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<span class="form-label">姓名</span>
|
||||
<el-input value="王平安" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<span class="form-label">ID:</span>
|
||||
<el-input placeholder="请输入设备名称" />
|
||||
</div>
|
||||
<el-button type="primary" class="register-btn">登记</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :lg="8" :xs="24">
|
||||
<div class="content-card">
|
||||
<h4 class="section-title">发送信息</h4>
|
||||
<div class="message-content">
|
||||
<el-input type="textarea" class="textareaTFT" :rows="4" value="现场危险,停止救援!紧急撤离至安全区域!"
|
||||
resize="none" />
|
||||
<el-button type="primary" class="send-btn">发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="DeviceControl" lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const brightness = ref(50);
|
||||
// 定义灯光模式的类型接口
|
||||
interface LightMode {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
activeIcon: string;
|
||||
active: boolean;
|
||||
switchStatus: boolean;
|
||||
}
|
||||
// 导入图片资源(确保路径正确)
|
||||
import strongLightDefault from '@/assets/images/strong-light.png';
|
||||
import strongLightActive from '@/assets/images/strong-light_HL.png';
|
||||
import weakLightDefault from '@/assets/images/weak-light.png';
|
||||
import weakLightActive from '@/assets/images/weak-light_HL.png';
|
||||
import strobeLightDefault from '@/assets/images/strobe-light.png';
|
||||
import strobeLightActive from '@/assets/images/strobe-light_HL.png';
|
||||
import floodLightDefault from '@/assets/images/flood-light.png';
|
||||
import floodLightActive from '@/assets/images/flood-light_HL.png';
|
||||
import laserLightDefault from '@/assets/images/laser-light.png';
|
||||
import laserLightActive from '@/assets/images/laser-light_HL.png';
|
||||
|
||||
// 灯光模式数据
|
||||
// 灯光模式数据(引用导入的图片)
|
||||
const lightModes = ref<LightMode[]>([
|
||||
{
|
||||
id: 'strong',
|
||||
name: '强光',
|
||||
icon: strongLightDefault, // 直接使用导入的变量
|
||||
activeIcon: strongLightActive,
|
||||
active: true,
|
||||
switchStatus: true
|
||||
},
|
||||
{
|
||||
id: 'weak',
|
||||
name: '弱光',
|
||||
icon: weakLightDefault,
|
||||
activeIcon: weakLightActive,
|
||||
active: false,
|
||||
switchStatus: false
|
||||
},
|
||||
{
|
||||
id: 'strobe',
|
||||
name: '爆闪',
|
||||
icon: strobeLightDefault,
|
||||
activeIcon: strobeLightActive,
|
||||
active: false,
|
||||
switchStatus: false
|
||||
},
|
||||
{
|
||||
id: 'flood',
|
||||
name: '泛光',
|
||||
icon: floodLightDefault,
|
||||
activeIcon: floodLightActive,
|
||||
active: false,
|
||||
switchStatus: false
|
||||
},
|
||||
{
|
||||
id: 'laser',
|
||||
name: '激光',
|
||||
icon: laserLightDefault,
|
||||
activeIcon: laserLightActive,
|
||||
active: false,
|
||||
switchStatus: false
|
||||
}
|
||||
]);
|
||||
// 当前选中的模式
|
||||
const currentMode = computed(() => {
|
||||
return lightModes.value.find(mode => mode.active);
|
||||
});
|
||||
// 处理模式点击事件
|
||||
const handleModeClick = (modeId: string) => {
|
||||
// 重置所有模式状态
|
||||
lightModes.value.forEach(mode => {
|
||||
const isTargetMode = mode.id === modeId;
|
||||
mode.active = isTargetMode;
|
||||
mode.switchStatus = isTargetMode;
|
||||
});
|
||||
|
||||
// 可以在这里添加模式切换的业务逻辑
|
||||
console.log(`切换到${lightModes.value.find(m => m.id === modeId)?.name}模式`);
|
||||
};
|
||||
|
||||
// 监听开关状态变化
|
||||
lightModes.value.forEach(mode => {
|
||||
watch(() => mode.switchStatus, (newVal) => {
|
||||
if (newVal) {
|
||||
// 如果打开当前开关,关闭其他所有开关
|
||||
handleModeClick(mode.id);
|
||||
} else {
|
||||
// 如果关闭当前开关
|
||||
mode.active = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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: 100%;
|
||||
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: 125px;
|
||||
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;
|
||||
}
|
||||
|
||||
.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: 70%;
|
||||
height: 54px;
|
||||
padding: 10px 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
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
218
src/views/controlCenter/controlPanel/components/map.vue
Normal file
@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="amap_page">
|
||||
<div ref="mapRef" class="amap-container"></div>
|
||||
<div class="content_top">
|
||||
<div class="content_layout">
|
||||
<!-- 左侧设备列表(带复选框) -->
|
||||
<div class="device_list">
|
||||
<!-- 全选复选框 -->
|
||||
<div class="list_header">
|
||||
<el-checkbox v-model="checkAll" @change="handleCheckAllChange" label="全选"></el-checkbox>
|
||||
</div>
|
||||
|
||||
<!-- 设备项(带复选框) -->
|
||||
<div class="device_item" v-for="device in deviceList" :key="device.id">
|
||||
<!-- 复选框 -->
|
||||
<el-checkbox :value="device.id" v-model="checkedDeviceIds" class="device_checkbox"></el-checkbox>
|
||||
|
||||
<!-- 设备信息 -->
|
||||
<div class="device_page">
|
||||
<div class="device_info">
|
||||
<div class="device_name">{{ device.name }}</div>
|
||||
<div class="device_model">{{ device.model }}</div>
|
||||
<div class="device_status" :class="{ online: device.status === '在线', offline: device.status === '离线' }">
|
||||
{{ device.status }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<el-button class="control_btn" @click="handleControl(device)">控制</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
// 声明高德地图全局类型
|
||||
declare var AMap: any;
|
||||
|
||||
// 高德Key与安全密钥
|
||||
const AMAP_KEY = '90bc158992feb8ccd0145e168cab1307';
|
||||
const AMAP_SECURITY_CODE = '5ed9004cb461cd463580b02a775c8d91';
|
||||
|
||||
// 地图实例
|
||||
const mapRef = ref<HTMLDivElement | null>(null);
|
||||
let mapInstance: any = null;
|
||||
|
||||
// 模拟设备数据
|
||||
const deviceList = ref([
|
||||
{ id: 1, name: '6170一号', model: 'BJQ6170', status: '在线', lng: 114.4074, lat: 30.5928 },
|
||||
{ id: 2, name: '6170二号', model: 'BJQ6170', status: '在线', lng: 114.4174, lat: 30.5928 },
|
||||
{ id: 3, name: '6170三号', model: 'BJQ6170', status: '离线', lng: 114.4074, lat: 30.6028 },
|
||||
{ id: 4, name: '6170四号', model: 'BJQ6170', status: '在线', lng: 114.4174, lat: 30.6028 },
|
||||
]);
|
||||
|
||||
// 复选框状态管理
|
||||
const checkedDeviceIds = ref<number[]>([]); // 存储选中的设备ID
|
||||
const checkAll = ref(false); // 全选状态
|
||||
|
||||
// 全选/取消全选
|
||||
const handleCheckAllChange = (val: boolean) => {
|
||||
checkedDeviceIds.value = val
|
||||
? deviceList.value.map(device => device.id) // 全选:选中所有设备ID
|
||||
: []; // 取消全选:清空
|
||||
};
|
||||
|
||||
// 监听单个复选框变化,更新全选状态
|
||||
watch(checkedDeviceIds, (newVal) => {
|
||||
checkAll.value = newVal.length === deviceList.value.length && deviceList.value.length > 0;
|
||||
});
|
||||
|
||||
// 设备控制事件
|
||||
const handleControl = (device: any) => {
|
||||
console.log('控制设备:', device);
|
||||
const deviceId = device.id;
|
||||
router.push('/controlCenter/6170/' + deviceId);
|
||||
};
|
||||
|
||||
// 地图初始化(保持不变)
|
||||
const initMap = () => {
|
||||
if (!window.AMap || !mapRef.value) return;
|
||||
|
||||
mapInstance = new AMap.Map(mapRef.value, {
|
||||
center: [114.4074, 30.5928],
|
||||
zoom: 15,
|
||||
resizeEnable: true
|
||||
});
|
||||
|
||||
// 设备标记点
|
||||
deviceList.value.forEach(device => {
|
||||
new AMap.Marker({
|
||||
position: [device.lng, device.lat],
|
||||
title: device.name,
|
||||
map: mapInstance
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window._AMapSecurityConfig = { securityJsCode: AMAP_SECURITY_CODE };
|
||||
if (window.AMap) {
|
||||
initMap();
|
||||
} else {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
|
||||
script.onload = initMap;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (mapInstance) mapInstance.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 地图容器 */
|
||||
.amap_page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.amap-container {
|
||||
height: 640px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content_top {
|
||||
width: 210px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
height: 620px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 10px;
|
||||
left: 10px
|
||||
}
|
||||
|
||||
/* 全选行样式 */
|
||||
.list_header {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 设备项样式 */
|
||||
.device_item {
|
||||
padding: 0px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
/* 复选框与内容间距 */
|
||||
cursor: default;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
/* 复选框样式 */
|
||||
.device_checkbox {
|
||||
flex-shrink: 0;
|
||||
/* 复选框不压缩 */
|
||||
}
|
||||
|
||||
/* 设备信息区域 */
|
||||
.device_page{
|
||||
background-color: rgba(247, 248, 252, 1);
|
||||
margin-bottom: 5px;
|
||||
width: 84%;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device_name {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.device_model {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.device_status {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.online {
|
||||
color: #00b42a;
|
||||
}
|
||||
|
||||
.offline {
|
||||
color: #f53f3f;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
.control_btn {
|
||||
font-size: 12px;
|
||||
padding: 0px 15px;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 10px;
|
||||
background: rgba(2, 124, 251, 0.06);
|
||||
color: rgba(2, 124, 251, 1);
|
||||
border:none;
|
||||
}
|
||||
</style>
|
297
src/views/controlCenter/controlPanel/index.vue
Normal file
@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<el-row :gutter="20">
|
||||
<!-- 部门树 -->
|
||||
<el-col :lg="4" :xs="24" style="" class="main-tree">
|
||||
<el-input v-model="deptName" placeholder="输入分组名称" prefix-icon="Search" clearable />
|
||||
<el-tree ref="deptTreeRef" class="mt-2" node-key="id" :data="deptOptions"
|
||||
:props="{ label: 'groupName', children: 'children' }" :expand-on-click-node="false"
|
||||
:filter-node-method="filterNode" highlight-current default-expand-all @node-click="handleNodeClick"></el-tree>
|
||||
</el-col>
|
||||
<el-col :lg="20" :xs="24">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
|
||||
:leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card>
|
||||
<!-- =========搜索按钮操作======= -->
|
||||
<div class="btn_search">
|
||||
<el-button :type="isListView ? 'primary' : ''" @click="switchView('list')">
|
||||
{{ isListView ? '列表显示' : '列表显示' }}
|
||||
</el-button>
|
||||
<el-button :type="!isListView ? 'primary' : ''" @click="switchView('map')">
|
||||
{{ !isListView ? '地图显示' : '地图显示' }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain>发送消息</el-button>
|
||||
<el-button type="primary" plain>电子围栏</el-button>
|
||||
<el-button type="danger" plain>强制报警</el-button>
|
||||
<el-button type="primary" plain>高级筛选</el-button>
|
||||
</div>
|
||||
<el-collapse accordion>
|
||||
<el-collapse-item title="高级筛选">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="queryFormRef">
|
||||
<el-form-item label="设备类型" prop="deviceId">
|
||||
<el-select v-model="queryParams.deviceId" placeholder="全部" clearable>
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称" prop="deviceName">
|
||||
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备状态" prop="deviceStatus">
|
||||
<el-select v-model="queryParams.deviceStatus" placeholder="设备状态" clearable>
|
||||
<el-option label="在线" value="1"></el-option>
|
||||
<el-option label="离线" value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备MAC" prop="deviceMac">
|
||||
<el-input v-model="queryParams.deviceMac" placeholder="请输入设备MAC" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备IMEI" prop="deviceImei">
|
||||
<el-input v-model="queryParams.deviceImei" placeholder="请输入设备IMEI" clearable
|
||||
@keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="使用人员" prop="currentOwnerId">
|
||||
<el-input v-model="queryParams.currentOwnerId" placeholder="请输入使用人员姓名" clearable
|
||||
@keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="通信方式" prop="communicationMode">
|
||||
<el-select v-model="queryParams.communicationMode" placeholder="请选择通信方式" clearable>
|
||||
<el-option label="4G" value="0"></el-option>
|
||||
<el-option label="蓝牙" value="1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
<el-card class="Maplist">
|
||||
<div v-if="isListView" key="list">
|
||||
<el-table v-loading="loading" border :data="deviceList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="设备名称" align="center" prop="deviceName" />
|
||||
<el-table-column label="设备图片" align="center" prop="devicePic">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" trigger="click">
|
||||
<template #reference>
|
||||
<img :src="scope.row.devicePic"
|
||||
style="width: 40px; height: 40px; cursor: pointer; object-fit: contain"
|
||||
class="hover:opacity-80 transition-opacity" />
|
||||
</template>
|
||||
<img :src="scope.row.devicePic" style="max-width: 600px; max-height: 600px; object-fit: contain" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="设备类型" align="center" prop="typeName" />
|
||||
<el-table-column label="使用人员" align="center" prop="personnelBy" />
|
||||
<el-table-column label="电量" align="center" prop="battery" />
|
||||
<el-table-column label="设备状态" align="center" prop="onlineStatus">
|
||||
<template #default="scope">
|
||||
<div class="normal green" v-if="scope.row.onlineStatus==1">在线</div>
|
||||
<div class="normal red" v-if="scope.row.onlineStatus==0">离线</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleControl(scope.row)">控制面板</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
|
||||
:total="total" @pagination="getList" />
|
||||
</div>
|
||||
<div v-else key="map">
|
||||
<Amap />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="User" lang="ts">
|
||||
import api from '@/api/controlCenter/controlPanel/index'
|
||||
import { deviceQuery, deviceVO } from '@/api/controlCenter/controlPanel/types';
|
||||
import Amap from "./components/map.vue";
|
||||
import { optionselect } from '@/api/system/post';
|
||||
import { UserVO } from '@/api/system/user/types';
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex'));
|
||||
const deviceList = ref<deviceVO[]>();
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<number | string>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const deptName = ref();
|
||||
const deptOptions = ref([])
|
||||
const sys_communication = ref([])
|
||||
const deptTreeRef = ref<ElTreeInstance>();
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const userFormRef = ref<ElFormInstance>();
|
||||
const isListView = ref(true);
|
||||
|
||||
const initData: PageData<'', deviceQuery> = {
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deviceId: '',
|
||||
deviceName: '',
|
||||
deviceStatus: '',
|
||||
deviceMac: '',
|
||||
deviceImei: '',
|
||||
currentOwnerId: '',
|
||||
communicationMode: '',
|
||||
queryParams: '',
|
||||
groupId: ''
|
||||
},
|
||||
rules: undefined,
|
||||
form: ''
|
||||
};
|
||||
const data = reactive<PageData<'', deviceQuery>>(initData);
|
||||
|
||||
const { queryParams, form, } = toRefs<PageData<'', deviceQuery>>(data);
|
||||
const switchView = (view) => {
|
||||
isListView.value = (view === 'list');
|
||||
};
|
||||
/** 通过条件过滤节点 */
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
};
|
||||
/** 根据名称筛选部门树 */
|
||||
watchEffect(
|
||||
() => {
|
||||
deptTreeRef.value?.filter(deptName.value);
|
||||
},
|
||||
{
|
||||
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
|
||||
}
|
||||
);
|
||||
|
||||
/** 查询用户列表 */
|
||||
const getList = async () => {
|
||||
loading.value = false;
|
||||
const res = await api.deviceControlCenterList(queryParams.value);
|
||||
loading.value = false;
|
||||
deviceList.value = res.rows;
|
||||
total.value = res.total;
|
||||
};
|
||||
|
||||
/** 查询部门下拉树结构 */
|
||||
const getDeptTree = async () => {
|
||||
const res = await api.devicegroupList('');
|
||||
|
||||
deptOptions.value = res.data;
|
||||
//enabledDeptOptions.value = filterDisabledDept(res.data);
|
||||
};
|
||||
|
||||
/** 过滤禁用的部门 */
|
||||
const filterDisabledDept = (deptList: any[]) => {
|
||||
return deptList.filter((dept) => {
|
||||
if (dept.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (dept.children && dept.children.length) {
|
||||
dept.children = filterDisabledDept(dept.children);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
/** 节点单击事件 */
|
||||
const handleNodeClick = (data: any) => {
|
||||
queryParams.value.groupId = data.id;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value.pageNum = 1;
|
||||
queryParams.value.groupId = undefined;
|
||||
deptTreeRef.value?.setCurrentKey(undefined);
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
|
||||
/** 设备控制跳转 */
|
||||
const handleControl = (row: any) => {
|
||||
const deviceId = row.id;
|
||||
router.push('/controlCenter/6170/' + deviceId);
|
||||
};
|
||||
|
||||
/** 选择条数 */
|
||||
const handleSelectionChange = (selection: UserVO[]) => {
|
||||
ids.value = selection.map((item:any) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
/** 重置操作表单 */
|
||||
const reset = () => {
|
||||
userFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDeptTree(); // 初始化部门数据
|
||||
getList(); // 初始化列表数据
|
||||
});
|
||||
|
||||
// async function handleDeptChange(value: number | string) {
|
||||
// const response = await optionselect(value);
|
||||
// postOptions.value = response.data;
|
||||
// form.value.postIds = [];
|
||||
// }
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.main-tree {
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
width: 212px;
|
||||
border: none;
|
||||
padding-top: 10px;
|
||||
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border: none
|
||||
}
|
||||
|
||||
.btn_search {
|
||||
padding: 0px 15px 15px 0px;
|
||||
// border-bottom: 1px solid rgba(235, 238, 248, 1);
|
||||
}
|
||||
|
||||
.queryFormRef {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.normal {}
|
||||
|
||||
.green {
|
||||
color: rgba(0, 165, 82, 1);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: rgba(224, 52, 52, 1);
|
||||
}
|
||||
|
||||
.Maplist {
|
||||
height: 680px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|