food/src/components/ControllerTabs.vue
tk 7d346defe2 【类 型】:refactor
【主	题】:调整 订单获取接口 相关代码
【描	述】:
	[原因]:数据库 订单表的解构变化 导致接口获取失败
	[过程]:取消quest字段 全部改用 status back字段 并且此俩字段的类型改为 枚举类型
	[影响]:
【结	束】

# 类型 包含:
# feat:新功能(feature)
# fix:修补bug
# docs:文档(documentation)
# style: 格式(不影响代码运行的变动)
# refactor:重构(即不是新增功能,也不是修改bug的代码变动)
# test:增加测试
# chore:构建过程或辅助工具的变动
2024-06-04 18:12:32 +08:00

648 lines
25 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>
<!-- 弹出框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="30%">
<!-- 起飞设置弹出框 -->
<template v-if="dialogItem == 'takeoffBox'">
<div class="slider-container">
<span class="m-l-10 m-r-10">高度(米)</span>
<el-slider class="w-70" v-model="takeoffValue" show-input>
</el-slider>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="medium" @click="dialogVisible = false">关闭</el-button>
<el-button size="medium" type="primary"
@click="publishFun(`{setPlaneState:{bit:6,state:1,count:1,param:[${takeoffValue}]}`)">确认起飞</el-button>
</span>
</template>
<!-- 摄像头弹出框 -->
<template v-if="dialogItem == 'cameraBox'">
<div class="slider-container">
<div class="m-l-10 m-r-10">俯</div>
<el-slider @change="releaseCameraSlider('pitch')" @input="setCamera('pitch')" v-model="pitchValue"
:show-tooltip="false" class="w-80"></el-slider>
<div class="m-l-10 m-r-10">仰</div>
</div>
<div class="slider-container">
<div class="m-l-10 m-r-10">左</div>
<el-slider @change="releaseCameraSlider('yaw')" @input="setCamera('yaw')" v-model="yawValue"
:show-tooltip="false" class="w-80"></el-slider>
<div class="m-l-10 m-r-10">右</div>
</div>
<div class="slider-container">
<div class="m-l-10 m-r-10">大</div>
<el-slider @change="releaseCameraSlider('scale')" @input="setCamera('scale')" v-model="scaleValue"
:show-tooltip="false" class="w-80"></el-slider>
<div class="m-l-10 m-r-10">小</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="medium" @click="dialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
<!-- 侧边 tab -->
<div class="tab-container">
<el-button size="medium" type="primary" :class="activeIndex === index ? 'butIconGroupBG' : ''" class="butIconGroup"
v-for="(item, index) in controlItems" :key="index" @click="toggleContent(index)">
<i :class="item.icon" class="iconfont f-s-35"></i>
<div class="m-t-5 fb">{{ item.title }}</div>
</el-button>
<!-- 子内容菜单 -->
<div class="content" :class="{ 'active': activeIndex !== null }">
<!-- 订单任务 -->
<div v-if="activeIndex === 0" class="contentBox">
<!-- 标题 -->
<div class="clearB m-b-10 fb f-s-16 contentTit">
<i class="iconfont icon-dingdanguanli f-s-22 m-r-5"></i>
<span>送餐任务</span>
</div>
<!-- 内容 -->
<el-form label-position="left" ref="questForm" :model="questForm" label-width="80px">
<el-form-item label="订单任务">
<el-select v-model="questForm.id" :filterable="isMobile" placeholder="请选择,也可输入搜索" :disabled="airLock">
<el-option v-for="item in questList" :key="item.id" :label="item.id" :value="item.id">
<span class="l">{{ item.id }}</span>
<span class="l m-l-5">{{ item.receiver }}</span>
<span class="l m-l-5">{{ item.receive_site_name }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="飞机操作">
<el-button-group>
<el-button size="mini" class="f-s-14" v-if="planeState & 1 && airLock" key="pubBut" type="info"
icon="iconfont el-icon-loading" disabled>
<font class="m-l-5">航线锁定</font>
</el-button>
<el-button size="mini" class="f-s-14" v-if="planeState & 1 && !airLock" type="primary"
icon="f-s-14 iconfont icon-chakanzhihangrizhi" @click="runQuest">
<font class="m-l-5">提交任务</font>
</el-button>
<el-button size="mini" class="f-s-14" v-if="planeState & 2" key="wirteBut" type="info"
icon="f-s-14 iconfont el-icon-loading" disabled>
<font class="m-l-5">航点写入中···</font>
</el-button>
<el-button size="mini" class="f-s-14" v-if="planeState & 4 && !(planeState & 16)" type="warning"
icon="f-s-14 iconfont icon-jiesuo"
@click="publishFun('{setPlaneState:{bit:3,state:1,count:2,param:[1,0]}}')">
<font class="m-l-5">解锁飞机</font>
</el-button>
<el-button size="mini" class="f-s-14" v-if="planeState & 16 && !(planeState & 1) && !(planeState & 2)"
type="success" icon="f-s-14 iconfont icon-yangshi_icon_tongyong_departure"
@click="publishFun('{setPlaneState:{bit:5,state:1}')">
<font class="m-l-5">执行任务</font>
</el-button>
</el-button-group>
</el-form-item>
<el-form-item label="任务操作">
<el-button-group>
<el-button size="mini" class="f-s-14" type="danger" icon="iconfont icon-meiyoudingdan-01" key="celBUt"
@click="reQuest">
<font class="m-l-5">取消任务</font>
</el-button>
<el-button size="mini" class="f-s-14" type="success" icon="iconfont icon-qiandai" key="bingBut"
@click="overQuest">
<font class="m-l-5">已送达</font>
</el-button>
</el-button-group>
</el-form-item>
</el-form>
</div>
<!-- 飞机操作 -->
<template v-if="activeIndex === 1">
<!-- 标题 -->
<div class="clearB m-b-10 fb f-s-16 contentTit">
<i class="iconfont icon-youxishoubing f-s-22 m-r-5"></i>
<span>飞机控制</span>
</div>
<div class="butIconBox">
<el-button size="medium" type="primary" class="butIcon border"
@click="publishFun('{setPlaneState:{bit:3,state:1,count:2,param:[1,0]}}')">
<i class="iconfont icon-jiesuo f-s-24"></i>
<div class="m-t-5">解锁</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="confirmation('飞机加锁,螺旋桨将停转,请谨慎操作!', '加锁操作', '{setPlaneState:{bit:3,state:0,count:2,param:[0,21196]}}')">
<i class=" iconfont icon-suoding f-s-24"></i>
<div class="m-t-5">加锁</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="dialogVisible = true; dialogTitle = '起飞参数设置'; dialogItem = 'takeoffBox'">
<i class="iconfont icon-yangshi_icon_tongyong_departure f-s-24"></i>
<div class="m-t-5">起飞</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="publishFun('{setPlaneState:{bit:7,state:1}')">
<i class="iconfont icon-fengzheng1 f-s-24"></i>
<div class="m-t-5">悬停</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border">
<i class="iconfont icon-duandianxufei f-s-24"></i>
<div class="m-t-5">复航</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="publishFun('{setPlaneState:{bit:9,state:1}')">
<i class="iconfont icon-yijianfanhang f-s-24"></i>
<div class="m-t-5">返航</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="publishFun('{setPlaneState:{bit:5,state:1}')">
<i class="iconfont icon-yangshi_icon_tongyong_arriving f-s-24"></i>
<div class="m-t-5">降落</div>
</el-button>
</div>
</template>
<!-- 附加模组操作 -->
<template v-if="activeIndex === 2">
<!-- 标题 -->
<div class="clearB m-b-10 fb f-s-16 contentTit">
<i class="iconfont icon-mianxingdiaogou f-s-22 m-r-5"></i>
<span>挂载仓控制</span>
</div>
<div class="butIconBox">
<el-button size="medium" type="primary" class="butIcon border" @click="publishFun('{hookConteroller:4}')">
<i class="iconfont icon-zhongliang f-s-24"></i>
<div class="m-t-5">归零</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border" @click="publishFun('{hookConteroller:0}')">
<i class="iconfont icon-xiangshang f-s-24"></i>
<div class="m-t-5">收钩</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border" @click="publishFun('{hookConteroller:3}')">
<i class="iconfont icon-qiyong f-s-24"></i>
<div class="m-t-5">继续</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border" @click="publishFun('{hookConteroller:2}')">
<i class="iconfont icon-xuanting-zanting f-s-24"></i>
<div class="m-t-5">暂停</div>
</el-button>
</div>
<!-- 标题 -->
<div class="clearB m-b-10 fb f-s-16 contentTit">
<i class="iconfont icon-shipinjiankong f-s-22 m-r-5"></i>
<span>摄像头控制</span>
</div>
<div class="butIconBox">
<el-button size="medium" type="primary" class="butIcon border"
@click="publishFun('{cameraController:{item:0,val:0}}')">
<i class="iconfont icon-icon-rotation f-s-24"></i>
<div class="m-t-5">回中</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="publishFun('{cameraController:{item:2,val:0,yaw:0,pitch:-50}}')">
<i class="iconfont icon-down f-s-24"></i>
<div class="m-t-5">俯视</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="dialogVisible = true; dialogTitle = '摄像头控制'; dialogItem = 'cameraBox'">
<i class="iconfont icon-chukong f-s-24"></i>
<div class="m-t-5">手动</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border"
@click="dialogVisible = true; dialogTitle = '摄像头控制'; dialogItem = 'cameraBox'">
<i class="iconfont icon-fangda f-s-24"></i>
<div class="m-t-5">焦距</div>
</el-button>
</div>
<!-- 标题 -->
<div class="clearB m-b-10 fb f-s-16 contentTit">
<i class="iconfont icon-tongzhi f-s-22 m-r-5"></i>
<span>喇叭控制</span>
</div>
<div class="butIconBox">
<el-button size="medium" type="primary" class="butIcon border">
<i class="iconfont icon-icon-test f-s-24"></i>
<div class="m-t-5">喊话</div>
</el-button>
</div>
</template>
<!-- 飞机调试 -->
<template v-if="activeIndex === 3">
<!-- 标题 -->
<div class="clearB m-b-10 fb f-s-16 contentTit">
<i class="iconfont icon-banshou_Line f-s-22 m-r-5"></i>
<span>飞机调试</span>
</div>
<div class="butIconBox">
<el-button size="medium" type="primary" class="butIcon border" @click="publishFun('{bit:11,state:1}')">
<i class="iconfont icon-zhinanzhen f-s-24"></i>
<div class="m-t-5">磁罗盘</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border">
<i class="iconfont icon-zuobiaozhoupeizhixiang f-s-24"></i>
<div class="m-t-5">加速度计</div>
</el-button>
<el-button size="medium" type="primary" class="butIcon border">
<i class="iconfont icon-canshupeizhi f-s-24"></i>
<div class="m-t-5">写入参数</div>
</el-button>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import { questAss } from '@/utils/api/table'
import mqtt from '@/utils/mqtt'
export default {
name: 'TabController',
data () {
return {
dialogTitle: '', // 弹出框 标题
dialogItem: '', // 弹出框 项目类型判断
dialogVisible: false, // 弹出框 显隐
pitchValue: 50, // 摄像头控制滑动条 俯仰值
yawValue: 50, // 摄像头控制滑动条 旋转值
scaleValue: 50, // 摄像头控制滑动条 焦距值
takeoffValue: 2, // 起飞高度
controlItems: [// 菜单
{ title: '任务', icon: 'icon-songcanfuwu' },
{ title: '控制', icon: 'icon-youxishoubing' },
{ title: '扩展', icon: 'icon-linghuokuozhan' },
{ title: '调试', icon: 'icon-banshou_Line' }
],
activeIndex: null, // 当前选中的菜单
tabIsOpen: false, // 判断tab 是否弹出
questForm: { // 送餐任务表单
id: ''
},
planesId: this.$route.params.id, // 飞机id
airLock: false // 当前飞机是否被锁定
}
},
props: {
plane: {
typeof: 'Object',
deep: true
}
},
computed: {
/**
* @description: 终端平台是否是pc
*/
isMobile () {
return this.$store.state.app.isMobile
},
/**
* @description: 飞机状态
*/
planeState () {
const plane = this.plane
return plane ? plane.planeState.state : null
},
/**
* @description: 已付款 已承接 未发货 未发起退款 订单列表
*/
questList () {
const plane = this.plane
return plane ? this.$store.state.orderList.filter((item) => item.shop_id === plane.shop_id && item.status === 'processing' && item.back === 'normal') : []
},
/**
* @description: 已发货 订单列表
*/
overQuestList () {
return this.$store.state.orderList.filter((item) => item.status === 'shipped')
},
/**
* @description: 航线列表
*/
routeList () {
return this.$store.state.routeList
},
/**
* @description: 获取站点列表
*/
siteList () {
return this.$store.state.siteList
}
},
methods: {
questAss,
/**
* @description: 摄像头 滑动条松开
* @param {string} item 项目
*/
releaseCameraSlider (item) {
if (item === 'pitch') {
this.pitchValue = 50
this.publishFun('{cameraController:{item:2,val:0,yaw:0,pitch:0}}')
} else if (item === 'yaw') {
this.yawValue = 50
this.publishFun('{cameraController:{item:2,val:0,yaw:0,pitch:0}}')
} else if (item === 'scale') {
this.publishFun('{cameraController:{item:1,val:0}}')
this.scaleValue = 50
}
},
/**
* @description: 摄像头 滑动条滚动 发送设置命令
* @param {*} item 项目
*/
setCamera (item) {
if (item === 'pitch') { // 设置pitch轴
const pitchV = (this.pitchValue - 50) * 2
this.publishFun(`{cameraController:{item:2,yaw:0,pitch:${pitchV}}`)
} else if (item === 'yaw') { // 设置yaw轴
const yawV = (this.yawValue - 50) * 2
this.publishFun(`{cameraController:{item:2,yaw:${yawV},pitch:0}`)
} else if (item === 'scale') {
if (this.scaleValue < 50) { // 焦距放大
this.publishFun('{cameraController:{item:1,val:1}}')
} else if (this.scaleValue > 50) {
this.publishFun('{cameraController:{item:1,val:255}}')
}
}
},
/**
* @description: 菜单切换 PS:UI
* @param {*} index 序号
*/
toggleContent (index) {
this.activeIndex = this.activeIndex === index ? null : index
if (this.tabIsOpen) {
if (index !== this.activeIndex) {
this.tabIsOpen = false
this.$emit('mapXOffset', -200)
}
} else {
this.tabIsOpen = true
this.$emit('mapXOffset', 200)
}
},
/**
* @description: 发布 mqtt 信息
* @param {*} jsonData {'item':val} // item: questAss飞行航点任务 setPlaneState 设置飞机状态 getPlaneState获取飞机状态 resetState设置飞机初始状态 chan1油门通道1 chan2油门通道2 chan3油门通道3 chan4油门通道4 hookConteroller钩子控制 cameraController云台相机控制
*/
publishFun (jsonData) {
if (this.plane) {
const val = jsonData
mqtt.publishFun(`cmd/${this.plane.macadd}`, val)
} else {
this.$message.warning('与飞机通信未接通,请稍后')
}
},
/**
* @description: 执行订单任务
*/
runQuest () {
if (this.questForm.id === '') {
this.$message.error('未选择订单任务!')
return
}
let i
this.questList.map((item) => {
if (this.questForm.id === item.id) {
i = true
/* 插入日志 */
this.$store.dispatch('fetchLog', { content: `${this.plane.name} 开始执行 订单ID${item.id}、叫餐号:${item.food_sn}号。` })
/* 执行写在这里 */
if (item.bind_route === null) { // 判断站点是否已经绑定站点 中断操作
this.$message.error('此站点,未绑定任务航点')
return
}
let routeData
try {
const bindRoute = item.bind_route.split(',')
routeData = this.routeList.find(element => element.id === bindRoute[0]).route_data
routeData = JSON.parse(routeData)// 反序列化
/* 处理声音航点 航点里面的表达式 如$food_sn$ 正则替换成订单对应的字段 */
item.telTail = item.tel.substr(-4)// 手动加一个手机尾号telTail字段 从 tel字段截取后四位
routeData.tasks.forEach((x, index) => {
if (x.sound) {
const str = this.voiceRouteParse(item, x.sound)
routeData.tasks[index].sound = str// 重新写入声音航点
}
})
routeData = JSON.stringify(routeData)// 重新序列化
} catch (error) {
this.$message.error('操作失败,请重新尝试')
}
this.publishFun(`{"questAss":${routeData}}`)// 发送航点信息主题
this.questAss(item.id, 'status', '30')// 订单改为发货状态
this.$store.dispatch('fetchLockSite', { id: item.receive_site_id, runing: this.plane.id })// 航线注册飞机 锁定送餐点
}
})
if (i) { return }
this.$message.error('此订单已被申请退款或者订单已经被取消!')
},
/**
* @description: 匹配声音航点字符串 比如$food_sn$ food_sn匹配成 送餐订单里面的对应字段
* @param {*} questItem 送餐的订单 ps:从数据库里拿的 订单信息对象
* @param {*} voiceRouteStr 声音航点的字符串
*/
voiceRouteParse (questItem, voiceRouteStr) {
// 定义正则表达式
const regex = /\$(.*?)\$/g
// 使用replace()方法进行匹配和替换
const replacedStr = voiceRouteStr.replace(regex, (match, p1) => {
// match表示匹配到的字符串p1表示捕获组中的内容
return questItem[p1] // 用捕获组的内容替换匹配到的字符串
})
// 输出替换后的字符串
return replacedStr // 输出 "请45号电话为13301115846的先生取餐"
},
/**
* @description: 确认操作
* @param {*}msg 提示的话语内容
* @param {*}tit 提示框的标题
* @param {*}instruct 需要发送的mqtt指令内容
*/
confirmation (msg, tit, instruct) {
this.$confirm(msg, tit, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.publishFun(instruct)
}).catch(() => {
this.$message.info('取消操作!')
})
},
/**
* @description: 取消任务
*/
reQuest () {
if (!this.airLock) { // 只有飞机锁定状态 才向量下执行 "取消"操作
return
}
this.$confirm('确认复位飞机状态,并清除航线的锁定?', '取消任务', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.overQuestList.forEach((item) => {
if (item.runing === this.planesId) {
/* 插入日志 */
this.$store.dispatch('fetchLog', { content: `订单ID${item.id},送餐任务取消。` })
/* 执行写在这里 */
this.publishFun('{"resetState":1}')// 发送设置飞机状态主题 状态设为闲置
this.questAss(item.id, 'status', '20')// 订单改回到发货状态之前 既“已付款状态”
this.$store.dispatch('fetchLockSite', { id: item.receive_site_id, runing: 'null' })// 解锁航线
}
})
}).catch(() => {
this.$message.info('取消操作!')
})
},
/**
* @description: 已送达任务
*/
overQuest () {
if (!this.airLock) { // 只有飞机锁定状态 才向量下执行 "已送达"操作
return
}
this.$confirm('确认将订单状态改为已送达吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 确认执行
this.overQuestList.map((item) => {
if (item.runing === this.planesId) { // 找出当前飞机正在执行的任务
/* 插入日志 */
this.$store.dispatch('fetchLog', { content: `订单ID${item.id} 送餐任务已完成。` })
/* 执行写在这里 */
this.publishFun('{"resetState":1}')// 发送设置飞机状态主题 状态设为闲置
this.questAss(item.id, 'status', '40')// 订单改为已完成状态
this.$store.dispatch('fetchLockSite', { id: item.receive_site_id, runing: 'null' })// 解锁航线
}
})
}).catch(() => {
this.$message.info('取消操作!')
})
},
/**
* @description: 在地图上绘制航线
*/
makeRouteForMap () {
let bindRoute
let routeData
this.siteList.forEach(item => {
if (item.runing === this.planesId) {
// 获取航点信息
if (item.bind_route !== null) { // 判断站点是否已经绑定站点
bindRoute = item.bind_route.split(',')
this.$store.dispatch('fetchRouteList').then(res => { // 只能异步拿 虽然效率低一些
routeData = res.find(element => element.id === bindRoute[0]).route_data
this.$emit('makeRoute', JSON.parse(routeData))
})
} else {
this.$message.error('此站点,未绑定任务航点')
}
}
})
}
},
created () {
setTimeout(() => {
console.log(this.$store.state.orderList)
}, 3000)
if (this.siteList && this.routeList) {
this.airLock = this.siteList.some(item => item.runing === this.planesId)
}
},
watch: {
airLock (val) {
if (val) { // 如果当前飞机正在执行任务 把航线绘制到地图上
this.makeRouteForMap()
} else { // 如果没有执行任务 把地图上航线清除
this.$emit('clearRoute')
}
this.$store.dispatch('fetchOrderList')// 刷新订单列表
this.$store.dispatch('fetchSiteList')// 刷新站点列表
},
siteList (val) {
this.airLock = val.some(item => item.runing === this.planesId)
}
}
}
</script>
<style lang="scss" scoped>
@import "@/styles/theme.scss";
.tab-container {
height: 365px;
width: 80px;
display: flex;
flex-direction: column;
align-items: flex-end;
position: fixed;
right: 15px;
top: 50%;
transform: translateY(-50%);
z-index: 1000;
}
.butIconGroup {
color: $maintext-color;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 15px;
padding: 5px;
width: 80px;
height: 80px;
text-align: center;
margin-bottom: 20px;
border: none;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
}
.butIconGroupBG {
color: $graylight-color;
background-color: $brand-color;
}
.butIconGroup:last-of-type {
margin-bottom: 0px;
}
.slider-container {
display: flex;
align-items: center;
/* 水平垂直居中 */
}
.content {
color: $maintext-color;
border-radius: 15px;
height: 100%;
position: fixed;
right: -400px;
top: 50%;
transform: translateY(-50%);
width: 350px;
background-color: rgba(255, 255, 255, 0.8);
padding: 20px;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
transition: right 0.3s ease;
}
.content.active {
right: 95px;
}
.contentTit i {
vertical-align: middle;
}
.butIcon {
border-radius: 10px;
padding: 5px;
width: 66px;
height: 66px;
text-align: center;
border: none;
float: left;
margin: 0px !important;
margin-bottom: 15px !important;
margin-right: 15px !important;
}
.butIconBox .butIcon:nth-child(4n) {
margin-right: 0 !important;
}
</style>