This commit is contained in:
24kycj
2026-05-28 08:41:10 +08:00
parent 22b17aed35
commit 505e72ce28
19 changed files with 589 additions and 134 deletions
+72 -38
View File
@@ -1,12 +1,10 @@
import { app, dialog, ipcMain, shell } from 'electron'
import { app, dialog, shell } from 'electron'
import fs from 'fs'
import path from 'path'
import log from 'electron-log'
import { CS_FAIL, CS_OK } from '../constants'
import { assertNotBusy, assertReady, mainAppState } from '../services/app-state'
import { configStore } from '../services/config-store'
import {
getActiveJobId,
startJobPoll,
startUsbPoll,
stopAllPolls,
@@ -14,7 +12,10 @@ import {
stopUsbPoll,
getPollMainWindow
} from '../services/poll-manager'
import { parsePrinterInfoFromDll, type PrinterStatusSnapshot } from '@shared/printer-info'
import { cleanPathPattern, getDirectorySizeBytes } from '../utils/dir-size'
import { getDesignAppPath } from '../services/app-config'
import { openDesignApp } from '../services/open-design-app'
import { parseSoonTemplate } from '../utils/parse-soon'
import {
dllAdminJobCancel,
@@ -28,6 +29,7 @@ import {
isCancelApiAvailable,
isRejectApiAvailable
} from '../services/work-dll.service'
import { tracedHandle } from './traced-handler'
function ok<T>(data?: T) {
return { ok: true as const, code: CS_OK, data }
@@ -39,16 +41,23 @@ function fail(code: number, message: string) {
let dllInitAttempted = false
function parseBool(v: unknown): boolean {
return v === true || v === 'true' || v === 1 || v === '1' || String(v).toLowerCase() === 'true'
}
export function registerIpcHandlers(): void {
ipcMain.handle('dll:init', (_e, params) => {
tracedHandle('dll:init', (_e, params) => {
if (dllInitAttempted) {
return fail(CS_FAIL, '请勿重复初始化,请完全退出应用后重新启动再试')
return ok({
skipped: true,
printerReady: false,
warning: '已初始化,跳过重复 Init'
})
}
stopAllPolls()
try {
const sharedDir = params?.sharedDir || (configStore.get('sharedDir') as string)
fs.mkdirSync(sharedDir, { recursive: true })
log.info(`SAPI_Init starting, sharedDir=${sharedDir}`)
const code = dllInit({
sharedDir,
keepCombinedImage: params?.keepCombinedImage,
@@ -59,36 +68,51 @@ export function registerIpcHandlers(): void {
logLevel: params?.logLevel,
outBack: params?.outBack
})
log.info(`SAPI_Init finished, code=${code}`)
dllInitAttempted = true
mainAppState.initialized = true
configStore.set('sharedDir', sharedDir)
if (code === CS_OK) {
return ok()
return ok({ code, printerReady: true })
}
log.warn(`SAPI_Init returned ${code}; UI ready, printer ops may fail until device connected`)
return ok({
code,
printerReady: false,
warning: '打印机未连接或驱动未就绪,界面可浏览,接好设备后可在设置中重试 Init'
})
} catch (err) {
mainAppState.initialized = false
log.error('dll:init', err)
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:printer-info', () => {
tracedHandle('dll:printer-info', () => {
try {
assertReady()
const r = dllGetPrinterInfo()
if (!r.json) return fail(0, '未连接打印机')
return ok(r.json)
if (!r.json) {
const cached = configStore.get('lastPrinterStatus')
if (cached) {
return ok({
...cached,
fromCache: true,
liveError: r.code <= 0 ? '未连接打印机' : `GetPrinterInfo code=${r.code}`
})
}
return fail(0, '未连接打印机')
}
const snapshot = parsePrinterInfoFromDll(r.json)
configStore.set('lastPrinterStatus', snapshot)
return ok({ ...r.json, snapshot })
} catch (err) {
const cached = configStore.get('lastPrinterStatus')
if (cached) {
return ok({ ...cached, fromCache: true, liveError: String(err) })
}
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:printer-reset', () => {
tracedHandle('dll:printer-reset', () => {
try {
assertReady()
const code = dllPrinterReset()
@@ -98,7 +122,7 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('dll:printer-reject', () => {
tracedHandle('dll:printer-reject', () => {
try {
assertReady()
if (!isRejectApiAvailable()) return fail(CS_FAIL, 'REJECT_API_UNAVAILABLE')
@@ -109,7 +133,7 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('dll:printer-error-str', (_e, errorNo?: number) => {
tracedHandle('dll:printer-error-str', (_e, errorNo?: number) => {
try {
assertReady()
return ok({ text: dllGetPrinterErrorStr(errorNo ?? -1) })
@@ -118,7 +142,7 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('dll:job-create', (_e, json: string) => {
tracedHandle('dll:job-create', (_e, json: string) => {
try {
assertReady()
assertNotBusy()
@@ -133,7 +157,7 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('dll:job-cancel', (_e, jobId: string) => {
tracedHandle('dll:job-cancel', (_e, jobId: string) => {
try {
assertReady()
const id = jobId || mainAppState.activeJobId
@@ -141,8 +165,6 @@ export function registerIpcHandlers(): void {
let code = CS_OK
if (isCancelApiAvailable()) {
code = dllAdminJobCancel(id)
} else {
log.info('dll:job-cancel: SAPI_AdminJobCancel not in DLL, poll stopped only')
}
mainAppState.mode = 'ready'
mainAppState.activeJobId = ''
@@ -152,7 +174,7 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('dll:usb-copy', (_e, req: { destFolder: string; cardOutput: number }) => {
tracedHandle('dll:usb-copy', (_e, req: { destFolder: string; cardOutput: number }) => {
try {
assertReady()
assertNotBusy()
@@ -168,28 +190,28 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('poll:job-start', (_e, jobId: string) => {
tracedHandle('poll:job-start', (_e, jobId: string) => {
startJobPoll(jobId)
return ok()
})
ipcMain.handle('poll:job-stop', () => {
tracedHandle('poll:job-stop', () => {
stopJobPoll(true)
return ok()
})
ipcMain.handle('poll:usb-start', () => {
tracedHandle('poll:usb-start', () => {
startUsbPoll()
return ok()
})
ipcMain.handle('poll:usb-stop', () => {
tracedHandle('poll:usb-stop', () => {
stopUsbPoll()
mainAppState.mode = 'ready'
return ok()
})
ipcMain.handle('dialog:open-directory', async () => {
tracedHandle('dialog:open-directory', async () => {
const win = getPollMainWindow()
const r = await dialog.showOpenDialog(win ?? undefined, {
properties: ['openDirectory', 'multiSelections']
@@ -198,7 +220,7 @@ export function registerIpcHandlers(): void {
return ok({ paths: r.filePaths })
})
ipcMain.handle('dialog:open-file', async (_e, filters?: { name: string; extensions: string[] }[]) => {
tracedHandle('dialog:open-file', async (_e, filters?: { name: string; extensions: string[] }[]) => {
const win = getPollMainWindow()
const r = await dialog.showOpenDialog(win ?? undefined, {
properties: ['openFile'],
@@ -208,7 +230,7 @@ export function registerIpcHandlers(): void {
return ok({ path: r.filePaths[0] })
})
ipcMain.handle('fs:path-exists', (_e, paths: string[]) => {
tracedHandle('fs:path-exists', (_e, paths: string[]) => {
const missing = paths
.map((raw) => ({ raw, dir: cleanPathPattern(raw) }))
.filter(({ dir }) => !dir || !fs.existsSync(dir))
@@ -216,7 +238,7 @@ export function registerIpcHandlers(): void {
return ok({ missing })
})
ipcMain.handle('fs:dir-size', (_e, paths: string[]) => {
tracedHandle('fs:dir-size', (_e, paths: string[]) => {
const items = paths.map((raw) => {
const dir = cleanPathPattern(raw)
if (!fs.existsSync(dir)) return { path: raw, bytes: 0, missing: true as const }
@@ -231,7 +253,7 @@ export function registerIpcHandlers(): void {
return ok({ items })
})
ipcMain.handle('fs:parse-soon', (_e, filePath: string) => {
tracedHandle('fs:parse-soon', (_e, filePath: string) => {
try {
const soonPath = String(filePath || '').trim()
if (!soonPath) return fail(CS_FAIL, '模板路径为空')
@@ -244,14 +266,18 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('config:get', () => {
tracedHandle('config:get', () => {
const payload: {
sharedDir: string
templateDir: string
traceEnabled: boolean
lastPrinterStatus?: PrinterStatusSnapshot
skipDllInit?: boolean
} = {
sharedDir: configStore.get('sharedDir'),
templateDir: configStore.get('templateDir')
templateDir: configStore.get('templateDir'),
traceEnabled: configStore.get('traceEnabled', true),
lastPrinterStatus: configStore.get('lastPrinterStatus')
}
if (!app.isPackaged) {
payload.skipDllInit = configStore.get('skipDllInit', false)
@@ -259,13 +285,15 @@ export function registerIpcHandlers(): void {
return ok(payload)
})
ipcMain.handle('config:set', (_e, patch: Record<string, unknown>) => {
tracedHandle('config:set', (_e, patch: Record<string, unknown>) => {
Object.entries(patch).forEach(([k, v]) => {
if (k === 'skipDllInit') {
if (app.isPackaged) return
const b =
v === true || v === 'true' || v === 1 || v === '1' || String(v).toLowerCase() === 'true'
configStore.set(k, b)
configStore.set(k, parseBool(v))
return
}
if (k === 'traceEnabled' || k === 'dllTraceEnabled') {
configStore.set('traceEnabled', parseBool(v))
return
}
configStore.set(k, v as string)
@@ -273,14 +301,20 @@ export function registerIpcHandlers(): void {
return ok(configStore.store)
})
ipcMain.handle('shell:open-path', (_e, target: string) => {
tracedHandle('shell:open-path', (_e, target: string) => {
const dir = target || (configStore.get('templateDir') as string)
fs.mkdirSync(dir, { recursive: true })
shell.openPath(dir)
return ok()
})
ipcMain.handle('dll:reject-available', () => ok({ available: isRejectApiAvailable() }))
tracedHandle('design:open', async () => {
const r = await openDesignApp(getDesignAppPath())
if (!r.ok) return fail(CS_FAIL, r.message)
return ok()
})
tracedHandle('dll:reject-available', () => ok({ available: isRejectApiAvailable() }))
}
export async function handleBeforeQuit(): Promise<void> {