src/views/mes/workbench/components/tabs/ProductIssueTab.vue
@@ -22,10 +22,13 @@
  cancelProductIssue,
  createProductIssue,
  deleteProductIssue,
  getProductIssue,
  getProductIssuePage,
  submitProductIssue,
  updateProductIssue,
} from '#/api/mes/wm/productissue';
import { getProductIssueLinesByIssue } from '#/api/mes/wm/productissue/line';
import dayjs from 'dayjs';
import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
@@ -240,7 +243,7 @@
      props: { type: DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS },
    },
  },
  { title: '操作', width: 260, fixed: 'right', slots: { default: 'actions' } },
  { title: '操作', width: 360, fixed: 'right', slots: { default: 'actions' } },
];
const gridHeight = ref(300);
@@ -304,6 +307,52 @@
/** 是否已选中工作站和任务,未选中时不展示表格 */
const isReady = computed(() => !!props.taskId && !!props.workstationId);
/** 单据内容全部按文本转义,避免物料名称或备注影响打印页面结构。 */
function escapePrintText(value: unknown) {
  return String(value ?? '').replace(/[&<>"']/g, (char) => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
  })[char] || char);
}
async function handlePrintIssue(row: MesWmProductIssueApi.ProductIssue) {
  if (!row.id) return;
  // 先打开窗口,避免异步请求结束后被浏览器作为弹窗拦截。
  const popup = window.open('', '_blank');
  if (!popup) { message.warning('浏览器拦截了打印窗口,请允许弹窗后重试'); return; }
  popup.document.title = '正在准备领料单';
  popup.document.body.textContent = '正在准备领料单…';
  try {
    const [issue, lines] = await Promise.all([getProductIssue(row.id), getProductIssueLinesByIssue(row.id)]);
    if (!issue) throw new Error('领料单不存在');
    const safe = escapePrintText;
    const date = (value: unknown) => value && dayjs(value as string | number | Date).isValid()
      ? dayjs(value as string | number | Date).format('YYYY-MM-DD HH:mm') : '';
    const statusNames: Record<number, string> = {
      [MesWmProductIssueStatusEnum.PREPARE]: '草稿',
      [MesWmProductIssueStatusEnum.APPROVING]: '待拣货',
      [MesWmProductIssueStatusEnum.APPROVED]: '待领出',
      [MesWmProductIssueStatusEnum.FINISHED]: '已完成',
      [MesWmProductIssueStatusEnum.CANCELED]: '已取消',
    };
    const rows = lines.map((item, index) => `<tr><td>${index + 1}</td><td>${safe(item.itemCode)}</td><td>${safe(item.itemName)}</td><td>${safe(item.specification)}</td><td>${safe(item.unitMeasureName)}</td><td>${safe(item.quantity)}</td><td>${safe(item.remark)}</td></tr>`).join('');
    popup.document.open();
    popup.document.write(`<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><title>领料单 ${safe(issue.code)}</title><style>
      @page { size: A4; margin: 15mm; } body { color: #111; font: 13px 'Microsoft YaHei', sans-serif; margin: 0 auto; max-width: 180mm; }
      h1 { text-align: center; font-size: 22px; margin: 12px 0 20px; } .meta { display: grid; grid-template-columns: 1fr 1fr; gap: 9px 16px; margin-bottom: 16px; overflow-wrap: anywhere; }
      table { border-collapse: collapse; width: 100%; table-layout: fixed; } th,td { border: 1px solid #555; padding: 7px 4px; word-break: break-word; text-align: left; } th:first-child,td:first-child { width: 32px; text-align: center; } th:nth-child(5),td:nth-child(5) { width: 42px; } th:nth-child(6),td:nth-child(6) { width: 56px; } thead { display: table-header-group; } tr { break-inside: avoid; }
      .sign { display: flex; justify-content: space-between; gap: 18px; margin-top: 32px; } .toolbar { text-align: right; padding: 12px 0; } button { padding: 7px 14px; cursor: pointer; } @media print { .toolbar { display: none; } }
    </style></head><body><div class="toolbar"><button onclick="window.print()">打印 / 保存为 PDF</button></div><h1>领料单</h1>
      <div class="meta"><div>领料单号:${safe(issue.code)}</div><div>领料单名称:${safe(issue.name)}</div><div>生产任务:${safe(issue.taskCode)}</div><div>生产订单:${safe(issue.workOrderCode)}</div><div>工作站:${safe(issue.workstationName || issue.workstationCode)}</div><div>需求时间:${safe(date(issue.requiredTime))}</div><div>状态:${safe(statusNames[issue.status ?? -1] ?? '未知')}</div><div>备注:${safe(issue.remark)}</div></div>
      <table><thead><tr><th>序号</th><th>物料编码</th><th>物料名称</th><th>规格型号</th><th>单位</th><th>申请数量</th><th>备注</th></tr></thead><tbody>${rows || '<tr><td colspan="7">暂无领料明细</td></tr>'}</tbody></table>
      <div class="sign"><span>申请人:____________</span><span>发料人:____________</span><span>领料人:____________</span><span>日期:____________</span></div></body></html>`);
    popup.document.close();
  } catch (error) {
    popup.close();
    message.error('无法生成领料单,请稍后重试');
    console.error(error);
  }
}
/** 编辑弹窗 */
const [EditFormModal, editFormModalApi] = useVbenModal({
  connectedComponent: ProductIssueFormModal,
@@ -361,6 +410,7 @@
      <div ref="listRef" class="issue-tab__list">
        <Grid table-title="领料记录">
          <template #actions="{ row }">
            <Button type="link" size="small" @click="handlePrintIssue(row)">打印 / 导出 PDF</Button>
            <Button
              v-if="row.status === MesWmProductIssueStatusEnum.PREPARE"
              type="link"