完善分发收集续做、打印机状态查询与任务字段对齐

- 完成/失败态统一卡位13自动续做,修复主进程失败时 mode 被重置导致续做被拒
- 接入 SAPI_PrinterCheckstatus,Header 轻量刷状态,Init 后一次 GetPrinterInfo
- print_flag 取自 soon.flag,path_file 目录级 staging,加密狗与格式化选项对齐
- 失败页并入运行页,移除独立 DistributeFailedView

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-06-02 12:16:03 +08:00
parent ece0a06a80
commit 3168daedf4
24 changed files with 389 additions and 255 deletions
+45 -1
View File
@@ -5,7 +5,7 @@ import { CS_FAIL, CS_OK } from '../constants'
import { assertNotBusy, assertReady, mainAppState } from '../services/app-state'
import { configStore } from '../services/config-store'
import { startUsbPoll, startJobPoll, stopAllPolls, stopJobPoll, stopUsbPoll, stopCardPositionPoll, startCardPositionPoll, getPollMainWindow, isJobPollActive } from '../services/poll-manager'
import { parsePrinterInfoFromDll, type PrinterStatusSnapshot } from '@shared/printer-info'
import { parsePrinterInfoFromDll, statusTextFromCheckstatus, 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'
@@ -83,6 +83,50 @@ export function registerIpcHandlers(): void {
}
})
tracedHandle('dll:printer-status', async () => {
if (!mainAppState.initialized) {
const cached = configStore.get('lastPrinterStatus')
return ok({
statusText: cached?.statusText ?? '未初始化',
fromCache: true
})
}
try {
assertReady()
const dll = await loadDllModule()
if (!dll.isPrinterCheckstatusApiAvailable()) {
return fail(CS_FAIL, '当前 DLL 不支持打印机状态查询')
}
const code = dll.dllPrinterCheckstatus()
const parsed = statusTextFromCheckstatus(code)
const cached = configStore.get('lastPrinterStatus')
if (parsed.ok) {
const snapshot: PrinterStatusSnapshot = {
ribbonType: cached?.ribbonType ?? '—',
statusText: parsed.statusText,
serialNo: cached?.serialNo ?? '—',
printedCount: cached?.printedCount ?? 0
}
configStore.set('lastPrinterStatus', snapshot)
return ok({ statusText: parsed.statusText, statusCode: code })
}
if (cached) {
return ok({
statusText: parsed.statusText,
fromCache: true,
liveError: parsed.liveError
})
}
return fail(code, parsed.liveError || parsed.statusText)
} catch (err) {
const cached = configStore.get('lastPrinterStatus')
if (cached) {
return ok({ statusText: cached.statusText, fromCache: true, liveError: String(err) })
}
return fail(CS_FAIL, String(err))
}
})
tracedHandle('dll:printer-reset', async () => {
try {
assertReady()
+18 -17
View File
@@ -95,14 +95,12 @@ export function startJobPoll(id: string): void {
log.warn('GetJobStateById query failed', r.queryErrorCode)
return
}
if (failed || cancelled) {
stopJobPoll(true)
} else if (finished) {
if (failed || cancelled || finished) {
stopJobPoll(false)
}
} catch (e) {
log.error('job poll error', e)
stopJobPoll(true)
stopJobPoll(false)
}
})()
}, POLL_INTERVAL_MS)
@@ -115,7 +113,7 @@ export function startUsbPoll(): void {
usbTimer = setInterval(() => {
void pollUsbOnce(gen).catch((e) => {
log.error('usb poll error', e)
stopUsbPoll(true)
stopUsbPoll(false)
})
}, POLL_INTERVAL_MS)
}
@@ -156,21 +154,24 @@ async function pollUsbOnce(gen: number): Promise<void> {
}
}
async function pollCardPositionOnce(): Promise<void> {
try {
const dll = await loadDllModule()
if (!dll.isCardPositionApiAvailable()) return
const r = dll.dllGetPrinterCardPosition()
const tick = { queryCode: r.queryCode, position: r.position }
emitTrace('[poll] card:position-tick', tick)
send('card:position-tick', tick)
} catch (e) {
log.error('card position poll error', e)
}
}
export function startCardPositionPoll(): void {
stopCardPositionPoll()
void pollCardPositionOnce()
cardTimer = setInterval(() => {
void (async () => {
try {
const dll = await loadDllModule()
if (!dll.isCardPositionApiAvailable()) return
const r = dll.dllGetPrinterCardPosition()
const tick = { queryCode: r.queryCode, position: r.position }
emitTrace('[poll] card:position-tick', tick)
send('card:position-tick', tick)
} catch (e) {
log.error('card position poll error', e)
}
})()
void pollCardPositionOnce()
}, POLL_INTERVAL_MS)
}
+27 -2
View File
@@ -47,9 +47,12 @@ let SAPI_PrinterMovetousbreader: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_GetPrinterCardPosition: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_PrinterCheckstatus: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_UploadFile: any = null
let hasRejectApi = false
let hasCardPositionApi = false
let hasCheckstatusApi = false
let hasCancelApi = false
let hasUploadApi = false
let hasPrinterInfoEx = false
@@ -179,13 +182,23 @@ function loadLibrary(): void {
emitTrace('[dll] SAPI_GetPrinterCardPosition not in workDll (optional)')
}
try {
SAPI_PrinterCheckstatus = lib.func('int __stdcall SAPI_PrinterCheckstatus()')
hasCheckstatusApi = true
} catch {
SAPI_PrinterCheckstatus = null
hasCheckstatusApi = false
emitTrace('[dll] SAPI_PrinterCheckstatus not in workDll (optional)')
}
log.info('workDll loaded', {
upload: hasUploadApi,
printerInfoEx: hasPrinterInfoEx,
cancel: hasCancelApi,
reject: hasRejectApi,
usbReader: hasUsbReaderApi,
cardPosition: hasCardPositionApi
cardPosition: hasCardPositionApi,
checkstatus: hasCheckstatusApi
})
}
@@ -214,6 +227,11 @@ export function isCardPositionApiAvailable(): boolean {
return hasCardPositionApi
}
export function isPrinterCheckstatusApiAvailable(): boolean {
loadLibrary()
return hasCheckstatusApi
}
export function dllInit(params: InitParams): number {
return traceCall(
'SAPI_Init',
@@ -336,7 +354,6 @@ export function dllCopyFromUsb(destFolder: string, cardOutput: number): number {
export function dllGetUsbCopyState(): {
queryCode: number
taskStatus: number
/** copy_progress 0-100 */
progress: number
} {
return traceCall('SAPI_GetUsbCopyState', undefined, () => {
@@ -384,3 +401,11 @@ export function dllGetPrinterCardPosition(): { queryCode: number; position: numb
return { queryCode, position: position[0] }
})
}
export function dllPrinterCheckstatus(): number {
return traceCall('SAPI_PrinterCheckstatus', undefined, () => {
loadLibrary()
if (!SAPI_PrinterCheckstatus) throw new Error('CHECKSTATUS_API_UNAVAILABLE')
return SAPI_PrinterCheckstatus!() as number
})
}
+13 -2
View File
@@ -11,10 +11,20 @@ export interface ParsedSoonTemplate {
frontImageUrl: string
backImageUrl: string
fields: TemplateFieldRow[]
printFlag: number
}
const SOON_FIELD_TYPES = new Set([1, 3, 4, 5])
export function readSoonPrintFlag(raw: Record<string, unknown>): number {
const flag = Number(raw.flag)
if (flag === 1 || flag === 2) return flag
const hasBack =
!!String(raw.backDisplayPic ?? '').trim() ||
(Array.isArray(raw.backData) && raw.backData.length > 0)
return hasBack ? 2 : 1
}
function pickArray(obj: Record<string, unknown>, key: string): Record<string, unknown>[] {
const entry = Object.entries(obj).find(([k]) => k.toLowerCase() === key.toLowerCase())
if (!Array.isArray(entry?.[1])) return []
@@ -83,7 +93,8 @@ function parseSoonWorkerDisk(soonPath: string, raw: Record<string, unknown>): Pa
return {
frontImageUrl: toImageUrl(soonPath, frontPic),
backImageUrl: toImageUrl(soonPath, backPic),
fields
fields,
printFlag: readSoonPrintFlag(raw)
}
}
@@ -119,7 +130,7 @@ function parseSoonLegacy(soonPath: string, raw: Record<string, unknown>): Parsed
fields.push({ label: toFieldLabel(name, side), value, originName: name })
})
return { frontImageUrl, backImageUrl, fields }
return { frontImageUrl, backImageUrl, fields, printFlag: readSoonPrintFlag(raw) }
}
export function parseSoonTemplate(soonPath: string, raw: Record<string, unknown>): ParsedSoonTemplate {
-3
View File
@@ -51,7 +51,6 @@ function stageSoonAssets(taskDir: string, soonSrc: string, soonDest: string): vo
}
}
/** 保留目录级 path_file,不展开为单文件列表 */
function normalizeCopyPaths(payload: Record<string, unknown>): void {
if (!Array.isArray(payload.path_file)) {
throw new Error('拷贝任务缺少 path_file')
@@ -87,7 +86,6 @@ function normalizeCopyPaths(payload: Record<string, unknown>): void {
payload.path_file = normalized
}
/** staging 后按 has_* 裁剪字段,并校验 RestJobEx 必填项 */
function finalizeRestJobPayload(payload: Record<string, unknown>): void {
const hasCopy = payload.has_copy_task === true
const hasPrint = payload.has_print_task === true
@@ -119,7 +117,6 @@ function finalizeRestJobPayload(payload: Record<string, unknown>): void {
}
}
/** 通过 SAPI_UploadFile + 本地落盘,准备 RestJobEx 所需路径 */
export function stageJobPayloadJson(
json: string,
sharedDir: string,