加折线图前

This commit is contained in:
Smile-Xin 2025-01-10 16:15:40 +08:00
parent f437f7dc77
commit fbdce2a6fb
10 changed files with 287 additions and 110 deletions

24
package-lock.json generated
View File

@ -12,7 +12,8 @@
"@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0", "@electron-toolkit/utils": "^3.0.0",
"element-plus": "^2.9.2", "element-plus": "^2.9.2",
"modbus-serial": "^8.0.17" "modbus-serial": "^8.0.17",
"vue-router": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2", "@electron-toolkit/eslint-config": "^1.0.2",
@ -2296,6 +2297,12 @@
"@vue/shared": "3.5.13" "@vue/shared": "3.5.13"
} }
}, },
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/eslint-config-prettier": { "node_modules/@vue/eslint-config-prettier": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmmirror.com/@vue/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", "resolved": "https://registry.npmmirror.com/@vue/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
@ -7093,6 +7100,21 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/vue-router": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz",
"integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",

View File

@ -21,7 +21,8 @@
"@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0", "@electron-toolkit/utils": "^3.0.0",
"element-plus": "^2.9.2", "element-plus": "^2.9.2",
"modbus-serial": "^8.0.17" "modbus-serial": "^8.0.17",
"vue-router": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2", "@electron-toolkit/eslint-config": "^1.0.2",

View File

@ -3,39 +3,103 @@ import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset' import icon from '../../resources/icon.png?asset'
// 模拟配置的modbus配置单
const modbusConfig = {
name: [
'风速',
'风向',
'温度',
'湿度',
'气压',
'光照',
'雨量',
'PM2.5',
'PM10',
'噪音',
], // 名称
}
var ModbusRTU = require('modbus-serial') var ModbusRTU = require('modbus-serial')
var client = new ModbusRTU() var client = new ModbusRTU()
var time // 寄存器循环
var time = null
// 主窗口
var mainWindow
// 登录窗口
var loginWindow
// 连接后读取modbus从机的连接
var readClient
// IPC test // 读取寄存器
ipcMain.on('read', (event, message) => { ipcMain.on('read', (event, message) => {
read(event, message) read(event, message)
console.log('pong') console.log('pong')
}) })
// 通过功能码03读取寄存器 // 登录后开启modbus连接
function read(event, message) { ipcMain.on('login', (event, message) => {
// login(event, message)
mainWindow.webContents.send('loginReply', 1)
loginWindow.close()
})
// 登录
// function login(event, message) {
// // 登录业务代码
// console.log('login', message, event)
// }
// 接收渲染进程发送的link请求
ipcMain.on('link', (event, message) => {
link(event, message)
})
// 连接modbus
function link(event, message) {
// 连接modbus
client.connectTCP(message.ip, { port: 502 }) client.connectTCP(message.ip, { port: 502 })
// client.connectTCP('192.168.151.59', { port: 502 }) // 连接成功后读取寄存器方法
console.log(message.ip) readClient = (event, message) => {
// 循环读取寄存器
time = setInterval(() => {
client.readHoldingRegisters(message.startAddress, message.quantity, function (err, data) { client.readHoldingRegisters(message.startAddress, message.quantity, function (err, data) {
if (err) { if (err) {
event.reply('readReply', err) event.reply('readReply', err)
clearInterval(time)
return return
} }
// 返回数据给渲染进程 event.reply('readReply', dataHandle(data.data))
event.reply('readReply', data.data)
}) })
}, 1000) }
read(event, message)
}
// 通过功能码03读取寄存器
function read(event, message) {
if (!(time == null)) {
console.log('重复读取数据', time)
return
}
// 循环读取寄存器
time = setInterval(() => {
readClient(event, message)
}, 500)
}
// 数据处理
function dataHandle(data) {
let result = []
for (let i = 0; i < modbusConfig.name.length; i++) {
result[i] = {
name: modbusConfig.name[i],
data: data[i]
}
}
return result
} }
// 停止modbus读取 // 停止modbus读取
ipcMain.on('stop', (event) => { ipcMain.on('stop', (event) => {
console.log('stop')
clearInterval(time) clearInterval(time)
time = null
console.log('stop')
// 返回数据给渲染进程 // 返回数据给渲染进程
event.reply('readReply', []) event.reply('readReply', [])
}) })
@ -43,7 +107,7 @@ ipcMain.on('stop', (event) => {
// 创建主窗口 // 创建主窗口
function createWindow() { function createWindow() {
// Create the browser window. // Create the browser window.
const mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 900, width: 900,
height: 670, height: 670,
show: false, show: false,
@ -54,7 +118,6 @@ function createWindow() {
sandbox: false sandbox: false
} }
}) })
mainWindow.on('ready-to-show', () => { mainWindow.on('ready-to-show', () => {
mainWindow.show() mainWindow.show()
}) })
@ -75,9 +138,10 @@ function createWindow() {
// 创建子窗口 // 创建子窗口
function createChildWindow() { function createChildWindow() {
let childWindow = new BrowserWindow({ loginWindow = new BrowserWindow({
width: 900, parent: mainWindow,
height: 670, width: 500,
height: 400,
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
@ -87,11 +151,12 @@ function createChildWindow() {
} }
}) })
childWindow.on('ready-to-show', () => { loginWindow.on('ready-to-show', () => {
childWindow.show() loginWindow.show()
}) })
childWindow.webContents.setWindowOpenHandler((details) => { loginWindow.webContents.setWindowOpenHandler((details) => {
// console.log('details.url', details.url)
shell.openExternal(details.url) shell.openExternal(details.url)
return { action: 'deny' } return { action: 'deny' }
}) })
@ -99,9 +164,10 @@ function createChildWindow() {
// HMR for renderer base on electron-vite cli. // HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production. // Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) { if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
childWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) // console.log(process.env['ELECTRON_RENDERER_URL'] + '/#/login')
loginWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/#/login')
} else { } else {
childWindow.loadFile(join(__dirname, '../renderer/login.html')) // loginWindow.loadURL(join(__dirname, '../renderer/login.html'))
} }
} }
@ -120,7 +186,7 @@ app.whenReady().then(() => {
}) })
createWindow() createWindow()
createChildWindow()
app.on('activate', function () { app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the // On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.

View File

@ -1,72 +1,11 @@
<script setup> <script setup>
import { reactive, ref, toRaw } from 'vue'
let modbusData = ref([])
let parameters = reactive({
ip: '192.168.151.59',
slaveId: 1,
startAddress: 600,
quantity: 10,
// unitId: 1,
functionCode: 3,
})
const ipcHandle = () => window.electron.ipcRenderer.send('read', toRaw(parameters))
const ipcModbus = () => window.electron.ipcRenderer.send('stop')
window.electron.ipcRenderer.on('readReply', (event, message) => {
modbusData.value = message
})
// const handleClick = (tab, event) => {
// console.log(tab, event)
// }
</script> </script>
<template> <template>
<!-- <img alt="logo" class="logo" src="./assets/electron.svg" /> --> <router-view />
<!-- <div class="creator">Powered by electron-vite</div> -->
<!-- <div class="text">
Build an Electron app with
<span class="vue">Vue</span>
</div> -->
<!-- <div class="tabs">
<el-tabs v-model="activeName" type="border-card" @tab-click="handleClick">
<el-tab-pane label="Tab 1" name="first">Tab 1</el-tab-pane>
<el-tab-pane label="Tab 2" name="second">Tab 2</el-tab-pane>
<el-tab-pane label="Tab 3" name="third">Tab 3</el-tab-pane>
</el-tabs>
</div> -->
<p class="tip">modbus测试</p>
<div class="fromDiv">
<p>ip地址</p> <input type="text" v-model="parameters.ip" /> <br />
<p>id</p> <input type="text" v-model="parameters.slaveId" /> <br />
<p>起始地址</p> <input type="text" v-model="parameters.startAddress" /> <br />
<p>读取长度</p> <input type="text" v-model="parameters.quantity" /> <br />
<!-- <p></p> <input type="text" v-model="parameters.unitId" /> -->
<p>功能码</p> <input type="text" v-model="parameters.functionCode" />
</div>
<div class="actions">
<div class="action">
<a target="_blank" rel="noreferrer" @click="ipcHandle">read</a>
<a target="_blank" rel="noreferrer" @click="ipcModbus">stop</a>
</div>
</div>
<p>{{ modbusData }}</p>
</template> </template>
<style scoped> <style scoped>
.tabs {
position: absolute;
top: 0;
left: 0;
}
.fromDiv > p {
display: inline-block;
width: 100px;
}
.fromDiv > input {
/* width: 200px; */
}
</style> </style>

View File

@ -0,0 +1,20 @@
<template>
<div class="topBar">
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
<el-menu-item index="1">modbus测试</el-menu-item>
<el-menu-item index="2">可视化图表</el-menu-item>
</el-menu>
</div>
</template>
<script setup>
import { ref } from 'vue'
const activeIndex = ref('1')
const handleSelect = (key, heyPath) => {
console.log(key, heyPath)
}
</script>
<style scoped>
</style>

View File

@ -1,21 +0,0 @@
<template>
<div>
<el-form>
<el-form-item label="IP">
<el-input v-model="param.ip"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input v-model="param.password" type="password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
</script>
<style scoped></style>

View File

@ -5,7 +5,9 @@ import App from './App.vue'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import router from './route'
const app = createApp(App) const app = createApp(App)
app.use(router)
app.use(ElementPlus) app.use(ElementPlus)
app.mount('#app') app.mount('#app')

View File

@ -0,0 +1,14 @@
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{ path: '/', component: () => import('../view/index.vue') },
{ path: '/login', component: () => import('../view/login.vue') }
]
const router = createRouter({
mode: 'hash',
history: createWebHashHistory(),
routes
})
export default router

View File

@ -0,0 +1,76 @@
<template>
<TopBar />
<div>
<p class="tip">modbus测试</p>
<el-table :data="modbusData" style="width: 100%">
<el-table-column prop="name" label="变量名"></el-table-column>
<el-table-column prop="data" label="数值"></el-table-column>
</el-table>
<div class="actions">
<div class="action">
<a target="_blank" rel="noreferrer" @click="ipcHandle">继续</a>
<a target="_blank" rel="noreferrer" @click="ipcModbus">停止</a>
</div>
</div>
<!-- <p>{{ modbusData }}</p> -->
</div>
</template>
<script setup>
import { reactive, toRaw } from 'vue'
import TopBar from '../component/topBar.vue'
let modbusData = reactive([])
let parameters = reactive({
ip: '192.168.151.59',
slaveId: 1,
startAddress: 600,
quantity: 10,
// unitId: 1,
functionCode: 3,
})
const ipcHandle = () => window.electron.ipcRenderer.send('read', toRaw(parameters))
const ipcModbus = () => window.electron.ipcRenderer.send('stop')
//
window.electron.ipcRenderer.on('loginReply', (event, message) => {
if (message) {
// modbus
window.electron.ipcRenderer.send('link', toRaw(parameters))
} else {
console.log('连接失败')
}
})
// modbus
window.electron.ipcRenderer.on('readReply', (event, message) => {
for (let i = 0; i < message.length; i++) {
modbusData[i] = {
name: message[i].name,
data: message[i].data
}
}
})
// const handleClick = (tab, event) => {
// console.log(tab, event)
// }
</script>
<style scoped>
.tabs {
position: absolute;
top: 0;
left: 0;
}
.fromDiv>p {
display: inline-block;
width: 100px;
}
.fromDiv>input {
/* width: 200px; */
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<div>
<div class="fromDiv">
<div class="fromLable">
<p>ip地址</p> <input v-model="parameters.ip" type="text" /> <br />
</div>
<div class="fromLable">
<button class="linkBtn" @click="login">连接</button>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, toRaw } from 'vue'
let parameters = reactive({
ip: '192.168.151.59'
})
function login() {
window.electron.ipcRenderer.send('login', toRaw(parameters))
}
</script>
<style scoped>
.fromLable {
margin-top: 20%;
}
p {
display: inline-block;
/* width: 100px; */
}
.fromDiv {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
}
.linkBtn {
width: 100px;
height: 40px;
background-color: #1c2834;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
display: inline-block;
text-align: center;
line-height: 40px;
font-size: 16px;
transition: 3s;
}
</style>