• 【Vue】Pdf转图片功能+多张图片拼接封装


    Pdf转图片功能+多张图片拼接封装

    HTML页面

    <template>
      <div class="main-marge">
        <div class="box">
          <van-uploader accept="image/*,.pdf" :before-read="(file) => beforeRead(file, '-1')">
            <div class="upImg">
              <img src="./assets/upload.png" />
              <span>请上传文件第{{ imglength }}</span>
            </div>
          </van-uploader>
          <div class="upImgtip">单文件大小不超过2MB,格式仅限PNGJPGJPEGPDF</div>
          <div class="imglist">
            <div class="title">上传材料</div>
            <div class="listform" v-if="filelist.length">
              <div class="listbar" v-for="(item, index) in filelist" :key="index">
                <div class="before">
                  <img :src="item.url" />
                  <div class="cont">{{ item.name }}</div>
                </div>
                <div class="bargroup">
                  <span @click="delImg(index)">删除</span>
                  <van-uploader accept="image/*,.pdf" :before-read="(file) => beforeRead(file, index)">
                    <span>重新上传</span>
                  </van-uploader>
                </div>
              </div>
            </div>
            <div class="nolist" v-else>
              <img src="./assets/nolist.png" />
              <span>暂未上传材料,请上传</span>
            </div>
          </div>
        </div>
        <div class="btngroup">
          <div class="cancle" @click="cancle">取消</div>
          <div class="confirm" @click="mergeImg">确定</div>
        </div>
        <!-- pdf绘制区域 -->
        <div class="canvasPDF">
          <div>
            <canvas
              id="canvas"
              :style="{border: '1px solid #eeeeee' }"
            ></canvas>
          </div>
          <div class="pdfbar" v-for="(item, i) in imgFiles" :key="i">
            <canvas :id="`pdf_canvas_${item}`" style="border: 1px solid #eeeeee"></canvas>
          </div>
        </div>
        <!-- 图片绘制合并区域 -->
        <div class="canvasPDF">
          <canvas
            id="myCanvas"
            :style="{ border: '1px solid #eeeeee' }"
          ></canvas>
        </div>
      </div>
    </template>
    
    <script>
    import { getPdfnum, PdfToImg, MeargeImg } from '../../utils/tools.js';
    export default {
      data() {
        return {
          // pdf
          newPrototype: [],
          newPrototypeValue: [],
          imgFiles: [], //pdf页数列表
          filelist: [], //列表
          //img
          realWidth: 720,
          dpr: '',
          loading: false,
        };
      },
      computed: {
        imglength() {
          return this.filelist.length + 1;
        },
      },
      created() {
      	//此功能是为了pdf.js内部,有时候会报for....in的错误,原因是原型方法被其他地方改变,这里需要改回来
        for (let key in Array.prototype) {
          if (!Array.prototype.hasOwnProperty(key)) continue;
          this.newPrototype.push(key);
        }
        // 存放原始键 原始方法
        this.newPrototypeValue = this.newPrototype.map((v) => ({ [v]: Array.prototype[v] }));
        // 删除直接属性
        this.newPrototype.forEach((v) => delete Array.prototype[v]);
      },
      beforeDestroy() {
        if (Array.isArray(this.newPrototypeValue) && this.newPrototypeValue.length > 0) {
          for (const key in this.newPrototypeValue) {
            const method = this.newPrototypeValue[key];
            // 确保该属性是函数(即方法)
            if (typeof method === 'function') {
              // 将方法重新赋值到Array.prototype上
              Array.prototype[key] = method;
            }
          }
        }
      },
      methods: {
        //pdf转图片
        async beforeRead(file, fileindex) {
          if (!file) {
            return false;
          }
          let loading = this.$Toast.loading({
            message: '加载中...',
            forbidClick: true,
            duration: 0,
          });
          try {
            this.imgFiles = await getPdfnum(file);
            const res = await PdfToImg(file);
            if (fileindex === '-1') {
              this.filelist.push(res);
            } else {
              this.filelist.splice(fileindex, 1, res);
            }
            loading.clear();
          } catch (error) {
            loading.clear();
            this.$confirm({
              title: '提示',
              message: error,
              showCancelButton: false,
              closeOnClickModal: false,
            });
          }
        },
        //合并图片
        async mergeImg() {
          let loading = this.$Toast.loading({
            message: '加载中...',
            forbidClick: true,
            duration: 0,
          });
          try {
            const res = await MeargeImg(this.filelist, this.realWidth);
            this.$emit('confirm-Merge', res.data);
            loading.clear();
          } catch (error) {
            loading.clear();
            this.$confirm({
              title: '提示',
              message: error,
              showCancelButton: false,
              closeOnClickModal: false,
            });
          }
        },
        //删除所选照片
        delImg(index) {
          this.filelist.splice(index, 1);
        },
        //关闭弹窗
        cancle() {
          this.$emit('hidden-cancle');
        },
      },
    };
    </script>
    
    <style scoped lang="scss">
    .main-marge {
      position: fixed;
      top: 0;
      left: 0;
      z-index: 99;
      height: 100vh;
      width: 100%;
      background: #fff;
      .tip {
        display: flex;
        padding: 9px 9px 9px 12px;
        background: rgba(253, 171, 77, 0.1);
        img {
          margin-right: 8px;
          width: 16px;
          height: 16px;
        }
        p {
          flex: 1;
          color: #fc7a43;
          font-family: 'PingFang SC';
          font-size: 12px;
          font-style: normal;
          font-weight: 400;
          line-height: 22px; /* 157.143% */
        }
      }
      .box {
        padding: 0 16px;
      }
      .upImg {
        margin-top: 16px;
        display: flex;
        padding: 12px 0px;
        flex-direction: column;
        align-items: center;
        border-radius: 8px;
        border: 1px dashed rgba(203, 180, 134, 0.3);
        background: rgba(203, 180, 134, 0.06);
        color: #b8926b;
        text-align: center;
        font-family: 'PingFang SC';
        font-size: 14px;
        font-style: normal;
        font-weight: 400;
        line-height: 22px; /* 157.143% */
        span {
          margin-top: 8px;
        }
      }
      .upImgtip {
        margin-top: 8px;
        color: rgba(0, 0, 0, 0.4);
        font-family: 'PingFang SC';
        font-size: 12px;
        font-style: normal;
        font-weight: 400;
        line-height: 20px; /* 166.667% */
      }
      .imglist {
        margin-top: 28px;
        .title {
          color: #000;
          font-family: 'PingFang SC';
          font-size: 18px;
          font-style: normal;
          font-weight: 500;
          line-height: 26px; /* 144.444% */
        }
        .listform {
          .listbar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            border-bottom: 1px solid #e5e5e5;
            padding: 12px 0;
            .before {
              display: flex;
              align-items: center;
              img {
                margin-right: 20px;
                width: 48px;
                height: 48px;
                border-radius: 6px;
              }
              .cont {
                width: 175px;
                color: rgba(0, 0, 0, 0.8);
                font-family: 'PingFang SC';
                font-size: 16px;
                font-style: normal;
                font-weight: 500;
                line-height: 24px; /* 150% */
              }
            }
            .bargroup {
              width: 100px;
              color: #cbb486;
              text-align: center;
              font-family: 'PingFang SC';
              font-size: 14px;
              font-style: normal;
              font-weight: 400;
              line-height: 22px; /* 157.143% */
            }
            &:last-child {
              border-bottom: none;
            }
          }
        }
        .nolist {
          margin-top: 24px;
          display: flex;
          flex-direction: column;
          align-items: center;
          img {
            width: 160px;
            height: 160px;
          }
          span {
            margin-top: 16px;
            color: rgba(0, 0, 0, 0.6);
            text-align: center;
            font-family: 'PingFang SC';
            font-size: 14px;
            font-style: normal;
            font-weight: 400;
            line-height: normal;
          }
        }
      }
      .btngroup {
        position: fixed;
        z-index: 2;
        bottom: 0;
        left: 0;
        width: 100%;
        padding: 12px 0;
        display: flex;
        align-items: center;
        justify-content: space-around;
        background: #fff;
        box-shadow: 0px -2px 8px 0px rgba(191, 191, 191, 0.15), 0px -2px 8px 0px rgba(191, 191, 191, 0.15);
        .cancle {
          width: 160px;
          padding: 11px 16px;
          border-radius: 8px;
          border: 1px solid #e5e5e5;
          background: #fff;
          color: rgba(0, 0, 0, 0.6);
          font-family: 'PingFang SC';
          font-size: 16px;
          font-style: normal;
          font-weight: 400;
          line-height: 26px; /* 144.444% */
          text-align: center;
        }
        .confirm {
          width: 160px;
          padding: 11px 16px;
          border-radius: 8px;
          background: linear-gradient(135deg, #e4c995 0%, #b9916a 100%);
          color: #fff;
          font-family: 'PingFang SC';
          font-size: 16px;
          font-style: normal;
          font-weight: 400;
          line-height: 26px; /* 144.444% */
          text-align: center;
        }
      }
      .canvasPDF {
        position: fixed;
        top: 0;
        left: -9999px;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        .pdfbar {
          position: relative;
          margin-top: 10px;
          z-index: 1;
        }
      }
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353

    tools.js文件

    import * as pdfjs from 'pdfjs-dist';
    import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
    /**
     * pdf转图片获取pdf页数,用于渲染页面
     * @param {file} file - 文件
     */
    export function getPdfnum(file) {
      return new Promise((resolve, reject) => {
        const imgFiles = [];
        if (file.type === 'application/pdf') {
          let reader = new FileReader();
          reader.readAsDataURL(file); //将文件读取为 DataURL
          reader.onload = function () {
            //文件读取成功完成时触发
            const loadingTask = pdfjs.getDocument(reader.result);
            loadingTask.promise.then(async (pdf) => {
              const pageNum = pdf.numPages;
              if (pageNum > 10) {
                reject('此pdf页数过多,请线下合并');
              }
              //准备图片
              for (let i = 1; i <= pageNum; i++) {
                imgFiles.push(i);
              }
              resolve(imgFiles);
            });
          };
          reader.onerror = function () {
            // 如果文件读取失败,拒绝Promise
            reject(new Error('Failed to read the file'));
          };
        } else {
          resolve([]);
        }
      });
    }
    //pdf转图片
    export function PdfToImg(file) {
      return new Promise((resolve, reject) => {
        const newimgList = [];
        const fileName = file.name.substring(0, file.name.lastIndexOf('.'));
        if (file.type === 'application/pdf') {
          let reader = new FileReader();
          reader.readAsDataURL(file); //将文件读取为 DataURL
          reader.onload = function () {
            //文件读取成功完成时触发
            const loadingTask = pdfjs.getDocument(reader.result);
            loadingTask.promise.then(async (pdf) => {
              let pageNum = pdf.numPages;
              // 处理
              for (let i = 1; i <= pageNum; i++) {
                let canvasItem = '';
                pdf.getPage(i).then(async (page) => {
                  const canvas = document.getElementById('pdf_canvas_' + i);
                  const ctx = canvas.getContext('2d');
                  const viewport = page.getViewport({ scale: 4 });
                  canvas.height = viewport.height;
                  canvas.width = viewport.width;
                  const destWidth = 298;
                  const destheight = destWidth * (viewport.height / viewport.width);
                  canvas.style.width = destWidth + 'px';
                  canvas.style.height = destWidth * (viewport.height / viewport.width) + 'px';
                  newimgList.push(canvas);
                  await page.render({ canvasContext: ctx, viewport });
                  // 使用file对象进行后续操作
                  if (i === pageNum) {
                    setTimeout(async () => {
                      const res = await savePdfImage(newimgList, destWidth, destheight, fileName);
                      resolve(res);
                    }, 500);
                  }
                });
              }
            });
          };
          reader.onerror = function () {
            // 如果文件读取失败,拒绝Promise
            reject(new Error('Failed to read the file'));
          };
        } else {
          const reader = new FileReader();
          reader.onload = function (e) {
            let pngData = e.target.result;
            let obj = {
              url: pngData,
              name: fileName,
            };
            resolve(obj);
          };
          // 开始读取文件
          reader.readAsDataURL(file);
        }
      });
    }
    /**
     * pdf保存图片
     * @param {Array} newimgList - canvas列表
     * @param {Number} x - 页面展示宽
     * @param {Number} y - 页面展示高
     * @param {String}fileName - 文件名称
     */
    function savePdfImage(newimgList, x, y, fileName) {
      return new Promise((resolve, reject) => {
        let allcanvas = document.getElementById('canvas');
        let allctx = allcanvas.getContext('2d');
        allcanvas.style.width = x;
        allcanvas.style.height = y * newimgList.length;
        const subCanvasWidth = newimgList[0].width;
        const subCanvasHeight = newimgList[0].height;
        allcanvas.width = subCanvasWidth;
        allcanvas.height = subCanvasHeight * newimgList.length;
        newimgList.forEach((subCanvas, index) => {
          // 计算当前图片在Canvas中的垂直位置
          const yPosition = index * subCanvasHeight;
          // 绘制图片
          // 使用drawImage的四个参数版本来指定绘制的源图像区域和目标区域
          allctx.drawImage(subCanvas, 0, yPosition, subCanvasWidth, subCanvasHeight);
          if (index === newimgList.length - 1) {
            let pngData = allcanvas.toDataURL('image/png');
            let obj = {
              url: pngData,
              name: fileName,
            };
            resolve(obj);
          }
        });
      });
    }
    /**
     * 合并图片
     * @param {file} filelist - 文件列表
     * @param {Number} realWidth - 合并后的文件宽度
     *
     */
    export function MeargeImg(filelist, realWidth) {
      return new Promise(async (resolve, reject) => {
        if (!filelist.length) {
          reject('材料不能为空,请检查');
        }
        let canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        let x = 0;
        let y = 0;
        //获取图片信息
        const dpr = window.devicePixelRatio;
        const res = await getImginfo(filelist, realWidth);
        //展示高度
        canvas.style.width = realWidth;
        canvas.style.height = res;
        canvas.width = realWidth * dpr;
        canvas.height = res * dpr;
        for (let i = 0; i < filelist.length; i++) {
          const img = new Image();
          img.src = filelist[i].url;
          img.crossOrigin = 'anonymous';
          img.onload = async () => {
            let { imgWidth, imgHeight } = await setPosition(x, y, img, realWidth);
            //获取比例
            ctx.drawImage(img, x, y, imgWidth, imgHeight);
            y += imgHeight;
            if (i === filelist.length - 1) {
              // 下载图片
              let pngData = canvas.toDataURL('image/png');
              let filename = filelist[0].name + '.png' || '合并图片.png';
              let fileimg = await base64ToFile(pngData, filename);
              let obj = {
                target: {
                  files: [fileimg],
                },
              };
              resolve({ data: obj });
            }
          };
        }
        ctx.scale(dpr, dpr);
      });
    }
    //获取实际位置,宽高
    function setPosition(x, y, img, realWidth) {
      return new Promise((resolve, reject) => {
        const scaledpr = img.width / realWidth;
        let imgWidth = img.width;
        let imgHeight = img.height;
        if (scaledpr > 1) {
          imgWidth = img.width / scaledpr;
          imgHeight = img.height / scaledpr;
        }
        resolve({
          imgWidth: imgWidth,
          imgHeight: imgHeight,
        });
      });
    }
    //获取所有图片的总高度
    function getImginfo(filelist, realWidth) {
      return new Promise((resolve, reject) => {
        let allHeight = 0;
        let loadedCount = 0; // 用于追踪已加载图片数量
        let totalImages = filelist.length; // 图片总数
        for (let i = 0; i < totalImages; i++) {
          const img = new Image();
          img.crossOrigin = 'anonymous';
          img.src = filelist[i].url;
          img.onload = () => {
            const scaledpr = img.width / realWidth;
            let imgHeight = img.height;
            if (scaledpr > 1) {
              imgHeight = img.height / scaledpr;
            }
            allHeight += imgHeight;
            loadedCount++; // 增加已加载图片计数
            if (loadedCount === totalImages) {
              resolve(allHeight);
            }
          };
          img.onerror = (error) => {
            reject(error);
          };
        }
      });
    }
    //base64转file文件
    function base64ToFile(base64, filename) {
      filename = filename || String(new Date().getTime());
      let arr = base64.split(',');
      let mime = arr[0].match(/:(.*?);/)[1];
      let bstr = atob(arr[1]);
      let n = bstr.length;
      let u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, { type: mime });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
  • 相关阅读:
    漏电继电器 LLJ-630F φ100 导轨安装 分体式结构 LLJ-630H(S) AC
    在数据分析时候的一些小技巧-基于python
    嵌入式实时操作系统的设计与开发(消息)
    【C语言刷LeetCode】1953. 你可以工作的最大周数(M)
    如何用Python自动爬取全国30+城市地铁图数据?
    学编程:Python入门考级必备[5]
    Json(一种数据格式,key:value)
    教你十分钟在Linux系统上快速装机并安装Ansible
    open mp笔记
    苹果电脑系统如何读取移动硬盘数据?
  • 原文地址:https://blog.csdn.net/weixin_44899940/article/details/138180598