Files
apt15e/src/pages/modules/build/index.vue
2025-10-31 10:06:20 +08:00

561 lines
17 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="page_container" :class="{'enClass': $i18n.locale === 'en-us'}">
<div id="v-step-1" class="map_container">
<gl-map ref="glMap"/>
</div>
<el-row type="flex" justify="space-between">
<el-col :span="18">
<el-row type="flex">
<button id="v-step-2" class="button_control" :class="{'button_control_gray': autoLoopEnable === '1'}" :disabled="disabled" @click="addPoint"><p>{{$t('MarkPoint')}}</p></button>
<button class="button_control" style="margin-left: 10px" :disabled="disabled" @click="closeCloud"><p>{{cloudOff ? $t('EnablePointCloud') : $t('DisablePointCloud')}}</p></button>
<div class="car-info">{{$t('CartPosition')}}: <span>{{ carPosition.x }}, {{carPosition.y}}</span></div>
</el-row>
</el-col>
<el-col :span="8">
<el-row type="flex" justify="end">
<button class="button_control" @click="$router.push('/index/home')"><p>{{$t('AbandonMapbuild')}}</p></button>
<button id="v-step-3" class="button_control" :class="{'button_control_gray': autoLoopEnable !== '1'}" style="margin-left: 10px" :disabled="disabled" @click="stopMappingConfirm"><p>{{$t('FinishMapbuild')}}</p></button>
</el-row>
</el-col>
</el-row>
<el-dialog
:title="$t('SetUpStation')"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
width="55%">
<el-form :model="dataForm" ref="dataForm" :rules="dataRule" :label-width="$i18n.locale === 'en-us' ? '' : '1.1rem'" size="mini" @submit.native.prevent>
<el-form-item :label="$t('StationName')" prop="stationName">
<el-input v-model="dataForm.stationName" id="stationName"></el-input>
</el-form-item>
</el-form>
<el-row type="flex" justify="space-around" style="margin-top: .3rem">
<el-col :span="7"><button class="button_control button_control_disabled" @click="dialogVisible = false"><p>{{$t('Cancel')}}</p></button></el-col>
<el-col :span="7"><button class="button_control" @click="stationConfirm"><p>{{$t('Save')}}</p></button></el-col>
</el-row>
</el-dialog>
<transition name="custom-message" >
<div v-if="message" class="message" :style="{'top': isTop ? '.87rem' : '.57rem', 'color': error ? '#F56C6C' : '#67C23A'}">
<i :class="error ? 'el-icon-error' : 'el-icon-success'"></i>
<p>{{ message }}</p>
</div>
</transition>
<div v-if="showProgress" class="progress-mask">
<!-- 进度条内容区 -->
<div class="progress-container">
<div class="progress_tip">{{warnTip ? $t('Mapdeployed') : $t('Mapgenerated')}}</div>
<el-progress
v-show="!warnTip"
:percentage="percentage"
:stroke-width="10"
style="width: 300px;"
></el-progress>
</div>
</div>
<div v-if="tipShow" class="progress-mask">
<div class="progress-container">
<div class="progress_tip">{{autoBackFinish === '2' ? $t('autobackfailedmanually') : $t('vehicleautobacknotmanwaitcompleted')}}</div>
<button v-if="autoBackFinish === '2'" class="button_control" style="margin-top: 25px;" @click="failHandle"><p>{{$t('Confirm')}}</p></button>
</div>
</div>
<driver-modal v-if="driverVisible" ref="driverModal" @driverConfirm="driverConfirm"/>
</div>
</template>
<script>
import DriverModal from './driver-modal.vue'
import GlMap from './gl-map.vue'
import { startMapping, stopMapping, getMappingStatus, setStation, oneClickDeployment, abandonMapping, sendAutoBack } from '@/config/getData.js'
import { mapGetters } from 'vuex'
export default {
name: 'ModuleBuilding',
components: {
DriverModal,
GlMap
},
beforeRouteLeave (to, from, next) {
if (this.needsConfirmation) {
this.$confirm(this.$t('Doabandonmapattempt'), this.$t('Prompt'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(async () => {
try {
// 显示加载中状态
this.loading = this.$loading({
lock: true,
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.6)'
})
// 调用放弃建图接口
const res = await abandonMapping()
this.loading.close()
if (res && res.code === 200) {
// 接口成功,允许离开页面
this.needsConfirmation = false // 标记无需再确认
next()
} else {
// 接口返回失败,提示错误并阻止离开
this.$message.error(res.message)
next(false)
}
} catch (error) {
// 发生异常,提示错误并阻止离开
this.loading.close()
next(false)
}
}).catch(() => {
next(false) // 阻止离开
})
} else {
next()
}
},
data () {
const validateStationName = (rule, value, callback) => {
if (value && value.includes(',')) {
callback(new Error(this.$t('stationnotcommas')));
} else {
callback();
}
}
return {
driverVisible: false,
cloudOff: false,
needsConfirmation: true,
mapName: '',
dialogVisible: false,
dataForm: {
stationCode: '',
stationName: ''
},
dataRule: {
stationName: [
{ validator: validateStationName, trigger: 'blur' }
]
},
submitSuccess: false,
keyPoints: [],
disabled: false,
message: '', // 用于显示消息
error: false,
showProgress: false,
warnTip: false,
percentage: 0,
intervalId: null, // 用于存储定时器ID
backActive: false, // 打点完成后新增一个弹框 提示用户是否自动开回上一个点
tipShow: false // 正在自动回退中的提示
}
},
computed: {
...mapGetters(['isTop', 'carPosition', 'autoLoopEnable', 'autoBackEnable', 'autoBackFinish'])
},
watch: {
autoBackEnable: {
handler (val) {
this.backHandleChange(this.backActive, val)
},
deep: true
},
backActive: {
handler (val) {
this.backHandleChange(val, this.autoBackEnable)
},
deep: true
},
autoBackFinish: {
handler (val) {
if (val === '1') {
this.$nextTick(() => {
this.$refs.glMap.init()
})
this.tipShow = false
}
},
deep: true
}
},
beforeDestroy () {
document.removeEventListener('keydown', this.handleKeydown);
if (this.intervalId) {
clearTimeout(this.intervalId)
}
},
mounted() {
// 初始化并启动引导
this.driverVisible = true
this.$nextTick(() => {
this.$refs.driverModal.init()
})
document.addEventListener('keydown', this.handleKeydown);
},
methods: {
driverConfirm () {
this._startMapping()
},
/* eslint-disable */
backHandleChange(active, val) {
if (active && val === '0') {
this.loading = this.$loading({
lock: true,
text: this.$t('systemcalculatingnotmovevehicle'),
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.6)'
})
}
if (active && val === '1') {
this.loading.close()
}
if (active && val === '2') {
this.loading.close()
this.$confirm(this.$t('Whetherdrivebackpoint'), this.$t('Prompt'), {
confirmButtonText: this.$t('yes'),
cancelButtonText: this.$t('no'),
type: 'warning'
}).then(() => {
this._sendAutoBack('0')
this.backActive = false
}).catch(() => {
this._sendAutoBack('1')
this.backActive = false
})
}
},
handleKeydown(event) {
// 仅在对话框可见时响应Enter键
if (this.dialogVisible && event.key === 'Enter') {
event.preventDefault(); // 阻止默认行为
this.stationConfirm();
}
},
// 开始建图
async _startMapping () {
try {
this.loading = this.$loading({
lock: true,
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.6)'
})
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.$t('carbuildingmap')
this.error = false
setTimeout(() => {
this.$nextTick(() => {
this.$refs.glMap.init()
})
}, 2000)
}
this.loading.close()
} catch (e) {
this.message = this.$t('errorbuildingredone')
this.error = true
this.loading.close()
}
},
// 打点
addPoint () {
if (this.autoLoopEnable === '1') return
this.dialogVisible = true
this.dataForm.stationCode = 'B' + (this.keyPoints.length + 1)
const na = this.$i18n.locale === 'en-us' ? 'Work point ' : '工作点'
this.dataForm.stationName = `${na}${this.keyPoints.length + 1}`
this.$nextTick(() => {
const input = document.getElementById('stationName');
if (input) {
input.focus();
}
})
},
// 打点->保存
stationConfirm () {
this.$refs.dataForm.validate((valid) => {
if (valid) {
this.submitSuccess = true
this._setStation()
setTimeout(() => {
this.submitSuccess = false
}, 3000)
} else {
return false
}
})
},
async _setStation () {
this.disabled = true
try {
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.stationCode)
this.backActive = true
} else {
this.$message.error(res.message)
this.backActive = false
}
}
this.dialogVisible = false
this.disabled = false
} catch (e) {
this.$message.error(e)
this.dialogVisible = false
this.disabled = false
this.backActive = false
}
},
stopMappingConfirm () {
if (this.autoLoopEnable !== '1') return
this.$confirm(this.$t('sureendbuilding'), this.$t('Prompt'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(() => {
this._stopMapping()
}).catch(() => {
this.$message({
type: 'info',
message: this.$t('endbuildingcancel')
})
})
},
// 结束建图
async _stopMapping () {
this.disabled = true
try {
this.loading = this.$loading({
lock: true,
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.6)'
})
let res = await stopMapping()
if (res && res.code === 200) {
this.message = ''
this.error = false
this.$nextTick(() => {
this.$refs.glMap.closeWebSocket()
})
this._getMappingStatus()
}
this.disabled = false
this.loading.close()
} catch (e) {
this.message = this.$t('errorendbuilding')
this.error = true
this.disabled = false
this.loading.close()
}
},
// 进度条
async _getMappingStatus () {
try {
let res = await getMappingStatus()
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.warnTip = true
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(this.$t('Newfailedagain'), this.$t('Prompt'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(() => {
this._startMapping()
}).catch(() => {
this.$message({
type: 'info',
message: this.$t('Mapbuildingcancel')
})
})
}
} catch (e) {
this.message = ''
this.error = false
this.keyPoints = []
this.$confirm(this.$t('Newfailedagain'), this.$t('Prompt'), {
confirmButtonText: this.$t('Confirm'),
cancelButtonText: this.$t('Cancel'),
type: 'warning'
}).then(() => {
this._startMapping()
}).catch(() => {
this.$message({
type: 'info',
message: this.$t('Mapbuildingcancel')
})
})
}
},
async _oneClickDeployment () {
try {
let res = await oneClickDeployment(this.mapName)
if (res && res.code === 200) {
this.$message({
type: 'success',
message: res.message
})
this.needsConfirmation = false
this.$router.push('/index/home')
} else {
this.$message.error(res.message)
}
this.showProgress = false
this.warnTip = false
this.message = ''
this.error = false
this.disabled = false
this.loading.close()
} catch (e) {
this.showProgress = false
this.warnTip = false
this.$message.error(e)
this.message = ''
this.error = false
this.disabled = false
this.loading.close()
}
},
closeCloud () {
this.cloudOff = !this.cloudOff
if (this.cloudOff) {
this.$nextTick(() => {
this.$refs.glMap.closeWebSocket()
})
} else {
this.$nextTick(() => {
this.$refs.glMap.init()
})
}
},
// 打点功能点击返回成功后
async _sendAutoBack (flag) {
try {
this.loading = this.$loading({
lock: true,
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.6)'
})
let res = await sendAutoBack(flag)
if (res && res.code === 200) {
if (flag === '0') {
this.$nextTick(() => {
this.$refs.glMap.closeWebSocket()
})
this.tipShow = true
}
}
this.loading.close()
} catch (e) {
this.loading.close()
}
},
failHandle () {
this.$nextTick(() => {
this.$refs.glMap.init()
})
this.tipShow = false
}
}
}
</script>
<style lang="stylus" scoped>
.map_container
position relative
width 100%
height calc(100% - .5rem)
margin-bottom .14rem
.message
min-width 380px
border 1px solid #e1f3d8
border-radius 4px
position fixed
left 50%
transform translateX(-50%)
background-color #f0f9eb
transition opacity .3s,transform .4s
overflow hidden
padding 8px 15px
z-index 2003
display flex
align-items center
font-size .2rem
list-height 1
p
margin-left 10px
.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);
}
.progress_tip {
font-size: .2rem
line-height: .34rem
color: #000
font-weight: 700
}
.car-info
max-width calc(100% - 3rem)
background: rgba(30, 95, 239, 60%);
padding: 0 10px;
border-radius: 8px;
margin-left: 10px;
font-size: 0.2rem;
line-height: 0.36rem;
color: #fff
overflow: hidden
.button_control
p
font-size .26rem
.enClass
.button_control
p
font-size .18rem
line-height .16rem
.car-info
font-size .16rem
</style>