WuYongJie 0dc3c09315 feat(资产与打印): 提交近期借还盘点与打印助手源码
- 提交借用单缺失异常处理、扫码借还搜索与站内消息快捷处理源码
- 提交盘点范围、多终端打印、组织同步与认证模式相关源码
- 提交 .NET/Node 打印助手及版本清单源码
2026-03-27 20:18:31 +08:00

276 lines
6.7 KiB
JavaScript

import path from 'node:path'
import os from 'node:os'
import { fileURLToPath } from 'node:url'
import {
app,
BrowserWindow,
ipcMain,
Menu,
Tray,
nativeImage,
shell
} from 'electron'
import { createConfigStore } from './config/storage.js'
import {
loadDriverResources,
resolveDriverDownloadUrl
} from './services/driverManifest.js'
import { createPrintAgentClient } from './services/apiClient.js'
import { createJobProcessor } from './services/jobProcessor.js'
import { createAgentRuntime } from './services/agentRuntime.js'
import {
buildAssetLabelPrintHtml,
buildTestLabelPrintHtml
} from './services/labelDocument.js'
import { printHtmlSilently } from './services/electronPrinter.js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const agentRoot = path.resolve(__dirname, '..')
let mainWindow = null
let tray = null
let runtime = null
let store = null
let isQuitting = false
let shouldShowMainWindow = false
function createTrayIcon() {
return nativeImage.createFromDataURL(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAK0lEQVR4AWP4//8/AyUYTFhYWPjPwMBgkJGR4T8GBgYmhjEwMDAwQAEA8A4Qbw6X8UIAAAAASUVORK5CYII='
)
}
function sendStateToRenderer(state) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('agent:state', state)
}
}
async function getPrinters() {
if (!mainWindow || mainWindow.isDestroyed()) {
return []
}
try {
const printers = await mainWindow.webContents.getPrintersAsync()
return printers.map((printer) => ({
name: printer.name,
description: printer.description || '',
status: printer.status ?? 0,
isDefault: Boolean(printer.isDefault)
}))
} catch (error) {
return [
{
name: '',
description: `打印机列表读取失败:${error.message}`,
status: -1,
isDefault: false
}
]
}
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1080,
height: 760,
minWidth: 960,
minHeight: 680,
show: false,
autoHideMenuBar: true,
backgroundColor: '#0b0d12',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
sandbox: false
}
})
mainWindow.loadFile(path.join(__dirname, 'index.html'))
mainWindow.on('ready-to-show', () => {
if (shouldShowMainWindow) {
mainWindow.show()
}
})
mainWindow.on('close', (event) => {
if (!isQuitting) {
event.preventDefault()
mainWindow.hide()
}
})
}
function createTray() {
tray = new Tray(createTrayIcon())
tray.setToolTip('固定资产打印助手')
tray.setContextMenu(
Menu.buildFromTemplate([
{
label: '打开打印助手',
click: () => mainWindow?.show()
},
{
label: '退出',
click: () => {
isQuitting = true
runtime?.stop()
app.quit()
}
}
])
)
tray.on('double-click', () => mainWindow?.show())
}
async function getDriverResourcesForUi(config) {
const resources = await loadDriverResources(agentRoot)
return {
...resources,
companyArchive: {
...resources.companyArchive,
downloadUrl: resolveDriverDownloadUrl(
config?.serverUrl || 'https://www.jsgdzc.cn',
resources.companyArchive
)
}
}
}
function registerIpcHandlers() {
ipcMain.handle('agent:bootstrap', async () => {
const config = await runtime.loadConfig()
const printers = await runtime.refreshPrinters()
const driverResources = await getDriverResourcesForUi(config)
return {
config,
printers,
driverResources,
runtimeState: runtime.getState()
}
})
ipcMain.handle('agent:save-config', async (_event, patch) => {
const config = await runtime.updateConfig(patch)
const driverResources = await getDriverResourcesForUi(config)
return { config, driverResources }
})
ipcMain.handle('agent:list-printers', async () => runtime.refreshPrinters())
ipcMain.handle('agent:heartbeat-now', async () => runtime.heartbeatOnce())
ipcMain.handle('agent:claim-now', async () => runtime.pollOnce())
ipcMain.handle('agent:test-print', async () => runtime.runTestPrint())
ipcMain.handle('agent:open-external', async (_event, url) => shell.openExternal(url))
ipcMain.handle('agent:pair', async (_event, payload) => {
const currentConfig = await runtime.loadConfig()
const client = createPrintAgentClient({
serverUrl: payload.serverUrl || currentConfig.serverUrl
})
const result = await client.pair({
pairCode: payload.pairCode,
terminalName: payload.terminalName,
machineName: payload.machineName || os.hostname(),
platform: payload.platform || process.platform
})
const config = await runtime.updateConfig({
serverUrl: payload.serverUrl || currentConfig.serverUrl,
terminalName: payload.terminalName || currentConfig.terminalName,
pairingCode: '',
pairResult: result
})
return {
config,
pairResult: result
}
})
}
async function createRuntime() {
store = createConfigStore(path.join(app.getPath('userData'), 'data'))
const jobProcessor = createJobProcessor({
async printAssetLabels({ items, printerName }) {
const html = await buildAssetLabelPrintHtml({
items
})
await printHtmlSilently({
html,
printerName,
jobName: '资产标签打印'
})
return {
printed_count: items.length
}
},
async printTestLabel({ printerName }) {
const html = await buildTestLabelPrintHtml({
printerName
})
await printHtmlSilently({
html,
printerName,
jobName: '测试标签打印'
})
}
})
runtime = createAgentRuntime({
store,
clientFactory: (options) => createPrintAgentClient(options),
getPrinters,
jobProcessor,
appVersion: app.getVersion(),
onStateChange: sendStateToRenderer
})
await runtime.start()
const config = runtime.getState().config
shouldShowMainWindow = !config?.deviceToken
if (shouldShowMainWindow) {
mainWindow?.show()
} else {
mainWindow?.hide()
}
}
app.setAppUserModelId('com.jsgdzc.printagent')
if (process.platform === 'win32') {
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: true
})
}
app.on('before-quit', () => {
isQuitting = true
})
app.whenReady().then(async () => {
createWindow()
createTray()
registerIpcHandlers()
await createRuntime()
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
} else {
mainWindow?.show()
}
})
app.on('window-all-closed', (event) => {
event.preventDefault()
})