Compare commits
No commits in common. "bd2014edf01c5081b990607e94cb5daf1148022f" and "dcae537302bb4c9bd321b43cff7ca90b04a77232" have entirely different histories.
bd2014edf0
...
dcae537302
@ -7,6 +7,8 @@ VITE_APP_ENV = 'development'
|
|||||||
# 招商车研物联网平台/开发环境
|
# 招商车研物联网平台/开发环境
|
||||||
VITE_APP_BASE_API = '/dev-api'
|
VITE_APP_BASE_API = '/dev-api'
|
||||||
|
|
||||||
VITE_API_URL = http://192.168.28.10:13080
|
VITE_API_URL = http://192.168.0.10:13080
|
||||||
VITE_WS_URL = ws://192.168.28.10:13080/ws
|
VITE_WS_URL = ws://192.168.0.10:13080/ws
|
||||||
|
# VITE_API_URL = http://10.148.108.95:13080 //杨溪IP
|
||||||
|
# VITE_API_URL = http://10.148.108.58:13080 //赵培利IP
|
||||||
|
|
||||||
|
|||||||
@ -13,5 +13,5 @@ VITE_BUILD_COMPRESS = gzip
|
|||||||
#node-red 服务地址
|
#node-red 服务地址
|
||||||
VITE_NODE_RED_URL = 'http://10.148.108.59:13080'
|
VITE_NODE_RED_URL = 'http://10.148.108.59:13080'
|
||||||
|
|
||||||
VITE_API_URL = http://192.168.28.10:13080
|
VITE_API_URL = http://192.168.0.100:13080
|
||||||
VITE_WS_URL = ws://192.168.28.10:13080/ws
|
VITE_WS_URL = ws://192.168.0.100:13080/ws
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build:dev": "vite build --mode development",
|
"build:dev": "vite build --mode development",
|
||||||
"build:prod": "vite build --mode production",
|
"build:prod": "vite build --mode production",
|
||||||
"build:staging": "vite build --mode staging --verbose",
|
"build:staging": "vite build --mode staging",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
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'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
export function getPointList(params) {
|
|
||||||
return request({
|
|
||||||
url: '/inspection/waypoint/list',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addPoint(data) {
|
|
||||||
return request({
|
|
||||||
url: '/inspection/waypoint',
|
|
||||||
method: 'post',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updatePoint(data) {
|
|
||||||
return request({
|
|
||||||
url: '/inspection/waypoint',
|
|
||||||
method: 'put',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deletePoint(id) {
|
|
||||||
return request({
|
|
||||||
url: `/inspection/waypoint/${id}`,
|
|
||||||
method: 'delete'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
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'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
export function getTaskList(params) {
|
|
||||||
return request({
|
|
||||||
url: '/inspection/task/list',
|
|
||||||
method: 'get',
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addTask(data) {
|
|
||||||
return request({
|
|
||||||
url: '/inspection/task',
|
|
||||||
method: 'post',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTask(data) {
|
|
||||||
return request({
|
|
||||||
url: '/inspection/task',
|
|
||||||
method: 'put',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteTask(id) {
|
|
||||||
return request({
|
|
||||||
url: `/inspection/task/${id}`,
|
|
||||||
method: 'delete'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -88,7 +88,7 @@ app.use(ElementPlus, {
|
|||||||
|
|
||||||
app.use(WebSocketPlugin, {
|
app.use(WebSocketPlugin, {
|
||||||
// url: import.meta.env.VITE_WS_URL,
|
// url: import.meta.env.VITE_WS_URL,
|
||||||
url: 'ws://192.168.0.201:13080/ws',
|
url: 'ws://192.168.0.100:13080/ws',
|
||||||
// url: 'ws://10.148.209.5:13080/ws',
|
// url: 'ws://10.148.209.5:13080/ws',
|
||||||
userId: 'your_user_id'
|
userId: 'your_user_id'
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export const useInspectionStore = defineStore('inspection', {
|
|||||||
camera: { // 相机参数
|
camera: { // 相机参数
|
||||||
centerX: 0, // 相机中心的世界坐标 X
|
centerX: 0, // 相机中心的世界坐标 X
|
||||||
centerY: 0, // 相机中心的世界坐标 Y
|
centerY: 0, // 相机中心的世界坐标 Y
|
||||||
pixelsPerMeter: 20, // 缩放:每米对应的屏幕像素数
|
pixelsPerMeter: 40, // 缩放:每米对应的屏幕像素数
|
||||||
},
|
},
|
||||||
canvasWidthPx: 0, // Canvas CSS 逻辑宽度(像素)
|
canvasWidthPx: 0, // Canvas CSS 逻辑宽度(像素)
|
||||||
canvasHeightPx: 0, // Canvas CSS 逻辑高度(像素)
|
canvasHeightPx: 0, // Canvas CSS 逻辑高度(像素)
|
||||||
|
|||||||
@ -23,11 +23,10 @@ function hexToCssRgba(hexString, alpha) {
|
|||||||
* @param {number} worldY - 世界坐标 Y(米)
|
* @param {number} worldY - 世界坐标 Y(米)
|
||||||
* @returns {{x: number, y: number}} Canvas 上的屏幕像素坐标
|
* @returns {{x: number, y: number}} Canvas 上的屏幕像素坐标
|
||||||
*/
|
*/
|
||||||
export const worldToScreen = (worldX, worldY, dataStore) => {
|
export const worldToScreen = (worldX, worldY) => {
|
||||||
const store = dataStore || inspectionStore;
|
const camera = inspectionStore.camera;
|
||||||
const camera = store.camera
|
const screenX = (worldX - camera.centerX) * camera.pixelsPerMeter + inspectionStore.canvasWidthPx / 2; // 水平偏移 + 居中
|
||||||
const screenX = (worldX - camera.centerX) * camera.pixelsPerMeter + store.canvasWidthPx / 2; // 水平偏移 + 居中
|
const screenY = -(worldY - camera.centerY) * camera.pixelsPerMeter + inspectionStore.canvasHeightPx / 2; // 垂直翻转 + 居中
|
||||||
const screenY = -(worldY - camera.centerY) * camera.pixelsPerMeter + store.canvasHeightPx / 2; // 垂直翻转 + 居中
|
|
||||||
return { x: screenX, y: screenY };
|
return { x: screenX, y: screenY };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,11 +37,10 @@ export const worldToScreen = (worldX, worldY, dataStore) => {
|
|||||||
* @param {number} screenY - Canvas 像素 Y
|
* @param {number} screenY - Canvas 像素 Y
|
||||||
* @returns {{x: number, y: number}} 世界坐标(米)
|
* @returns {{x: number, y: number}} 世界坐标(米)
|
||||||
*/
|
*/
|
||||||
export const screenToWorld = (screenX, screenY, dataStore) => {
|
export const screenToWorld = (screenX, screenY) => {
|
||||||
const store = dataStore || inspectionStore;
|
const camera = inspectionStore.camera;
|
||||||
const camera = store.camera;
|
const worldX = (screenX - inspectionStore.canvasWidthPx / 2) / camera.pixelsPerMeter + camera.centerX; // 水平反算
|
||||||
const worldX = (screenX - store.canvasWidthPx / 2) / camera.pixelsPerMeter + camera.centerX; // 水平反算
|
const worldY = -(screenY - inspectionStore.canvasHeightPx / 2) / camera.pixelsPerMeter + camera.centerY; // 垂直反算(带翻转)
|
||||||
const worldY = -(screenY - store.canvasHeightPx / 2) / camera.pixelsPerMeter + camera.centerY; // 垂直反算(带翻转)
|
|
||||||
return { x: worldX, y: worldY };
|
return { x: worldX, y: worldY };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,15 +79,13 @@ export const toCssRgba = (colorObj, alphaOverride) => {
|
|||||||
* 将预渲染的离屏 Canvas 图像绘制到主 Canvas 上,并根据当前地图范围进行缩放和平移
|
* 将预渲染的离屏 Canvas 图像绘制到主 Canvas 上,并根据当前地图范围进行缩放和平移
|
||||||
* @param {*} canvasCtx
|
* @param {*} canvasCtx
|
||||||
* @param {*} map
|
* @param {*} map
|
||||||
* @param {*} gridOffscreen
|
|
||||||
*/
|
*/
|
||||||
export const drawGrid = (canvasCtx, map, dataStore) => {
|
export const drawGrid = (canvasCtx, map) => {
|
||||||
const store = dataStore || inspectionStore;
|
const topLeftScreen = worldToScreen(map.originX, map.maxY); // 图像左上角(世界坐标→屏幕)
|
||||||
const topLeftScreen = worldToScreen(map.originX, map.maxY, store); // 图像左上角(世界坐标→屏幕)
|
const bottomRightScreen = worldToScreen(map.maxX, map.originY); // 图像右下角(世界坐标→屏幕)
|
||||||
const bottomRightScreen = worldToScreen(map.maxX, map.originY, store); // 图像右下角(世界坐标→屏幕)
|
|
||||||
canvasCtx.imageSmoothingEnabled = false; // 关闭抗锯齿(保持像素清晰)
|
canvasCtx.imageSmoothingEnabled = false; // 关闭抗锯齿(保持像素清晰)
|
||||||
canvasCtx.drawImage(
|
canvasCtx.drawImage(
|
||||||
store.gridOffscreen, // 离屏 Canvas 图像源
|
inspectionStore.gridOffscreen, // 离屏 Canvas 图像源
|
||||||
topLeftScreen.x, topLeftScreen.y, // 目标左上角
|
topLeftScreen.x, topLeftScreen.y, // 目标左上角
|
||||||
bottomRightScreen.x - topLeftScreen.x, // 目标宽度
|
bottomRightScreen.x - topLeftScreen.x, // 目标宽度
|
||||||
bottomRightScreen.y - topLeftScreen.y // 目标高度
|
bottomRightScreen.y - topLeftScreen.y // 目标高度
|
||||||
@ -102,11 +98,10 @@ export const drawGrid = (canvasCtx, map, dataStore) => {
|
|||||||
* @param {*} canvasWidthPx
|
* @param {*} canvasWidthPx
|
||||||
* @param {*} canvasHeightPx
|
* @param {*} canvasHeightPx
|
||||||
*/
|
*/
|
||||||
export const drawGridlines = (canvasCtx, dataStore) => {
|
export const drawGridlines = (canvasCtx) => {
|
||||||
// 计算当前可视范围的世界坐标
|
// 计算当前可视范围的世界坐标
|
||||||
const store = dataStore || inspectionStore;
|
const viewTopLeft = screenToWorld(0, 0); // 视口左上角世界坐标
|
||||||
const viewTopLeft = screenToWorld(0, 0, store); // 视口左上角世界坐标
|
const viewBottomRight = screenToWorld(inspectionStore.canvasWidthPx, inspectionStore.canvasHeightPx); // 视口右下角世界坐标
|
||||||
const viewBottomRight = screenToWorld(store.canvasWidthPx, store.canvasHeightPx, store); // 视口右下角世界坐标
|
|
||||||
const viewMinX = Math.floor(Math.min(viewTopLeft.x, viewBottomRight.x)); // 可视X最小整数
|
const viewMinX = Math.floor(Math.min(viewTopLeft.x, viewBottomRight.x)); // 可视X最小整数
|
||||||
const viewMaxX = Math.ceil(Math.max(viewTopLeft.x, viewBottomRight.x)); // 可视X最大整数
|
const viewMaxX = Math.ceil(Math.max(viewTopLeft.x, viewBottomRight.x)); // 可视X最大整数
|
||||||
const viewMinY = Math.floor(Math.min(viewTopLeft.y, viewBottomRight.y)); // 可视Y最小整数
|
const viewMinY = Math.floor(Math.min(viewTopLeft.y, viewBottomRight.y)); // 可视Y最小整数
|
||||||
@ -117,14 +112,14 @@ export const drawGridlines = (canvasCtx, dataStore) => {
|
|||||||
canvasCtx.lineWidth = 0.5; // 细线
|
canvasCtx.lineWidth = 0.5; // 细线
|
||||||
canvasCtx.beginPath();
|
canvasCtx.beginPath();
|
||||||
for (let worldX = viewMinX; worldX <= viewMaxX; worldX++) { // 逐列绘制垂直线
|
for (let worldX = viewMinX; worldX <= viewMaxX; worldX++) { // 逐列绘制垂直线
|
||||||
const screenPos = worldToScreen(worldX, 0, store);
|
const screenPos = worldToScreen(worldX, 0);
|
||||||
canvasCtx.moveTo(screenPos.x, 0); // 从画布顶部
|
canvasCtx.moveTo(screenPos.x, 0); // 从画布顶部
|
||||||
canvasCtx.lineTo(screenPos.x, store.canvasHeightPx); // 到画布底部
|
canvasCtx.lineTo(screenPos.x, inspectionStore.canvasHeightPx); // 到画布底部
|
||||||
}
|
}
|
||||||
for (let worldY = viewMinY; worldY <= viewMaxY; worldY++) { // 逐行绘制水平线
|
for (let worldY = viewMinY; worldY <= viewMaxY; worldY++) { // 逐行绘制水平线
|
||||||
const screenPos = worldToScreen(0, worldY, store);
|
const screenPos = worldToScreen(0, worldY);
|
||||||
canvasCtx.moveTo(0, screenPos.y); // 从画布左边
|
canvasCtx.moveTo(0, screenPos.y); // 从画布左边
|
||||||
canvasCtx.lineTo(store.canvasWidthPx, screenPos.y); // 到画布右边
|
canvasCtx.lineTo(inspectionStore.canvasWidthPx, screenPos.y); // 到画布右边
|
||||||
}
|
}
|
||||||
canvasCtx.stroke();
|
canvasCtx.stroke();
|
||||||
}
|
}
|
||||||
@ -133,10 +128,8 @@ export const drawGridlines = (canvasCtx, dataStore) => {
|
|||||||
* 绘制高级区域列表
|
* 绘制高级区域列表
|
||||||
* @param {*} canvasCtx
|
* @param {*} canvasCtx
|
||||||
* @param {*} map
|
* @param {*} map
|
||||||
* @param {*} dataStore
|
|
||||||
*/
|
*/
|
||||||
export const drawAdvancedAreaList = (canvasCtx, map, dataStore) => {
|
export const drawAdvancedAreaList = (canvasCtx, map) => {
|
||||||
const store = dataStore || inspectionStore;
|
|
||||||
for (let areaIndex = 0; areaIndex < map.advancedAreas.length; areaIndex++) {
|
for (let areaIndex = 0; areaIndex < map.advancedAreas.length; areaIndex++) {
|
||||||
const area = map.advancedAreas[areaIndex]; // 当前区域
|
const area = map.advancedAreas[areaIndex]; // 当前区域
|
||||||
const vertices = area.posGroup || []; // 多边形顶点数组
|
const vertices = area.posGroup || []; // 多边形顶点数组
|
||||||
@ -162,10 +155,10 @@ export const drawAdvancedAreaList = (canvasCtx, map, dataStore) => {
|
|||||||
canvasCtx.strokeStyle = strokeColorCss; // 设置描边色
|
canvasCtx.strokeStyle = strokeColorCss; // 设置描边色
|
||||||
canvasCtx.lineWidth = 1.2; // 描边宽度
|
canvasCtx.lineWidth = 1.2; // 描边宽度
|
||||||
canvasCtx.beginPath();
|
canvasCtx.beginPath();
|
||||||
const firstVertexScreen = worldToScreen(vertices[0].x, vertices[0].y, store); // 第一个顶点的屏幕位置
|
const firstVertexScreen = worldToScreen(vertices[0].x, vertices[0].y); // 第一个顶点的屏幕位置
|
||||||
canvasCtx.moveTo(firstVertexScreen.x, firstVertexScreen.y); // 移动到起点
|
canvasCtx.moveTo(firstVertexScreen.x, firstVertexScreen.y); // 移动到起点
|
||||||
for (let i = 1; i < vertices.length; i++) { // 依次连接后续顶点
|
for (let i = 1; i < vertices.length; i++) { // 依次连接后续顶点
|
||||||
const vertexScreen = worldToScreen(vertices[i].x, vertices[i].y, store);
|
const vertexScreen = worldToScreen(vertices[i].x, vertices[i].y);
|
||||||
canvasCtx.lineTo(vertexScreen.x, vertexScreen.y);
|
canvasCtx.lineTo(vertexScreen.x, vertexScreen.y);
|
||||||
}
|
}
|
||||||
canvasCtx.closePath(); // 闭合路径
|
canvasCtx.closePath(); // 闭合路径
|
||||||
@ -177,7 +170,7 @@ export const drawAdvancedAreaList = (canvasCtx, map, dataStore) => {
|
|||||||
for (const v of vertices) { centroidWorldX += v.x; centroidWorldY += v.y; } // 累加顶点坐标
|
for (const v of vertices) { centroidWorldX += v.x; centroidWorldY += v.y; } // 累加顶点坐标
|
||||||
centroidWorldX /= vertices.length; // 求平均得到质心 X
|
centroidWorldX /= vertices.length; // 求平均得到质心 X
|
||||||
centroidWorldY /= vertices.length; // 求平均得到质心 Y
|
centroidWorldY /= vertices.length; // 求平均得到质心 Y
|
||||||
const centroidScreen = worldToScreen(centroidWorldX, centroidWorldY, store);
|
const centroidScreen = worldToScreen(centroidWorldX, centroidWorldY);
|
||||||
canvasCtx.font = '500 8px "JetBrains Mono"'; // 标签字体
|
canvasCtx.font = '500 8px "JetBrains Mono"'; // 标签字体
|
||||||
canvasCtx.fillStyle = strokeColorCss; // 与描边同色
|
canvasCtx.fillStyle = strokeColorCss; // 与描边同色
|
||||||
canvasCtx.textAlign = 'center'; // 居中对齐
|
canvasCtx.textAlign = 'center'; // 居中对齐
|
||||||
@ -190,15 +183,14 @@ export const drawAdvancedAreaList = (canvasCtx, map, dataStore) => {
|
|||||||
* @param {*} canvasCtx
|
* @param {*} canvasCtx
|
||||||
* @param {*} map
|
* @param {*} map
|
||||||
*/
|
*/
|
||||||
export const drawAdvancedLineList = (canvasCtx, map, dataStore) => {
|
export const drawAdvancedLineList = (canvasCtx, map) => {
|
||||||
const store = dataStore || inspectionStore;
|
|
||||||
for (let advLineIndex = 0; advLineIndex < map.advancedLines.length; advLineIndex++) {
|
for (let advLineIndex = 0; advLineIndex < map.advancedLines.length; advLineIndex++) {
|
||||||
const advLine = map.advancedLines[advLineIndex]; // 当前高级线
|
const advLine = map.advancedLines[advLineIndex]; // 当前高级线
|
||||||
const lineData = advLine.line; // 内部的 line 对象
|
const lineData = advLine.line; // 内部的 line 对象
|
||||||
if (!lineData || !lineData.startPos || !lineData.endPos) continue; // 缺少端点则跳过
|
if (!lineData || !lineData.startPos || !lineData.endPos) continue; // 缺少端点则跳过
|
||||||
|
|
||||||
const startScreen = worldToScreen(lineData.startPos.x, lineData.startPos.y, store);
|
const startScreen = worldToScreen(lineData.startPos.x, lineData.startPos.y);
|
||||||
const endScreen = worldToScreen(lineData.endPos.x, lineData.endPos.y, store);
|
const endScreen = worldToScreen(lineData.endPos.x, lineData.endPos.y);
|
||||||
|
|
||||||
// ── 根据 className 决定颜色和线型 ──
|
// ── 根据 className 决定颜色和线型 ──
|
||||||
let lineColorHex = '#ff6e40'; // 默认橙色
|
let lineColorHex = '#ff6e40'; // 默认橙色
|
||||||
@ -241,14 +233,12 @@ export const drawAdvancedLineList = (canvasCtx, map, dataStore) => {
|
|||||||
* 绘制基础直线列表
|
* 绘制基础直线列表
|
||||||
* @param {*} canvasCtx
|
* @param {*} canvasCtx
|
||||||
* @param {*} map
|
* @param {*} map
|
||||||
* @param {*} dataStore
|
|
||||||
*/
|
*/
|
||||||
export const drawNormalLines = (canvasCtx, map, dataStore) => {
|
export const drawNormalLines = (canvasCtx, map) => {
|
||||||
const store = dataStore || inspectionStore;
|
|
||||||
for (let lineIndex = 0; lineIndex < map.normalLines.length; lineIndex++) {
|
for (let lineIndex = 0; lineIndex < map.normalLines.length; lineIndex++) {
|
||||||
const line = map.normalLines[lineIndex]; // 当前直线
|
const line = map.normalLines[lineIndex]; // 当前直线
|
||||||
const startScreen = worldToScreen(line.startPos.x, line.startPos.y, store); // 起点屏幕位置
|
const startScreen = worldToScreen(line.startPos.x, line.startPos.y); // 起点屏幕位置
|
||||||
const endScreen = worldToScreen(line.endPos.x, line.endPos.y, store); // 终点屏幕位置
|
const endScreen = worldToScreen(line.endPos.x, line.endPos.y); // 终点屏幕位置
|
||||||
|
|
||||||
// 绘制发光直线
|
// 绘制发光直线
|
||||||
canvasCtx.save();
|
canvasCtx.save();
|
||||||
@ -282,10 +272,8 @@ export const drawNormalLines = (canvasCtx, map, dataStore) => {
|
|||||||
* 绘制高级曲线列表
|
* 绘制高级曲线列表
|
||||||
* @param {*} canvasCtx
|
* @param {*} canvasCtx
|
||||||
* @param {*} map
|
* @param {*} map
|
||||||
* @param {*} dataStore
|
|
||||||
*/
|
*/
|
||||||
export const drawAdvancedCurveList = (canvasCtx, map, dataStore) => {
|
export const drawAdvancedCurveList = (canvasCtx, map) => {
|
||||||
const store = dataStore || inspectionStore;
|
|
||||||
for (let curveIndex = 0; curveIndex < map.advancedCurves.length; curveIndex++) {
|
for (let curveIndex = 0; curveIndex < map.advancedCurves.length; curveIndex++) {
|
||||||
const curve = map.advancedCurves[curveIndex]; // 当前曲线
|
const curve = map.advancedCurves[curveIndex]; // 当前曲线
|
||||||
|
|
||||||
@ -297,10 +285,10 @@ export const drawAdvancedCurveList = (canvasCtx, map, dataStore) => {
|
|||||||
if (!startPoint || !endPoint) continue; // 缺少端点则跳过
|
if (!startPoint || !endPoint) continue; // 缺少端点则跳过
|
||||||
|
|
||||||
// 转换为屏幕坐标
|
// 转换为屏幕坐标
|
||||||
const startScreen = worldToScreen(startPoint.x, startPoint.y, store);
|
const startScreen = worldToScreen(startPoint.x, startPoint.y);
|
||||||
const endScreen = worldToScreen(endPoint.x, endPoint.y, store);
|
const endScreen = worldToScreen(endPoint.x, endPoint.y);
|
||||||
const cp1Screen = controlPoint1 ? worldToScreen(controlPoint1.x, controlPoint1.y, store) : null; // 可能不存在
|
const cp1Screen = controlPoint1 ? worldToScreen(controlPoint1.x, controlPoint1.y) : null; // 可能不存在
|
||||||
const cp2Screen = controlPoint2 ? worldToScreen(controlPoint2.x, controlPoint2.y, store) : null;
|
const cp2Screen = controlPoint2 ? worldToScreen(controlPoint2.x, controlPoint2.y) : null;
|
||||||
|
|
||||||
// ── 绘制贝塞尔曲线(虚线)──
|
// ── 绘制贝塞尔曲线(虚线)──
|
||||||
canvasCtx.save();
|
canvasCtx.save();
|
||||||
@ -369,18 +357,16 @@ export const drawAdvancedCurveList = (canvasCtx, map, dataStore) => {
|
|||||||
* 绘制高级点列表
|
* 绘制高级点列表
|
||||||
* @param {*} canvasCtx
|
* @param {*} canvasCtx
|
||||||
* @param {*} map
|
* @param {*} map
|
||||||
* @param {*} dataStore
|
|
||||||
*/
|
*/
|
||||||
export const drawAdvancedPointList = (canvasCtx, map, dataStore) => {
|
export const drawAdvancedPointList = (canvasCtx, map) => {
|
||||||
const store = dataStore || inspectionStore;
|
|
||||||
for (let pointIndex = 0; pointIndex < map.advancedPoints.length; pointIndex++) {
|
for (let pointIndex = 0; pointIndex < map.advancedPoints.length; pointIndex++) {
|
||||||
const advPoint = map.advancedPoints[pointIndex]; // 当前高级点
|
const advPoint = map.advancedPoints[pointIndex]; // 当前高级点
|
||||||
const pointScreen = worldToScreen(advPoint.pos.x, advPoint.pos.y, store); // 屏幕位置
|
const pointScreen = worldToScreen(advPoint.pos.x, advPoint.pos.y); // 屏幕位置
|
||||||
|
|
||||||
// 判断是否被鼠标悬停
|
// 判断是否被鼠标悬停
|
||||||
const isHovered = store.hoveredElement &&
|
const isHovered = inspectionStore.hoveredElement &&
|
||||||
store.hoveredElement.kind === 'point' &&
|
inspectionStore.hoveredElement.kind === 'point' &&
|
||||||
store.hoveredElement.index === pointIndex;
|
inspectionStore.hoveredElement.index === pointIndex;
|
||||||
|
|
||||||
// ── 根据 className 决定颜色 ──
|
// ── 根据 className 决定颜色 ──
|
||||||
let pointColorHex = '#ffd600'; // 默认琥珀色
|
let pointColorHex = '#ffd600'; // 默认琥珀色
|
||||||
@ -399,7 +385,7 @@ export const drawAdvancedPointList = (canvasCtx, map, dataStore) => {
|
|||||||
|
|
||||||
// ── 绘制菱形标记 ──
|
// ── 绘制菱形标记 ──
|
||||||
const diamondRadius = isHovered ? 8 : 5; // 悬停时放大
|
const diamondRadius = isHovered ? 8 : 5; // 悬停时放大
|
||||||
const pulseAlpha = isHovered ? Math.sin(store.animationTimeSeconds * 5) * 0.2 + 0.8 : 1; // 悬停时脉冲闪烁
|
const pulseAlpha = isHovered ? Math.sin(inspectionStore.animationTimeSeconds * 5) * 0.2 + 0.8 : 1; // 悬停时脉冲闪烁
|
||||||
|
|
||||||
canvasCtx.save();
|
canvasCtx.save();
|
||||||
canvasCtx.globalAlpha = pulseAlpha; // 应用透明度
|
canvasCtx.globalAlpha = pulseAlpha; // 应用透明度
|
||||||
@ -456,12 +442,10 @@ export const drawAdvancedPointList = (canvasCtx, map, dataStore) => {
|
|||||||
* 绘制地图边界
|
* 绘制地图边界
|
||||||
* @param {*} canvasCtx
|
* @param {*} canvasCtx
|
||||||
* @param {*} map
|
* @param {*} map
|
||||||
* @param {*} dataStore
|
|
||||||
*/
|
*/
|
||||||
export const drawMapBoundary = (canvasCtx, map, dataStore) => {
|
export const drawMapBoundary = (canvasCtx, map) => {
|
||||||
const store = dataStore || inspectionStore;
|
const boundaryTopLeft = worldToScreen(map.originX, map.maxY); // 边界左上角屏幕位置
|
||||||
const boundaryTopLeft = worldToScreen(map.originX, map.maxY, store); // 边界左上角屏幕位置
|
const boundaryBottomRight = worldToScreen(map.maxX, map.originY); // 边界右下角屏幕位置
|
||||||
const boundaryBottomRight = worldToScreen(map.maxX, map.originY, store); // 边界右下角屏幕位置
|
|
||||||
canvasCtx.strokeStyle = 'rgba(0,229,160,0.1)'; // 极淡的青绿色
|
canvasCtx.strokeStyle = 'rgba(0,229,160,0.1)'; // 极淡的青绿色
|
||||||
canvasCtx.lineWidth = 1;
|
canvasCtx.lineWidth = 1;
|
||||||
canvasCtx.setLineDash([5, 4]); // 虚线
|
canvasCtx.setLineDash([5, 4]); // 虚线
|
||||||
@ -559,6 +543,7 @@ export const buildOccupancyGridImage = (parsedMap, showEdgeGlow) => {
|
|||||||
return offscreenCanvas;
|
return offscreenCanvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// import bot from '@/assets/icons/svg/bot.svg'
|
||||||
export class MobileRobot {
|
export class MobileRobot {
|
||||||
constructor(id, name, x, y, angleRad = 0, color= "#00D4FF", imageUrl = null) {
|
constructor(id, name, x, y, angleRad = 0, color= "#00D4FF", imageUrl = null) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -575,28 +560,23 @@ export class MobileRobot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init(color, imageUrl) {
|
async init(color, imageUrl) {
|
||||||
|
console.log('imageUrl', imageUrl)
|
||||||
const response = await fetch(imageUrl);
|
const response = await fetch(imageUrl);
|
||||||
let svgText = await response.text();
|
let svgText = await response.text();
|
||||||
|
console.log('svgText', svgText)
|
||||||
// 替换颜色
|
// 替换颜色
|
||||||
svgText = svgText.replace(/currentColor/g, color);
|
svgText = svgText.replace(/currentColor/g, color);
|
||||||
svgText = svgText.replace(/stroke="[^"]*"/g, `stroke="${color}"`);
|
svgText = svgText.replace(/fill="[^"]*"/g, `fill="${color}"`);
|
||||||
// 创建Blob URL
|
// 创建Blob URL
|
||||||
const blob = new Blob([svgText], { type: 'image/svg+xml' });
|
const blob = new Blob([svgText], { type: 'image/svg+xml' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
this.imgElement = new Image();
|
this.imgElement = new Image();
|
||||||
this.imgElement.src = url;
|
this.imgElement.src = url;
|
||||||
}
|
}
|
||||||
// 命中检测: 检查世界坐标是否在机器人区域内 (半径0.35米)
|
|
||||||
hitTest(worldX, worldY, pixelsPerMeter){
|
|
||||||
const dx = worldX - this.x;
|
|
||||||
const dy = worldY - this.y;
|
|
||||||
const distance = Math.hypot(dx, dy);
|
|
||||||
const hitRadius = 0.35; // 米
|
|
||||||
return distance < hitRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制到canvas上下文 (屏幕坐标)
|
// 绘制到canvas上下文 (屏幕坐标)
|
||||||
draw(ctx, screenX, screenY, scale = 1) {
|
draw(ctx, screenX, screenY, scale = 1) {
|
||||||
|
console.log('imgElement', this.imgElement)
|
||||||
const size = 18; // 绘制大小px
|
const size = 18; // 绘制大小px
|
||||||
if (this.imgElement && this.imgElement.complete) {
|
if (this.imgElement && this.imgElement.complete) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@ -606,10 +586,10 @@ export class MobileRobot {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
// 名字标签
|
// 名字标签
|
||||||
ctx.font = '12px "JetBrains Mono"';
|
ctx.font = '8px "JetBrains Mono"';
|
||||||
ctx.fillStyle = '#a0c0f0';
|
ctx.fillStyle = '#a0c0f0';
|
||||||
ctx.shadowBlur = 0;
|
ctx.shadowBlur = 0;
|
||||||
ctx.fillText(this.name, screenX - 30, screenY - 20);
|
ctx.fillText(this.name, screenX - 10, screenY - 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新位置
|
// 更新位置
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<canvas id="map-canvas"></canvas>
|
<canvas id="map-canvas"></canvas>
|
||||||
<div id="tooltip">
|
|
||||||
<div class="tooltip-title"></div>
|
|
||||||
<div class="tooltip-detail"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -30,6 +26,7 @@ import { useInspectionStore } from "@/store/modules/inspection";
|
|||||||
const inspectionStore = useInspectionStore();
|
const inspectionStore = useInspectionStore();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用全局状态对象,集中管理所有可变数据
|
* 应用全局状态对象,集中管理所有可变数据
|
||||||
* - parsedMap: 解析后的地图数据结构
|
* - parsedMap: 解析后的地图数据结构
|
||||||
@ -153,13 +150,14 @@ async function loadMapIntoApplication(parsedMap) {
|
|||||||
const worldHeightMeters = parsedMap.maxY - parsedMap.originY; // 地图世界高度
|
const worldHeightMeters = parsedMap.maxY - parsedMap.originY; // 地图世界高度
|
||||||
const fitScaleX = inspectionStore.canvasWidthPx / worldWidthMeters; // 水平方向恰好填满的缩放
|
const fitScaleX = inspectionStore.canvasWidthPx / worldWidthMeters; // 水平方向恰好填满的缩放
|
||||||
const fitScaleY = inspectionStore.canvasHeightPx / worldHeightMeters; // 垂直方向恰好填满的缩放
|
const fitScaleY = inspectionStore.canvasHeightPx / worldHeightMeters; // 垂直方向恰好填满的缩放
|
||||||
inspectionStore.camera.pixelsPerMeter = Math.max(1, Math.min(fitScaleX, fitScaleY) * 1); // 取较小值
|
inspectionStore.camera.pixelsPerMeter = Math.max(1, Math.min(fitScaleX, fitScaleY) * 0.88); // 取较小值并留12%边距
|
||||||
|
|
||||||
// 标记加载完成并更新UI
|
// 标记加载完成并更新UI
|
||||||
applicationState.isMapLoaded = true;
|
applicationState.isMapLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主渲染函数,由 requestAnimationFrame 循环调用
|
* 主渲染函数,由 requestAnimationFrame 循环调用
|
||||||
* 按层顺序绘制:栅格 → 网格线 → 区域 → 基础线 → 高级线 → 曲线 → 点 → 边界
|
* 按层顺序绘制:栅格 → 网格线 → 区域 → 基础线 → 高级线 → 曲线 → 点 → 边界
|
||||||
@ -232,6 +230,7 @@ function renderFrame(timestamp) {
|
|||||||
const drawAllRobots = (ctx) => {
|
const drawAllRobots = (ctx) => {
|
||||||
if (!applicationState.layerVisibility.robots) return;
|
if (!applicationState.layerVisibility.robots) return;
|
||||||
for (const robot of applicationState.robots) {
|
for (const robot of applicationState.robots) {
|
||||||
|
console.log(1222)
|
||||||
const screenPos = worldToScreen(robot.x, robot.y);
|
const screenPos = worldToScreen(robot.x, robot.y);
|
||||||
// 裁剪超出画布简易跳过
|
// 裁剪超出画布简易跳过
|
||||||
if (screenPos.x + 20 < 0 || screenPos.x - 20 > inspectionStore.canvasWidthPx || screenPos.y + 20 < 0 || screenPos.y - 20 > inspectionStore.canvasHeightPx) {
|
if (screenPos.x + 20 < 0 || screenPos.x - 20 > inspectionStore.canvasWidthPx || screenPos.y + 20 < 0 || screenPos.y - 20 > inspectionStore.canvasHeightPx) {
|
||||||
@ -257,9 +256,6 @@ const bindEvent = () => {
|
|||||||
inspectionStore.camera.centerX -= mouseEvent.movementX / inspectionStore.camera.pixelsPerMeter; // 水平平移
|
inspectionStore.camera.centerX -= mouseEvent.movementX / inspectionStore.camera.pixelsPerMeter; // 水平平移
|
||||||
inspectionStore.camera.centerY += mouseEvent.movementY / inspectionStore.camera.pixelsPerMeter; // 垂直平移(Y翻转)
|
inspectionStore.camera.centerY += mouseEvent.movementY / inspectionStore.camera.pixelsPerMeter; // 垂直平移(Y翻转)
|
||||||
}
|
}
|
||||||
|
|
||||||
let hit = hitTestRobots(worldPos.x, worldPos.y);
|
|
||||||
updateTooltipDisplay(hit);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 鼠标按下:开始拖拽 ──
|
// ── 鼠标按下:开始拖拽 ──
|
||||||
@ -290,55 +286,6 @@ const bindEvent = () => {
|
|||||||
}, { passive: false }); // passive:false 允许 preventDefault
|
}, { passive: false }); // passive:false 允许 preventDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const updateTooltipDisplay = (hitResult) => {
|
|
||||||
const tooltipEl = document.getElementById('tooltip');
|
|
||||||
if (!hitResult) {
|
|
||||||
tooltipEl.style.display = 'none';
|
|
||||||
inspectionStore.hoveredElement = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
inspectionStore.hoveredElement = hitResult;
|
|
||||||
let title = '', details = '';
|
|
||||||
if (hitResult.kind === 'robot') {
|
|
||||||
const robot = hitResult.robot;
|
|
||||||
title = `${robot.name}`;
|
|
||||||
details = `<div class="tooltip-detail">位置: (${robot.x.toFixed(3)}, ${robot.y.toFixed(3)})</div>
|
|
||||||
<div class="tooltip-detail">朝向: ${(robot.angle * 180 / Math.PI).toFixed(1)}°</div>
|
|
||||||
<div class="tooltip-detail">ID: ${robot.id}</div>`;
|
|
||||||
}
|
|
||||||
tooltipEl.innerHTML = `<div class="tooltip-title" style="color:#00e5a0">${title}</div>${details}`;
|
|
||||||
tooltipEl.style.display = 'block';
|
|
||||||
tooltipEl.style.left = Math.min(applicationState.mouseState.screenX + 14, inspectionStore.canvasWidthPx - 220) + 'px';
|
|
||||||
tooltipEl.style.top = Math.max(4, applicationState.mouseState.screenY - 8) + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
const hitTestRobots = (worldX, worldY) => {
|
|
||||||
for (let i = 0; i < applicationState.robots.length; i++) {
|
|
||||||
const robot = applicationState.robots[i];
|
|
||||||
if (robot.hitTest(worldX, worldY)) {
|
|
||||||
return { kind: 'robot', index: i, robot: robot };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const zoomCanvas = (zoomIn) => {
|
|
||||||
const zoomFactor = zoomIn ? 1.12 : 0.88;
|
|
||||||
const worldBefore = screenToWorld(inspectionStore.canvasWidthPx / 2, inspectionStore.canvasHeightPx / 2);
|
|
||||||
inspectionStore.camera.pixelsPerMeter = Math.max(0.5, Math.min(2000, inspectionStore.camera.pixelsPerMeter * zoomFactor));
|
|
||||||
const worldAfter = screenToWorld(inspectionStore.canvasWidthPx / 2, inspectionStore.canvasHeightPx / 2);
|
|
||||||
inspectionStore.camera.centerX += worldBefore.x - worldAfter.x;
|
|
||||||
inspectionStore.camera.centerY += worldBefore.y - worldAfter.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
const centerCanvasView = () => {
|
|
||||||
const map = applicationState.parsedMap;
|
|
||||||
if (!map) return;
|
|
||||||
inspectionStore.camera.centerX = (map.originX + map.maxX) / 2;
|
|
||||||
inspectionStore.camera.centerY = (map.originY + map.maxY) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadSourceMap = async (url) => {
|
const loadSourceMap = async (url) => {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) throw new Error(`加载失败:${response.status}`);
|
if (!response.ok) throw new Error(`加载失败:${response.status}`);
|
||||||
@ -348,7 +295,7 @@ const loadSourceMap = async (url) => {
|
|||||||
/**
|
/**
|
||||||
* 加载 smap JSON 文件
|
* 加载 smap JSON 文件
|
||||||
*/
|
*/
|
||||||
const loadUserFile = async () => {
|
async function loadUserFile() {
|
||||||
try {
|
try {
|
||||||
const fileTextContent = await loadSourceMap('/3.smap');
|
const fileTextContent = await loadSourceMap('/3.smap');
|
||||||
const rawJsonObject = JSON.parse(fileTextContent); // 解析 JSON
|
const rawJsonObject = JSON.parse(fileTextContent); // 解析 JSON
|
||||||
@ -359,12 +306,14 @@ const loadUserFile = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addRobot = (id, name, angleRad, color) => {
|
const addRobot = () => {
|
||||||
const map = applicationState.parsedMap;
|
const map = applicationState.parsedMap;
|
||||||
|
console.log('map', map)
|
||||||
// 放置于地图中央附近
|
// 放置于地图中央附近
|
||||||
const centerX = (map.originX + map.maxX) / 2;
|
const centerX = (map.originX + map.maxX) / 2;
|
||||||
const centerY = (map.originY + map.maxY) / 2;
|
const centerY = (map.originY + map.maxY) / 2;
|
||||||
const robot = new MobileRobot(id, name, centerX + (Math.random() - 0.5) * 10, centerY + (Math.random() - 0.5) * 10, angleRad, color);
|
const id = 0;
|
||||||
|
const robot = new MobileRobot(id, `Robot${id}`, centerX + (Math.random() - 0.5) * 3, centerY + (Math.random() - 0.5) * 3, 0);
|
||||||
applicationState.robots.push(robot);
|
applicationState.robots.push(robot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,15 +332,8 @@ onMounted(() => {
|
|||||||
resizeCanvasToContainer(); // 初始调用一次
|
resizeCanvasToContainer(); // 初始调用一次
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
bindEvent()
|
bindEvent()
|
||||||
addRobot(1, '巡检机器人#1', 0, '#00D4FF')
|
addRobot()
|
||||||
addRobot(2, '巡检机器人#2', 0, '#FFB300')
|
}, 100)
|
||||||
addRobot(3, '巡检机器人#3', 0, '#00FF88')
|
|
||||||
}, 300)
|
|
||||||
})
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
zoomCanvas,
|
|
||||||
centerCanvasView
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -404,34 +346,5 @@ onUnmounted(() => {
|
|||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
#tooltip {
|
|
||||||
position: absolute;
|
|
||||||
display: none;
|
|
||||||
background: #0b1120;
|
|
||||||
border: 1px solid #2a3a5c;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 20;
|
|
||||||
font-size: 10px;
|
|
||||||
min-width: 180px;
|
|
||||||
max-width: 280px;
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, .5);
|
|
||||||
|
|
||||||
.tooltip-title {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 11px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.tooltip-detail) {
|
|
||||||
color: #94a3b8;
|
|
||||||
padding: 2px 0;
|
|
||||||
font-size: 9px;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -49,19 +49,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="map-controller">
|
<div class="map-controller">
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<SvgIcon name="zoom-in" color='#00D4FF' :size="16" @click="zoomCanvas(true)" />
|
<SvgIcon name="zoom-in" color='#00D4FF' :size="16" />
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<SvgIcon name="zoom-out" color='#00D4FF' :size="16" @click="zoomCanvas(false)" />
|
<SvgIcon name="zoom-out" color='#00D4FF' :size="16" />
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<SvgIcon name="locate-fixed" color='#00D4FF' :size="16" @click="centerCanvasView" />
|
<SvgIcon name="locate-fixed" color='#00D4FF' :size="16" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="map-box">
|
<div class="map-box">
|
||||||
<MapCanvas ref="mapCanvasRef" />
|
<MapCanvas />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-container">
|
<div class="right-container">
|
||||||
@ -87,19 +87,7 @@ import { onMounted, onUnmounted } from "vue";
|
|||||||
|
|
||||||
const robotState = ref('online')
|
const robotState = ref('online')
|
||||||
|
|
||||||
const mapCanvasRef = ref(null)
|
|
||||||
|
|
||||||
const zoomCanvas = (isZoomIn) => {
|
|
||||||
if (mapCanvasRef.value) {
|
|
||||||
mapCanvasRef.value.zoomCanvas(isZoomIn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const centerCanvasView = () => {
|
|
||||||
if (mapCanvasRef.value) {
|
|
||||||
mapCanvasRef.value.centerCanvasView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@ -1,579 +1,3 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div>地图管理</div>
|
||||||
<div ref="topContainerRef">
|
|
||||||
<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="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>
|
|
||||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-row :gutter="10" class="mb8">
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="primary" plain icon="Plus" @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%" style="width: 100%" :data="tableDataList">
|
|
||||||
<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>
|
|
||||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
|
|
||||||
v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加或修改语料库对话框 -->
|
|
||||||
<el-dialog :title="title" v-model="open" width="580px" append-to-body>
|
|
||||||
<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="mapDesc">
|
|
||||||
<el-input v-model="form.mapDesc" type="textarea" placeholder="请输入地图描述" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="选择机器人地图">
|
|
||||||
<el-col :span="11">
|
|
||||||
<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>
|
|
||||||
<el-col class="text-center" :span="2">
|
|
||||||
<span class="text-gray-500">-</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="11">
|
|
||||||
<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>
|
|
||||||
</el-form-item>
|
|
||||||
<div class="map-container">
|
|
||||||
<canvas id="mapCanvas"></canvas>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<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
|
|
||||||
} 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);
|
|
||||||
const loading = ref(true);
|
|
||||||
const showSearch = ref(true);
|
|
||||||
|
|
||||||
const total = ref(0);
|
|
||||||
const title = ref("");
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
form: {
|
|
||||||
mapName: "",
|
|
||||||
mapDesc: "",
|
|
||||||
mapSourceRobotId: "",
|
|
||||||
mapSourceName: "",
|
|
||||||
},
|
|
||||||
queryParams: {
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
mapDesc: null,
|
|
||||||
mapName: null,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
mapName: [
|
|
||||||
{ required: true, message: "请输入地图名称", trigger: "change" },
|
|
||||||
],
|
|
||||||
mapDesc: [
|
|
||||||
{ required: true, message: "请输入地图描述", trigger: "blur" },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { queryParams, form, rules } = toRefs(data);
|
|
||||||
|
|
||||||
/** 查询列表 */
|
|
||||||
function getList() {
|
|
||||||
loading.value = true;
|
|
||||||
getMapList(queryParams.value).then((response) => {
|
|
||||||
tableDataList.value = response.rows;
|
|
||||||
total.value = response.total;
|
|
||||||
loading.value = false;
|
|
||||||
}).catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
}).finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消按钮
|
|
||||||
function cancel() {
|
|
||||||
open.value = false;
|
|
||||||
reset();
|
|
||||||
if (animationId) {
|
|
||||||
cancelAnimationFrame(animationId);
|
|
||||||
animationId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.removeEventListener('resize', resizeCanvasToContainer)
|
|
||||||
canvasCtx = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单重置
|
|
||||||
function reset() {
|
|
||||||
mapRef.value.resetFields();
|
|
||||||
form.value = {
|
|
||||||
mapName: "",
|
|
||||||
mapDesc: "",
|
|
||||||
mapSourceRobotId: "",
|
|
||||||
mapSourceName: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
function handleQuery() {
|
|
||||||
queryParams.value.pageNum = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
function resetQuery() {
|
|
||||||
queryParams.value.mapName = null;
|
|
||||||
queryParams.value.mapDesc = null;
|
|
||||||
handleQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
|
||||||
function handleAdd() {
|
|
||||||
open.value = true;
|
|
||||||
isNew.value = true;
|
|
||||||
title.value = "添加地图";
|
|
||||||
setTimeout(() => {
|
|
||||||
init();
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
} 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();
|
|
||||||
};
|
|
||||||
|
|
||||||
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; // 每像素代表的世界长度(米)
|
|
||||||
|
|
||||||
// ── 计算栅格尺寸 ──
|
|
||||||
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 || [],
|
|
||||||
};
|
|
||||||
|
|
||||||
return parsedMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
const applicationState = {
|
|
||||||
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 元素
|
|
||||||
let canvasCtx; // 2D 渲染上下文
|
|
||||||
|
|
||||||
const init = () => {
|
|
||||||
mapCanvas = document.getElementById('mapCanvas');
|
|
||||||
canvasCtx = mapCanvas.getContext('2d');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据容器尺寸和设备像素比调整 Canvas 大小
|
|
||||||
* 在窗口 resize 时自动调用
|
|
||||||
*/
|
|
||||||
function resizeCanvasToContainer() {
|
|
||||||
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 有机会刷新
|
|
||||||
|
|
||||||
// 保存地图数据到全局状态
|
|
||||||
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)); // 取较小值并留12%边距
|
|
||||||
|
|
||||||
// 标记加载完成并更新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;
|
|
||||||
|
|
||||||
// 如果正在拖拽,平移相机
|
|
||||||
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('mouseup', () => {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载 smap JSON 文件
|
|
||||||
*/
|
|
||||||
const loadUserFile = async () => {
|
|
||||||
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) {
|
|
||||||
// 更新动画时间
|
|
||||||
applicationState.animationTimeSeconds = (timestamp || 0) / 1000;
|
|
||||||
|
|
||||||
// 设置 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); // 清空画布
|
|
||||||
}
|
|
||||||
|
|
||||||
getMapArr(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapChange = () => {
|
|
||||||
loadUserFile();
|
|
||||||
|
|
||||||
if (animationId) {
|
|
||||||
cancelAnimationFrame(animationId);
|
|
||||||
}
|
|
||||||
animationId = requestAnimationFrame(renderFrame);
|
|
||||||
window.addEventListener('resize', resizeCanvasToContainer); // 监听窗口大小变化
|
|
||||||
resizeCanvasToContainer();
|
|
||||||
setTimeout(() => {
|
|
||||||
|
|
||||||
bindEvent()
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getList();
|
|
||||||
getRobotArr();
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.map-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,403 +1,3 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div>点位管理</div>
|
||||||
<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="mapId">
|
|
||||||
<el-select
|
|
||||||
v-model="queryParams.mapId"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择所属地图"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in mapList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.mapName"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" :xxl="6">
|
|
||||||
<el-form-item label="启用状态" prop="enabled">
|
|
||||||
<el-select
|
|
||||||
v-model="queryParams.enabled"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择启用状态"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option label="启用" value="0" />
|
|
||||||
<el-option label="禁用" value="1" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</template>
|
</template>
|
||||||
</TableSearch>
|
|
||||||
|
|
||||||
<el-row :gutter="10" class="mb8">
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button
|
|
||||||
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="mapName"
|
|
||||||
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="configStatus">
|
|
||||||
<template #default="scope">
|
|
||||||
<TableTags
|
|
||||||
:options="[
|
|
||||||
{
|
|
||||||
label: '已配置',
|
|
||||||
value: '0',
|
|
||||||
type: 'success',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '未配置',
|
|
||||||
value: '1',
|
|
||||||
type: 'warning',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
:value="scope.row.configStatus"
|
|
||||||
></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="pointRef" :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="mapId">
|
|
||||||
<el-select
|
|
||||||
v-model="form.mapId"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择所属地图"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in mapList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.mapName"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</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 {
|
|
||||||
addPoint,
|
|
||||||
deletePoint,
|
|
||||||
getPointList,
|
|
||||||
updatePoint,
|
|
||||||
} from "@/api/inspection/point";
|
|
||||||
import { getMapList } from "@/api/inspection/map";
|
|
||||||
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";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { el } from "element-plus/es/locale/index.mjs";
|
|
||||||
import { map } from "lodash";
|
|
||||||
|
|
||||||
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,
|
|
||||||
enabled: null,
|
|
||||||
mapId: null,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
waypointName: [
|
|
||||||
{ required: true, message: "点位名称不能为空", trigger: "blur" },
|
|
||||||
],
|
|
||||||
mapId: [
|
|
||||||
{ required: true, message: "所属地图不能为空", trigger: "change" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapList = ref([]);
|
|
||||||
// 获取地图列表
|
|
||||||
const getMapArr = () => {
|
|
||||||
getMapList({
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10000,
|
|
||||||
}).then((response) => {
|
|
||||||
mapList.value = response.rows;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { queryParams, form, rules } = toRefs(data);
|
|
||||||
|
|
||||||
/** 查询点位列表 */
|
|
||||||
function getList() {
|
|
||||||
loading.value = true;
|
|
||||||
getPointList(queryParams.value)
|
|
||||||
.then((res) => {
|
|
||||||
tableDataList.value = res.rows;
|
|
||||||
total.value = res.total;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消按钮
|
|
||||||
function cancel() {
|
|
||||||
open.value = false;
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单重置
|
|
||||||
function reset() {
|
|
||||||
pointRef.value.resetFields();
|
|
||||||
form.value = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
function handleQuery() {
|
|
||||||
queryParams.value.pageNum = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
function resetQuery() {
|
|
||||||
handleQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
|
||||||
function handleAdd() {
|
|
||||||
isNew.value = true;
|
|
||||||
open.value = true;
|
|
||||||
title.value = "添加点位";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
|
||||||
function handleUpdate(row) {
|
|
||||||
form.value = { ...row };
|
|
||||||
isNew.value = false;
|
|
||||||
open.value = true;
|
|
||||||
title.value = "修改点位";
|
|
||||||
}
|
|
||||||
|
|
||||||
const pointRef = ref();
|
|
||||||
const isNew = ref(true);
|
|
||||||
/** 提交按钮 */
|
|
||||||
function submitForm() {
|
|
||||||
pointRef.value.validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
if (isNew.value) {
|
|
||||||
addPoint(form.value).then(() => {
|
|
||||||
ElMessage.success("添加成功");
|
|
||||||
getList();
|
|
||||||
open.value = false;
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updatePoint(form.value).then(() => {
|
|
||||||
ElMessage.success("修改成功");
|
|
||||||
getList();
|
|
||||||
open.value = false;
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
|
||||||
function handleDelete(row) {
|
|
||||||
ElMessageBox.confirm("确定要删除该点位吗?", "提示", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}).then(() => {
|
|
||||||
deletePoint(row.id).then(() => {
|
|
||||||
ElMessage.success("删除成功");
|
|
||||||
getList();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 配置按钮操作 */
|
|
||||||
function handleDeploy(row) {
|
|
||||||
const params = { itemId: row.detectItemId, name: row.waypointName, state: row.status };
|
|
||||||
const fullPath = appendParamsToPath("/flow", params);
|
|
||||||
const p = window.open(fullPath, "_blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getList();
|
|
||||||
getMapArr();
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -1,324 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<div ref="topContainerRef">
|
|
||||||
<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-form-item>
|
|
||||||
<el-form-item label="IP地址" prop="ipAddress">
|
|
||||||
<el-input v-model="queryParams.ipAddress" placeholder="请输入IP地址" clearable
|
|
||||||
@keyup.enter="handleQuery" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
||||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-row :gutter="10" class="mb8">
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="primary" plain icon="Plus" @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%" style="width: 100%" :data="tableDataList">
|
|
||||||
<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="batteryLevel" />
|
|
||||||
<el-table-column label="状态" align="center" prop="status" />
|
|
||||||
<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>
|
|
||||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
|
|
||||||
v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加或修改语料库对话框 -->
|
|
||||||
<el-dialog :title="title" v-model="open" width="580px" append-to-body>
|
|
||||||
<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">
|
|
||||||
<template #append>
|
|
||||||
<div class="connection" :class="{ success: connectionStatus }" @click="testConnection">
|
|
||||||
{{ connectionStatus ? '连接成功' : '测试连接' }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup name="Corpus">
|
|
||||||
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);
|
|
||||||
|
|
||||||
const tableDataList = ref([]);
|
|
||||||
const open = ref(false);
|
|
||||||
const loading = ref(true);
|
|
||||||
const showSearch = ref(true);
|
|
||||||
|
|
||||||
const total = ref(0);
|
|
||||||
const title = ref("");
|
|
||||||
const isNew = ref(true);
|
|
||||||
|
|
||||||
const mapList = ref([])
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
form: {
|
|
||||||
robotName: "",
|
|
||||||
robotType: "",
|
|
||||||
ipAddress: "",
|
|
||||||
port: "",
|
|
||||||
currentMapId: ""
|
|
||||||
},
|
|
||||||
queryParams: {
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
robotName: [
|
|
||||||
{ required: true, message: "请输入机器人名称", trigger: "change" },
|
|
||||||
],
|
|
||||||
robotType: [
|
|
||||||
{ required: true, message: "请输入机器人类型", trigger: "change" },
|
|
||||||
],
|
|
||||||
ipAddress: [
|
|
||||||
{ 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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { queryParams, form, rules } = toRefs(data);
|
|
||||||
|
|
||||||
/** 查询语料库列表 */
|
|
||||||
function getList() {
|
|
||||||
loading.value = true;
|
|
||||||
getRobotList(queryParams.value).then((response) => {
|
|
||||||
tableDataList.value = response.rows;
|
|
||||||
total.value = response.total;
|
|
||||||
loading.value = false;
|
|
||||||
}).catch(() => {
|
|
||||||
loading.value = false;
|
|
||||||
}).finally(() => {
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消按钮
|
|
||||||
function cancel() {
|
|
||||||
open.value = false;
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单重置
|
|
||||||
function reset() {
|
|
||||||
robotRef.value.resetFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
function handleQuery() {
|
|
||||||
queryParams.value.pageNum = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
function resetQuery() {
|
|
||||||
queryParams.value.name = null;
|
|
||||||
queryParams.value.ipAddress = null;
|
|
||||||
handleQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
|
||||||
function handleAdd() {
|
|
||||||
open.value = true;
|
|
||||||
title.value = "添加机器人";
|
|
||||||
isNew.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectionStatus = ref(false);
|
|
||||||
/**
|
|
||||||
* 测试连接按钮操作
|
|
||||||
*/
|
|
||||||
const testConnection = () => {
|
|
||||||
// 模拟测试连接的逻辑
|
|
||||||
if (form.value.ipAddress) {
|
|
||||||
if (Math.random() > 0.5) {
|
|
||||||
connectionStatus.value = true;
|
|
||||||
ElMessage.success("连接成功!");
|
|
||||||
} else {
|
|
||||||
connectionStatus.value = false;
|
|
||||||
ElMessage.error("连接失败!");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ElMessage.error("请输入IP地址进行测试连接!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipChange = () => {
|
|
||||||
connectionStatus.value = false; // IP地址改变后重置连接状态
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
|
||||||
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 {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
:deep(.el-input__wrapper) {
|
|
||||||
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 1px;
|
|
||||||
background-color: #fff;
|
|
||||||
/* 覆盖右边阴影,需要与背景色相同 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-input-group__append) {
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
.connection {
|
|
||||||
background: #dcdfdf;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
height: calc(100% - 8px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
background: #67c23a;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,232 +1,3 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div>任务管理</div>
|
||||||
<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="taskName">
|
|
||||||
<el-input v-model="queryParams.taskName" 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="taskType">
|
|
||||||
<el-select v-model="queryParams.taskType" clearable placeholder="请选择任务类型"
|
|
||||||
style="width: 100%">
|
|
||||||
<el-option label="巡检任务" value="1" />
|
|
||||||
<el-option label="讲解任务" value="2" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</template>
|
</template>
|
||||||
</TableSearch>
|
|
||||||
|
|
||||||
<el-row :gutter="10" class="mb8">
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button 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="taskName" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="任务描述" prop="remark" show-overflow-tooltip />
|
|
||||||
<el-table-column align="center" label="任务类型" prop="taskType">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="scope.row.taskType === '1' ? 'primary' : 'success'">
|
|
||||||
{{ scope.row.taskType === '1' ? '巡检任务' : '讲解任务' }}
|
|
||||||
</el-tag>
|
|
||||||
</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="260">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button icon="Edit" link type="primary" @click="handleUpdate(scope.row)">
|
|
||||||
修改
|
|
||||||
</el-button>
|
|
||||||
<el-button icon="Cpu" link type="primary" @click="handleBindPoint(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="taskRef" :model="form" :rules="rules" label-width="100px">
|
|
||||||
<el-form-item label="任务名称" prop="taskName">
|
|
||||||
<el-input v-model="form.taskName" placeholder="请输入任务名称" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="任务类型" prop="taskType">
|
|
||||||
<el-select v-model="queryParams.taskType" clearable placeholder="请选择任务类型" style="width: 100%">
|
|
||||||
<el-option label="巡检任务" value="1" />
|
|
||||||
<el-option label="讲解任务" value="2" />
|
|
||||||
</el-select>
|
|
||||||
</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 { addTask, updateTask, getTaskList, deleteTask } from "../../../api/inspection/task";
|
|
||||||
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";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { de } from "element-plus/es/locale/index.mjs";
|
|
||||||
|
|
||||||
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,
|
|
||||||
taskName: null,
|
|
||||||
taskType: null,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
taskName: [
|
|
||||||
{ required: true, message: "任务名称不能为空", trigger: "blur" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { queryParams, form, rules } = toRefs(data);
|
|
||||||
|
|
||||||
/** 查询任务列表 */
|
|
||||||
function getList() {
|
|
||||||
loading.value = false;
|
|
||||||
getTaskList(queryParams.value).then((res) => {
|
|
||||||
tableDataList.value = res.rows;
|
|
||||||
total.value = res.total;
|
|
||||||
loading.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消按钮
|
|
||||||
function cancel() {
|
|
||||||
open.value = false;
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单重置
|
|
||||||
function reset() {
|
|
||||||
taskRef.value.resetFields();
|
|
||||||
form.value = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
function handleQuery() {
|
|
||||||
queryParams.value.pageNum = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
function resetQuery() {
|
|
||||||
handleQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
|
||||||
function handleAdd() {
|
|
||||||
isNew.value = true;
|
|
||||||
open.value = true;
|
|
||||||
title.value = "添加任务";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
|
||||||
function handleUpdate(row) {
|
|
||||||
form.value = { ...row };
|
|
||||||
isNew.value = false;
|
|
||||||
open.value = true;
|
|
||||||
title.value = "修改任务";
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskRef = ref();
|
|
||||||
const isNew = ref(true);
|
|
||||||
/** 提交按钮 */
|
|
||||||
function submitForm() {
|
|
||||||
taskRef.value.validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
if (isNew.value) {
|
|
||||||
addTask(form.value).then(() => {
|
|
||||||
ElMessage.success("添加成功");
|
|
||||||
cancel();
|
|
||||||
getList();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updateTask(form.value).then(() => {
|
|
||||||
ElMessage.success("修改成功");
|
|
||||||
cancel();
|
|
||||||
getList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
|
||||||
function handleDelete(row) {
|
|
||||||
ElMessageBox.confirm("确定要删除该任务吗?", "提示", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}).then(() => {
|
|
||||||
deleteTask(row.id).then(() => {
|
|
||||||
ElMessage.success("删除成功");
|
|
||||||
getList();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBindPoint = (row) => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getList();
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -222,7 +222,7 @@
|
|||||||
<el-form-item label="检测项描述" prop="detectDesc">
|
<el-form-item label="检测项描述" prop="detectDesc">
|
||||||
<el-input v-model="form.detectDesc" placeholder="请输入检测项描述" />
|
<el-input v-model="form.detectDesc" placeholder="请输入检测项描述" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="所属分组" prop="groupId">
|
<el-form-item label="所属分组">
|
||||||
<el-select v-model="form.groupId" placeholder="请选择所属分组">
|
<el-select v-model="form.groupId" placeholder="请选择所属分组">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="group in groupList"
|
v-for="group in groupList"
|
||||||
@ -323,9 +323,6 @@ const data = reactive({
|
|||||||
detectName: [
|
detectName: [
|
||||||
{ required: true, message: "检测项名称不能为空", trigger: "blur" },
|
{ required: true, message: "检测项名称不能为空", trigger: "blur" },
|
||||||
],
|
],
|
||||||
groupId: [
|
|
||||||
{ required: true, message: "所属分组不能为空", trigger: "change" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -47,10 +47,11 @@ export default defineConfig(({mode, command}) => {
|
|||||||
proxy: {
|
proxy: {
|
||||||
// https://cn.vitejs.dev/config/#server-proxy
|
// https://cn.vitejs.dev/config/#server-proxy
|
||||||
'/dev-api': {
|
'/dev-api': {
|
||||||
//李小龙 http://192.168.0.201:13080
|
//服务器 http://192.168.0.100:13080
|
||||||
|
//李小龙 http://192.168.0.5:13080
|
||||||
// dev http://10.148.20.34:13080
|
// dev http://10.148.20.34:13080
|
||||||
// target: VITE_API_URL,
|
// target: VITE_API_URL,
|
||||||
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.201:13080',
|
target: command === 'build' ? VITE_API_URL : 'http://10.148.121.100:13080',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user