工具类代码

import QRCode from 'qrcode';
import JSZip from 'jszip';
import dayjs from 'dayjs';

class MaterialCodeGenerator {
  constructor() {
    this.loading = false;
  }

  /**
   * 生成物料码
   *
   * @param {Array} materials 物料数据
   * @param {Array} customFields 自定义字段
   */
  async generateQRCode(materials, customFields) {
    if (materials.length === 0) {
      throw new Error('请先生成物料数据');
    }
    const arr = [];
    this.loading = true;
    materials.forEach(async (materialInfo, index) => {
      arr.push(
        this.drawMaterialCode(
          await QRCode.toDataURL(materialInfo.supplierCode),
          { ...materialInfo, now: this.formatDateTime(dayjs()) },
          customFields
        )
      );
      if (index === materials.length - 1) {
        Promise.all(arr).then(async (res) => {
          await this.packageAndDownload(res);
          this.loading = false;
        });
      }
    });
  }

  /**
   * 绘制物料码
   *
   * @param {string} qrCodeData 二维码数据
   * @param {Object} materialInfo 物料信息
   * @param {Array} customFields 自定义字段
   * @returns {Promise} 返回绘制好的二维码图片
   */
  async drawMaterialCode(qrCodeData, materialInfo, customFields) {
    const fieldHeight = 30; // 每个字段的高度
    const baseHeight = 40; // 标题高度
    const margin = 30; // 底部边缘留白
    const canvasHeight = baseHeight + customFields.length * fieldHeight + margin;
    return new Promise((resolve) => {
      const canvas = document.createElement('canvas');
      canvas.width = 400; // 增加宽度
      canvas.height = canvasHeight; // 根据字段数量动态计算高度
      const ctx = canvas.getContext('2d');
      ctx.fillStyle = '#FFFFFF';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = '#000';
      ctx.font = 'bold 24px Arial';
      ctx.textAlign = 'center';
      ctx.fillText('产品二维码', canvas.width / 2, 40);
      const qrImg = new Image();
      qrImg.src = qrCodeData;
      qrImg.onload = () => {
        ctx.drawImage(qrImg, canvas.width - 120, canvas.height / 2 - 60, 120, 120);
        ctx.font = '18px Arial';
        ctx.textAlign = 'start';
        let yPos = 80; // 初始文本位置
        customFields.forEach((field) => {
          ctx.fillText${field.label}: ${materialInfo[field.value]}, 20, yPos);
          yPos += fieldHeight;
        });
        ctx.strokeStyle = '#000000';
        ctx.lineWidth = 1;
        ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2);
        const imageURL = canvas.toDataURL('image/png');
        resolve({ url: imageURL, ...materialInfo });
      };
    });
  }

  /**
   * 打包并下载物料码
   *
   * @param {Array} res 物料码数据
   */
  async packageAndDownload(res) {
    const zip = new JSZip();
    res.forEach(({ url, id }) => {
      zip.file${id}.png, url.split('base64,')[1], { base64: true });
    });
    zip.generateAsync({ type: 'blob' }).then((content) => {
      const a = document.createElement('a');
      const url = URL.createObjectURL(content);
      a.href = url;
      a.download = '产品物料码.zip';
      a.click();
      URL.revokeObjectURL(url);
    });
  }

  /**
   * 格式化日期时间
   *
   * @param {Date} date 日期时间
   * @returns {string} 格式化后的日期时间
   */
  formatDateTime(date) {
    return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
  }
}

export default MaterialCodeGenerator;

调用生成物料实例

const customFields = [
  {
    label: '产品名称',
    value: 'productName'
  },
  {
    label: '打印日期',
    value: 'now'
  },
  {
    label: '颜色',
    value: 'color'
  },
  {
    label: '编号',
    value: 'colorCode'
  },
  {
    label: '数量',
    value: 'quantity'
  },
  {
    label: '供应商编码',
    value: 'supplierCode'
  },
  {
    label: '批次人',
    value: 'printPerson'
  }
]

const materialCodeGenerator = new MaterialCodeGenerator()

materialCodeGenerator
  .generateQRCode(this.materials, customFields)
  .then(() => {
    message.success('物料码生成成功')
  })
  .catch((error) => {
    message.error(`物料码生成失败: ${error.message}`)
  })

完整代码

<template>
  <div class="container">
    <div class="buttons">
      <a-button @click="generateRandomMaterials">生成随机物料数据</a-button>
      <a-button @click="generateQRCode" :loading="loading">生成物料码</a-button>
    </div>
    <a-table :columns="columns" :dataSource="materials" :rowKey="(record) => record.id"> </a-table>
  </div>
</template>

<script>
import dayjs from 'dayjs'
import { Button, message, Table } from 'ant-design-vue'
import MaterialCodeGenerator from './utils/index'
class SequentialIdGenerator {
  constructor(start = 1000000000) {
    if (start < 1000000000 || start > 9999999999) {
      throw new Error('Start ID must be a 10-digit number.')
    }
    this.currentId = start
  }

  generateId() {
    if (this.currentId > 9999999999) {
      throw new Error('Maximum ID limit reached.')
    }
    return this.currentId++
  }
}

const idGenerator = new SequentialIdGenerator() // 默认从1000000000开始

export default {
  components: {
    AButton: Button,
    ATable: Table
  },
  data() {
    return {
      loading: false,
      materials: [],
      columns: [
        {
          title: 'ID',
          dataIndex: 'id',
          key: 'id'
        },
        {
          title: '产品名称',
          dataIndex: 'productName',
          key: 'productName'
        },
        {
          title: '颜色',
          dataIndex: 'color',
          key: 'color'
        },
        {
          title: '编号',
          dataIndex: 'colorCode',
          key: 'colorCode'
        },
        {
          title: '数量',
          dataIndex: 'quantity',
          key: 'quantity'
        },
        {
          title: '供应商编码',
          dataIndex: 'supplierCode',
          key: 'supplierCode'
        },
        {
          title: '批次人',
          dataIndex: 'printPerson',
          key: 'printPerson'
        }
      ]
    }
  },

  methods: {
    /**
     * 生成物料码
     *
     * @returns {Promise}
     */
    async generateQRCode() {
      if (this.materials.length === 0) {
        message.error('请先生成物料数据')
        return
      }

      const customFields = [
        {
          label: '产品名称',
          value: 'productName'
        },
        {
          label: '打印日期',
          value: 'now'
        },
        {
          label: '颜色',
          value: 'color'
        },
        {
          label: '编号',
          value: 'colorCode'
        },
        {
          label: '数量',
          value: 'quantity'
        },
        {
          label: '供应商编码',
          value: 'supplierCode'
        },
        {
          label: '批次人',
          value: 'printPerson'
        }
      ]

      const materialCodeGenerator = new MaterialCodeGenerator()
      materialCodeGenerator
        .generateQRCode(this.materials, customFields)
        .then(() => {
          message.success('物料码生成成功')
        })
        .catch((error) => {
          message.error(`物料码生成失败: ${error.message}`)
        })
    },

    /**

     * 生成随机物料数据

     */

    generateRandomMaterials() {
      const numItems = 50 // 生成50条数据
      const productNames = ['氨纶方格布1', '氨纶方格布2', '尼龙布', '棉麻混纺', '涤纶布']
      const colors = ['白色', '黑色', '红色', '蓝色', '绿色']
      const supplierCodes = ['20102939', '20103948', '20104857', '20105766', '20106675']
      const persons = ['小林', '张三', '李四', '王五', '赵六']
      this.materials = []

      for (let i = 0; i < numItems; i++) {
        const material = {
          id: this.generateId(), // 增加唯一id以便用作key
          productName: productNames[Math.floor(Math.random() * productNames.length)],
          color: colors[Math.floor(Math.random() * colors.length)],
          colorCode: `T00${Math.floor(Math.random() * 1000) + 900}A`,
          quantity: Math.floor(Math.random() * 1000) + 1,
          supplierCode: supplierCodes[Math.floor(Math.random() * supplierCodes.length)],
          printPerson: persons[Math.floor(Math.random() * persons.length)]
        }
        this.materials.push(material)
      }
    },

    /**
     * 格式化日期时间
     *
     * @param {Date} date 日期时间
     * @returns {string} 格式化后的日期时间
     */
    formatDateTime(date) {
      return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
    },

    /**
     * 生成唯一ID
     */
    generateId() {
      return idGenerator.generateId()
    }
  }
}
</script>

<style>
.container {
  padding: 20px;
  width: 800px;
  height: 600px;
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  .buttons {
    display: flex;
    gap: 10px;
  }
}
</style>