xiao_003_更新电子秤开关闭串口

This commit is contained in:
doudou201703
2026-08-14 02:11:36 +08:00
parent 000e45648b
commit 298043d2fc
14 changed files with 217 additions and 166 deletions
-2
View File
@@ -5,8 +5,6 @@
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="wxLogin" level="application" />
<orderEntry type="library" name="wx-login" level="application" />
</component> </component>
<component name="TemplatesService"> <component name="TemplatesService">
<option name="TEMPLATE_FOLDERS"> <option name="TEMPLATE_FOLDERS">
BIN
View File
Binary file not shown.
+11 -15
View File
@@ -48,11 +48,12 @@ function createWindow(windowWidth: number = 1920, windowHeight: number = 997): v
x: 0, x: 0,
y: 0, y: 0,
autoHideMenuBar: true, autoHideMenuBar: true,
// accentColor: "rgb(254, 38, 53)", // alwaysOnTop: true, // app页面置顶,点击别的地方,不会隐藏;
// titleBarStyle: "hidden", // accentColor: "rgb(254, 38, 53)", //只有window系统适用
titleBarStyle: "default", //窗口标题栏的样式。默认是 默认的。可能的值有
// backgroundColor: "#2e2c29", //设置背景颜色 // backgroundColor: "#2e2c29", //设置背景颜色
// frame: false, // 这里可以去掉标题栏,Mac上的体现为 左上角的 x 没有了 // frame: false, // 这里可以去掉标题栏,Mac上的体现为 左上角的 x 没有了
backgroundMaterial: "auto", //设置窗口的系统绘制背景材质,包括非客户区的背景。 backgroundMaterial: "auto", //win32只适用于 window 系统 设置窗口的系统绘制背景材质,包括非客户区的背景。
title: "江苏六果科技有限公司", //默认窗口标题。默认值为:"Electron"。 如果由loadURL()加载的HTML文件中含有标签<title>,此属性将被忽略。 title: "江苏六果科技有限公司", //默认窗口标题。默认值为:"Electron"。 如果由loadURL()加载的HTML文件中含有标签<title>,此属性将被忽略。
...(process.platform === "linux" ? { icon } : {}), ...(process.platform === "linux" ? { icon } : {}),
webPreferences: { webPreferences: {
@@ -61,8 +62,6 @@ function createWindow(windowWidth: number = 1920, windowHeight: number = 997): v
webSecurity: true webSecurity: true
} }
}); });
// initScanner();
OninitScannerSerialPort监听电子秤(mainWindow);
// 可以打开,开发者工具 // 可以打开,开发者工具
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
// 关键:修改 CSP 允许连接 six.sixgou.com // 关键:修改 CSP 允许连接 six.sixgou.com
@@ -74,6 +73,7 @@ function createWindow(windowWidth: number = 1920, windowHeight: number = 997): v
shell.openExternal(details.url).then(); shell.openExternal(details.url).then();
return { action: "deny" }; return { action: "deny" };
}); });
// 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"]) {
@@ -85,13 +85,10 @@ function createWindow(windowWidth: number = 1920, windowHeight: number = 997): v
console.log("打包后的状态错误回调", reason); console.log("打包后的状态错误回调", reason);
}); });
} }
OninitScannerSerialPort监听电子秤(mainWindow).then(() => {
});
} }
// 当电子完成后,该方法将被调用
// 初始化,准备创建浏览器窗口。
// 有些 API 只能在该事件发生后使用。
// 初始化完成后的生命周期
app.whenReady().then(() => { app.whenReady().then(() => {
// Set app user model id for windows // Set app user model id for windows
electronApp.setAppUserModelId("com.electron"); electronApp.setAppUserModelId("com.electron");
@@ -107,7 +104,6 @@ app.whenReady().then(() => {
app.on("browser-window-created", (_, window) => { app.on("browser-window-created", (_, window) => {
optimizer.watchWindowShortcuts(window); optimizer.watchWindowShortcuts(window);
}); });
// IPC test // IPC test
ipcMain.on("ping", (_, args: string) => { ipcMain.on("ping", (_, args: string) => {
console.log("单向的通信成交了啦", args); console.log("单向的通信成交了啦", args);
@@ -122,6 +118,7 @@ app.whenReady().then(() => {
return "双向通信打印了啦"; return "双向通信打印了啦";
}); });
createWindow(windowWidth, windowHeight); createWindow(windowWidth, windowHeight);
// 这个activate事件,只有苹果电脑才会有的监听事件, 相当于应有被激活的作用,
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.
@@ -130,15 +127,14 @@ app.whenReady().then(() => {
// ✅ 注册 myapp 协议 // ✅ 注册 myapp 协议
}); });
//当所有窗口关闭时退出,macOS除外。在那里,这很常见
// 应用程序及其菜单栏保持活跃,直到用户退出
//明确表示 Cmd + Q。 //明确表示 Cmd + Q。
app.on("window-all-closed", () => { app.on("window-all-closed", async () => {
console.log("窗口关闭啥时候调用的呀");
if (process.platform !== "darwin") { if (process.platform !== "darwin") {
app.quit(); app.quit();
} }
// app.quit(); // ✅ macOS / Windows 都退出
}); });
// 在应用入口处注册退出事件 // 在应用入口处注册退出事件
// OninitScannerSerialPort监听电子秤(mainWindow);
+92 -27
View File
@@ -1,43 +1,108 @@
import { DelimiterParser, SerialPort } from "serialport"; import { DelimiterParser, SerialPort } from "serialport";
import { BrowserWindow } from "electron"; import { app, BrowserWindow } from "electron";
interface PortInfo {
path: string;
manufacturer?: string;
serialNumber?: string;
pnpId?: string;
locationId?: string;
productId?: string;
vendorId?: string;
}
// 全局保存串口引用,方便退出时清理
let scalePortInstance: SerialPort | null = null;
export const OninitScannerSerialPort监听电子秤 = async (mainWindow: BrowserWindow): Promise<void> => {
try {
// 1. 获取端口列表
const portsList: PortInfo[] = await SerialPort.list();
const scalePort = portsList.find((p) => {
return p.vendorId === "0403" && p.productId === "6001";
});
export function OninitScannerSerialPort监听电子秤(mainWindow: BrowserWindow) {
SerialPort.list().then((ports) => {
console.log(ports, typeof ports);
const scalePort = ports.find(p => {
return p.vendorId === "0403" && p.productId === "6001";
}
);
if (!scalePort) { if (!scalePort) {
console.error("❌ 未找到电子秤串口"); console.warn("⚠️ 未找到电子秤串口(VID=0403 PID=6001)");
// 可选:通知渲染进程
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("电子秤状态", { error: "未找到电子秤串口" });
}
return; return;
} }
console.log("✅ 找到电子秤串口:", scalePort.path); console.log(`✅ 找到电子秤串口: ${scalePort.path}`);
// 2. 创建串口实例,使用 autoOpen: false 手动控制打开时机
const port = new SerialPort({ const port = new SerialPort({
// path: "/dev/tty.usbserial-A5069RR4", path: scalePort.path,
path: scalePort.path, // ✅ 动态获取,自动适配各平台 baudRate: 9600,
baudRate: 9600 autoOpen: false, // 重要:先不自动打开
}, (res) => { // 可选的超时和重试策略
console.log(res, "为null时,说明没有报错"); lock: true // 默认就是 true,保持独占
}); });
// 以 0x03 0x04 作为帧分隔符 // 3. 打开串口(Promise 化)
const parser = port.pipe(new DelimiterParser({ delimiter: [0x03, 0x04] })); await new Promise<void>((resolve, reject) => {
// const parser = port.pipe(new DelimiterParser({ delimiter: [0x60] })); port.open(err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
parser.on("data", (frame) => { console.log("✅ 串口已打开");
const text = frame.toString("ascii").trim();
// 提取重量数值 // 4. 设置解析器(必须在打开成功后)
const match = text.match(/[+-]?\d+\.\d+/); const parser = port.pipe(new DelimiterParser({ delimiter: [0x03, 0x04] }));
if (match) {
mainWindow.webContents.send("电子秤", parseFloat(match[0])); parser.on("data", (frame: Buffer) => {
try {
const text = frame.toString("ascii").trim();
const match = text.match(/[+-]?\d+\.\d+/);
if (match && mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("电子秤", parseFloat(match[0]));
}
} catch (err) {
console.error("❌ 处理串口数据时出错:", err);
} }
}); });
parser.on("error", (err) => { parser.on("error", (err: Error) => {
console.error("❌ 串口错误:", err.message); console.error("❌ 串口解析错误:", err.message);
}); });
}); // 5. 保存引用,用于退出时清理
scalePortInstance = port;
// 6. 注册应用退出时的清理逻辑(只注册一次)
if (!app.isReady()) {
app.once("will-quit", cleanupSerialPort);
} else {
// 如果 app 已经 ready,直接监听
mainWindow.on("close", cleanupSerialPort);
app.on("will-quit", cleanupSerialPort);
}
} catch (e) {
console.error("❌ 初始化电子秤串口失败:", e);
// 通知渲染进程
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("电子秤状态", { error: String(e) });
}
}
};
function cleanupSerialPort() {
if (scalePortInstance && scalePortInstance.isOpen) {
scalePortInstance.close(err => {
if (err) {
console.error("❌ 关闭串口失败:", err.message);
} else {
console.log("❌ 串口已关闭");
}
});
}
} }
-1
View File
@@ -12,7 +12,6 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElResult: typeof import('element-plus/es')['ElResult'] ElResult: typeof import('element-plus/es')['ElResult']
+2 -1
View File
@@ -2,7 +2,7 @@
<html class="dark"> <html class="dark">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Limited Company-管理端</title> <!-- <title>Limited Company-管理端</title>-->
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP--> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP-->
<!-- 重点: 这里配置了请求头,可以解决csp,也能解决跨域的问题 --> <!-- 重点: 这里配置了请求头,可以解决csp,也能解决跨域的问题 -->
<meta <meta
@@ -20,6 +20,7 @@
<link href="https://hudie.xiao.band/static/wx-login.css"> <link href="https://hudie.xiao.band/static/wx-login.css">
<script src="https://at.alicdn.com/t/c/font_5198345_yvza0orv9pd.js"></script> <script src="https://at.alicdn.com/t/c/font_5198345_yvza0orv9pd.js"></script>
<script src="https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script> <script src="https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>
<title></title>
</head> </head>
<body> <body>
-10
View File
@@ -9,15 +9,6 @@
onMounted(() => { onMounted(() => {
// router_path.push({ name: "登陆页" }); // router_path.push({ name: "登陆页" });
router_path.replace({ name: "学习页面" }); router_path.replace({ name: "学习页面" });
// instance.post<string>("https://six.sixgou.com/demo/xiaozizi/", {},
// { headers: { "Authorization": xiaoMain.weChatUserInfo?.token } }
// ).then((data) => {
// console.log(data);
// router_path.replace({ name: "学习页面" });
// }).catch((error) => {
// console.log("错误被捕获了", error);
// router_path.replace({ name: "登陆页" });
// });
}); });
</script> </script>
@@ -35,7 +26,6 @@
<style lang="scss"> <style lang="scss">
#app { #app {
height: 100vh; height: 100vh;
border: #a855f7 1px dashed;
.传送门 { .传送门 {
position: absolute; position: absolute;

Before

Width:  |  Height:  |  Size: 564 KiB

After

Width:  |  Height:  |  Size: 564 KiB

@@ -51,7 +51,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue"; import { onMounted } from "vue";
import AdvanceAndRetreat from "@renderer/通用组件/浮动路由组件.vue"; import AdvanceAndRetreat from "@renderer/通用组件/浮动路由组件.vue";
import Sidebar from "@renderer/页面目录/主页面/主页面组件/左侧导航菜单.vue"; import Sidebar from "@renderer/页面目录/主页面/主页面组件/左侧导航菜单.vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
@@ -63,13 +63,5 @@
router.push({ name: "商品统计页面" }); router.push({ name: "商品统计页面" });
}); });
// 组件销毁时清理监听器,防止内存泄漏
onBeforeUnmount(() => {
window.electron.ipcRenderer.removeAllListeners("扫码");
});
let goods_card_list = ref<{ codeid: string, goods_number: number }[]>([]);
</script> </script>
@@ -1,20 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ name: "Zhong" }); import { useVModels } from "@vueuse/core";
let root_url = "https://open.weixin.qq.com/connect/qrconnect?";
let response_type = "response_type=code";
let appid = "appid=wx6c9d4541e0285d02";
let scope = "scope=snsapi_login";
const encoded = encodeURIComponent("https://six.sixgou.com/web/auth/get_code/");
let redirect_uri = "redirect_uri=" + encoded;
let url = `${root_url}&${appid}&${redirect_uri}&${response_type}&${scope}&state=STATE#wechat_redirect`; defineOptions({ name: "xiao-zizi-com" });
async function 测试跨域(): Promise<void> { async function 测试跨域(): Promise<void> {
// let data = await instance.post("https://six.sixgou.com/demo/xiaozizi/", {}); console.log("------------");
console.log(url); modelValue.value = modelValue.value + 200;
// let data = await instance.get(url, {});
window.location.href = url;
} }
interface definePropsType {
modelValue: number;// 对象类型的 prop
}
// 2. 使用 withDefaults 定义默认值
const props = withDefaults(defineProps<definePropsType>(), {});
const emit = defineEmits<{
(eventName: "update:modelValue", value: string): void
}>();
const { modelValue } = useVModels(props, emit);
</script> </script>
<template> <template>
@@ -22,6 +26,7 @@
<div> <div>
<button class="毛玻璃按钮" @click="测试跨域">测试跨域</button> <button class="毛玻璃按钮" @click="测试跨域">测试跨域</button>
</div> </div>
<div>{{ modelValue }}</div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -3,6 +3,8 @@
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { instance } from "@renderer/utils/request"; import { instance } from "@renderer/utils/request";
import { watchThrottled } from "@vueuse/core"; import { watchThrottled } from "@vueuse/core";
import XiaoZiziCom from "@renderer/页面目录/学习页面/子组件.vue";
const 电子秤重量 = ref(); const 电子秤重量 = ref();
watchThrottled(电子秤重量, (value, oldValue, onCleanup) => { watchThrottled(电子秤重量, (value, oldValue, onCleanup) => {
@@ -30,6 +32,7 @@
const 发起请求 = async (): Promise<void> => { const 发起请求 = async (): Promise<void> => {
await instance.post("/demo/中文/", {}); await instance.post("/demo/中文/", {});
}; };
const zizi_modelValue = ref(100);
</script> </script>
<template> <template>
@@ -50,6 +53,9 @@
<div> <div>
电子秤重量{{ 电子秤重量 }} 电子秤重量{{ 电子秤重量 }}
</div> </div>
<div>
<xiao-zizi-com v-model="zizi_modelValue"></xiao-zizi-com>
</div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -4,7 +4,7 @@
<!-- 左侧图片区 --> <!-- 左侧图片区 -->
<div class="左侧图片区域"> <div class="左侧图片区域">
<div class="图片内容"> <div class="图片内容">
<img src="../../assets/logo.png" alt="登录插图" class="插画" /> <img src="https://hudie.sixgou.com/icon/six.png" alt="登录插图" class="插画" />
<div class="图片文字"> <div class="图片文字">
<h2>高效管理</h2> <h2>高效管理</h2>
<p>让工作更简单,让协作更顺畅</p> <p>让工作更简单,让协作更顺畅</p>
@@ -52,7 +52,6 @@
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px; padding: 20px;
border: red 1px dashed;
box-sizing: border-box; box-sizing: border-box;
.登录卡片包裹层 { .登录卡片包裹层 {
@@ -1,65 +1,83 @@
<style lang="scss" scoped> <style lang="scss" scoped>
.登录卡片 { .登录卡片 {
width: 440px; width: 440px;
padding: 44px 42px 50px; padding: 44px 42px 50px;
background: #ffffff; background: #ffffff;
.登录头部 { .登录头部 {
text-align: center; text-align: center;
margin-bottom: 34px; margin-bottom: 34px;
.公司图标 { .公司图标 {
width: 72px; width: 72px;
height: 72px; height: 72px;
border-radius: 16px; border-radius: 16px;
margin-bottom: 16px; margin-bottom: 16px;
} }
.公司名称 { .公司名称 {
font-size: 26px; font-size: 26px;
font-weight: 800; font-weight: 800;
color: #1a1a2e; color: #1a1a2e;
margin-bottom: 10px; margin-bottom: 10px;
} }
.欢迎文字 { .欢迎文字 {
font-size: 14px; font-size: 14px;
color: #8b8fa3; color: #8b8fa3;
}
}
.微信登录区域 {
text-align: center;
.二维码外壳 {
position: relative;
padding: 24px;
background: #f7fafc;
border-radius: 16px;
border: 1px solid #e2e8f0;
.扫码提示 {
margin-top: 24px;
font-size: 13px;
color: #718096;
} }
} }
.刷新提示 { .微信登录区域 {
margin-top: 16px; text-align: center;
font-size: 12px;
color: #a0aec0;
display: flex;
justify-content: center;
gap: 12px;
align-items: center;
.刷新按钮 { .二维码外壳 {
position: relative;
padding: 24px;
background: #f7fafc;
border-radius: 16px;
border: 1px solid #e2e8f0;
.扫码提示 {
margin-top: 24px;
font-size: 13px;
color: #718096;
}
}
.刷新提示 {
margin-top: 16px;
font-size: 12px;
color: #a0aec0;
display: flex;
justify-content: center;
gap: 12px;
align-items: center;
.刷新按钮 {
background: none;
border: none;
color: #667eea;
cursor: pointer;
font-size: 12px;
&:hover {
text-decoration: underline;
}
}
}
}
.底部操作区 {
margin-top: 30px;
text-align: center;
.备用登录方式 {
background: none; background: none;
border: none; border: none;
color: #667eea; color: #667eea;
font-size: 14px;
cursor: pointer; cursor: pointer;
font-size: 12px;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
@@ -68,30 +86,12 @@
} }
} }
.底部操作区 {
margin-top: 30px;
text-align: center;
.备用登录方式 {
background: none;
border: none;
color: #667eea;
font-size: 14px;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
}
</style> </style>
<template> <template>
<div class="登录卡片"> <div class="登录卡片">
<div class="登录头部"> <div class="登录头部">
<img src="../../assets/logo.png" alt="公司Logo" class="公司图标" /> <img src="https://hudie.sixgou.com/icon/six.png" alt="公司Logo" class="公司图标" />
<h1 class="公司名称">Limited Company</h1> <h1 class="公司名称">Limited Company</h1>
<p class="欢迎文字">请使用微信扫码登录</p> <p class="欢迎文字">请使用微信扫码登录</p>
</div> </div>
@@ -115,26 +115,26 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import WxLoginCode from "./登陆组件.vue"; import WxLoginCode from "./登陆组件.vue";
defineOptions({ name: "WeChatLoginComponent" }); defineOptions({ name: "WeChatLoginComponent" });
const 刷新按钮 = async (): Promise<void> => { const 刷新按钮 = async (): Promise<void> => {
window.location.reload(); window.location.reload();
}; };
const 使用账号密码登录 = async (): Promise<void> => { const 使用账号密码登录 = async (): Promise<void> => {
console.log("============="); console.log("=============");
emit("update:修改为表单", "表单"); emit("update:修改为表单", "表单");
}; };
const emit = defineEmits<{ const emit = defineEmits<{
/*update:modelValue 用法: /*update:modelValue 用法:
* 在父组件中绑定 v-model=父组件变量 * 在父组件中绑定 v-model=父组件变量
* 2:在子组件中直接调用 emit("update:modelValue",值) * 2:在子组件中直接调用 emit("update:modelValue",值)
* 3:重点,此时传递给父组件的值,会自动更新 v-model绑定的父组件的 变量的值; * 3:重点,此时传递给父组件的值,会自动更新 v-model绑定的父组件的 变量的值;
* 示例: <Child v-model:zizi="变量" /> * 示例: <Child v-model:zizi="变量" />
* 4:update:zizi 是如何实现的 在父组件中传递 v-model:zizi = xxx;*/ * 4:update:zizi 是如何实现的 在父组件中传递 v-model:zizi = xxx;*/
(eventName: "update:修改为表单", value: "表单"): void (eventName: "update:修改为表单", value: "表单"): void
}>(); }>();
</script> </script>
@@ -1,7 +1,7 @@
<template> <template>
<div class="login-card"> <div class="login-card">
<div class="login-header"> <div class="login-header">
<img src="../../assets/logo.png" alt="六果科技" class="logo" /> <img src="https://hudie.sixgou.com/icon/six.png" alt="六果科技" class="logo" />
<h1 class="company-name">Limited Company</h1> <h1 class="company-name">Limited Company</h1>
<p class="welcome-text">欢迎回来,请登录您的账号</p> <p class="welcome-text">欢迎回来,请登录您的账号</p>
</div> </div>