涂装车间GP12触摸屏

This commit is contained in:
2025-01-02 17:54:39 +08:00
parent 058b20ef5a
commit 40676be6d0
28 changed files with 5442 additions and 135 deletions

View File

@@ -0,0 +1,104 @@
<template>
<div class="header">
<div class="title">{{ title }}</div>
<div class="menu-time-container">
<div class="menu">
<button v-for="(item, index) in btnItems" :key="index" @click="handleMenuClick(item)"
class="icon-button">
<span :class="['mdi', item.icon]"></span>
</button>
</div>
<div class="time">{{ currentTime }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'TouchScreenHeader',
props: {
title: {
type: String,
required: true
},
btnItems: {
type: Array,
required: true
}
},
data() {
return {
currentTime: ''
};
},
created() {
this.updateTime();
this.timer = setInterval(this.updateTime, 1000);
},
beforeDestroy() {
clearInterval(this.timer);
},
methods: {
handleMenuClick(item) {
this.$emit('menu-click', item);
},
updateTime() {
this.currentTime = new Date().toLocaleTimeString();
}
}
}
</script>
<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #2980b9;
/* 明亮的蓝色 */
color: #ecf0f1;
/* 浅灰色文字颜色 */
padding: 20px 20px;
/* 增加标题高度 */
}
.title {
font-size: 32px;
/* 增大文字大小 */
font-weight: bold;
}
.menu-time-container {
display: flex;
align-items: center;
}
.menu {
display: flex;
align-items: center;
margin-right: 15px;
/* 图标按钮靠近时间左边 */
}
.menu .icon-button {
background-color: transparent;
border: none;
color: #ecf0f1;
/* 浅灰色文字颜色 */
font-size: 24px;
/* 增大图标大小 */
cursor: pointer;
margin-left: 15px;
padding: 0;
}
.menu .icon-button:hover {
text-decoration: underline;
}
.time {
font-size: 24px;
/* 增大文字大小 */
font-weight: normal;
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div class="current-time">
{{ formattedTime }}
</div>
</template>
<script>
export default {
data() {
return {
now: new Date(), // 初始化当前时间为当前时间
timer: null // 用于存储计时器的引用
}
},
computed: {
formattedTime() {
// 返回格式化后的时间字符串
return this.now.toLocaleTimeString();
}
},
created() {
// 初始更新时间
this.updateTime();
// 设置每秒更新一次时间的计时器
this.timer = setInterval(this.updateTime, 1000);
},
beforeDestroy() {
// 在组件销毁前清除计时器,避免内存泄漏
clearInterval(this.timer);
},
methods: {
updateTime() {
// 更新当前时间为最新的时间
this.now = new Date();
}
}
}
</script>
<style scoped>
.current-time {
font-size: 20px;
/* 设置字体大小 */
font-weight: bold;
/* 设置字体加粗 */
}
</style>

View File

@@ -0,0 +1,20 @@
.container{
margin: 0;
padding: 10px;
width: 100%;
min-height: 93vh;
background-color: #000d2c;
display: flex;
flex-direction: column;
}
.nav-right{
display: flex;
flex-direction: row;
width: 300px;
}
.text-green{
color: #67C23A;
}
.text-red{
color: red;
}

View File

@@ -0,0 +1,255 @@
<template>
<div id="app">
<!-- 顶部标题与功能菜单栏 -->
<TouchScreenHeader :title="title" :btnItems="btnItems" @menu-click="clickTopMenu">
</TouchScreenHeader>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 左侧主页面 -->
<div class="main-page">
<!-- 主页面内容 -->
<div v-if="currentMenu === 0">
<WorkOrder></WorkOrder>
</div>
<div v-if="currentMenu === 1">记录面内容</div>
<div v-if="currentMenu === 2">工具面内容</div>
<div v-if="currentMenu === 3">
<comText></comText>
</div>
</div>
<!-- 右侧目录菜单 -->
<div class="menu">
<ul>
<li v-for="(item, index) in menuList" :key="index" @click="changeMenu(index)"
:class="{ active: currentMenu === index }">
<div :class="`mdi mdi-${item.icon}`"></div>
<div>{{ item.name }}</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import TouchScreenHeader from './components/header.vue';
import comText from '../tool/comText.vue';
import WorkOrder from '../workorder/index.vue';
export default {
name: 'App',
components: {
TouchScreenHeader,
WorkOrder,
comText
},
data() {
return {
loading: false,
title: '上海干巷车镜GP12缺陷采集系统',
btnItems: [
{ label: 'Home', icon: 'mdi-home' },
{ label: 'FullScreen', icon: 'mdi-fullscreen' },
{ label: 'COM', icon: 'mdi-usb-port' },
// { label: 'Settings', icon: 'mdi-cog' },
// { label: 'Profile', icon: 'mdi-account' }
],
currentMenu: 0,
menuList: [{
name: '质量填写',
icon: 'clipboard-list'
}, {
name: '记录',
icon: 'file-document'
}, {
name: '工具',
icon: 'toolbox'
}, {
name: '串口调试',
icon: 'usb-port'
}]
};
},
methods: {
clickTopMenu(item) {
// 返回主页
if (item.label === 'Home') {
this.$router.push('/');
}
// 全屏
if (item.label === 'FullScreen') {
this.toggleFullScreen();
}
// 串口通讯
if (item.label === 'COM') {
this.connectSerialPort()
}
},
// 方法定义
changeMenu(index = -1) {
this.currentMenu = index;
},
toggleFullScreen() {
const element = document.documentElement;
if (!document.fullscreenElement) {
element.requestFullscreen().catch(err => {
console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
});
} else {
document.exitFullscreen().catch(err => {
console.error(`Error attempting to exit full-screen mode: ${err.message} (${err.name})`);
});
}
},
async connectSerialPort() {
try {
// 请求用户选择串口
this.serialPort = await navigator.serial.requestPort();
// 打开串口
await this.serialPort.open({ baudRate: 9600 });
// 创建读取器
const decoder = new TextDecoderStream();
this.reader = this.serialPort.readable.pipeThrough(decoder).getReader();
// 读取数据
while (true) {
const { value, done } = await this.reader.read();
if (done) {
break;
}
console.log(value);
}
} catch (error) {
console.error('无法连接到串口:', error);
}
}
}
};
</script>
<style scoped>
@import './index.css';
.border {
border: 1px solid red;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #2c3e50;
/* 设置背景颜色 */
color: #ecf0f1;
/* 设置主要文字颜色 */
}
.main-content {
display: flex;
flex: 1;
}
.main-page {
flex: 1;
background-color: #2c3e50;
/* 移除内边距并保持一致背景色 */
}
.menu {
width: 250px;
padding: 10px;
background-color: #34495e;
/* 更柔和的颜色 */
border-left: 1px solid #2980b9;
/* 添加左边框以区分 */
}
.menu ul {
list-style: none;
padding: 0;
margin: 0;
}
.menu li {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #2980b9;
/* 设置底部边框颜色 */
cursor: pointer;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
color: #bdc3c7;
/* 设置次要文字颜色 */
}
.menu li:last-child {
border-bottom: none;
}
.menu li:hover {
background-color: #3498db;
/* 蓝色鼠标悬停背景色 */
color: #ecf0f1;
/* 浅灰色文字颜色 */
}
.menu li.active {
background-color: #2ecc71;
/* 绿色激活状态背景色 */
color: #ecf0f1;
/* 浅灰色文字颜色 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
/* 设置选中状态下的阴影 */
}
.menu li .mdi {
font-size: 32px;
margin-right: 15px;
}
.menu li div {
font-size: 24px;
}
/* 顶部标题与功能菜单栏样式 */
.top-header {
background-color: #2980b9;
/* 明亮的蓝色 */
color: #ecf0f1;
/* 浅灰色文字颜色 */
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #2980b9;
/* 添加底部边框 */
}
.top-header h1 {
margin: 0;
font-size: 1.5em;
}
.top-header nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
.top-header nav ul li {
margin-left: 1rem;
}
.top-header nav ul li a {
color: #ecf0f1;
/* 浅灰色文字颜色 */
text-decoration: none;
font-weight: bold;
}
.top-header nav ul li a:hover {
text-decoration: underline;
}
</style>

View File

@@ -0,0 +1,128 @@
<template>
<div>
<button @click="connectSerial" :disabled="serialPort !== null">
连接串口
</button>
<button @click="disconnectSerial" :disabled="serialPort === null">
断开串口
</button>
<p v-if="serialPort !== null">已连接到: {{ serialPort.getInfo().usbVendorId }}:{{ serialPort.getInfo().usbProductId
}}</p>
<p v-else>未连接</p>
<p v-if="errorMessage">{{ errorMessage }}</p>
<pre>{{ receivedData }}</pre>
<pre>{{ code }}</pre>
</div>
</template>
<script>
import SCAN from "./scanCode" // from后面为创建的SCAN类文件地址
export default {
data() {
return {
serialPort: null,
reader: null,
writer: null,
errorMessage: '',
receivedData: '',
code: ''
};
},
async created() {
this.scan = new SCAN(this, 200)
// 重新注册scanCode事件
// this.$bus.$on('scanCode', this.handleCode)
this.scan.start() //开启监听
},
beforeDestroy() {
// this.$bus.$off('scanCode')
this.scan.stop()
},
activated() {
// 注销scanCode事件
// this.$bus.$off('scanCode')
// this.$bus.$on('scanCode', this.handleCode)
this.scan.start()
},
methods: {
async connectSerial() {
try {
this.serialPort = await navigator.serial.requestPort();
console.log('选择了串口:', this.serialPort);
// 检查浏览器是否支持所需的波特率
const options = { baudRate: 9600 };
await this.serialPort.open(options);
console.log('串口已打开');
// 设置读取器
this.reader = this.serialPort.readable.getReader();
this.readLoop();
} catch (error) {
console.error('无法打开串口:', error);
this.errorMessage = `无法打开串口: ${error.message}`;
this.serialPort = null
}
},
async readLoop() {
while (true) {
try {
const { value, done } = await this.reader.read();
if (done) {
console.log('读取完成');
break;
}
// 将接收到的字节数组转换为字符串
const decoder = new TextDecoder();
const decodedValue = decoder.decode(value, { stream: true });
this.receivedData += decodedValue;
} catch (error) {
console.error('读取数据时出错:', error);
break;
}
}
// 关闭读取器
this.reader.releaseLock();
this.reader = null;
},
disconnectSerial() {
if (this.reader) {
this.reader.cancel();
this.reader.releaseLock();
this.reader = null;
}
if (this.writer) {
this.writer.close();
this.writer = null;
}
if (this.serialPort) {
this.serialPort.close();
this.serialPort = null;
console.log('串口已关闭');
}
},
handleCode(code) {
code = code.trim()
if (!code) return
this.code = code
this.handleSearchCode()
},
},
};
</script>
<style scoped>
button {
margin: 5px;
}
pre {
background-color: #f4f4f4;
padding: 10px;
border-radius: 5px;
overflow-y: auto;
max-height: 200px;
}
</style>

View File

@@ -0,0 +1,78 @@
class SCAN {
constructor(vm, timeout = 100) {
this.barCode = ''
this.vm = vm
this.lastTime = 0
this.timeout = timeout
this.timer = null
this.event = this.eventListenerScanCode.bind(this)
}
//监听扫码枪
eventListenerScanCode(e) {
e = e || window.event
let currCode = e.keyCode || e.which || e.charCode
let currTime = new Date().getTime()
if (this.lastTime > 0) {
if (currTime - this.lastTime <= this.timeout) {
// 扫码枪有效输入间隔毫秒
this.barCode += String.fromCharCode(currCode)
} else if (currTime - this.lastTime > this.timeout) {
// 超过间隔时间,认为不是扫码枪输入内容,清空
this.ClearBarCode()
}
} else {
// 第一次按键
this.barCode = String.fromCharCode(currCode)
}
console.log(currTime, '监听扫监听到的值:', this.barCode)
this.lastTime = currTime
if (currCode == 13) {
if (this.barCode) {
console.log('扫码结果:' + this.barCode)
let code = this.barCode.substring(0, this.barCode.length - 1)
if (!code) return
this.vm && this.vm.$bus.$emit('scanCode', code)
}
this.ClearBarCode()
}
// 在没有按enter的情况下定时清空
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
this.timer = setTimeout(() => {
if (this.lastTime) {
this.ClearBarCode()
console.log('执行清空')
}
clearTimeout(this.timer)
this.timer = null
}, this.timeout)
}
ClearBarCode() {
this.barCode = ''
this.lastTime = 0
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
}
start() {
window.addEventListener('keypress', this.event)
}
stop() {
window.removeEventListener('keypress', this.event)
this.ClearBarCode()
}
}
export default SCAN

View File

@@ -0,0 +1,217 @@
<template>
<div class="container">
<el-row :gutter="10">
<el-col :span="8" v-for="(group, index) in groups" :key="index">
<el-card shadow="hover" class="box-card">
<div class="card-header">
{{ group.title }}
</div>
<div>
<el-row :gutter="20">
<el-col :span="3" v-for="(item, index) in 8" :key="index">
<div class="defect-item-box">
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="3" v-for="(item, index) in 8" :key="index">
<div class="defect-item-box">
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="3" v-for="(item, index) in 8" :key="index">
<div class="defect-item-box">
</div>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="workorder-card">
<div class="workorder-card-header">
<div>
<span>工单编号:</span>
<span>{{ formData.workOrder }}</span>
</div>
<div>
<span class="text-green">正常</span>
</div>
</div>
<div class="workorder-card-button-box">
<el-button type="primary">刷新数据</el-button>
<el-button type="success">开始工单</el-button>
</div>
<table class="workorder-card-table">
<tr>
<td>班组</td>
<td>
<el-select v-model="formData.team" placeholder="请选择班组">
<el-option v-for="item in teamOptions" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</td>
<td>站点</td>
<td>
<el-select v-model="formData.site" placeholder="请选择站点">
<el-option v-for="item in siteOptions" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</td>
</tr>
<tr>
<td>零件号</td>
<td>81212532GZAG</td>
<td>描述</td>
<td>GEN12鲨鱼鳍</td>
<!-- <td :colspan="3">GEN12鲨鱼鳍</td> -->
</tr>
<tr>
<td>规格</td>
<td>1</td>
<td>颜色</td>
<td>1</td>
</tr>
<tr>
<td>投入数</td>
<td>0</td>
<td>合格数</td>
<td>0</td>
</tr>
<tr>
<td>合格率</td>
<td>0%</td>
<td>抛光数</td>
<td>0</td>
</tr>
<tr>
<td>打磨数</td>
<td>0</td>
<td>报废数</td>
<td>0</td>
</tr>
<tr>
<td>开始时间</td>
<td>2025-01-02 17:46:10</td>
<td>结束时间</td>
<td>2025-01-02 17:46:10</td>
</tr>
</table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'WorkOrder',
data() {
return {
groups: [
{ title: '油漆', description: '检查设备A' },
{ title: '设备', description: '检查设备B' },
{ title: '毛坯', description: '检查设备C' },
{ title: '程序', description: '检查设备D' },
{ title: '班组操作', description: '检查设备E' }
],
teamOptions: [
{ label: 'A组', value: 'A组' },
{ label: 'B组', value: 'B组' },
{ label: 'C组', value: 'C组' },
],
siteOptions: [
{ label: '1站点', value: '1站点' },
{ label: '2站点', value: '2站点' },
{ label: '3站点', value: '3站点' },
],
formData: {
workOrder: 'W20250102001',
team: '',
site: ''
}
};
},
methods: {
init() {
// 初始化方法
}
}
};
</script>
<style scoped>
.box-card {
width: 100%;
background-color: #032169;
/* 深墨绿色背景 */
color: #ecf0f1;
/* 浅灰色文字颜色 */
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
/* 设置阴影 */
margin-top: 10px;
margin-bottom: 10px;
/* 增加卡片底部间距 */
}
.workorder-card {
min-height: 600px;
}
.card-header {
color: #ecf0f1;
padding-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 600;
}
.workorder-card-header {
width: 100%;
font-size: 24px;
font-weight: 700;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.workorder-card-table {
width: 100%;
font-size: 20px;
font-weight: 600;
border: 2px solid #000;
border-collapse: collapse;
}
.workorder-card-table td {
padding: 10px;
border: 2px solid #000;
}
.workorder-card-button-box {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.defect-item-box {
width: 100%;
min-height: 120px;
margin-top: 10px;
margin-bottom: 10px;
background-color: #016129;
}
</style>