打印标签内容以及尺寸确定

This commit is contained in:
dxc
2026-02-04 10:35:13 +08:00
parent 3257973820
commit 4aa43a0607

View File

@ -25,10 +25,18 @@ class LabelPrintService:
LABEL_WIDTH = int(LABEL_WIDTH_MM * DOTS_PER_MM)
LABEL_HEIGHT = int(LABEL_HEIGHT_MM * DOTS_PER_MM)
# 顶部留白: 10mm (120px) - 内容从这里开始
TOP_MARGIN_MM = 2
# 顶部留白
TOP_MARGIN_MM = 1.5
TOP_MARGIN_PX = int(TOP_MARGIN_MM * DOTS_PER_MM)
# 定义左边距 (3mm) - 用于正文左对齐
MARGIN_LEFT = int(3 * DOTS_PER_MM)
# 定义右边距 (防止文字贴边,留 2mm)
MARGIN_RIGHT = int(2 * DOTS_PER_MM)
# 计算文字允许的最大像素宽度
MAX_TEXT_WIDTH = LABEL_WIDTH - MARGIN_LEFT - MARGIN_RIGHT
@staticmethod
def _get_font(size):
"""获取字体"""
@ -45,138 +53,177 @@ class LabelPrintService:
@staticmethod
def _generate_barcode_image(content, width_px, height_px):
"""
生成真实的条形码图片
"""
"""生成真实的条形码图片"""
try:
# 确保内容字符串有效
if not content:
content = "0000000000"
# 使用 Code128 编码(通用性最强)
if not content: content = "0000000000"
code128 = barcode.get('code128', content, writer=ImageWriter())
# 渲染到内存
buffer = BytesIO()
# write_text=False: 不在条码下方生成自带的数字(我们自己画)
code128.write(buffer, options={"write_text": False, "module_height": 10.0, "quiet_zone": 1.0})
# 打开生成的条码图
buffer.seek(0)
bc_img = Image.open(buffer)
# 强制调整大小以适应我们的布局
return bc_img.resize((width_px, height_px), Image.Resampling.LANCZOS)
except Exception as e:
print(f"条形码生成失败: {e}")
# 生成失败时返回一个黑色方块占位
return Image.new('RGB', (width_px, height_px), color='black')
@staticmethod
def draw_text_wrap(draw, text, x, y, font, max_width, line_spacing=0):
"""
[核心功能] 自动换行绘制文本
:param line_spacing: 行与行之间的额外像素距离
:return: 绘制结束后的 Y 坐标
"""
if not text:
return y
lines = []
current_line = ""
# 1. 计算折行逻辑
for char in text:
# 预测加入新字符后的宽度
test_line = current_line + char
width = font.getlength(test_line)
if width <= max_width:
current_line = test_line
else:
# 宽度超出,将当前行存入,新字符作为下一行开头
lines.append(current_line)
current_line = char
# 将最后剩余的内容加入
if current_line:
lines.append(current_line)
# 2. 绘制每一行
current_y = y
font_height = font.size # 获取字号高度
for line in lines:
# 边界检查:如果超出图片高度,停止绘制
if current_y + font_height > LabelPrintService.LABEL_HEIGHT:
break
# 绘制文字 (stroke_width=1 加粗)
draw.text((x, current_y), line, font=font, fill='black', stroke_width=1, stroke_fill='black')
# 更新 Y 坐标 (字高 + 行间距)
current_y += font_height + line_spacing
return current_y
@staticmethod
def _create_image_object(data):
"""
[绘图层] 生成标签图片
"""
# 1. 创建画布 (白底)
# 1. 创建画布
img = Image.new('RGB', (LabelPrintService.LABEL_WIDTH, LabelPrintService.LABEL_HEIGHT), color='white')
d = ImageDraw.Draw(img)
# 2. 统一字体配置 (字号 30)
UNIFIED_FONT_SIZE = 30
font = LabelPrintService._get_font(UNIFIED_FONT_SIZE)
# 2. 字体配置
# [保持] 正文内容维持 24号以节省空间
font_body = LabelPrintService._get_font(24)
# [修改] 编码(SKU)字体设置为 30号
font_code = LabelPrintService._get_font(30)
# 3. 提取条码内容 (优先使用 SKU)
# 逻辑:使用自动生成的 SKU (如 00000001) 作为条码内容
# 3. 数据准备
sku_code = data.get('sku')
if not sku_code:
# 兜底逻辑:如果没有 SKU尝试用 serial_number 或 global_id
sku_code = data.get('serial_number') or str(data.get('global_print_id', '0000000000')).zfill(10)
# ==================== 绘制布局 ====================
# X 轴偏移 (5mm)
GLOBAL_OFFSET_X = int(2 * LabelPrintService.DOTS_PER_MM)
# Y 轴起始: 10mm 处
GLOBAL_OFFSET_X = LabelPrintService.MARGIN_LEFT
CURRENT_Y = LabelPrintService.TOP_MARGIN_PX
# --- A. 绘制真实条形码 (内容为 SKU) ---
bc_w = int(35 * LabelPrintService.DOTS_PER_MM) # 条码宽约 28mm
bc_h = int(10 * LabelPrintService.DOTS_PER_MM) # 条码高约 6mm
# --- A. 绘制条形码 (居中) ---
bc_w = int(35 * LabelPrintService.DOTS_PER_MM)
bc_h = int(7 * LabelPrintService.DOTS_PER_MM) # 高度
# 生成并粘贴条码
bc_img = LabelPrintService._generate_barcode_image(sku_code, bc_w, bc_h)
# 粘贴位置: (X, Y)
img.paste(bc_img, (GLOBAL_OFFSET_X - 10, CURRENT_Y))
# 更新 Y 坐标 (条码高度 + 间距)
CURRENT_Y += bc_h + 5
# [修改核心] 计算条形码的居中 X 坐标
# 公式:(标签总宽 - 条码宽) / 2
bc_x_centered = (LabelPrintService.LABEL_WIDTH - bc_w) // 2
# --- B. 绘制文字 (统一加粗) ---
img.paste(bc_img, (bc_x_centered, CURRENT_Y))
# 准备数据 (名称、规格等)
# --- B. 绘制条形码下方数字 (居中 + 30号字) ---
text_y_pos = CURRENT_Y + bc_h + 2
# [修改核心] 计算文字宽度 并 居中
text_width = font_code.getlength(sku_code)
text_x_centered = (LabelPrintService.LABEL_WIDTH - text_width) // 2
d.text(
(text_x_centered, text_y_pos),
sku_code,
font=font_code, # 使用30号字体
fill='black',
stroke_width=1,
stroke_fill='black'
)
# 更新 Y 坐标,准备开始绘制正文
# 30(字高) + 4(间距)
CURRENT_Y = text_y_pos + 30 + 4
# --- C. 绘制其余信息 (保持左对齐 + 24号字 + 自动换行) ---
# 1. 准备完整文本
name = str(data.get('material_name', '') or '-')
if len(name) > 8: name = name[:7] + ".."
spec = str(data.get('spec_model', '') or '-')
if len(spec) > 11: spec = spec[:10] + ".."
loc = str(data.get('warehouse_loc', '') or '-')
cat = str(data.get('category', '') or '')
typ = str(data.get('material_type', '') or '')
attr = f"{cat}/{typ}"
if len(attr) > 11: attr = attr[:10] + ".."
# -----------------------------------------------------------
# [修改点] 底部显示的文字:不再显示 NO.SKU而是显示 SN 或 BN
# -----------------------------------------------------------
# 底部文字
bottom_text = ""
# 1. 优先检查前端是否传了明确的 print_no (打印值) 和 print_label (序/批)
if data.get('print_no'):
val = str(data.get('print_no'))
label_type = data.get('print_label', '')
if label_type == '':
bottom_text = f"SN: {val}"
elif label_type == '':
bottom_text = f"BN: {val}"
else:
bottom_text = f"NO: {val}"
# 2. 如果没有前端打印参数,检查数据中的序列号
bottom_text = f"{'SN' if label_type == '' else 'BN' if label_type == '' else 'NO'}: {val}"
elif data.get('serial_number'):
bottom_text = f"SN: {data.get('serial_number')}"
# 3. 检查数据中的批号
elif data.get('batch_number'):
bottom_text = f"BN: {data.get('batch_number')}"
# 4. 兜底 (如果什么都没有,才显示 SKU)
else:
bottom_text = f"NO: {sku_code}"
# -----------------------------------------------------------
# 2. 依次调用自动换行绘制函数 (使用正文字体 font_body且坐标使用 GLOBAL_OFFSET_X)
lines = [
f"名: {name}",
f": {spec}",
f"库: {loc}",
f"属: {attr}",
f"{bottom_text}" # 这里放置计算好的 SN/BN 文字
]
# 绘制名称
CURRENT_Y = LabelPrintService.draw_text_wrap(
d, f": {name}", GLOBAL_OFFSET_X, CURRENT_Y, font_body, LabelPrintService.MAX_TEXT_WIDTH, line_spacing=2
)
CURRENT_Y += 2
line_height = 36 # 行高 (字号30 + 6间距)
# 绘制规格
CURRENT_Y = LabelPrintService.draw_text_wrap(
d, f"规: {spec}", GLOBAL_OFFSET_X, CURRENT_Y, font_body, LabelPrintService.MAX_TEXT_WIDTH, line_spacing=2
)
CURRENT_Y += 2
for line in lines:
# 边界检查
if CURRENT_Y + UNIFIED_FONT_SIZE > LabelPrintService.LABEL_HEIGHT:
break
# 绘制库位
CURRENT_Y = LabelPrintService.draw_text_wrap(
d, f"库: {loc}", GLOBAL_OFFSET_X, CURRENT_Y, font_body, LabelPrintService.MAX_TEXT_WIDTH, line_spacing=2
)
CURRENT_Y += 2
# 绘制文字 (stroke_width=1 实现加粗效果)
d.text((GLOBAL_OFFSET_X, CURRENT_Y), line, font=font, fill='black', stroke_width=1, stroke_fill='black')
CURRENT_Y += line_height
# 绘制属性
CURRENT_Y = LabelPrintService.draw_text_wrap(
d, f"属: {attr}", GLOBAL_OFFSET_X, CURRENT_Y, font_body, LabelPrintService.MAX_TEXT_WIDTH, line_spacing=2
)
CURRENT_Y += 2
# 绘制底部编号
CURRENT_Y = LabelPrintService.draw_text_wrap(
d, bottom_text, GLOBAL_OFFSET_X, CURRENT_Y, font_body, LabelPrintService.MAX_TEXT_WIDTH, line_spacing=2
)
return img
@ -194,19 +241,12 @@ class LabelPrintService:
port = LabelPrintService.PRINTER_PORT
try:
# 1. 获取图像 (RGB)
img_rgb = LabelPrintService._create_image_object(data)
# 2. 图像转二值化 (白=1, 黑=0 的逻辑,不反转)
img_gray = img_rgb.convert('L')
img_bw = img_gray.convert('1', dither=Image.Dither.NONE)
# 获取位图数据
bitmap_data = img_bw.tobytes()
width_bytes = (img_bw.width + 7) // 8
height_dots = img_bw.height
# 3. TSPL 指令
header = (
f"SIZE {LabelPrintService.LABEL_WIDTH_MM} mm, {LabelPrintService.LABEL_HEIGHT_MM} mm\r\n"
"GAP 2 mm, 0 mm\r\n"
@ -214,18 +254,14 @@ class LabelPrintService:
"DIRECTION 1\r\n"
"REFERENCE 0, 0\r\n"
).encode('gbk')
bitmap_cmd = f"BITMAP 0,0,{width_bytes},{height_dots},0,".encode('gbk')
footer = b"\r\nPRINT 1,1\r\n"
# 4. 发送
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((ip, port))
s.sendall(header + bitmap_cmd + bitmap_data + footer)
s.close()
return True
except Exception as e:
print(f"❌ 打印异常: {e}")
raise Exception(f"打印机连接失败: {str(e)}")