Commit a2410eaa authored by 高晓帆's avatar 高晓帆

change:softDrivingPattern

parent 528d0460
cmake_minimum_required(VERSION 3.5)
project (TruckMonitorView)
set (CMAKE_CXX_STANDARD 11)
# Version.
set (MAJOR_VERSION "1")
set (MINOR_VERSION "1")
set (PATCH_VERSION "3")
# Import cmake modules.
list (APPEND CMAKE_MODULE_PATH "$ENV{HT_BUILDSYS_CMAKE_MODULES}")
include (HtCMakePub)
install_system_packages (
PACKAGES "nodejs"
)
add_custom_command (
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/dist"
COMMAND "npm" "install" "--force"
COMMAND "npm" "run" "build"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
add_custom_target ("${PROJECT_NAME}" ALL
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/dist"
)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/dist"
DESTINATION "/var/www/${PROJECT_NAME}"
)
# Pack.
set (CPACK_PACKAGE_NAME "ht-${PROJECT_NAME}")
set (CPACK_PACKAGE_VERSION_MAJOR "${MAJOR_VERSION}")
set (CPACK_PACKAGE_VERSION_MINOR "${MINOR_VERSION}")
set (CPACK_PACKAGE_VERSION_PATCH "${PATCH_VERSION}")
set (CPACK_PACKAGE_CONTACT "Fan Jianping <jianping.fan@waytous.com>, Li Lingyan <lingyan.li@waytous.com>, Gao Xiaofan <xiaofan.gao@waytous.com>")
set (CPACK_DEBIAN_PACKAGE_DESCRIPTION "webtruckview. Branch: ${GIT_BRANCH}. Commit: ${GIT_LAST_COMMIT}.")
include(HtCMakeDeb)
cmake_minimum_required(VERSION 3.5)
project (TruckMonitorView)
set (CMAKE_CXX_STANDARD 11)
# Version.
set (MAJOR_VERSION "2")
set (MINOR_VERSION "1")
set (PATCH_VERSION "1")
# Import cmake modules.
list (APPEND CMAKE_MODULE_PATH "$ENV{HT_BUILDSYS_CMAKE_MODULES}")
include (HtCMakePub)
install_system_packages (
PACKAGES "nodejs"
)
add_custom_command (
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/dist"
COMMAND "npm" "install" "--force"
COMMAND "npm" "run" "build"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
add_custom_target ("${PROJECT_NAME}" ALL
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/dist"
)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/dist"
DESTINATION "/var/www/${PROJECT_NAME}"
)
# Pack.
set (CPACK_PACKAGE_NAME "ht-${PROJECT_NAME}")
set (CPACK_PACKAGE_VERSION_MAJOR "${MAJOR_VERSION}")
set (CPACK_PACKAGE_VERSION_MINOR "${MINOR_VERSION}")
set (CPACK_PACKAGE_VERSION_PATCH "${PATCH_VERSION}")
set (CPACK_PACKAGE_CONTACT "Fan Jianping <jianping.fan@waytous.com>, Li Lingyan <lingyan.li@waytous.com>, Gao Xiaofan <xiaofan.gao@waytous.com>")
set (CPACK_DEBIAN_PACKAGE_DESCRIPTION "webtruckview. Branch: ${GIT_BRANCH}. Commit: ${GIT_LAST_COMMIT}.")
include(HtCMakeDeb)
import { useStateStore } from '@/store/StateStore'
import { useVehicleStore } from '@/store/VehicleStore.js'
import { useMapStore } from '@/store/MapStore.js'
import writeLog from '@/js/writeLog.js'
import router from '../../router'
import { storeToRefs } from 'pinia'
import { Message } from 'view-ui-plus'
const pingIntervalSec = 5000 // 心跳链接时间
const NETWORK_COUNT = 3
const TRUCK_MONITOR_EXIT = 15 * 60
const TOPIC_CLASS = {
vehicle: ['/vehicle/positioninfo'],
map: ['/map/obstacles', '/map/lanenode', '/map/lane', '/map/runablearea', '/map/centerline', '/map/diggingworkarea', '/map/dumparea', '/map/barricade', '/map/stationarea', '/map/parkspot', '/map/staticobjs', '/map/electronicFence', 'mapVersionAck'],
path: ['/vehicle/currPathinfo', '/vehicle/nextPathinfo', '/singleTrack/morepoint', '/singleTrack/morePath', '/vehicle/basicinfo', '/vehicle/dynamicPathinfo'], // 车辆循迹路径信息,当前路径;车辆循迹路径信息,下一段路径;单机循迹预览多路径;单机循迹预览单路径; 车辆动态路权信息
hmi: [
'/vehicle/debuginfo', // 车辆调试信息
'/vehicle/statusinfo', // 车辆状态信息
'/vehicle/errorCode', // 车辆故障码信息
'/vehicle/pairinfo', // 车辆配对信息
'/info/commandInfo', // 车辆提示信息(左下角)
'/info/tipInfo', // 车辆弹框提示信息
'/vehicle/paraminfo', // 车辆全局参数信息
'/command/debugack', //
'/pattern/ack', // 车辆模式切换按钮-发送模式切换回复
'/task/retranTimer', // 车辆任务重传定时器消息
'/setting/singleTrackAck', // 设置单机循迹
'/product',
'/info/popup', // 弹框确认信息
'/setting/basicinfo', // 基本设置数据信息
'/setting/light', // 灯光
'/setting/safe', // 安全
'/setting/controlInfo', // 启动参数
'/setting/ack', // 设置信息结果返回
'/setting/terminal', // 终端对应关系列表
'/setting/terminalAck',
'/setting/ignoreObstaclesAck', // 忽略障碍物回复消息
],
mapHmi: [
'/setting/map', // 地图管理图层
'/setting/mapControlAck', // 地图设置回复
'/setting/roughnessFileAck', // 地图不平整度文件回复
],
login: [
'/login/ack', // 登录信息
'/btn/quitApp', // 退出
'/truckmonitor/start', // 车端登录成功,有连结进来 车端就会发这个消息
'/truckmonitor/exit', // 车端程序退出,不关闭websocket
],
heart: ['heartbeat']
}
const SETTING_TYPE = {
1: '基本信息',
2: '灯光信息',
3: '安全信息',
4: '启动控制信息',
5: '地图管理信息',
6: '单机循迹信息',
// 7: '终端打印信息'
}
export default class Socket {
constructor(root) {
this.ws = new WebSocket(`ws://${document.location.serviceIP}`)
this.stateStore = useStateStore()
this.vehicleStore = useVehicleStore()
this.mapStore = useMapStore()
this.heartTimer = null // 心跳定时器
this.serverTimer = null // 服务器超时定时器
this.sendFixHeartTimer = null // 定时发送心跳
this.timeoutTimer = null // 超时定时器
this.lockReconnect = false // 是否真正建立连接
this.reconnectTimer = null // 重连倒计时
this.isSendSync = false // 是否要重连成功之后,发送同步消息
this.heartCount = 0 // 重连之后,如果没有收到车端登录的消息,需要发送同步消息
this.root = root
}
getConnection() {
const self = this
return new Promise((resolve, reject) => {
self.ws = new WebSocket(`ws://${document.location.serviceIP}`)
self.ws.onopen = function () {
// 开启心跳
self.start()
self.sendFixHeart()
self.resetTimeout()
self.count = 0
resolve(self.ws)
}
self.ws.onerror = function (err) {
console.log(err)
self.isSendSync = true
self.reconnect()
}
self.ws.onclose = function () {
!self.isClosed && self.reconnect()
console.log('closed', new Date())
}
self.ws.onmessage = function (msg) {
self.dealData(msg.data)
// 收到服务器信息,心跳重置
self.reset()
}
})
}
start() {
this.heartTimer && clearTimeout(this.heartTimer)
this.serverTimer && clearTimeout(this.serverTimer)
this.heartTimer = setTimeout(() => {
this.send({
type: 'heartbeat',
msg: 'heart beat'
})
// 超时关闭
this.serverTimer = setTimeout(() => {
this.ws.close()
}, 15000)
}, pingIntervalSec)
}
// 15s定时发送心跳
sendFixHeart() {
this.sendFixHeartTimer && clearInterval(this.sendFixHeartTimer)
this.sendFixHeartTimer = setInterval(() => {
this.send({
type: 'heartbeat',
msg: 'heart beat'
})
}, 15000)
}
resetTimeout() {
const self = this
if (this.timeoutTimer) {
this.timeoutCount = 0
this.stateStore.dealNetworkTip({ type: false })
clearInterval(this.timeoutTimer)
}
this.timeoutTimer = setInterval(() => {
if (this.timeoutCount > NETWORK_COUNT) {
writeLog('WARNING',`网络异常,请检测网络!连接超时${this.timeoutCount}s`)
this.stateStore.dealNetworkTip({ type: true, count: this.timeoutCount })
}
this.timeoutCount++
// 超时一定时间,认为车端程序未启动,跳转登录页面
if (self.timeoutCount > TRUCK_MONITOR_EXIT) {
this.dealTruckMointor({
visible: true,
msg: '车端程序未运行',
type: 1
})
router.push({
name: 'login'
})
}
}, 1000)
}
reset() {
this.start()
}
reconnect() {
if (this.lockReconnect) return
const self = this
// this.resetTimeout()
console.log('reconnect-----')
if (this.count > 5) {
Message.error('当前网络不稳定')
this.count = 0
// return
}
this.count++
this.lockReconnect = true
this.reconnectTimer && clearTimeout(this.reconnectTimer)
this.reconnectTimer = setTimeout(() => {
this.getConnection().then(sock => {
self.root.sock = sock
})
this.lockReconnect = false
}, 4000)
}
send(msg) {
msg = JSON.stringify(msg)
console.log(`发送消息${msg}`, new Date())
this.ws && this.ws.send(msg)
}
sendLoginMsg() {
let loginForm = this.stateStore.loginForm || localStorage.getItem(`${location.host}loginForm`)
if (typeof loginForm === 'string') {
loginForm = JSON.parse(loginForm)
}
this.send({
type: '/login/req',
msg: loginForm
})
}
judgeCurrentPath() {
let currentPath = router.currentRoute.value.path
if (currentPath.includes('basic-message')) {
this.sendLoginMsg()
}
}
dealTruckMointor(msg) {
this.stateStore.setTruckMonitor(msg)
}
// 初始化indexDB里的地图图层数据
initMapLayer(layerName, version) {
let db = this.root.indexDB
db.getStoreData(layerName, version).then(res => {
if (res) {
this.mapStore.setData(layerName, res.msg)
} else {
let data = {
type: 'syncMapData',
msg: [layerName]
}
this.send(data)
}
}).catch(error => {
console.warn(error)
})
}
sendVehicleSyncMsg() {
let syncVehicleData = {
type: 'syncVehicleData',
msg: {}
}
this.send(syncVehicleData)
}
async dealMapVersion(msg) {
console.log('msg', msg)
let storeLayers = window.localStorage.getItem('layers')
let syncMapLayers = [], initLayers = []
if (storeLayers) {
let layers = JSON.parse(storeLayers)
for (let i = 0; i < msg.length; i++) {
let item = msg[i]
let { layerName, layerVersion } = item
let oldLayer = layers.find(item => item.layerName === layerName)
if (oldLayer) {
let { layerVersion: oldLayerVersion } = oldLayer
if (oldLayerVersion !== layerVersion) {
syncMapLayers.push(layerName)
} else {
initLayers.push({ layerName, layerVersion })
}
} else {
syncMapLayers.push(layerName)
}
}
} else {
syncMapLayers = msg.map(item => item.layerName)
}
if (syncMapLayers.length) {
await this.root.indexDB.initStore(syncMapLayers)
if (initLayers.length) {
await this.root.indexDB.compareTable(initLayers)
initLayers.forEach(({ layerName, layerVersion }) => {
this.initMapLayer(layerName, layerVersion)
})
}
let data = {
type: 'syncMapData',
msg: syncMapLayers
}
this.send(data)
} else {
if (initLayers.length) {
await this.root.indexDB.compareTable(initLayers)
initLayers.forEach(({ layerName, layerVersion }) => {
this.initMapLayer(layerName, layerVersion)
})
}
this.sendVehicleSyncMsg()
}
localStorage.setItem('layers', JSON.stringify(msg))
}
dealheartData() {
if (this.isSendSync && this.heartCount > 2) {
// 没有收到车端退出的消息时,收到3次心跳,就发送同步消息
if (this.heartCount > 2) {
this.judgeCurrentPath()
this.isSendSync = false
this.heartCount = 0
} else {
this.heartCount++
}
}
}
dealmapData(result, key) {
const self = this
const { type, msg, version } = result
let worker = this.root.worker.worker[key]
switch (type) {
case 'mapVersionAck':
this.dealMapVersion(msg)
break
default:
let strData = window.atob(msg);
worker.postMessage({ type, msg: strData, version })
break
}
worker.onmessage = ({ data }) => {
const { mapName, result, mapVersion } = data
self.mapStore.setData(mapName, result)
self.root.indexDB.storeData(mapName, result, mapVersion)
self.sendVehicleSyncMsg()
}
}
dealpathData(result, key) {
const { type, msg } = result
let [, , name] = type.split('/')
const worker = this.root.worker.worker[key]
switch (type) {
case '/vehicle/basicinfo':
const { avoidancePath, obstacle } = msg
avoidancePath && worker.postMessage({ name: 'avoidancePath', msg: avoidancePath })
obstacle && worker.postMessage({ name: 'obstacle', msg: obstacle })
this.vehicleStore.setBasicInfo(msg)
break
case '/vehicle/nextPathinfo':
let { available, path } = msg
this.mapStore.setData('nextPathAvailable', available)
path && worker.postMessage({ name, msg: path })
break
default:
msg && worker.postMessage({ name, msg })
break
}
worker.onmessage = ({ data }) => {
const { name, dealedData } = data
this.mapStore.setData(name === 'obstacle' ? 'obstacleData' : name, dealedData)
}
}
dealvehicleData(result, key) {
const { type, msg } = result
const vehicleStoreToRefs = storeToRefs(this.vehicleStore)
const { paraminfo, positioninfo } = vehicleStoreToRefs
const { deviceId } = paraminfo.value
const worker = this.root.worker.worker[key]
const prePositionInfo = JSON.parse(JSON.stringify(positioninfo.value))
worker.postMessage({ type, msg, deviceId, pre: prePositionInfo })
const diffWorker = this.root.worker.worker['vehicleDiff']
diffWorker.postMessage({ pre: prePositionInfo, cur: msg })
worker.onmessage = ({ data }) => {
const { vehicles, safeObj, trajObj, gps, currentBasic } = data
this.stateStore.setData('gps', gps)
currentBasic && this.vehicleStore.setData('currentBasic', currentBasic)
this.vehicleStore.$patch({
vehiclesPositioninfo: vehicles,
safeObj: safeObj,
trajObj: trajObj
})
}
diffWorker.onmessage = ({ data }) => {
this.vehicleStore.setData('deleteVehicles', data)
}
this.vehicleStore.setData('positioninfo', msg)
}
handleSettingAck(msg) {
let { type, code } = msg
if (!SETTING_TYPE[type]) return
let ackType = code === 0 ? 'success' : 'error'
let text = SETTING_TYPE[type];
if (type == 4) {
text = '指令'
}
Message[ackType](`${text}设置${code === 0 ? '成功' : '失败'}`)
if (type == 6 && code === 0) {
let data = {
type: '/setting/req',
msg: {
type: 6
}
}
this.send(data)
}
}
dealObstaclesAck(msg) {
let { code } = msg
let type = code === 0 ? 'success' : 'error'
let tip = code === 0 ? '成功' : '失败'
Message[type](`发送车端${tip}`)
}
dealhmiData(result) {
const { type, msg } = result
let [, , name] = type.split('/')
// console.log(name, msg)
switch (type) {
// 车辆状态信息
case '/vehicle/statusinfo':
const { pattern, connStatus } = msg
this.stateStore.setData('wifi', connStatus)
this.vehicleStore.setData('pattern', pattern)
this.vehicleStore.setData(name, msg)
break
// 车辆故障码信息
case '/vehicle/errorCode':
this.vehicleStore.dealErrorCode(msg)
break
// 车辆模式切换按钮-发送模式切换回复
case '/pattern/ack':
this.vehicleStore.setData('patternAck', msg)
break
// 弹框确认信息
case '/info/popup':
this.vehicleStore.dealInfoPop(msg)
break
// 设置信息结果返回
case '/setting/ack':
this.handleSettingAck(msg)
break
case '/setting/terminalAck':
this.vehicleStore.dealTerminalAck(msg)
break
case '/setting/ignoreObstaclesAck':
this.dealObstaclesAck(msg)
break
default:
this.vehicleStore.setData(name, msg)
break
}
}
dealmapHmiData(result) {
const { type, msg } = result
let [, , name] = type.split('/')
switch (type) {
// 地图不平整度文件回复
case '/setting/roughnessFileAck':
const { type, fileName } = msg
if (type === 1) {
this.mapStore.setData(name, fileName)
}
break
default:
this.mapStore.setData(name, msg)
break
}
}
startTime() {
const self = this
const worker = this.root.worker.worker.time
worker.postMessage('start')
worker.onmessage = ({ data }) => {
self.stateStore.setData('currentTime', data)
}
}
postParentMessage(data) {
if (data) {
let loginInfo = data
if (window.top && loginInfo && data.code === 0) {
window.top.postMessage({
type: 'token',
data: {
ip: window.location.host,
loginInfo,
}
}, '*')
}
try {
localStorage.setItem(window.location.host, JSON.stringify(loginInfo))
} catch (error) {
this.isTraceless = true
console.log(error)
Message.error('请退出无痕模式!')
}
}
}
dealloginData(result) {
const { type, msg } = result
switch (type) {
// 车端登录成功,有连结进来 车端就会发这个消息
case '/truckmonitor/start':
// 收到车端登录成功的消息,则不需要判断收到心跳的次数
// 需要判断是否再登录页面,如果再登录页面,不需要处理,如果不在登录页面,则需要发送同步信息
this.isSendSync = false
this.judgeCurrentPath()
this.dealTruckMointor({
visible: false,
type: 1
})
break
case '/truckmonitor/exit':
router.push({
name: 'login'
})
this.dealTruckMointor({
visible: true,
msg: '车端程序未运行',
type: 1
})
this.stateStore.setData('loginAck', msg)
break
// 登录
case '/login/ack':
console.log('收到登录信息', new Date())
this.stateStore.setData('loginAck', msg)
this.postParentMessage(msg)
// this.startTime()
break
// 退出
case '/btn/quitApp':
if (window.state === 'logout') {
window.state = null
localStorage.removeItem(window.location.host)
this.close()
router.push({
name: 'login'
})
}
}
}
async dealData(data) {
this.resetTimeout()
let result = await (new Response(data)).text()
if (typeof result === 'string' && result !== 'heart beat') {
try {
result = JSON.parse(result)
} catch (err) {
console.log('err----', result)
}
}
const { type, msg } = result
// console.log(`接收消息${type}`, new Date().format('yyyy-MM-dd hh:mm:ss'))
if (!type) return
// console.log(type)
let keys = Object.keys(TOPIC_CLASS)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
if (TOPIC_CLASS[key].includes(type)) {
this[`deal${key}Data`](result, key)
break
}
}
}
close() {
this.isClosed = true
this.heartTimer && clearTimeout(this.heartTimer)
this.serverTimer && clearTimeout(this.serverTimer)
this.reconnectTimer && clearTimeout(this.reconnectTimer)
this.sendFixHeartTimer && clearInterval(this.sendFixHeartTimer)
this.timeoutTimer && clearInterval(this.timeoutTimer)
this.heartTimer = null
this.serverTimer = null
this.reconnectTimer = null
this.sendFixHeartTimer = null
this.ws.close()
this.root.sock = null
this.root.worker = null
this.root.indexDB = null
}
}
\ No newline at end of file
import { useStateStore } from '@/store/StateStore'
import { useVehicleStore } from '@/store/VehicleStore.js'
import { useMapStore } from '@/store/MapStore.js'
import writeLog from '@/js/writeLog.js'
import router from '../../router'
import { storeToRefs } from 'pinia'
import { Message } from 'view-ui-plus'
const pingIntervalSec = 5000 // 心跳链接时间
const NETWORK_COUNT = 3
const TRUCK_MONITOR_EXIT = 15 * 60
const TOPIC_CLASS = {
vehicle: ['/vehicle/positioninfo'],
map: ['/map/obstacles', '/map/lanenode', '/map/lane', '/map/runablearea', '/map/centerline', '/map/diggingworkarea', '/map/dumparea', '/map/barricade', '/map/stationarea', '/map/parkspot', '/map/staticobjs', '/map/electronicFence', 'mapVersionAck'],
path: ['/vehicle/currPathinfo', '/vehicle/nextPathinfo', '/singleTrack/morepoint', '/singleTrack/morePath', '/vehicle/basicinfo', '/vehicle/dynamicPathinfo'], // 车辆循迹路径信息,当前路径;车辆循迹路径信息,下一段路径;单机循迹预览多路径;单机循迹预览单路径; 车辆动态路权信息
hmi: [
'/vehicle/debuginfo', // 车辆调试信息
'/vehicle/statusinfo', // 车辆状态信息
'/vehicle/errorCode', // 车辆故障码信息
'/vehicle/pairinfo', // 车辆配对信息
'/info/commandInfo', // 车辆提示信息(左下角)
'/info/tipInfo', // 车辆弹框提示信息
'/vehicle/paraminfo', // 车辆全局参数信息
'/command/debugack', //
'/pattern/ack', // 车辆模式切换按钮-发送模式切换回复
'/task/retranTimer', // 车辆任务重传定时器消息
'/setting/singleTrackAck', // 设置单机循迹
'/product',
'/info/popup', // 弹框确认信息
'/setting/basicinfo', // 基本设置数据信息
'/setting/light', // 灯光
'/setting/safe', // 安全
'/setting/controlInfo', // 启动参数
'/setting/ack', // 设置信息结果返回
'/setting/terminal', // 终端对应关系列表
'/setting/terminalAck',
'/setting/ignoreObstaclesAck', // 忽略障碍物回复消息
],
mapHmi: [
'/setting/map', // 地图管理图层
'/setting/mapControlAck', // 地图设置回复
'/setting/roughnessFileAck', // 地图不平整度文件回复
],
login: [
'/login/ack', // 登录信息
'/btn/quitApp', // 退出
'/truckmonitor/start', // 车端登录成功,有连结进来 车端就会发这个消息
'/truckmonitor/exit', // 车端程序退出,不关闭websocket
],
heart: ['heartbeat']
}
const SETTING_TYPE = {
1: '基本信息',
2: '灯光信息',
3: '安全信息',
4: '启动控制信息',
5: '地图管理信息',
6: '单机循迹信息',
// 7: '终端打印信息'
}
export default class Socket {
constructor(root) {
this.ws = new WebSocket(`ws://${document.location.serviceIP}`)
this.stateStore = useStateStore()
this.vehicleStore = useVehicleStore()
this.mapStore = useMapStore()
this.heartTimer = null // 心跳定时器
this.serverTimer = null // 服务器超时定时器
this.sendFixHeartTimer = null // 定时发送心跳
this.timeoutTimer = null // 超时定时器
this.lockReconnect = false // 是否真正建立连接
this.reconnectTimer = null // 重连倒计时
this.isSendSync = false // 是否要重连成功之后,发送同步消息
this.heartCount = 0 // 重连之后,如果没有收到车端登录的消息,需要发送同步消息
this.root = root
}
getConnection() {
const self = this
return new Promise((resolve, reject) => {
self.ws = new WebSocket(`ws://${document.location.serviceIP}`)
self.ws.onopen = function () {
// 开启心跳
self.start()
self.sendFixHeart()
self.resetTimeout()
self.count = 0
resolve(self.ws)
}
self.ws.onerror = function (err) {
console.log(err)
self.isSendSync = true
self.reconnect()
}
self.ws.onclose = function () {
!self.isClosed && self.reconnect()
console.log('closed', new Date())
}
self.ws.onmessage = function (msg) {
self.dealData(msg.data)
// 收到服务器信息,心跳重置
self.reset()
}
})
}
start() {
this.heartTimer && clearTimeout(this.heartTimer)
this.serverTimer && clearTimeout(this.serverTimer)
this.heartTimer = setTimeout(() => {
this.send({
type: 'heartbeat',
msg: 'heart beat'
})
// 超时关闭
this.serverTimer = setTimeout(() => {
this.ws.close()
}, 15000)
}, pingIntervalSec)
}
// 15s定时发送心跳
sendFixHeart() {
this.sendFixHeartTimer && clearInterval(this.sendFixHeartTimer)
this.sendFixHeartTimer = setInterval(() => {
this.send({
type: 'heartbeat',
msg: 'heart beat'
})
}, 15000)
}
resetTimeout() {
const self = this
if (this.timeoutTimer) {
this.timeoutCount = 0
this.stateStore.dealNetworkTip({ type: false })
clearInterval(this.timeoutTimer)
}
this.timeoutTimer = setInterval(() => {
if (this.timeoutCount > NETWORK_COUNT) {
writeLog('WARNING', `网络异常,请检测网络!连接超时${this.timeoutCount}s`)
this.stateStore.dealNetworkTip({ type: true, count: this.timeoutCount })
}
this.timeoutCount++
// 超时一定时间,认为车端程序未启动,跳转登录页面
if (self.timeoutCount > TRUCK_MONITOR_EXIT) {
this.dealTruckMointor({
visible: true,
msg: '车端程序未运行',
type: 1
})
router.push({
name: 'login'
})
}
}, 1000)
}
reset() {
this.start()
}
reconnect() {
if (this.lockReconnect) return
const self = this
// this.resetTimeout()
console.log('reconnect-----')
if (this.count > 5) {
Message.error('当前网络不稳定')
this.count = 0
// return
}
this.count++
this.lockReconnect = true
this.reconnectTimer && clearTimeout(this.reconnectTimer)
this.reconnectTimer = setTimeout(() => {
this.getConnection().then(sock => {
self.root.sock = sock
})
this.lockReconnect = false
}, 4000)
}
send(msg) {
msg = JSON.stringify(msg)
console.log(`发送消息${msg}`, new Date())
this.ws && this.ws.send(msg)
}
sendLoginMsg() {
let loginForm = this.stateStore.loginForm || localStorage.getItem(`${location.host}loginForm`)
if (typeof loginForm === 'string') {
loginForm = JSON.parse(loginForm)
}
this.send({
type: '/login/req',
msg: loginForm
})
}
judgeCurrentPath() {
let currentPath = router.currentRoute.value.path
if (currentPath.includes('basic-message')) {
this.sendLoginMsg()
}
}
dealTruckMointor(msg) {
this.stateStore.setTruckMonitor(msg)
}
// 初始化indexDB里的地图图层数据
initMapLayer(layerName, version) {
let db = this.root.indexDB
db.getStoreData(layerName, version).then(res => {
if (res) {
this.mapStore.setData(layerName, res.msg)
} else {
let data = {
type: 'syncMapData',
msg: [layerName]
}
this.send(data)
}
}).catch(error => {
console.warn(error)
})
}
sendVehicleSyncMsg() {
let syncVehicleData = {
type: 'syncVehicleData',
msg: {}
}
this.send(syncVehicleData)
}
async dealMapVersion(msg) {
console.log('msg', msg)
let storeLayers = window.localStorage.getItem('layers')
let syncMapLayers = [], initLayers = []
if (storeLayers) {
let layers = JSON.parse(storeLayers)
for (let i = 0; i < msg.length; i++) {
let item = msg[i]
let { layerName, layerVersion } = item
let oldLayer = layers.find(item => item.layerName === layerName)
if (oldLayer) {
let { layerVersion: oldLayerVersion } = oldLayer
if (oldLayerVersion !== layerVersion) {
syncMapLayers.push(layerName)
} else {
initLayers.push({ layerName, layerVersion })
}
} else {
syncMapLayers.push(layerName)
}
}
} else {
syncMapLayers = msg.map(item => item.layerName)
}
if (syncMapLayers.length) {
await this.root.indexDB.initStore(syncMapLayers)
if (initLayers.length) {
await this.root.indexDB.compareTable(initLayers)
initLayers.forEach(({ layerName, layerVersion }) => {
this.initMapLayer(layerName, layerVersion)
})
}
let data = {
type: 'syncMapData',
msg: syncMapLayers
}
this.send(data)
} else {
if (initLayers.length) {
await this.root.indexDB.compareTable(initLayers)
initLayers.forEach(({ layerName, layerVersion }) => {
this.initMapLayer(layerName, layerVersion)
})
}
this.sendVehicleSyncMsg()
}
localStorage.setItem('layers', JSON.stringify(msg))
}
dealheartData() {
if (this.isSendSync && this.heartCount > 2) {
// 没有收到车端退出的消息时,收到3次心跳,就发送同步消息
if (this.heartCount > 2) {
this.judgeCurrentPath()
this.isSendSync = false
this.heartCount = 0
} else {
this.heartCount++
}
}
}
dealmapData(result, key) {
const self = this
const { type, msg, version } = result
let worker = this.root.worker.worker[key]
switch (type) {
case 'mapVersionAck':
this.dealMapVersion(msg)
break
default:
let strData = window.atob(msg);
worker.postMessage({ type, msg: strData, version })
break
}
worker.onmessage = ({ data }) => {
const { mapName, result, mapVersion } = data
self.mapStore.setData(mapName, result)
self.root.indexDB.storeData(mapName, result, mapVersion)
self.sendVehicleSyncMsg()
}
}
dealpathData(result, key) {
const { type, msg } = result
let [, , name] = type.split('/')
const worker = this.root.worker.worker[key]
switch (type) {
case '/vehicle/basicinfo':
const { avoidancePath, obstacle } = msg
avoidancePath && worker.postMessage({ name: 'avoidancePath', msg: avoidancePath })
obstacle && worker.postMessage({ name: 'obstacle', msg: obstacle })
this.vehicleStore.setBasicInfo(msg)
break
case '/vehicle/nextPathinfo':
let { available, path } = msg
this.mapStore.setData('nextPathAvailable', available)
path && worker.postMessage({ name, msg: path })
break
default:
msg && worker.postMessage({ name, msg })
break
}
worker.onmessage = ({ data }) => {
const { name, dealedData } = data
this.mapStore.setData(name === 'obstacle' ? 'obstacleData' : name, dealedData)
}
}
dealvehicleData(result, key) {
const { type, msg } = result
const vehicleStoreToRefs = storeToRefs(this.vehicleStore)
const { paraminfo, positioninfo } = vehicleStoreToRefs
const { deviceId } = paraminfo.value
const worker = this.root.worker.worker[key]
const prePositionInfo = JSON.parse(JSON.stringify(positioninfo.value))
worker.postMessage({ type, msg, deviceId, pre: prePositionInfo })
const diffWorker = this.root.worker.worker['vehicleDiff']
diffWorker.postMessage({ pre: prePositionInfo, cur: msg })
worker.onmessage = ({ data }) => {
const { vehicles, safeObj, trajObj, gps, currentBasic } = data
this.stateStore.setData('gps', gps)
currentBasic && this.vehicleStore.setData('currentBasic', currentBasic)
this.vehicleStore.$patch({
vehiclesPositioninfo: vehicles,
safeObj: safeObj,
trajObj: trajObj
})
}
diffWorker.onmessage = ({ data }) => {
this.vehicleStore.setData('deleteVehicles', data)
}
this.vehicleStore.setData('positioninfo', msg)
}
handleSettingAck(msg) {
let { type, code } = msg
if (!SETTING_TYPE[type]) return
let ackType = code === 0 ? 'success' : 'error'
let text = SETTING_TYPE[type];
if (type == 4) {
text = '指令'
}
Message[ackType](`${text}设置${code === 0 ? '成功' : '失败'}`)
if (type == 6 && code === 0) {
let data = {
type: '/setting/req',
msg: {
type: 6
}
}
this.send(data)
}
}
dealObstaclesAck(msg) {
let { code } = msg
let type = code === 0 ? 'success' : 'error'
let tip = code === 0 ? '成功' : '失败'
Message[type](`发送车端${tip}`)
}
dealhmiData(result) {
const { type, msg } = result
let [, , name] = type.split('/')
// console.log(name, msg)
switch (type) {
// 车辆状态信息
case '/vehicle/statusinfo':
const { pattern, softDrivingPattern, connStatus } = msg
this.stateStore.setData('wifi', connStatus)
this.vehicleStore.setData('pattern', pattern)
this.vehicleStore.setData('softDrivingPattern', softDrivingPattern)
this.vehicleStore.setData(name, msg)
break
// 车辆故障码信息
case '/vehicle/errorCode':
this.vehicleStore.dealErrorCode(msg)
break
// 车辆模式切换按钮-发送模式切换回复
case '/pattern/ack':
this.vehicleStore.setData('patternAck', msg)
break
// 弹框确认信息
case '/info/popup':
this.vehicleStore.dealInfoPop(msg)
break
// 设置信息结果返回
case '/setting/ack':
this.handleSettingAck(msg)
break
case '/setting/terminalAck':
this.vehicleStore.dealTerminalAck(msg)
break
case '/setting/ignoreObstaclesAck':
this.dealObstaclesAck(msg)
break
default:
this.vehicleStore.setData(name, msg)
break
}
}
dealmapHmiData(result) {
const { type, msg } = result
let [, , name] = type.split('/')
switch (type) {
// 地图不平整度文件回复
case '/setting/roughnessFileAck':
const { type, fileName } = msg
if (type === 1) {
this.mapStore.setData(name, fileName)
}
break
default:
this.mapStore.setData(name, msg)
break
}
}
startTime() {
const self = this
const worker = this.root.worker.worker.time
worker.postMessage('start')
worker.onmessage = ({ data }) => {
self.stateStore.setData('currentTime', data)
}
}
postParentMessage(data) {
if (data) {
let loginInfo = data
if (window.top && loginInfo && data.code === 0) {
window.top.postMessage({
type: 'token',
data: {
ip: window.location.host,
loginInfo,
}
}, '*')
}
try {
localStorage.setItem(window.location.host, JSON.stringify(loginInfo))
} catch (error) {
this.isTraceless = true
console.log(error)
Message.error('请退出无痕模式!')
}
}
}
dealloginData(result) {
const { type, msg } = result
switch (type) {
// 车端登录成功,有连结进来 车端就会发这个消息
case '/truckmonitor/start':
// 收到车端登录成功的消息,则不需要判断收到心跳的次数
// 需要判断是否再登录页面,如果再登录页面,不需要处理,如果不在登录页面,则需要发送同步信息
this.isSendSync = false
this.judgeCurrentPath()
this.dealTruckMointor({
visible: false,
type: 1
})
break
case '/truckmonitor/exit':
router.push({
name: 'login'
})
this.dealTruckMointor({
visible: true,
msg: '车端程序未运行',
type: 1
})
this.stateStore.setData('loginAck', msg)
break
// 登录
case '/login/ack':
console.log('收到登录信息', new Date())
this.stateStore.setData('loginAck', msg)
this.postParentMessage(msg)
// this.startTime()
break
// 退出
case '/btn/quitApp':
if (window.state === 'logout') {
window.state = null
localStorage.removeItem(window.location.host)
this.close()
router.push({
name: 'login'
})
}
}
}
async dealData(data) {
this.resetTimeout()
let result = await (new Response(data)).text()
if (typeof result === 'string' && result !== 'heart beat') {
try {
result = JSON.parse(result)
} catch (err) {
console.log('err----', result)
}
}
const { type, msg } = result
// console.log(`接收消息${type}`, new Date().format('yyyy-MM-dd hh:mm:ss'))
if (!type) return
// console.log(type)
let keys = Object.keys(TOPIC_CLASS)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
if (TOPIC_CLASS[key].includes(type)) {
this[`deal${key}Data`](result, key)
break
}
}
}
close() {
this.isClosed = true
this.heartTimer && clearTimeout(this.heartTimer)
this.serverTimer && clearTimeout(this.serverTimer)
this.reconnectTimer && clearTimeout(this.reconnectTimer)
this.sendFixHeartTimer && clearInterval(this.sendFixHeartTimer)
this.timeoutTimer && clearInterval(this.timeoutTimer)
this.heartTimer = null
this.serverTimer = null
this.reconnectTimer = null
this.sendFixHeartTimer = null
this.ws.close()
this.root.sock = null
this.root.worker = null
this.root.indexDB = null
}
}
\ No newline at end of file
<template>
<div class="basic-message">
<div class="basic-title">
<p>
<span>基础信息</span>
<Icon class="basic-close" type="ios-close-circle-outline" @click="handleClose" />
</p>
<i class="basic-title-line"></i>
</div>
<div class="basic">
<div class="basic-top">
<i :class="['vehicle-state', vehicleState]" @click="showStates"></i>
<span class="vehicle-msg">
<span>{{pattern == 0 ? '司机' : '安全员'}}{{name}}</span>
<span>工号:{{number}}</span>
</span>
</div>
<ul class="basic-middle">
<li v-for="item in list" :key="item.index">
<span class="middle-img" :style="`background-image: url('/image/ic_${item.name}.png')`">
{{basicMid[item.title] || 0}}{{item.unit}}
</span>
<span>{{item.label}}</span>
</li>
</ul>
</div>
<ul class="basic-info">
<li v-for="item in infos" :key="item.index">
<p v-for="list in item" :key="list.name">
<span class="label">{{list.label}}</span>
<span v-if="list.fixed">{{ debug[list.name] ? debug[list.name].toFixed(list.fixed) : list.default }}</span>
<span v-else>{{debug[list.name] || list.default}}</span>
</p>
</li>
</ul>
</div>
</template>
<script setup>
import { reactive, markRaw, ref, watch, computed } from 'vue';
import { useVehicleStore } from '@/store/VehicleStore.js'
import { useStateStore } from '@/store/StateStore.js'
import { storeToRefs } from 'pinia'
const BASIC_MESSAGE_INFO = [
[
{
name: 'target_velocity',
label: '速度(线控)',
default: 0,
fixed: 1,
},
{
name: 'steerangle',
label: '转向',
default: '——',
},
],
[
{
name: 'dump_bed',
label: '举升',
default: '——',
},
{
name: 'throttlecmd',
label: '油门',
default: '——',
},
],
[
{
name: 'Retarder_cmd',
label: '制动',
default: '——',
},
],
[
{
name: 'lateralDeviation',
label: '横向偏差',
default: 0.00,
},
],
[
{
name: 'courseDeviation',
label: '航向偏差',
default: 0.00,
},
],
[
{
name: 'obstacleFrontDistance',
label: '前向障碍物距离',
fixed: 2,
default: 0.00,
},
],
[
{
name: 'obstacleBackDistance',
label: '后向障碍物距离',
fixed: 2,
default: 0.00,
},
],
[
{
name: 'obstacleBackBarriDistance',
label: '后向挡墙距离',
fixed: 2,
default: 0.00,
},
],
]
const VEHICLE_TIRES = [
{
name: 'front_right',
icon: 'ic_tireLeft',
tire: 'tire0'
},
{
name: 'back_right_inside',
icon: 'ic_tireMiddle',
tire: 'tire1'
},
{
name: 'back_right_outside',
icon: 'ic_tireRight',
tire: 'tire2'
},
{
name: 'front_left',
icon: 'ic_tireLeft',
tire: 'tire3'
},
{
name: 'back_left_inside',
icon: 'ic_tireMiddle',
tire: 'tire4'
},
{
name: 'back_left_outside',
icon: 'ic_tireRight',
tire: 'tire5'
}
]
const BASIC_MESSAGE = [
{
label: '载重量',
name: 'weight',
title: 'vehicleLoad',
unit: 'T'
},
{
label: '时速(rosbeacon)',
name: 'speed',
title: 'vehicleSpeed',
unit: 'Km/h'
},
{
label: '油量',
name: 'oil',
title: 'oil',
unit: '%'
},
]
const list = markRaw(BASIC_MESSAGE)
const infos = markRaw(BASIC_MESSAGE_INFO)
const debug = ref({})
const name = ref('')
const number = ref('')
let basicMid = reactive({})
const vehicleStore = useVehicleStore()
const vehicleStoreToRefs = storeToRefs(vehicleStore)
const { getVehicleDebug, getVehicleBasic, getCurrentBasic} = vehicleStoreToRefs
const stateStore = useStateStore()
const stateStoreToRefs = storeToRefs(stateStore)
const { getLoginAck } = stateStoreToRefs
const emits = defineEmits(['handleClose', 'showModal'])
const vehicleState = computed(() => {
const {isError} = vehicleStoreToRefs
return isError.value
})
const pattern = computed(() => {
const {pattern} = vehicleStoreToRefs
return pattern.value
})
function handleClose() {
emits('handleClose')
}
function showStates() {
if (vehicleState.value === 'normal') return
emits('showModal', {name: 'states'})
}
watch([getVehicleDebug, getLoginAck, getVehicleBasic, getCurrentBasic], ([debugValue, loginAckValue, vehicleBasic, currentBasic]) => {
if (debugValue) {
debug.value = debugValue
}
if (loginAckValue) {
name.value = loginAckValue.name
number.value = loginAckValue.number
}
if (vehicleBasic) {
const {oil} = vehicleBasic
basicMid['oil'] = oil ? oil.toFixed(2) : 0
}
if (currentBasic) {
const {vehicleLoad, vehicleSpeed} = currentBasic
let result = {
vehicleLoad: vehicleLoad ? vehicleLoad.toFixed(2) : 0,
vehicleSpeed: vehicleSpeed ? vehicleSpeed.toFixed(2) : 0,
oil: basicMid.oil
}
basicMid = Object.assign(basicMid, result)
}
}, {
immediate: true
})
</script>
<style lang="less" scoped>
.basic-message {
.basic-title {
padding: 5px 20px 10px;
color: #fff;
position: relative;
p {
display: flex;
align-items: center;
justify-content: space-between;
span:nth-child(1) {
margin-left: 10px;
}
}
.basic-close {
color: #63A9FB;
cursor: pointer;
}
.basic-title-line {
display: inline-block;
background: url('/image/basic-title.png') no-repeat;
background-size: cover;
width: 100%;
height: 10px;
position: absolute;
}
}
.basic {
color: #fff;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
.basic-top {
display: flex;
align-items: center;
width: 100%;
padding-left: 10px;
margin-bottom: 10px;
.vehicle-state {
display: inline-block;
width: 6vw;
height: 6vw;
background-size: contain;
background-repeat: no-repeat;
margin-right: 10px;
cursor: pointer;
}
@media screen and (min-width: 1400px) {
.vehicle-state {
width: 5vw;
height: 5vw;
}
}
.normal {
background-image: url('/image/heal_normal.png');
}
.fault {
background-image: url('/image/heal_fault.png');
}
.warning {
background-image: url('/image/heal_warning.png');
}
.vehicle-msg {
display: flex;
flex-direction: column;
span {
line-height: 5vh;
display: inline-block;
}
}
}
.basic-middle {
display: flex;
width: 100%;
justify-content: space-between;
li {
text-align: center;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
.middle-img {
display: block;
width: 6vw;
height: 6vw;
min-width: 50px;
min-height: 50px;
background-size: cover;
line-height: 7vw;
text-align: center;
}
@media screen and (min-width: 1400px) {
.middle-img {
width: 5vw;
height: 5vw;
line-height: 5vw;
}
}
@media screen and (max-width: 631px) {
.middle-img {
line-height: 9vw;
}
}
}
}
}
.basic-info {
color: #fff;
padding: 10px 20px;
margin-top: 10px;
li {
display: flex;
p {
min-width: 50%;
white-space: nowrap;
}
.label {
color: #63A9FB;
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="basic-message">
<div class="basic-title">
<p>
<span>基础信息</span>
<Icon class="basic-close" type="ios-close-circle-outline" @click="handleClose" />
</p>
<i class="basic-title-line"></i>
</div>
<div class="basic">
<div class="basic-top">
<i :class="['vehicle-state', vehicleState]" @click="showStates"></i>
<span class="vehicle-msg">
<span>{{softDrivingPattern == 0 ? '司机' : '安全员'}}{{name}}</span>
<span>工号:{{number}}</span>
</span>
</div>
<ul class="basic-middle">
<li v-for="item in list" :key="item.index">
<span class="middle-img" :style="`background-image: url('/image/ic_${item.name}.png')`">
{{basicMid[item.title] || 0}}{{item.unit}}
</span>
<span>{{item.label}}</span>
</li>
</ul>
</div>
<ul class="basic-info">
<li v-for="item in infos" :key="item.index">
<p v-for="list in item" :key="list.name">
<span class="label">{{list.label}}</span>
<span v-if="list.fixed">{{ debug[list.name] ? debug[list.name].toFixed(list.fixed) : list.default }}</span>
<span v-else>{{debug[list.name] || list.default}}</span>
</p>
</li>
</ul>
</div>
</template>
<script setup>
import { reactive, markRaw, ref, watch, computed } from 'vue';
import { useVehicleStore } from '@/store/VehicleStore.js'
import { useStateStore } from '@/store/StateStore.js'
import { storeToRefs } from 'pinia'
const BASIC_MESSAGE_INFO = [
[
{
name: 'target_velocity',
label: '速度(线控)',
default: 0,
fixed: 1,
},
{
name: 'steerangle',
label: '转向',
default: '——',
},
],
[
{
name: 'dump_bed',
label: '举升',
default: '——',
},
{
name: 'throttlecmd',
label: '油门',
default: '——',
},
],
[
{
name: 'Retarder_cmd',
label: '制动',
default: '——',
},
],
[
{
name: 'lateralDeviation',
label: '横向偏差',
default: 0.00,
},
],
[
{
name: 'courseDeviation',
label: '航向偏差',
default: 0.00,
},
],
[
{
name: 'obstacleFrontDistance',
label: '前向障碍物距离',
fixed: 2,
default: 0.00,
},
],
[
{
name: 'obstacleBackDistance',
label: '后向障碍物距离',
fixed: 2,
default: 0.00,
},
],
[
{
name: 'obstacleBackBarriDistance',
label: '后向挡墙距离',
fixed: 2,
default: 0.00,
},
],
]
const VEHICLE_TIRES = [
{
name: 'front_right',
icon: 'ic_tireLeft',
tire: 'tire0'
},
{
name: 'back_right_inside',
icon: 'ic_tireMiddle',
tire: 'tire1'
},
{
name: 'back_right_outside',
icon: 'ic_tireRight',
tire: 'tire2'
},
{
name: 'front_left',
icon: 'ic_tireLeft',
tire: 'tire3'
},
{
name: 'back_left_inside',
icon: 'ic_tireMiddle',
tire: 'tire4'
},
{
name: 'back_left_outside',
icon: 'ic_tireRight',
tire: 'tire5'
}
]
const BASIC_MESSAGE = [
{
label: '载重量',
name: 'weight',
title: 'vehicleLoad',
unit: 'T'
},
{
label: '时速(rosbeacon)',
name: 'speed',
title: 'vehicleSpeed',
unit: 'Km/h'
},
{
label: '油量',
name: 'oil',
title: 'oil',
unit: '%'
},
]
const list = markRaw(BASIC_MESSAGE)
const infos = markRaw(BASIC_MESSAGE_INFO)
const debug = ref({})
const name = ref('')
const number = ref('')
let basicMid = reactive({})
const vehicleStore = useVehicleStore()
const vehicleStoreToRefs = storeToRefs(vehicleStore)
const { getVehicleDebug, getVehicleBasic, getCurrentBasic} = vehicleStoreToRefs
const stateStore = useStateStore()
const stateStoreToRefs = storeToRefs(stateStore)
const { getLoginAck } = stateStoreToRefs
const emits = defineEmits(['handleClose', 'showModal'])
const vehicleState = computed(() => {
const {isError} = vehicleStoreToRefs
return isError.value
})
const pattern = computed(() => {
const {softDrivingPattern} = vehicleStoreToRefs
return softDrivingPattern.value
})
function handleClose() {
emits('handleClose')
}
function showStates() {
if (vehicleState.value === 'normal') return
emits('showModal', {name: 'states'})
}
watch([getVehicleDebug, getLoginAck, getVehicleBasic, getCurrentBasic], ([debugValue, loginAckValue, vehicleBasic, currentBasic]) => {
if (debugValue) {
debug.value = debugValue
}
if (loginAckValue) {
name.value = loginAckValue.name
number.value = loginAckValue.number
}
if (vehicleBasic) {
const {oil} = vehicleBasic
basicMid['oil'] = oil ? oil.toFixed(2) : 0
}
if (currentBasic) {
const {vehicleLoad, vehicleSpeed} = currentBasic
let result = {
vehicleLoad: vehicleLoad ? vehicleLoad.toFixed(2) : 0,
vehicleSpeed: vehicleSpeed ? vehicleSpeed.toFixed(2) : 0,
oil: basicMid.oil
}
basicMid = Object.assign(basicMid, result)
}
}, {
immediate: true
})
</script>
<style lang="less" scoped>
.basic-message {
.basic-title {
padding: 5px 20px 10px;
color: #fff;
position: relative;
p {
display: flex;
align-items: center;
justify-content: space-between;
span:nth-child(1) {
margin-left: 10px;
}
}
.basic-close {
color: #63A9FB;
cursor: pointer;
}
.basic-title-line {
display: inline-block;
background: url('/image/basic-title.png') no-repeat;
background-size: cover;
width: 100%;
height: 10px;
position: absolute;
}
}
.basic {
color: #fff;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
.basic-top {
display: flex;
align-items: center;
width: 100%;
padding-left: 10px;
margin-bottom: 10px;
.vehicle-state {
display: inline-block;
width: 6vw;
height: 6vw;
background-size: contain;
background-repeat: no-repeat;
margin-right: 10px;
cursor: pointer;
}
@media screen and (min-width: 1400px) {
.vehicle-state {
width: 5vw;
height: 5vw;
}
}
.normal {
background-image: url('/image/heal_normal.png');
}
.fault {
background-image: url('/image/heal_fault.png');
}
.warning {
background-image: url('/image/heal_warning.png');
}
.vehicle-msg {
display: flex;
flex-direction: column;
span {
line-height: 5vh;
display: inline-block;
}
}
}
.basic-middle {
display: flex;
width: 100%;
justify-content: space-between;
li {
text-align: center;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
.middle-img {
display: block;
width: 6vw;
height: 6vw;
min-width: 50px;
min-height: 50px;
background-size: cover;
line-height: 7vw;
text-align: center;
}
@media screen and (min-width: 1400px) {
.middle-img {
width: 5vw;
height: 5vw;
line-height: 5vw;
}
}
@media screen and (max-width: 631px) {
.middle-img {
line-height: 9vw;
}
}
}
}
}
.basic-info {
color: #fff;
padding: 10px 20px;
margin-top: 10px;
li {
display: flex;
p {
min-width: 50%;
white-space: nowrap;
}
.label {
color: #63A9FB;
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="log-out">
<p>{{ tipsLogout }}</p>
<div class="footer">
<Button type="info" class="btn" @click="doSureLogout">确定</Button>
<Button type="error" @click="doCancelLogout">取消</Button>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useVehicleStore } from '../store/VehicleStore';
import { useStateStore } from '../store/StateStore';
import { storeToRefs } from 'pinia';
import writeLog from '../js/writeLog.js'
const props = defineProps({
isShow: Boolean
})
const tipsLogout = ref('退出当前系统')
const logoutStep = ref(0)
const vehicleStore = useVehicleStore()
const vehicleStoreToRefs = storeToRefs(vehicleStore)
const { pattern } = vehicleStoreToRefs
const stateStore = useStateStore()
const emits = defineEmits(['handleSend', 'closeModal'])
function doCancelLogout() {
logoutStep.value = 0
emits('closeModal')
}
function closeSystem() {
logoutStep.value = 0
window.state = 'logout'
emits('handleSend', {
type: '/btn/quitApp',
msg: {}
})
stateStore.setData('loginAck', null)
}
function doSureLogout() {
if (pattern.value === 1 && logoutStep.value === 0) {
tipsLogout.value = '当前是无人模式,确定退出程序'
logoutStep.value = 1
} else {
closeSystem()
}
}
watch(() => props.isShow, (value) => {
if (value) {
writeLog('INFO',"退出系统")
tipsLogout.value = '退出当前系统'
}
}, {
immediate: true
})
</script>
<style lang="less" scoped>
.log-out {
&>p {
margin-bottom: 20px;
}
.footer {
text-align: end;
.btn {
margin-right: 10px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="log-out">
<p>{{ tipsLogout }}</p>
<div class="footer">
<Button type="info" class="btn" @click="doSureLogout">确定</Button>
<Button type="error" @click="doCancelLogout">取消</Button>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useVehicleStore } from '../store/VehicleStore';
import { useStateStore } from '../store/StateStore';
import { storeToRefs } from 'pinia';
import writeLog from '../js/writeLog.js'
const props = defineProps({
isShow: Boolean
})
const tipsLogout = ref('退出当前系统')
const logoutStep = ref(0)
const vehicleStore = useVehicleStore()
const vehicleStoreToRefs = storeToRefs(vehicleStore)
const { pattern ,softDrivingPattern} = vehicleStoreToRefs
const stateStore = useStateStore()
const emits = defineEmits(['handleSend', 'closeModal'])
function doCancelLogout() {
logoutStep.value = 0
emits('closeModal')
}
function closeSystem() {
logoutStep.value = 0
window.state = 'logout'
emits('handleSend', {
type: '/btn/quitApp',
msg: {}
})
stateStore.setData('loginAck', null)
}
function doSureLogout() {
if (softDrivingPattern.value === 1 && logoutStep.value === 0) {
tipsLogout.value = '当前是无人模式,确定退出程序'
logoutStep.value = 1
} else {
closeSystem()
}
}
watch(() => props.isShow, (value) => {
if (value) {
writeLog('INFO',"退出系统")
tipsLogout.value = '退出当前系统'
}
}, {
immediate: true
})
</script>
<style lang="less" scoped>
.log-out {
&>p {
margin-bottom: 20px;
}
.footer {
text-align: end;
.btn {
margin-right: 10px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="mode-switch">
<div class="content">
<p>{{content}}</p>
<p>{{contentTip}}</p>
</div>
<div class="footer">
<Button class="btn" type="info" @click="doSure" v-show="isShowSure">{{sureText}}</Button>
<Button type="error" @click="doCancel">{{cancelText}}</Button>
</div>
</div>
</template>
<script setup>
import { watch, ref } from 'vue'
import { useVehicleStore } from '../store/VehicleStore';
import { storeToRefs } from 'pinia';
let modeTimer = null
const CLOSE_MODE_TIME = 5 * 1000
const props = defineProps({
isShow: Boolean
})
const content = ref('')
const contentTip = ref('')
const isShowSure = ref(true)
const sureText = ref('确定')
const cancelText = ref('取消')
const step = ref(0)
const vehicleStore = useVehicleStore()
const vehicleStoreToRefs = storeToRefs(vehicleStore)
const { pattern, patternAck, singleTrackAck } = vehicleStoreToRefs
const emits = defineEmits(['handleSend', 'closeModal'])
function doSure() {
let type = null, iscontinue = null
// 有人——无人
if (pattern.value === 0) {
if (step.value === 1) {
type = 0
content.value = '正在等待机群批准模式切换请求'
step.value = 2
} else if (step.value === 3) {
type = 2
iscontinue = 1
content.value = '模式切换审批成功,正在接管无人驾驶'
step.value = 4
}
} else {
// 有人——无人
if (step.value === 1) {
type = 2
content.value = '正在取消无人驾驶接管'
step.value = 4
}
}
isShowSure.value = false
cancelText.value = '取消'
const changePattern = pattern.value === 0 ? 1 : 0
emits('handleSend', {
type: '/pattern/req',
msg: {
type,
pattern: changePattern,
iscontinue
}
})
}
function doCancel() {
if ([2, 3, 4].includes(step.value)) {
// type === 1:取消云端请求;type === 3:取消车端请求
let type = step.value === 2 ? 1 : 3
let iscontinue = null
// 切换为无人驾驶,并且不继续上一次任务
if (pattern.value === 0 && step.value === 3) {
iscontinue = 0
type = 2
content.value = '模式切换审批成功,正在接管无人驾驶'
step.value = 4
isShowSure.value = false
cancelText.value = '取消'
}
const changePattern = pattern.value === 0 ? 1 : 0
emits('handleSend', {
type: '/pattern/req',
msg: {
type,
pattern: changePattern,
iscontinue
}
})
} else {
emits('closeModal')
}
}
function doCloseMode() {
emits('closeModal')
if (modeTimer) {
clearTimeout(modeTimer)
modeTimer = null
}
}
watch(() => props.isShow, (value) => {
if (value) {
step.value = 1
isShowSure.value = true
sureText.value = '确定'
cancelText.value = '取消'
// 有人——无人
if (pattern.value === 0) {
const { singleTrackEnable } = singleTrackAck.value || {}
if (singleTrackEnable) {
content.value = '当前处于单机循迹模式,是否切换无人模式'
contentTip.value = '(确保当前位置处于路线中)'
} else {
content.value = '切换为无人驾驶模式需要发送机群确认'
contentTip.value = ''
}
} else {
content.value = '是否切换为有人驾驶模式'
contentTip.value = ''
}
}
}, {
immediate: true
})
watch(patternAck, (result) => {
console.log(result)
if (!result) return
contentTip.value = ''
const { singleTrackEnable } = singleTrackAck.value || {}
if (modeTimer) {
clearTimeout(modeTimer)
modeTimer = null
}
const {type, pattern: resultPattern, status} = result
if (step.value === 2) {
isShowSure.value = false
cancelText.value = '确定'
if (type === 0) {
switch (status) {
case 0:
if (singleTrackEnable) {
emits('handleSend', {
type: '/pattern/req',
msg: {
type: 2,
pattern: 1,
iscontinue: 0
}
})
} else {
content.value = '是否继续上一次任务(确保车辆位置未变动)'
step.value = 3
isShowSure.value = true
sureText.value = '是'
cancelText.value = '否'
}
break
case 1:
content.value = '模式切换请求未获得批准'
break
case 2:
content.value = '请先完成此设备的点检工作'
break
case 200:
content.value = '参数校验失败'
break
}
} else {
switch (status) {
case 0:
content.value = '成功'
break
case 1:
content.value = '失败'
break
case 200:
content.value = '参数校验失败'
break
}
step.value = 0
modeTimer = setTimeout(doCloseMode, CLOSE_MODE_TIME)
}
} else if (step.value === 4) {
isShowSure.value = false
cancelText.value = '确定'
if (type === 2) {
switch (status) {
case 0:
content.value = resultPattern === 1 ? '接管成功' : '取消接管成功'
break
case 1:
content.value = resultPattern === 1 ? '接管失败' : '取消接管失败'
break
case 200:
content.value = '参数校验失败'
break
}
} else {
switch (status) {
case 0:
content.value = '成功'
break
case 1:
content.value = '失败'
break
case 200:
content.value = '参数校验失败'
break
}
}
step.value = 0
modeTimer = setTimeout(doCloseMode, CLOSE_MODE_TIME)
}
}, {
deep: true
})
</script>
<style lang="less" scoped>
.mode-switch {
.content {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30px;
}
.footer {
text-align: end;
.btn {
margin-right: 10px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="mode-switch">
<div class="content">
<p>{{content}}</p>
<p>{{contentTip}}</p>
</div>
<div class="footer">
<Button class="btn" type="info" @click="doSure" v-show="isShowSure">{{sureText}}</Button>
<Button type="error" @click="doCancel">{{cancelText}}</Button>
</div>
</div>
</template>
<script setup>
import { watch, ref } from 'vue'
import { useVehicleStore } from '../store/VehicleStore';
import { storeToRefs } from 'pinia';
let modeTimer = null
const CLOSE_MODE_TIME = 5 * 1000
const props = defineProps({
isShow: Boolean
})
const content = ref('')
const contentTip = ref('')
const isShowSure = ref(true)
const sureText = ref('确定')
const cancelText = ref('取消')
const step = ref(0)
const vehicleStore = useVehicleStore()
const vehicleStoreToRefs = storeToRefs(vehicleStore)
const { pattern,softDrivingPattern, patternAck, singleTrackAck } = vehicleStoreToRefs
const emits = defineEmits(['handleSend', 'closeModal'])
function doSure() {
let type = null, iscontinue = null
// 有人——无人
if (softDrivingPattern.value === 0) {
if (step.value === 1) {
type = 0
content.value = '正在等待机群批准模式切换请求'
step.value = 2
} else if (step.value === 3) {
type = 2
iscontinue = 1
content.value = '模式切换审批成功,正在接管无人驾驶'
step.value = 4
}
} else {
// 有人——无人
if (step.value === 1) {
type = 2
content.value = '正在取消无人驾驶接管'
step.value = 4
}
}
isShowSure.value = false
cancelText.value = '取消'
const changePattern = softDrivingPattern.value === 0 ? 1 : 0
emits('handleSend', {
type: '/pattern/req',
msg: {
type,
pattern: changePattern,
iscontinue
}
})
}
function doCancel() {
if ([2, 3, 4].includes(step.value)) {
// type === 1:取消云端请求;type === 3:取消车端请求
let type = step.value === 2 ? 1 : 3
let iscontinue = null
// 切换为无人驾驶,并且不继续上一次任务
if (softDrivingPattern.value === 0 && step.value === 3) {
iscontinue = 0
type = 2
content.value = '模式切换审批成功,正在接管无人驾驶'
step.value = 4
isShowSure.value = false
cancelText.value = '取消'
}
const changePattern = softDrivingPattern.value === 0 ? 1 : 0
emits('handleSend', {
type: '/pattern/req',
msg: {
type,
pattern: changePattern,
iscontinue
}
})
} else {
emits('closeModal')
}
}
function doCloseMode() {
emits('closeModal')
if (modeTimer) {
clearTimeout(modeTimer)
modeTimer = null
}
}
watch(() => props.isShow, (value) => {
if (value) {
step.value = 1
isShowSure.value = true
sureText.value = '确定'
cancelText.value = '取消'
// 有人——无人
if (softDrivingPattern.value === 0) {
const { singleTrackEnable } = singleTrackAck.value || {}
if (singleTrackEnable) {
content.value = '当前处于单机循迹模式,是否切换无人模式'
contentTip.value = '(确保当前位置处于路线中)'
} else {
content.value = '切换为无人驾驶模式需要发送机群确认'
contentTip.value = ''
}
} else {
content.value = '是否切换为有人驾驶模式'
contentTip.value = ''
}
}
}, {
immediate: true
})
watch(patternAck, (result) => {
console.log(result)
if (!result) return
contentTip.value = ''
const { singleTrackEnable } = singleTrackAck.value || {}
if (modeTimer) {
clearTimeout(modeTimer)
modeTimer = null
}
const {type, pattern: resultPattern, status} = result
if (step.value === 2) {
isShowSure.value = false
cancelText.value = '确定'
if (type === 0) {
switch (status) {
case 0:
if (singleTrackEnable) {
emits('handleSend', {
type: '/pattern/req',
msg: {
type: 2,
pattern: 1,
iscontinue: 0
}
})
} else {
content.value = '是否继续上一次任务(确保车辆位置未变动)'
step.value = 3
isShowSure.value = true
sureText.value = '是'
cancelText.value = '否'
}
break
case 1:
content.value = '模式切换请求未获得批准'
break
case 2:
content.value = '请先完成此设备的点检工作'
break
case 200:
content.value = '参数校验失败'
break
}
} else {
switch (status) {
case 0:
content.value = '成功'
break
case 1:
content.value = '失败'
break
case 200:
content.value = '参数校验失败'
break
}
step.value = 0
modeTimer = setTimeout(doCloseMode, CLOSE_MODE_TIME)
}
} else if (step.value === 4) {
isShowSure.value = false
cancelText.value = '确定'
if (type === 2) {
switch (status) {
case 0:
content.value = resultPattern === 1 ? '接管成功' : '取消接管成功'
break
case 1:
content.value = resultPattern === 1 ? '接管失败' : '取消接管失败'
break
case 200:
content.value = '参数校验失败'
break
}
} else {
switch (status) {
case 0:
content.value = '成功'
break
case 1:
content.value = '失败'
break
case 200:
content.value = '参数校验失败'
break
}
}
step.value = 0
modeTimer = setTimeout(doCloseMode, CLOSE_MODE_TIME)
}
}, {
deep: true
})
</script>
<style lang="less" scoped>
.mode-switch {
.content {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30px;
}
.footer {
text-align: end;
.btn {
margin-right: 10px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="side-bar">
<ul class="side-list">
<li v-if="isBasic"
data-name="basic_message"
class="active"
@click="doCheck">
<i :style="`background-image: url('/image/Info_white.png')`"></i>
<span>基本信息</span>
</li>
<li v-else
data-name="mode_information"
class="active"
@click="doCheck">
<i :style="`background-image: url('/image/debug_white.png')`"></i>
<span>调试信息</span>
</li>
<li v-for="item in list"
:key="item.index"
:data-name="item.name"
:class="active === item.name ? 'active' : ''"
@click="changeActive">
<i :class="item.name === 'mode_switch' ? isDisable : ''"
:style="`background-image: url('/image/${item.icon}${active === item.name ? '_white' : '_blue'}.png')`"></i>
<span :class="item.name === 'mode_switch' ? isDisable : ''">{{item.label}}</span>
</li>
</ul>
</div>
</template>
<script setup>
import { markRaw, ref, computed } from 'vue';
import { useVehicleStore } from '@/store/VehicleStore.js'
import { storeToRefs } from 'pinia'
import { Message } from 'view-ui-plus'
const SIDE_BAR = [
{
name: 'mode_switch',
label: '模式切换',
icon: 'pattern'
},
{
name: 'log',
label: '终端信息',
icon: 'log'
},
{
name: 'set',
label: '设置',
icon: 'set'
},
{
name: 'logout',
label: '退出',
icon: 'quit'
},
]
/**
* defineProps 和 defineEmits 都是只能在 <script setup> 中使用的编译器宏。
* 他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。
* defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值
*/
const emit = defineEmits(['changeTab'])
const list = markRaw(SIDE_BAR)
const active = ref('basic_message')
const isBasic = ref(true)
const vehicleStore = useVehicleStore()
const vehicleStoreToRefs = storeToRefs(vehicleStore)
const isDisable = computed(() => {
const {isError, pattern} = vehicleStoreToRefs
if (isError.value === 'fault' && pattern.value === 0) {
return 'disabled'
}
return ''
})
function doCheck(evt) {
isBasic.value = !isBasic.value
const target = evt.currentTarget
const {name} = target.dataset
active.value = name === 'basic_message' ? 'mode_information' : 'basic_message'
emit('changeTab', active.value)
}
function changeActive(evt) {
const target = evt.currentTarget
const {name} = target.dataset
const {debuginfo} = vehicleStoreToRefs
const {Current_Gear,loadBrakeStatus}=debuginfo.value;
if (name === 'mode_switch' && isDisable.value === 'disabled') {
return
}
if(name=='mode_switch'){
if (!(['P', 126].includes(Current_Gear) || (['N', 0].includes(Current_Gear) && loadBrakeStatus != 0))) {
Message.error('不能切换无人模式')
return
}
}
active.value = name
emit('changeTab', name)
}
function changeMode() {
active.value = 'basic_message'
}
defineExpose({
changeMode
})
</script>
<style lang="less" scoped>
.side-list {
background: #202634;
color: #5490D6;
height: 100%;
overflow: auto;
li {
display: flex;
flex-direction: column;
align-items: center;
padding: .1rem;
cursor: pointer;
i {
display: inline-block;
width: 2.5vw;
height: 2.5vw;
background-size: cover;
}
&.active {
color: #fff;
}
}
.disabled {
cursor: not-allowed;
}
}
</style>
\ No newline at end of file
......@@ -11,7 +11,7 @@
<div class="basic-top">
<i :class="['vehicle-state', vehicleState]" @click="showStates"></i>
<span class="vehicle-msg">
<span>{{pattern == 0 ? '司机' : '安全员'}}{{name}}</span>
<span>{{softDrivingPattern == 0 ? '司机' : '安全员'}}{{name}}</span>
<span>工号:{{number}}</span>
</span>
</div>
......@@ -190,8 +190,8 @@
})
const pattern = computed(() => {
const {pattern} = vehicleStoreToRefs
return pattern.value
const {softDrivingPattern} = vehicleStoreToRefs
return softDrivingPattern.value
})
function handleClose() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment