Files
apt15e/src/pages/shells/index.vue
2025-08-14 20:05:52 +08:00

348 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="4">
<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="20">
<el-row type="flex" justify="end" align="middle">
<div class="warn_image" @click="$router.push('/index/warning')"></div>
<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', 'paddingBottom': taskSeq.length > 0 ? '.6rem': '0'}">
<div class="absolute hud_left"></div>
<div class="absolute hud_left hud_right"></div>
<router-view></router-view>
</el-row>
<div v-if="taskSeq.length > 0" class="task_wraper">
<div class="task_content">
<div class="step_item" v-for="(e, i) in taskSeq" :key="i" :class="{'step_active': currentStep === i, 'step_actived': i < currentStep}">
<div class="step_name">{{ e }}</div>
<div v-show="i !== taskSeq.length - 1" class="step_arrow"><img src="../../images/new/arrow.png"></div>
</div>
</div>
</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 { mapGetters } from 'vuex'
export default {
name: 'ShellIndex',
components: {
LoginModal,
ConfigModal
},
data () {
return {
websocket: null, // WebSocket实例
reconnectTimer: null, // 重连计时器
topInfo: {},
topInfo1: {
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: '工作点1-工作点2-工作点3',
task_point: '工作点2'
},
taskSeq: [],
currentStep: null,
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.$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)
this.closeWebSocket() // 组件销毁时关闭连接
if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
},
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()
})
},
// 初始化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 = () => {
this.topInfo = {}
this.taskSeq = []
this.currentStep = null
this.reconnectWebSocket()
}
},
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)
},
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秒后重连
}
}
}
</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(1rem, .3rem)
_font(.18rem,.3rem,#fff,,center)
font-family 'Adobe Heiti Std'
margin-right .08rem
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(.6rem, .3rem)
margin-right .08rem
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(.18rem, .3rem, #fff,,center)
font-family 'Adobe Heiti Std'
.icon-user
font-size .24rem
margin-right .08rem
.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_wraper
position fixed
bottom 0
left 2%
width 96%
height .5rem
padding 0 .09rem
margin-bottom .1rem
.task_content
height 100%
display flex
justify-content: center
border 1px dashed #1e9fe7
.step_item
display flex
align-items center
.step_arrow
position relative
width .7rem
height 12px
background-color #0091de
overflow hidden
img
_wh(21px, 9px)
position: absolute;
left 50%
top: 50%;
transform: translate(-50%, -50%);
.step_name
_font(.18rem, .3rem, #fff, ,center)
padding 0 .06rem
border 1px solid #00d0fc
background-image linear-gradient(to bottom,rgba(42, 83, 138, 50%), rgba(57, 101, 181, 50%))
box-shadow inset 0 0px 3px 1px rgba(98, 180, 243, 50%)
border-radius 2px
.step_active
.step_name
background-image linear-gradient(to bottom,rgba(251, 143, 0, 30%), rgba(251, 143, 0, 60%))
box-shadow inset 0 0px 3px 1px rgba(251, 143, 0, 50%)
border-color #fb8f00
.step_arrow
img
left 0
animation: moveRight 3s linear infinite;
.step_actived
.step_name
background-image linear-gradient(to bottom,rgba(210, 210, 227, 30%), rgba(210, 210, 227, 60%))
box-shadow inset 0 0px 3px 1px rgba(210, 210, 227, 50%)
border-color #d2d2e3
.step_arrow
background-color rgba(210, 210, 227, 50%)
@keyframes moveRight {
from {
left: 0;
}
to {
left: calc(100% + 11px);
}
}
.task_wraper-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)
.warn_image
_wh(.36rem, .32rem)
margin 0 .08rem 0 0
background center / 100% auto url(../../images/new/warn_green.png) no-repeat
</style>