【canvas】把数据对象转换成发票

355 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

在工作中遇到需要将后端传过来的对象用 canvas 画出发票

一、 封装工具函数

  1. 第一个参数是发票数据对象
  2. 第二个是 canvas 的dom元素,我们这里使用 document.getElementById('这里填那个canvas的id') 来获取
  • canvas.toDataURL 将画布转换成base64的数据格式
export drawMain = (data, el) => {
  var canvas = el;

  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');
    canvas.height = canvas.height;
    ctx.strokeRect(60, 17, 108, 108);//二维码
    ctx.font = "14px serif";
    ctx.fillText(data.kpjh, 125, 145);//机器编号
    ctx.fillText(data.fphm, 889 + 27, 56);//发票号码
    ctx.fillText(data.fpdm, 889 + 27, 85);//发票代码
    ctx.fillText(data.kprq, 889 + 27, 114);//开票日期
    if (data.jym) {
      ctx.fillText(data.jym.substring(0, 5) + ' ' + data.jym.substring(5, 10) + ' ' + data.jym.substring(10, 15) + ' ' + data.jym.substring(15, 20) + ' ' + data.jym.substring(20, 25), 808 + 27 + 81, 143);//效验码
    }

    ctx.fillText(data.gfmc, 38 + 43 + 5 + 115, 155 + 29 - 7);//名称
    ctx.fillText(data.gfsh, 38 + 43 + 5 + 115, 155 + 29 + 29 - 7);//纳税人识别号
    ctx.fillText(data.gfdzdh, 38 + 43 + 5 + 115, 155 + 29 + 29 + 29 - 7);//地 址、电 话
    ctx.fillText(data.gfyhzh, 38 + 43 + 5 + 115, 155 + 29 + 29 + 29 + 29 - 7);//开户行及账号


    ctx.fillText(data.xfmc, 38 + 43 + 5 + 115, 155 + 15 + 4 + 117 + 278 + 6);//名称
    ctx.fillText(data.xfsh, 38 + 43 + 5 + 115, 155 + 29 + 15 + 2 + 117 + 278 - 2 + 4);//纳税人识别号
    ctx.fillText(data.xfdzdh, 38 + 43 + 5 + 115, 155 + 29 + 29 + 15 + 2 + 117 + 278 - 4 + 2);//地 址、电 话
    ctx.fillText(data.xfyhzh, 38 + 43 + 5 + 115, 155 + 29 + 29 + 29 + 15 + 2 + 117 + 278 - 6);//开户行及账号

    if (data.bz) {
      ctx.fillText(data.bz.substring(0, 52), 38 + 43 + 570 + 28 + 5, 155 + 15 + 4 + 117 + 278 + 6);//备注第一行
      ctx.fillText(data.bz.substring(52, 104), 38 + 43 + 570 + 28 + 5, 155 + 29 + 15 + 2 + 117 + 278 - 2 + 4);//备注第二行
      ctx.fillText(data.bz.substring(104, 104 + 52), 38 + 43 + 570 + 28 + 5, 155 + 29 + 29 + 15 + 2 + 117 + 278 - 4 + 2);//备注第三行
      ctx.fillText(data.bz.substring(104 + 52, 208), 38 + 43 + 570 + 28 + 5, 155 + 29 + 29 + 29 + 15 + 2 + 117 + 278 - 6);//备注第四行
    }

    ctx.fillText('¥' + (parseFloat(data.hjje+'')+parseFloat(data.hjse+'')), 38 + 277 + 138 + 64 + 106 + 106 + 160 + 15 + 30, 155 + 117 + 278 - 15);//(小写)
    ctx.fillText(toChies(parseFloat(data.hjje+'')+parseFloat(data.hjse+'')), 38 + 136 + 136 + 15, 155 + 117 + 278 - 15);//价税合计(大写)
    ctx.fillText(data.skr, 38 + 43 + 5 + 35, 155 + 503 + 20);//收款人
    ctx.fillText(data.fhr, 38 + 80 + 277 + 35, 155 + 503 + 20);//复核
    ctx.fillText(data.kpr, 38 + 43 + 570 + 35, 155 + 503 + 20);//开票人

    if (data.qdbz == '1') {
      ctx.fillText('*详见销货清单*', 38 + 5, 155 + 117 + 40 + 2 + 4);//明细

      ctx.textAlign = "right";
      ctx.fillText('¥' + data.hjje, 38 + 277 + 138 + 64 + 106 + 106 + 160 - 5, 155 + 117 + 40 + 2 + 4);//合计金额
      ctx.fillText('¥' + data.hjse, 38 + 1068 - 5, 155 + 117 + 40 + 2 + 4);//合计税额
    } else {
      for (let i = 0; i < data.xxfpMxList.length; i++) {
        let temp = data.xxfpMxList[i];
        ctx.textAlign = "start";
        ctx.fillText(temp.spmc, 38 + 5, 155 + 117 + 40 + (20 * i) + 2 + 2 * i + i + 4);

        ctx.textAlign = "right";
        ctx.fillText(temp.ggxh, 38 + 277 + 138 - 5, 155 + 117 + 40 + 2 + (20 * i) + 2 * i + i)//规格型号
        ctx.fillText(temp.jldw, 38 + 277 + 138 + 64 - 5, 155 + 117 + 40 + 2 + (20 * i) + 2 * i + i)//单位
        ctx.fillText(temp.sl, 38 + 277 + 138 + 64 + 106 - 5, 155 + 117 + 40 + 2 + (20 * i) + 2 * i + i)//数量
        ctx.fillText('¥' +temp.dj, 38 + 277 + 138 + 64 + 106 + 106 - 5, 155 + 117 + 40 + 2 + (20 * i) + 2 * i + i)//单价
        ctx.fillText('¥' +temp.je, 38 + 277 + 138 + 64 + 106 + 106 + 160 + -5, 155 + 117 + 40 + 2 + (20 * i) + 2 * i + i)//金额
        ctx.fillText(temp.slv*100+'%', 38 + 277 + 138 + 64 + 106 + 106 + 160 + 60 - 5, 155 + 117 + 40 + 2 + (20 * i) + 2 * i + i)//税率
        ctx.fillText('¥' + temp.se, 38 + 1068 - 5, 155 + 117 + 40 + 2 + (20 * i) + 2 * i + i)//税额
      }
    }

    ctx.textAlign = "right";
    ctx.fillText('¥' + data.hjje, 38 + 277 + 138 + 64 + 106 + 106 + 160 - 5, 155 + 117 + 278 - 50);//合计金额
    ctx.fillText('¥' + data.hjse, 38 + 1068 - 5, 155 + 117 + 278 - 50);//合计税额

    ctx.fillStyle = "#A66A4C";
    ctx.strokeStyle = "#A66A4C";

    let title='';
    if (
      data.fpzl == 'p'
    ) {
      title = '增值税电子普通发票'
    }
    if ( data.fpzl == 's') {
      title = '增值税专用发票'
    }
    if ( data.fpzl == 'c') {
      title = '增值税普通发票'
    }
    ctx.font = "48px serif";
    ctx.textAlign = "center";
    ctx.fillText(title, 571, 84);

    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('机器编号:', 44, 145);

    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('发票代码:', 808 + 27, 56);

    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('发票号码:', 808 + 27, 85);

    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('开票日期:', 808 + 27, 114);

    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('效 验 码:', 808 + 27, 143);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('购', 38 + 21, 155 + 29 + 5);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('买', 38 + 21, 155 + 29 + 29 + 5);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('方', 38 + 21, 155 + 29 + 29 + 29 + 5);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('销', 38 + 21, 155 + 29 + 5 + 117 + 278);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('售', 38 + 21, 155 + 29 + 29 + 5 + 117 + 278);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('方', 38 + 21, 155 + 29 + 29 + 29 + 5 + 117 + 278);

    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('名        称:', 38 + 43 + 5, 155 + 29 - 7);
    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('纳税人识别号:', 38 + 43 + 5, 155 + 29 + 29 - 7);
    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('地 址、电 话:', 38 + 43 + 5, 155 + 29 + 29 + 29 - 7);
    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('开户行及账号:', 38 + 43 + 5, 155 + 29 + 29 + 29 + 29 - 7);

    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('名        称:', 38 + 43 + 5, 155 + 15 + 4 + 117 + 278 + 6);
    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('纳税人识别号:', 38 + 43 + 5, 155 + 29 + 15 + 2 + 117 + 278 - 2 + 4);
    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('地 址、电 话:', 38 + 43 + 5, 155 + 29 + 29 + 15 + 2 + 117 + 278 - 4 + 2);
    ctx.font = "14px serif";
    ctx.textAlign = "start";
    ctx.fillText('开户行及账号:', 38 + 43 + 5, 155 + 29 + 29 + 29 + 15 + 2 + 117 + 278 - 6);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('货物或应税劳务、服务名称', 38 + 136, 155 + 117 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('合        计', 38 + 136, 155 + 117 + 278 - 50);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('(小写)', 38 + 277 + 138 + 64 + 106 + 106 + 160 + 15, 155 + 117 + 278 - 15);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('价税合计(大写)', 38 + 136, 155 + 117 + 278 - 15);

    ctx.fillText('收款人:', 38 + 43 + 5, 155 + 503 + 20);
    ctx.fillText('复核:', 38 + 80 + 277, 155 + 503 + 20);
    ctx.fillText('开票人:', 38 + 43 + 570, 155 + 503 + 20);
    ctx.fillText('销售方:(章)', 38 + 277 + 138 + 64 + 106 + 106 + 160 + 35, 155 + 503 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('规格型号', 38 + 277 + 69, 155 + 117 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('单位', 38 + 277 + 138 + 32, 155 + 117 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('数量', 38 + 277 + 138 + 64 + 53, 155 + 117 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('单价', 38 + 277 + 138 + 64 + 106 + 53, 155 + 117 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('金    额', 38 + 277 + 138 + 64 + 106 + 106 + 80, 155 + 117 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('税率', 38 + 277 + 138 + 64 + 106 + 106 + 160 + 30, 155 + 117 + 20);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('税    额', 38 + 277 + 138 + 64 + 106 + 106 + 160 + 60 + 80, 155 + 117 + 20);


    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('密', 38 + 43 + 570 + 14, 155 + 29 + 5);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('码', 38 + 43 + 570 + 14, 155 + 29 + 29 + 5);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('区', 38 + 43 + 570 + 14, 155 + 29 + 29 + 29 + 5);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('备', 38 + 43 + 570 + 14, 155 + 278 + 117 + 40);

    ctx.font = "14px serif";
    ctx.textAlign = "center";
    ctx.fillText('注', 38 + 43 + 570 + 14, 155 + 278 + 117 + 27 + 55);

    ctx.beginPath();
    ctx.moveTo(373, 97);
    ctx.lineTo(769, 97);
    ctx.closePath();
    ctx.stroke();//标题线条

    ctx.beginPath();
    ctx.moveTo(373, 101);
    ctx.lineTo(769, 101);
    ctx.closePath();
    ctx.stroke();//标题线条

    ctx.strokeRect(38, 155, 1068, 503);//发票体外框

    ctx.beginPath();

    ctx.moveTo(38 + 43, 155);
    ctx.lineTo(38 + 43, 155 + 117);//购买方右边框

    ctx.moveTo(38 + 43 + 570, 155);
    ctx.lineTo(38 + 43 + 570, 155 + 117);//密码区左边框

    ctx.moveTo(38 + 43 + 570 + 28, 155);
    ctx.lineTo(38 + 43 + 570 + 28, 155 + 117);//密码区右边框

    ctx.moveTo(38, 155 + 117);
    ctx.lineTo(38 + 1068, 155 + 117); //明细上边框

    ctx.moveTo(38, 155 + 117 + 236);
    ctx.lineTo(38 + 1068, 155 + 117 + 236);//明细下边框

    ctx.moveTo(38, 155 + 117 + 278);
    ctx.lineTo(38 + 1068, 155 + 117 + 236 + 42);//价税合计下边框

    ctx.moveTo(38 + 277, 155 + 117);
    ctx.lineTo(38 + 277, 155 + 117 + 278);//商品名右边框

    ctx.moveTo(38 + 277 + 138, 155 + 117);
    ctx.lineTo(38 + 277 + 138, 155 + 117 + 236);//规格型号右边框

    ctx.moveTo(38 + 277 + 138 + 64, 155 + 117);
    ctx.lineTo(38 + 277 + 138 + 64, 155 + 117 + 236); //单位右边框

    ctx.moveTo(38 + 277 + 138 + 64 + 106, 155 + 117);
    ctx.lineTo(38 + 277 + 138 + 64 + 106, 155 + 117 + 236);//数量右边框

    ctx.moveTo(38 + 277 + 138 + 64 + 106 + 106, 155 + 117);
    ctx.lineTo(38 + 277 + 138 + 64 + 106 + 106, 155 + 117 + 236);//单价右边框

    ctx.moveTo(38 + 277 + 138 + 64 + 106 + 106 + 160, 155 + 117);
    ctx.lineTo(38 + 277 + 138 + 64 + 106 + 106 + 160, 155 + 117 + 236);//金额右边框


    ctx.moveTo(38 + 277 + 138 + 64 + 106 + 106 + 160 + 60, 155 + 117);
    ctx.lineTo(38 + 277 + 138 + 64 + 106 + 106 + 160 + 60, 155 + 117 + 236);//税率右边框

    ctx.moveTo(38 + 43, 155 + 117 + 278);
    ctx.lineTo(38 + 43, 155 + 503);//销售方右边框

    ctx.moveTo(38 + 43 + 570, 155 + 117 + 278);
    ctx.lineTo(38 + 43 + 570, 155 + 503);//备注左边框

    ctx.moveTo(38 + 43 + 570 + 28, 155 + 278 + 117);
    ctx.lineTo(38 + 43 + 570 + 28, 155 + 503);//备注右边框

    ctx.stroke();
    // 画布 --> base64
    let url = canvas.toDataURL('image/jpg')
    
    /**这里填入不同的代码片段,使函数达到不同的效果
    
    */    
  }
}

// 转大写
let toChies =function(amount) { //形参
  // 汉字的数字
  const cnNums = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
  // 基本单位
  const cnIntRadice = ["", "拾", "佰", "仟"];
  // 对应整数部分扩展单位
  const cnIntUnits = ["", "万", "亿", "兆"];
  // 对应小数部分单位
  const cnDecUnits = ["角", "分"];
  // 整数金额时后面跟的字符
  const cnInteger = "整";
  // 整型完以后的单位
  const cnIntLast = "元";
  // 最大处理的数字
  const maxNum = 9999999999999999.99;
  // 金额整数部分
  let integerNum;
  // 金额小数部分
  let decimalNum;
  // 输出的中文金额字符串
  let chineseStr = "";
  // 分离金额后用的数组,预定义
  let parts;
  if (amount === "") {
    return "";
  }
  amount = parseFloat(amount);
  if (amount >= maxNum) {
    // 超出最大处理数字
    return "";
  }
  if (amount === 0) {
    chineseStr = cnNums[0] + cnIntLast + cnInteger;
    return chineseStr;
  }
  // 转换为字符串
  amount = amount.toString();
  if (amount.indexOf(".") === -1) {
    integerNum = amount;

    decimalNum = "";
  } else {
    parts = amount.split(".");
    integerNum = parts[0];
    decimalNum = parts[1].substr(0, 4);
  }
  // 获取整型部分转换
  if (parseInt(integerNum, 10) > 0) {
    let zeroCount = 0;
    const IntLen = integerNum.length;
    for (let i = 0; i < IntLen; i++) {
      const n = integerNum.substr(i, 1);
      const p = IntLen - i - 1;
      const q = p / 4;
      const m = p % 4;
      if (n === "0") {
        zeroCount++;
      } else {
        if (zeroCount > 0) {
          chineseStr += cnNums[0];
        }
        // 归零
        zeroCount = 0;
        //alert(cnNums[parseInt(n)])
        chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
      }
      if (m === 0 && zeroCount < 4) {
        chineseStr += cnIntUnits[q];
      }
    }
    chineseStr += cnIntLast;
  }
  // 小数部分
  if (decimalNum !== "") {
    const decLen = decimalNum.length;
    for (let i = 0; i < decLen; i++) {
      const n = decimalNum.substr(i, 1);
      if (n !== "0") {
        chineseStr += cnNums[Number(n)] + cnDecUnits[i];
      }
    }
  }
  if (chineseStr === "") {
    chineseStr += cnNums[0] + cnIntLast + cnInteger;
  } else if (decimalNum === "") {
    chineseStr += cnInteger;
  }
  return chineseStr;
}

1. 代码片段一

这个片段实现的是点击按钮,将canvas画好的发票下载到本地

    let a = document.createElement('a')
    let event = new MouseEvent('click')
    a.download = data.fphm + '-' + data.fpdm
    a.href = url
    a.dispatchEvent(event)

2. 代码片段二

这个片段实现的是点击按钮,将canvas画好的发票转成 file 可以发送

     // base64 --> file
    let arr = url.split(','),
        bstr = atob(arr[1]),
        mime = arr[0].match(/:(.*?);/)[1], //图片后缀
        n = bstr.length,
        u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    let blob = new Blob([u8arr], {type:mime })
    blob.lastModifiedDate = new Date()

二、使用

这里的画布要隐藏起来 style="display: none"

<button @click="downLoad"></button>
<div style="display: none">
      <canvas id="tutorial" width="1142" height="738"></canvas>
</div>
downLoad() {
    let objInvioce =   {
            "fpdm": "4433263500",
            "gfsh": "91330621769630618N",
            "hjse": 734.50,
            "xxfpMxList": [
                {
                    "ggxh": "物料开票规格",
                    "se": 146.90,
                    "jldw": "KG",
                    "dj": 100.00000000,
                    "sl": 10.00000000,
                    "je": 1000.00,
                    "slv": 0.13
                },
                {
                    "ggxh": "物料开票规格",
                    "se": 146.90,
                    "jldw": "KG",
                    "dj": 100.00000000,
                    "sl": 10.00000000,
                    "je": 1000.00,
                    "slv": 0.13
                }   "slv": 0.13
            ],
            "gfmc": "成都旭光电子股份有限公司",
            "gfyhzh": "中国工商银行股份有限公司成都马超西路支行 440294300902216611",
            "kpr": "五",
            "fpzl": "s",
            "hjje": 5000.00,
            "xfyhzh": "1 11",
            "kpjh": "00",
            "fhr": "四",
            "kprq": "2022-06-27 15:41:59",
            "gfdzdh": "成都市新都区新都镇新工大道318号 028-83967159 028-83967159",
            "bz": null,
            "xfdzdh": "1 13905719774",
            "id": 180,
            "xfsh": "test",
            "skr": "三",
            "xfmc": "1",
            "fphm": "09395297",
            "qdbz": "0"
        },
}

drawMain(objInvioce,document.getElementById('tutorial'))