完善分发与收集任务全流程
- RestJobEx 对齐 task_id,path_file 提交目录路径 - 运行页进度仅跟接口轮询,失败态停留 page3 UI - 完成态卡位续做,USB/任务轮询生命周期优化 - 拆分 DLL 加载、任务 staging 与提交前校验 - 移除 mock 与冗余样式,补充 native 依赖 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,34 +1,19 @@
|
||||
import { app, dialog, shell } from 'electron'
|
||||
import { dialog, shell } from 'electron'
|
||||
import fs from 'fs'
|
||||
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 {
|
||||
startJobPoll,
|
||||
startUsbPoll,
|
||||
stopAllPolls,
|
||||
stopJobPoll,
|
||||
stopUsbPoll,
|
||||
getPollMainWindow
|
||||
} from '../services/poll-manager'
|
||||
import { startUsbPoll, startJobPoll, stopAllPolls, stopJobPoll, stopUsbPoll, stopCardPositionPoll, startCardPositionPoll, getPollMainWindow, isJobPollActive } 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 { writeJobCsv, type JobCsvRow } from '../utils/job-csv'
|
||||
import { parseSoonTemplate } from '../utils/parse-soon'
|
||||
import {
|
||||
dllAdminJobCancel,
|
||||
dllCopyFromUsb,
|
||||
dllGetPrinterErrorStr,
|
||||
dllGetPrinterInfo,
|
||||
dllInit,
|
||||
dllPrinterReject,
|
||||
dllPrinterReset,
|
||||
dllRestJobEx,
|
||||
isCancelApiAvailable,
|
||||
isRejectApiAvailable
|
||||
} from '../services/work-dll.service'
|
||||
import { stageJobPayloadJson } from '../utils/stage-job-payload'
|
||||
import { ensureDllInitialized, isDllInitAttempted } from '../services/dll-bootstrap'
|
||||
import { loadDllModule } from '../services/dll-loader'
|
||||
import { tracedHandle } from './traced-handler'
|
||||
|
||||
function ok<T>(data?: T) {
|
||||
@@ -39,63 +24,49 @@ function fail(code: number, message: string) {
|
||||
return { ok: false as const, code, message }
|
||||
}
|
||||
|
||||
let dllInitAttempted = false
|
||||
function summarizeStagedPayload(json: string): Record<string, unknown> {
|
||||
try {
|
||||
const p = JSON.parse(json) as Record<string, unknown>
|
||||
return {
|
||||
task_id: p.task_id,
|
||||
has_copy_task: p.has_copy_task,
|
||||
has_print_task: p.has_print_task,
|
||||
path_file_count: Array.isArray(p.path_file) ? p.path_file.length : 0,
|
||||
json_file: p.json_file,
|
||||
udf_file: p.udf_file
|
||||
}
|
||||
} catch {
|
||||
return { parseError: true }
|
||||
}
|
||||
}
|
||||
|
||||
function parseBool(v: unknown): boolean {
|
||||
return v === true || v === 'true' || v === 1 || v === '1' || String(v).toLowerCase() === 'true'
|
||||
}
|
||||
|
||||
export function registerIpcHandlers(): void {
|
||||
tracedHandle('dll:init', (_e, params) => {
|
||||
if (dllInitAttempted) {
|
||||
return ok({
|
||||
skipped: true,
|
||||
printerReady: false,
|
||||
warning: '已初始化,跳过重复 Init'
|
||||
})
|
||||
}
|
||||
tracedHandle('dll:init', async (_e, params) => {
|
||||
stopAllPolls()
|
||||
try {
|
||||
const sharedDir = params?.sharedDir || (configStore.get('sharedDir') as string)
|
||||
fs.mkdirSync(sharedDir, { recursive: true })
|
||||
const code = dllInit({
|
||||
sharedDir,
|
||||
keepCombinedImage: params?.keepCombinedImage,
|
||||
stopOnFailure: params?.stopOnFailure,
|
||||
cleanTaskFile: params?.cleanTaskFile,
|
||||
autoRetryTimes: params?.autoRetryTimes,
|
||||
rejectConfig: params?.rejectConfig,
|
||||
logLevel: params?.logLevel,
|
||||
outBack: params?.outBack
|
||||
})
|
||||
dllInitAttempted = true
|
||||
mainAppState.initialized = true
|
||||
configStore.set('sharedDir', sharedDir)
|
||||
if (code === CS_OK) {
|
||||
return ok({ code, printerReady: true })
|
||||
}
|
||||
return ok({
|
||||
code,
|
||||
printerReady: false,
|
||||
warning: '打印机未连接或驱动未就绪,界面可浏览,接好设备后可在设置中重试 Init'
|
||||
})
|
||||
const r = await ensureDllInitialized(params)
|
||||
return ok({ code: r.code, warning: r.warning })
|
||||
} catch (err) {
|
||||
mainAppState.initialized = false
|
||||
return fail(CS_FAIL, String(err))
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('dll:printer-info', () => {
|
||||
tracedHandle('dll:printer-info', async () => {
|
||||
try {
|
||||
assertReady()
|
||||
const r = dllGetPrinterInfo()
|
||||
const dll = await loadDllModule()
|
||||
const r = dll.dllGetPrinterInfo()
|
||||
if (!r.json) {
|
||||
const cached = configStore.get('lastPrinterStatus')
|
||||
if (cached) {
|
||||
return ok({
|
||||
...cached,
|
||||
fromCache: true,
|
||||
liveError: r.code <= 0 ? '未连接打印机' : `GetPrinterInfo code=${r.code}`
|
||||
liveError: r.code <= 0 ? '未连接打印机' : `GetPrinterInfoEx code=${r.code}`
|
||||
})
|
||||
}
|
||||
return fail(0, '未连接打印机')
|
||||
@@ -112,77 +83,139 @@ export function registerIpcHandlers(): void {
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('dll:printer-reset', () => {
|
||||
tracedHandle('dll:printer-reset', async () => {
|
||||
try {
|
||||
assertReady()
|
||||
const code = dllPrinterReset()
|
||||
const dll = await loadDllModule()
|
||||
const code = dll.dllPrinterReset()
|
||||
return code === CS_OK ? ok() : fail(code, '重置失败')
|
||||
} catch (err) {
|
||||
return fail(CS_FAIL, String(err))
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('dll:printer-reject', () => {
|
||||
tracedHandle('dll:printer-reject', async () => {
|
||||
try {
|
||||
assertReady()
|
||||
if (!isRejectApiAvailable()) return fail(CS_FAIL, 'REJECT_API_UNAVAILABLE')
|
||||
const code = dllPrinterReject()
|
||||
const dll = await loadDllModule()
|
||||
if (!dll.isRejectApiAvailable()) return fail(CS_FAIL, 'REJECT_API_UNAVAILABLE')
|
||||
const code = dll.dllPrinterReject()
|
||||
return code === CS_OK ? ok() : fail(code, '废卡失败')
|
||||
} catch (err) {
|
||||
return fail(CS_FAIL, String(err))
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('dll:printer-error-str', (_e, errorNo?: number) => {
|
||||
tracedHandle('dll:printer-error-str', async (_e, errorNo?: number) => {
|
||||
if (!mainAppState.initialized) return ok({ text: '' })
|
||||
try {
|
||||
assertReady()
|
||||
return ok({ text: dllGetPrinterErrorStr(errorNo ?? -1) })
|
||||
const dll = await loadDllModule()
|
||||
return ok({ text: dll.dllGetPrinterErrorStr(errorNo ?? -1) })
|
||||
} catch (err) {
|
||||
return fail(CS_FAIL, String(err))
|
||||
log.warn('dll:printer-error-str', err)
|
||||
return ok({ text: '' })
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('dll:job-create', (_e, json: string) => {
|
||||
tracedHandle('dll:job-create', async (_e, json: string, opts?: { resubmit?: boolean }) => {
|
||||
try {
|
||||
assertReady()
|
||||
assertNotBusy()
|
||||
const r = dllRestJobEx(json)
|
||||
if (r.code !== CS_OK) return fail(r.code, 'RestJobEx 失败')
|
||||
if (opts?.resubmit) {
|
||||
if (mainAppState.mode !== 'distributing') {
|
||||
return fail(CS_FAIL, '当前不在分发任务会话中')
|
||||
}
|
||||
stopCardPositionPoll()
|
||||
} else {
|
||||
assertNotBusy()
|
||||
}
|
||||
const dll = await loadDllModule()
|
||||
const sharedDir = configStore.get('sharedDir') as string
|
||||
let staged: { json: string; taskDir: string }
|
||||
try {
|
||||
staged = stageJobPayloadJson(json, sharedDir, dll)
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
return fail(CS_FAIL, msg)
|
||||
}
|
||||
log.info('RestJobEx staging', {
|
||||
taskDir: staged.taskDir,
|
||||
summary: summarizeStagedPayload(staged.json)
|
||||
})
|
||||
const r = dll.dllRestJobEx(staged.json)
|
||||
if (r.code !== CS_OK) {
|
||||
log.warn('RestJobEx rejected', { code: r.code, json: staged.json.slice(0, 800) })
|
||||
const errText = dll.dllGetPrinterErrorStr(r.code)
|
||||
let detail = errText ? `${errText} (code=${r.code})` : `RestJobEx 失败 (code=${r.code})`
|
||||
if (r.code === -1 && !errText) {
|
||||
detail +=
|
||||
':请确认模板路径、变量 CSV(udf_file)及拷贝路径有效,且打印机/任务目录已就绪'
|
||||
}
|
||||
return fail(r.code, detail)
|
||||
}
|
||||
if (!r.jobId?.trim()) {
|
||||
return fail(CS_FAIL, 'RestJobEx 未返回 jobId')
|
||||
}
|
||||
mainAppState.mode = 'distributing'
|
||||
mainAppState.activeJobId = r.jobId
|
||||
return ok({ jobId: r.jobId })
|
||||
} catch (err) {
|
||||
if (String(err).includes('BUSY')) return fail(CS_FAIL, '已有任务在执行')
|
||||
return fail(CS_FAIL, String(err))
|
||||
const msg = String(err)
|
||||
if (msg.includes('BUSY')) return fail(CS_FAIL, '已有任务在执行')
|
||||
if (msg.includes('NOT_INITIALIZED')) {
|
||||
return fail(CS_FAIL, '系统未初始化,请重启应用')
|
||||
}
|
||||
return fail(CS_FAIL, msg)
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('dll:job-cancel', (_e, jobId: string) => {
|
||||
tracedHandle('dll:job-cancel', async (_e, jobId: string) => {
|
||||
const id = String(jobId || mainAppState.activeJobId || '').trim()
|
||||
stopJobPoll(true)
|
||||
mainAppState.mode = 'ready'
|
||||
mainAppState.activeJobId = ''
|
||||
if (!id) return ok()
|
||||
if (!mainAppState.initialized) return ok()
|
||||
try {
|
||||
assertReady()
|
||||
const id = jobId || mainAppState.activeJobId
|
||||
stopJobPoll(true)
|
||||
let code = CS_OK
|
||||
if (isCancelApiAvailable()) {
|
||||
code = dllAdminJobCancel(id)
|
||||
}
|
||||
mainAppState.mode = 'ready'
|
||||
mainAppState.activeJobId = ''
|
||||
const dll = await loadDllModule()
|
||||
if (!dll.isCancelApiAvailable()) return ok()
|
||||
const code = dll.dllAdminJobCancel(id)
|
||||
return code === CS_OK ? ok() : fail(code, '取消失败')
|
||||
} catch (err) {
|
||||
return fail(CS_FAIL, String(err))
|
||||
log.warn('dll:job-cancel', err)
|
||||
return ok()
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('dll:usb-copy', (_e, req: { destFolder: string; cardOutput: number }) => {
|
||||
tracedHandle('dll:usb-copy', async (_e, req: { destFolder: string; cardOutput: number; resubmit?: boolean }) => {
|
||||
try {
|
||||
assertReady()
|
||||
assertNotBusy()
|
||||
const code = dllCopyFromUsb(req.destFolder, req.cardOutput)
|
||||
if (req.resubmit) {
|
||||
if (mainAppState.mode !== 'usbCopying') {
|
||||
return fail(CS_FAIL, '当前不在数据收集会话中')
|
||||
}
|
||||
stopCardPositionPoll()
|
||||
} else {
|
||||
assertNotBusy()
|
||||
}
|
||||
const destFolder = String(req.destFolder || '').trim()
|
||||
if (!destFolder) return fail(CS_FAIL, '请先选择数据导入目录')
|
||||
fs.mkdirSync(destFolder, { recursive: true })
|
||||
|
||||
const dll = await loadDllModule()
|
||||
if (dll.isUsbReaderApiAvailable()) {
|
||||
const moveCode = dll.dllPrinterMoveToUsbReader()
|
||||
if (moveCode !== CS_OK) {
|
||||
log.warn('MoveToUsbReader before copy', { moveCode })
|
||||
}
|
||||
}
|
||||
|
||||
const code = dll.dllCopyFromUsb(destFolder, req.cardOutput)
|
||||
if (code !== CS_OK) {
|
||||
return fail(code, '可能已有任务在执行')
|
||||
const errText = dll.dllGetPrinterErrorStr(code)
|
||||
return fail(code, errText || '启动 USB 收集失败')
|
||||
}
|
||||
mainAppState.mode = 'usbCopying'
|
||||
startUsbPoll()
|
||||
return ok()
|
||||
} catch (err) {
|
||||
if (String(err).includes('BUSY')) return fail(CS_FAIL, '已有任务在执行')
|
||||
@@ -195,8 +228,8 @@ export function registerIpcHandlers(): void {
|
||||
return ok()
|
||||
})
|
||||
|
||||
tracedHandle('poll:job-stop', () => {
|
||||
stopJobPoll(true)
|
||||
tracedHandle('poll:job-stop', (_e, opts?: { resetMode?: boolean }) => {
|
||||
stopJobPoll(opts?.resetMode !== false)
|
||||
return ok()
|
||||
})
|
||||
|
||||
@@ -205,9 +238,27 @@ export function registerIpcHandlers(): void {
|
||||
return ok()
|
||||
})
|
||||
|
||||
tracedHandle('poll:usb-stop', () => {
|
||||
stopUsbPoll()
|
||||
mainAppState.mode = 'ready'
|
||||
tracedHandle('poll:usb-stop', (_e, opts?: { resetMode?: boolean }) => {
|
||||
stopUsbPoll(opts?.resetMode !== false)
|
||||
return ok()
|
||||
})
|
||||
|
||||
tracedHandle('poll:card-position-start', async () => {
|
||||
try {
|
||||
assertReady()
|
||||
const dll = await loadDllModule()
|
||||
if (!dll.isCardPositionApiAvailable()) {
|
||||
return fail(CS_FAIL, '当前 DLL 不支持卡位查询,无法自动续做')
|
||||
}
|
||||
startCardPositionPoll()
|
||||
return ok()
|
||||
} catch (err) {
|
||||
return fail(CS_FAIL, String(err))
|
||||
}
|
||||
})
|
||||
|
||||
tracedHandle('poll:card-position-stop', () => {
|
||||
stopCardPositionPoll()
|
||||
return ok()
|
||||
})
|
||||
|
||||
@@ -253,6 +304,19 @@ export function registerIpcHandlers(): void {
|
||||
return ok({ items })
|
||||
})
|
||||
|
||||
tracedHandle(
|
||||
'fs:write-job-csv',
|
||||
(_e, payload: { taskId: string; rows: JobCsvRow[] }) => {
|
||||
try {
|
||||
const sharedDir = configStore.get('sharedDir') as string
|
||||
const csvPath = writeJobCsv(sharedDir, payload.taskId, payload.rows || [])
|
||||
return ok({ path: csvPath })
|
||||
} catch (err) {
|
||||
return fail(CS_FAIL, err instanceof Error ? err.message : String(err))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
tracedHandle('fs:parse-soon', (_e, filePath: string) => {
|
||||
try {
|
||||
const soonPath = String(filePath || '').trim()
|
||||
@@ -272,26 +336,19 @@ export function registerIpcHandlers(): void {
|
||||
templateDir: string
|
||||
traceEnabled: boolean
|
||||
lastPrinterStatus?: PrinterStatusSnapshot
|
||||
skipDllInit?: boolean
|
||||
dllInitialized: boolean
|
||||
} = {
|
||||
sharedDir: configStore.get('sharedDir'),
|
||||
templateDir: configStore.get('templateDir'),
|
||||
traceEnabled: configStore.get('traceEnabled', true),
|
||||
lastPrinterStatus: configStore.get('lastPrinterStatus')
|
||||
}
|
||||
if (!app.isPackaged) {
|
||||
payload.skipDllInit = configStore.get('skipDllInit', false)
|
||||
lastPrinterStatus: configStore.get('lastPrinterStatus'),
|
||||
dllInitialized: mainAppState.initialized
|
||||
}
|
||||
return ok(payload)
|
||||
})
|
||||
|
||||
tracedHandle('config:set', (_e, patch: Record<string, unknown>) => {
|
||||
Object.entries(patch).forEach(([k, v]) => {
|
||||
if (k === 'skipDllInit') {
|
||||
if (app.isPackaged) return
|
||||
configStore.set(k, parseBool(v))
|
||||
return
|
||||
}
|
||||
if (k === 'traceEnabled' || k === 'dllTraceEnabled') {
|
||||
configStore.set('traceEnabled', parseBool(v))
|
||||
return
|
||||
@@ -314,19 +371,24 @@ export function registerIpcHandlers(): void {
|
||||
return ok()
|
||||
})
|
||||
|
||||
tracedHandle('dll:reject-available', () => ok({ available: isRejectApiAvailable() }))
|
||||
tracedHandle('dll:reject-available', async () => {
|
||||
const dll = await loadDllModule()
|
||||
return ok({ available: dll.isRejectApiAvailable() })
|
||||
})
|
||||
}
|
||||
|
||||
export async function handleBeforeQuit(): Promise<void> {
|
||||
const dll = isDllInitAttempted() ? await loadDllModule().catch(() => null) : null
|
||||
const shouldCancel =
|
||||
mainAppState.mode === 'distributing' &&
|
||||
!!mainAppState.activeJobId &&
|
||||
isCancelApiAvailable()
|
||||
isJobPollActive() &&
|
||||
!!dll?.isCancelApiAvailable()
|
||||
const cancelJobId = mainAppState.activeJobId
|
||||
stopAllPolls()
|
||||
if (shouldCancel && cancelJobId) {
|
||||
if (shouldCancel && cancelJobId && dll) {
|
||||
try {
|
||||
dllAdminJobCancel(cancelJobId)
|
||||
dll.dllAdminJobCancel(cancelJobId)
|
||||
} catch (e) {
|
||||
log.warn('before-quit cancel', e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user