云
16 小时以前 c2e8526f8f857be5e3a8c6f2b39c5efd424de33e
src/views/wls/barcode/components/printer-label.vue
@@ -1,30 +1,184 @@
<script lang="ts" setup>
import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
import { nextTick, ref } from 'vue';
import { Button, message } from 'ant-design-vue';
import { createBarcode, getBarcodeByBusiness } from '#/api/mes/wm/barcode';
import WlsBarcode from './barcode.vue';
defineOptions({ name: 'WlsPrinterLabel' });
withDefaults(
/** 标签上附加展示的业务字段(可选,默认不展示) */
interface LabelField {
  label: string;
  value?: number | string;
}
const props = withDefaults(
  defineProps<{
    bizCode?: string;
    bizId?: number;
    bizType?: string;
    /** 业务名称,作为标签主信息展示在条码下方 */
    bizName?: string;
    bizType?: number;
    /** 标签标题 */
    title?: string;
    /** 按钮文案 */
    labelText?: string;
    /** 附加字段,一般不需要传 */
    fields?: LabelField[];
  }>(),
  {
    bizCode: undefined,
    bizId: undefined,
    bizName: undefined,
    bizType: undefined,
    title: '',
    labelText: '标签打印',
    fields: () => [],
  },
);
function handlePrint() {
  message.warning('标签打印功能暂未实现,敬请期待');
const barcodeRef = ref<InstanceType<typeof WlsBarcode>>();
const barcode = ref<MesWmBarcodeApi.Barcode>();
const renderKey = ref(0);
const printing = ref(false);
/** 打印窗口:需在用户点击时同步打开,否则会被浏览器拦截 */
const printWindow = ref<null | Window>(null);
function escapeHtml(value: string) {
  return value
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;');
}
/** 构建标签 HTML:标题 + 条码图 + 名称(居中),附加字段可选 */
function buildLabelHtml(base64: string) {
  const nameHtml = props.bizName
    ? `<div class="label-name">${escapeHtml(props.bizName)}</div>`
    : '';
  const infoHtml = props.fields
    .filter((item) => item.value !== undefined && item.value !== '')
    .map(
      (item) =>
        `<p><span class="info-label">${escapeHtml(item.label)}</span>${escapeHtml(String(item.value))}</p>`,
    )
    .join('');
  return `<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>${escapeHtml(props.title || '打印标签')}</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:Arial,"Microsoft YaHei",sans-serif;display:flex;justify-content:center;padding:12px}
.label{width:320px;padding:8px;text-align:center}
.label-title{font-size:13px;color:#333;margin-bottom:6px}
.label-barcode{width:200px;margin:0 auto}
.label-barcode img{display:block;width:100%}
.label-name{margin-top:8px;font-size:16px;font-weight:700;line-height:1.4;word-break:break-all}
.label-info{margin-top:8px;font-size:12px;line-height:1.6;text-align:left;color:#333}
.label-info p{margin:2px 0}
.info-label{color:#666;margin-right:4px}
.info-label::after{content:":"}
@page{margin:5mm}
@media print{body{padding:0}.label{width:auto;max-width:100%}}
</style>
</head><body><div class="label">
${props.title ? `<div class="label-title">${escapeHtml(props.title)}</div>` : ''}
<div class="label-barcode"><img src="${base64}" alt="条码" /></div>
${nameHtml}
${infoHtml ? `<div class="label-info">${infoHtml}</div>` : ''}
</div></body></html>`;
}
/** 条码图渲染完成,把标签内容写入已打开的打印窗口并调起打印 */
function handleBarcodeDone() {
  const win = printWindow.value;
  const base64 = barcodeRef.value?.getImageBase64();
  printWindow.value = null;
  if (!win || win.closed) {
    return;
  }
  if (!base64) {
    message.warning('条码渲染失败,无法打印标签');
    win.close();
    return;
  }
  win.document.open();
  win.document.write(buildLabelHtml(base64));
  win.document.close();
  setTimeout(() => win.print(), 500);
}
/** 打印标签:无条码时先生成,再渲染条码图后打印 */
async function handlePrint() {
  if (!props.bizType || !props.bizId) {
    message.warning('缺少业务类型或业务编号,无法打印标签');
    return;
  }
  if (printing.value) {
    return;
  }
  // 在用户手势内同步打开窗口,避免弹窗被拦截
  const win = window.open('', '_blank');
  if (!win) {
    message.error('无法打开打印窗口,请检查浏览器设置');
    return;
  }
  win.document.write(
    '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body style="font-family:Arial;padding:20px">正在生成标签,请稍候…</body></html>',
  );
  printWindow.value = win;
  printing.value = true;
  try {
    let data = await getBarcodeByBusiness(props.bizType, props.bizId);
    if (!data?.content) {
      await createBarcode({
        bizCode: props.bizCode || '',
        bizId: props.bizId,
        bizName: props.bizName || '',
        bizType: props.bizType,
      });
      data = await getBarcodeByBusiness(props.bizType, props.bizId);
    }
    if (!data?.content) {
      message.warning('条码生成失败,无法打印标签');
      win.close();
      printWindow.value = null;
      return;
    }
    barcode.value = data;
    renderKey.value += 1;
    await nextTick();
  } catch {
    message.error('打印标签失败,请重试');
    win.close();
    printWindow.value = null;
  } finally {
    printing.value = false;
  }
}
</script>
<template>
  <Button type="link" @click="handlePrint">
  <Button :loading="printing" @click="handlePrint">
    {{ labelText }}
  </Button>
  <!-- 隐藏的条码渲染容器,渲染完成后取出图片写入打印窗口 -->
  <div
    v-if="barcode?.content"
    class="pointer-events-none fixed top-0 -left-[9999px] h-0 w-0 overflow-hidden"
  >
    <WlsBarcode
      :key="renderKey"
      ref="barcodeRef"
      :content="barcode.content"
      :format="barcode.format"
      :height="150"
      :width="280"
      @done="handleBarcodeDone"
    />
  </div>
</template>