import { buildDmsPrintBarcode } from './dmsPrintBarcode';
|
import type { DmsPrintAttempt, DmsPrintBatch as DmsAttemptBatch } from './dmsPrintAttempt';
|
import { attemptBatches, recoverDmsBatchReceipts, recordDmsBatchSubmitted, updateDmsPrintAttempt } from './dmsPrintAttempt';
|
import type { DmsPrintRenderItem } from '#/api/x/dms/print';
|
import type { DmsPageVariables, DmsPreviewPrintOptions, DmsPrintConfig, DmsPrintCopyBarcodes } from '#/components/GlobalOfficeDocumentModal/types';
|
import type { HiprintPrintConfig, HiprintPrintResult } from '#/utils/jnpf';
|
|
import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist';
|
import pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
import { v4 as uuidv4 } from 'uuid';
|
|
import { createDmsFilePrintTicket, getDmsFilePageCount, renderDmsPrintPdf } from '#/api/x/dms/print';
|
import { watermarkParameters } from '#/api/x/dms/watermarkParameters';
|
import { onlineUtils } from '#/utils/jnpf';
|
|
GlobalWorkerOptions.workerSrc = pdfWorkerUrl;
|
|
const MAX_PAGE_VARIABLES = 1_000;
|
const MAX_SELECTED_PAGES = 1_000;
|
const MAX_VARIABLES_PER_PAGE = 50;
|
const MAX_VARIABLE_NAME_LENGTH = 100;
|
const MAX_STRING_LENGTH = 2_000;
|
const MAX_PRINT_OPTIONS_BYTES = 1024 * 1024;
|
const MAX_COPY_BARCODE_COPIES = 999;
|
const MAX_BATCH_COPIES = 20;
|
const MAX_BATCH_PAGES = 100;
|
const PRINT_CONFIRM_TIMEOUT_MILLIS = 60_000;
|
|
export class DmsPrintError extends Error {
|
constructor(
|
public readonly code: 'INVALID_PAGES' | 'INVALID_VARIABLES' | 'PRINT_FAILED',
|
message: string,
|
) {
|
super(message);
|
this.name = 'DmsPrintError';
|
}
|
}
|
|
export interface DmsPrintPageContext {
|
canvas: HTMLCanvasElement;
|
pageNo: number;
|
variables: Readonly<Record<string, unknown>>;
|
}
|
|
export type DmsPrintPageVariableHandler = (context: DmsPrintPageContext) => Promise<void> | void;
|
|
const pageVariableHandlers = new Set<DmsPrintPageVariableHandler>();
|
|
/** 登记业务页面变量处理器;返回值用于在业务模块卸载时取消登记。 */
|
export function registerDmsPrintPageVariableHandler(handler: DmsPrintPageVariableHandler) {
|
pageVariableHandlers.add(handler);
|
return () => pageVariableHandlers.delete(handler);
|
}
|
|
function invalidPages(message = '打印页码无效') {
|
return new DmsPrintError('INVALID_PAGES', message);
|
}
|
|
function invalidVariables(message = '页面打印参数无效') {
|
return new DmsPrintError('INVALID_VARIABLES', message);
|
}
|
|
export function parseDmsPrintPages(value?: string, pageCount?: number): number[] | undefined {
|
const text = value?.trim();
|
if (!text) return undefined;
|
|
const pages = new Set<number>();
|
for (const part of text.split(',')) {
|
const token = part.trim();
|
if (!token) throw invalidPages();
|
const match = /^(\d+)(?:\s*-\s*(\d+))?$/.exec(token);
|
if (!match) throw invalidPages();
|
const start = Number(match[1]);
|
const end = Number(match[2] ?? match[1]);
|
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start) throw invalidPages();
|
const actualEnd = pageCount === undefined ? end : Math.min(end, pageCount);
|
for (let pageNo = start; pageNo <= actualEnd; pageNo++) {
|
if (pages.has(pageNo)) continue;
|
if (pages.size >= MAX_SELECTED_PAGES) throw invalidPages('打印页数超过系统限制');
|
pages.add(pageNo);
|
}
|
}
|
return [...pages].sort((left, right) => left - right);
|
}
|
|
function validateVariableValue(value: unknown) {
|
if (value === null || typeof value === 'boolean' || typeof value === 'number') return;
|
if (typeof value === 'string') {
|
if (value.length > MAX_STRING_LENGTH) throw invalidVariables('页面变量字符串超过长度限制');
|
return;
|
}
|
if (Array.isArray(value)) {
|
for (const item of value) {
|
if (Array.isArray(item) || (typeof item === 'object' && item !== null) || item === undefined) throw invalidVariables();
|
validateVariableValue(item);
|
}
|
return;
|
}
|
throw invalidVariables();
|
}
|
|
export function normalizeDmsPrintOptions(
|
options?: DmsPreviewPrintOptions,
|
): Pick<DmsPreviewPrintOptions, 'copyBarcodes' | 'pages'> & Required<Pick<DmsPreviewPrintOptions, 'copies' | 'pageVariables'>> {
|
const copyBarcodes = normalizeCopyBarcodes(options?.copyBarcodes);
|
const copies = options?.copies ?? copyBarcodes?.length ?? 1;
|
if (!Number.isInteger(copies) || copies < 1 || copies > 999) throw invalidVariables('打印份数必须为 1~999 的整数');
|
if (copyBarcodes && copies !== copyBarcodes.length) throw invalidVariables('打印份数必须与逐份条码配置数量一致');
|
|
const pages = options?.pages?.trim() || undefined;
|
const selectedPages = parseDmsPrintPages(pages);
|
const selectedPageSet = selectedPages ? new Set(selectedPages) : undefined;
|
const pageVariables = options?.pageVariables ?? [];
|
if (!Array.isArray(pageVariables) || pageVariables.length > MAX_PAGE_VARIABLES) throw invalidVariables();
|
|
const pageNumbers = new Set<number>();
|
for (const item of pageVariables) {
|
if (!item || !Number.isInteger(item.pageNo) || item.pageNo < 1 || pageNumbers.has(item.pageNo)) throw invalidVariables();
|
if (selectedPageSet && !selectedPageSet.has(item.pageNo)) throw invalidVariables('页面变量引用了未选择的打印页');
|
if (!item.variables || Array.isArray(item.variables) || typeof item.variables !== 'object') throw invalidVariables();
|
const entries = Object.entries(item.variables);
|
if (entries.length > MAX_VARIABLES_PER_PAGE) throw invalidVariables('单页变量数量超过系统限制');
|
for (const [name, value] of entries) {
|
if (!name || name.length > MAX_VARIABLE_NAME_LENGTH) throw invalidVariables('页面变量名称无效');
|
validateVariableValue(value);
|
}
|
pageNumbers.add(item.pageNo);
|
}
|
|
if (new TextEncoder().encode(JSON.stringify({ copies, copyBarcodes, pages, pageVariables })).byteLength > MAX_PRINT_OPTIONS_BYTES) {
|
throw invalidVariables('页面打印参数超过系统大小限制');
|
}
|
return { copies, copyBarcodes, pageVariables, pages };
|
}
|
|
function normalizeCopyBarcodes(value?: DmsPrintCopyBarcodes[]) {
|
if (value === undefined) return undefined;
|
if (!Array.isArray(value) || value.length < 1 || value.length > MAX_COPY_BARCODE_COPIES) {
|
throw invalidVariables(`逐份条码配置数量必须为 1~${MAX_COPY_BARCODE_COPIES}`);
|
}
|
const normalized = value
|
.map((item) => {
|
if (!item || !Number.isInteger(item.copyNo) || item.copyNo < 1 || !Array.isArray(item.customBarcode) || item.customBarcode.length < 1) {
|
throw invalidVariables('逐份条码配置无效');
|
}
|
return {
|
copyNo: item.copyNo,
|
customBarcode: item.customBarcode.map((barcode) => {
|
if (
|
!barcode ||
|
(barcode.page !== undefined && barcode.page !== null && (!Number.isInteger(barcode.page) || barcode.page < 1)) ||
|
typeof barcode.barValue !== 'string' ||
|
!barcode.barValue.trim() ||
|
typeof barcode.barText !== 'string' ||
|
!barcode.barText.trim()
|
) {
|
throw invalidVariables('逐份条码内容无效');
|
}
|
return { ...barcode };
|
}),
|
};
|
})
|
.sort((left, right) => left.copyNo - right.copyNo);
|
normalized.forEach((item, index) => {
|
if (item.copyNo !== index + 1) throw invalidVariables('打印份序必须从 1 开始连续且不可重复');
|
});
|
return normalized;
|
}
|
|
function pageVariablesMap(values: DmsPageVariables[]) {
|
return new Map(values.map((item) => [item.pageNo, Object.freeze({ ...item.variables })]));
|
}
|
|
function printableHtml(pages: Array<{ dataUrl: string; height: number; width: number }>) {
|
const body = pages
|
.map((page) => `<section class="jnpf-dms-print-page" style="width:${page.width}pt;height:${page.height}pt"><img alt="" src="${page.dataUrl}" /></section>`)
|
.join('');
|
return `<style>
|
.jnpf-dms-print-pages { margin: 0; padding: 0; }
|
.jnpf-dms-print-page { box-sizing: border-box; margin: 0; overflow: hidden; break-after: page; page-break-after: always; }
|
.jnpf-dms-print-page:last-child { break-after: auto; page-break-after: auto; }
|
.jnpf-dms-print-page img { display: block; width: 100%; height: 100%; object-fit: fill; }
|
</style><div class="jnpf-dms-print-pages">${body}</div>`;
|
}
|
|
export async function buildDmsPrintableHtml(pdf: ArrayBuffer | Blob, options: DmsPreviewPrintOptions, documentCopies = 1, sourcePageNumbers?: number[]) {
|
const bytes = pdf instanceof Blob ? await pdf.arrayBuffer() : pdf;
|
const task = getDocument({ data: new Uint8Array(bytes) });
|
const pdfDocument = await task.promise;
|
try {
|
if (!Number.isInteger(documentCopies) || documentCopies < 1 || pdfDocument.numPages % documentCopies !== 0) {
|
throw new DmsPrintError('PRINT_FAILED', '批量打印 PDF 页数与打印份数不匹配');
|
}
|
const sourcePageCount = pdfDocument.numPages / documentCopies;
|
const serverFiltered = sourcePageNumbers !== undefined;
|
const selectedPages =
|
sourcePageNumbers ?? parseDmsPrintPages(options.pages, sourcePageCount) ?? Array.from({ length: sourcePageCount }, (_, index) => index + 1);
|
if ((serverFiltered && selectedPages.length !== sourcePageCount) || selectedPages.some((pageNo) => !Number.isInteger(pageNo) || pageNo < 1)) {
|
throw invalidPages('服务端打印页数与指定页码不一致');
|
}
|
const selectedPageSet = new Set(selectedPages);
|
const variables = pageVariablesMap((options.pageVariables ?? []).filter((item) => selectedPageSet.has(item.pageNo)));
|
|
const renderedPages: Array<{ dataUrl: string; height: number; width: number }> = [];
|
for (let copyIndex = 0; copyIndex < documentCopies; copyIndex++) {
|
for (const [pageIndex, selectedPage] of selectedPages.entries()) {
|
const pageNo = selectedPage!;
|
const page = await pdfDocument.getPage(copyIndex * sourcePageCount + (serverFiltered ? pageIndex + 1 : pageNo));
|
const outputScale = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
|
const viewport = page.getViewport({ scale: outputScale });
|
const canvas = document.createElement('canvas');
|
canvas.width = Math.ceil(viewport.width);
|
canvas.height = Math.ceil(viewport.height);
|
const context = canvas.getContext('2d', { alpha: false });
|
if (!context) throw new DmsPrintError('PRINT_FAILED', '无法生成打印页面');
|
await page.render({ canvasContext: context, viewport }).promise;
|
const pageContext: DmsPrintPageContext = { canvas, pageNo, variables: variables.get(pageNo) ?? Object.freeze({}) };
|
for (const handler of pageVariableHandlers) await handler(pageContext);
|
renderedPages.push({
|
dataUrl: canvas.toDataURL('image/png'),
|
height: viewport.height / outputScale,
|
width: viewport.width / outputScale,
|
});
|
page.cleanup();
|
}
|
}
|
return printableHtml(renderedPages);
|
} finally {
|
await pdfDocument.destroy();
|
}
|
}
|
|
interface DmsPrintBatch {
|
items: DmsPrintRenderItem[];
|
pageNumbers: number[];
|
}
|
|
function formatPageNumbers(pages: number[]) {
|
const ranges: string[] = [];
|
for (let index = 0; index < pages.length; index++) {
|
const start = pages[index]!;
|
let end = start;
|
while (index + 1 < pages.length && pages[index + 1] === end + 1) end = pages[++index]!;
|
ranges.push(start === end ? String(start) : `${start}-${end}`);
|
}
|
return ranges.join(',');
|
}
|
|
function withBatchPages(items: DmsPrintRenderItem[], pageNumbers: number[]) {
|
const printPages = formatPageNumbers(pageNumbers);
|
return items.map((item) => ({ ...item, printPages }));
|
}
|
|
function createPrintBatches(items: DmsPrintRenderItem[], pageNumbers: number[], copySpecific: boolean): DmsPrintBatch[] {
|
const pageChunks: number[][] = [];
|
for (let index = 0; index < pageNumbers.length; index += MAX_BATCH_PAGES) {
|
pageChunks.push(pageNumbers.slice(index, index + MAX_BATCH_PAGES));
|
}
|
if (!copySpecific) {
|
return pageChunks.map((pages) => ({ items: withBatchPages(items, pages), pageNumbers: pages }));
|
}
|
if (pageChunks.length > 1) {
|
return items.flatMap((item) => pageChunks.map((pages) => ({ items: withBatchPages([item], pages), pageNumbers: pages })));
|
}
|
const pages = pageChunks[0]!;
|
const batchCopies = Math.max(1, Math.min(MAX_BATCH_COPIES, Math.floor(MAX_BATCH_PAGES / pages.length)));
|
const batches: DmsPrintBatch[] = [];
|
for (let index = 0; index < items.length; index += batchCopies) {
|
batches.push({ items: withBatchPages(items.slice(index, index + batchCopies), pages), pageNumbers: pages });
|
}
|
return batches;
|
}
|
|
function splitPrintBatch(batch: DmsPrintBatch): DmsPrintBatch[] | undefined {
|
if (batch.items.length > 1) {
|
const middle = Math.ceil(batch.items.length / 2);
|
return [
|
{ items: batch.items.slice(0, middle), pageNumbers: batch.pageNumbers },
|
{ items: batch.items.slice(middle), pageNumbers: batch.pageNumbers },
|
];
|
}
|
if (batch.pageNumbers.length > 1) {
|
const middle = Math.ceil(batch.pageNumbers.length / 2);
|
const left = batch.pageNumbers.slice(0, middle);
|
const right = batch.pageNumbers.slice(middle);
|
return [
|
{ items: withBatchPages(batch.items, left), pageNumbers: left },
|
{ items: withBatchPages(batch.items, right), pageNumbers: right },
|
];
|
}
|
}
|
|
async function isBatchPdfSizeError(error: any) {
|
const responseData = error?.response?.data;
|
let responseText = '';
|
if (responseData instanceof Blob) responseText = await responseData.text();
|
else if (typeof responseData === 'string') responseText = responseData;
|
else if (responseData) responseText = JSON.stringify(responseData);
|
return `${error?.message ?? ''} ${responseText}`.includes('打印 PDF 超过系统大小限制');
|
}
|
|
async function dispatchDmsPrintBatch(config: HiprintPrintConfig, onSubmitted?: () => Promise<unknown>) {
|
let receiptSeen = false;
|
let settled = false;
|
let resolveConfirmation!: (result: HiprintPrintResult) => void;
|
let rejectConfirmation!: (error: unknown) => void;
|
const confirmation = new Promise<HiprintPrintResult>((resolve, reject) => {
|
resolveConfirmation = resolve;
|
rejectConfirmation = reject;
|
});
|
const timeoutId = window.setTimeout(() => {
|
if (settled) return;
|
settled = true;
|
rejectConfirmation(new DmsPrintError('PRINT_FAILED', '打印客户端确认任务超时,发送结果未知,请核对客户端任务和实际输出,勿直接重复打印'));
|
}, PRINT_CONFIRM_TIMEOUT_MILLIS);
|
const resolveOnce = (result: HiprintPrintResult) => {
|
if (receiptSeen) return;
|
receiptSeen = true;
|
window.clearTimeout(timeoutId);
|
// 计时器只结束前端等待,不抛弃成功回执;持久化失败交由 outbox 恢复。
|
void (async () => {
|
try {
|
await onSubmitted?.();
|
if (!settled) {
|
settled = true;
|
resolveConfirmation(result);
|
}
|
} catch (error) {
|
if (!settled) {
|
settled = true;
|
rejectConfirmation(error);
|
}
|
}
|
})();
|
};
|
const rejectOnce = (error: unknown) => {
|
if (settled) return;
|
settled = true;
|
window.clearTimeout(timeoutId);
|
rejectConfirmation(error || new DmsPrintError('PRINT_FAILED', '打印客户端发送任务失败'));
|
};
|
|
try {
|
const dispatched = await onlineUtils.hiprint({
|
...config,
|
errorMessageMode: 'none',
|
onError: rejectOnce,
|
onPrint: resolveOnce,
|
});
|
if (!dispatched) rejectOnce(new DmsPrintError('PRINT_FAILED', '打印任务派发失败'));
|
} catch (error) {
|
rejectOnce(error);
|
}
|
return confirmation;
|
}
|
|
export async function printDmsDocument(fileName: string, printConfig: DmsPrintConfig, shouldContinue: () => boolean = () => true) {
|
const normalized = normalizeDmsPrintOptions(printConfig);
|
if (!shouldContinue()) return;
|
const pageCountResponse = await getDmsFilePageCount(printConfig.fileVersionId);
|
if (!shouldContinue()) return;
|
const pageCount = pageCountResponse.data?.pageCount;
|
if (!Number.isInteger(pageCount) || pageCount < 1) throw new DmsPrintError('PRINT_FAILED', '后端返回的文件页数无效');
|
const selectedPages = parseDmsPrintPages(normalized.pages, pageCount) ?? Array.from({ length: pageCount }, (_, index) => index + 1);
|
if (selectedPages.length === 0) return;
|
const authorizedPages = formatPageNumbers(selectedPages);
|
const copyParameters = normalized.copyBarcodes?.map((item) => ({
|
...watermarkParameters(printConfig),
|
customBarcode: item.customBarcode,
|
})) ?? [watermarkParameters(printConfig)];
|
const renderItems: DmsPrintRenderItem[] = [];
|
for (const parameters of copyParameters) {
|
const ticketResponse = await createDmsFilePrintTicket({
|
...parameters,
|
copies: normalized.copyBarcodes ? 1 : normalized.copies,
|
operationId: uuidv4(),
|
printPages: authorizedPages,
|
resourceId: printConfig.fileVersionId,
|
resourceType: 'FILE_VERSION',
|
});
|
if (!shouldContinue()) return;
|
const ticket = ticketResponse.data;
|
if (!ticket?.accessTicket || !ticket.fileVersionId || !ticket.fileName) throw new DmsPrintError('PRINT_FAILED', '后端返回的 DMS 打印票据不完整');
|
if (ticket.fileVersionId !== printConfig.fileVersionId) throw new DmsPrintError('PRINT_FAILED', '打印文件版本与预览版本不一致');
|
renderItems.push({ accessTicket: ticket.accessTicket, fileName: ticket.fileName, fileVersionId: ticket.fileVersionId });
|
}
|
|
const source = renderItems[0]!;
|
const batches = createPrintBatches(renderItems, selectedPages, Boolean(normalized.copyBarcodes));
|
let lastResult;
|
for (let batchIndex = 0; batchIndex < batches.length; ) {
|
const batch = batches[batchIndex]!;
|
let pdf: Blob;
|
try {
|
pdf = await renderDmsPrintPdf({ items: batch.items, source });
|
} catch (error) {
|
const split = (await isBatchPdfSizeError(error)) && splitPrintBatch(batch);
|
if (!split) throw error;
|
batches.splice(batchIndex, 1, ...split);
|
continue;
|
}
|
if (!shouldContinue()) return;
|
const html = await buildDmsPrintableHtml(pdf, { ...normalized, pages: undefined }, normalized.copyBarcodes ? batch.items.length : 1, batch.pageNumbers);
|
if (!shouldContinue()) return;
|
lastResult = await dispatchDmsPrintBatch({
|
clientOptions: {
|
copies: normalized.copyBarcodes ? 1 : normalized.copies,
|
title: batches.length > 1 ? `${fileName} (第 ${batchIndex + 1} 批)` : fileName,
|
},
|
html,
|
});
|
batchIndex++;
|
// 已收到最后一批成功回执后必须完成业务确认,不能因预览关闭而丢弃记账。
|
if (batchIndex < batches.length && !shouldContinue()) return;
|
}
|
await printConfig.onPrintSuccess?.();
|
return lastResult;
|
}
|
|
export interface DmsManagedPrintTransport {
|
recover: typeof recoverDmsBatchReceipts;
|
update: typeof updateDmsPrintAttempt;
|
submitted: typeof recordDmsBatchSubmitted;
|
allowHistoricalSerial?: boolean;
|
}
|
|
/** 台账专用发送:所有恢复均读取服务端计划,SENDING 永不自动重发。 */
|
export async function printManagedDmsDocument(fileName: string, config: DmsPrintConfig, original: DmsPrintAttempt, transport: DmsManagedPrintTransport = {
|
recover: recoverDmsBatchReceipts, update: updateDmsPrintAttempt, submitted: recordDmsBatchSubmitted,
|
}) {
|
if (config.copyBarcodes || config.pages) throw new Error('台账打印必须发送完整文档,不能覆盖页段或逐份配置');
|
const normalized = normalizeDmsPrintOptions({ ...config, copies: original.copies });
|
let attempt = await transport.recover(original);
|
if (attempt.state !== 'OPEN') return;
|
const numbers: Array<{ serialNumber: string; countSerialNumber: string }> = JSON.parse(attempt.numbersJson || '[]');
|
if (numbers.length !== attempt.copies || numbers.some((n) => !n.serialNumber || (!transport.allowHistoricalSerial && !/^\d{8}$/.test(n.countSerialNumber)))) {
|
throw new Error('打印号码未完整冻结,请核对原发送任务,不能重新发送');
|
}
|
let batches = attemptBatches(attempt);
|
if (batches.some((batch) => batch.state === 'SENDING')) throw new Error('打印发送结果未知,请核对原客户端任务并等待回传;系统已阻止重复发送');
|
if (!batches.length) {
|
const response = await getDmsFilePageCount(config.fileVersionId);
|
const pageCount = response.data?.pageCount;
|
if (!Number.isInteger(pageCount) || pageCount < 1 || pageCount > MAX_SELECTED_PAGES) throw new Error('后端返回的文件页数无效');
|
const plan: DmsAttemptBatch[] = [];
|
for (let copy = 0; copy < attempt.copies; copy++) {
|
for (let page = 1; page <= pageCount; page += 100) {
|
plan.push({ id: uuidv4(), pages: Array.from({ length: Math.min(100, pageCount - page + 1) }, (_, i) => page + i), items: [copy], state: 'READY' });
|
}
|
}
|
attempt = await transport.update(attempt, 'fasong-jihua', { batches: plan });
|
batches = attemptBatches(attempt);
|
}
|
// 所有待发送份的格式先校验;每份只读取一次,分页/拆批复用该结果。
|
const barcodes = new Map<number, Awaited<ReturnType<typeof buildDmsPrintBarcode>>>();
|
for (const batch of batches) {
|
if (batch.state === 'SUBMITTED') continue;
|
if (batch.state !== 'READY') throw new Error('打印发送结果未知,禁止重复发送');
|
const copy = batch.items[0]!;
|
const number = numbers[copy];
|
if (!number || batch.items.length !== 1) throw new Error('批次与逐份号码不匹配');
|
if (!barcodes.has(copy)) barcodes.set(copy, await buildDmsPrintBarcode(number, config.buildCustomBarcode));
|
}
|
for (let index = 0; index < batches.length; ) {
|
const batch = batches[index]!;
|
if (batch.state === 'SUBMITTED') {
|
index++;
|
continue;
|
}
|
if (batch.state !== 'READY') throw new Error('打印发送结果未知,禁止重复发送');
|
const number = numbers[batch.items[0]!];
|
if (!number || batch.items.length !== 1) throw new Error('批次与逐份号码不匹配');
|
const ticketResponse = await createDmsFilePrintTicket({
|
requireSerialWatermark: true,
|
...watermarkParameters(config),
|
customBarcode: barcodes.get(batch.items[0]!)!,
|
copies: 1,
|
operationId: batch.id,
|
printPages: formatPageNumbers(batch.pages),
|
resourceId: config.fileVersionId,
|
resourceType: 'FILE_VERSION',
|
});
|
const ticket = ticketResponse.data;
|
if (!ticket?.accessTicket || ticket.fileVersionId !== attempt.fileVersionId || !ticket.fileName) throw new Error('打印票据版本或内容无效');
|
const item = {
|
accessTicket: ticket.accessTicket,
|
fileName: ticket.fileName,
|
fileVersionId: ticket.fileVersionId,
|
printPages: formatPageNumbers(batch.pages),
|
};
|
let pdf: Blob;
|
try {
|
pdf = await renderDmsPrintPdf({ items: [item], source: item });
|
} catch (error) {
|
if (!(await isBatchPdfSizeError(error)) || batch.pages.length < 2) throw error;
|
const middle = Math.ceil(batch.pages.length / 2);
|
const parts = [batch.pages.slice(0, middle), batch.pages.slice(middle)].map((pages) => ({ id: uuidv4(), pages, items: [...batch.items], state: 'READY' }));
|
attempt = await transport.update(attempt, 'chaifen-pici', { batchId: batch.id, batches: parts });
|
batches = attemptBatches(attempt);
|
continue;
|
}
|
const html = await buildDmsPrintableHtml(pdf, { ...normalized, pages: undefined }, 1, batch.pages);
|
if (!(await onlineUtils.isHiprintClientReady())) throw new Error('打印客户端未连接,请启动客户端后恢复原任务');
|
const token = uuidv4();
|
// 只有成功取得一次派发许可才调用客户端;许可响应丢失按未知处理。
|
await transport.update(attempt, 'kaishi-pici', { batchId: batch.id, token });
|
await dispatchDmsPrintBatch(
|
{
|
html,
|
clientOptions: { copies: 1, title: `${fileName} [${attempt.id}/${batch.id}]` },
|
},
|
async () => {
|
attempt = await transport.submitted(attempt, batch, token);
|
},
|
);
|
batch.state = 'SUBMITTED';
|
index++;
|
}
|
}
|