Browse Source

feat: 调整配置,实现可以打包

master
WIN-87ES3P38OPV\EDY 21 hours ago
parent
commit
ca8938718f
  1. 1
      .gitignore
  2. 5
      .npmrc
  3. 3
      .vs/ProjectSettings.json
  4. 7
      .vs/VSWorkspaceState.json
  5. BIN
      .vs/pc.slnx/FileContentIndex/d3fd8aeb-ed16-44cf-8d07-50a8e0006342.vsidx
  6. BIN
      .vs/pc.slnx/v18/.wsuo
  7. 12
      .vs/pc.slnx/v18/DocumentLayout.json
  8. BIN
      .vs/pc/CopilotIndices/18.8.1096.64007/CodeChunks.db
  9. BIN
      .vs/pc/CopilotIndices/18.8.1096.64007/SemanticSymbols.db
  10. BIN
      .vs/slnx.sqlite
  11. 4
      README.md
  12. 101
      forge.config.js
  13. 282
      main.js
  14. 11967
      package-lock.json
  15. 13
      package.json
  16. 199
      preload.js

1
.gitignore vendored

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
/node_modules
/dist
/.vscode
out/

5
.npmrc

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
registry=https://registry.npmmirror.com
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_custom_dir={{ version }}
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/

3
.vs/ProjectSettings.json

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}

7
.vs/VSWorkspaceState.json

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
{
"ExpandedNodes": [
""
],
"SelectedNode": "\\package.json",
"PreviewInSolutionExplorer": false
}

BIN
.vs/pc.slnx/FileContentIndex/d3fd8aeb-ed16-44cf-8d07-50a8e0006342.vsidx

Binary file not shown.

BIN
.vs/pc.slnx/v18/.wsuo

Binary file not shown.

12
.vs/pc.slnx/v18/DocumentLayout.json

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\company\\bestway\\code\\\u5149\u4F0F\\pc\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": []
}
]
}

BIN
.vs/pc/CopilotIndices/18.8.1096.64007/CodeChunks.db

Binary file not shown.

BIN
.vs/pc/CopilotIndices/18.8.1096.64007/SemanticSymbols.db

Binary file not shown.

BIN
.vs/slnx.sqlite

Binary file not shown.

4
README.md

@ -2,10 +2,10 @@ @@ -2,10 +2,10 @@
* @Author: 李皓 hao_li_work@163.com
* @Date: 2026-07-28 15:15:43
* @LastEditors: 李皓 hao_li_work@163.com
* @LastEditTime: 2026-07-28 16:09:35
* @LastEditTime: 2026-07-29 15:55:02
* @FilePath: \pc\README.md
* @Description:
* @Version: 1.0.0
-->
node -v
24.18.0
16.14.2

101
forge.config.js

@ -0,0 +1,101 @@ @@ -0,0 +1,101 @@
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { MakerSquirrel } = require('@electron-forge/maker-squirrel');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
const { convertVersion, createWindowsInstaller } = require('electron-winstaller');
const fs = require('fs-extra');
const os = require('node:os');
const path = require('node:path');
class AsciiOutputSquirrelMaker extends MakerSquirrel {
async make({ dir, makeDir, targetArch, packageJSON, appName, forgeConfig }) {
const finalOutPath = path.resolve(makeDir, `squirrel.windows/${targetArch}`);
await this.ensureDirectory(finalOutPath);
const tmpAppDir = await fs.mkdtemp(path.join(os.tmpdir(), 'squirrel-maker-app-'));
const tmpOutPath = await fs.mkdtemp(path.join(os.tmpdir(), 'squirrel-maker-out-'));
await fs.copy(dir, tmpAppDir);
try {
const winstallerConfig = {
name: typeof packageJSON.name === 'string'
? packageJSON.name.replace(/-/g, '_')
: undefined,
title: appName,
noMsi: true,
exe: `${forgeConfig.packagerConfig.executableName || appName}.exe`,
setupExe: `${appName}-${packageJSON.version} Setup.exe`,
...this.config,
appDirectory: tmpAppDir,
outputDirectory: tmpOutPath,
};
await createWindowsInstaller(winstallerConfig);
await fs.copy(tmpOutPath, finalOutPath, { overwrite: true });
const nupkgVersion = convertVersion(packageJSON.version);
const artifacts = [
path.resolve(finalOutPath, 'RELEASES'),
path.resolve(finalOutPath, winstallerConfig.setupExe || `${appName}Setup.exe`),
path.resolve(finalOutPath, `${winstallerConfig.name}-${nupkgVersion}-full.nupkg`),
];
const deltaPath = path.resolve(finalOutPath, `${winstallerConfig.name}-${nupkgVersion}-delta.nupkg`);
if (
winstallerConfig.remoteReleases &&
!winstallerConfig.noDelta &&
await fs.pathExists(deltaPath)
) {
artifacts.push(deltaPath);
}
const msiPath = path.resolve(finalOutPath, winstallerConfig.setupMsi || `${appName}Setup.msi`);
if (!winstallerConfig.noMsi && await fs.pathExists(msiPath)) {
artifacts.push(msiPath);
}
return artifacts;
} finally {
await fs.remove(tmpAppDir);
await fs.remove(tmpOutPath);
}
}
}
module.exports = {
packagerConfig: {
asar: true,
},
rebuildConfig: {
onlyModules: [],
},
makers: [
new AsciiOutputSquirrelMaker(),
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};

282
main.js

@ -10,45 +10,166 @@ @@ -10,45 +10,166 @@
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
const { SerialPort } = require('serialport')
const { WitMotionSensor } = require('./sensor')
const { UsbSerialBridge } = require('./usb-serial-bridge')
const pkg = require('./package.json')
// 缓存文件路径(存储在应用用户数据目录下)
const cacheDir = app.getPath('userData')
const cacheFile = path.join(cacheDir, 'app-cache.json')
const fileRoot = path.join(cacheDir, 'files')
// 确保缓存目录存在
function ensureCacheDir() {
let mainWindow = null
let sensor = null
let usbBridge = null
let sensorOpen = false
let sensorDevices = []
let lastSensorValues = {}
function ensureDir(dir) {
try {
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true })
}
} catch { /* 忽略 */ }
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
} catch {}
}
// 读取缓存文件
function readCache() {
try {
if (fs.existsSync(cacheFile)) {
const raw = fs.readFileSync(cacheFile, 'utf-8')
return JSON.parse(raw)
}
} catch { /* 忽略解析错误 */ }
if (fs.existsSync(cacheFile)) return JSON.parse(fs.readFileSync(cacheFile, 'utf-8'))
} catch {}
return {}
}
// 写入缓存文件
function writeCache(data) {
try {
ensureCacheDir()
ensureDir(cacheDir)
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), 'utf-8')
} catch { /* 忽略写入错误 */ }
} catch {}
}
let sensor = null
function resolveAppFile(inputPath) {
const raw = String(inputPath || '').trim()
if (!raw) return fileRoot
if (path.isAbsolute(raw) && /^[a-zA-Z]:[\\/]/.test(raw)) return raw
const relative = raw.replace(/^[/\\]+/, '')
return path.join(fileRoot, relative)
}
function fileList(dir) {
try {
const target = resolveAppFile(dir)
if (!fs.existsSync(target)) return ''
const stat = fs.statSync(target)
if (stat.isFile()) return target
return fs.readdirSync(target).map(name => path.join(target, name)).join('\n')
} catch {
return ''
}
}
function sensorOperations(id) {
return [
{ id: 'read-version', name: '获取当前传感器版本' },
{ id: 'read-temp', name: '获取设备温度' },
{ id: 'read-euler', name: '获取设备角度' },
{ id: 'reset-euler', name: '重置角度传感器基准' },
{ id: 'read-heading-and-vertical', name: '获取朝向和垂直夹角' }
].map(op => ({ ...op, sensorId: id }))
}
async function refreshSensorDevices() {
try {
const ports = await SerialPort.list()
sensorDevices = ports
.filter(p => p.path && /^COM\d+$/i.test(p.path))
.map(p => ({
id: `HWT6053:${p.path}`,
name: 'HWT6053',
port: p.path,
baudRate: 9600,
isOpen: sensorOpen,
operations: sensorOperations(`HWT6053:${p.path}`)
}))
for (const device of sensorDevices) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('sensor-attached', { id: device.id, name: device.name })
}
}
} catch (err) {
console.error('[sensor] 刷新设备失败:', err.message)
sensorDevices = []
}
}
function normalizeSensorData(data) {
if (!data || data.sensor !== 'HWT6053') return data
if (data.method === 'temperature') {
const value = data.value && typeof data.value === 'object' ? data.value.temp : data.value
return { sensor: 'HWT6053', method: 'read-temp', value: String(value ?? '') }
}
if (data.method === 'angle') {
return { sensor: 'HWT6053', method: 'read-euler', value: data.value }
}
if (data.method === 'magnetic') {
return { sensor: 'HWT6053', method: 'read-heading-and-vertical', value: data.value }
}
return data
}
function cacheSensorValue(data) {
const normalized = normalizeSensorData(data)
if (!normalized) return
lastSensorValues[normalized.method] = normalized.value
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('sensor-data', normalized)
}
}
function ensureSensor() {
if (sensor) return sensor
sensor = new WitMotionSensor({
onData: cacheSensorValue,
onLog: (level, msg) => {
console.log(`[sensor] ${msg}`)
if (mainWindow && !mainWindow.isDestroyed()) {
try { mainWindow.webContents.send('sensor-log', { level, msg }) } catch {}
}
}
})
return sensor
}
function openAppSensor() {
sensorOpen = true
refreshSensorDevices()
ensureSensor().connect()
return true
}
const createWindow = () => {
const win = new BrowserWindow({
function closeAppSensor() {
sensorOpen = false
if (sensor) sensor.disconnect()
sensor = null
return true
}
function runSensorOperation(opId) {
if (opId === 'read-version') return pkg.version || '1'
if (opId === 'read-temp') return lastSensorValues['read-temp'] ?? null
if (opId === 'read-euler') {
const value = lastSensorValues['read-euler']
return value == null ? null : JSON.stringify(value)
}
if (opId === 'read-heading-and-vertical') {
const value = lastSensorValues['read-heading-and-vertical']
return value == null ? null : JSON.stringify(value)
}
if (opId === 'reset-euler') return '0'
return null
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
@ -59,37 +180,40 @@ const createWindow = () => { @@ -59,37 +180,40 @@ const createWindow = () => {
}
})
win.loadFile('./dist/index.html')
mainWindow.loadFile('./dist/index.html')
usbBridge = new UsbSerialBridge(mainWindow)
refreshSensorDevices()
}
// 初始化 USB Serial 桥接模块
const usbBridge = new UsbSerialBridge(win)
function registerIpc() {
ipcMain.on('app-clear-cache-sync', (event) => {
writeCache({})
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.reload()
event.returnValue = true
})
// 窗口创建后连接传感器
sensor = new WitMotionSensor({
onData: (data) => {
if (!win.isDestroyed()) {
win.webContents.send('sensor-data', data)
}
},
onLog: (level, msg) => {
console.log(`[sensor] ${msg}`)
// 也发送到渲染进程,方便静态页面查看日志
if (win && !win.isDestroyed()) {
try { win.webContents.send('sensor-log', { level, msg }) } catch {}
}
}
ipcMain.on('app-version-code-sync', (event) => {
const major = Number(String(pkg.version || '1').split('.')[0]) || 1
event.returnValue = major
})
ipcMain.on('get-cache-sync', (event, key) => {
const cache = readCache()
event.returnValue = key ? (cache[key] ?? null) : JSON.stringify(cache)
})
ipcMain.on('set-cache-sync', (event, key, value) => {
const cache = readCache()
cache[key] = value
writeCache(cache)
event.returnValue = true
})
sensor.connect()
}
app.whenReady().then(() => {
// 注册 IPC 处理器:获取缓存
ipcMain.handle('get-cache', (_event, key) => {
const cache = readCache()
return key ? cache[key] : cache
return key ? (cache[key] ?? null) : cache
})
// 注册 IPC 处理器:设置缓存
ipcMain.handle('set-cache', (_event, key, value) => {
const cache = readCache()
cache[key] = value
@ -97,11 +221,81 @@ app.whenReady().then(() => { @@ -97,11 +221,81 @@ app.whenReady().then(() => {
return true
})
ipcMain.on('app-offline-page', () => {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('app-page-mode', 'offline')
})
ipcMain.on('app-online-page', () => {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('app-page-mode', 'online')
})
ipcMain.on('app-file-list-sync', (event, dir) => {
event.returnValue = fileList(dir)
})
ipcMain.on('app-file-write-string', (event, filePath, content, token) => {
const target = resolveAppFile(filePath)
fs.promises.mkdir(path.dirname(target), { recursive: true })
.then(() => fs.promises.writeFile(target, String(content ?? ''), 'utf-8'))
.then(() => event.sender.send('app-file-write-callback', token, target, true, ''))
.catch(err => event.sender.send('app-file-write-callback', token, target, false, err.message))
})
ipcMain.on('app-file-read-string', (event, filePath, token) => {
const target = resolveAppFile(filePath)
fs.promises.readFile(target, 'utf-8')
.then(content => event.sender.send('app-file-read-callback', token, target, content))
.catch(() => event.sender.send('app-file-read-callback', token, target, null))
})
ipcMain.on('app-file-delete', (event, filePath, token) => {
const target = resolveAppFile(filePath)
fs.promises.unlink(target)
.then(() => event.sender.send('app-file-delete-callback', token, target, true))
.catch(() => event.sender.send('app-file-delete-callback', token, target, false))
})
ipcMain.on('app-sensor-open-sync', (event) => { event.returnValue = openAppSensor() })
ipcMain.on('app-sensor-close-sync', (event) => { event.returnValue = closeAppSensor() })
ipcMain.on('app-sensor-is-open-sync', (event) => { event.returnValue = sensorOpen })
ipcMain.on('sensor-init-all-sync', (event) => {
refreshSensorDevices()
event.returnValue = true
})
ipcMain.on('sensor-flush-sync', (event) => {
refreshSensorDevices()
event.returnValue = true
})
ipcMain.on('sensor-list-sync', (event) => {
event.returnValue = sensorDevices
})
ipcMain.on('sensor-init-sync', (event, id) => {
event.returnValue = sensorDevices.some(device => device.id === id)
})
ipcMain.on('sensor-open-sync', (event) => { event.returnValue = openAppSensor() })
ipcMain.on('sensor-close-sync', (event) => { event.returnValue = closeAppSensor() })
ipcMain.on('sensor-operation-sync', (event, _id, opId) => {
event.returnValue = runSensorOperation(opId)
})
ipcMain.on('sensor-operation-async', (event, id, opId) => {
const result = runSensorOperation(opId)
event.sender.send('sensor-operation-callback', id, opId, result)
})
ipcMain.on('app-location-is-support-sync', (event) => { event.returnValue = false })
ipcMain.on('app-location-is-open-sync', (event) => { event.returnValue = false })
ipcMain.on('app-location-open', (event) => { event.sender.send('app-location-opened', 1) })
ipcMain.on('app-location-close', () => {})
}
app.whenReady().then(() => {
registerIpc()
ensureDir(fileRoot)
createWindow()
// 应用退出时清理
app.on('before-quit', () => {
if (sensor) sensor.disconnect()
// usbBridge 的 cleanup 由窗口 close 事件触发
if (usbBridge) usbBridge.disconnectAll()
})
})

11967
package-lock.json generated

File diff suppressed because it is too large Load Diff

13
package.json

@ -4,16 +4,27 @@ @@ -4,16 +4,27 @@
"description": "光伏pc端应用",
"main": "main.js",
"scripts": {
"start": "electron .",
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "hao_li_work@163.com",
"license": "ISC",
"devDependencies": {
"@electron-forge/cli": "^7.11.2",
"@electron-forge/maker-deb": "^7.11.2",
"@electron-forge/maker-rpm": "^7.11.2",
"@electron-forge/maker-squirrel": "^7.11.2",
"@electron-forge/maker-zip": "^7.11.2",
"@electron-forge/plugin-auto-unpack-natives": "^7.11.2",
"@electron-forge/plugin-fuses": "^7.11.2",
"@electron/fuses": "^1.8.0",
"electron": "^43.2.0"
},
"dependencies": {
"electron-squirrel-startup": "^1.0.1",
"serialport": "^13.0.0"
}
}

199
preload.js

@ -11,9 +11,6 @@ const { contextBridge, ipcRenderer } = require('electron') @@ -11,9 +11,6 @@ const { contextBridge, ipcRenderer } = require('electron')
const nodeNet = require('net')
const dns = require('dns')
/**
* 尝试通过 TCP 连接指定端口检测主机是否可达
*/
function tryConnect(host, port, timeout) {
return new Promise((resolve) => {
const socket = new nodeNet.Socket()
@ -25,88 +22,132 @@ function tryConnect(host, port, timeout) { @@ -25,88 +22,132 @@ function tryConnect(host, port, timeout) {
})
}
/**
* 网络检测对象
*/
const networkUtil = {
/**
* 检测目标主机网络连通性
* @param {string} host - 目标主机地址
* @param {number} timeout - 超时时间毫秒
* @returns {Promise<boolean>} - 是否可达
*/
const net = {
async ping(host, timeout) {
// 1. 先检查 DNS 能否解析
try {
await dns.promises.resolve(host)
} catch {
return false
}
for (const port of [443, 80]) {
if (await tryConnect(host, port, timeout)) return true
}
return false
}
}
// 2. 依次尝试常见端口(443 优先,80 兜底)
const ports = [443, 80]
for (const port of ports) {
const ok = await tryConnect(host, port, timeout)
if (ok) return true
const appCallbacks = {
onFileWrite: null,
onFileRead: null,
onFileDelete: null,
onPageModeChanged: null
}
return false
ipcRenderer.on('app-file-write-callback', (_event, token, filePath, success, err) => {
if (typeof appCallbacks.onFileWrite === 'function') appCallbacks.onFileWrite(token, filePath, success, err)
})
ipcRenderer.on('app-file-read-callback', (_event, token, filePath, content) => {
if (typeof appCallbacks.onFileRead === 'function') appCallbacks.onFileRead(token, filePath, content)
})
ipcRenderer.on('app-file-delete-callback', (_event, token, filePath, success) => {
if (typeof appCallbacks.onFileDelete === 'function') appCallbacks.onFileDelete(token, filePath, success)
})
ipcRenderer.on('app-page-mode', (_event, mode) => {
if (typeof appCallbacks.onPageModeChanged === 'function') appCallbacks.onPageModeChanged(mode)
})
const app = {
clearCache() { return ipcRenderer.sendSync('app-clear-cache-sync') },
versionCode() { return ipcRenderer.sendSync('app-version-code-sync') },
setCache(key, value) { return ipcRenderer.sendSync('set-cache-sync', key, value) },
getCache(key) { return ipcRenderer.sendSync('get-cache-sync', key) },
offlinePage() { ipcRenderer.send('app-offline-page') },
onlinePage() { ipcRenderer.send('app-online-page') },
fileList(dir) { return ipcRenderer.sendSync('app-file-list-sync', dir) },
fileWriteString(filePath, content, token) { ipcRenderer.send('app-file-write-string', filePath, content, token) },
fileReadString(filePath, token) { ipcRenderer.send('app-file-read-string', filePath, token) },
fileDelete(filePath, token) { ipcRenderer.send('app-file-delete', filePath, token) },
get onFileWrite() { return appCallbacks.onFileWrite },
set onFileWrite(fn) { appCallbacks.onFileWrite = fn },
get onFileRead() { return appCallbacks.onFileRead },
set onFileRead(fn) { appCallbacks.onFileRead = fn },
get onFileDelete() { return appCallbacks.onFileDelete },
set onFileDelete(fn) { appCallbacks.onFileDelete = fn },
get onPageModeChanged() { return appCallbacks.onPageModeChanged },
set onPageModeChanged(fn) { appCallbacks.onPageModeChanged = fn }
}
const sensorCallbacks = {
onSensorAttached: null,
onSensorDetached: null,
onOperationCallback: null
}
/**
* 应用传感器对象
* 静态页面设置 appSensor.onValueCallback = function(obj) { ... }
* obj 包含: { method, sensor, value }
*/
const sensorCallbacks = { onValueCallback: null }
ipcRenderer.on('sensor-attached', (_event, data) => {
if (typeof sensorCallbacks.onSensorAttached === 'function') sensorCallbacks.onSensorAttached(data.id, data.name)
})
ipcRenderer.on('sensor-detached', (_event, data) => {
if (typeof sensorCallbacks.onSensorDetached === 'function') sensorCallbacks.onSensorDetached(data.id, data.name)
})
ipcRenderer.on('sensor-operation-callback', (_event, id, opId, result) => {
if (typeof sensorCallbacks.onOperationCallback === 'function') sensorCallbacks.onOperationCallback(id, opId, result)
})
const sensor = {
initAll() { return ipcRenderer.sendSync('sensor-init-all-sync') },
flush() { return ipcRenderer.sendSync('sensor-flush-sync') },
sensorList() { return ipcRenderer.sendSync('sensor-list-sync') },
init(id) { return ipcRenderer.sendSync('sensor-init-sync', id) },
open(id) { return ipcRenderer.sendSync('sensor-open-sync', id) },
close(id) { return ipcRenderer.sendSync('sensor-close-sync', id) },
operation(id, opId, args) { return ipcRenderer.sendSync('sensor-operation-sync', id, opId, args) },
operationAsync(id, opId, args) { ipcRenderer.send('sensor-operation-async', id, opId, args) },
get onSensorAttached() { return sensorCallbacks.onSensorAttached },
set onSensorAttached(fn) { sensorCallbacks.onSensorAttached = fn },
get onSensorDetached() { return sensorCallbacks.onSensorDetached },
set onSensorDetached(fn) { sensorCallbacks.onSensorDetached = fn },
get onOperationCallback() { return sensorCallbacks.onOperationCallback },
set onOperationCallback(fn) { sensorCallbacks.onOperationCallback = fn }
}
const appSensorCallbacks = { onValueCallback: null }
const appSensor = {
version: '1.0.0',
name: 'appSensor',
get onValueCallback() {
return sensorCallbacks.onValueCallback
},
set onValueCallback(fn) {
sensorCallbacks.onValueCallback = fn
}
open() { return ipcRenderer.sendSync('app-sensor-open-sync') },
close() { return ipcRenderer.sendSync('app-sensor-close-sync') },
isOpen() { return ipcRenderer.sendSync('app-sensor-is-open-sync') },
get onValueCallback() { return appSensorCallbacks.onValueCallback },
set onValueCallback(fn) { appSensorCallbacks.onValueCallback = fn }
}
// 监听主进程发送的传感器数据
ipcRenderer.on('sensor-data', (_event, data) => {
if (sensorCallbacks.onValueCallback) {
sensorCallbacks.onValueCallback(data)
}
if (typeof appSensorCallbacks.onValueCallback === 'function') appSensorCallbacks.onValueCallback(data)
})
/**
* 应用缓存对象数据持久化到本地文件
*/
const app = {
/**
* 获取缓存数据
* @param {string} key - 缓存键名
* @returns {Promise<any>} 缓存值
*/
getCache(key) {
return ipcRenderer.invoke('get-cache', key)
},
/**
* 设置缓存数据
* @param {string} key - 缓存键名
* @param {any} value - 缓存值建议传入 JSON.stringify 后的字符串
* @returns {Promise<boolean>}
*/
setCache(key, value) {
return ipcRenderer.invoke('set-cache', key, value)
const appLocationCallbacks = {
onOpened: null,
onLocationChanged: null
}
ipcRenderer.on('app-location-opened', (_event, status) => {
if (typeof appLocationCallbacks.onOpened === 'function') appLocationCallbacks.onOpened(status)
})
ipcRenderer.on('app-location-changed', (_event, location) => {
if (typeof appLocationCallbacks.onLocationChanged === 'function') appLocationCallbacks.onLocationChanged(location)
})
const appLocation = {
isSupport() { return ipcRenderer.sendSync('app-location-is-support-sync') },
isOpen() { return ipcRenderer.sendSync('app-location-is-open-sync') },
open() { ipcRenderer.send('app-location-open') },
close() { ipcRenderer.send('app-location-close') },
get onOpened() { return appLocationCallbacks.onOpened },
set onOpened(fn) { appLocationCallbacks.onOpened = fn },
get onLocationChanged() { return appLocationCallbacks.onLocationChanged },
set onLocationChanged(fn) { appLocationCallbacks.onLocationChanged = fn }
}
/**
* USB Serial JS 桥接对象
* 静态页面可直接使用 usbSerial window.usbSerial
* 接口规范: docs/webview-usb-serial.md
*/
const usbSerialCallbacks = {
onDeviceAttached: null,
onDeviceDetached: null,
@ -117,9 +158,7 @@ const usbSerialCallbacks = { @@ -117,9 +158,7 @@ const usbSerialCallbacks = {
onDeviceData: null
}
// 监听主进程发送的 usb 事件,转换为回调调用
function setupUsbSerialListeners() {
const events = [
for (const [eventName, cbName] of [
['usb-callback-device-attached', 'onDeviceAttached'],
['usb-callback-device-detached', 'onDeviceDetached'],
['usb-callback-device-discovered', 'onDeviceDiscovered'],
@ -127,21 +166,14 @@ function setupUsbSerialListeners() { @@ -127,21 +166,14 @@ function setupUsbSerialListeners() {
['usb-callback-device-opened', 'onDeviceOpened'],
['usb-callback-device-state', 'onDeviceState'],
['usb-callback-device-data', 'onDeviceData']
]
for (const [eventName, cbName] of events) {
]) {
ipcRenderer.on(eventName, (_event, jsonStr) => {
const cb = usbSerialCallbacks[cbName]
if (typeof cb === 'function') {
cb(jsonStr)
}
if (typeof cb === 'function') cb(jsonStr)
})
}
}
setupUsbSerialListeners()
const usbSerial = {
// --- 回调属性(可读写)---
get onDeviceAttached() { return usbSerialCallbacks.onDeviceAttached },
set onDeviceAttached(fn) { usbSerialCallbacks.onDeviceAttached = fn },
get onDeviceDetached() { return usbSerialCallbacks.onDeviceDetached },
@ -156,17 +188,9 @@ const usbSerial = { @@ -156,17 +188,9 @@ const usbSerial = {
set onDeviceState(fn) { usbSerialCallbacks.onDeviceState = fn },
get onDeviceData() { return usbSerialCallbacks.onDeviceData },
set onDeviceData(fn) { usbSerialCallbacks.onDeviceData = fn },
// --- 方法 ---
startDiscovery() {
ipcRenderer.send('usb-start-discovery')
},
stopDiscovery() {
ipcRenderer.send('usb-stop-discovery')
},
getDiscoveredDevices() {
return ipcRenderer.invoke('usb-get-discovered')
},
startDiscovery() { ipcRenderer.send('usb-start-discovery') },
stopDiscovery() { ipcRenderer.send('usb-stop-discovery') },
getDiscoveredDevices() { return ipcRenderer.invoke('usb-get-discovered') },
deviceProbe(deviceName, portNumber, baudRate, hexCmd, timeoutMs) {
ipcRenderer.send('usb-device-probe', { deviceName, portNumber, baudRate, hexCmd, timeoutMs })
},
@ -181,8 +205,9 @@ const usbSerial = { @@ -181,8 +205,9 @@ const usbSerial = {
}
}
// 安全暴露 API 给渲染进程
contextBridge.exposeInMainWorld('net', networkUtil)
contextBridge.exposeInMainWorld('appSensor', appSensor)
contextBridge.exposeInMainWorld('app', app)
contextBridge.exposeInMainWorld('net', net)
contextBridge.exposeInMainWorld('sensor', sensor)
contextBridge.exposeInMainWorld('appSensor', appSensor)
contextBridge.exposeInMainWorld('appLocation', appLocation)
contextBridge.exposeInMainWorld('usbSerial', usbSerial)

Loading…
Cancel
Save