diff --git a/src/Oms.Application/Labeling/Hiprint/LabelPdfFontRegistrar.cs b/src/Oms.Application/Labeling/Hiprint/LabelPdfFontRegistrar.cs
new file mode 100644
index 0000000..ee65228
--- /dev/null
+++ b/src/Oms.Application/Labeling/Hiprint/LabelPdfFontRegistrar.cs
@@ -0,0 +1,88 @@
+using QuestPDF.Drawing;
+
+namespace Oms.Application.Labeling.Hiprint;
+
+///
+/// 面单 PDF 中文字体:QuestPDF 默认字体无 CJK,不注册会显示 □ / ?。
+///
+public static class LabelPdfFontRegistrar
+{
+ public const string FamilyName = "OmsLabelSans";
+
+ private static int _state; // 0=未试 1=成功 2=失败
+ private static string? _sourcePath;
+
+ ///
+ /// 确保已注册可用中文字体;成功返回 ,否则 null(仅西文)。
+ ///
+ public static string? EnsureRegistered()
+ {
+ var state = Volatile.Read(ref _state);
+ if (state == 1)
+ {
+ return FamilyName;
+ }
+
+ if (state == 2)
+ {
+ return null;
+ }
+
+ foreach (var path in EnumerateCandidates())
+ {
+ if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
+ {
+ continue;
+ }
+
+ try
+ {
+ using var stream = File.OpenRead(path);
+ FontManager.RegisterFontWithCustomName(FamilyName, stream);
+ _sourcePath = path;
+ Interlocked.Exchange(ref _state, 1);
+ return FamilyName;
+ }
+ catch
+ {
+ // 试下一个候选
+ }
+ }
+
+ Interlocked.Exchange(ref _state, 2);
+ return null;
+ }
+
+ /// 已成功注册时的字体文件路径(诊断用)。
+ public static string? RegisteredSourcePath => _sourcePath;
+
+ private static IEnumerable EnumerateCandidates()
+ {
+ var fromEnv = Environment.GetEnvironmentVariable("OMS_LABEL_FONT");
+ if (!string.IsNullOrWhiteSpace(fromEnv))
+ {
+ yield return fromEnv.Trim();
+ }
+
+ var baseDir = AppContext.BaseDirectory;
+ yield return Path.Combine(baseDir, "Fonts", "NotoSansSC-Regular.otf");
+ yield return Path.Combine(baseDir, "Fonts", "NotoSansSC-Regular.ttf");
+ yield return Path.Combine(baseDir, "Fonts", "NotoSansCJKsc-Regular.otf");
+
+ // Windows
+ var winFonts = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
+ if (!string.IsNullOrWhiteSpace(winFonts))
+ {
+ yield return Path.Combine(winFonts, "msyh.ttc");
+ yield return Path.Combine(winFonts, "msyhbd.ttc");
+ yield return Path.Combine(winFonts, "simhei.ttf");
+ yield return Path.Combine(winFonts, "simsun.ttc");
+ }
+
+ // Debian/Ubuntu(Dockerfile 安装 fonts-noto-cjk)
+ yield return "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc";
+ yield return "/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf";
+ yield return "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc";
+ yield return "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc";
+ }
+}
diff --git a/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs b/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs
index 7d265a6..3472a49 100644
--- a/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs
+++ b/src/Oms.Application/Labeling/Hiprint/QuestPdfLabelRenderer.cs
@@ -23,6 +23,8 @@ public sealed class QuestPdfLabelRenderer(HiprintTemplateParser parser) : ILabel
CancellationToken cancellationToken = default)
{
EnsureLicense();
+ // SKU 名称等含中文;未注册 CJK 字体会输出 □
+ _ = LabelPdfFontRegistrar.EnsureRegistered();
cancellationToken.ThrowIfCancellationRequested();
if (printDataPages == null || printDataPages.Count == 0)
@@ -90,6 +92,12 @@ public sealed class QuestPdfLabelRenderer(HiprintTemplateParser parser) : ILabel
.FontSize(text.FontSizePt)
.FontColor(ParseColor(text.ColorHex));
+ var fontFamily = LabelPdfFontRegistrar.EnsureRegistered();
+ if (!string.IsNullOrEmpty(fontFamily))
+ {
+ span.FontFamily(fontFamily);
+ }
+
if (text.Bold)
{
span.Bold();
diff --git a/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs b/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs
index 31e3d23..c55b6a2 100644
--- a/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs
+++ b/src/Oms.Application/Labeling/InboundBoxLabelPrintDataBuilder.cs
@@ -61,6 +61,7 @@ public static class InboundBoxLabelPrintDataBuilder
["boxId"] = box.Id,
["boxSeqNo"] = box.BoxSeqNo,
+ ["boxCount"] = pageCount,
["markBoxNo"] = box.MarkBoxNo,
["barcode"] = barcode,
@@ -103,6 +104,7 @@ public static class InboundBoxLabelPrintDataBuilder
["remark"] = null,
["boxId"] = 1,
["boxSeqNo"] = "1",
+ ["boxCount"] = 2,
["markBoxNo"] = "BOX-001",
["barcode"] = "BOX-001",
["warehouseName"] = "SZ-01",
diff --git a/src/Oms.Application/Labeling/LabelFieldDictionary.cs b/src/Oms.Application/Labeling/LabelFieldDictionary.cs
index 410535c..58b9f1c 100644
--- a/src/Oms.Application/Labeling/LabelFieldDictionary.cs
+++ b/src/Oms.Application/Labeling/LabelFieldDictionary.cs
@@ -85,6 +85,7 @@ public static class LabelFieldDictionary
Field("markReference", "唛头参考"),
Field("markBoxNo", "箱唛号"),
Field("boxSeqNo", "箱序号"),
+ Field("boxCount", "箱子总数"),
Field("barcode", "条码内容"),
Field("boxIndexText", "箱序页码"),
Field("skuSummary", "SKU 摘要"),
diff --git a/src/SimpleAdmin.Api/Dockerfile b/src/SimpleAdmin.Api/Dockerfile
index 0d22ae1..d209edf 100644
--- a/src/SimpleAdmin.Api/Dockerfile
+++ b/src/SimpleAdmin.Api/Dockerfile
@@ -24,6 +24,10 @@ RUN dotnet publish "SimpleAdmin.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publ
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
+# 面单 QuestPDF 需 CJK 字体,否则中文 SKU 等渲染为 □
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends fonts-noto-cjk \
+ && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish .
# Local 存储落盘 wwwroot;非 root 用户需可写 uploads(Provider=Cos 时可不依赖此目录)
RUN mkdir -p /app/wwwroot/uploads \