diff --git a/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.test.tsx b/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.test.tsx
index 8c82874b..262f8def 100644
--- a/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.test.tsx
+++ b/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.test.tsx
@@ -1,8 +1,27 @@
-import { render, screen } from '@testing-library/react'
+import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { BarcodePrintCertificateEditDialog } from './BarcodePrintCertificateEditDialog'
import { getSofiaCartonInputStyle } from './barcodePrintSofiaCartonLayout'
+jest.mock('./barcodePrintQrCode', () => ({
+ createBarcodeQrCodeDataUrl: jest.fn(async (barcode: string) =>
+ `data:image/png;barcode=${encodeURIComponent(barcode)}`,
+ ),
+}))
+
+const COMMON_FORM = {
+ productName: '餐椅-奶油白',
+ barcode: 'WPH-SZ015713N1170V01',
+ size: '100*70*40',
+ hangtagMaterial: '科技布',
+ maker: '常州朴美家居有限公司',
+ executionStandard: 'GB/T 3324',
+ level: '合格',
+ auther: 'B',
+ address: '常州经济开发区横林镇红赵路62号',
+ phone: '19941684327',
+}
+
describe('BarcodePrintCertificateEditDialog', () => {
it('shows Sofia carton editable fields before printing', async () => {
const actor = userEvent.setup()
@@ -59,24 +78,13 @@ describe('BarcodePrintCertificateEditDialog', () => {
})
})
- it('keeps maker address and phone hidden for common template without supplier', () => {
+ it('keeps maker address and phone hidden for common template without supplier', async () => {
render(
,
@@ -87,5 +95,41 @@ describe('BarcodePrintCertificateEditDialog', () => {
expect(screen.queryByLabelText('制造商')).not.toBeInTheDocument()
expect(screen.queryByLabelText('地址')).not.toBeInTheDocument()
expect(screen.queryByLabelText('联系电话')).not.toBeInTheDocument()
+ await waitFor(() =>
+ expect(screen.getByRole('img', { name: '条码二维码' })).toHaveAttribute(
+ 'data-barcode',
+ COMMON_FORM.barcode,
+ ),
+ )
+ })
+
+ it.each([
+ ['commonWithMaker', '通用横版贴纸-带供应商(10cm*7cm)'],
+ ['commonWithoutMaker', '通用横版贴纸-不带供应商(10cm*7cm)'],
+ ] as const)('keeps the %s QR code synced with the editable barcode', async (editMode, templateTitle) => {
+ const actor = userEvent.setup()
+
+ render(
+ ,
+ )
+
+ const qrCode = screen.getByRole('img', { name: '条码二维码' })
+ await waitFor(() =>
+ expect(qrCode).toHaveAttribute('data-barcode', COMMON_FORM.barcode),
+ )
+
+ await actor.clear(screen.getByLabelText('条码'))
+ await actor.type(screen.getByLabelText('条码'), 'GBSFY-SJ037640N1410V01')
+
+ await waitFor(() =>
+ expect(qrCode).toHaveAttribute('data-barcode', 'GBSFY-SJ037640N1410V01'),
+ )
})
})
diff --git a/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.tsx b/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.tsx
index 82278446..af560b89 100644
--- a/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.tsx
+++ b/furniture-3d-system/src/app/intake/barcode-print/BarcodePrintCertificateEditDialog.tsx
@@ -2,6 +2,7 @@
import { Printer, Upload, X } from "lucide-react";
import { useEffect, useRef, useState, type CSSProperties } from "react";
+import { BarcodePrintQrCodePreview } from "./BarcodePrintQrCodePreview";
import type {
CertificatePrintOptions,
CertificateSpecialEditMode,
@@ -294,6 +295,7 @@ export function BarcodePrintCertificateEditDialog({
backgroundSize: "cover",
}}
>
+
(EMPTY_QR_CODE);
+
+ useEffect(() => {
+ if (!normalizedBarcode) return;
+
+ let active = true;
+ void createBarcodeQrCodeDataUrl(normalizedBarcode)
+ .then((dataUrl) => {
+ if (active) {
+ setRenderedQrCode({ barcode: normalizedBarcode, dataUrl });
+ }
+ })
+ .catch(() => {
+ if (active) {
+ setRenderedQrCode(EMPTY_QR_CODE);
+ }
+ });
+
+ return () => {
+ active = false;
+ };
+ }, [normalizedBarcode]);
+
+ const isCurrentQrCode = renderedQrCode.barcode === normalizedBarcode;
+
+ return (
+
+ {isCurrentQrCode && renderedQrCode.dataUrl ? (
+ // eslint-disable-next-line @next/next/no-img-element -- QR data URLs are generated locally.
+

+ ) : null}
+
+ );
+}
diff --git a/furniture-3d-system/src/app/intake/barcode-print/barcodePrintCertificate.ts b/furniture-3d-system/src/app/intake/barcode-print/barcodePrintCertificate.ts
index 9204ba15..5f88c33a 100644
--- a/furniture-3d-system/src/app/intake/barcode-print/barcodePrintCertificate.ts
+++ b/furniture-3d-system/src/app/intake/barcode-print/barcodePrintCertificate.ts
@@ -1,11 +1,14 @@
"use client";
import html2canvas from "html2canvas";
-import QRCode from "qrcode";
import {
GUJIA_CERTIFICATE_CANVAS,
getGujiaCertificateFieldStyle,
} from "./barcodePrintGujiaCertificateLayout";
+import {
+ createBarcodeQrCodeDataUrl,
+ resolveCertificateQrCodeValue,
+} from "./barcodePrintQrCode";
import {
getSofiaCartonTitleStyle,
getSofiaCartonValueStyle,
@@ -635,28 +638,6 @@ function findCertificateTemplate(title: string) {
return template;
}
-async function createQrCodeBase64(barcode: string) {
- if (!barcode) return "";
-
- return new Promise((resolve, reject) => {
- QRCode.toDataURL(
- barcode,
- {
- width: 182,
- margin: 0,
- },
- (error, url) => {
- if (error) {
- reject(error);
- return;
- }
-
- resolve(url);
- },
- );
- });
-}
-
function appendHiddenRenderRoot(element: HTMLElement) {
const root = document.createElement("div");
Object.assign(root.style, {
@@ -749,7 +730,11 @@ export async function createCertificatePrintPreview(
): Promise {
const template = findCertificateTemplate(templateTitle);
const record = buildCertificateRecord(row);
- const qrCodeBase64 = await createQrCodeBase64(record.barcode);
+ const qrCodeValue = resolveCertificateQrCodeValue(
+ record.barcode,
+ options.commonForm?.barcode,
+ );
+ const qrCodeBase64 = await createBarcodeQrCodeDataUrl(qrCodeValue);
const backgroundUrl = assetUrl(`${TEMPLATE_BASE}/${template.background}`);
const backgroundDataUrl = await imageUrlToDataUrl(backgroundUrl);
const element = template.render(record, qrCodeBase64, options, backgroundDataUrl);
diff --git a/furniture-3d-system/src/app/intake/barcode-print/barcodePrintQrCode.test.ts b/furniture-3d-system/src/app/intake/barcode-print/barcodePrintQrCode.test.ts
new file mode 100644
index 00000000..88cd4d6b
--- /dev/null
+++ b/furniture-3d-system/src/app/intake/barcode-print/barcodePrintQrCode.test.ts
@@ -0,0 +1,22 @@
+import { resolveCertificateQrCodeValue } from "./barcodePrintQrCode";
+
+describe("resolveCertificateQrCodeValue", () => {
+ it("uses the edited common-template barcode", () => {
+ expect(
+ resolveCertificateQrCodeValue(
+ "GBGJ-DNZ036501N08V01",
+ " GBSFY-SJ037640N1410V01 ",
+ ),
+ ).toBe("GBSFY-SJ037640N1410V01");
+ });
+
+ it("keeps an intentionally cleared edited barcode blank", () => {
+ expect(resolveCertificateQrCodeValue("GBGJ-DNZ036501N08V01", "")).toBe("");
+ });
+
+ it("falls back to the row barcode when there is no editable form", () => {
+ expect(resolveCertificateQrCodeValue(" GBGJ-DNZ036501N08V01 ")).toBe(
+ "GBGJ-DNZ036501N08V01",
+ );
+ });
+});
diff --git a/furniture-3d-system/src/app/intake/barcode-print/barcodePrintQrCode.ts b/furniture-3d-system/src/app/intake/barcode-print/barcodePrintQrCode.ts
new file mode 100644
index 00000000..8073063e
--- /dev/null
+++ b/furniture-3d-system/src/app/intake/barcode-print/barcodePrintQrCode.ts
@@ -0,0 +1,33 @@
+import QRCode from "qrcode";
+
+const BARCODE_QR_CODE_SIZE = 182;
+
+export function resolveCertificateQrCodeValue(
+ originalBarcode: string,
+ editedBarcode?: string,
+) {
+ return (editedBarcode ?? originalBarcode).trim();
+}
+
+export async function createBarcodeQrCodeDataUrl(barcode: string) {
+ const value = barcode.trim();
+ if (!value) return "";
+
+ return new Promise((resolve, reject) => {
+ QRCode.toDataURL(
+ value,
+ {
+ width: BARCODE_QR_CODE_SIZE,
+ margin: 0,
+ },
+ (error, url) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ resolve(url);
+ },
+ );
+ });
+}