feat(device): 增加设备详情和地图功能
- 在 Device 接口中添加了 hardwareVersion、firmwareVersion、productName 和 id 字段 - 修改了 sendCommand函数,增加了 deviceId 和 qos 参数- 更新了设备详情页面,添加了新的设备信息字段 - 在地图页面添加了设备列表和设备信息窗口 - 优化了表单组件,增加了 Qos等级选择和字段校验- 统一了删除按钮的样式
This commit is contained in:
parent
39fca49ea6
commit
085d0ae690
@ -13,6 +13,10 @@ export interface DeviceRecord {
|
|||||||
longitude?: number;
|
longitude?: number;
|
||||||
latitude?: number;
|
latitude?: number;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
hardwareVersion?: string;
|
||||||
|
firmwareVersion?: string;
|
||||||
|
productName?: string;
|
||||||
|
id?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceCreateRecord {
|
export interface DeviceCreateRecord {
|
||||||
@ -108,10 +112,15 @@ export function triggerEvent(data: DeviceEventRecord) {
|
|||||||
|
|
||||||
// 下发命令
|
// 下发命令
|
||||||
export function sendCommand(data: any) {
|
export function sendCommand(data: any) {
|
||||||
|
const { deviceId, qos, ...rest } = data;
|
||||||
return axios({
|
return axios({
|
||||||
url: `/api/rest/device/send`,
|
url: `/api/rest/device/send`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
params: {
|
||||||
|
deviceId,
|
||||||
|
qos,
|
||||||
|
},
|
||||||
|
data: rest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
status="success"
|
status="success"
|
||||||
@click="openServeForm(record)"
|
@click="openServeForm(record)"
|
||||||
>
|
>
|
||||||
执行
|
<icon-plus />执行
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
@ -90,7 +90,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<a-modal
|
<a-modal
|
||||||
width="900px"
|
width="1100px"
|
||||||
height="500px"
|
height="500px"
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
@ -111,7 +111,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { queryDeviceDetail, queryDeviceRecord, sendCommand } from '@/api/device';
|
import { queryDeviceDetail, queryDeviceRecord, sendCommand } from '@/api/device';
|
||||||
import { queryServeDetail, queryServeList } from '@/api/tsl';
|
import { queryServeDetail, queryServeList } from '@/api/tsl';
|
||||||
@ -174,6 +174,7 @@
|
|||||||
];
|
];
|
||||||
const activeKey = ref('1');
|
const activeKey = ref('1');
|
||||||
const renderData = ref({
|
const renderData = ref({
|
||||||
|
deviceId: 0,
|
||||||
clientId: 1,
|
clientId: 1,
|
||||||
productId: 1
|
productId: 1
|
||||||
});
|
});
|
||||||
@ -200,6 +201,7 @@
|
|||||||
activeKey.value = e;
|
activeKey.value = e;
|
||||||
};
|
};
|
||||||
const dynamicFormData = ref();
|
const dynamicFormData = ref();
|
||||||
|
const formRef = ref();
|
||||||
// 关闭
|
// 关闭
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
@ -211,21 +213,35 @@
|
|||||||
fields.value = res.data.inputs;
|
fields.value = res.data.inputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormDataUpdate = (newFormData) => {
|
const handleFormDataUpdate = (newFormData: any) => {
|
||||||
dynamicFormData.value = newFormData; // 更新 formData
|
dynamicFormData.value = newFormData; // 更新 formData
|
||||||
};
|
};
|
||||||
// 确定
|
// 确定
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const res = await sendCommand(dynamicFormData.value);
|
try {
|
||||||
if (res.status === 200) {
|
const { qos, ...rest } = dynamicFormData.value;
|
||||||
Message.success({
|
const params = {
|
||||||
content: '执行成功',
|
deviceId: id,
|
||||||
duration: 5 * 1000,
|
qos,
|
||||||
});
|
paras: rest,
|
||||||
setVisible(false);
|
};
|
||||||
} else {
|
console.log(params);
|
||||||
|
const res = await sendCommand(params);
|
||||||
|
if (res.status === 200) {
|
||||||
|
Message.success({
|
||||||
|
content: '执行成功',
|
||||||
|
duration: 5 * 1000,
|
||||||
|
});
|
||||||
|
setVisible(false);
|
||||||
|
} else {
|
||||||
|
Message.error({
|
||||||
|
content: '执行失败',
|
||||||
|
duration: 5 * 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
Message.error({
|
Message.error({
|
||||||
content: '执行失败',
|
content: '表单不能为空',
|
||||||
duration: 5 * 1000,
|
duration: 5 * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@
|
|||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
/>
|
/>
|
||||||
<a-button type="text" @click="handleDeleteParams(index)"
|
<a-button type="text" @click="handleDeleteParams(index)"
|
||||||
><icon-minus-circle
|
><icon-minus-circle style="color: red;"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-form
|
<a-form
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:style="{ width: '800px' }"
|
ref="formRef"
|
||||||
|
:style="{ width: '1000px'}"
|
||||||
>
|
>
|
||||||
|
<a-form-item label="Qos等级" :rules="rules.qos">
|
||||||
|
<a-select
|
||||||
|
v-model="formData.qos"
|
||||||
|
placeholder="Qos等级"
|
||||||
|
:options="[
|
||||||
|
{ value: 0, label: '最多一次' },
|
||||||
|
{ value: 1, label: '至少一次' },
|
||||||
|
{ value: 2, label: '仅一次' },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
<template v-for="(field, index) in fields" :key="index">
|
<template v-for="(field, index) in fields" :key="index">
|
||||||
<a-form-item :label="field.name">
|
<a-form-item :label="field.name" :rules="rules[field.identifier]">
|
||||||
<a-input
|
<a-input
|
||||||
v-model="formData[field.identifier]"
|
v-model="formData[field.identifier]"
|
||||||
:placeholder="field.name"
|
:placeholder="field.name"
|
||||||
@ -26,13 +38,25 @@
|
|||||||
|
|
||||||
const formData = ref({});
|
const formData = ref({});
|
||||||
const emit = defineEmits(['update:formData']);
|
const emit = defineEmits(['update:formData']);
|
||||||
|
const formRef = ref(null); // 定义 formRef
|
||||||
|
|
||||||
|
// 定义校验规则
|
||||||
|
const rules = {
|
||||||
|
qos: [{ required: true, message: '请选择 Qos 等级', trigger: 'change' }],
|
||||||
|
...props.fields.reduce((acc, field) => {
|
||||||
|
acc[field.identifier] = [
|
||||||
|
{ required: true, message: `${field.name}不能为空`, trigger: 'blur' },
|
||||||
|
];
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
};
|
||||||
|
|
||||||
// 监听 formData 变化并触发事件
|
// 监听 formData 变化并触发事件
|
||||||
watch(formData, (newValue) => {
|
watch(formData, (newValue) => {
|
||||||
emit('update:formData', newValue);
|
emit('update:formData', newValue);
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* ... existing code ... */
|
/* ... existing code ... */
|
||||||
</style>
|
</style>
|
||||||
|
@ -140,7 +140,7 @@
|
|||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
/>
|
/>
|
||||||
<a-button type="text" @click="handleDeleteParams(index)"
|
<a-button type="text" @click="handleDeleteParams(index)"
|
||||||
><icon-minus-circle
|
><icon-minus-circle style="color: red;"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
<a-layout>
|
<a-layout>
|
||||||
<a-layout-sider :resize-directions="['right']">
|
<a-layout-sider :resize-directions="['right']">
|
||||||
<a-row>
|
<a-row>
|
||||||
|
<a-col :span="24" v-for="device in deviceList" :key="device.id" @click="selectDevice(device)">
|
||||||
|
{{ device.name }}
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
@ -11,13 +14,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import AMapLoader from '@amap/amap-jsapi-loader';
|
import AMapLoader from '@amap/amap-jsapi-loader';
|
||||||
import { DeviceRecord, queryDeviceList } from '@/api/device';
|
import { DeviceRecord, queryDeviceList } from '@/api/device';
|
||||||
import "@amap/amap-jsapi-types";
|
import "@amap/amap-jsapi-types";
|
||||||
|
import router from '@/router';
|
||||||
|
|
||||||
let map: any = null;
|
let map: any = null;
|
||||||
const deviceList = ref<DeviceRecord[]>([]);
|
const deviceList = ref<DeviceRecord[]>([]);
|
||||||
|
let selectDevice = (device: DeviceRecord) => {
|
||||||
|
console.log(device);
|
||||||
|
}
|
||||||
|
|
||||||
AMapLoader.load({
|
AMapLoader.load({
|
||||||
key: 'a4e80eed798a56451b226dcfca81b846', // 申请好的Web端开发者Key,首次调用 load 时必填
|
key: 'a4e80eed798a56451b226dcfca81b846', // 申请好的Web端开发者Key,首次调用 load 时必填
|
||||||
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
|
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
|
||||||
@ -30,32 +38,71 @@
|
|||||||
zoom: 11, // 初始化地图级别
|
zoom: 11, // 初始化地图级别
|
||||||
center: [116.397428, 39.90923], // 初始化地图中心点位置
|
center: [116.397428, 39.90923], // 初始化地图中心点位置
|
||||||
});
|
});
|
||||||
map.on('click', (e: any) => {
|
|
||||||
// 获取点击位置的经纬度
|
selectDevice = (device: DeviceRecord) => {
|
||||||
const lngLat = e.lnglat;
|
if (device.longitude && device.latitude) {
|
||||||
const content = document.createElement('div');
|
const lngLat = [device.longitude, device.latitude];
|
||||||
content.className = 'custom-content-marker';
|
const content = document.createElement('div');
|
||||||
content.innerHTML = `
|
content.className = 'custom-content-marker';
|
||||||
<img src="https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png" alt="标记点" />
|
content.innerHTML = `
|
||||||
<div class="close-btn">×</div>`;
|
<img src="https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png" alt="标记点" />`;
|
||||||
// 创建标记点
|
|
||||||
const marker = new AMap.Marker({
|
// 创建标记点
|
||||||
position: lngLat,
|
const marker = new AMap.Marker({
|
||||||
content,
|
position: lngLat,
|
||||||
});
|
content,
|
||||||
// 将标记点添加到地图上
|
});
|
||||||
map.add(marker);
|
|
||||||
// 监听删除按钮的点击事件
|
// 将标记点添加到地图上
|
||||||
const closeBtn = marker.getContent().querySelector('.close-btn');
|
map.setCenter(lngLat);
|
||||||
closeBtn.addEventListener('click', () => {
|
map.setZoom(16);
|
||||||
map.remove(marker); // 移除标记点
|
map.add(marker);
|
||||||
});
|
|
||||||
});
|
// const infoContent = `
|
||||||
|
// <div style="width: 220px; padding: 10px; background: #fff; border-radius: 4px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);">
|
||||||
|
// <h4 style="margin: 0 0 10px; font-size: 16px; color: #333;">设备信息</h4>
|
||||||
|
// <p style="margin: 0; font-size: 14px; color: #555;"><strong>设备名称:</strong> ${device.name}</p>
|
||||||
|
// <p style="margin: 0; font-size: 14px; color: #555;"><strong>硬件版本:</strong> ${device.hardwareVersion}</p>
|
||||||
|
// <p style="margin: 0; font-size: 14px; color: #555;"><strong>固件版本:</strong> ${device.firmwareVersion}</p>
|
||||||
|
// <p style="margin: 0; font-size: 14px; color: #555;"><strong>所属产品:</strong> ${device.productId}</p>
|
||||||
|
// </div>
|
||||||
|
// `;
|
||||||
|
const infoContent = [
|
||||||
|
`<br/><div style="width: 300px;margin-left: 20px;padding-bottom: 10px"><b>设备名称: ${device.name}</b>`,
|
||||||
|
`<span style="font-size: 16px; color: #333;">硬件版本: ${device.hardwareVersion}</span>`,
|
||||||
|
`<span style="font-size: 16px; color: #333;">固件版本: ${device.firmwareVersion}</span>`,
|
||||||
|
`<span style="font-size: 16px; color: #333;">所属产品: ${device.productName}</span>`,
|
||||||
|
`<a-button style="margin-left: 200px;color: #0960bd" onclick="handleViewDetail('${device.id}')">查看详情
|
||||||
|
</a-button></div>`
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleViewDetail = (deviceId: number) => {
|
||||||
|
router.push({ name: 'deviceDetail', params: { id: deviceId } });
|
||||||
|
};
|
||||||
|
window.handleViewDetail = handleViewDetail;
|
||||||
|
|
||||||
|
|
||||||
|
// 初始化信息窗体
|
||||||
|
const infoWindow = new AMap.InfoWindow({
|
||||||
|
content: infoContent.join('<br>'),
|
||||||
|
offset: new AMap.Pixel(10, 10), // 调整信息窗体的偏移量
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绑定标记点点击事件
|
||||||
|
marker.on('click', () => {
|
||||||
|
infoWindow.open(map, lngLat); // 打开信息窗体
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('设备缺少经纬度信息', device);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 将依赖于 AMap 的代码放在 then 回调中
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取设备列表
|
// 获取设备列表
|
||||||
queryDeviceList({
|
queryDeviceList({
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
/>
|
/>
|
||||||
<a-button type="text" @click="handleDeleteParams(index)"
|
<a-button type="text" @click="handleDeleteParams(index)"
|
||||||
><icon-minus-circle
|
><icon-minus-circle style="color: red;"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
@ -235,7 +235,7 @@
|
|||||||
<a-button
|
<a-button
|
||||||
type="text"
|
type="text"
|
||||||
@click="handleDeleteParams(index, 'serveInputData')"
|
@click="handleDeleteParams(index, 'serveInputData')"
|
||||||
><icon-minus-circle
|
><icon-minus-circle style="color: red;"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
@ -280,7 +280,7 @@
|
|||||||
<a-button
|
<a-button
|
||||||
type="text"
|
type="text"
|
||||||
@click="handleDeleteParams(index, 'serveOutputData')"
|
@click="handleDeleteParams(index, 'serveOutputData')"
|
||||||
><icon-minus-circle
|
><icon-minus-circle style="color: red;"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
@ -385,7 +385,7 @@
|
|||||||
<a-button
|
<a-button
|
||||||
type="text"
|
type="text"
|
||||||
@click="handleDeleteParams(index, 'eventOutputData')"
|
@click="handleDeleteParams(index, 'eventOutputData')"
|
||||||
><icon-minus-circle
|
><icon-minus-circle style="color: red;"
|
||||||
/></a-button>
|
/></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user