chore: 初始化株洲中车部装平台 Pad

This commit is contained in:
yexingqiang
2026-06-08 17:09:27 +08:00
commit 0b5ea4b5db
46 changed files with 12346 additions and 0 deletions

663
src/views/HMIView/index.vue Normal file
View File

@@ -0,0 +1,663 @@
<template>
<div class="main">
<div class="top">
<div class="left">
<span>当前RGV通道</span>
<span class="text">{{ store.user.opDescription || store.user.opName }}</span>
</div>
<div class="right">
<el-button type="success" @mousedown="WritePlc('启动',1)" @mouseup="WritePlc('启动',0)">启动</el-button>
<el-button type="warning" @mousedown="WritePlc('复位',1)" @mouseup="WritePlc('复位',0)">复位</el-button>
</div>
</div>
<div class="center">
<fieldset class="left">
<legend class="title">主界面</legend>
<div class="main">
<div class="carBattery">
当前电量
<div class="battery">
<div :style="{ height: data.batteryLevel + '%' }" class="battery-bar"></div>
<div style="position: relative;top: 40%">{{ data.batteryLevel }} %</div>
</div>
</div>
<div class="carPosList">
<div class="item">
<div class="unit">{{ data.XCWZ }} mm</div>
<div class="text">小车位置</div>
</div>
<div class="item">
<div class="unit">{{ data.SJWZ }} mm</div>
<div class="text">升降位置</div>
</div>
</div>
<div class="carStatus">
<div class="item">
<div :style="{ backgroundColor: data.carStatus1 ? '#1afa29' : '#8a8a8a' }" class="status"></div>
<el-button type="primary" @mousedown="WritePlc('点动',1)" @mouseup="WritePlc('点动',0)">点动</el-button>
</div>
<div class="item">
<div :style="{ backgroundColor: data.carStatus2 ? '#1afa29' : '#8a8a8a' }" class="status"></div>
<el-button type="primary" @mousedown="WritePlc('半自动',1)" @mouseup="WritePlc('半自动',0)">半自动</el-button>
</div>
<div class="item">
<div :style="{ backgroundColor: data.carStatus3 ? '#1afa29' : '#8a8a8a' }" class="status"></div>
<el-button type="primary" @mousedown="WritePlc('上料自动',1)" @mouseup="WritePlc('上料自动',0)">上料自动</el-button>
</div>
<div class="item">
<div :style="{ backgroundColor: data.carStatus4 ? '#1afa29' : '#8a8a8a' }" class="status"></div>
<el-button type="primary" @mousedown="WritePlc('下料自动',1)" @mouseup="WritePlc('下料自动',0)">下料自动</el-button>
</div>
</div>
</div>
</fieldset>
<fieldset class="right">
<legend class="title">手动操作</legend>
<div class="itemList">
<div class="item"></div>
<div class="item">
<div class="ellipseTest"
:style="{ backgroundColor: data.SJHYWC ? 'rgba(26, 250, 41,1)' : 'rgba(26, 250, 41,0)' }">升降回原完成</div>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('小车前进',1)" @mouseup="WritePlc('小车前进',0)">小车前进</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('升降回原位',1)" @mouseup="WritePlc('升降回原位',0)">升降回原位</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('小车后退',1)" @mouseup="WritePlc('小车后退',0)">小车后退</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('上升定位',1)" @mouseup="WritePlc('上升定位',0)">上升定位</el-button>
</div>
<div class="item"></div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('下降定位',1)" @mouseup="WritePlc('下降定位',0)">下降定位</el-button>
</div>
</div>
</fieldset>
</div>
<div class="bottom">
<fieldset class="left">
<legend class="title">充电桩</legend>
<div class="itemList">
<div class="item">
<div class="ellipseTest"
:style="{ backgroundColor: data.ZZCD ? 'rgba(26, 250, 41,1)' : 'rgba(26, 250, 41,0)' }">正在充电</div>
</div>
<div class="item">
<div class="unit">{{ data.CDDL }}</div>
<div class="text">充电电流</div>
</div>
<div class="item">
<div class="unit">{{ data.CDDY }}</div>
<div class="text">充电电压</div>
</div>
<div class="item">
<div class="ellipseTest"
:style="{ backgroundColor: data.DSSCDW ? 'rgba(26, 250, 41,1)' : 'rgba(26, 250, 41,0)' }">电刷伸出到位</div>
</div>
<div class="item">
<div class="unit">{{ data.CDMS }}</div>
<div class="text">充电模式</div>
</div>
<div class="item">
<div class="unit">{{ data.XT }}</div>
<div class="text">心跳</div>
</div>
</div>
<div style="display: flex">
<div class="item">
<el-button type="primary" @mousedown="WritePlc('开始充电',1)" @mouseup="WritePlc('开始充电',0)">开始充电</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('停止充电',1)" @mouseup="WritePlc('停止充电',0)">停止充电</el-button>
</div>
</div>
</fieldset>
<fieldset class="right">
<legend class="title">工位操作</legend>
<div class="itemList">
<div class="item">上料位</div>
<div class="item">充电位</div>
<div class="item">位置1</div>
<div class="item">位置2</div>
<div class="item">位置3</div>
<div class="item">位置4</div>
<div class="item">位置5</div>
<div class="item" :style="{ color: getYdColor(data.SLDD) }">{{ data.SLDD ? '已到' : '未到' }}</div>
<div class="item" :style="{ color: getYdColor(data.CDWDD) }">{{ data.CDWDD ? '已到' : '未到' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ1DD) }">{{ data.WZ1DD ? '已到' : '未到' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ2DD) }">{{ data.WZ2DD ? '已到' : '未到' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ3DD) }">{{ data.WZ3DD ? '已到' : '未到' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ4DD) }">{{ data.WZ4DD ? '已到' : '未到' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ5DD) }">{{ data.WZ5DD ? '已到' : '未到' }}</div>
<div class="item" :style="{ color: getYdColor(data.SLXZ) }">{{ data.SLXZ ? '已选' : '未选' }}</div>
<div class="item" :style="{ color: getYdColor(data.CDXZ) }">{{ data.CDXZ ? '已选' : '未选' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ1XZ) }">{{ data.WZ1XZ ? '已选' : '未选' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ2XZ) }">{{ data.WZ2XZ ? '已选' : '未选' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ3XZ) }">{{ data.WZ3XZ ? '已选' : '未选' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ4XZ) }">{{ data.WZ4XZ ? '已选' : '未选' }}</div>
<div class="item" :style="{ color: getYdColor(data.WZ5XZ) }">{{ data.WZ5XZ ? '已选' : '未选' }}</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('上料位选择',1)" @mouseup="WritePlc('上料位选择',0)">选择</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('充电位选择',1)" @mouseup="WritePlc('充电位选择',0)">选择</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('位置1选择',1)" @mouseup="WritePlc('位置1选择',0)">选择</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('位置2选择',1)" @mouseup="WritePlc('位置2选择',0)">选择</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('位置3选择',1)" @mouseup="WritePlc('位置3选择',0)">选择</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('位置4选择',1)" @mouseup="WritePlc('位置4选择',0)">选择</el-button>
</div>
<div class="item">
<el-button type="primary" @mousedown="WritePlc('位置5选择',1)" @mouseup="WritePlc('位置5选择',0)">选择</el-button>
</div>
</div>
<el-button type="primary" @mousedown="WritePlc('定位启动',1)" @mouseup="WritePlc('定位启动',0)">定位启动</el-button>
</fieldset>
</div>
</div>
</template>
<script setup>
import { useAppStore } from "@/store/app.js";
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
import mqtt from "mqtt";
import { ElMessage, ElMessageBox } from 'element-plus'
const store = useAppStore();
const CreateData = inject('CreateData')
const ExecDatabase = inject('ExecDatabase')
const ExecReadPLC = inject('ExecReadPLC')
const ExecIMESAPI = inject('ExecIMESAPI')
const ExecWritePLC = inject('ExecWritePLC')
const ExecMultReadPLC = inject('ExecMultReadPLC')
const data = reactive({
batteryLevel: 0, // 当前电量
XCWZ: '0', // 小车位置
SJWZ: '0.00', // 升降位置
carStatus1: false, // 点动
carStatus2: false, // 半自动
carStatus3: false, // 上料自动
carStatus4: false, // 下料自动
SJHYWC: false, // 升降回原完成
ZZCD: false, // 正在充电
DSSCDW: false, // 电刷伸出到位
CDDL: '0.00', // 充电电流
CDDY: '0.00', // 充电电压
CDMS: '0.00', // 充电模式
XT: '0.00', // 心跳
SLDD: false, // 上料到达
CDWDD: false, // 充电位到达
WZ1DD: false, // 位置1到达
WZ2DD: false, // 位置2到达
WZ3DD: false, // 位置3到达
WZ4DD: false, // 位置4到达
WZ5DD: false, // 位置5到达
SLXZ: false, // 上料选择
CDWXZ: false, // 充电位选择
WZ1XZ: false, // 位置1选择
WZ2XZ: false, // 位置2选择
WZ3XZ: false, // 位置3选择
WZ4XZ: false, // 位置4选择
WZ5XZ: false, // 位置5选择
})
var client = null
var timer1 = null
onMounted(() => {
connectMqtt()
initData()
timer1 = setInterval(() => {
readMultiPLCByTagIds()
}, 1000 * 2)
})
onUnmounted(() => {
try {
if (client) {
client.end();
}
} catch (error) {
console.log('关闭连接失败:', error);
}
// 可选:重置 client 为 null
client = null;
// 关闭定时器
clearInterval(timer1);
});
//连接MQTT
const connectMqtt = async () => {
const topics = ['PLCToWebUI']
// 连接选项
const options = {
keepalive: 60, // 心跳时间
reconnectPeriod: 3000, // 重新连接间隔时间
clean: true, // 是否清除会话
clientId: 'WEB_Op_' + getUUID(),//要确保唯一性例如随机数或者UUID,
username: '',//mqtt服务器配置里面的账号,
password: ''//mqtt服务器配置里面的密码,
}
console.log(window.g.Mqtt_Ip)
client = mqtt.connect(window.g.Mqtt_Ip, options);
client.on('reconnect', (error) => {
console.log('正在重连:', error)
ElMessage.warning('正在重连' + error)
})
client.on('error', (error) => {
console.log('连接失败:', error)
ElMessage.warning('连接失败' + error)
})
client.on('connect', (e) => {
console.log('MQTT连接成功');
// 订阅多个主题
client.subscribe(topics, { qos: 0 }, function (err) {
if (!err) {
console.log('订阅成功');
} else {
console.error('订阅失败:', err);
ElMessage.warning('订阅失败' + err)
}
})
})
client.on('message', (topic, message, packet) => {
messageHandle(topic, message.toString())
})
}
//状态灯信号监控
// case 600004://点动选择灯
// case 600005://半自动选择灯
// case 600006://上料自动选择灯
// case 600007://下料自动选择灯
// case 600010://正在充电灯
// case 600011://电刷伸出到位灯
// case 600020://升降回原完成灯
// case 600025://上料位选择状态灯
// case 600026://上料位到达状态灯
// case 600030://充电位选择状态灯
// case 600031://充电位到达状态灯
// case 600033://位置1选择状态灯
// case 600034://位置1到达状态灯
// case 600036://位置2选择状态灯
// case 600037://位置2到达状态灯
// case 600041://位置3选择状态灯
// case 600042://位置3到达状态灯
// case 600044://位置4选择状态灯
// case 600045://位置4到达状态灯
// case 600047://位置5选择状态灯
// case 600050://位置5到达状态灯
// case 600060://当前电量
// case 600100://小车位置
// case 600100://小车位置
// case 600140://升降位置
// case 600180://充电电流
// case 600200://充电电压
// case 600220://心跳计数
// case 600240://充电模式
const correspondenceList = window.plcPos.correspondenceList
const buttonList = window.plcPos.buttonList
const messageHandle = (topic, message) => {
switch (topic) {
case 'PLCToWebUI':
let reqData = JSON.parse(message)
console.log(reqData)
if (reqData.OPCode == store.user.opName) {
// 先判断data对象是否存在该tagId 如果存在 则该对应的数据
if (reqData.tagTypeCodeID in correspondenceList) {
data[correspondenceList[reqData.tagTypeCodeID].value] = reqData.tagValue
console.log('接收到信号【' + correspondenceList[reqData.tagTypeCodeID].name + '】信号值:' + reqData.tagValue)
}
}
break;
default:
return
break;
}
}
const getUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// 批量读取PLC标签
const readMultiPLCByTagIds = () => {
// 需要读取的所有标签ID
const tagIds = ["600060", "600100", "600140", "600180", "600200", "600220", "600240"]
ExecMultReadPLC(store.user.opName, tagIds).then(response => {
if (response.data.code == 0) {
const results = response.data.ReadRes_SignalValues
// 处理所有返回的数据
results.forEach(result => {
const tagId = result.tagTypeCodeID
let value = result.tagValue
// 转换布尔值
if (value === 'True') {
value = true
} else if (value === 'False') {
value = false
}
// 如果对应表中有该标签,则更新数据
if (tagId in correspondenceList) {
data[correspondenceList[tagId].value] = value
}
})
}
})
}
// 保留原始方法,但不再使用
const readPLCByTagId = (tagId) => {
ExecReadPLC(store.user.opName, tagId).then(responseReadPLC => {
if (responseReadPLC.data.code == 0) {
if (responseReadPLC.data.tagValue == 'True') {
responseReadPLC.data.tagValue = true
} else if (responseReadPLC.data.tagValue == 'False') {
responseReadPLC.data.tagValue = false
}
data[correspondenceList[tagId].value] = responseReadPLC.data.tagValue
}
})
}
const initData = async () => {
var param = {
OPName: store.user.opName,
Operator: store.user.AID
}
let response = await ExecIMESAPI('WebHMIInit', param)
if (response.data.code != 0) {
ElMessage.error(response.data.msg)
} else {
for (let item of response.data.dataValues) {
if (item.tagValue == 'True') {
item.tagValue = true
} else if (item.tagValue == 'False') {
item.tagValue = false
}
data[correspondenceList[item.tagTypeCodeID].value] = item.tagValue
}
ElMessage.success('初始化成功!')
}
}
const getYdColor = (isOk) => {
if (isOk) {
return '#1afa29'
} else {
return '#000000'
}
}
const WritePlc = async (tagName, value) => {
let tagId = buttonList[tagName]
console.log('WritePlc',tagName,tagId, value)
let responseReadPLC = await ExecWritePLC(store.user.opName, tagId, value)
if (responseReadPLC.data.code == 0) {
if (value == 1) {
ElMessage.warning(tagName+'按钮按下')
} else if(value == 0) {
ElMessage.success(tagName+'按钮抬起')
}
} else {
ElMessage.error(responseReadPLC.data.msg)
}
}
</script>
<style lang="scss" scoped>
// 左侧盒子
.left {
width: 49vw;
padding-left: 1vw;
}
// 右侧盒子
.right {
width: 49vw;
padding-left: 1vw;
}
// 标题
.title {
font-size: 1.5vw;
}
// 椭圆状态球
.ellipseTest {
width: 15vw;
height: 6vh;
line-height: 6vh;
font-size: 1.5vw;
//color: #fff;
border-radius: 50%;
//background-color: #1296db;
border: 10px solid #1296db;
margin: 0 auto;
}
.main {
font-size: 1vw;
.top {
display: flex;
padding: 2vh 0;
font-size: 2vw;
height: 7vh;
.left {
.text {
background-color: #008000;
margin-left: 20px;
color: #fff;
}
}
.right {
.el-button {
width: 9vw;
height: 6vh;
font-size: 1.5vw;
margin: 0 20px;
}
}
}
//中间部分
.center {
display: flex;
// 主界面
.left {
.main {
display: flex;
justify-content: space-around;
.carBattery {
text-align: center;
.battery {
width: 8vw;
height: 30vh;
background-color: #ddd;
position: relative;
border-radius: 3px;
overflow: hidden;
.battery-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: #32cd32;
transition: height 0.3s ease;
/* 平滑过渡效果 */
}
}
}
.carPosList {
width: 15vw;
text-align: center;
line-height: 8.2vh;
.item {
font-size: 1.5vw;
.unit {
height: 8.2vh;
background-color: #ffffc0;
}
.text {
height: 8.2vh;
background-color: #48d1cc;
}
}
}
.carStatus {
.item {
display: flex;
margin: 1.5vh 0;
.status {
width: 3vw;
height: 3vw;
border-radius: 50%;
background-color: #1afa29;
}
.el-button {
width: 9vw;
height: 6vh;
font-size: 1.5vw;
margin: 0 20px;
}
}
}
}
}
// 手动操作
.right {
.itemList {
display: flex;
flex-wrap: wrap;
height: 100%;
.item {
width: 50%;
height: 25%;
text-align: center;
.el-button {
height: 80%;
width: 60%;
font-size: 1.5vw;
}
}
}
}
}
.bottom {
display: flex;
.left {
.itemList {
display: flex;
flex-wrap: wrap;
height: 100%;
.item {
width: 30%;
height: 25%;
margin: 1vh 1.6%;
text-align: center;
font-size: 1.5vw;
.unit {
height: 5.2vh;
background-color: #ffffc0;
}
.text {
height: 5.2vh;
background-color: #48d1cc;
}
.ellipseTest {
position: relative;
left: -1vw;
top: 2vh;
}
}
}
.el-button {
margin: 2vh 2vw;
width: 9vw;
height: 6vh;
font-size: 1.5vw;
}
}
.right {
.itemList {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
.item {
width: 14.28%;
height: 7vh;
text-align: center;
font-size: 1.5vw;
.el-button {
width: 6vw;
height: 5vh;
font-size: 1.3vw;
margin: 0;
}
}
}
.el-button {
width: 9vw;
height: 6vh;
font-size: 1.5vw;
}
}
}
}
</style>