报警列表卡片,模式页面布局修改,估计播放,优化体验
This commit is contained in:
@ -6,6 +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" />
|
||||
<script src="https://webapi.amap.com/maps?v=2.0&key=90bc158992feb8ccd0145e168cab1307"></script>
|
||||
<title>物联网管理平台</title>
|
||||
<!--[if lt IE 11
|
||||
]><script>
|
||||
|
@ -9,7 +9,8 @@ export interface deviceQuery {
|
||||
personnelBy: string;
|
||||
communicationMode: string;
|
||||
pageSize: Number;
|
||||
deviceType: string
|
||||
deviceType: string;
|
||||
content:string
|
||||
|
||||
}
|
||||
export interface deviceVO {
|
||||
|
@ -15,9 +15,8 @@ export const deviceInstructionRecord = (params) => {
|
||||
params: params
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 导出
|
||||
export default {
|
||||
devicegroupList,
|
||||
deviceInstructionRecord,
|
||||
deviceInstructionRecord
|
||||
};
|
@ -8,6 +8,7 @@ export interface deviceQuery {
|
||||
deviceType: string;
|
||||
startTime: string; // 接口要求的开始时间字段
|
||||
endTime: string // 接口要求的结束时间字段
|
||||
content:string
|
||||
}
|
||||
export interface deviceVO {
|
||||
id: number; // 设备ID
|
||||
|
23
src/api/controlCenter/historyjectory/index.ts
Normal file
23
src/api/controlCenter/historyjectory/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import request from '@/utils/request';
|
||||
// 轨迹记录
|
||||
export const devicelocationHistory = (params) => {
|
||||
return request({
|
||||
url: '/api/device/locationHistory',
|
||||
method: 'get',
|
||||
params: params
|
||||
});
|
||||
};
|
||||
// 轨迹播放
|
||||
export const getLocationHistoryDetail = (params) => {
|
||||
return request({
|
||||
url: '/api/device/getLocationHistoryDetail',
|
||||
method: 'get',
|
||||
params: params
|
||||
});
|
||||
};
|
||||
|
||||
// 导出
|
||||
export default {
|
||||
devicelocationHistory,
|
||||
getLocationHistoryDetail
|
||||
};
|
22
src/api/controlCenter/historyjectory/types.ts
Normal file
22
src/api/controlCenter/historyjectory/types.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export interface deviceQuery {
|
||||
groupId: string;
|
||||
pageNum: number;
|
||||
deviceName: string;
|
||||
deviceMac: string;
|
||||
deviceImei: string;
|
||||
pageSize: Number;
|
||||
deviceType: string;
|
||||
startTime: string; // 接口要求的开始时间字段
|
||||
endTime: string // 接口要求的结束时间字段
|
||||
content: string
|
||||
}
|
||||
export interface deviceVO {
|
||||
id: number; // 设备ID
|
||||
deviceName: string; // 设备名称(对应子组件的device.name)
|
||||
typeName: string; // 设备类型/型号(对应子组件的device.model)
|
||||
onlineStatus: 0 | 1; // 设备状态(0=失效/离线,1=正常/在线,对应子组件的device.status)
|
||||
lng?: number; // 经度(地图打点用)
|
||||
lat?: number; // 纬度(地图打点用)
|
||||
// 其他字段...
|
||||
}
|
||||
|
@ -1,63 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { EquipmentAlarmRecordVO, EquipmentAlarmRecordForm, EquipmentAlarmRecordQuery } from '@/api/equipmentAlarmRecord/types';
|
||||
import { AlarmVO, AlarmForm, AlarmQuery } from '@/api/equipment/alarm/types';
|
||||
|
||||
/**
|
||||
* 查询设备报警记录列表
|
||||
* 查询设备告警列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listEquipmentAlarmRecord = (query?: EquipmentAlarmRecordQuery): AxiosPromise<EquipmentAlarmRecordVO[]> => {
|
||||
export const listAlarm = (query?: AlarmQuery): AxiosPromise<AlarmVO[]> => {
|
||||
return request({
|
||||
url: '/equipment/equipmentAlarmRecord/list',
|
||||
url: 'api/equipment/alarm/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询设备报警记录详细
|
||||
* 查询设备告警详细
|
||||
* @param id
|
||||
*/
|
||||
export const getEquipmentAlarmRecord = (id: string | number): AxiosPromise<EquipmentAlarmRecordVO> => {
|
||||
export const getAlarm = (id: string | number): AxiosPromise<AlarmVO> => {
|
||||
return request({
|
||||
url: '/equipment/equipmentAlarmRecord/' + id,
|
||||
url: '/equipment/alarm/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增设备报警记录
|
||||
* 新增设备告警
|
||||
* @param data
|
||||
*/
|
||||
export const addEquipmentAlarmRecord = (data: EquipmentAlarmRecordForm) => {
|
||||
export const addAlarm = (data: AlarmForm) => {
|
||||
return request({
|
||||
url: '/equipment/equipmentAlarmRecord',
|
||||
url: '/equipment/alarm',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改设备报警记录
|
||||
* 修改设备告警
|
||||
* @param data
|
||||
*/
|
||||
export const updateEquipmentAlarmRecord = (data: EquipmentAlarmRecordForm) => {
|
||||
export const updateAlarm = (data: AlarmForm) => {
|
||||
return request({
|
||||
url: '/equipment/equipmentAlarmRecord',
|
||||
url: '/equipment/alarm',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除设备报警记录
|
||||
* 删除设备告警
|
||||
* @param id
|
||||
*/
|
||||
export const delEquipmentAlarmRecord = (id: string | number | Array<string | number>) => {
|
||||
export const delAlarm = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/equipment/equipmentAlarmRecord/' + id,
|
||||
url: '/equipment/alarm/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
@ -1,82 +1,39 @@
|
||||
export interface EquipmentAlarmRecordVO {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 报警设备id
|
||||
*/
|
||||
deviceId: string | number;
|
||||
|
||||
/**
|
||||
* 设备IMEI
|
||||
*/
|
||||
deviceImei: string;
|
||||
|
||||
/**
|
||||
* 设备MAC
|
||||
*/
|
||||
deviceMac: string;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
export interface AlarmVO {
|
||||
startTime: string;
|
||||
treatmentState?: string | number
|
||||
deviceAction?: string | number;
|
||||
deviceName: string;
|
||||
|
||||
/**
|
||||
* 所属代理(客户)
|
||||
*/
|
||||
agent: number;
|
||||
|
||||
/**
|
||||
* 绑定app用户
|
||||
*/
|
||||
bindApp: number;
|
||||
|
||||
/**
|
||||
* 报警类型
|
||||
*/
|
||||
alarmType: number;
|
||||
|
||||
/**
|
||||
* 报警编码
|
||||
|
||||
*/
|
||||
alarmCode: string;
|
||||
|
||||
/**
|
||||
* 报警描述
|
||||
*/
|
||||
alarmDescription: string;
|
||||
|
||||
/**
|
||||
* 报警时间
|
||||
*/
|
||||
alarmTime: string;
|
||||
|
||||
dataSource: string;
|
||||
content: string;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
location: string;
|
||||
queryTime1?: string | number;
|
||||
queryTime2?: string | number;
|
||||
durationTime: string;
|
||||
deviceTypeName: string;
|
||||
deviceImei: string;
|
||||
deviceMac: string;
|
||||
devicePic:string;
|
||||
finishTime: string;
|
||||
|
||||
}
|
||||
|
||||
export interface EquipmentAlarmRecordForm extends BaseEntity {
|
||||
export interface AlarmForm extends BaseEntity {
|
||||
/**
|
||||
*
|
||||
* ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 报警设备id
|
||||
* 设备ID
|
||||
*/
|
||||
deviceId?: string | number;
|
||||
|
||||
/**
|
||||
* 设备IMEI
|
||||
* 报警事项
|
||||
*/
|
||||
deviceImei?: string;
|
||||
|
||||
/**
|
||||
* 设备MAC
|
||||
*/
|
||||
deviceMac?: string;
|
||||
deviceAction?: string;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
@ -84,54 +41,69 @@ export interface EquipmentAlarmRecordForm extends BaseEntity {
|
||||
deviceName?: string;
|
||||
|
||||
/**
|
||||
* 所属代理(客户)
|
||||
* 数据来源
|
||||
*/
|
||||
agent?: number;
|
||||
dataSource?: string;
|
||||
|
||||
/**
|
||||
* 绑定app用户
|
||||
* 内容
|
||||
*/
|
||||
bindApp?: number;
|
||||
content?: string;
|
||||
|
||||
/**
|
||||
* 报警类型
|
||||
* 设备类型
|
||||
*/
|
||||
alarmType?: number;
|
||||
deviceType?: number;
|
||||
|
||||
/**
|
||||
* 报警编码
|
||||
* 经度
|
||||
|
||||
*/
|
||||
alarmCode?: string;
|
||||
longitude?: number;
|
||||
|
||||
/**
|
||||
* 报警描述
|
||||
* 纬度
|
||||
*/
|
||||
alarmDescription?: string;
|
||||
latitude?: number;
|
||||
|
||||
/**
|
||||
* 报警时间
|
||||
* 报警位置
|
||||
*/
|
||||
alarmTime?: string;
|
||||
location?: string;
|
||||
|
||||
/**
|
||||
* 报警开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 报警结束时间
|
||||
*/
|
||||
finishTime?: string;
|
||||
|
||||
/**
|
||||
* 报警持续时间
|
||||
*/
|
||||
durationTime?: string;
|
||||
|
||||
/**
|
||||
* 0已处理,1未处理
|
||||
*/
|
||||
treatmentState?: number;
|
||||
|
||||
}
|
||||
|
||||
export interface EquipmentAlarmRecordQuery extends PageQuery {
|
||||
export interface AlarmQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 报警设备id
|
||||
* 设备ID
|
||||
*/
|
||||
deviceId?: string | number;
|
||||
|
||||
/**
|
||||
* 设备IMEI
|
||||
* 报警事项
|
||||
*/
|
||||
deviceImei?: string;
|
||||
|
||||
/**
|
||||
* 设备MAC
|
||||
*/
|
||||
deviceMac?: string;
|
||||
deviceAction?: string;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
@ -139,40 +111,60 @@ export interface EquipmentAlarmRecordQuery extends PageQuery {
|
||||
deviceName?: string;
|
||||
|
||||
/**
|
||||
* 所属代理(客户)
|
||||
* 数据来源
|
||||
*/
|
||||
agent?: number;
|
||||
dataSource?: string;
|
||||
|
||||
/**
|
||||
* 绑定app用户
|
||||
* 内容
|
||||
*/
|
||||
bindApp?: number;
|
||||
content?: string;
|
||||
|
||||
/**
|
||||
* 报警类型
|
||||
* 设备类型
|
||||
*/
|
||||
alarmType?: number;
|
||||
deviceType?: number;
|
||||
|
||||
/**
|
||||
* 报警编码
|
||||
* 经度
|
||||
|
||||
*/
|
||||
alarmCode?: string;
|
||||
longitude?: number;
|
||||
|
||||
/**
|
||||
* 报警描述
|
||||
* 纬度
|
||||
*/
|
||||
alarmDescription?: string;
|
||||
latitude?: number;
|
||||
|
||||
/**
|
||||
* 报警时间
|
||||
* 报警位置
|
||||
*/
|
||||
alarmTime?: string;
|
||||
location?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
/**
|
||||
* 报警开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 报警结束时间
|
||||
*/
|
||||
finishTime?: string;
|
||||
|
||||
/**
|
||||
* 报警持续时间
|
||||
*/
|
||||
durationTime?: string;
|
||||
|
||||
/**
|
||||
* 0已处理,1未处理
|
||||
*/
|
||||
treatmentState?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
BIN
src/assets/images/nodata.png
Normal file
BIN
src/assets/images/nodata.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -12,7 +12,25 @@ export const generateShortId = (): string => {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
|
||||
};
|
||||
|
||||
export default generateShortId;
|
||||
|
||||
// 时间转化
|
||||
/**
|
||||
* 时间戳转 24小时制 HH:mm
|
||||
* @param timestamp 秒/毫秒级时间戳
|
||||
*/
|
||||
export const formatTimestampToHM = (timestamp: number) => {
|
||||
if (!timestamp) return '';
|
||||
// 秒级时间戳 → 毫秒级
|
||||
const msTimestamp = timestamp.toString().length === 10
|
||||
? timestamp * 1000
|
||||
: timestamp;
|
||||
const date = new Date(msTimestamp);
|
||||
// 小时、分钟补零
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
|
||||
// 类型定义
|
||||
export interface DeviceStatusOptions {
|
||||
@ -87,25 +105,4 @@ export async function getDeviceStatus(
|
||||
|
||||
return checkStatus();
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
/*
|
||||
import { getDeviceStatus } from '@/utils/deviceUtils';
|
||||
import { apiGetDeviceStatus } from '@/api/device';
|
||||
|
||||
try {
|
||||
const result = await getDeviceStatus(
|
||||
{
|
||||
functionMode: 1,
|
||||
batchId: '12345',
|
||||
typeName: 'sensor',
|
||||
deviceImei: '1234567890',
|
||||
interval: 1000
|
||||
},
|
||||
apiGetDeviceStatus
|
||||
);
|
||||
console.log('设备状态:', result.data);
|
||||
} catch (error) {
|
||||
console.error('获取设备状态失败:', error);
|
||||
}
|
||||
*/
|
||||
export default { generateShortId, formatTimestampToHM }
|
@ -246,7 +246,6 @@ const handleModeClick = async (modeId: string) => {
|
||||
if (res.code === 200) {
|
||||
proxy?.$modal.msgSuccess(res.msg);
|
||||
setActiveLightMode(modeId);
|
||||
await getList();
|
||||
} else {
|
||||
proxy?.$modal.msgError(res.msg);
|
||||
const prevActiveMode = lightModes.value.find(m => m.active);
|
||||
@ -325,7 +324,6 @@ const handleLaserClick = async () => {
|
||||
proxy?.$modal.msgSuccess(res.msg);
|
||||
laserMode.value.active = targetStatus;
|
||||
laserMode.value.switchStatus = targetStatus;
|
||||
getList();
|
||||
} else {
|
||||
proxy?.$modal.msgError(res.msg);
|
||||
// 恢复之前的状态
|
||||
@ -370,7 +368,6 @@ const registerPostInit = () => {
|
||||
if (res.code === 200) {
|
||||
fullscreenLoading.value = false
|
||||
proxy?.$modal.msgSuccess(res.msg);
|
||||
getList();
|
||||
} else {
|
||||
fullscreenLoading.value = false
|
||||
proxy?.$modal.msgError(res.msg);
|
||||
@ -389,7 +386,7 @@ const saveBtn = () => {
|
||||
if (res.code === 200) {
|
||||
lightModesLoading.value = false
|
||||
proxy?.$modal.msgSuccess(res.msg);
|
||||
getList();
|
||||
|
||||
} else {
|
||||
lightModesLoading.value = false
|
||||
proxy?.$modal.msgError(res.msg);
|
||||
@ -417,11 +414,10 @@ const forceAlarm = async () => {
|
||||
}
|
||||
// 4. 获取设备状态
|
||||
let deviceImei = deviceDetail.value.deviceImei
|
||||
let typeName = deviceDetail.value.typeName
|
||||
const statusRes = await getDeviceStatus({
|
||||
functionMode: 2,
|
||||
batchId,
|
||||
typeName,
|
||||
typeName: 'FunctionAccessBatchStatusRule',
|
||||
deviceImei,
|
||||
interval: 500
|
||||
},
|
||||
@ -465,11 +461,10 @@ const sendTextMessage = async () => {
|
||||
}
|
||||
// 4. 获取设备状态
|
||||
let deviceImei = deviceDetail.value.deviceImei
|
||||
let typeName = deviceDetail.value.typeName
|
||||
const statusRes = await getDeviceStatus({
|
||||
functionMode: 2,
|
||||
batchId,
|
||||
typeName,
|
||||
typeName: 'FunctionAccessBatchStatusRule',
|
||||
deviceImei,
|
||||
interval: 500
|
||||
},
|
||||
|
@ -47,23 +47,15 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
console.log(props.deviceList);
|
||||
|
||||
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 checkedDeviceIds = ref(); // 存储选中的设备ID
|
||||
const checkAll = ref(false); // 全选状态
|
||||
|
||||
// 全选/取消全选
|
||||
const handleCheckAllChange = (val: boolean) => {
|
||||
checkedDeviceIds.value = val
|
||||
@ -133,9 +125,8 @@ watch(
|
||||
// 1. 清除地图上已有的所有标记(避免重复打点)
|
||||
mapInstance.clearMap();
|
||||
// 2. 重新添加新的设备标记
|
||||
newDeviceList.forEach((device) => {
|
||||
newDeviceList.forEach((device:any) => {
|
||||
console.log(device, 'device');
|
||||
|
||||
// 确保设备有经纬度(lng和lat),避免无效打点
|
||||
if (device.longitude && device.latitude) {
|
||||
new AMap.Marker({
|
||||
@ -150,15 +141,7 @@ watch(
|
||||
);
|
||||
|
||||
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(() => {
|
||||
|
@ -27,8 +27,8 @@
|
||||
:loading-text="forceAlarmLoading ? '报警中...' : '强制报警'"> {{
|
||||
forceAlarmLoading ? '报警中' : '强制报警' }}</el-button>
|
||||
<div style="position: absolute; right:30px; top:20px">
|
||||
<el-input v-model="queryParams.deviceMac" placeholder="MAC/IMEI" clearable
|
||||
style="width: 200px; margin-right: 20px;" />
|
||||
<el-input v-model="queryParams.content" placeholder="MAC/IMEI" clearable
|
||||
style="width: 200px; margin-right: 20px;" @keyup.enter="handleQuery" @input="handleInput" />
|
||||
<el-button type="primary" plain @click="toggleFilter">高级筛选</el-button>
|
||||
</div>
|
||||
|
||||
@ -171,6 +171,7 @@ const deviceTypeOptions = ref([]); //设备类型
|
||||
const enabledDeptOptions = ref();
|
||||
const forceAlarmLoading = ref(false) //强制报警
|
||||
const sendTextLoading = ref(false)
|
||||
const debounceTimer = ref(null) // 用于防抖的定时器
|
||||
const form = ref({
|
||||
messageToSend: ''
|
||||
})
|
||||
@ -186,7 +187,8 @@ const initData: PageData<'', deviceQuery> = {
|
||||
personnelBy: '',
|
||||
communicationMode: '',
|
||||
groupId: '',
|
||||
deviceType: ''
|
||||
deviceType: '',
|
||||
content: ''
|
||||
},
|
||||
rules: undefined,
|
||||
form: ''
|
||||
@ -258,7 +260,15 @@ const handleNodeClick = (data: any) => {
|
||||
queryParams.value.groupId = data.id;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
const handleInput = () => {
|
||||
if (debounceTimer.value) {
|
||||
clearTimeout(debounceTimer.value)
|
||||
}
|
||||
// 300ms后执行查询(避免输入过程中频繁调用接口)
|
||||
debounceTimer.value = setTimeout(() => {
|
||||
handleQuery() // 调用查询接口的方法
|
||||
}, 300)
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
@ -363,10 +373,11 @@ const submitForm = async () => {
|
||||
return
|
||||
}
|
||||
// 4. 获取设备状态
|
||||
// 获取实时状态类型:FunctionAccessBatchstatusRule 批量 ,FunctionAccessStatusRule 单个
|
||||
const statusRes = await getDeviceStatus({
|
||||
functionMode: 2,
|
||||
batchId,
|
||||
typeName,
|
||||
typeName: 'FunctionAccessBatchStatusRule',
|
||||
deviceImeiList,
|
||||
deviceIds,
|
||||
interval: 500
|
||||
@ -423,7 +434,7 @@ const forceAlarm = async () => {
|
||||
const statusRes = await getDeviceStatus({
|
||||
functionMode: 2,
|
||||
batchId,
|
||||
typeName,
|
||||
typeName: 'FunctionAccessBatchStatusRule',
|
||||
deviceImeiList,
|
||||
deviceIds,
|
||||
interval: 500
|
||||
|
@ -15,10 +15,11 @@
|
||||
<el-card>
|
||||
<!-- =========搜索按钮操作======= -->
|
||||
<div class="btn_search">
|
||||
<el-button type="primary" plain>导出</el-button>
|
||||
<el-button type="primary" plain icon="Download"
|
||||
@click="handleExport">导出</el-button>
|
||||
<div style="position: absolute; right:30px; top:20px">
|
||||
<el-input v-model="queryParams.deviceMac" placeholder="MAC/IMEI" clearable
|
||||
style="width: 200px; margin-right: 20px;" />
|
||||
<el-input v-model="queryParams.content" placeholder="MAC/IMEI" clearable
|
||||
style="width: 200px; margin-right: 20px;" @keyup.enter="handleQuery" @input="handleInput" />
|
||||
<el-button type="primary" plain @click="toggleFilter">高级筛选</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,7 +64,7 @@
|
||||
<el-table-column label="设备名称" align="center" prop="deviceName" />
|
||||
<el-table-column label="设备型号" align="center" prop="deviceType" />
|
||||
<el-table-column label="操作模块" align="center" prop="deviceAction" />
|
||||
<el-table-column label="操作内容" align="center" prop="content" />
|
||||
<el-table-column label="操作内容" align="center" prop="content" show-overflow-tooltip />
|
||||
<el-table-column label="操作时间" align="center" prop="createTime" />
|
||||
</el-table>
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
|
||||
@ -96,7 +97,7 @@ const queryFormRef = ref<ElFormInstance>();
|
||||
const activeNames = ref([]);
|
||||
const deviceTypeOptions = ref([]); //设备类型
|
||||
const enabledDeptOptions = ref();
|
||||
|
||||
const debounceTimer = ref(null) // 用于防抖的定时器
|
||||
const initData: PageData<'', deviceQuery> = {
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
@ -107,7 +108,8 @@ const initData: PageData<'', deviceQuery> = {
|
||||
groupId: '',
|
||||
deviceType: '',
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
endTime: '',
|
||||
content:""
|
||||
},
|
||||
rules: undefined,
|
||||
form: ''
|
||||
@ -166,7 +168,15 @@ const handleNodeClick = (data: any) => {
|
||||
queryParams.value.groupId = data.id;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
const handleInput = () => {
|
||||
if (debounceTimer.value) {
|
||||
clearTimeout(debounceTimer.value)
|
||||
}
|
||||
// 300ms后执行查询(避免输入过程中频繁调用接口)
|
||||
debounceTimer.value = setTimeout(() => {
|
||||
handleQuery() // 调用查询接口的方法
|
||||
}, 300)
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
@ -211,23 +221,34 @@ const getList = async () => {
|
||||
/** 查询部结构 */
|
||||
const getDeptTree = async () => {
|
||||
const res = await api.devicegroupList('');
|
||||
const allDeviceOption = {
|
||||
const allDeviceOption = {
|
||||
id: '',
|
||||
groupName: '全部设备',
|
||||
disabled: false,
|
||||
children: []
|
||||
disabled: false,
|
||||
children: []
|
||||
|
||||
};
|
||||
deptOptions.value = [allDeviceOption, ...res.data]
|
||||
enabledDeptOptions.value = filterDisabledDept(res.data);
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'/api/device/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`指令下发记录${new Date().getTime()}.xlsx`,
|
||||
'get'
|
||||
);
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep .el-collapse-item__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
|
||||
color: rgba(2, 124, 251, 1);
|
||||
background: transparent;
|
||||
|
304
src/views/controlCenter/historyjectory/index.vue
Normal file
304
src/views/controlCenter/historyjectory/index.vue
Normal file
@ -0,0 +1,304 @@
|
||||
<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="primary" plain icon="Download" @click="handleExport">导出</el-button>
|
||||
<div style="position: absolute; right:30px; top:20px">
|
||||
<el-input v-model="queryParams.content" placeholder="MAC/IMEI" clearable
|
||||
style="width: 200px; margin-right: 20px;" @keyup.enter="handleQuery" @input="handleInput" />
|
||||
<el-button type="primary" plain @click="toggleFilter">高级筛选</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-collapse accordion v-model="activeNames">
|
||||
<el-collapse-item name="1">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="queryFormRef">
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="queryParams.deviceType" placeholder="设备类型">
|
||||
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.typeName"
|
||||
:value="item.deviceTypeId" />
|
||||
</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="设备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="轨迹时间" style="width: 308px">
|
||||
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
|
||||
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker>
|
||||
</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>
|
||||
<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="deviceType" />
|
||||
<el-table-column label="设备ImeI" align="center" prop="deviceImei" />
|
||||
<el-table-column label="设备MAC" align="center" prop="deviceMac" show-overflow-tooltip />
|
||||
<el-table-column label="轨迹记录" align="center">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="historyjectory(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>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup name="User" lang="ts">
|
||||
import api from '@/api/controlCenter/historyjectory/index'
|
||||
import apiGroup from '@/api/controlCenter/controlPanel/index'
|
||||
import apiTypeAll from '@/api/equipmentManagement/device/index';
|
||||
import { deviceQuery, deviceVO } from '@/api/controlCenter/historyjectory/types';
|
||||
const router = useRouter();
|
||||
const dateRange = ref(['', '']);
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
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 deptTreeRef = ref<ElTreeInstance>();
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const activeNames = ref([]);
|
||||
const deviceTypeOptions = ref([]); //设备类型
|
||||
const enabledDeptOptions = ref();
|
||||
const debounceTimer = ref(null) // 用于防抖的定时器
|
||||
const initData: PageData<'', deviceQuery> = {
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deviceName: '',
|
||||
deviceMac: '',
|
||||
deviceImei: '',
|
||||
groupId: '',
|
||||
deviceType: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
content: ''
|
||||
},
|
||||
rules: undefined,
|
||||
form: ''
|
||||
};
|
||||
|
||||
const data = reactive<PageData<'', deviceQuery>>(initData);
|
||||
const { queryParams } = toRefs<PageData<'', deviceQuery>>(data);
|
||||
|
||||
/** 通过条件过滤节点 */
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
return data.groupName.indexOf(value) !== -1;
|
||||
};
|
||||
// 设备类型
|
||||
const getDeviceType = () => {
|
||||
apiTypeAll.deviceTypeAll().then(res => {
|
||||
if (res.code == 200) {
|
||||
deviceTypeOptions.value = res.data
|
||||
}
|
||||
}).catch(err => {
|
||||
|
||||
})
|
||||
};
|
||||
/** 根据名称筛选部门树 */
|
||||
watchEffect(
|
||||
() => {
|
||||
deptTreeRef.value?.filter(deptName.value);
|
||||
},
|
||||
{
|
||||
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
|
||||
}
|
||||
);
|
||||
const toggleFilter = () => {
|
||||
if (activeNames.value.length > 0) {
|
||||
activeNames.value = [];
|
||||
} else {
|
||||
activeNames.value = ['1'];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** 过滤禁用的部门 */
|
||||
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 handleInput = () => {
|
||||
if (debounceTimer.value) {
|
||||
clearTimeout(debounceTimer.value)
|
||||
}
|
||||
// 300ms后执行查询(避免输入过程中频繁调用接口)
|
||||
debounceTimer.value = setTimeout(() => {
|
||||
handleQuery() // 调用查询接口的方法
|
||||
}, 300)
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
dateRange.value = ['', ''];
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value.pageNum = 1;
|
||||
queryParams.value.groupId = undefined;
|
||||
deptTreeRef.value?.setCurrentKey(undefined);
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 选择条数 */
|
||||
const handleSelectionChange = (selection: deviceVO[]) => {
|
||||
ids.value = selection;
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDeptTree(); // 初始化部门数据
|
||||
getList(); // 初始化列表数据
|
||||
getDeviceType() //设备类型
|
||||
});
|
||||
/** 查询用户列表 */
|
||||
const getList = async () => {
|
||||
loading.value = false;
|
||||
const [startTime, endTime] = dateRange.value;
|
||||
queryParams.value = {
|
||||
...queryParams.value,
|
||||
startTime: startTime,
|
||||
endTime: endTime
|
||||
};
|
||||
const res = await api.devicelocationHistory(queryParams.value)
|
||||
loading.value = false;
|
||||
deviceList.value = res.rows;
|
||||
total.value = res.total;
|
||||
};
|
||||
|
||||
/** 查询部结构 */
|
||||
const getDeptTree = async () => {
|
||||
const res = await apiGroup.devicegroupList('');
|
||||
const allDeviceOption = {
|
||||
id: '',
|
||||
groupName: '全部设备',
|
||||
disabled: false,
|
||||
children: []
|
||||
|
||||
};
|
||||
deptOptions.value = [allDeviceOption, ...res.data]
|
||||
enabledDeptOptions.value = filterDisabledDept(res.data);
|
||||
};
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'/api/device/locationHistoryExport',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`历史轨迹${new Date().getTime()}.xlsx`,
|
||||
'get'
|
||||
);
|
||||
};
|
||||
/**历史轨迹跳转 */
|
||||
const historyjectory = (row: any) => {
|
||||
const id = row.id;
|
||||
router.push('/controlCenter/historyjectory/' + id);
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep .el-collapse-item__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
|
||||
color: rgba(2, 124, 251, 1);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: rgba(0, 165, 82, 1);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: rgba(224, 52, 52, 1);
|
||||
}
|
||||
|
||||
.Maplist {
|
||||
height: 680px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
449
src/views/controlCenter/historyjectory/trackplayback.vue
Normal file
449
src/views/controlCenter/historyjectory/trackplayback.vue
Normal file
@ -0,0 +1,449 @@
|
||||
<template>
|
||||
<div class="track-player">
|
||||
<!-- 地图容器 -->
|
||||
<div id="amap-container" class="map-container"></div>
|
||||
<div class="content_top">
|
||||
<div class="content_layout">
|
||||
<div v-if="deviceGroups && deviceGroups.length > 0">
|
||||
<el-timeline style="margin-left: -50px;">
|
||||
<!-- 日期分组循环 -->
|
||||
<el-timeline-item v-for="(group, groupIndex) in deviceGroups" :key="groupIndex"
|
||||
:timestamp="group.date" placement="top" icon="Time" color="rgba(0, 198, 250, 1)">
|
||||
<!-- 设备项循环 -->
|
||||
<div class="device-item">
|
||||
<!-- 设备名称 + 时间 -->
|
||||
<div class="device-header">
|
||||
<span class="device-name">{{ group.deviceName }}</span>
|
||||
<span class="device-time">{{ formatTimestampToHM(group.detailList[0].timestamp) }}</span>
|
||||
</div>
|
||||
<!-- 地点信息 -->
|
||||
<div class="device-place">
|
||||
<div class="d_f">
|
||||
<div class="Stime">初始地点:</div>
|
||||
<div class="Sloation">{{
|
||||
group.startLocation }}</div>
|
||||
</div>
|
||||
<div class="d_f">
|
||||
<div class="Stime">结束地点:</div>
|
||||
<div class="Sloation">{{
|
||||
group.endLocation }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 轨迹播放按钮 -->
|
||||
<div class="control_btt">
|
||||
<el-button class="control_btn" @click="handlePlayPause(group)">轨迹播放</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
<div v-if="deviceGroups == null" class="nodata">
|
||||
<img src="@/assets/images/nodata.png" alt="" class="nodataImg">
|
||||
<div class="title">暂无数据</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import api from "@/api/controlCenter/historyjectory/index"
|
||||
import { formatTimestampToHM } from "@/utils/function"
|
||||
const route = useRoute();
|
||||
// -------------------------- 状态管理 --------------------------
|
||||
// 地图实例
|
||||
const mapRef = ref<AMap.Map | null>(null);
|
||||
// 轨迹线实例
|
||||
const polylineRef = ref<AMap.Polyline | null>(null);
|
||||
// 移动标记点实例
|
||||
const markerRef = ref<AMap.Marker | null>(null);
|
||||
// 轨迹数据(初始为空,从设备列表动态获取)
|
||||
const trackPoints = ref<[number, number][]>([]); // 移除硬编码默认值
|
||||
// 设备分组数据(包含各设备的轨迹点)
|
||||
const deviceGroups = ref([])
|
||||
// 播放状态
|
||||
const isPlaying = ref<boolean>(false);
|
||||
const isLoading = ref<boolean>(true);
|
||||
const playSpeed = ref<number>(1);
|
||||
const currentIndex = ref<number>(0);
|
||||
const playTimer = ref<number | null>(null);
|
||||
|
||||
// -------------------------- 地图初始化 --------------------------
|
||||
const initMap = () => {
|
||||
try {
|
||||
if (!window.AMap) {
|
||||
throw new Error('高德地图API加载失败');
|
||||
}
|
||||
// 创建地图实例:不设置 center 和 zoom,由 setBounds 自动调整
|
||||
const map = new AMap.Map('amap-container', {
|
||||
resizeEnable: true, // 允许地图自适应容器大小
|
||||
// 移除 center、zoom 等固定视角的配置
|
||||
});
|
||||
mapRef.value = map;
|
||||
// 创建轨迹线(初始无数据)
|
||||
const polyline = new AMap.Polyline({
|
||||
path: [],
|
||||
strokeColor: '#3886ff',
|
||||
strokeWeight: 10,
|
||||
strokeOpacity: 0.8,
|
||||
});
|
||||
map.add(polyline);
|
||||
polylineRef.value = polyline;
|
||||
// 创建标记点(初始位置临时设为 [0, 0],播放时会覆盖)
|
||||
const marker = new AMap.Marker({
|
||||
position: [0, 0],
|
||||
icon: new AMap.Icon({
|
||||
size: new AMap.Size(40, 40),
|
||||
image: 'https://webapi.amap.com/images/car.png',
|
||||
imageSize: new AMap.Size(40, 40),
|
||||
}),
|
||||
anchor: 'center',
|
||||
});
|
||||
map.add(marker);
|
||||
markerRef.value = marker;
|
||||
|
||||
isLoading.value = false;
|
||||
fetchTrackData(); // 数据请求移到地图初始化后
|
||||
} catch (error) {
|
||||
console.error('地图初始化失败:', error);
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
// 单独的接口请求函数
|
||||
const fetchTrackData = () => {
|
||||
const data = {
|
||||
id: route.params.id as string
|
||||
};
|
||||
api.getLocationHistoryDetail(data).then((res) => {
|
||||
if (res.code === 200) {
|
||||
deviceGroups.value = res.data;
|
||||
// 数据加载完成后,自动播放第一个设备的轨迹
|
||||
if (deviceGroups.value&&deviceGroups.value.length > 0) {
|
||||
const firstGroup = deviceGroups.value[0];
|
||||
const firstDetailList = firstGroup.detailList;
|
||||
// 检查轨迹列表是否有数据
|
||||
if (firstDetailList && firstDetailList.length > 0) {
|
||||
const firstPoint = firstDetailList[0];
|
||||
const centerLng = Number(firstPoint.longitude);
|
||||
const centerLat = Number(firstPoint.latitude);
|
||||
// 验证经纬度有效性
|
||||
if (!isNaN(centerLng) && !isNaN(centerLat) && mapRef.value) {
|
||||
// 设置地图中心为第一个轨迹点
|
||||
mapRef.value.setCenter([centerLng, centerLat]);
|
||||
// 设置合适的缩放级别(16为街道级,清晰无需手动放大)
|
||||
mapRef.value.setZoom(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('获取轨迹数据失败');
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// -------------------------- 轨迹播放核心逻辑 --------------------------
|
||||
// 平滑移动标记点
|
||||
const smoothMoveMarker = (
|
||||
start: [number, number],
|
||||
end: [number, number],
|
||||
duration: number
|
||||
) => {
|
||||
const startLngLat = new AMap.LngLat(start[0], start[1]);
|
||||
const endLngLat = new AMap.LngLat(end[0], end[1]);
|
||||
const distance = startLngLat.distance(endLngLat);
|
||||
const speed = distance / duration;
|
||||
let startTime = Date.now();
|
||||
const moveFrame = () => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
const currentLng = start[0] + (end[0] - start[0]) * progress;
|
||||
const currentLat = start[1] + (end[1] - start[1]) * progress;
|
||||
markerRef.value?.setPosition([currentLng, currentLat]);
|
||||
if (progress < 1) {
|
||||
playTimer.value = requestAnimationFrame(moveFrame);
|
||||
} else {
|
||||
currentIndex.value += 1;
|
||||
if (currentIndex.value < trackPoints.value.length - 1) {
|
||||
playNextSegment();
|
||||
} else {
|
||||
// 播放完成
|
||||
isPlaying.value = false;
|
||||
currentIndex.value = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
playTimer.value = requestAnimationFrame(moveFrame);
|
||||
};
|
||||
|
||||
// 播放下一段轨迹
|
||||
const playNextSegment = () => {
|
||||
if (currentIndex.value >= trackPoints.value.length - 1) return;
|
||||
const startPoint = trackPoints.value[currentIndex.value];
|
||||
const endPoint = trackPoints.value[currentIndex.value + 1];
|
||||
const baseDuration = 500;
|
||||
const duration = baseDuration / playSpeed.value;
|
||||
smoothMoveMarker(startPoint, endPoint, duration);
|
||||
};
|
||||
|
||||
// 重置播放状态(切换设备时调用)
|
||||
const resetPlayState = () => {
|
||||
// 停止当前播放
|
||||
if (isPlaying.value && playTimer.value) {
|
||||
cancelAnimationFrame(playTimer.value);
|
||||
}
|
||||
// 重置状态
|
||||
isPlaying.value = false;
|
||||
currentIndex.value = 0;
|
||||
};
|
||||
|
||||
// 播放/暂停切换(接收设备的trackPoints)
|
||||
const handlePlayPause = (group: any) => {
|
||||
const trackPointList = group.detailList;
|
||||
const validTrackPoints = trackPointList.map((point: any) => {
|
||||
const lng = Number(point.longitude); // 经度
|
||||
const lat = Number(point.latitude); // 纬度
|
||||
// 验证经纬度有效性(避免NaN)
|
||||
return isNaN(lng) || isNaN(lat) ? null : [lng, lat] as [number, number];
|
||||
}).filter((point: any) => point !== null); // 过滤无效点
|
||||
|
||||
if (validTrackPoints.length < 2) {
|
||||
ElMessage.warning('有效轨迹点不足2个,无法播放');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 重置播放状态并赋值轨迹数据
|
||||
resetPlayState();
|
||||
trackPoints.value = validTrackPoints;
|
||||
|
||||
// 4. 开始播放
|
||||
isPlaying.value = true;
|
||||
playNextSegment();
|
||||
};
|
||||
|
||||
// -------------------------- 生命周期与监听 --------------------------
|
||||
onMounted(() => {
|
||||
initMap();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (playTimer.value) {
|
||||
cancelAnimationFrame(playTimer.value);
|
||||
}
|
||||
mapRef.value?.remove(polylineRef.value!);
|
||||
mapRef.value?.remove(markerRef.value!);
|
||||
});
|
||||
|
||||
// 监听轨迹数据变化(更新轨迹线和地图范围)
|
||||
watch(trackPoints, (newPoints) => {
|
||||
if (!mapRef.value || !polylineRef.value || !markerRef.value) return;
|
||||
if (newPoints.length === 0) return;
|
||||
// 1. 更新轨迹线
|
||||
polylineRef.value.setPath(newPoints);
|
||||
// 2. 获取第一个轨迹点(作为地图中心)
|
||||
const firstPoint = newPoints[0]; // [经度, 纬度]
|
||||
const centerLngLat = new AMap.LngLat(firstPoint[0], firstPoint[1]);
|
||||
// 3. 地图聚焦到第一个点,并设置固定zoom(16为街道级,足够大,无需手动放大)
|
||||
mapRef.value.setCenter(centerLngLat); // 设中心
|
||||
mapRef.value.setZoom(15); // 固定缩放级别(1-20,越大越近)
|
||||
// 4. 标记点移到第一个点
|
||||
markerRef.value.setPosition(firstPoint);
|
||||
}, { deep: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式保持不变 */
|
||||
:deep .el-timeline-item__tail {
|
||||
border-left: 1px solid rgba(0, 198, 250, 0.2);
|
||||
}
|
||||
|
||||
:deep .el-timeline-item__timestamp {
|
||||
color: rgba(2, 124, 251, 1);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.track-player {
|
||||
width: 100%;
|
||||
height: 90vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content_top {
|
||||
width: 300px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
|
||||
background: #fff;
|
||||
height: 88vh;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.Stime {
|
||||
color: rgba(56, 64, 79, 0.6);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.Sloation {
|
||||
color: rgba(56, 64, 79, 1);
|
||||
width: 150px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.d_f {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content_layout {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 时间线整体 */
|
||||
.el-timeline {
|
||||
margin-left: 20px;
|
||||
/* 调整时间线与左侧的间距 */
|
||||
}
|
||||
|
||||
/* 时间戳(日期) */
|
||||
.el-timeline-item__timestamp {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(2, 124, 251, 1);
|
||||
}
|
||||
|
||||
/* 设备项容器 */
|
||||
.device-item {
|
||||
background-color: #f9fafc;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 设备名称 + 时间 行 */
|
||||
.device-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 设备名称 */
|
||||
.device-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 设备时间 */
|
||||
.device-time {
|
||||
color: rgba(97, 128, 183, 1);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 设备型号 */
|
||||
.device-model {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 地点信息 */
|
||||
.device-place p {
|
||||
margin: 4px 0;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.date_group {
|
||||
margin-bottom: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.date_header {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.device_item {
|
||||
padding: 10px 12px;
|
||||
background-color: #f7f8fc;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device_info {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.device_name {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.device_model {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.time_info {
|
||||
font-size: 15px;
|
||||
color: rgba(97, 128, 183, 1);
|
||||
}
|
||||
|
||||
.place_info {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.place_info p {
|
||||
margin: 2px 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.control_btn {
|
||||
font-size: 14px;
|
||||
padding: 4px 12px;
|
||||
background: rgba(2, 124, 251, 0.06);
|
||||
color: #027cfb;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
align-self: flex-end;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.control_btt {
|
||||
text-align: end;
|
||||
|
||||
}
|
||||
.nodata{
|
||||
text-align: center;
|
||||
transform: translate(-1%,100%);
|
||||
left:50%;
|
||||
height: 30vh;
|
||||
}
|
||||
.nodataImg{
|
||||
width: 130px;
|
||||
}
|
||||
.title{
|
||||
color: #666;
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
@ -4,19 +4,41 @@
|
||||
:leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-button :type="isListView ? 'primary' : ''" @click="switchView('card')">
|
||||
{{ isListView ? '卡片显示' : '卡片显示' }}
|
||||
</el-button>
|
||||
<el-button :type="!isListView ? 'primary' : ''" @click="switchView('list')">
|
||||
{{ !isListView ? '列表显示' : '列表显示' }}
|
||||
</el-button>
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" style="margin-top: 20px;">
|
||||
<el-form-item label="设备名称" prop="deviceName">
|
||||
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备MAC" prop="deviceMac">
|
||||
<el-input v-model="queryParams.deviceMac" placeholder="请输入设备MAC" clearable @keyup.enter="handleQuery" />
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="queryParams.deviceType" placeholder="设备类型">
|
||||
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.typeName"
|
||||
:value="item.deviceTypeId" />
|
||||
</el-select>
|
||||
</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 label="报警事项" prop="deviceAction">
|
||||
<el-select v-model="queryParams.deviceAction">
|
||||
<el-option value="0" label="强制报警"></el-option>
|
||||
<el-option value="1" label="撞击闯入"></el-option>
|
||||
<el-option value="2" label="手动报警"></el-option>
|
||||
<el-option value="3" label="电子围栏告警"></el-option>
|
||||
<el-option value="4" label="强制告警"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警时间" prop="alarmTime">
|
||||
<el-date-picker clearable v-model="queryParams.alarmTime" type="date" value-format="YYYY-MM-DD"
|
||||
placeholder="请选择报警时间" />
|
||||
<el-form-item label="报警时间" style="width: 308px">
|
||||
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
|
||||
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警状态" prop="treatmentState">
|
||||
<el-select v-model="queryParams.treatmentState">
|
||||
<el-option value="0" label="已处理"></el-option>
|
||||
<el-option value="1" label="未处理"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
@ -28,184 +50,187 @@
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd"
|
||||
v-hasPermi="['equipment:equipmentAlarmRecord:add']">新增</el-button>
|
||||
<div v-if="isListView" key="card">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6" v-for="(item, index) in alarmList" :key="index">
|
||||
<el-card class="custom-alarm-card">
|
||||
<div class="card-header">
|
||||
<h4 class="device-name">{{ item.startTime }}</h4>
|
||||
</div>
|
||||
<!-- 卡片内容:报警事项、地点、时间等信息 -->
|
||||
<div class="card-body">
|
||||
<div class="d_fl">
|
||||
<div class="d_flex">
|
||||
<div>
|
||||
<img :src="item.devicePic" alt="" class="devicePicImg">
|
||||
</div>
|
||||
<div style="margin-left:10px;">
|
||||
<div class="deviceName">{{ item.deviceName }}</div>
|
||||
<div class="deviceTypeName">{{ item.deviceTypeName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="treatment-state" :class="item.treatmentState === 0 ? 'handled' : 'pending'">
|
||||
{{ item.treatmentState === 0 ? '已处理' : '待处理' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">报警事项</div>
|
||||
<div class="alearm">
|
||||
<template v-if="item.deviceAction === 0">强制报警
|
||||
<span v-if="item.treatmentState === 1">({{ item.startTime.split(' ')[1] || '' }})</span>
|
||||
</template>
|
||||
<template v-else-if="item.deviceAction === 1">撞击闯入
|
||||
<span v-if="item.treatmentState === 1">({{ item.startTime.split(' ')[1] || '' }})</span>
|
||||
</template>
|
||||
<template v-else-if="item.deviceAction === 2">手动报警
|
||||
<span v-if="item.treatmentState === 1">({{ item.startTime.split(' ')[1] || '' }})</span>
|
||||
</template>
|
||||
<template v-else-if="item.deviceAction === 3">电子围栏告警
|
||||
<span v-if="item.treatmentState === 1">({{ item.startTime.split(' ')[1] || '' }})</span>
|
||||
</template>
|
||||
<template v-else-if="item.deviceAction === 4">强制告警
|
||||
<span v-if="item.treatmentState === 1">({{ item.startTime.split(' ')[1] || '' }})</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="label">报警地点:</div>
|
||||
<div class="alearmADD">
|
||||
{{ item.location }}
|
||||
</div>
|
||||
<div v-if="item.treatmentState === 0" class="dl_bot d_fl">
|
||||
<div>时长:{{ item.durationTime}}</div>
|
||||
<div>解除: {{ item.finishTime.split(' ')[1] || '' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
|
||||
v-hasPermi="['equipment:equipmentAlarmRecord:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
|
||||
v-hasPermi="['equipment:equipmentAlarmRecord:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport"
|
||||
v-hasPermi="['equipment:equipmentAlarmRecord:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="equipmentAlarmRecordList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="报警设备id" align="center" prop="deviceId" />
|
||||
<el-table-column label="设备IMEI" align="center" prop="deviceImei" />
|
||||
<el-table-column label="设备MAC" align="center" prop="deviceMac" />
|
||||
<el-table-column label="设备名称" align="center" prop="deviceName" />
|
||||
<el-table-column label="所属代理" align="center" prop="agent" />
|
||||
<el-table-column label="绑定app用户" align="center" prop="bindApp" />
|
||||
<el-table-column label="报警类型" align="center" prop="alarmType" />
|
||||
<el-table-column label="报警编码
|
||||
" align="center" prop="alarmCode" />
|
||||
<el-table-column label="报警描述" align="center" prop="alarmDescription" />
|
||||
<el-table-column label="报警时间" align="center" prop="alarmTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.alarmTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['equipment:equipmentAlarmRecord:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
|
||||
v-hasPermi="['equipment:equipmentAlarmRecord:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
<div v-else key="list">
|
||||
<el-table v-loading="loading" border :data="alarmList">
|
||||
<el-table-column label="设备名称" align="center" prop="deviceName" />
|
||||
<el-table-column label="设备类型" align="center" prop="deviceTypeName" />
|
||||
<el-table-column label="IMEI/MAC" align="center">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.deviceImei }} {{ scope.row.deviceMac }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报警地点" align="center" prop="location" />
|
||||
<el-table-column label="报警事项" align="center" prop="deviceAction">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.deviceAction == 0">强制报警</div>
|
||||
<div v-if="scope.row.deviceAction == 1">撞击闯入</div>
|
||||
<div v-if="scope.row.deviceAction == 2">手动报警</div>
|
||||
<div v-if="scope.row.deviceAction == 3">电子围栏告警</div>
|
||||
<div v-if="scope.row.deviceAction == 4">强制告警</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报警持续时间" align="center" prop="durationTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.durationTime }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="处理状态" align="center" prop="treatmentState">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.treatmentState == 0">已处理</div>
|
||||
<div v-if="scope.row.treatmentState == 1">未处理</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报警时间" align="center" prop="startTime" />
|
||||
</el-table>
|
||||
</div>
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改设备报警记录对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="equipmentAlarmRecordFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="报警设备id" prop="deviceId">
|
||||
<el-input v-model="form.deviceId" placeholder="请输入报警设备id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备IMEI" prop="deviceImei">
|
||||
<el-input v-model="form.deviceImei" placeholder="请输入设备IMEI" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备MAC" prop="deviceMac">
|
||||
<el-input v-model="form.deviceMac" placeholder="请输入设备MAC" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称" prop="deviceName">
|
||||
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属代理" prop="agent">
|
||||
<el-input v-model="form.agent" placeholder="请输入所属代理" />
|
||||
</el-form-item>
|
||||
<el-form-item label="绑定app用户" prop="bindApp">
|
||||
<el-input v-model="form.bindApp" placeholder="请输入绑定app用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="报警编码
|
||||
" prop="alarmCode">
|
||||
<el-input v-model="form.alarmCode" placeholder="请输入报警编码
|
||||
" />
|
||||
</el-form-item>
|
||||
<el-form-item label="报警描述" prop="alarmDescription">
|
||||
<el-input v-model="form.alarmDescription" placeholder="请输入报警描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="报警时间" prop="alarmTime">
|
||||
<el-date-picker clearable v-model="form.alarmTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择报警时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="EquipmentAlarmRecord" lang="ts">
|
||||
import { listEquipmentAlarmRecord, getEquipmentAlarmRecord, delEquipmentAlarmRecord, addEquipmentAlarmRecord, updateEquipmentAlarmRecord } from '@/api/equipmentAlarmRecord';
|
||||
import { EquipmentAlarmRecordVO, EquipmentAlarmRecordQuery, EquipmentAlarmRecordForm } from '@/api/equipmentAlarmRecord/types';
|
||||
|
||||
<script setup name="Alarm" lang="ts">
|
||||
import { listAlarm } from '@/api/equipmentAlarmRecord/index';
|
||||
import { AlarmVO, AlarmQuery, AlarmForm } from '@/api/equipmentAlarmRecord/types';
|
||||
import apiTypeAll from '@/api/equipmentManagement/device/index';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const equipmentAlarmRecordList = ref<EquipmentAlarmRecordVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const alarmList = ref<AlarmVO[]>([]);
|
||||
const isListView = ref(true);
|
||||
const dateRange = ref(['', '']);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const deviceTypeOptions = ref([]); //设备类型
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const equipmentAlarmRecordFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: EquipmentAlarmRecordForm = {
|
||||
const alarmFormRef = ref<ElFormInstance>();
|
||||
const initFormData: AlarmForm = {
|
||||
id: undefined,
|
||||
deviceId: undefined,
|
||||
deviceImei: undefined,
|
||||
deviceMac: undefined,
|
||||
deviceAction: undefined,
|
||||
deviceName: undefined,
|
||||
agent: undefined,
|
||||
bindApp: undefined,
|
||||
alarmType: undefined,
|
||||
alarmCode: undefined,
|
||||
alarmDescription: undefined,
|
||||
alarmTime: undefined
|
||||
dataSource: undefined,
|
||||
content: undefined,
|
||||
deviceType: undefined,
|
||||
longitude: undefined,
|
||||
latitude: undefined,
|
||||
location: undefined,
|
||||
startTime: undefined,
|
||||
}
|
||||
const data = reactive<PageData<EquipmentAlarmRecordForm, EquipmentAlarmRecordQuery>>({
|
||||
const data = reactive<PageData<AlarmForm, AlarmQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deviceImei: undefined,
|
||||
deviceMac: undefined,
|
||||
pageSize: 8,
|
||||
deviceId: undefined,
|
||||
deviceAction: undefined,
|
||||
deviceName: undefined,
|
||||
alarmTime: undefined,
|
||||
dataSource: undefined,
|
||||
content: undefined,
|
||||
deviceType: undefined,
|
||||
longitude: undefined,
|
||||
latitude: undefined,
|
||||
durationTime: undefined,
|
||||
treatmentState: undefined,
|
||||
params: {
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
id: [
|
||||
{ required: true, message: "不能为空", trigger: "blur" }
|
||||
{ required: true, message: "ID不能为空", trigger: "blur" }
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询设备报警记录列表 */
|
||||
const switchView = (view: 'list' | 'card') => {
|
||||
isListView.value = (view === 'card');
|
||||
};
|
||||
/** 查询设备告警列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listEquipmentAlarmRecord(queryParams.value);
|
||||
equipmentAlarmRecordList.value = res.rows;
|
||||
const [startTime, endTime] = dateRange.value;
|
||||
queryParams.value = {
|
||||
...queryParams.value,
|
||||
queryTime1: startTime,
|
||||
queryTime2: endTime
|
||||
};
|
||||
const res = await listAlarm(queryParams.value);
|
||||
alarmList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
}
|
||||
// 设备类型
|
||||
const getDeviceType = () => {
|
||||
apiTypeAll.deviceTypeAll().then(res => {
|
||||
if (res.code == 200) {
|
||||
deviceTypeOptions.value = res.data
|
||||
}
|
||||
}).catch(err => {
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
}
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
equipmentAlarmRecordFormRef.value?.resetFields();
|
||||
alarmFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@ -220,64 +245,140 @@ const resetQuery = () => {
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: EquipmentAlarmRecordVO[]) => {
|
||||
ids.value = selection.map(item => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = "添加设备报警记录";
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: EquipmentAlarmRecordVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0]
|
||||
const res = await getEquipmentAlarmRecord(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = "修改设备报警记录";
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
equipmentAlarmRecordFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateEquipmentAlarmRecord(form.value).finally(() => buttonLoading.value = false);
|
||||
} else {
|
||||
await addEquipmentAlarmRecord(form.value).finally(() => buttonLoading.value = false);
|
||||
}
|
||||
proxy?.$modal.msgSuccess("操作成功");
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: EquipmentAlarmRecordVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除设备报警记录编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
|
||||
await delEquipmentAlarmRecord(_ids);
|
||||
proxy?.$modal.msgSuccess("删除成功");
|
||||
await getList();
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download('system/equipmentAlarmRecord/export', {
|
||||
...queryParams.value
|
||||
}, `equipmentAlarmRecord_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getDeviceType()
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.custom-alarm-card {
|
||||
height:298px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 27, 74, 0.1);
|
||||
background: linear-gradient(127.63deg, rgba(255, 240, 240, 1), rgba(255, 255, 255, 1) 100%);
|
||||
border: none;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 卡片头部:设备名称 + 处理状态 布局 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(235, 238, 248, 1);
|
||||
height: 30px;
|
||||
margin-top: -11px;
|
||||
}
|
||||
|
||||
.d_fl {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.d_flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.devicePicImg {
|
||||
width: 48px;
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
.deviceTypeName {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
|
||||
.deviceName {
|
||||
color: rgba(56, 64, 79, 1);
|
||||
font-family: Microsoft YaHei;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 处理状态标签:不同状态不同颜色 */
|
||||
.treatment-state {
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.handled {
|
||||
/* 已处理-绿色 */
|
||||
color: rgba(0, 165, 82, 1);
|
||||
}
|
||||
|
||||
.pending {
|
||||
/* 待处理-红色 */
|
||||
color: rgba(224, 52, 52, 1);
|
||||
}
|
||||
|
||||
/* 标签与值的间距 */
|
||||
.label {
|
||||
font-weight: 500;
|
||||
margin-right: 4px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.alearm {
|
||||
border-radius: 4px;
|
||||
background: rgba(224, 52, 52, 0.04);
|
||||
padding: 8px 18px;
|
||||
margin: 10px 0 10px 0;
|
||||
position: relative;
|
||||
color: rgb(224, 52, 52);
|
||||
}
|
||||
|
||||
.alearm::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: rgb(224, 52, 52);
|
||||
border-radius: 50%;
|
||||
left: 6px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
.alearmADD {
|
||||
border-radius: 4px;
|
||||
background: rgba(224, 52, 52, 0.04);
|
||||
padding: 8px 18px;
|
||||
margin: 10px 0 10px 0;
|
||||
position: relative;
|
||||
color: rgba(56, 64, 79, 1);
|
||||
}
|
||||
|
||||
.alearmADD::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: rgba(56, 64, 79, 1);
|
||||
border-radius: 50%;
|
||||
left: 6px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
.dl_bot {
|
||||
border-top: 1px solid rgba(235, 238, 248, 1);
|
||||
padding-top: 10px;
|
||||
color: rgba(56, 64, 79, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
@ -42,7 +42,8 @@
|
||||
<el-table-column label="型号名称" align="center" prop="typeName" />
|
||||
<el-table-column label="类型code" align="center" prop="appModelDictionary">
|
||||
<template #default="scope">
|
||||
{{ modelDictionaryOptions.find(item => item.dictValue === String(scope.row.appModelDictionary))?.dictLabel }}
|
||||
{{appmodelDictionaryOptions.find(item => item.dictValue === String(scope.row.appModelDictionary))?.dictLabel
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否支持蓝牙" align="center" prop="isSupportBle">
|
||||
@ -97,9 +98,15 @@
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="路由跳转" prop="modelDictionary">
|
||||
<el-form-item label="pc路由跳转" prop="modelDictionary">
|
||||
<el-select v-model="form.modelDictionary" placeholder="请选择">
|
||||
<el-option v-for="item in modelDictionaryOptions" :key="item.dictValue" :label="item.dictLabel"
|
||||
<el-option v-for="item in pcmodelDictionaryOptions" :key="item.dictValue" :label="item.dictLabel"
|
||||
:value="item.dictValue" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="app路由跳转" prop="modelDictionary">
|
||||
<el-select v-model="form.modelDictionary" placeholder="请选择">
|
||||
<el-option v-for="item in appmodelDictionaryOptions" :key="item.dictValue" :label="item.dictLabel"
|
||||
:value="item.dictValue" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@ -180,7 +187,8 @@ const queryFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
const userFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
const formDialogRef = ref<InstanceType<typeof ElDialog>>();
|
||||
const loadingIng = ref(false)
|
||||
const modelDictionaryOptions = ref<any[]>([]);
|
||||
const appmodelDictionaryOptions = ref<any[]>([]);
|
||||
const pcmodelDictionaryOptions = ref<any[]>([]);
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
@ -237,11 +245,11 @@ const getList = async () => {
|
||||
};
|
||||
const getDict = async () => {
|
||||
const res = await getDicts('app_model_dictionary');
|
||||
modelDictionaryOptions.value = res.data;
|
||||
appmodelDictionaryOptions.value = res.data;
|
||||
}
|
||||
const pcgetDict = async () => {
|
||||
const res = await getDicts('pc_model_dictionary');
|
||||
modelDictionaryOptions.value = res.data;
|
||||
pcmodelDictionaryOptions.value = res.data;
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
@ -321,7 +329,9 @@ const submitForm = () => {
|
||||
try {
|
||||
const payload = {
|
||||
...form.value,
|
||||
modelDictionary: form.value.modelDictionary,
|
||||
appModelDictionary: form.value.modelDictionary,
|
||||
pcModelDictionary:form.value.modelDictionary,
|
||||
|
||||
locateMode: Number(form.value.locateMode),
|
||||
communicationMode: Number(form.value.communicationMode)
|
||||
};
|
||||
|
Reference in New Issue
Block a user