feat: 点位管理和机器人
This commit is contained in:
parent
4e8998216f
commit
d198189d2c
33
src/api/inspection/map.js
Normal file
33
src/api/inspection/map.js
Normal file
@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
|
||||
export function getMapList(params) {
|
||||
return request({
|
||||
url: '/inspection/map/list',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addMap(data) {
|
||||
return request({
|
||||
url: '/inspection/map',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateMap(data) {
|
||||
return request({
|
||||
url: '/inspection/map',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteMap(id) {
|
||||
return request({
|
||||
url: `/inspection/map/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
32
src/api/inspection/robot.js
Normal file
32
src/api/inspection/robot.js
Normal file
@ -0,0 +1,32 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getRobotList(params) {
|
||||
return request({
|
||||
url: '/inspection/robot/list',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addRobot(data) {
|
||||
return request({
|
||||
url: '/inspection/robot',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRobot(data) {
|
||||
return request({
|
||||
url: '/inspection/robot',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRobot(id) {
|
||||
return request({
|
||||
url: `/inspection/robot/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@ -88,7 +88,7 @@ app.use(ElementPlus, {
|
||||
|
||||
app.use(WebSocketPlugin, {
|
||||
// url: import.meta.env.VITE_WS_URL,
|
||||
url: 'ws://192.168.0.100:13080/ws',
|
||||
url: 'ws://192.168.0.114:13080/ws',
|
||||
// url: 'ws://10.148.209.5:13080/ws',
|
||||
userId: 'your_user_id'
|
||||
});
|
||||
|
||||
@ -9,7 +9,7 @@ export const useInspectionStore = defineStore('inspection', {
|
||||
camera: { // 相机参数
|
||||
centerX: 0, // 相机中心的世界坐标 X
|
||||
centerY: 0, // 相机中心的世界坐标 Y
|
||||
pixelsPerMeter: 40, // 缩放:每米对应的屏幕像素数
|
||||
pixelsPerMeter: 20, // 缩放:每米对应的屏幕像素数
|
||||
},
|
||||
canvasWidthPx: 0, // Canvas CSS 逻辑宽度(像素)
|
||||
canvasHeightPx: 0, // Canvas CSS 逻辑高度(像素)
|
||||
|
||||
@ -153,7 +153,7 @@ async function loadMapIntoApplication(parsedMap) {
|
||||
const worldHeightMeters = parsedMap.maxY - parsedMap.originY; // 地图世界高度
|
||||
const fitScaleX = inspectionStore.canvasWidthPx / worldWidthMeters; // 水平方向恰好填满的缩放
|
||||
const fitScaleY = inspectionStore.canvasHeightPx / worldHeightMeters; // 垂直方向恰好填满的缩放
|
||||
inspectionStore.camera.pixelsPerMeter = Math.max(1, Math.min(fitScaleX, fitScaleY) * 0.88); // 取较小值并留12%边距
|
||||
inspectionStore.camera.pixelsPerMeter = Math.max(1, Math.min(fitScaleX, fitScaleY) * 1); // 取较小值
|
||||
|
||||
// 标记加载完成并更新UI
|
||||
applicationState.isMapLoaded = true;
|
||||
|
||||
@ -1,3 +1,307 @@
|
||||
<template>
|
||||
<div>流程管理</div>
|
||||
<div class="app-container">
|
||||
<div ref="topContainerRef">
|
||||
<TableSearch
|
||||
:queryParams="queryParams"
|
||||
:showSearch="showSearch"
|
||||
label-width="90px"
|
||||
queryRef="queryRef"
|
||||
@refresh="resetQuery"
|
||||
@search="handleQuery"
|
||||
>
|
||||
<template #one>
|
||||
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" :xxl="6">
|
||||
<el-form-item label="点位名称" prop="waypointName">
|
||||
<el-input
|
||||
v-model="queryParams.waypointName"
|
||||
clearable
|
||||
placeholder="请输入点位名称"
|
||||
style="width: 100%"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" :xxl="6">
|
||||
<el-form-item label="点位描述" prop="remark">
|
||||
<el-input
|
||||
v-model="queryParams.remark"
|
||||
clearable
|
||||
placeholder="请输入点位描述"
|
||||
style="width: 100%"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
</TableSearch>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPermi="['test:detect:add']"
|
||||
icon="Plus"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
>新增
|
||||
</el-button>
|
||||
</el-col>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
@queryTable="getList"
|
||||
></right-toolbar>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div :style="containerHeight">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
height="100%"
|
||||
:data="tableDataList"
|
||||
>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="id"
|
||||
prop="id"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="点位名称"
|
||||
prop="waypointName"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="点位描述"
|
||||
prop="remark"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column align="center" label="启用状态" prop="enabled">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.enabled"
|
||||
active-text="启用"
|
||||
active-value="0"
|
||||
inactive-text="禁用"
|
||||
inactive-value="1"
|
||||
inline-prompt
|
||||
@change="handleChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="配置状态" prop="isDeploy">
|
||||
<template #default="scope">
|
||||
<TableTags
|
||||
:options="[
|
||||
{
|
||||
label: '已配置',
|
||||
value: '0',
|
||||
type: 'success',
|
||||
},
|
||||
{
|
||||
label: '未配置',
|
||||
value: '1',
|
||||
type: 'warning',
|
||||
},
|
||||
]"
|
||||
:value="scope.row.isDeploy"
|
||||
></TableTags>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="创建人"
|
||||
prop="createBy"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
align="center"
|
||||
class-name="small-padding fixed-width"
|
||||
label="操作"
|
||||
width="200"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="Edit"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Cpu"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDeploy(scope.row)"
|
||||
>配置
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="Delete"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNum"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 添加或修改检测项配置对话框 -->
|
||||
<el-dialog v-model="open" :title="title" append-to-body width="500px">
|
||||
<el-form ref="detectRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="点位名称" prop="waypointName">
|
||||
<el-input v-model="form.waypointName" placeholder="请输入点位名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点位描述" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入点位描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
:rows="2"
|
||||
placeholder="请输入备注"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="参数配置" width="1080px">
|
||||
<EditTable></EditTable>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script name="Detect" setup>
|
||||
import {
|
||||
addDetect,
|
||||
delDetect,
|
||||
getDetect,
|
||||
listDetect,
|
||||
updateDetect,
|
||||
} from "@/api/test/detect";
|
||||
import TableSearch from "@/components/TableSearch/index.vue";
|
||||
import { listGroup } from "@/api/system/group.js";
|
||||
import { appendParamsToPath } from "@/utils/fn.js";
|
||||
import TableTags from "@/components/TableTags/index.vue";
|
||||
import EditTable from "@/components/EditTable/index.vue";
|
||||
|
||||
import { useContainerHeight } from "@/hooks/tableHeight";
|
||||
|
||||
const topContainerRef = ref();
|
||||
const containerHeight = useContainerHeight(topContainerRef);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const tableDataList = ref([]);
|
||||
const open = ref(false);
|
||||
const dialogVisible = ref(false);
|
||||
const loading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
|
||||
const total = ref(0);
|
||||
const title = ref("");
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
waypointName: null,
|
||||
remark: null,
|
||||
},
|
||||
rules: {
|
||||
waypointName: [
|
||||
{ required: true, message: "点位名称不能为空", trigger: "blur" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询检测项配置列表 */
|
||||
function getList() {
|
||||
// loading.value = true;
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
function cancel() {
|
||||
open.value = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
function reset() {
|
||||
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset();
|
||||
open.value = true;
|
||||
title.value = "添加检测项配置";
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
|
||||
}
|
||||
|
||||
/** 配置按钮操作 */
|
||||
function handleDeploy(row) {
|
||||
const params = { itemId: row.id, name: row.waypointName, state: row.status };
|
||||
const fullPath = appendParamsToPath("/flow", params);
|
||||
const p = window.open(fullPath, "_blank");
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div ref="topContainerRef">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="地图名称" prop="corpusName">
|
||||
<el-input v-model="queryParams.corpusName" placeholder="请输入地图名称" clearable @keyup.enter="handleQuery" />
|
||||
<el-form :model="queryParams" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="地图名称" prop="mapName">
|
||||
<el-input v-model="queryParams.mapName" placeholder="请输入地图名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地图描述" prop="voiceType">
|
||||
<el-input v-model="queryParams.voiceType" placeholder="请输入地图描述" clearable @keyup.enter="handleQuery" />
|
||||
<el-form-item label="地图描述" prop="mapDesc">
|
||||
<el-input v-model="queryParams.mapDesc" placeholder="请输入地图描述" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
@ -24,10 +24,10 @@
|
||||
|
||||
<div :style="containerHeight">
|
||||
<el-table v-loading="loading" height="100%" style="width: 100%" :data="tableDataList">
|
||||
<el-table-column label="地图名称" align="center" prop="corpusName" />
|
||||
<el-table-column label="地图描述" align="center" prop="textContent" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="创建人" align="center" prop="status" />
|
||||
<el-table-column label="创建时间" align="center" prop="status" />
|
||||
<el-table-column label="地图名称" align="center" prop="mapName" />
|
||||
<el-table-column label="地图描述" align="center" prop="mapDesc" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="创建人" align="center" prop="createBy" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" />
|
||||
<el-table-column label="操作" class-name="small-padding fixed-width" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
@ -42,19 +42,19 @@
|
||||
|
||||
<!-- 添加或修改语料库对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="580px" append-to-body>
|
||||
<el-form ref="corpusRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="地图名称" prop="corpusName">
|
||||
<el-input v-model="form.corpusName" placeholder="请输入地图名称" />
|
||||
<el-form ref="mapRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="地图名称" prop="mapName">
|
||||
<el-input v-model="form.mapName" placeholder="请输入地图名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地图描述" prop="textContent">
|
||||
<el-input v-model="form.textContent" type="textarea" placeholder="请输入地图描述" />
|
||||
<el-form-item label="地图描述" prop="mapDesc">
|
||||
<el-input v-model="form.mapDesc" type="textarea" placeholder="请输入地图描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择机器人地图" prop="dialect">
|
||||
<el-form-item label="选择机器人地图">
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="date1">
|
||||
<el-select v-model="form.robotId" placeholder="请选择机器人" clearable style="width: 100%">
|
||||
<el-option label="机器人1" value="zh"></el-option>
|
||||
<el-option label="机器人2" value="en"></el-option>
|
||||
<el-form-item prop="mapSourceRobotId">
|
||||
<el-select v-model="form.mapSourceRobotId" placeholder="请选择机器人" clearable style="width: 100%"
|
||||
@change="robotChange">
|
||||
<el-option v-for="robot in robotList" :key="robot.value" :label="robot.label" :value="robot.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -62,10 +62,10 @@
|
||||
<span class="text-gray-500">-</span>
|
||||
</el-col>
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="date2">
|
||||
<el-select v-model="form.mapId" placeholder="请选择地图" clearable style="width: 100%" @change="mapChange">
|
||||
<el-option label="地图1" value="zh"></el-option>
|
||||
<el-option label="地图2" value="en"></el-option>
|
||||
<el-form-item prop="mapSourceName">
|
||||
<el-select v-model="form.mapSourceName" placeholder="请选择地图" clearable style="width: 100%"
|
||||
@change="mapChange">
|
||||
<el-option v-for="map in mapList" :key="map.value" :label="map.label" :value="map.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -84,30 +84,35 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Corpus">
|
||||
<script setup name="Map">
|
||||
|
||||
import { useContainerHeight } from "@/hooks/tableHeight";
|
||||
|
||||
import { addMap, updateMap, deleteMap, getMapList } from "../../../api/inspection/map";
|
||||
import { getRobotList } from "../../../api/inspection/robot";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import {
|
||||
worldToScreen,
|
||||
screenToWorld,
|
||||
drawGrid,
|
||||
drawGridlines,
|
||||
drawAdvancedAreaList,
|
||||
drawAdvancedLineList,
|
||||
drawNormalLines,
|
||||
drawAdvancedCurveList,
|
||||
drawAdvancedPointList,
|
||||
drawMapBoundary,
|
||||
buildOccupancyGridImage,
|
||||
MobileRobot
|
||||
worldToScreen,
|
||||
screenToWorld,
|
||||
drawGrid,
|
||||
drawGridlines,
|
||||
drawAdvancedAreaList,
|
||||
drawAdvancedLineList,
|
||||
drawNormalLines,
|
||||
drawAdvancedCurveList,
|
||||
drawAdvancedPointList,
|
||||
drawMapBoundary,
|
||||
buildOccupancyGridImage,
|
||||
MobileRobot
|
||||
} from '../cockpit/canvasUtils';
|
||||
import { de } from "element-plus/es/locale/index.mjs";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
|
||||
const topContainerRef = ref();
|
||||
const containerHeight = useContainerHeight(topContainerRef);
|
||||
|
||||
const mapRef = ref();
|
||||
const isNew = ref(true);
|
||||
|
||||
const tableDataList = ref([]);
|
||||
const open = ref(false);
|
||||
@ -119,21 +124,24 @@ const title = ref("");
|
||||
|
||||
const data = reactive({
|
||||
form: {
|
||||
type: "WAKEUP",
|
||||
mapName: "",
|
||||
mapDesc: "",
|
||||
mapSourceRobotId: "",
|
||||
mapSourceName: "",
|
||||
},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
mapDesc: null,
|
||||
mapName: null,
|
||||
},
|
||||
rules: {
|
||||
corpusName: [
|
||||
{ required: true, message: "请输入语料名称", trigger: "change" },
|
||||
mapName: [
|
||||
{ required: true, message: "请输入地图名称", trigger: "change" },
|
||||
],
|
||||
textContent: [
|
||||
{ required: true, message: "请输入语音文本", trigger: "blur" },
|
||||
],
|
||||
voiceType: [{ required: true, message: "请选择音色", trigger: "blur" }],
|
||||
dialect: [{ required: true, message: "请选择语种", trigger: "blur" }],
|
||||
mapDesc: [
|
||||
{ required: true, message: "请输入地图描述", trigger: "blur" },
|
||||
]
|
||||
},
|
||||
});
|
||||
|
||||
@ -142,11 +150,15 @@ const { queryParams, form, rules } = toRefs(data);
|
||||
/** 查询列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
|
||||
tableDataList.value = [];
|
||||
total.value = 0;
|
||||
loading.value = false;
|
||||
|
||||
getMapList(queryParams.value).then((response) => {
|
||||
tableDataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
}).catch(() => {
|
||||
loading.value = false;
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
@ -154,8 +166,8 @@ function cancel() {
|
||||
open.value = false;
|
||||
reset();
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
animationId = null;
|
||||
cancelAnimationFrame(animationId);
|
||||
animationId = null;
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', resizeCanvasToContainer)
|
||||
@ -164,9 +176,13 @@ function cancel() {
|
||||
|
||||
// 表单重置
|
||||
function reset() {
|
||||
mapRef.value.resetFields();
|
||||
form.value = {
|
||||
};
|
||||
|
||||
mapName: "",
|
||||
mapDesc: "",
|
||||
mapSourceRobotId: "",
|
||||
mapSourceName: "",
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@ -177,14 +193,16 @@ function handleQuery() {
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
queryParams.value.mapName = null;
|
||||
queryParams.value.mapDesc = null;
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset();
|
||||
open.value = true;
|
||||
isNew.value = true;
|
||||
title.value = "添加地图";
|
||||
setTimeout(() => {
|
||||
init();
|
||||
@ -193,98 +211,141 @@ function handleAdd() {
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
|
||||
form.value = { ...row };
|
||||
isNew.value = false;
|
||||
open.value = true;
|
||||
title.value = "修改地图";
|
||||
setTimeout(() => {
|
||||
init();
|
||||
if (form.value.mapSourceName) {
|
||||
mapChange()
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
|
||||
mapRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isNew.value) {
|
||||
// 调用新增接口
|
||||
addMap(form.value).then(() => {
|
||||
ElMessage.success("地图添加成功!");
|
||||
getList();
|
||||
reset();
|
||||
open.value = false;
|
||||
});
|
||||
ElMessage.success("地图添加成功!");
|
||||
getList();
|
||||
} else {
|
||||
// 调用修改接口
|
||||
updateMap(form.value).then(() => {
|
||||
ElMessage.success("地图修改成功!");
|
||||
getList();
|
||||
reset();
|
||||
open.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
ElMessageBox.confirm('确定要删除该地图吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
deleteMap(row.id).then(() => {
|
||||
ElMessage.success("地图删除成功!");
|
||||
getList();
|
||||
});
|
||||
}).catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const loadSourceMap = async (url) => {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`加载失败:${response.status}`);
|
||||
return response.text();
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`加载失败:${response.status}`);
|
||||
return response.text();
|
||||
};
|
||||
|
||||
function parseSmapJson(rawJson) {
|
||||
// ── 解析 header ──
|
||||
const header = rawJson.header || rawJson; // 兼容 header 嵌套或平铺
|
||||
const minimumPos = header.minPos || { x: 0, y: 0 }; // 地图左下角(世界坐标)
|
||||
const maximumPos = header.maxPos || { x: 1, y: 1 }; // 地图右上角(世界坐标)
|
||||
const resolution = header.resolution || 0.05; // 每像素代表的世界长度(米)
|
||||
// ── 解析 header ──
|
||||
const header = rawJson.header || rawJson; // 兼容 header 嵌套或平铺
|
||||
const minimumPos = header.minPos || { x: 0, y: 0 }; // 地图左下角(世界坐标)
|
||||
const maximumPos = header.maxPos || { x: 1, y: 1 }; // 地图右上角(世界坐标)
|
||||
const resolution = header.resolution || 0.05; // 每像素代表的世界长度(米)
|
||||
|
||||
// ── 计算栅格尺寸 ──
|
||||
const worldWidth = maximumPos.x - minimumPos.x; // 地图世界宽度(米)
|
||||
const worldHeight = maximumPos.y - minimumPos.y; // 地图世界高度(米)
|
||||
const gridWidthPx = Math.ceil(worldWidth / resolution) + 1; // 栅格像素宽度
|
||||
const gridHeightPx = Math.ceil(worldHeight / resolution) + 1;// 栅格像素高度
|
||||
// ── 计算栅格尺寸 ──
|
||||
const worldWidth = maximumPos.x - minimumPos.x; // 地图世界宽度(米)
|
||||
const worldHeight = maximumPos.y - minimumPos.y; // 地图世界高度(米)
|
||||
const gridWidthPx = Math.ceil(worldWidth / resolution) + 1; // 栅格像素宽度
|
||||
const gridHeightPx = Math.ceil(worldHeight / resolution) + 1;// 栅格像素高度
|
||||
|
||||
// ── 构建统一数据结构 ──
|
||||
const parsedMap = {
|
||||
name: header.mapName || 'Untitled', // 地图名称
|
||||
version: header.version || '', // 版本号
|
||||
mapType: header.mapType || '', // 地图类型(如 "2D-Map")
|
||||
resolution: resolution, // 分辨率(米/像素)
|
||||
originX: minimumPos.x, // 地图左下角 X
|
||||
originY: minimumPos.y, // 地图左下角 Y
|
||||
maxX: maximumPos.x, // 地图右上角 X
|
||||
maxY: maximumPos.y, // 地图右上角 Y
|
||||
gridWidth: gridWidthPx, // 栅格宽度(像素)
|
||||
gridHeight: gridHeightPx, // 栅格高度(像素)
|
||||
// normalPosList: 自由空间点,直接拷贝
|
||||
normalPositions: rawJson.normalPosList || [],
|
||||
// normalLineList: 直接拷贝,每项含 {startPos:{x,y}, endPos:{x,y}}
|
||||
normalLines: rawJson.normalLineList || [],
|
||||
// advancedPointList: 直接拷贝,每项含 {className, instanceName, pos:{x,y}, dir, desc, property:[]}
|
||||
advancedPoints: rawJson.advancedPointList || [],
|
||||
// advancedLineList: 直接拷贝,每项含 {className, instanceName, line:{startPos, endPos}}
|
||||
advancedLines: rawJson.advancedLineList || [],
|
||||
// advancedCurveList: 直接拷贝,每项含 {className, instanceName, controlPos1, controlPos2, startPos, endPos}
|
||||
advancedCurves: rawJson.advancedCurveList || [],
|
||||
// advancedAreaList: 直接拷贝,每项含 {className, instanceName, dir, posGroup:[], attribute:{}}
|
||||
advancedAreas: rawJson.advancedAreaList || [],
|
||||
};
|
||||
// ── 构建统一数据结构 ──
|
||||
const parsedMap = {
|
||||
name: header.mapName || 'Untitled', // 地图名称
|
||||
version: header.version || '', // 版本号
|
||||
mapType: header.mapType || '', // 地图类型(如 "2D-Map")
|
||||
resolution: resolution, // 分辨率(米/像素)
|
||||
originX: minimumPos.x, // 地图左下角 X
|
||||
originY: minimumPos.y, // 地图左下角 Y
|
||||
maxX: maximumPos.x, // 地图右上角 X
|
||||
maxY: maximumPos.y, // 地图右上角 Y
|
||||
gridWidth: gridWidthPx, // 栅格宽度(像素)
|
||||
gridHeight: gridHeightPx, // 栅格高度(像素)
|
||||
// normalPosList: 自由空间点,直接拷贝
|
||||
normalPositions: rawJson.normalPosList || [],
|
||||
// normalLineList: 直接拷贝,每项含 {startPos:{x,y}, endPos:{x,y}}
|
||||
normalLines: rawJson.normalLineList || [],
|
||||
// advancedPointList: 直接拷贝,每项含 {className, instanceName, pos:{x,y}, dir, desc, property:[]}
|
||||
advancedPoints: rawJson.advancedPointList || [],
|
||||
// advancedLineList: 直接拷贝,每项含 {className, instanceName, line:{startPos, endPos}}
|
||||
advancedLines: rawJson.advancedLineList || [],
|
||||
// advancedCurveList: 直接拷贝,每项含 {className, instanceName, controlPos1, controlPos2, startPos, endPos}
|
||||
advancedCurves: rawJson.advancedCurveList || [],
|
||||
// advancedAreaList: 直接拷贝,每项含 {className, instanceName, dir, posGroup:[], attribute:{}}
|
||||
advancedAreas: rawJson.advancedAreaList || [],
|
||||
};
|
||||
|
||||
return parsedMap;
|
||||
return parsedMap;
|
||||
}
|
||||
|
||||
const applicationState = {
|
||||
parsedMap: null, // 解析后的完整地图数据
|
||||
gridOffscreen: null, // 栅格图像(离屏Canvas)
|
||||
hoveredElement: null, // 当前悬停的元素 {kind, index}
|
||||
animationTimeSeconds: 0, // 全局动画时间戳(秒),用于脉冲动画效果
|
||||
camera: { // 相机参数
|
||||
centerX: 0, // 相机中心的世界坐标 X
|
||||
centerY: 0, // 相机中心的世界坐标 Y
|
||||
pixelsPerMeter: 40, // 缩放:每米对应的屏幕像素数
|
||||
},
|
||||
canvasWidthPx: 0, // Canvas CSS 逻辑宽度(像素)
|
||||
canvasHeightPx: 0, // Canvas CSS 逻辑高度(像素)
|
||||
devicePixelRatio: 1, // 设备像素比(高DPI屏幕 > 1)
|
||||
mouseState: { // 鼠标交互状态
|
||||
screenX: 0, // 鼠标在 Canvas 上的像素 X
|
||||
screenY: 0, // 鼠标在 Canvas 上的像素 Y
|
||||
worldX: 0, // 鼠标对应的世界坐标 X
|
||||
worldY: 0, // 鼠标对应的世界坐标 Y
|
||||
isDragging: false, // 是否正在拖拽平移
|
||||
},
|
||||
isMapLoaded: false, // 地图加载完成标志,
|
||||
layerVisibility: { // 各图层是否可见
|
||||
grid: true, // 占据栅格底图
|
||||
edges: true, // 墙壁边缘发光
|
||||
gridlines: true, // 1米网格辅助线
|
||||
normalLines: true, // normalLineList
|
||||
advLines: true, // advancedLineList
|
||||
curves: true, // advancedCurveList
|
||||
points: true, // advancedPointList
|
||||
areas: true, // advancedAreaList
|
||||
}
|
||||
parsedMap: null, // 解析后的完整地图数据
|
||||
gridOffscreen: null, // 栅格图像(离屏Canvas)
|
||||
hoveredElement: null, // 当前悬停的元素 {kind, index}
|
||||
animationTimeSeconds: 0, // 全局动画时间戳(秒),用于脉冲动画效果
|
||||
camera: { // 相机参数
|
||||
centerX: 0, // 相机中心的世界坐标 X
|
||||
centerY: 0, // 相机中心的世界坐标 Y
|
||||
pixelsPerMeter: 20, // 缩放:每米对应的屏幕像素数
|
||||
},
|
||||
canvasWidthPx: 0, // Canvas CSS 逻辑宽度(像素)
|
||||
canvasHeightPx: 0, // Canvas CSS 逻辑高度(像素)
|
||||
devicePixelRatio: 1, // 设备像素比(高DPI屏幕 > 1)
|
||||
mouseState: { // 鼠标交互状态
|
||||
screenX: 0, // 鼠标在 Canvas 上的像素 X
|
||||
screenY: 0, // 鼠标在 Canvas 上的像素 Y
|
||||
worldX: 0, // 鼠标对应的世界坐标 X
|
||||
worldY: 0, // 鼠标对应的世界坐标 Y
|
||||
isDragging: false, // 是否正在拖拽平移
|
||||
},
|
||||
isMapLoaded: false, // 地图加载完成标志,
|
||||
layerVisibility: { // 各图层是否可见
|
||||
grid: true, // 占据栅格底图
|
||||
edges: true, // 墙壁边缘发光
|
||||
gridlines: true, // 1米网格辅助线
|
||||
normalLines: true, // normalLineList
|
||||
advLines: true, // advancedLineList
|
||||
curves: true, // advancedCurveList
|
||||
points: true, // advancedPointList
|
||||
areas: true, // advancedAreaList
|
||||
}
|
||||
};
|
||||
|
||||
let mapCanvas; // 主渲染画布 DOM 元素
|
||||
@ -300,179 +361,214 @@ const init = () => {
|
||||
* 在窗口 resize 时自动调用
|
||||
*/
|
||||
function resizeCanvasToContainer() {
|
||||
const containerRect = mapCanvas.parentElement.getBoundingClientRect(); // 获取容器实际尺寸
|
||||
console.log('Container size:', containerRect.width, 'x', containerRect.height);
|
||||
devicePixelRatio = window.devicePixelRatio || 1; // 获取设备像素比
|
||||
applicationState.canvasWidthPx = containerRect.width; // 更新逻辑宽度
|
||||
applicationState.canvasHeightPx = containerRect.height; // 逻辑高度 = 容器高度 - 状态栏高度
|
||||
mapCanvas.width = applicationState.canvasWidthPx * devicePixelRatio; // Canvas 物理像素宽
|
||||
mapCanvas.height = applicationState.canvasHeightPx * devicePixelRatio; // Canvas 物理像素高
|
||||
mapCanvas.style.width = applicationState.canvasWidthPx + 'px'; // CSS 显示宽度
|
||||
mapCanvas.style.height = applicationState.canvasHeightPx + 'px'; // CSS 显示高度
|
||||
const containerRect = mapCanvas.parentElement.getBoundingClientRect(); // 获取容器实际尺寸
|
||||
console.log('containerRect', containerRect)
|
||||
devicePixelRatio = window.devicePixelRatio || 1; // 获取设备像素比
|
||||
applicationState.canvasWidthPx = containerRect.width; // 更新逻辑宽度
|
||||
applicationState.canvasHeightPx = containerRect.height; // 逻辑高度 = 容器高度 - 状态栏高度
|
||||
mapCanvas.width = applicationState.canvasWidthPx * devicePixelRatio; // Canvas 物理像素宽
|
||||
mapCanvas.height = applicationState.canvasHeightPx * devicePixelRatio; // Canvas 物理像素高
|
||||
mapCanvas.style.width = applicationState.canvasWidthPx + 'px'; // CSS 显示宽度
|
||||
mapCanvas.style.height = applicationState.canvasHeightPx + 'px'; // CSS 显示高度
|
||||
}
|
||||
|
||||
async function loadMapIntoApplication(parsedMap) {
|
||||
await new Promise(resolve => setTimeout(resolve, 30)); // 短暂等待让 UI 有机会刷新
|
||||
await new Promise(resolve => setTimeout(resolve, 30)); // 短暂等待让 UI 有机会刷新
|
||||
|
||||
// 保存地图数据到全局状态
|
||||
applicationState.parsedMap = parsedMap;
|
||||
// 构建栅格图像(带边缘高亮)
|
||||
applicationState.gridOffscreen = buildOccupancyGridImage(parsedMap, applicationState.layerVisibility.edges);
|
||||
// 保存地图数据到全局状态
|
||||
applicationState.parsedMap = parsedMap;
|
||||
// 构建栅格图像(带边缘高亮)
|
||||
applicationState.gridOffscreen = buildOccupancyGridImage(parsedMap, applicationState.layerVisibility.edges);
|
||||
|
||||
// ── 设置相机初始位置(地图中心)──
|
||||
applicationState.camera.centerX = (parsedMap.originX + parsedMap.maxX) / 2; // 水平居中
|
||||
applicationState.camera.centerY = (parsedMap.originY + parsedMap.maxY) / 2; // 垂直居中
|
||||
// 计算合适的缩放级别,使地图完整显示在画布内
|
||||
const worldWidthMeters = parsedMap.maxX - parsedMap.originX; // 地图世界宽度
|
||||
const worldHeightMeters = parsedMap.maxY - parsedMap.originY; // 地图世界高度
|
||||
const fitScaleX = applicationState.canvasWidthPx / worldWidthMeters; // 水平方向恰好填满的缩放
|
||||
const fitScaleY = applicationState.canvasHeightPx / worldHeightMeters; // 垂直方向恰好填满的缩放
|
||||
applicationState.camera.pixelsPerMeter = Math.max(1, Math.min(fitScaleX, fitScaleY) * 0.88); // 取较小值并留12%边距
|
||||
// ── 设置相机初始位置(地图中心)──
|
||||
applicationState.camera.centerX = (parsedMap.originX + parsedMap.maxX) / 2; // 水平居中
|
||||
applicationState.camera.centerY = (parsedMap.originY + parsedMap.maxY) / 2; // 垂直居中
|
||||
// 计算合适的缩放级别,使地图完整显示在画布内
|
||||
const worldWidthMeters = parsedMap.maxX - parsedMap.originX; // 地图世界宽度
|
||||
const worldHeightMeters = parsedMap.maxY - parsedMap.originY; // 地图世界高度
|
||||
const fitScaleX = applicationState.canvasWidthPx / worldWidthMeters; // 水平方向恰好填满的缩放
|
||||
const fitScaleY = applicationState.canvasHeightPx / worldHeightMeters; // 垂直方向恰好填满的缩放
|
||||
applicationState.camera.pixelsPerMeter = Math.max(1, Math.min(fitScaleX, fitScaleY)); // 取较小值并留12%边距
|
||||
|
||||
// 标记加载完成并更新UI
|
||||
applicationState.isMapLoaded = true;
|
||||
// 标记加载完成并更新UI
|
||||
applicationState.isMapLoaded = true;
|
||||
}
|
||||
|
||||
const bindEvent = () => {
|
||||
// ── 鼠标移动:更新坐标、拖拽平移、悬停检测 ──
|
||||
mapCanvas.addEventListener('mousemove', (mouseEvent) => {
|
||||
const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置
|
||||
applicationState.mouseState.screenX = mouseEvent.clientX - canvasRect.left; // 鼠标在 Canvas 上的 X
|
||||
applicationState.mouseState.screenY = mouseEvent.clientY - canvasRect.top; // 鼠标在 Canvas 上的 Y
|
||||
// 反算世界坐标
|
||||
const worldPos = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState);
|
||||
applicationState.mouseState.worldX = worldPos.x;
|
||||
applicationState.mouseState.worldY = worldPos.y;
|
||||
// ── 鼠标移动:更新坐标、拖拽平移、悬停检测 ──
|
||||
mapCanvas.addEventListener('mousemove', (mouseEvent) => {
|
||||
const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置
|
||||
applicationState.mouseState.screenX = mouseEvent.clientX - canvasRect.left; // 鼠标在 Canvas 上的 X
|
||||
applicationState.mouseState.screenY = mouseEvent.clientY - canvasRect.top; // 鼠标在 Canvas 上的 Y
|
||||
// 反算世界坐标
|
||||
const worldPos = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState);
|
||||
applicationState.mouseState.worldX = worldPos.x;
|
||||
applicationState.mouseState.worldY = worldPos.y;
|
||||
|
||||
// 如果正在拖拽,平移相机
|
||||
if (applicationState.mouseState.isDragging) {
|
||||
applicationState.camera.centerX -= mouseEvent.movementX / applicationState.camera.pixelsPerMeter; // 水平平移
|
||||
applicationState.camera.centerY += mouseEvent.movementY / applicationState.camera.pixelsPerMeter; // 垂直平移(Y翻转)
|
||||
}
|
||||
});
|
||||
// 如果正在拖拽,平移相机
|
||||
if (applicationState.mouseState.isDragging) {
|
||||
applicationState.camera.centerX -= mouseEvent.movementX / applicationState.camera.pixelsPerMeter; // 水平平移
|
||||
applicationState.camera.centerY += mouseEvent.movementY / applicationState.camera.pixelsPerMeter; // 垂直平移(Y翻转)
|
||||
}
|
||||
});
|
||||
|
||||
// ── 鼠标按下:开始拖拽 ──
|
||||
mapCanvas.addEventListener('mousedown', () => {
|
||||
applicationState.mouseState.isDragging = true; // 标记拖拽开始
|
||||
});
|
||||
// ── 鼠标按下:开始拖拽 ──
|
||||
mapCanvas.addEventListener('mousedown', () => {
|
||||
applicationState.mouseState.isDragging = true; // 标记拖拽开始
|
||||
});
|
||||
|
||||
// ── 鼠标抬起:结束拖拽 ──
|
||||
mapCanvas.addEventListener('mouseup', () => {
|
||||
applicationState.mouseState.isDragging = false; // 标记拖拽结束
|
||||
});
|
||||
// ── 鼠标抬起:结束拖拽 ──
|
||||
mapCanvas.addEventListener('mouseup', () => {
|
||||
applicationState.mouseState.isDragging = false; // 标记拖拽结束
|
||||
});
|
||||
|
||||
// ── 鼠标离开画布:结束拖拽并隐藏 Tooltip ──
|
||||
mapCanvas.addEventListener('mouseleave', () => {
|
||||
applicationState.mouseState.isDragging = false;
|
||||
});
|
||||
// ── 鼠标离开画布:结束拖拽并隐藏 Tooltip ──
|
||||
mapCanvas.addEventListener('mouseleave', () => {
|
||||
applicationState.mouseState.isDragging = false;
|
||||
});
|
||||
|
||||
// ── 滚轮:缩放(向鼠标位置缩放)──
|
||||
mapCanvas.addEventListener('wheel', (wheelEvent) => {
|
||||
wheelEvent.preventDefault(); // 阻止页面滚动
|
||||
const zoomFactor = wheelEvent.deltaY > 0 ? 0.88 : 1.12; // 向下滚动=缩小,向上滚动=放大
|
||||
const mouseWorldBefore = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState); // 缩放前鼠标世界坐标
|
||||
applicationState.camera.pixelsPerMeter = Math.max(0.5, Math.min(2000, applicationState.camera.pixelsPerMeter * zoomFactor)); // 应用缩放并钳制范围
|
||||
const mouseWorldAfter = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState); // 缩放后鼠标世界坐标
|
||||
// 调整相机中心,使鼠标指向的世界坐标保持不变
|
||||
applicationState.camera.centerX += mouseWorldBefore.x - mouseWorldAfter.x;
|
||||
applicationState.camera.centerY += mouseWorldBefore.y - mouseWorldAfter.y;
|
||||
}, { passive: false }); // passive:false 允许 preventDefault
|
||||
// ── 滚轮:缩放(向鼠标位置缩放)──
|
||||
mapCanvas.addEventListener('wheel', (wheelEvent) => {
|
||||
wheelEvent.preventDefault(); // 阻止页面滚动
|
||||
const zoomFactor = wheelEvent.deltaY > 0 ? 0.88 : 1.12; // 向下滚动=缩小,向上滚动=放大
|
||||
const mouseWorldBefore = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState); // 缩放前鼠标世界坐标
|
||||
applicationState.camera.pixelsPerMeter = Math.max(0.5, Math.min(2000, applicationState.camera.pixelsPerMeter * zoomFactor)); // 应用缩放并钳制范围
|
||||
const mouseWorldAfter = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState); // 缩放后鼠标世界坐标
|
||||
// 调整相机中心,使鼠标指向的世界坐标保持不变
|
||||
applicationState.camera.centerX += mouseWorldBefore.x - mouseWorldAfter.x;
|
||||
applicationState.camera.centerY += mouseWorldBefore.y - mouseWorldAfter.y;
|
||||
}, { passive: false }); // passive:false 允许 preventDefault
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 smap JSON 文件
|
||||
*/
|
||||
const loadUserFile = async () => {
|
||||
try {
|
||||
const fileTextContent = await loadSourceMap('/3.smap');
|
||||
const rawJsonObject = JSON.parse(fileTextContent); // 解析 JSON
|
||||
const parsedMap = parseSmapJson(rawJsonObject); // 转换为内部结构
|
||||
await loadMapIntoApplication(parsedMap); // 加载到应用
|
||||
} catch (parseError) {
|
||||
alert('Parse error: ' + parseError.message); // 显示错误信息
|
||||
}
|
||||
try {
|
||||
const filemapDesc = await loadSourceMap('/3.smap');
|
||||
const rawJsonObject = JSON.parse(filemapDesc); // 解析 JSON
|
||||
const parsedMap = parseSmapJson(rawJsonObject); // 转换为内部结构
|
||||
await loadMapIntoApplication(parsedMap); // 加载到应用
|
||||
} catch (parseError) {
|
||||
alert('Parse error: ' + parseError.message); // 显示错误信息
|
||||
}
|
||||
}
|
||||
|
||||
// 声明一个变量保存 requestAnimationFrame 的 ID
|
||||
let animationId = null;
|
||||
|
||||
function renderFrame(timestamp) {
|
||||
console.log("渲染中")
|
||||
// 更新动画时间
|
||||
applicationState.animationTimeSeconds = (timestamp || 0) / 1000;
|
||||
// 更新动画时间
|
||||
applicationState.animationTimeSeconds = (timestamp || 0) / 1000;
|
||||
|
||||
// 设置 Canvas 变换矩阵(考虑设备像素比)
|
||||
canvasCtx.setTransform(applicationState.devicePixelRatio, 0, 0, applicationState.devicePixelRatio, 0, 0);
|
||||
// 设置 Canvas 变换矩阵(考虑设备像素比)
|
||||
canvasCtx.setTransform(applicationState.devicePixelRatio, 0, 0, applicationState.devicePixelRatio, 0, 0);
|
||||
canvasCtx.clearRect(0, 0, applicationState.canvasWidthPx, applicationState.canvasHeightPx); // 清空画布
|
||||
|
||||
// 如果地图未加载,跳过绘制
|
||||
if (!applicationState.isMapLoaded) {
|
||||
animationId = requestAnimationFrame(renderFrame); // 继续请求下一帧
|
||||
return;
|
||||
}
|
||||
|
||||
const map = applicationState.parsedMap; // 当前地图数据
|
||||
const layers = applicationState.layerVisibility; // 图层可见性
|
||||
|
||||
// 图层1: 占据栅格底图
|
||||
if (layers.grid && applicationState.gridOffscreen) {
|
||||
drawGrid(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层2: 网格辅助线(每1米一条)
|
||||
if (layers.gridlines) {
|
||||
drawGridlines(canvasCtx, applicationState)
|
||||
}
|
||||
|
||||
// 图层3: advancedAreaList(半透明多边形区域)
|
||||
if (layers.areas) {
|
||||
drawAdvancedAreaList(canvasCtx, map, applicationState)
|
||||
}
|
||||
|
||||
// 图层4: normalLineList(基础直线,青色 #00e5ff)
|
||||
if (layers.normalLines) {
|
||||
drawNormalLines(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层5: advancedLineList(高级线,如 ForbiddenLine)
|
||||
if (layers.advLines) {
|
||||
drawAdvancedLineList(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层6: advancedCurveList(贝塞尔曲线路径,酸绿色 #76ff03)
|
||||
if (layers.curves) {
|
||||
drawAdvancedCurveList(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层7: advancedPointList(高级点标记,菱形)
|
||||
if (layers.points) {
|
||||
drawAdvancedPointList(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
|
||||
// 地图边界虚线框
|
||||
drawMapBoundary(canvasCtx, map, applicationState);
|
||||
|
||||
// 请求下一帧渲染
|
||||
animationId = requestAnimationFrame(renderFrame);
|
||||
}
|
||||
|
||||
const robotList = ref([])
|
||||
const mapList = ref([])
|
||||
|
||||
const getRobotArr = () => {
|
||||
getRobotList({
|
||||
pageNum: 1,
|
||||
pageSize: 10000,
|
||||
}).then((response) => {
|
||||
robotList.value = response.rows.map(item => ({
|
||||
label: item.robotName,
|
||||
value: item.id
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const getMapArr = (robotId) => {
|
||||
// TODO: 这里可以根据 robotId 来过滤地图列表,目前接口没有提供这个功能,所以暂时获取全部地图
|
||||
mapList.value = [
|
||||
{ label: "地图1", value: "map1" },
|
||||
{ label: "地图2", value: "map2" },
|
||||
{ label: "地图3", value: "map3" },
|
||||
]
|
||||
}
|
||||
|
||||
const robotChange = (value) => {
|
||||
form.value.mapSourceRobotId = value; // 更新选中的机器人ID
|
||||
form.value.mapSourceName = ""; // 清空地图选择
|
||||
if (canvasCtx) {
|
||||
canvasCtx.clearRect(0, 0, applicationState.canvasWidthPx, applicationState.canvasHeightPx); // 清空画布
|
||||
}
|
||||
|
||||
// 如果地图未加载,跳过绘制
|
||||
if (!applicationState.isMapLoaded) {
|
||||
animationId = requestAnimationFrame(renderFrame); // 继续请求下一帧
|
||||
return;
|
||||
}
|
||||
|
||||
const map = applicationState.parsedMap; // 当前地图数据
|
||||
const layers = applicationState.layerVisibility; // 图层可见性
|
||||
|
||||
// 图层1: 占据栅格底图
|
||||
if (layers.grid && applicationState.gridOffscreen) {
|
||||
drawGrid(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层2: 网格辅助线(每1米一条)
|
||||
if (layers.gridlines) {
|
||||
drawGridlines(canvasCtx, applicationState)
|
||||
}
|
||||
|
||||
// 图层3: advancedAreaList(半透明多边形区域)
|
||||
if (layers.areas) {
|
||||
drawAdvancedAreaList(canvasCtx, map, applicationState)
|
||||
}
|
||||
|
||||
// 图层4: normalLineList(基础直线,青色 #00e5ff)
|
||||
if (layers.normalLines) {
|
||||
drawNormalLines(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层5: advancedLineList(高级线,如 ForbiddenLine)
|
||||
if (layers.advLines) {
|
||||
drawAdvancedLineList(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层6: advancedCurveList(贝塞尔曲线路径,酸绿色 #76ff03)
|
||||
if (layers.curves) {
|
||||
drawAdvancedCurveList(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
// 图层7: advancedPointList(高级点标记,菱形)
|
||||
if (layers.points) {
|
||||
drawAdvancedPointList(canvasCtx, map, applicationState);
|
||||
}
|
||||
|
||||
|
||||
// 地图边界虚线框
|
||||
drawMapBoundary(canvasCtx, map, applicationState);
|
||||
|
||||
// 请求下一帧渲染
|
||||
animationId = requestAnimationFrame(renderFrame);
|
||||
getMapArr(value);
|
||||
}
|
||||
|
||||
const mapChange = () => {
|
||||
loadUserFile();
|
||||
window.addEventListener('resize', resizeCanvasToContainer); // 监听窗口大小变化
|
||||
resizeCanvasToContainer();
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
animationId = requestAnimationFrame(renderFrame);
|
||||
loadUserFile();
|
||||
|
||||
setTimeout(() => {
|
||||
bindEvent()
|
||||
}, 100)
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
animationId = requestAnimationFrame(renderFrame);
|
||||
window.addEventListener('resize', resizeCanvasToContainer); // 监听窗口大小变化
|
||||
resizeCanvasToContainer();
|
||||
setTimeout(() => {
|
||||
|
||||
bindEvent()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getRobotArr();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div ref="topContainerRef">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="100px">
|
||||
<el-form :model="queryParams" :inline="true" v-show="showSearch" label-width="100px">
|
||||
<el-form-item label="机器人名称" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入机器人名称" clearable
|
||||
@keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.name" placeholder="请输入机器人名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="IP地址" prop="ipAddress">
|
||||
<el-input v-model="queryParams.ipAddress" placeholder="请输入IP地址" clearable
|
||||
@ -26,11 +25,12 @@
|
||||
|
||||
<div :style="containerHeight">
|
||||
<el-table v-loading="loading" height="100%" style="width: 100%" :data="tableDataList">
|
||||
<el-table-column label="机器人名称" align="center" prop="name" />
|
||||
<el-table-column label="机器人名称" align="center" prop="robotName" />
|
||||
<el-table-column label="机器人类型" align="center" prop="robotType" />
|
||||
<el-table-column label="IP地址" align="center" prop="ipAddress" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="电量" align="center" prop="battery" />
|
||||
<el-table-column label="电量" align="center" prop="batteryLevel" />
|
||||
<el-table-column label="状态" align="center" prop="status" />
|
||||
<el-table-column label="当前地图" align="center" prop="currentMap" />
|
||||
<el-table-column label="当前地图" align="center" prop="currentMapName" />
|
||||
<el-table-column label="操作" class-name="small-padding fixed-width" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
@ -45,9 +45,15 @@
|
||||
|
||||
<!-- 添加或修改语料库对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="580px" append-to-body>
|
||||
<el-form :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="机器人名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入机器人名称" />
|
||||
<el-form ref="robotRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="机器人名称" prop="robotName">
|
||||
<el-input v-model="form.robotName" placeholder="请输入机器人名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="机器人类型" prop="robotType">
|
||||
<el-select v-model="form.robotType" placeholder="请输入机器人类型" clearable style="width: 100%">
|
||||
<el-option v-for="type in robot_type" :key="type.value" :label="type.label"
|
||||
:value="type.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP地址" prop="ipAddress">
|
||||
<el-input class="test" v-model="form.ipAddress" placeholder="请输入IP地址" @input="ipChange">
|
||||
@ -58,9 +64,11 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="地图选择" prop="currentMap">
|
||||
<el-select v-model="form.currentMap" placeholder="请选择地图" clearable style="width: 100%">
|
||||
<el-form-item label="端口号" prop="port">
|
||||
<el-input v-model="form.port" placeholder="请输入端口号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地图选择" prop="currentMapId">
|
||||
<el-select v-model="form.currentMapId" placeholder="请选择地图" clearable style="width: 100%">
|
||||
<el-option v-for="map in mapList" :key="map.value" :label="map.label" :value="map.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@ -76,8 +84,17 @@
|
||||
</template>
|
||||
|
||||
<script setup name="Corpus">
|
||||
import { ElMessage } from "element-plus";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { useContainerHeight } from "@/hooks/tableHeight";
|
||||
import { addRobot, updateRobot, deleteRobot, getRobotList } from "../../../api/inspection/robot";
|
||||
import { getMapList } from "../../../api/inspection/map";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { robot_type } = proxy.useDict(
|
||||
"robot_type"
|
||||
);
|
||||
|
||||
const robotRef = ref();
|
||||
|
||||
const topContainerRef = ref();
|
||||
const containerHeight = useContainerHeight(topContainerRef);
|
||||
@ -89,29 +106,44 @@ const showSearch = ref(true);
|
||||
|
||||
const total = ref(0);
|
||||
const title = ref("");
|
||||
const isNew = ref(true);
|
||||
|
||||
const mapList = ref([
|
||||
{ label: "地图1", value: "map1" },
|
||||
{ label: "地图2", value: "map2" },
|
||||
{ label: "地图3", value: "map3" },
|
||||
])
|
||||
const mapList = ref([])
|
||||
|
||||
const data = reactive({
|
||||
form: {
|
||||
name: "",
|
||||
robotName: "",
|
||||
robotType: "",
|
||||
ipAddress: "",
|
||||
currentMap: ""
|
||||
port: "",
|
||||
currentMapId: ""
|
||||
},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
robotName: [
|
||||
{ required: true, message: "请输入机器人名称", trigger: "change" },
|
||||
],
|
||||
robotType: [
|
||||
{ required: true, message: "请输入机器人类型", trigger: "change" },
|
||||
],
|
||||
ipAddress: [
|
||||
{ required: true, message: "请输入IP地址", trigger: "blur" },
|
||||
{ required: true, message: "请输入IP地址", trigger: "change" },
|
||||
{
|
||||
pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||
message: "请输入正确的IP地址格式(例如:192.168.1.1)",
|
||||
trigger: "change"
|
||||
}
|
||||
],
|
||||
port: [
|
||||
{ required: true, message: "请输入端口号", trigger: "blur" },
|
||||
{
|
||||
pattern: /^\d+$/,
|
||||
message: "端口号只能为数字",
|
||||
trigger: "change"
|
||||
}
|
||||
]
|
||||
},
|
||||
});
|
||||
@ -121,10 +153,15 @@ const { queryParams, form, rules } = toRefs(data);
|
||||
/** 查询语料库列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
tableDataList.value = [];
|
||||
total.value = 0;
|
||||
loading.value = false;
|
||||
|
||||
getRobotList(queryParams.value).then((response) => {
|
||||
tableDataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
}).catch(() => {
|
||||
loading.value = false;
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
@ -135,12 +172,7 @@ function cancel() {
|
||||
|
||||
// 表单重置
|
||||
function reset() {
|
||||
form.value = {
|
||||
name: "",
|
||||
ipAddress: "",
|
||||
currentMap: ""
|
||||
};
|
||||
|
||||
robotRef.value.resetFields();
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@ -151,18 +183,23 @@ function handleQuery() {
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
queryParams.value.name = null;
|
||||
queryParams.value.ipAddress = null;
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset();
|
||||
open.value = true;
|
||||
title.value = "添加机器人";
|
||||
isNew.value = true;
|
||||
}
|
||||
|
||||
const connectionStatus = ref(false);
|
||||
/**
|
||||
* 测试连接按钮操作
|
||||
*/
|
||||
const testConnection = () => {
|
||||
// 模拟测试连接的逻辑
|
||||
if (form.value.ipAddress) {
|
||||
@ -184,21 +221,66 @@ const ipChange = () => {
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
|
||||
form.value = { ...row };
|
||||
open.value = true;
|
||||
title.value = "修改机器人";
|
||||
isNew.value = false;
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
|
||||
robotRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isNew.value) {
|
||||
// 调用新增接口
|
||||
addRobot(form.value).then(() => {
|
||||
ElMessage.success("机器人添加成功!");
|
||||
getList();
|
||||
reset();
|
||||
open.value = false;
|
||||
});
|
||||
} else {
|
||||
// 调用修改接口
|
||||
updateRobot(form.value).then(() => {
|
||||
ElMessage.success("机器人修改成功!");
|
||||
getList();
|
||||
reset();
|
||||
open.value = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ElMessage.error("请完善表单信息!");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
|
||||
ElMessageBox.confirm('确定要删除该机器人吗?', '提示', {}).then(() => {
|
||||
deleteRobot(row.id).then(() => {
|
||||
ElMessage.success("机器人删除成功!");
|
||||
getList();
|
||||
});
|
||||
}).catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
}
|
||||
|
||||
const getMapArr = () => {
|
||||
getMapList({
|
||||
pageNum: 1,
|
||||
pageSize: 10000,
|
||||
}).then((response) => {
|
||||
mapList.value = response.rows.map(item => ({
|
||||
label: item.mapName,
|
||||
value: item.id
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
getList();
|
||||
getMapArr();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.test {
|
||||
@ -214,7 +296,8 @@ getList();
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background-color: #fff; /* 覆盖右边阴影,需要与背景色相同 */
|
||||
background-color: #fff;
|
||||
/* 覆盖右边阴影,需要与背景色相同 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -47,11 +47,11 @@ export default defineConfig(({mode, command}) => {
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
'/dev-api': {
|
||||
//服务器 http://192.168.0.100:13080
|
||||
//服务器 http://192.168.0.114:13080
|
||||
//李小龙 http://192.168.0.5:13080
|
||||
// dev http://10.148.20.34:13080
|
||||
// target: VITE_API_URL,
|
||||
target: command === 'build' ? VITE_API_URL : 'http://192.168.28.10:13080',
|
||||
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.114:13080',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user