uniapp 图片上传功能以及给图片添加水印

发布于:2024-12-21 ⋅ 阅读:(11) ⋅ 点赞:(0)

完整图片上传封装代码:

<template>
  <view class="add-photo">
    <view class="tips" v-if="title">
      <text class="require" v-if="require">*</text>
      <text>{{ title }}</text>
    </view>
    <van-uploader :file-list="fileList" :max-count="maxCount" :accept="accept" :capture="capture" :preview-image="true" @afterRead="afterReadHandle" @delete="deleteHandle" :sizeType="['compressed']">
      <slot name="default"></slot>
    </van-uploader>
    <!-- 获取有水印的图片过程 必须使canvas显示 获取完成后在隐藏掉canvas 配合canvas样式定位 使其错位 -->
    <!-- canvas的隐藏 在小程序中 使用 v-if或display:none 都不生效   使用hidden属性 true隐藏 false显示 -->
    <canvas :style="{ width: waterMarkParams.canvasWidth, height: waterMarkParams.canvasHeight }" canvas-id="myCanvas" :hidden="waterMarkParams.display"></canvas>
  </view>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { UserModule } from "@/store/modules/user";
import { uploadFile } from "@/utils/uni-api";
import APIConfig from "@/config";
import { generateWaterMarkText } from "@/utils/common";

@Component
export default class UploadPhoto extends Vue {
  @Prop({ default: "", type: String }) private title?: string;
  @Prop({ default: false, type: Boolean }) private require?: boolean;
  @Prop({ default: () => [], type: Array }) private previewFileList!: any[];
  @Prop({ default: 99, type: Number }) private maxCount?: number;
  @Prop({ default: "image", type: String }) private accept?: string;
  @Prop({ default: ["album", "camera"], type: Array }) private capture?: Array<string>;
  @Prop({ default: false, type: Boolean }) private needDelConfirm?: boolean;
  @Prop({ default: null, type: Number }) private maxSize?: number; // kb
  @Prop({ default: null, type: Number }) private minSize?: number; // kb
  @Prop({ default: false, type: Boolean }) private needWatermark?: boolean;
  @Prop({ default: "", type: String }) private waterMarkText?: any;

  private fileList: any[] = [];
  // private token: string = `bearer ${UserModule.token}`;
  private files: any[] = [];
  private waterMarkParams = {
    display: false, // 控制 canvas 创建与销毁
    canvasWidth: "", // 默认宽度
    canvasHeight: "", // 默认高度
    contentHeight: 150, // 将要被绘制到图像中的矩形的高度(px)
  };

  @Watch("previewFileList", { immediate: true })
  private watchPreViewFileList(newVal: any[]): void {
    if (newVal.length > 0) {
      this.fileList = this.previewFileList.map((item) => ({
        path: item.prefix ? item.prefix + item.url : item.url,
      }));
      this.files = this.previewFileList;
      // console.log(this.fileList);
    }
  }

  private async afterReadHandle(e: any): Promise<void> {
    console.log("file", e);
    if (this.maxSize && this.minSize && e.detail.file) {
      const size = e.detail.file.size / 1024; // kb
      const isLt2M = size >= this.minSize && size <= this.maxSize;
      if (!isLt2M) {
        uni.showToast({
          title: "图片文件大小需在20k-80k之间。",
          icon: "none",
        });
        return;
      }
    }
    const filePath = e.detail.file.path;
    uni.showLoading({
      title: "上传中...",
    });
    if (this.needWatermark) {
      this.addWatermark(filePath);
    } else {
      this.uploadFn(filePath);
    }
  }

  private deleteHandle(e: any): void {
    const self = this;
    if (this.needDelConfirm) {
      uni.showModal({
        content: "确认删除?",
        success(res) {
          if (res.confirm) {
            self.fileList.splice(e.detail.index, 1);
            self.files.splice(e.detail.index, 1);
            self.$emit("del-success", self.files);
          } else if (res.cancel) {
          }
        },
      });
      return;
    }
    this.fileList.splice(e.detail.index, 1);
    this.files.splice(e.detail.index, 1);
    this.$emit("del-success", this.files);
  }

  private async uploadFn(filePath: string) {
    const url = APIConfig[UserModule.type][process.env.NODE_ENV] + "/file/upload";
    try {
      let token = "";
      if (UserModule.type === UserModule.CORRECTION) {
        token = `${UserModule.correctionToken}`;
      } else {
        token = `bearer ${UserModule.token}`;
      }
      const result = await uploadFile(url, filePath, token);
      if (result.code === 0) {
        // 回显的数据
        this.fileList.push({
          url: result.data,
        });

        // 提交的文件数据 矫正对象端与工作人员端返回格式不一样
        if (UserModule.type === UserModule.CORRECTION) {
          this.files.push({
            url: result.data,
          });
        } else {
          this.files.push({
            prefix: result.data.prefix,
            url: result.data.url,
            title: result.data.title,
            type: 3,
          });
        }
        this.$emit("upload-success", this.files);
      } else {
        uni.showToast({
          icon: "none",
          title: "文件上传失败:" + result.msg,
        });
        this.$emit("upload-error", this.files);
      }
    } catch (error) {
      this.$emit("upload-error", error);
    }
  }

  private addWatermark(filePath: string) {
    console.log("filePath", filePath);
    this.waterMarkParams.display = false;
    const text = this.waterMarkText || generateWaterMarkText();
    return new Promise((resolve, reject) => {
      // 获取图片信息,配置 canvas 尺寸
      uni.getImageInfo({
        // 注意此时的地址是正常的图片地址 以下是给图片添加水印返回新的url地址
        src: filePath,
        success: (res) => {
          console.log("getImageInfo", res);
          const width = res.width < 600 ? res.width : 600;
          const height = res.height < 800 ? res.height : 800;

          this.waterMarkParams.canvasWidth = `${width}px`;
          this.waterMarkParams.canvasHeight = `${height}px`;
          // var ctx = uni.createCanvasContext('myCanvas');
          // 在自定义组件内 需要传递第二参数 this canvas才生效
          var ctx = uni.createCanvasContext("myCanvas", this);
          ctx.clearRect(0, 0, width, height);
          ctx.beginPath();
          ctx.drawImage(filePath, 0, 0, width, height); // 第一个参数是图片 第二、三是图片在画布位置 第四、五是将图片绘制成多大宽高(不写四五就是原图宽高)

          // 为图片添加水印
          ctx.translate(width / 2, height / 2);
          ctx.rotate((40 * Math.PI) / 180);

          //这部分是水印的大小位置和数量
          let fonstsize = 30;
          let horizontal = fonstsize * 20;
          let vertical = height / 4;

          for (let i = -1; i <= 1; i++) {
            for (let j = 0; j <= 4; j++) {
              ctx.beginPath();
              ctx.setFontSize(fonstsize);
              ctx.setFillStyle("#eee");
              // ctx.fillText("-仅供保险投保使用-", i * horizontal - res.width / 2, j * vertical - res.height / 2);
              /**
               * context.fillText(text,x,y,maxWidth);
               * text 规定在画布上输出的文本。
               * x 开始绘制文本的 x 坐标位置(相对于画布)。
               * y 开始绘制文本的 y 坐标位置(相对于画布)。
               * maxWidth 可选。允许的最大文本宽度,以像素计。
               */
              ctx.fillText(text, i * horizontal - horizontal / 2, j * vertical - height / 2, horizontal);
            }
          }

          // 开始绘制添加水印的图片并显示在页面中
          ctx.draw(false, () => {
            setTimeout(() => {
              console.log("asdf");
              uni.canvasToTempFilePath(
                {
                  canvasId: "myCanvas",
                  quality: 0.6, // 图片质量,范围0-1,1为最高质量
                  width: width,
                  height: height,
                  destWidth: width,
                  destHeight: height,
                  success: (res) => {
                    // 注意此时的地址是加了水印的图片地址(直接url输入浏览器也可以查看包含水印)
                    console.log("canvasToTempFilePath", res.tempFilePath);
                    this.waterMarkParams.display = true;
                    this.uploadFn(res.tempFilePath);
                  },
                  fail: (err) => {
                    uni.hideLoading();
                  },
                },
                this
              );
              // 在自定义组件内 需要传递第二参数 this canvas才生效
              // }, this)
            }, 500);
          });
        },
        fail: (err) => {
          uni.hideLoading();
        },
      });
    });
  }
}
</script>

<style lang="scss" scoped>
.add-photo {
  padding: $medium-spacing-size;
  font-size: $small-text-size;
  background-color: $bg-color;

  .require {
    color: $danger-color;
  }

  .tips {
    margin-bottom: $large-spacing-size;
  }
}

canvas {
  border: solid 1px gray;
  position: absolute;
  left: 5000upx;
}
</style>

给图片添加水印代码

<template>
    <!-- 获取有水印的图片过程 必须使canvas显示 获取完成后在隐藏掉canvas 配合canvas样式定位 使其错位 -->
    <!-- canvas的隐藏 在小程序中 使用 v-if或display:none 都不生效   使用hidden属性 true隐藏 false显示 -->
    <canvas :style="{ width: waterMarkParams.canvasWidth, height: waterMarkParams.canvasHeight }" canvas-id="myCanvas" :hidden="waterMarkParams.display"></canvas>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";

private waterMarkParams = {
  display: false, // 控制 canvas 创建与销毁
  canvasWidth: "", // 默认宽度
  canvasHeight: "", // 默认高度
  contentHeight: 150, // 将要被绘制到图像中的矩形的高度(px)
};

// 传入图片路径
private addWatermark(filePath: string) {
  console.log("filePath", filePath);
  this.waterMarkParams.display = false;
  const text = this.waterMarkText || generateWaterMarkText();
  return new Promise((resolve, reject) => {
    // 获取图片信息,配置 canvas 尺寸
    uni.getImageInfo({
      // 注意此时的地址是正常的图片地址 以下是给图片添加水印返回新的url地址
      src: filePath,
      success: (res) => {
        console.log("getImageInfo", res);
        const width = res.width < 600 ? res.width : 600;
        const height = res.height < 800 ? res.height : 800;

        this.waterMarkParams.canvasWidth = `${width}px`;
        this.waterMarkParams.canvasHeight = `${height}px`;
        // var ctx = uni.createCanvasContext('myCanvas');
        // 在自定义组件内 需要传递第二参数 this canvas才生效
        var ctx = uni.createCanvasContext("myCanvas", this);
        ctx.clearRect(0, 0, width, height);
        ctx.beginPath();
        ctx.drawImage(filePath, 0, 0, width, height); // 第一个参数是图片 第二、三是图片在画布位置 第四、五是将图片绘制成多大宽高(不写四五就是原图宽高)

        // 为图片添加水印
        ctx.translate(width / 2, height / 2);
        ctx.rotate((40 * Math.PI) / 180);

        //这部分是水印的大小位置和数量
        let fonstsize = 30;
        let horizontal = fonstsize * 20;
        let vertical = height / 4;

        for (let i = -1; i <= 1; i++) {
          for (let j = 0; j <= 4; j++) {
            ctx.beginPath();
            ctx.setFontSize(fonstsize);
            ctx.setFillStyle("#eee");
            // ctx.fillText("-仅供保险投保使用-", i * horizontal - res.width / 2, j * vertical - res.height / 2);
            /**
             * context.fillText(text,x,y,maxWidth);
             * text 规定在画布上输出的文本。
             * x 开始绘制文本的 x 坐标位置(相对于画布)。
             * y 开始绘制文本的 y 坐标位置(相对于画布)。
             * maxWidth 可选。允许的最大文本宽度,以像素计。
             */
            ctx.fillText(text, i * horizontal - horizontal / 2, j * vertical - height / 2, horizontal);
          }
        }

        // 开始绘制添加水印的图片并显示在页面中
        ctx.draw(false, () => {
          setTimeout(() => {
            console.log("asdf");
            uni.canvasToTempFilePath(
              {
                canvasId: "myCanvas",
                quality: 0.6, // 图片质量,范围0-1,1为最高质量
                width: width,
                height: height,
                destWidth: width,
                destHeight: height,
                success: (res) => {
                  // 注意此时的地址是加了水印的图片地址(直接url输入浏览器也可以查看包含水印)
                  console.log("canvasToTempFilePath", res.tempFilePath);
                  this.waterMarkParams.display = true;
                  this.uploadFn(res.tempFilePath);
                },
                fail: (err) => {
                  uni.hideLoading();
                },
              },
              this
            );
            // 在自定义组件内 需要传递第二参数 this canvas才生效
            // }, this)
          }, 500);
        });
      },
      fail: (err) => {
        uni.hideLoading();
      },
    });
  });
}
</script>

注意:如果图片不显示,可能是图片太大了,可以试着压缩一下图片大小

参考链接:https://blog.csdn.net/i_am_a_div/article/details/118302757