修改
This commit is contained in:
@@ -1,3 +1,2 @@
|
||||
NODE_ENV=production
|
||||
# VUE_APP_API_BASE_URL=http://localhost:8011
|
||||
VUE_APP_API_BASE_URL=http://192.168.100.201:8011
|
||||
VUE_APP_API_BASE_URL=http://localhost:8011
|
||||
@@ -14,6 +14,7 @@
|
||||
"hammerjs": "^2.0.8",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"pixi.js": "^8.12.0",
|
||||
"simple-keyboard": "^3.8.72",
|
||||
"simple-keyboard-layouts": "^3.4.114",
|
||||
"three": "^0.179.1",
|
||||
|
||||
@@ -7,7 +7,8 @@ export const authlogin = (username, password) => post('auth/login', {
|
||||
})
|
||||
// 建图
|
||||
export const startMapping = (na) => post('teaching/startMapping?mapName=' + na, {})
|
||||
export const setStation = (sn) => post('teaching/setStation?stationName=' + sn, {})
|
||||
export const getMappingStatus = () => post('teaching/getMappingStatus', {})
|
||||
export const setStation = (sn, code) => post('teaching/setStation?stationName=' + sn + '&stationCode=' + code, {})
|
||||
export const stopMapping = () => post('teaching/stopMapping', {})
|
||||
export const getLocalMaps = () => post('teaching/getLocalMaps', {})
|
||||
export const oneClickDeployment = (map) => post('teaching/oneClickDeployment?mapName=' + map, {})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import axios from 'axios'
|
||||
import i18n from '../i18n/i18n'
|
||||
import store from '../vuex/store'
|
||||
|
||||
const urlHost = process.env.VUE_APP_API_BASE_URL
|
||||
const urlHost = store.getters.serverUrl
|
||||
|
||||
axios.defaults.timeout = 50000
|
||||
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
|
||||
|
||||
@@ -103,3 +103,19 @@ export const startMapping = () => {
|
||||
}
|
||||
return res
|
||||
}
|
||||
export const stopMapping = () => {
|
||||
let res = {
|
||||
code: 200,
|
||||
message: 'ok'
|
||||
}
|
||||
return res
|
||||
}
|
||||
export const getMappingStatus = () => {
|
||||
let res = {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
mapping_return: '2',
|
||||
mapping_percent: '100'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
34
src/main.js
34
src/main.js
@@ -5,13 +5,12 @@ import store from './vuex/store'
|
||||
import './style/reset.css'
|
||||
import VueTouchKeyboard from 'vue-touch-keyboard'
|
||||
import 'vue-touch-keyboard/dist/vue-touch-keyboard.css'
|
||||
import { Row, Col, Button, Icon, Dialog, Form, FormItem, Input, Select, Option, Table, TableColumn, Tabs, TabPane, Popover, Loading, MessageBox, Message, Steps, Step } from 'element-ui'
|
||||
import { Row, Col, Button, Icon, Dialog, Form, FormItem, Input, Select, Option, Table, TableColumn, Tabs, TabPane, Popover, Loading, MessageBox, Message, Progress, Steps, Step } from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import './style/common.styl'
|
||||
import i18n from './i18n/i18n'
|
||||
import './config/rem.js'
|
||||
import {post} from './config/http.js'
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@@ -31,6 +30,7 @@ Vue.use(Tabs)
|
||||
Vue.use(TabPane)
|
||||
Vue.use(Popover)
|
||||
Vue.use(Loading)
|
||||
Vue.use(Progress)
|
||||
Vue.use(Steps)
|
||||
Vue.use(Step)
|
||||
Vue.prototype.$confirm = MessageBox.confirm
|
||||
@@ -46,30 +46,14 @@ Vue.prototype.$langPre = {
|
||||
}
|
||||
}
|
||||
|
||||
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +
|
||||
'2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ=='
|
||||
|
||||
const privateKey = 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8\n' +
|
||||
'mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9p\n' +
|
||||
'B6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue\n' +
|
||||
'/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZ\n' +
|
||||
'UBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6\n' +
|
||||
'vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha\n' +
|
||||
'4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3\n' +
|
||||
'tTbklZkD2A=='
|
||||
|
||||
// 加密
|
||||
export function encrypt (txt) {
|
||||
const encryptor = new JSEncrypt()
|
||||
encryptor.setPublicKey(publicKey) // 设置公钥
|
||||
return encryptor.encrypt(txt) // 对需要加密的数据进行加密
|
||||
// 从本地存储中读取 serverUrl,userRole
|
||||
const savedServerUrl = localStorage.getItem('serverUrl');
|
||||
const savedUserRole = localStorage.getItem('userRole');
|
||||
if (savedServerUrl) {
|
||||
store.commit('SET_BASE_URL', savedServerUrl);
|
||||
}
|
||||
|
||||
// 解密
|
||||
export function decrypt (txt) {
|
||||
const encryptor = new JSEncrypt()
|
||||
encryptor.setPrivateKey(privateKey)
|
||||
return encryptor.decrypt(txt)
|
||||
if (savedUserRole) {
|
||||
store.commit('SET_USER_ROLE', parseInt(savedUserRole, 10));
|
||||
}
|
||||
|
||||
Vue.filter('findByValue', (array, value) => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="10"><button class="button_control" :disabled="disabled" @click="addPoint"><p>打点</p></button></el-col>
|
||||
</el-row>
|
||||
<gl-map></gl-map>
|
||||
<gl-map ref="glMap"/>
|
||||
<el-row type="flex" justify="end">
|
||||
<button class="button_control" @click="$router.push('/index/home')"><p>放弃建图</p></button>
|
||||
<button class="button_control" style="margin-left: 10px" :disabled="disabled" @click="_stopMapping"><p>结束建图</p></button>
|
||||
@@ -13,8 +13,8 @@
|
||||
:visible.sync="dialogVisible"
|
||||
width="50%">
|
||||
<el-form :model="dataForm" ref="dataForm" :label-width="$i18n.locale === 'en-us' ? '' : '1.1rem'" size="mini">
|
||||
<el-form-item label="站点名称" prop="point">
|
||||
<el-input v-model="dataForm.point" id="point" @focus="show" data-layout="normal"></el-input>
|
||||
<el-form-item label="站点名称" prop="stationName">
|
||||
<el-input v-model="dataForm.stationName" id="stationName" @focus="show" data-layout="normal"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="space-around" style="margin-top: .3rem">
|
||||
@@ -29,17 +29,30 @@
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</transition>
|
||||
<div v-if="showProgress" class="progress-mask">
|
||||
<!-- 进度条内容区 -->
|
||||
<div class="progress-container">
|
||||
<el-progress
|
||||
:percentage="percentage"
|
||||
:stroke-width="6"
|
||||
style="width: 300px;"
|
||||
></el-progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GlMap from './gl-map1.vue'
|
||||
import { startMapping, setStation, stopMapping, getLocalMaps, oneClickDeployment, abandonMapping } from '../../config/getData.js'
|
||||
import GlMap from './gl-map.vue'
|
||||
// import PointCloudMap from './point-cloud-map.vue'
|
||||
// import { startMapping, stopMapping, getMappingStatus } from '../../config/mork.js'
|
||||
import { startMapping, stopMapping, getMappingStatus, setStation, oneClickDeployment, abandonMapping } from '../../config/getData.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'ModuleBuilding',
|
||||
components: {
|
||||
GlMap
|
||||
GlMap,
|
||||
// PointCloudMap
|
||||
},
|
||||
beforeRouteLeave (to, from, next) {
|
||||
if (this.needsConfirmation) {
|
||||
@@ -86,21 +99,23 @@ export default {
|
||||
mapName: '',
|
||||
dialogVisible: false,
|
||||
dataForm: {
|
||||
point: ''
|
||||
stationCode: '',
|
||||
stationName: ''
|
||||
},
|
||||
keyPoints: [],
|
||||
disabled: false,
|
||||
message: '', // 用于显示消息
|
||||
error: false,
|
||||
intervalId: null, // 用于存储定时器ID
|
||||
startTime: null, // 用于记录开始时间
|
||||
visible: false,
|
||||
layout: 'normal',
|
||||
input: null,
|
||||
options: {
|
||||
useKbEvents: false,
|
||||
preventClickEvent: false
|
||||
}
|
||||
},
|
||||
showProgress: false,
|
||||
percentage: 0,
|
||||
intervalId: null // 用于存储定时器ID
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -108,7 +123,7 @@ export default {
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
clearTimeout(this.intervalId)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@@ -157,12 +172,21 @@ export default {
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.6)'
|
||||
})
|
||||
const getTimestamp = new Date().getTime()
|
||||
this.mapName = `apt_map_${getTimestamp}`
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0') // 月份从0开始,补0
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
const hours = String(now.getHours()).padStart(2, '0')
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0')
|
||||
this.mapName = `apt_map_${year}${month}${day}${hours}${minutes}${seconds}`
|
||||
let res = await startMapping(this.mapName)
|
||||
if (res && res.code === 200) {
|
||||
this.message = '小车正在建图中'
|
||||
this.error = false
|
||||
this.$nextTick(() => {
|
||||
this.$refs.glMap.init()
|
||||
})
|
||||
}
|
||||
this.loading.close()
|
||||
} catch (e) {
|
||||
@@ -174,20 +198,21 @@ export default {
|
||||
// 打点
|
||||
addPoint () {
|
||||
this.dialogVisible = true
|
||||
this.dataForm.point = 'B' + (this.keyPoints.length + 1)
|
||||
this.dataForm.stationCode = 'B' + (this.keyPoints.length + 1)
|
||||
this.dataForm.stationName = 'B' + (this.keyPoints.length + 1)
|
||||
},
|
||||
// 打点->保存
|
||||
async _setStation () {
|
||||
this.disabled = true
|
||||
try {
|
||||
let res = await setStation(this.dataForm.point)
|
||||
let res = await setStation(this.dataForm.stationName, this.dataForm.stationCode)
|
||||
if (res) {
|
||||
if (res.code === 200) {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: res.message
|
||||
})
|
||||
this.keyPoints.push(this.dataForm.point)
|
||||
this.keyPoints.push(this.dataForm.stationCode)
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
@@ -213,65 +238,75 @@ export default {
|
||||
})
|
||||
let res = await stopMapping()
|
||||
if (res && res.code === 200) {
|
||||
this.message = '正在建图,请等待。。。'
|
||||
this.error = false
|
||||
this.startTime = Date.now() // 记录开始时间
|
||||
this._getLocalMaps()
|
||||
this.intervalId = setInterval(this._getLocalMaps, 3000) // 每5秒检查一次
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
this.message = ''
|
||||
this.error = false
|
||||
this.$nextTick(() => {
|
||||
this.$refs.glMap.closeWebSocket()
|
||||
})
|
||||
this._getMappingStatus()
|
||||
}
|
||||
this.disabled = false
|
||||
this.loading.close()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error(e)
|
||||
this.message = ''
|
||||
this.error = false
|
||||
this.message = '结束建图出现错误'
|
||||
this.error = true
|
||||
this.disabled = false
|
||||
this.loading.close()
|
||||
}
|
||||
},
|
||||
async _getLocalMaps () {
|
||||
// 进度条
|
||||
async _getMappingStatus () {
|
||||
try {
|
||||
let res = await getLocalMaps()
|
||||
let res = await getMappingStatus()
|
||||
if (res && res.code === 200) {
|
||||
let flag = false
|
||||
res.data.map(el => {
|
||||
if (el.id === this.mapName) {
|
||||
flag = true
|
||||
this.showProgress = true
|
||||
this.percentage = Number(res.mapping_percent) || 0
|
||||
if (res.mapping_return === '0') {
|
||||
if (this.intervalId) clearTimeout(this.intervalId)
|
||||
this.intervalId = setTimeout(() => this._getMappingStatus(), 1000)
|
||||
}
|
||||
if (res.mapping_return === '1') {
|
||||
if (this.intervalId) clearTimeout(this.intervalId)
|
||||
this.showProgress = false
|
||||
this.percentage = 0
|
||||
this._oneClickDeployment()
|
||||
}
|
||||
if (res.mapping_return === '2') {
|
||||
if (this.intervalId) clearTimeout(this.intervalId)
|
||||
this.showProgress = false
|
||||
this.percentage = 0
|
||||
this.keyPoints = []
|
||||
this.$confirm('新建地图失败, 是否重新建图?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this._startMapping()
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消建图'
|
||||
})
|
||||
if (flag) {
|
||||
clearInterval(this.intervalId) // 停止定时器
|
||||
await this._oneClickDeployment()
|
||||
} else {
|
||||
const elapsedTime = Date.now() - this.startTime
|
||||
if (elapsedTime >= 60000) { // 超过1分钟
|
||||
clearInterval(this.intervalId) // 停止定时器
|
||||
this.message = ''
|
||||
this.error = false
|
||||
this.$message.error('建图失败,请重新建图')
|
||||
this.disabled = false
|
||||
})
|
||||
}
|
||||
}
|
||||
this.loading.close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearInterval(this.intervalId) // 停止定时器
|
||||
this.message = ''
|
||||
this.error = false
|
||||
this.$message.error('建图失败,请重新建图')
|
||||
this.disabled = false
|
||||
this.loading.close()
|
||||
}
|
||||
} catch (e) {
|
||||
clearInterval(this.intervalId) // 出错时停止定时器
|
||||
this.message = ''
|
||||
this.error = false
|
||||
this.disabled = false
|
||||
this.$message.error(e)
|
||||
this.loading.close()
|
||||
this.keyPoints = []
|
||||
this.$confirm('新建地图失败, 是否重新建图?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this._startMapping()
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消建图'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
async _oneClickDeployment () {
|
||||
@@ -298,17 +333,6 @@ export default {
|
||||
this.disabled = false
|
||||
this.loading.close()
|
||||
}
|
||||
},
|
||||
// 放大
|
||||
zoomIn () {
|
||||
// this.scale += 0.1
|
||||
// this.redrawCanvas()
|
||||
},
|
||||
// 缩小
|
||||
zoomOut () {
|
||||
// this.scale -= 0.1
|
||||
// if (this.scale < 0.1) this.scale = 0.1 // 防止缩放到 0 或负值
|
||||
// this.redrawCanvas()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,4 +360,25 @@ export default {
|
||||
.custom-message-enter, .custom-message-leave-to
|
||||
opacity 0
|
||||
transform translate(-50%,-100%)
|
||||
.progress-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6); /* 半透明黑色遮罩 */
|
||||
z-index: 9999; /* 确保在最上层 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 进度条容器 */
|
||||
.progress-container {
|
||||
background-color: #fff;
|
||||
padding: 30px 40px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { points } from '../../config/point.js'
|
||||
// import { points } from '../../config/point.js'
|
||||
export default {
|
||||
/* eslint-disable */
|
||||
data () {
|
||||
@@ -36,6 +36,7 @@ export default {
|
||||
// WebSocket相关
|
||||
socket: null,
|
||||
isLoading: true,
|
||||
reconnectTimer: null, // 重连计时器
|
||||
// 动画与性能
|
||||
viewSize: 20,
|
||||
animationId: null,
|
||||
@@ -46,7 +47,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['carPosition']),
|
||||
...mapGetters(['serverUrl', , 'userRole', 'carPosition']),
|
||||
},
|
||||
watch: {
|
||||
carPosition: {
|
||||
@@ -65,9 +66,6 @@ export default {
|
||||
this.initPointCloud();
|
||||
// 初始化小车模型
|
||||
this.initCar();
|
||||
// 初始化WebSocket连接
|
||||
this.initWebSocket();
|
||||
// this.init()
|
||||
// 启动动画循环
|
||||
this.startAnimationLoop();
|
||||
// 监听窗口大小变化
|
||||
@@ -81,14 +79,7 @@ export default {
|
||||
// 先标记组件已销毁,阻止新消息处理
|
||||
this.isDestroyed = true;
|
||||
// 1. 清理WebSocket(先移除事件监听,再关闭连接)
|
||||
if (this.socket) {
|
||||
this.socket.onopen = null;
|
||||
this.socket.onmessage = null;
|
||||
this.socket.onclose = null;
|
||||
this.socket.onerror = null;
|
||||
this.socket.close(1000, '组件销毁');
|
||||
this.socket = null;
|
||||
}
|
||||
this.closeWebSocket()
|
||||
// 2. 停止动画循环并清理
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
@@ -144,7 +135,6 @@ export default {
|
||||
// 8. 移除窗口大小变化监听(避免重复监听)
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
// 9. 清空其他引用(帮助GC回收)
|
||||
this.allPoints = [];
|
||||
this.camera = null;
|
||||
this.carMesh = null;
|
||||
this.pointCloudMesh = null;
|
||||
@@ -294,9 +284,9 @@ export default {
|
||||
* 初始化WebSocket连接
|
||||
*/
|
||||
initWebSocket() {
|
||||
const wsHost = process.env.VUE_APP_API_BASE_URL.replace(/^https?:\/\//, '')
|
||||
const sid = this.$store.getters.userInfo === 'true' ? 1 : 2
|
||||
this.socket = new WebSocket(`ws://${wsHost}/webSocket/PointCloudData/${sid}`);
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
const wsHost = this.serverUrl.replace(/^https?:\/\//, '')
|
||||
this.socket = new WebSocket(`ws://${wsHost}/webSocket/PointCloudData/${this.userRole}`);
|
||||
|
||||
this.socket.onopen = () => {
|
||||
console.log('WebSocket连接已建立');
|
||||
@@ -313,7 +303,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.lastUpdateTime = now;
|
||||
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
const pointData = JSON.parse(event.data);
|
||||
this.updatePointCloud(pointData.data);
|
||||
} catch (error) {
|
||||
@@ -325,22 +315,38 @@ export default {
|
||||
console.log('WebSocket连接已关闭,代码:', event.code);
|
||||
this.isLoading = true;
|
||||
// 自动重连
|
||||
setTimeout(() => this.initWebSocket(), 3000);
|
||||
this.reconnectTimer = setTimeout(() => this.initWebSocket(), 3000);
|
||||
};
|
||||
|
||||
this.socket.onerror = (error) => {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
console.error('WebSocket错误:', error);
|
||||
this.isLoading = true;
|
||||
};
|
||||
},
|
||||
|
||||
closeWebSocket () {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
if (this.socket) {
|
||||
this.socket.onopen = null;
|
||||
this.socket.onmessage = null;
|
||||
this.socket.onclose = null;
|
||||
this.socket.onerror = null;
|
||||
this.socket.close(1000, '组件销毁');
|
||||
this.socket = null;
|
||||
}
|
||||
this.allPoints = []
|
||||
},
|
||||
|
||||
init () {
|
||||
const pointData = points.data
|
||||
this.updatePointCloud(pointData);
|
||||
setTimeout(() => {
|
||||
const arr = [{x: 2, y: 2}, {x: 4, y: 4}]
|
||||
this.updatePointCloud(arr);
|
||||
}, 2000)
|
||||
// 初始化WebSocket连接
|
||||
this.initWebSocket();
|
||||
// const pointData = points.data
|
||||
// this.updatePointCloud(pointData);
|
||||
// setTimeout(() => {
|
||||
// const arr = [{x: 2, y: 2}, {x: 4, y: 4}]
|
||||
// this.updatePointCloud(arr);
|
||||
// }, 2000)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -457,7 +463,7 @@ export default {
|
||||
.point-cloud-map
|
||||
position relative
|
||||
width 100%
|
||||
height calc(100% - 1rem)
|
||||
height calc(100% - 1rem - 6px)
|
||||
margin .14rem 0
|
||||
background-color rgba(4, 33, 58, 70%)
|
||||
box-shadow inset 1px 1px 7px 2px #4d9bcd
|
||||
@@ -1,364 +0,0 @@
|
||||
<template>
|
||||
<div class="canvas-container" :style="{'background-color': isConnected ? 'rgba(4, 33, 58, 70%)' : '#fff'}">
|
||||
<div ref="container" class="point-cloud-container"></div>
|
||||
<!-- <div v-if="!isConnected" class="reload_bg"><el-button type="danger">刷新</el-button></div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { points } from '../../config/point.js'
|
||||
export default {
|
||||
/* eslint-disable */
|
||||
data () {
|
||||
return {
|
||||
isConnected: true,
|
||||
points: [], // 存储所有点云数据
|
||||
ws: null, // WebSocket 实例
|
||||
// Three.js 相关对象
|
||||
scene: null,
|
||||
camera: null,
|
||||
renderer: null,
|
||||
controls: null,
|
||||
pointCloud: null,
|
||||
vehicleSprite: null,
|
||||
// 渲染循环
|
||||
animationFrameId: null,
|
||||
// 性能优化
|
||||
pointsBuffer: [],
|
||||
bufferUpdateThreshold: 500,
|
||||
lastUpdateTime: 0,
|
||||
// 小车图片
|
||||
vehicleImage: require('../../images/new/agv.png'),
|
||||
// 2D视图参数
|
||||
viewSize: 20,
|
||||
minZoom: 0.5,
|
||||
maxZoom: 5
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['agvObj', 'position', 'rotation']),
|
||||
},
|
||||
watch: {
|
||||
agvObj: {
|
||||
handler() {
|
||||
this.updateVehiclePosition()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
position: {
|
||||
handler() {
|
||||
this.updateVehiclePosition()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
rotation: {
|
||||
handler() {
|
||||
this.updateVehiclePosition()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initThreeJS()
|
||||
// this.initWebSocket()
|
||||
this.init()
|
||||
this.startRendering()
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.ws) this.ws.close()
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
if (this.renderer) this.renderer.dispose()
|
||||
if (this.controls) {
|
||||
this.controls.dispose()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initWebSocket() {
|
||||
const wsHost = process.env.VUE_APP_API_BASE_URL.replace(/^https?:\/\//, '')
|
||||
const sid = this.$store.getters.userInfo === 'true' ? 1 : 2
|
||||
this.ws = new WebSocket(`ws://${wsHost}/webSocket/PointCloudData/${sid}`)
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket connected')
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
const newPoints = event.data
|
||||
// 确保所有点都在XY平面(z=0)
|
||||
const points2D = newPoints.map(p => ({ x: p.x, y: p.y, z: 0 }))
|
||||
this.processNewPoints(points2D)
|
||||
}
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
}
|
||||
|
||||
this.ws.onclose = () => {
|
||||
console.log('WebSocket disconnected')
|
||||
}
|
||||
},
|
||||
|
||||
init () {
|
||||
this.isConnected = true
|
||||
const newPoints = points.data
|
||||
const points2D = newPoints.map(p => ({ x: p.x, y: p.y, z: 0 }))
|
||||
this.processNewPoints(points2D)
|
||||
},
|
||||
|
||||
initThreeJS() {
|
||||
// viewSize: 控制视图范围大小,值越小看到的范围越小(视角越近)
|
||||
// camera.position.z: 相机Z轴位置,值越小视角越近
|
||||
// camera.zoom: 直接控制缩放级别,大于1放大,小于1缩小
|
||||
// 1. 创建场景
|
||||
this.scene = new THREE.Scene()
|
||||
|
||||
// 2. 创建正交相机(2D视图)
|
||||
const container = this.$refs.container
|
||||
const aspect = container.clientWidth / container.clientHeight
|
||||
|
||||
this.camera = new THREE.OrthographicCamera(
|
||||
-this.viewSize * aspect / 2,
|
||||
this.viewSize * aspect / 2,
|
||||
this.viewSize / 2,
|
||||
-this.viewSize / 2,
|
||||
0.1,
|
||||
1000
|
||||
)
|
||||
this.camera.position.set(0, 0, 40)
|
||||
this.camera.lookAt(0, 0, 0)
|
||||
|
||||
// 3.创建渲染器 - 启用alpha通道实现透明背景
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true, // 关键:启用alpha通道
|
||||
transparent: true // 关键:允许透明
|
||||
});
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio)
|
||||
this.renderer.setSize(container.clientWidth, container.clientHeight)
|
||||
// 可选:设置clearAlpha确保完全透明
|
||||
this.renderer.setClearAlpha(0);
|
||||
container.appendChild(this.renderer.domElement)
|
||||
|
||||
// 4. 添加OrbitControls并配置为2D模式
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
|
||||
this.configure2DControls()
|
||||
|
||||
// 5. 添加2D坐标轴辅助
|
||||
const axesHelper = new THREE.AxesHelper(10)
|
||||
this.scene.add(axesHelper)
|
||||
|
||||
// 6. 初始化点云
|
||||
this.initPointCloud()
|
||||
|
||||
// 7. 初始化车辆精灵
|
||||
this.initVehicleSprite()
|
||||
},
|
||||
|
||||
configure2DControls() {
|
||||
// 禁用旋转
|
||||
this.controls.enableRotate = false
|
||||
|
||||
// 启用平面平移
|
||||
this.controls.screenSpacePanning = true
|
||||
|
||||
// 设置缩放限制
|
||||
this.controls.minZoom = this.minZoom
|
||||
this.controls.maxZoom = this.maxZoom
|
||||
|
||||
// 启用阻尼效果
|
||||
this.controls.enableDamping = true
|
||||
this.controls.dampingFactor = 0.25
|
||||
|
||||
// 确保相机保持2D视角
|
||||
this.controls.addEventListener('change', () => {
|
||||
this.camera.position.z = 40
|
||||
// this.camera.lookAt(this.controls.target)
|
||||
})
|
||||
|
||||
// 修复触摸事件处理
|
||||
this.fixTouchEvents()
|
||||
},
|
||||
|
||||
fixTouchEvents() {
|
||||
// 确保控制器的触摸处理函数存在
|
||||
if (!this.controls) return;
|
||||
|
||||
// 备份原始触摸处理函数
|
||||
const originalOnTouchStart = this.controls.onTouchStart
|
||||
const originalOnTouchMove = this.controls.onTouchMove
|
||||
const originalOnTouchEnd = this.controls.onTouchEnd
|
||||
|
||||
// 修复触摸开始事件
|
||||
this.controls.onTouchStart = (event) => {
|
||||
if (!event.touches) return
|
||||
if (originalOnTouchStart) originalOnTouchStart(event)
|
||||
}
|
||||
|
||||
// 修复触摸移动事件
|
||||
this.controls.onTouchMove = (event) => {
|
||||
if (!event.touches || event.touches.length === 0) return
|
||||
if (event.touches[0] && event.touches[0].clientX !== undefined) {
|
||||
if (originalOnTouchMove) originalOnTouchMove(event)
|
||||
}
|
||||
}
|
||||
|
||||
// 修复触摸结束事件
|
||||
this.controls.onTouchEnd = (event) => {
|
||||
if (!event.touches) return
|
||||
if (originalOnTouchEnd) originalOnTouchEnd(event)
|
||||
}
|
||||
},
|
||||
|
||||
initPointCloud() {
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
const material = new THREE.PointsMaterial({
|
||||
color: 0xffffff,
|
||||
size: 1,
|
||||
transparent: true,
|
||||
opacity: 1
|
||||
})
|
||||
|
||||
this.pointCloud = new THREE.Points(geometry, material)
|
||||
this.scene.add(this.pointCloud)
|
||||
},
|
||||
|
||||
initVehicleSprite() {
|
||||
const textureLoader = new THREE.TextureLoader()
|
||||
textureLoader.load(this.vehicleImage, (texture) => {
|
||||
// 97px × 67px图片的比例
|
||||
const imageAspect = 97 / 67
|
||||
const spriteWidth = 2
|
||||
const spriteHeight = spriteWidth / imageAspect
|
||||
|
||||
this.vehicleSprite = new THREE.Sprite(
|
||||
new THREE.SpriteMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.9
|
||||
})
|
||||
)
|
||||
this.vehicleSprite.scale.set(spriteWidth, spriteHeight, 1)
|
||||
|
||||
// 角度偏移(根据图片初始朝向调整)
|
||||
this.updateVehiclePosition()
|
||||
this.scene.add(this.vehicleSprite)
|
||||
})
|
||||
},
|
||||
|
||||
processNewPoints(newPoints) {
|
||||
this.pointsBuffer.push(...newPoints)
|
||||
const now = performance.now()
|
||||
if (this.pointsBuffer.length >= this.bufferUpdateThreshold || now - this.lastUpdateTime > 1000) {
|
||||
this.updatePointCloudGeometry()
|
||||
this.lastUpdateTime = now
|
||||
}
|
||||
},
|
||||
|
||||
updatePointCloudGeometry() {
|
||||
if (this.pointsBuffer.length === 0) return
|
||||
|
||||
this.points = this.points.concat(this.pointsBuffer)
|
||||
this.pointsBuffer = []
|
||||
|
||||
if (this.points.length > 50000) {
|
||||
this.points = this.points.slice(-40000)
|
||||
}
|
||||
|
||||
const positions = new Float32Array(this.points.length * 3)
|
||||
for (let i = 0; i < this.points.length; i++) {
|
||||
positions[i * 3] = this.points[i].x
|
||||
positions[i * 3 + 1] = this.points[i].y
|
||||
positions[i * 3 + 2] = 0
|
||||
}
|
||||
|
||||
this.pointCloud.geometry.dispose()
|
||||
this.pointCloud.geometry = new THREE.BufferGeometry()
|
||||
this.pointCloud.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
|
||||
},
|
||||
|
||||
updateVehiclePosition() {
|
||||
if (!this.vehicleSprite) return
|
||||
|
||||
this.vehicleSprite.position.set(
|
||||
this.position.x,
|
||||
this.position.y,
|
||||
0
|
||||
)
|
||||
const radians = this.rotation * (Math.PI / 180)
|
||||
|
||||
this.vehicleSprite.rotation.X = radians
|
||||
},
|
||||
|
||||
startRendering() {
|
||||
const render = () => {
|
||||
this.controls.update()
|
||||
this.renderer.render(this.scene, this.camera)
|
||||
this.animationFrameId = requestAnimationFrame(render)
|
||||
}
|
||||
render()
|
||||
},
|
||||
|
||||
handleResize() {
|
||||
const container = this.$refs.container
|
||||
const width = container.clientWidth
|
||||
const height = container.clientHeight
|
||||
const aspect = width / height
|
||||
|
||||
this.camera.left = -this.viewSize * aspect / 2
|
||||
this.camera.right = this.viewSize * aspect / 2
|
||||
this.camera.top = this.viewSize / 2
|
||||
this.camera.bottom = -this.viewSize / 2
|
||||
this.camera.updateProjectionMatrix()
|
||||
|
||||
this.renderer.setSize(width, height)
|
||||
},
|
||||
|
||||
zoomToArea(minX, maxX, minY, maxY) {
|
||||
const width = maxX - minX
|
||||
const height = maxY - minY
|
||||
const centerX = (minX + maxX) / 2
|
||||
const centerY = (minY + maxY) / 2
|
||||
|
||||
const margin = 0.1
|
||||
const targetViewSize = Math.max(width, height) * (1 + margin)
|
||||
|
||||
// 计算合适的缩放级别
|
||||
const zoomLevel = this.viewSize / targetViewSize
|
||||
this.controls.target.set(centerX, centerY, 0)
|
||||
// 通过调整camera.zoom实现平滑缩放
|
||||
this.camera.zoom = Math.min(this.maxZoom, Math.max(this.minZoom, zoomLevel))
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.controls.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.canvas-container
|
||||
position relative
|
||||
display flex
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height calc(100% - 1rem)
|
||||
margin .14rem 0
|
||||
box-shadow inset 1px 1px 7px 2px #4d9bcd
|
||||
overflow hidden
|
||||
.point-cloud-container
|
||||
width 100%
|
||||
height 100%
|
||||
.reload_bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,384 +0,0 @@
|
||||
<template>
|
||||
<div class="point-cloud-map">
|
||||
<div ref="canvasContainer" class="canvas-container"></div>
|
||||
<!-- 加载状态指示器 -->
|
||||
<div v-if="isLoading" class="loading-indicator">
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i> 加载中...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { points } from '../../config/point.js'
|
||||
export default {
|
||||
/* eslint-disable */
|
||||
data () {
|
||||
return {
|
||||
// Three.js核心对象
|
||||
scene: null,
|
||||
camera: null,
|
||||
renderer: null,
|
||||
controls: null,
|
||||
// 点云相关
|
||||
pointCloudGeometry: null,
|
||||
pointCloudMaterial: null,
|
||||
pointCloudMesh: null,
|
||||
pointCount: 0,
|
||||
pointScale: 1.0,
|
||||
// 小车相关
|
||||
vehicleImage: require('../../images/new/agv.png'),
|
||||
carMesh: null,
|
||||
carTexture: null,
|
||||
// WebSocket相关
|
||||
socket: null,
|
||||
isLoading: true,
|
||||
// 动画与性能
|
||||
viewSize: 20,
|
||||
animationId: null,
|
||||
lastUpdateTime: 0,
|
||||
updateInterval: 100, // 限制更新频率,毫秒
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['carPosition']),
|
||||
},
|
||||
watch: {
|
||||
carPosition: {
|
||||
handler(newVal) {
|
||||
this.updateCarPosition(newVal)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 初始化Three.js场景
|
||||
this.initThreeJs();
|
||||
// 初始化控制器(拖动和缩放)
|
||||
this.initControls();
|
||||
// 初始化点云
|
||||
this.initPointCloud();
|
||||
// 初始化小车模型
|
||||
this.initCar();
|
||||
// 初始化WebSocket连接
|
||||
// this.initWebSocket();
|
||||
this.init()
|
||||
// 启动动画循环
|
||||
this.startAnimationLoop();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
// 初始加载完成
|
||||
setTimeout(() => {
|
||||
this.isLoading = false;
|
||||
}, 1000);
|
||||
},
|
||||
beforeDestroy () {
|
||||
// 清理资源
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
}
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
}
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
// 清理Three.js资源
|
||||
if (this.renderer) this.renderer.dispose();
|
||||
if (this.pointCloudGeometry) this.pointCloudGeometry.dispose();
|
||||
if (this.pointCloudMaterial) this.pointCloudMaterial.dispose();
|
||||
if (this.carTexture) this.carTexture.dispose();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化Three.js场景、相机和渲染器
|
||||
*/
|
||||
initThreeJs() {
|
||||
const container = this.$refs.canvasContainer;
|
||||
const aspect = container.clientWidth / container.clientHeight
|
||||
|
||||
// 创建场景
|
||||
this.scene = new THREE.Scene();
|
||||
|
||||
// 创建正交相机(适合2D场景)
|
||||
this.camera = new THREE.OrthographicCamera(
|
||||
-this.viewSize * aspect / 2, // left
|
||||
this.viewSize * aspect / 2, // right
|
||||
this.viewSize / 2, // top
|
||||
-this.viewSize / 2, // bottom
|
||||
0.1, // near
|
||||
1000 // far
|
||||
);
|
||||
this.camera.position.z = 100; // 保持在Z轴上方,不影响2D视图
|
||||
|
||||
// 创建渲染器 - 启用alpha通道实现透明背景
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true, // 关键:启用alpha通道
|
||||
transparent: true // 关键:允许透明
|
||||
});
|
||||
this.renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
// 可选:设置clearAlpha确保完全透明
|
||||
this.renderer.setClearAlpha(0);
|
||||
container.appendChild(this.renderer.domElement);
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化控制器,支持拖动和缩放
|
||||
*/
|
||||
initControls() {
|
||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.controls.enableRotate = false; // 禁用旋转,保持2D视图
|
||||
this.controls.enableZoom = true; // 启用缩放
|
||||
this.controls.enablePan = true; // 启用平移(拖动)
|
||||
this.controls.screenSpacePanning = true; // 2D平移模式
|
||||
this.controls.touchZoomSpeed = 0.5; // 降低触摸缩放速度
|
||||
this.controls.panSpeed = 0.3; // 降低平移速度
|
||||
|
||||
// 鼠标/触摸优化
|
||||
this.controls.mouseButtons = {
|
||||
LEFT: THREE.MOUSE.PAN, // 左键拖动
|
||||
MIDDLE: THREE.MOUSE.DOLLY // 中键缩放(可选)
|
||||
};
|
||||
|
||||
// 触摸支持
|
||||
this.controls.touchAction = 'none';
|
||||
this.controls.touchPan = true;
|
||||
this.controls.touchZoom = true;
|
||||
|
||||
// 添加错误处理逻辑
|
||||
this.controls.addEventListener('error', (event) => {
|
||||
console.error('OrbitControls error:', event);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化点云
|
||||
*/
|
||||
initPointCloud() {
|
||||
// 使用BufferGeometry提高大量点的渲染性能
|
||||
this.pointCloudGeometry = new THREE.BufferGeometry();
|
||||
|
||||
// 点材质设置
|
||||
this.pointCloudMaterial = new THREE.PointsMaterial({
|
||||
color: 0xFFFFFF,
|
||||
size: 1, // 初始值会被动态覆盖
|
||||
sizeAttenuation: true, // 关键!
|
||||
transparent: true,
|
||||
opacity: 0.8
|
||||
});
|
||||
|
||||
// 创建点云网格
|
||||
this.pointCloudMesh = new THREE.Points(
|
||||
this.pointCloudGeometry,
|
||||
this.pointCloudMaterial
|
||||
);
|
||||
this.scene.add(this.pointCloudMesh);
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化小车模型
|
||||
*/
|
||||
initCar() {
|
||||
// 加载小车图片作为精灵
|
||||
const loader = new THREE.TextureLoader();
|
||||
this.carTexture = loader.load(this.vehicleImage, (texture) => {
|
||||
// 97px × 67px图片的比例
|
||||
const imageAspect = 97 / 67
|
||||
const spriteWidth = 2
|
||||
const spriteHeight = spriteWidth / imageAspect
|
||||
|
||||
// 创建平面几何体
|
||||
const geometry = new THREE.PlaneGeometry(spriteWidth, spriteHeight);
|
||||
|
||||
// 创建材质并应用纹理
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.9
|
||||
});
|
||||
|
||||
// 创建网格对象
|
||||
this.carMesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
// 设置锚点为小车中心(以便旋转正确)
|
||||
this.carMesh.position.set(spriteWidth / 2, spriteHeight / 2, 0);
|
||||
|
||||
// 添加到场景中
|
||||
this.scene.add(this.carMesh);
|
||||
|
||||
// 初始位置角度更新
|
||||
if (this.carPosition) {
|
||||
this.updateCarPosition(this.carPosition);
|
||||
}
|
||||
}, undefined, (error) => {
|
||||
console.error('小车图片加载失败:', error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新小车位置和角度
|
||||
*/
|
||||
updateCarPosition(position) {
|
||||
if (this.carMesh && position) {
|
||||
this.carMesh.position.x = position.x * this.pointScale;
|
||||
this.carMesh.position.y = position.y * this.pointScale;
|
||||
// 转换角度为弧度并调整方向(Three.js使用弧度)
|
||||
this.carMesh.rotation.z = THREE.MathUtils.degToRad(position.angle);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化WebSocket连接
|
||||
*/
|
||||
initWebSocket() {
|
||||
const wsHost = process.env.VUE_APP_API_BASE_URL.replace(/^https?:\/\//, '')
|
||||
const sid = this.$store.getters.userInfo === 'true' ? 1 : 2
|
||||
this.socket = new WebSocket(`ws://${wsHost}/webSocket/PointCloudData/${sid}`);
|
||||
|
||||
this.socket.onopen = () => {
|
||||
console.log('WebSocket连接已建立');
|
||||
this.isLoading = false;
|
||||
};
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
try {
|
||||
// 限制更新频率,优化性能
|
||||
const now = Date.now();
|
||||
if (now - this.lastUpdateTime < this.updateInterval) {
|
||||
return;
|
||||
}
|
||||
this.lastUpdateTime = now;
|
||||
|
||||
const pointData = event.data;
|
||||
this.updatePointCloud(pointData);
|
||||
} catch (error) {
|
||||
console.error('解析点云数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.socket.onclose = (event) => {
|
||||
console.log('WebSocket连接已关闭,代码:', event.code);
|
||||
this.isLoading = true;
|
||||
// 自动重连
|
||||
setTimeout(() => this.initWebSocket(), 3000);
|
||||
};
|
||||
|
||||
this.socket.onerror = (error) => {
|
||||
console.error('WebSocket错误:', error);
|
||||
this.isLoading = true;
|
||||
};
|
||||
},
|
||||
|
||||
init () {
|
||||
const pointData = points.data
|
||||
this.updatePointCloud(pointData);
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新点云数据,优化大量点的渲染性能
|
||||
*/
|
||||
updatePointCloud(points) {
|
||||
if (!Array.isArray(points) || points.length === 0) return;
|
||||
|
||||
// 性能优化:仅在点数变化时重新分配缓冲区
|
||||
if (points.length !== this.pointCount) {
|
||||
this.pointCount = points.length;
|
||||
// 创建新的缓冲区 (x, y, z),z始终为0
|
||||
const positions = new Float32Array(this.pointCount * 3);
|
||||
this.pointCloudGeometry.setAttribute(
|
||||
'position',
|
||||
new THREE.BufferAttribute(positions, 3)
|
||||
);
|
||||
}
|
||||
|
||||
// 获取位置缓冲区并更新数据
|
||||
const positionAttribute = this.pointCloudGeometry.getAttribute('position');
|
||||
const positions = positionAttribute.array;
|
||||
|
||||
// 填充数据(z始终为0)
|
||||
for (let i = 0; i < this.pointCount; i++) {
|
||||
const point = points[i];
|
||||
// 假设点数据格式为 {x: number, y: number}
|
||||
positions[i * 3] = (point.x || 0) * this.pointScale;
|
||||
positions[i * 3 + 1] = (point.y || 0) * this.pointScale;
|
||||
positions[i * 3 + 2] = 0; // Z轴固定为0
|
||||
}
|
||||
|
||||
// 标记属性需要更新
|
||||
positionAttribute.needsUpdate = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理窗口大小变化
|
||||
*/
|
||||
handleResize() {
|
||||
const container = this.$refs.canvasContainer;
|
||||
const width = container.clientWidth;
|
||||
const height = container.clientHeight;
|
||||
|
||||
// 更新相机
|
||||
this.camera.left = width / -2;
|
||||
this.camera.right = width / 2;
|
||||
this.camera.top = height / 2;
|
||||
this.camera.bottom = height / -2;
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
// 更新渲染器
|
||||
this.renderer.setSize(width, height);
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动动画循环
|
||||
*/
|
||||
startAnimationLoop() {
|
||||
const animate = () => {
|
||||
this.animationId = requestAnimationFrame(animate);
|
||||
|
||||
// 更新控制器(用于阻尼效果)
|
||||
if (this.controls) {
|
||||
this.controls.update();
|
||||
}
|
||||
|
||||
// 渲染场景
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
};
|
||||
|
||||
animate();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.point-cloud-map
|
||||
position relative
|
||||
width 100%
|
||||
height calc(100% - 1rem)
|
||||
margin .14rem 0
|
||||
background-color rgba(4, 33, 58, 70%)
|
||||
box-shadow inset 1px 1px 7px 2px #4d9bcd
|
||||
overflow hidden
|
||||
.canvas-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
.loading-indicator {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: .01rem .02rem;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
font-size: .12rem;
|
||||
color: #333;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['carPosition'])
|
||||
...mapGetters(['serverUrl', 'carPosition'])
|
||||
},
|
||||
watch: {
|
||||
carPosition: {
|
||||
@@ -172,8 +172,7 @@ export default {
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
const urlHost = process.env.VUE_APP_API_BASE_URL
|
||||
img.src = `${urlHost}${this.mapData.mapImageAddress}`
|
||||
img.src = `${this.serverUrl}${this.mapData.mapImageAddress}`
|
||||
// img.src = this.mapData.mapImageAddress
|
||||
img.onload = () => {
|
||||
this.cachedImages.map = img
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
|
||||
<template>
|
||||
<div class="page_container">
|
||||
<div class="canvas-container">
|
||||
<canvas
|
||||
id="mapCanvas"
|
||||
ref="mapCanvas"
|
||||
@wheel="handleZoom"
|
||||
@click="handleCanvasClick"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@mousedown="handleMouseDown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp"
|
||||
></canvas>
|
||||
<el-row type="flex" justify="end" class="map_tools">
|
||||
<el-button type="primary" :disabled="zoomPercentage === 2" icon="el-icon-minus" size="mini" style="border: 0;border-radius: 0;" @click="zoomOut"></el-button>
|
||||
<div class="zoom_data">{{ zoomPercentage }}%</div>
|
||||
<el-button type="primary" :disabled="zoomPercentage === 200" icon="el-icon-plus" size="mini" style="border: 0;border-radius: 0;" @click="zoomIn"></el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<div
|
||||
v-if="showPopup"
|
||||
class="point-popup"
|
||||
:style="popupStyle"
|
||||
@click.stop
|
||||
>
|
||||
<el-row type="flex" justify="space-between" class="popup-content" style="border-bottom: 1px solid #fff;">
|
||||
<el-col :span="10"><h3>编号</h3></el-col>
|
||||
<el-col :span="14"><p>{{selectedPoint.station_code}}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>别名</h3></el-col>
|
||||
<el-col :span="14"><p>{{selectedPoint.station_name}}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>X坐标</h3></el-col>
|
||||
<el-col :span="14"><p>{{ selectedPoint.x }}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>Y坐标</h3></el-col>
|
||||
<el-col :span="14"><p>{{ selectedPoint.y }}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>角度值</h3></el-col>
|
||||
<el-col :span="14"><p>{{ selectedPoint.angle }}</p></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { throttle } from 'lodash'
|
||||
import markerImage from '@images/new/station.png'
|
||||
import { getMapInfoByCode, getRouteInfo, queryMapAllStation } from '../../config/mork.js'
|
||||
import canvasZoomDrag from '../../config/canvasZoomDrag'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
canvas: null, // Canvas 元素
|
||||
ctx: null, // Canvas 绘图上下文
|
||||
mapData: null, // 地图数据
|
||||
pathData: null, // 路径数据
|
||||
pointData: null, // 点位数据
|
||||
showPopup: false,
|
||||
selectedPoint: {},
|
||||
popupStyle: {left: '0px', top: '0px'},
|
||||
selectedPointId: null
|
||||
}
|
||||
},
|
||||
mixins: [canvasZoomDrag],
|
||||
mounted () {
|
||||
this._getMapInfoByCode()
|
||||
document.addEventListener('click', this.handleDocumentClick)
|
||||
},
|
||||
beforeDestroy () {
|
||||
// 移除事件监听
|
||||
document.removeEventListener('click', this.handleDocumentClick)
|
||||
},
|
||||
methods: {
|
||||
async _getMapInfoByCode () {
|
||||
try {
|
||||
let res = await getMapInfoByCode()
|
||||
if (res) {
|
||||
this.mapData = res
|
||||
this._getRouteInfo()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error(e)
|
||||
}
|
||||
},
|
||||
async _getRouteInfo () {
|
||||
try {
|
||||
let res = await getRouteInfo()
|
||||
this.pathData = [...res]
|
||||
this._queryMapAllStation()
|
||||
} catch (e) {
|
||||
this.$message.error(e)
|
||||
}
|
||||
},
|
||||
async _queryMapAllStation () {
|
||||
try {
|
||||
let res = await queryMapAllStation()
|
||||
const stations = [...res]
|
||||
this.pointData = this.filterPointData(this.pathData, stations)
|
||||
this.initCanvas()
|
||||
} catch (e) {
|
||||
this.$message.error(e)
|
||||
}
|
||||
},
|
||||
filterPointData (routes, stations) {
|
||||
const result = []
|
||||
const seenStationIds = new Set()
|
||||
routes.forEach((route) => {
|
||||
const startStation = stations.find((station) => station.station_id === route.start_id)
|
||||
const endStation = stations.find((station) => station.station_id === route.end_id)
|
||||
if (startStation && !seenStationIds.has(startStation.station_id)) {
|
||||
result.push(startStation)
|
||||
seenStationIds.add(startStation.station_id)
|
||||
}
|
||||
if (endStation && !seenStationIds.has(endStation.station_id)) {
|
||||
result.push(endStation)
|
||||
seenStationIds.add(endStation.station_id) // 标记为已添加
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
initCanvas () {
|
||||
this.canvas = this.$refs.mapCanvas
|
||||
this.ctx = this.canvas.getContext('2d')
|
||||
this.canvas.width = this.mapData.width
|
||||
this.canvas.height = this.mapData.height
|
||||
this.redrawCanvas()
|
||||
},
|
||||
redrawCanvas () {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
const img = new Image()
|
||||
// img.src = `${this.$store.getters.baseUrl}${this.mapData.mapImageAddress}`
|
||||
img.src = `${this.mapData.mapImageAddress}`
|
||||
img.onload = () => {
|
||||
this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height)
|
||||
this.ctx.save()
|
||||
this.drawPath()
|
||||
this.drawMarkers()
|
||||
}
|
||||
},
|
||||
drawPath () {
|
||||
this.ctx.beginPath()
|
||||
this.pathData.forEach((point, index) => {
|
||||
const startX = (point.start_x - this.mapData.x) / this.mapData.resolution
|
||||
const startY = this.mapData.height - (point.start_y - this.mapData.y) / this.mapData.resolution
|
||||
const endX = (point.end_x - this.mapData.x) / this.mapData.resolution
|
||||
const endY = this.mapData.height - (point.end_y - this.mapData.y) / this.mapData.resolution
|
||||
this.ctx.moveTo(startX, startY)
|
||||
this.ctx.lineTo(endX, endY)
|
||||
this.ctx.strokeStyle = '#009de5'
|
||||
this.ctx.lineWidth = 2
|
||||
this.ctx.stroke()
|
||||
})
|
||||
},
|
||||
drawMarkers () {
|
||||
const markerImg = new Image()
|
||||
markerImg.src = markerImage
|
||||
markerImg.onload = () => {
|
||||
this.pointData.forEach(point => {
|
||||
const x = (point.x - this.mapData.x) / this.mapData.resolution
|
||||
const y = this.mapData.height - (point.y - this.mapData.y) / this.mapData.resolution
|
||||
if (point.station_id === this.selectedPointId) {
|
||||
this.ctx.beginPath()
|
||||
this.ctx.arc(x, y, 5, 0, Math.PI * 2)
|
||||
this.ctx.fillStyle = '#59ccd2'
|
||||
this.ctx.fill()
|
||||
} else {
|
||||
this.ctx.drawImage(markerImg, x - 5, y - 5, 10, 10)
|
||||
}
|
||||
this.ctx.font = '12px Arial'
|
||||
this.ctx.fillStyle = 'white'
|
||||
this.ctx.textAlign = 'center'
|
||||
this.ctx.fillText(point.station_name, x, y + 18)
|
||||
})
|
||||
}
|
||||
},
|
||||
handleCanvasClick (event) {
|
||||
const rect = this.canvas.getBoundingClientRect()
|
||||
const mouseX = (event.clientX - rect.left) / this.scale
|
||||
const mouseY = (event.clientY - rect.top) / this.scale
|
||||
this.pointData.forEach(point => {
|
||||
let x = (point.x - this.mapData.x) / this.mapData.resolution
|
||||
let y = this.mapData.height - (point.y - this.mapData.y) / this.mapData.resolution
|
||||
x = Math.abs(x) === 0 ? 0 : x
|
||||
y = Math.abs(y) === 0 ? 0 : y
|
||||
// 检查点击位置是否在标记图片内
|
||||
if (mouseX >= x - 10 && mouseX <= x + 10 && mouseY >= y - 10 && mouseY <= y + 10) {
|
||||
if (this.selectedPointId === point.station_id) {
|
||||
this.resetSelection()
|
||||
} else {
|
||||
this.selectedPointId = point.station_id
|
||||
this.selectedPoint = point
|
||||
this.showPopup = true
|
||||
this.popupStyle = {
|
||||
left: `${event.clientX - 40}px`,
|
||||
top: `${event.clientY - 150}px`
|
||||
}
|
||||
this.initCanvas()
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDocumentClick () {
|
||||
this.resetSelection()
|
||||
},
|
||||
resetSelection () {
|
||||
if (this.selectedPointId) {
|
||||
this.selectedPointId = null
|
||||
this.selectedPoint = null
|
||||
this.showPopup = false
|
||||
this.initCanvas()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.canvas-container
|
||||
position relative
|
||||
display flex
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height calc(100% - .32rem)
|
||||
background-color rgba(4, 33, 58, 70%)
|
||||
box-shadow inset 1px 1px 7px 2px #4d9bcd
|
||||
overflow hidden
|
||||
.map_tools
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
.zoom_data
|
||||
width .6rem
|
||||
font-size .16rem
|
||||
height .32rem
|
||||
line-height .32rem
|
||||
color #00d9f3
|
||||
text-align center
|
||||
border-top 1px solid #009fde
|
||||
border-bottom 1px solid #009fde
|
||||
.point-popup
|
||||
position fixed
|
||||
background rgba(0, 0, 0, 70%)
|
||||
border 1px solid rgba(255, 255, 255, .3)
|
||||
border-radius: 8px;
|
||||
padding 10px
|
||||
box-shadow 0 0px 4px 2px rgba(255,255,255,0.1)
|
||||
z-index 100
|
||||
min-width 150px
|
||||
animation fadeIn 0.2s ease-out
|
||||
h3
|
||||
color #fff
|
||||
font-size 14px
|
||||
line-height 24px
|
||||
text-align center
|
||||
p
|
||||
color #fff
|
||||
font-size 14px
|
||||
line-height 24px
|
||||
text-align center
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
@@ -1,430 +0,0 @@
|
||||
|
||||
<template>
|
||||
<div class="page_container">
|
||||
<div class="canvas-container">
|
||||
<canvas
|
||||
id="mapCanvas"
|
||||
ref="mapCanvas"
|
||||
@wheel="handleZoom"
|
||||
@click="handleCanvasClick"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@mousedown="handleMouseDown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp"
|
||||
></canvas>
|
||||
<el-row type="flex" justify="end" class="map_tools">
|
||||
<el-button type="primary" :disabled="zoomPercentage === 2" icon="el-icon-minus" size="mini" style="border: 0;border-radius: 0;" @click="zoomOut"></el-button>
|
||||
<div class="zoom_data">{{ zoomPercentage }}%</div>
|
||||
<el-button type="primary" :disabled="zoomPercentage === 200" icon="el-icon-plus" size="mini" style="border: 0;border-radius: 0;" @click="zoomIn"></el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<div
|
||||
v-if="showPopup"
|
||||
class="point-popup"
|
||||
:style="popupStyle"
|
||||
@click.stop
|
||||
>
|
||||
<el-row type="flex" justify="space-between" class="popup-content" style="border-bottom: 1px solid #fff;">
|
||||
<el-col :span="10"><h3>编号</h3></el-col>
|
||||
<el-col :span="14"><p>{{selectedPoint.station_code}}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>别名</h3></el-col>
|
||||
<el-col :span="14"><p>{{selectedPoint.station_name}}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>X坐标</h3></el-col>
|
||||
<el-col :span="14"><p>{{ selectedPoint.x }}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>Y坐标</h3></el-col>
|
||||
<el-col :span="14"><p>{{ selectedPoint.y }}</p></el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between" class="popup-content">
|
||||
<el-col :span="10"><h3>角度值</h3></el-col>
|
||||
<el-col :span="14"><p>{{ selectedPoint.angle }}</p></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { throttle } from 'lodash'
|
||||
import markerImage from '@images/new/station.png'
|
||||
import carImage from '../../images/new/car.png'
|
||||
import { getMapInfoByCode, getRouteInfo, queryMapAllStation } from '../../config/getData.js'
|
||||
import canvasZoomDrag from '../../config/canvasZoomDrag'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
canvas: null, // Canvas 元素
|
||||
ctx: null, // Canvas 绘图上下文
|
||||
mapData: null, // 地图数据
|
||||
pathData: null, // 路径数据
|
||||
pointData: null, // 点位数据
|
||||
showPopup: false,
|
||||
selectedPoint: {},
|
||||
popupStyle: {left: '0px', top: '0px'},
|
||||
selectedPointId: null,
|
||||
cachedImages: {
|
||||
map: null,
|
||||
marker: null,
|
||||
car: null
|
||||
},
|
||||
imageLoadStatus: {
|
||||
map: false,
|
||||
marker: false,
|
||||
car: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['agvObj'])
|
||||
},
|
||||
mixins: [canvasZoomDrag],
|
||||
mounted () {
|
||||
this.preloadMarkerImage()
|
||||
this.preloadCarImage()
|
||||
this.loadAllDataInParallel()
|
||||
document.addEventListener('click', this.handleDocumentClick)
|
||||
this.carPositionWatcher = this.$watch(
|
||||
() => this.agvObj,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.x !== undefined && newVal.y !== undefined) {
|
||||
this.redrawCanvas()
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
},
|
||||
beforeDestroy () {
|
||||
document.removeEventListener('click', this.handleDocumentClick)
|
||||
if (this.carPositionWatcher) {
|
||||
this.carPositionWatcher()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
preloadCarImage () {
|
||||
if (this.cachedImages.car) {
|
||||
this.imageLoadStatus.car = true
|
||||
return
|
||||
}
|
||||
const img = new Image()
|
||||
img.src = carImage
|
||||
img.onload = () => {
|
||||
this.cachedImages.car = img
|
||||
this.imageLoadStatus.car = true
|
||||
this.checkImagesLoadedAndRedraw()
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.error('小车图片加载失败')
|
||||
this.imageLoadStatus.car = true
|
||||
this.checkImagesLoadedAndRedraw()
|
||||
}
|
||||
},
|
||||
preloadMarkerImage () {
|
||||
if (this.cachedImages.marker) {
|
||||
this.imageLoadStatus.marker = true
|
||||
return
|
||||
}
|
||||
const img = new Image()
|
||||
img.src = markerImage
|
||||
img.onload = () => {
|
||||
this.cachedImages.marker = img
|
||||
this.imageLoadStatus.marker = true
|
||||
this.checkImagesLoadedAndRedraw()
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.error('标记图片加载失败')
|
||||
this.imageLoadStatus.marker = true
|
||||
this.checkImagesLoadedAndRedraw()
|
||||
}
|
||||
},
|
||||
loadMapImage () {
|
||||
if (!this.mapData.mapImageAddress) {
|
||||
return Promise.reject(new Error('地图数据缺失'))
|
||||
}
|
||||
if (this.cachedImages.map && this.imageLoadStatus.map) {
|
||||
return Promise.resolve(this.cachedImages.map)
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = `${this.$store.getters.baseUrl}${this.mapData.mapImageAddress}`
|
||||
// img.src = this.mapData.mapImageAddress
|
||||
img.onload = () => {
|
||||
this.cachedImages.map = img
|
||||
this.imageLoadStatus.map = true
|
||||
this.checkImagesLoadedAndRedraw()
|
||||
resolve(img)
|
||||
}
|
||||
img.onerror = (error) => {
|
||||
console.error('地图图片加载失败:', error)
|
||||
this.imageLoadStatus.map = true
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
},
|
||||
checkImagesLoadedAndRedraw () {
|
||||
if (this.imageLoadStatus.map && this.imageLoadStatus.marker && this.imageLoadStatus.car) {
|
||||
this.redrawCanvas()
|
||||
}
|
||||
},
|
||||
async loadAllDataInParallel () {
|
||||
try {
|
||||
this.loading = this.$loading({
|
||||
lock: true,
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.6)'
|
||||
})
|
||||
const [mapData, pathData, stations] = await Promise.all([
|
||||
this._getMapInfoByCode(),
|
||||
this._getRouteInfo(),
|
||||
this._queryMapAllStation()
|
||||
])
|
||||
this.mapData = mapData
|
||||
this.pathData = [...pathData]
|
||||
this.pointData = this.filterPointData(pathData, stations)
|
||||
this.initCanvas()
|
||||
await this.loadMapImage()
|
||||
this.loading.close()
|
||||
} catch (e) {
|
||||
this.$message.error(`数据加载失败: ${e.message || '未知错误'}`)
|
||||
this.loading.close()
|
||||
}
|
||||
},
|
||||
async _getMapInfoByCode () {
|
||||
try {
|
||||
const res = await getMapInfoByCode()
|
||||
if (!res) throw new Error('地图信息为空')
|
||||
return res
|
||||
} catch (e) {
|
||||
console.error('获取地图信息失败:', e)
|
||||
throw new Error(`获取地图信息失败: ${e.message}`)
|
||||
}
|
||||
},
|
||||
async _getRouteInfo () {
|
||||
try {
|
||||
const res = await getRouteInfo()
|
||||
if (!res) throw new Error('路径信息为空')
|
||||
return res
|
||||
} catch (e) {
|
||||
console.error('获取路径信息失败:', e)
|
||||
throw new Error(`获取路径信息失败: ${e.message}`)
|
||||
}
|
||||
},
|
||||
async _queryMapAllStation () {
|
||||
try {
|
||||
const res = await queryMapAllStation()
|
||||
if (!res) throw new Error('站点信息为空')
|
||||
return res
|
||||
} catch (e) {
|
||||
console.error('获取站点信息失败:', e)
|
||||
throw new Error(`获取站点信息失败: ${e.message}`)
|
||||
}
|
||||
},
|
||||
filterPointData (routes, stations) {
|
||||
const result = []
|
||||
const seenStationIds = new Set()
|
||||
routes.forEach(route => {
|
||||
const startStation = stations.find(s => s.station_id === route.start_id)
|
||||
const endStation = stations.find(s => s.station_id === route.end_id)
|
||||
if (startStation && !seenStationIds.has(startStation.station_id)) {
|
||||
result.push(startStation)
|
||||
seenStationIds.add(startStation.station_id)
|
||||
}
|
||||
if (endStation && !seenStationIds.has(endStation.station_id)) {
|
||||
result.push(endStation)
|
||||
seenStationIds.add(endStation.station_id)
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
initCanvas () {
|
||||
this.canvas = this.$refs.mapCanvas
|
||||
this.ctx = this.canvas.getContext('2d')
|
||||
this.canvas.width = this.mapData.width
|
||||
this.canvas.height = this.mapData.height
|
||||
this.redrawCanvas()
|
||||
},
|
||||
redrawCanvas: throttle(function () {
|
||||
if (!this.ctx || !this.mapData) return
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
this.ctx.save()
|
||||
// 绘制地图背景
|
||||
if (this.cachedImages.map) {
|
||||
this.ctx.drawImage(
|
||||
this.cachedImages.map,
|
||||
0, 0,
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
)
|
||||
} else {
|
||||
this.ctx.fillStyle = '#ccc'
|
||||
this.ctx.fillText('地图加载中...', 50, 50)
|
||||
}
|
||||
this.drawPath()
|
||||
this.drawMarkers()
|
||||
this.drawCar()
|
||||
this.ctx.restore()
|
||||
}, 30),
|
||||
drawPath () {
|
||||
if (!this.pathData.length) return
|
||||
this.ctx.beginPath()
|
||||
this.pathData.forEach((point, index) => {
|
||||
const startX = (point.start_x - this.mapData.x) / this.mapData.resolution
|
||||
const startY = this.mapData.height - (point.start_y - this.mapData.y) / this.mapData.resolution
|
||||
const endX = (point.end_x - this.mapData.x) / this.mapData.resolution
|
||||
const endY = this.mapData.height - (point.end_y - this.mapData.y) / this.mapData.resolution
|
||||
this.ctx.moveTo(startX, startY)
|
||||
this.ctx.lineTo(endX, endY)
|
||||
})
|
||||
this.ctx.strokeStyle = '#009de5'
|
||||
this.ctx.lineWidth = 2
|
||||
this.ctx.stroke()
|
||||
},
|
||||
drawMarkers () {
|
||||
if (!this.pointData.length) return
|
||||
const markerSize = 10 // 标记大小随缩放变化
|
||||
this.pointData.forEach(point => {
|
||||
const x = (point.x - this.mapData.x) / this.mapData.resolution
|
||||
const y = this.mapData.height - (point.y - this.mapData.y) / this.mapData.resolution
|
||||
// 绘制选中状态
|
||||
if (point.station_id === this.selectedPointId) {
|
||||
this.ctx.beginPath()
|
||||
this.ctx.arc(x, y, 5, 0, Math.PI * 2)
|
||||
this.ctx.fillStyle = '#59ccd2'
|
||||
this.ctx.fill()
|
||||
} else if (this.cachedImages.marker) {
|
||||
this.ctx.drawImage(
|
||||
this.cachedImages.marker,
|
||||
x - markerSize / 2, // 居中对齐
|
||||
y - markerSize / 2,
|
||||
markerSize,
|
||||
markerSize
|
||||
)
|
||||
}
|
||||
// 绘制点位名称(文字大小随缩放变化)
|
||||
this.ctx.font = '12px Arial'
|
||||
this.ctx.fillStyle = 'white'
|
||||
this.ctx.textAlign = 'center'
|
||||
this.ctx.fillText(point.station_name, x, y + 18)
|
||||
})
|
||||
},
|
||||
drawCar () {
|
||||
console.log(this.$store.getters.agvObj)
|
||||
if (!this.agvObj || !this.cachedImages.car || !this.mapData) return
|
||||
const carX = (this.agvObj.x - this.mapData.x) / this.mapData.resolution
|
||||
const carY = this.mapData.height - (this.agvObj.y - this.mapData.y) / this.mapData.resolution
|
||||
this.ctx.drawImage(
|
||||
this.cachedImages.car,
|
||||
carX - 35,
|
||||
carY - 21,
|
||||
70,
|
||||
42
|
||||
)
|
||||
},
|
||||
handleCanvasClick (event) {
|
||||
const rect = this.canvas.getBoundingClientRect()
|
||||
const mouseX = (event.clientX - rect.left) / this.scale
|
||||
const mouseY = (event.clientY - rect.top) / this.scale
|
||||
this.pointData.forEach(point => {
|
||||
let x = (point.x - this.mapData.x) / this.mapData.resolution
|
||||
let y = this.mapData.height - (point.y - this.mapData.y) / this.mapData.resolution
|
||||
x = Math.abs(x) === 0 ? 0 : x
|
||||
y = Math.abs(y) === 0 ? 0 : y
|
||||
// 检查点击位置是否在标记图片内
|
||||
if (mouseX >= x - 10 && mouseX <= x + 10 && mouseY >= y - 10 && mouseY <= y + 10) {
|
||||
this.handlePointSelect(point, event)
|
||||
event.stopPropagation()
|
||||
}
|
||||
})
|
||||
},
|
||||
handlePointSelect (point, event) {
|
||||
if (this.selectedPointId === point.station_id) {
|
||||
this.resetSelection()
|
||||
} else {
|
||||
this.selectedPointId = point.station_id
|
||||
this.selectedPoint = point
|
||||
this.showPopup = true
|
||||
this.calculatePopupPosition(event)
|
||||
this.redrawCanvas()
|
||||
}
|
||||
},
|
||||
calculatePopupPosition (event) {
|
||||
const popupWidth = 200
|
||||
const popupHeight = 180
|
||||
let left = event.clientX - 40
|
||||
let top = event.clientY - 150
|
||||
|
||||
// 限制弹窗在窗口可视范围内
|
||||
left = Math.max(10, Math.min(left, window.innerWidth - popupWidth - 10))
|
||||
top = Math.max(10, Math.min(top, window.innerHeight - popupHeight - 10))
|
||||
|
||||
this.popupStyle = { left: `${left}px`, top: `${top}px` }
|
||||
},
|
||||
handleDocumentClick () {
|
||||
this.resetSelection()
|
||||
},
|
||||
resetSelection () {
|
||||
if (this.selectedPointId) {
|
||||
this.selectedPointId = null
|
||||
this.selectedPoint = null
|
||||
this.showPopup = false
|
||||
this.redrawCanvas()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.canvas-container
|
||||
position relative
|
||||
display flex
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height calc(100% - .32rem)
|
||||
background-color rgba(4, 33, 58, 70%)
|
||||
box-shadow inset 1px 1px 7px 2px #4d9bcd
|
||||
overflow hidden
|
||||
.map_tools
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
.zoom_data
|
||||
width .6rem
|
||||
font-size .16rem
|
||||
height .32rem
|
||||
line-height .32rem
|
||||
color #00d9f3
|
||||
text-align center
|
||||
border-top 1px solid #009fde
|
||||
border-bottom 1px solid #009fde
|
||||
.point-popup
|
||||
position fixed
|
||||
background rgba(0, 0, 0, 70%)
|
||||
border 1px solid rgba(255, 255, 255, .3)
|
||||
border-radius: 8px;
|
||||
padding 10px
|
||||
box-shadow 0 0px 4px 2px rgba(255,255,255,0.1)
|
||||
z-index 100
|
||||
min-width 150px
|
||||
animation fadeIn 0.2s ease-out
|
||||
h3
|
||||
color #fff
|
||||
font-size 14px
|
||||
line-height 24px
|
||||
text-align center
|
||||
p
|
||||
color #fff
|
||||
font-size 14px
|
||||
line-height 24px
|
||||
text-align center
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
295
src/pages/modules/point-cloud-map.vue
Normal file
295
src/pages/modules/point-cloud-map.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<div class="point-cloud-map">
|
||||
<div ref="canvasContainer" class="canvas-container"></div>
|
||||
<div v-if="isLoading" class="loading-indicator">
|
||||
<i class="fa fa-circle-o-notch fa-spin"></i> 加载中...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Application, ParticleContainer, Sprite, Graphics, Texture } from 'pixi.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { points } from '../../config/point.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// PixiJS核心对象
|
||||
app: null,
|
||||
pointContainer: null,
|
||||
carSprite: null,
|
||||
|
||||
// 点云数据
|
||||
allPoints: [],
|
||||
pointScale: 1.0,
|
||||
|
||||
// 小车资源
|
||||
vehicleImage: require('../../images/new/agv.png'),
|
||||
carTexture: null,
|
||||
|
||||
// WebSocket
|
||||
socket: null,
|
||||
isLoading: true,
|
||||
|
||||
// 性能控制
|
||||
lastUpdateTime: 0,
|
||||
updateInterval: 800,
|
||||
isDestroyed: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['serverUrl', 'userRole', 'carPosition']),
|
||||
},
|
||||
watch: {
|
||||
carPosition: {
|
||||
handler(newVal) {
|
||||
this.updateCarPosition(newVal)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 使用$nextTick确保DOM完全渲染后再初始化
|
||||
this.$nextTick(() => {
|
||||
this.initPixi()
|
||||
// this.initWebSocket()
|
||||
this.init()
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
setTimeout(() => {
|
||||
this.isLoading = false
|
||||
}, 1000)
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.isDestroyed = true
|
||||
|
||||
// 清理WebSocket
|
||||
if (this.socket) {
|
||||
this.socket.close()
|
||||
this.socket = null
|
||||
}
|
||||
|
||||
// 销毁Pixi应用
|
||||
if (this.app) {
|
||||
this.app.destroy()
|
||||
const container = this.$refs.canvasContainer
|
||||
if (container && container.children.length > 0) {
|
||||
container.removeChild(container.children[0])
|
||||
}
|
||||
this.app = null
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
if (this.carTexture) {
|
||||
this.carTexture.destroy()
|
||||
this.carTexture = null
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
this.allPoints = []
|
||||
},
|
||||
methods: {
|
||||
async initPixi() {
|
||||
// 确保容器元素存在
|
||||
const container = this.$refs.canvasContainer
|
||||
if (!container) {
|
||||
console.error('Canvas容器未找到')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// PixiJS v8+ 使用 Application.init() 替代 new Application()
|
||||
this.app = await Application.init({
|
||||
width: container.clientWidth,
|
||||
height: container.clientHeight,
|
||||
backgroundColor: 0x0c0c0c,
|
||||
antialias: true,
|
||||
resolution: window.devicePixelRatio || 1,
|
||||
autoDensity: true,
|
||||
})
|
||||
|
||||
// PixiJS v8+ 使用 canvas 属性替代 view 属性
|
||||
if (this.app.canvas) {
|
||||
container.appendChild(this.app.canvas)
|
||||
} else {
|
||||
console.error('PixiJS未能创建有效的canvas')
|
||||
return
|
||||
}
|
||||
|
||||
// 创建点容器 - 使用ParticleContainer提高性能
|
||||
this.pointContainer = new ParticleContainer(10000, {
|
||||
position: true,
|
||||
rotation: true,
|
||||
tint: true,
|
||||
alpha: true
|
||||
})
|
||||
this.app.stage.addChild(this.pointContainer)
|
||||
|
||||
// 加载小车纹理
|
||||
this.carTexture = await Texture.fromURL(this.vehicleImage)
|
||||
this.initCar()
|
||||
} catch (error) {
|
||||
console.error('Pixi初始化失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
initCar() {
|
||||
if (!this.carTexture) {
|
||||
console.error('小车纹理未加载')
|
||||
return
|
||||
}
|
||||
|
||||
this.carSprite = new Sprite(this.carTexture)
|
||||
this.carSprite.anchor.set(0.5) // 设置锚点为中心
|
||||
this.carSprite.width = 30
|
||||
this.carSprite.height = 30
|
||||
this.app.stage.addChild(this.carSprite)
|
||||
|
||||
if (this.carPosition) {
|
||||
this.updateCarPosition(this.carPosition)
|
||||
}
|
||||
},
|
||||
|
||||
updateCarPosition(position) {
|
||||
if (!this.carSprite || !position) return
|
||||
|
||||
this.carSprite.x = position.x * this.pointScale
|
||||
this.carSprite.y = position.y * this.pointScale
|
||||
this.carSprite.rotation = position.angle * (Math.PI / 180) // 转换为弧度
|
||||
},
|
||||
|
||||
initWebSocket() {
|
||||
const wsHost = this.serverUrl.replace(/^https?:\/\//, '')
|
||||
this.socket = new WebSocket(`ws://${wsHost}/webSocket/PointCloudData/${this.userRole}`)
|
||||
|
||||
this.socket.onopen = () => {
|
||||
console.log('WebSocket连接已建立')
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
if (this.isDestroyed) return
|
||||
|
||||
try {
|
||||
const now = Date.now()
|
||||
if (now - this.lastUpdateTime < this.updateInterval) return
|
||||
this.lastUpdateTime = now
|
||||
|
||||
const pointData = JSON.parse(event.data)
|
||||
this.updatePointCloud(pointData.data)
|
||||
} catch (error) {
|
||||
console.error('解析点云数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
this.socket.onclose = (event) => {
|
||||
console.log('WebSocket连接已关闭,代码:', event.code)
|
||||
this.isLoading = true
|
||||
setTimeout(() => this.initWebSocket(), 3000)
|
||||
}
|
||||
|
||||
this.socket.onerror = (error) => {
|
||||
console.error('WebSocket错误:', error)
|
||||
this.isLoading = true
|
||||
}
|
||||
},
|
||||
|
||||
updatePointCloud(points) {
|
||||
if (!Array.isArray(points) || points.length === 0) return
|
||||
|
||||
// 去重逻辑
|
||||
const existingPoints = {}
|
||||
this.allPoints.forEach(point => {
|
||||
existingPoints[`${point.x},${point.y}`] = true
|
||||
})
|
||||
|
||||
const newUniquePoints = points.filter(point => {
|
||||
const key = `${point.x},${point.y}`
|
||||
if (!existingPoints[key]) {
|
||||
existingPoints[key] = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
this.allPoints = [...this.allPoints, ...newUniquePoints]
|
||||
|
||||
// 限制总点数
|
||||
const maxPoints = 5000
|
||||
if (this.allPoints.length > maxPoints) {
|
||||
this.allPoints = this.allPoints.slice(-maxPoints)
|
||||
}
|
||||
|
||||
// 清空容器
|
||||
this.pointContainer.removeChildren()
|
||||
|
||||
// 添加新点 - 使用Graphics批量绘制
|
||||
const graphics = new Graphics()
|
||||
graphics.beginFill(0xFFFFFF, 0.8)
|
||||
|
||||
this.allPoints.forEach(point => {
|
||||
graphics.drawCircle(
|
||||
point.x * this.pointScale,
|
||||
point.y * this.pointScale,
|
||||
1
|
||||
)
|
||||
})
|
||||
|
||||
graphics.endFill()
|
||||
this.pointContainer.addChild(graphics)
|
||||
},
|
||||
|
||||
handleResize() {
|
||||
if (!this.app || !this.$refs.canvasContainer) return
|
||||
|
||||
const container = this.$refs.canvasContainer
|
||||
this.app.renderer.resize(
|
||||
container.clientWidth,
|
||||
container.clientHeight
|
||||
)
|
||||
},
|
||||
|
||||
init () {
|
||||
this.isLoading = false
|
||||
const pointData = points.data
|
||||
this.updatePointCloud(pointData);
|
||||
setTimeout(() => {
|
||||
const arr = [{x: 2, y: 2}, {x: 4, y: 4}]
|
||||
this.updatePointCloud(arr);
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.point-cloud-map
|
||||
position relative
|
||||
width 100%
|
||||
height calc(100% - 1rem)
|
||||
margin .14rem 0
|
||||
background-color rgba(4, 33, 58, 70%)
|
||||
box-shadow inset 1px 1px 7px 2px #4d9bcd
|
||||
overflow hidden
|
||||
|
||||
.canvas-container
|
||||
width 100%
|
||||
height 100%
|
||||
position relative
|
||||
z-index 0
|
||||
|
||||
.loading-indicator
|
||||
position absolute
|
||||
top 50%
|
||||
left 50%
|
||||
transform translate(-50%, -50%)
|
||||
background-color rgba(255, 255, 255, 0.8)
|
||||
padding .01rem .02rem
|
||||
border-radius 4px
|
||||
box-shadow 0 2px 10px rgba(0, 0, 0, 0.1)
|
||||
font-size .12rem
|
||||
color #333
|
||||
z-index 100
|
||||
display flex
|
||||
align-items center
|
||||
</style>
|
||||
@@ -17,6 +17,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<p class="tip">{{$t('Parameterconfiguration')}}</p>
|
||||
<el-form-item label="服务IP" prop="serverIp">
|
||||
<el-input placeholder="请输入服务IP" v-model="dataForm.serverIp" id="ip" @focus="show" data-layout="normal"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('SchedulingIP')" prop="ip">
|
||||
<el-input :placeholder="$t('PleaseIP')" v-model="dataForm.ip" id="ip" @focus="show" data-layout="normal"></el-input>
|
||||
</el-form-item>
|
||||
@@ -33,16 +36,21 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataForm: {
|
||||
selectedLanguage: '',
|
||||
serverIp: '',
|
||||
ip: '',
|
||||
wifi: ''
|
||||
},
|
||||
dataRule: {
|
||||
serverIp: [
|
||||
{ required: true, message: '服务IP不能为空', trigger: 'blur' }
|
||||
],
|
||||
ip: [
|
||||
{ required: true, message: this.$t('Schedulingnotempty'), trigger: 'blur' }
|
||||
],
|
||||
@@ -64,13 +72,25 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['serverUrl']),
|
||||
},
|
||||
watch: {
|
||||
serverUrl (newVal) {
|
||||
if (newVal) {
|
||||
this.dataForm.serverIp = newVal
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setServerUrl']),
|
||||
init () {
|
||||
if (this.$i18n.locale === 'zh-cn') {
|
||||
this.dataForm.selectedLanguage = 'zh-cn'
|
||||
} else if (this.$i18n.locale === 'en-us') {
|
||||
this.dataForm.selectedLanguage = 'en-us'
|
||||
}
|
||||
this.dataForm.serverIp = this.serverUrl
|
||||
this.dialogVisible = true
|
||||
},
|
||||
exitUser () {
|
||||
@@ -82,6 +102,7 @@ export default {
|
||||
this.visible = false
|
||||
this.$i18n.locale = this.dataForm.selectedLanguage
|
||||
window.localStorage.setItem('locale', this.dataForm.selectedLanguage)
|
||||
this.setServerUrl(this.dataForm.serverIp)
|
||||
},
|
||||
handleClose (done) {
|
||||
this.visible = false
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="absolute elec-qty-border"></div>
|
||||
<div class="elec-txt"></div>
|
||||
</div>
|
||||
<i class="el-icon-user-solid icon-user" :style="{'color': $store.getters.userInfo === 'true' ? '#00d0fc' : '#737f92'}" @click="loginModalHandle"></i>
|
||||
<i class="el-icon-user-solid icon-user" :style="{'color': userRole === 1 ? '#00d0fc' : '#737f92'}" @click="loginModalHandle"></i>
|
||||
<i class="el-icon-s-tools icon-tools" @click="configModalHandle"></i>
|
||||
</el-row>
|
||||
</el-col>
|
||||
@@ -47,7 +47,7 @@
|
||||
<script>
|
||||
import LoginModal from './login-modal.vue'
|
||||
import ConfigModal from './config-modal.vue'
|
||||
import { sendWebsocket, closeWebsocket } from '@/config/websocket.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'ShellIndex',
|
||||
components: {
|
||||
@@ -56,7 +56,8 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
timer: null,
|
||||
websocket: null, // WebSocket实例
|
||||
reconnectTimer: null, // 重连计时器
|
||||
topInfo: {},
|
||||
// topInfo: {
|
||||
// batteryPower: -1.0,
|
||||
@@ -85,6 +86,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['serverUrl', 'userRole']),
|
||||
exception () {
|
||||
let str = ''
|
||||
if (JSON.stringify(this.topInfo) !== '{}') {
|
||||
@@ -94,23 +96,20 @@ export default {
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this._queryHead()
|
||||
},
|
||||
mounted () {
|
||||
this.checkTextOverflow()
|
||||
window.addEventListener('resize', this.checkTextOverflow)
|
||||
// this.$store.dispatch('setAgvObj', this.topInfo)
|
||||
// this.taskSeq = this.topInfo.task_seq.split('-')
|
||||
// const target = this.topInfo.task_point
|
||||
// this.currentStep = this.taskSeq.findIndex(item => item === target)
|
||||
this.initWebSocket()
|
||||
},
|
||||
mounted () {
|
||||
this.checkTextOverflow()
|
||||
window.addEventListener('resize', this.checkTextOverflow)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('resize', this.checkTextOverflow)
|
||||
clearTimeout(this.timer)
|
||||
closeWebsocket(true)
|
||||
},
|
||||
destroyed () {
|
||||
clearTimeout(this.timer)
|
||||
this.closeWebSocket() // 组件销毁时关闭连接
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
},
|
||||
methods: {
|
||||
// 滚动区域
|
||||
@@ -143,23 +142,52 @@ export default {
|
||||
this.$refs.configModal.init()
|
||||
})
|
||||
},
|
||||
_queryHead () {
|
||||
const wsHost = process.env.VUE_APP_API_BASE_URL.replace(/^https?:\/\//, '')
|
||||
const sid = this.$store.getters.userInfo === 'true' ? 1 : 2
|
||||
sendWebsocket(`ws://${wsHost}/webSocket/VehicleInfo/${sid}`, {}, this.wsMessage, this.wsErr)
|
||||
// 初始化WebSocket连接
|
||||
initWebSocket () {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
const wsHost = this.serverUrl.replace(/^https?:\/\//, '')
|
||||
const wsUrl = `ws://${wsHost}/webSocket/VehicleInfo/${this.userRole}`
|
||||
this.closeWebSocket()
|
||||
this.websocket = new WebSocket(wsUrl)
|
||||
this.websocket.onopen = () => {}
|
||||
this.websocket.onmessage = (event) => {
|
||||
try {
|
||||
const res = JSON.parse(event.data)
|
||||
this.handleWebSocketMessage(res)
|
||||
} catch (error) {
|
||||
console.error('WebSocket消息解析失败:', error)
|
||||
}
|
||||
}
|
||||
this.websocket.onerror = (error) => {
|
||||
this.$message.error('WebSocket连接错误:', error)
|
||||
}
|
||||
this.websocket.onclose = (event) => {
|
||||
console.log(`WebSocket连接关闭: 代码=${event.code}, 原因=${event.reason}`)
|
||||
this.reconnectWebSocket()
|
||||
}
|
||||
},
|
||||
wsMessage (res) {
|
||||
clearTimeout(this.timer)
|
||||
handleWebSocketMessage (res) {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
this.topInfo = res.data
|
||||
if (this.topInfo.task_seq) {
|
||||
this.taskSeq = this.topInfo.task_seq.split('-')
|
||||
const target = this.topInfo.task_point
|
||||
this.currentStep = this.taskSeq.findIndex(item => item === target)
|
||||
}
|
||||
this.$store.dispatch('setAgvObj', this.topInfo)
|
||||
},
|
||||
wsErr () {
|
||||
this.timer = setTimeout(() => {
|
||||
this._queryHead()
|
||||
}, 10000)
|
||||
closeWebSocket () {
|
||||
if (this.websocket) {
|
||||
this.websocket.close(1000, '正常关闭')
|
||||
this.websocket = null
|
||||
}
|
||||
},
|
||||
reconnectWebSocket () {
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.$message.error('尝试重新连接WebSocket...')
|
||||
this.initWebSocket()
|
||||
}, 5000) // 5秒后重连
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
261
src/pages/shells/index的副本.vue
Normal file
261
src/pages/shells/index的副本.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<div class="zbox body-container" :class="{'enClass': $i18n.locale === 'en-us'}">
|
||||
<el-row type="flex" class="header-container" justify="space-between" align="middle">
|
||||
<el-col :span="3">
|
||||
<el-button type="primary" icon="el-icon-s-home" class="button-home" @click="$router.push('/index/home')">{{ $t('index') }}</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-row type="flex" justify="end" align="middle">
|
||||
<div v-if="JSON.stringify(topInfo) !== '{}'" class="state-item">{{ topInfo.isManual ? '手动模式' : '自动模式' }}</div>
|
||||
<div v-if="JSON.stringify(topInfo) !== '{}'" class="state-item">{{ topInfo.state }}</div>
|
||||
<div v-if="JSON.stringify(topInfo) !== '{}'" class="relative elec-qty-wrap" :class="{'elec-wraning': topInfo.batteryPower <= 40}">
|
||||
<div class="absolute elec-qty" :style="{ width: Number(topInfo.batteryPower) !== -1 ? `calc(100% - ${topInfo.batteryPower}%)` : '100%' }"></div>
|
||||
<div class="absolute elec-qty-border"></div>
|
||||
<div class="elec-txt">{{Number(topInfo.batteryPower) !== -1 ? `${topInfo.batteryPower}%` : '0'}}</div>
|
||||
</div>
|
||||
<div v-else class="relative elec-qty-wrap elec-wraning">
|
||||
<div class="absolute elec-qty" style="width: 100%"></div>
|
||||
<div class="absolute elec-qty-border"></div>
|
||||
<div class="elec-txt"></div>
|
||||
</div>
|
||||
<i class="el-icon-user-solid icon-user" :style="{'color': userRole === 1 ? '#00d0fc' : '#737f92'}" @click="loginModalHandle"></i>
|
||||
<i class="el-icon-s-tools icon-tools" @click="configModalHandle"></i>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-if="exception !== ''" class="error-tips" ref="scrollContainer" @click="$router.push('/index/warning')">
|
||||
<div class="zbox scroll-text" ref="scrollText">
|
||||
<i class="el-icon-warning icon-warning"></i>
|
||||
<div class="error-tips-t">错误提示:{{ exception }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-row type="flex" class="relative main-conatiner" :style="{'paddingTop': exception !== '' ? '.3rem' : '0'}">
|
||||
<div class="absolute hud_left"></div>
|
||||
<div class="absolute hud_left hud_right"></div>
|
||||
<router-view></router-view>
|
||||
</el-row>
|
||||
<div v-if="JSON.stringify(topInfo) !== '{}'" class="task-tips">
|
||||
<el-steps :active="currentStep" simple>
|
||||
<el-step v-for="(e, i) in taskSeq" :key="i" :title="e" icon="el-icon-location"></el-step>
|
||||
</el-steps>
|
||||
</div>
|
||||
<login-modal v-if="loginVisible" ref="loginModal"/>
|
||||
<config-modal v-if="configVisible" ref="configModal"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoginModal from './login-modal.vue'
|
||||
import ConfigModal from './config-modal.vue'
|
||||
import { sendWebsocket, closeWebsocket } from '@/config/websocket.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'ShellIndex',
|
||||
components: {
|
||||
LoginModal,
|
||||
ConfigModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
timer: null,
|
||||
topInfo: {},
|
||||
// topInfo: {
|
||||
// batteryPower: -1.0,
|
||||
// exceptionInfo: {
|
||||
// exception: ['重定位失败,检查环境或标记是否有变化.', '定位超时,尝试移动小车位置后重试.', '定位超时,尝试移动小车位置后重试.', '定位超时,尝试移动小车位置后重试.'],
|
||||
// exceptionCodes: [1, 17179869185, 1335734829057]
|
||||
// },
|
||||
// id: '1', // 车号ID
|
||||
// ip: '192.168.100.82', // 车辆IP
|
||||
// isManual: false, // 是否是手动,true是手动,false是自动
|
||||
// mapId: 39, // 地图ID
|
||||
// mapName: 'apt_map_1753078481180', // 地图名称
|
||||
// name: 'V1', // 车辆名称
|
||||
// state: '未知状态', // 车辆状态
|
||||
// stateId: 7, // 车辆状态ID
|
||||
// theta: 0.9073792099952698, // 车辆航向角
|
||||
// x: 0.004027, // 车辆x坐标
|
||||
// y: -0.001812, // 车辆y坐标
|
||||
// task_seq: 'B1-C',
|
||||
// task_point: 'C'
|
||||
// },
|
||||
taskSeq: '',
|
||||
currentStep: 0,
|
||||
loginVisible: false,
|
||||
configVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['serverUrl', 'userRole']),
|
||||
exception () {
|
||||
let str = ''
|
||||
if (JSON.stringify(this.topInfo) !== '{}') {
|
||||
str = this.topInfo.exceptionInfo.exception.map((item, index) => `${index + 1}.${item}`).join('')
|
||||
}
|
||||
return str
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// this._queryHead()
|
||||
},
|
||||
mounted () {
|
||||
this.checkTextOverflow()
|
||||
window.addEventListener('resize', this.checkTextOverflow)
|
||||
this.$store.dispatch('setAgvObj', this.topInfo)
|
||||
// this.taskSeq = this.topInfo.task_seq.split('-')
|
||||
// const target = this.topInfo.task_point
|
||||
// this.currentStep = this.taskSeq.findIndex(item => item === target)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('resize', this.checkTextOverflow)
|
||||
clearTimeout(this.timer)
|
||||
closeWebsocket(true)
|
||||
},
|
||||
destroyed () {
|
||||
clearTimeout(this.timer)
|
||||
},
|
||||
methods: {
|
||||
// 滚动区域
|
||||
checkTextOverflow () {
|
||||
const container = this.$refs.scrollContainer
|
||||
const text = this.$refs.scrollText
|
||||
if (!container || !text) return
|
||||
const containerWidth = container.offsetWidth
|
||||
const textWidth = text.scrollWidth
|
||||
if (textWidth > containerWidth) {
|
||||
// 如果文字超出容器宽度,启动滚动动画
|
||||
const duration = (textWidth / containerWidth) * 10
|
||||
text.style.animation = `scrollText ${duration}s linear infinite`
|
||||
} else {
|
||||
// 如果文字没有超出容器宽度,停止滚动动画
|
||||
text.style.animation = 'none'
|
||||
}
|
||||
},
|
||||
// 管理员登录
|
||||
loginModalHandle () {
|
||||
this.loginVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.loginModal.init()
|
||||
})
|
||||
},
|
||||
// 配置
|
||||
configModalHandle () {
|
||||
this.configVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.configModal.init()
|
||||
})
|
||||
},
|
||||
_queryHead () {
|
||||
const wsHost = this.serverUrl.replace(/^https?:\/\//, '')
|
||||
sendWebsocket(`ws://${wsHost}/webSocket/VehicleInfo/${this.userRole}`, {}, this.wsMessage, this.wsErr)
|
||||
},
|
||||
wsMessage (res) {
|
||||
clearTimeout(this.timer)
|
||||
this.topInfo = res.data
|
||||
this.taskSeq = this.topInfo.task_seq.split('-')
|
||||
const target = this.topInfo.task_point
|
||||
this.currentStep = this.taskSeq.findIndex(item => item === target)
|
||||
this.$store.dispatch('setAgvObj', this.topInfo)
|
||||
},
|
||||
wsErr () {
|
||||
this.timer = setTimeout(() => {
|
||||
this._queryHead()
|
||||
}, 10000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '../../style/mixin'
|
||||
.header-container
|
||||
_wh(100%, .48rem)
|
||||
padding 0 2%
|
||||
background top center / 100% 84px url(../../images/new/header_bg_1.png) no-repeat
|
||||
.button-home
|
||||
background linear-gradient(0deg, #E64F29, rgba(230, 79, 41, 0.5))
|
||||
border-color rgba(230, 79, 41, 0.7)
|
||||
.state-item
|
||||
_wh(.72rem, .25rem)
|
||||
_font(.14rem,.25rem,#fff,,center)
|
||||
font-family 'Adobe Heiti Std'
|
||||
margin-right .04rem
|
||||
background center / 100% 100% url(../../images/new/state-item_bg.png) no-repeat
|
||||
box-shadow inset 0px 0px 3px 2px rgb(149, 221, 253, 70%)
|
||||
.elec-qty-wrap
|
||||
_wh(.5rem, .25rem)
|
||||
margin-right .04rem
|
||||
background center / 100% 100% url(../../images/new/elec.png) no-repeat
|
||||
border 1px solid #5ADFBC
|
||||
z-index 151
|
||||
.elec-qty
|
||||
right 0
|
||||
z-index 152
|
||||
height 100%
|
||||
background-color #0d2d59
|
||||
.elec-qty-border
|
||||
_wh(100%, 100%)
|
||||
z-index 152
|
||||
box-shadow inset 0px 0px 3px 2px rgba(90, 223, 188, 70%)
|
||||
.elec-wraning
|
||||
border-color #ffe600
|
||||
background center / 100% 100% url(../../images/new/elec_y.png) no-repeat
|
||||
.elec-qty-border
|
||||
box-shadow inset 0px 0px 3px 2px rgba(192, 176, 28, 70%)
|
||||
.elec-txt
|
||||
position relative
|
||||
z-index 153
|
||||
_font(.15rem, .25rem, #fff,,center)
|
||||
font-family 'Adobe Heiti Std'
|
||||
.icon-user
|
||||
font-size .24rem
|
||||
margin-right .04rem
|
||||
.icon-tools
|
||||
font-size .24rem
|
||||
color #fff
|
||||
cursor pointer
|
||||
.error-tips
|
||||
position fixed
|
||||
top .48rem
|
||||
left 0
|
||||
width 100%
|
||||
height .3rem
|
||||
line-height .3rem
|
||||
overflow hidden
|
||||
white-space nowrap
|
||||
background-color rgba(253, 246, 236, .4)
|
||||
.scroll-text
|
||||
display inline-block
|
||||
text-align center
|
||||
animation none
|
||||
.icon-warning
|
||||
font-size .18rem
|
||||
color #e6bb3c
|
||||
margin-right .05rem
|
||||
.error-tips-t
|
||||
display inline-block
|
||||
font-size .16rem
|
||||
color #e6bb3c
|
||||
.task-tips
|
||||
display flex
|
||||
align-items: center;
|
||||
position fixed
|
||||
bottom 0
|
||||
left 0
|
||||
width 100%
|
||||
height .4rem
|
||||
padding 0 2%
|
||||
// background linear-gradient(90deg, #1DB8FF,#1DC5E1, #27C8B0,#819269, #E46355)
|
||||
.task-tips-t
|
||||
_font(.2rem, .4rem, #fff, ,center)
|
||||
.hud_left
|
||||
position absolute
|
||||
left 0
|
||||
top 0
|
||||
_wh(2%, 100%)
|
||||
background center / 100% auto url(../../images/new/hud_left.png) no-repeat
|
||||
.hud_right
|
||||
left auto
|
||||
right 0
|
||||
background-image url(../../images/new/hud_right.png)
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
:visible.sync="dialogVisible"
|
||||
width="50%"
|
||||
:before-close="handleClose">
|
||||
<p class="tip" v-if="$store.getters.userInfo === 'true'">登录成功</p>
|
||||
<p class="tip" v-if="userRole === 1">登录成功</p>
|
||||
<div v-else>
|
||||
<p class="tip">{{$t('Notloggedinyet')}}</p>
|
||||
<el-form :model="dataForm" ref="dataForm" :rules="dataRule" :label-width="$i18n.locale === 'en-us' ? '' : '1.1rem'" size="mini">
|
||||
@@ -14,7 +14,7 @@
|
||||
</el-form>
|
||||
</div>
|
||||
<el-row type="flex" justify="space-around" style="margin-top: .3rem">
|
||||
<el-col :span="7" v-if="$store.getters.userInfo === 'true'"><button class="button_control button_control_disabled" @click="exitUser"><p>{{ $t('Logout') }}</p></button></el-col>
|
||||
<el-col :span="7" v-if="userRole === 1"><button class="button_control button_control_disabled" @click="exitUser"><p>{{ $t('Logout') }}</p></button></el-col>
|
||||
<el-col :span="7" v-else><button class="button_control" @click="dataFormSubmit"><p>{{$t('Login')}}</p></button></el-col>
|
||||
</el-row>
|
||||
<vue-touch-keyboard id="keyboard" :options="options" v-if="visible" :layout="layout" :cancel="hide" :accept="accept" :input="input" :next="next" />
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
<script>
|
||||
import config from '../../../public/config.json'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
@@ -45,23 +46,27 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userRole']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setUserRole']),
|
||||
init () {
|
||||
this.dialogVisible = true
|
||||
},
|
||||
exitUser () {
|
||||
this.dialogVisible = false
|
||||
this.visible = false
|
||||
this.$store.dispatch('setSignOut')
|
||||
this.setUserRole(2)
|
||||
},
|
||||
dataFormSubmit () {
|
||||
this.dialogVisible = false
|
||||
this.visible = false
|
||||
if (this.$store.getters.userInfo === 'true') {
|
||||
if (this.userRole === 1) {
|
||||
return
|
||||
}
|
||||
if (this.dataForm.password === config.password) {
|
||||
this.$store.dispatch('userInfo', 'true')
|
||||
this.setUserRole(1)
|
||||
this.$message({
|
||||
message: '登录成功',
|
||||
type: 'success'
|
||||
|
||||
@@ -216,6 +216,8 @@
|
||||
border-color #17ff09
|
||||
.el-step__title.is-process
|
||||
color #17ff09
|
||||
.el-progress-bar__inner,.el-progress-bar__outer
|
||||
border-radius 0
|
||||
|
||||
// button
|
||||
.button_control
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import * as types from '../types'
|
||||
|
||||
const state = {
|
||||
agvObj: '',
|
||||
carPosition: {x: '', y: '', angle: ''},
|
||||
isTop: false
|
||||
}
|
||||
|
||||
const getters = {
|
||||
agvObj: state => state.agvObj,
|
||||
carPosition: state => state.carPosition,
|
||||
isTop: state => state.isTop
|
||||
}
|
||||
@@ -21,11 +19,15 @@ const actions = {
|
||||
const mutations = {
|
||||
[types.SET_AGV_OBJ] (state, data) {
|
||||
state.agvObj = Object.prototype.toString.call(data) === '[object Object]' ? JSON.stringify(data) : ''
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'x') && Object.prototype.hasOwnProperty.call(data, 'y') && Object.prototype.hasOwnProperty.call(data, 'theta')) {
|
||||
state.carPosition = {x: data.x, y: data.y, angle: data.theta}
|
||||
if ('exceptionInfo' in data) {
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'exceptionInfo')) {
|
||||
if (Object.prototype.hasOwnProperty.call(data.exceptionInfo, 'exception')) {
|
||||
state.isTop = data.exceptionInfo.exception.length > 0 ? true : false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import * as types from '../types'
|
||||
import { getStore, setStore } from '../../config/utils.js'
|
||||
|
||||
const state = {
|
||||
userInfo: getStore('userInfo') || '' // 用户信息
|
||||
serverUrl: 'http://localhost:8011', // ip服务
|
||||
userRole: 2, // 用户角色,1 表示管理员,2 表示普通用户
|
||||
}
|
||||
|
||||
const getters = {
|
||||
userInfo: state => state.userInfo
|
||||
}
|
||||
|
||||
const actions = {
|
||||
userInfo ({ commit }, res) {
|
||||
setStore('userInfo', res)
|
||||
commit(types.SET_USER_INFO, res)
|
||||
},
|
||||
setSignOut ({ commit }) {
|
||||
localStorage.removeItem('userInfo')
|
||||
commit(types.SET_SIGN_OUT)
|
||||
}
|
||||
serverUrl: state => state.serverUrl,
|
||||
userRole: state => state.userRole
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
[types.SET_USER_INFO] (state, res) {
|
||||
state.userInfo = res
|
||||
[types.SET_BASE_URL](state, url) {
|
||||
state.serverUrl = url
|
||||
localStorage.setItem('serverUrl', url)
|
||||
},
|
||||
[types.SET_SIGN_OUT] (state) {
|
||||
state.userInfo = ''
|
||||
[types.SET_USER_ROLE](state, role) {
|
||||
state.userRole = role
|
||||
localStorage.setItem('userRole', role)
|
||||
}
|
||||
}
|
||||
const actions = {
|
||||
setServerUrl ({commit}, url) {
|
||||
commit(types.SET_BASE_URL, url)
|
||||
},
|
||||
setUserRole ({commit}, role) {
|
||||
commit(types.SET_USER_ROLE, role)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
state,
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
// 公共配置
|
||||
export const COM_CONFIG = 'COM_CONFIG'
|
||||
|
||||
// 用户
|
||||
export const SET_USER_INFO = 'SET_USER_INFO'
|
||||
export const SET_SIGN_OUT = 'SET_SIGN_OUT'
|
||||
|
||||
// 数据
|
||||
export const SET_DEVICE = 'SET_DEVICE'
|
||||
export const SET_BASE_URL = 'SET_BASE_URL'
|
||||
export const SET_USER_ROLE = 'SET_USER_ROLE'
|
||||
export const SET_AGV_OBJ = 'SET_AGV_OBJ'
|
||||
|
||||
// hcx数据
|
||||
export const MATER_OBJ = 'MATER_OBJ' // 物料查询-盘点修改
|
||||
export const HCX_CHECK_OBJ = 'HCX_CHECK_OBJ' // 缓存线盘点 - 盘点修改
|
||||
|
||||
export const UPDATE_WEB_SOCKET_DATA = 'UPDATE_WEB_SOCKET_DATA'
|
||||
|
||||
73
yarn.lock
73
yarn.lock
@@ -1019,6 +1019,11 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@pixi/colord@^2.9.6":
|
||||
version "2.9.6"
|
||||
resolved "https://registry.npmmirror.com/@pixi/colord/-/colord-2.9.6.tgz#7e4e7851480da6fd3cef4e331f008d60be7e1204"
|
||||
integrity sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==
|
||||
|
||||
"@pkgjs/parseargs@^0.11.0":
|
||||
version "0.11.0"
|
||||
resolved "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
@@ -1096,6 +1101,16 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/css-font-loading-module@^0.0.12":
|
||||
version "0.0.12"
|
||||
resolved "https://registry.npmmirror.com/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz#65494833928823f998fbe8e86312821875d80db5"
|
||||
integrity sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==
|
||||
|
||||
"@types/earcut@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/@types/earcut/-/earcut-3.0.0.tgz#c21ab8372c47f8af1bec63cb36eecb6917b6c5b6"
|
||||
integrity sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==
|
||||
|
||||
"@types/eslint-scope@^3.7.7":
|
||||
version "3.7.7"
|
||||
resolved "https://registry.npmmirror.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5"
|
||||
@@ -1736,6 +1751,16 @@
|
||||
"@webassemblyjs/ast" "1.14.1"
|
||||
"@xtuc/long" "4.2.2"
|
||||
|
||||
"@webgpu/types@^0.1.40":
|
||||
version "0.1.64"
|
||||
resolved "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.64.tgz#62c5f9d345d4d270fcf3ecbb111c3b98dcc1aca3"
|
||||
integrity sha512-84kRIAGV46LJTlJZWxShiOrNL30A+9KokD7RB3dRCIqODFjodS5tCD5yyiZ8kIReGVZSDfA3XkkwyyOIF6K62A==
|
||||
|
||||
"@xmldom/xmldom@^0.8.10":
|
||||
version "0.8.10"
|
||||
resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
|
||||
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
|
||||
|
||||
"@xtuc/ieee754@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||
@@ -2821,6 +2846,11 @@ duplexer@^0.1.2:
|
||||
resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
|
||||
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
|
||||
|
||||
earcut@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmmirror.com/earcut/-/earcut-3.0.2.tgz#d478a29aaf99acf418151493048aa197d0512248"
|
||||
integrity sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==
|
||||
|
||||
eastasianwidth@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||
@@ -3163,6 +3193,11 @@ eventemitter3@^4.0.0:
|
||||
resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||
|
||||
eventemitter3@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
|
||||
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
|
||||
|
||||
events@^3.2.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||
@@ -3486,6 +3521,13 @@ get-stream@^6.0.0:
|
||||
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
|
||||
|
||||
gifuct-js@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmmirror.com/gifuct-js/-/gifuct-js-2.1.2.tgz#06152437ba30ec914db8398bd838bd0fbc8a6ecd"
|
||||
integrity sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==
|
||||
dependencies:
|
||||
js-binary-schema-parser "^2.0.3"
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
@@ -3933,6 +3975,11 @@ isexe@^2.0.0:
|
||||
resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
ismobilejs@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmmirror.com/ismobilejs/-/ismobilejs-1.1.1.tgz#c56ca0ae8e52b24ca0f22ba5ef3215a2ddbbaa0e"
|
||||
integrity sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==
|
||||
|
||||
isobject@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
@@ -3981,6 +4028,11 @@ joi@^17.4.0:
|
||||
"@sideway/formula" "^3.0.1"
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
js-binary-schema-parser@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmmirror.com/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz#3d7848748e8586e63b34e8911b643f59cfb6396e"
|
||||
integrity sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==
|
||||
|
||||
js-message@1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
|
||||
@@ -4704,6 +4756,11 @@ parse-json@^5.0.0:
|
||||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse-svg-path@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmmirror.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb"
|
||||
integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||
@@ -4792,6 +4849,22 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
pixi.js@^8.12.0:
|
||||
version "8.12.0"
|
||||
resolved "https://registry.npmmirror.com/pixi.js/-/pixi.js-8.12.0.tgz#6079f953de83491ebc021f8a0ead0d167cf24191"
|
||||
integrity sha512-or7vrH7WajLevu/JnGMdD80JaSpTlXfjwCLtzhg2BL60LWPf1pF0w08Qleiqr1Saj012gevguM//+6HzzVlnfA==
|
||||
dependencies:
|
||||
"@pixi/colord" "^2.9.6"
|
||||
"@types/css-font-loading-module" "^0.0.12"
|
||||
"@types/earcut" "^3.0.0"
|
||||
"@webgpu/types" "^0.1.40"
|
||||
"@xmldom/xmldom" "^0.8.10"
|
||||
earcut "^3.0.2"
|
||||
eventemitter3 "^5.0.1"
|
||||
gifuct-js "^2.1.2"
|
||||
ismobilejs "^1.1.1"
|
||||
parse-svg-path "^0.1.2"
|
||||
|
||||
pkg-dir@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
|
||||
|
||||
Reference in New Issue
Block a user