element ui图片上传组件封装+校验黑白照片

发布时间:2024年01月03日

项目需求是在上传照片的时候,不能上传黑白照片。如果上传的黑白照片需要提示。所以就封装的一个组件,校验照片颜色也是我在网上找到的,但是原文链接找不见了。所以自己改了改封装的一个小功能。

// components/ImageUpload/index.vue
<template>
  <div class="component-upload-image">
    <el-upload
      multiple
      name="multipartFile"
      :action="uploadImgUrl"
      :data="{ 上传时附带的额外参数 }"
      list-type="picture-card"
      :on-success="handleUploadSuccess"
      :before-upload="handleBeforeUpload"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      ref="imageUpload"
      :on-remove="handleDelete"
      :show-file-list="true"
      :headers="headers"
      :file-list="fileList"
      :on-preview="handlePictureCardPreview"
      :class="{hide: this.fileList.length >= this.limit}"
    >
      <i class="el-icon-plus"></i>
    </el-upload>

    <!-- 上传提示 -->
    <div class="el-upload__tip" slot="tip" v-if="showTip">
      请上传
      <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
      <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
      的文件
    </div>

    <el-dialog
      :visible.sync="dialogVisible"
      title="预览"
      width="800"
      append-to-body
    >
      <img
        :src="dialogImageUrl"
        style="display: block; max-width: 100%; margin: 0 auto"
      />
    </el-dialog>

    <canvas style="display: none" id="canvas"></canvas>
    <canvas-straw :src="imgSrc"></canvas-straw>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import defaultSettings from "@/settings";
import themeColor from "../CanvasStraw/index.js";
import canvasStraw from "../CanvasStraw/canvas-straw.vue";

export default {
  components: {
    canvasStraw,
  },
  props: {
    value: [String, Object, Array],
    // 是否检验图片色系
    checkColor: {
      type: Boolean,
      default: false,
    },
    // 图片数量限制
    limit: {
      type: Number,
      default: 5,
    },
    // 大小限制(MB)
    fileSize: {
       type: Number,
      default: 10,
    },
    // 文件类型, 例如['png', 'jpg', 'jpeg']
    fileType: {
      type: Array,
      default: () => ["png", "jpg", "jpeg", "bmp"],
    },
    formFileList: {
      type: Array,
      default: () => [],
    },
    // 是否显示提示
    isShowTip: {
      type: Boolean,
      default: true
    },
    busiType: {
      type: String,
    },
  },
  data() {
    return {
      number: 0,
      uploadList: [],
      dialogImageUrl: "",
      dialogVisible: false,
      hideUpload: false,
      baseUrl: // 地址,
      uploadImgUrl: , // 上传的图片服务器地址
      headers: {
        Authorization: "Bearer " + getToken(),
      },
      fileList: [],
      imgSrc: ""
    };
  },
  watch: {
    formFileList: {
      handler(val) {
        if (val !== undefined) {
          this.fileList = val;
        }
        if (val == null) {
          this.fileList = [];
          return;
        }
      },
      deep: true,
      immediate: true,
    },
    value: {
      handler(val) {
        if (val) {
          // 首先将值转为数组
          const list = Array.isArray(val) ? val : this.value.split(',');
          // 然后将数组转为对象数组
          this.fileList = list.map(item => {
            if (typeof item === "string") {
              item = { name: item.fileName, url: defaultSettings.minioUrl +"/minio-api/" + item.systemCode + item.data.filePath };
            }
            return item;
          });
        } else {
          this.fileList = [];
          return [];
        }
      },
      deep: true,
      immediate: true
    }
  },
  computed: {
    // 是否显示提示
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize);
    },
  },
  methods: {
    // 上传前loading加载
    async handleBeforeUpload(file) {
      let isImg = false;
      if (this.fileType.length) {
        let fileExtension = "";
        if (file.name.lastIndexOf(".") > -1) {
          fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
        }
        isImg = this.fileType.some(type => {
          if (file.type.indexOf(type) > -1) return true;
          if (fileExtension && fileExtension.indexOf(type) > -1) return true;
          return false;
        });
      } else {
        isImg = file.type.indexOf("image") > -1;
      }

      if (!isImg) {
        this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`);
        return false;
      }
      if (this.fileSize) {
        const isLt = file.size / 1024 / 1024 < this.fileSize;
        if (!isLt) {
          this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
          return false;
        }
      }

      this.$modal.loading("正在上传图片,请稍候...");
      this.number++;
    },
    // 文件个数超出
    handleExceed() {
      this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
    },
    // 上传成功回调
    async handleUploadSuccess(res, file) {
      // 检测照片黑白
      const url = 这个是我们图片的地址
      this.imgSrc = url;
      if(this.checkColor){
        const result = await this.checkImgColor(url)
        if (!result) {
          this.number--;
          this.$modal.closeLoading();
          this.$modal.msgError("检测到图片为黑白色,请上传彩色照片");
          this.$refs.imageUpload.handleRemove(file);
          return false
        }
        if (res.code === 200 && result) {
          this.uploadList.push(res.data);
          this.uploadedSuccessfully();
        } else {
          this.number--;
          this.$modal.closeLoading();
          this.$modal.msgError(res.msg);
          this.$refs.imageUpload.handleRemove(file);
          this.uploadedSuccessfully();
        }
      } else {
        if (res.code === 200) {
          this.uploadList.push(res.data);
          this.uploadedSuccessfully();
        } else {
          this.number--;
          this.$modal.closeLoading();
          this.$modal.msgError(res.msg);
          this.$refs.imageUpload.handleRemove(file);
          this.uploadedSuccessfully();
        }
      }
    },
    checkImgColor(url) {
      const img = new Image();
      img.src = url;
      img.crossOrigin = "anonymous";
      return new Promise((resolve, reject) => {
        img.onload = () => {
          themeColor(30, img, 10, (colorArr) => {
            console.log(colorArr.length);
            if (colorArr.length <= 2) {
              resolve(false)
            } else {
              resolve(true)
            }
          });
        };
      })
    },
    // 删除图片
    handleDelete(file) {
      let index = this.fileList.findIndex(item => item.fileId == file.fileId)
      // console.log(index);
      if(index > -1) {
        this.fileList.splice(index, 1);
        this.$emit("input", this.listToString(this.fileList));
        this.$emit("fileUploadSuccess", this.fileList);
      }
    },
    // 上传失败
    handleUploadError() {
      this.number--;
      this.$modal.msgError("上传图片失败,请重试");
      this.$modal.closeLoading();
    },
    // 上传结束处理
    uploadedSuccessfully() {
      if (this.number > 0 && this.uploadList.length === this.number) {
        const resList = this.uploadList.map(item => {
          return {...item, url: defaultSettings.minioUrl +"/minio-api/" + item.systemCode + item.filePath}
        })
        this.fileList = this.fileList.concat(resList);
        this.uploadList = [];
        this.number = 0;
        this.$emit("input", this.listToString(this.fileList));
        this.$modal.closeLoading();
        this.$emit("fileUploadSuccess", this.fileList);
      }
    },
    // 预览
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    // 对象转成指定字符串分隔
    listToString(list, separator) {
      let strs = "";
      separator = separator || ",";
      for (let i in list) {
        if (list[i].url) {
          strs += list[i].url.replace(this.baseUrl, "") + separator;
        }
      }
      return strs != '' ? strs.substr(0, strs.length - 1) : '';
    }
  }
};
</script>
<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
::v-deep.hide .el-upload--picture-card {
    display: none;
}
// 去掉动画效果
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
    transition: all 0s;
}

::v-deep .el-list-enter, .el-list-leave-active {
    opacity: 0;
    transform: translateY(0);
}
</style>
// components/CanvasStraw/index.js
/**
 * 颜色盒子类
 *
 * @param {Array} colorRange    [[rMin, rMax],[gMin, gMax], [bMin, bMax]] 颜色范围
 * @param {any} total   像素总数, imageData / 4
 * @param {any} data    像素数据集合
 */
class ColorBox {
  constructor(colorRange, total, data) {
      this.colorRange = colorRange;
      this.total = total;
      this.data = data;
      this.volume = (colorRange[0][1] - colorRange[0][0]) * (colorRange[1][1] - colorRange[1][0]) * (colorRange[2][1] - colorRange[2][0]);
      this.rank = total * this.volume;
  }
  getColor() {
      const total = this.total;
      const data = this.data;
      let redCount = 0,
          greenCount = 0,
          blueCount = 0;

      for (let i = 0; i < total; i++) {
          redCount += data[i * 4];
          greenCount += data[i * 4 + 1];
          blueCount += data[i * 4 + 2];
      }
      return [redCount / total, greenCount / total, blueCount / total];
  }
}

// 获取切割边
const getCutSide = (colorRange) => {   // r:0,g:1,b:2
  const arr = [];
  for (let i = 0; i < 3; i++) {
      arr.push(colorRange[i][1] - colorRange[i][0]);
  }
  return arr.indexOf(Math.max(arr[0], arr[1], arr[2]));
}

// 切割颜色范围
const cutRange = (colorRange, colorSide, cutValue) => {
  const arr1 = [];
  const arr2 = [];
  colorRange.forEach(function (item) {
      arr1.push(item.slice());
      arr2.push(item.slice());
  })
  arr1[colorSide][1] = cutValue;
  arr2[colorSide][0] = cutValue;

  return [arr1, arr2];
}

// 找到出现次数为中位数的颜色
const __quickSort = (arr) => {
  if (arr.length <= 1) {
      return arr;
  }
  const pivotIndex = Math.floor(arr.length / 2);
  const pivot = arr.splice(pivotIndex, 1)[0];
  const left = [];
  const right = [];
  for (let i = 0; i < arr.length; i++) {
      if (arr[i].count <= pivot.count) {
          left.push(arr[i]);
      }
      else {
          right.push(arr[i]);
      }
  }
  return __quickSort(left).concat([pivot], __quickSort(right));
}

const getMedianColor = (colorCountMap, total) => {

  const arr = [];
  for (const key in colorCountMap) {
      arr.push({
          color: parseInt(key),
          count: colorCountMap[key]
      })
  }

  const sortArr = __quickSort(arr);
  let medianCount = 0;
  const medianIndex = Math.floor(sortArr.length / 2)

  for (let i = 0; i <= medianIndex; i++) {
      medianCount += sortArr[i].count;
  }

  return {
      color: parseInt(sortArr[medianIndex].color),
      count: medianCount
  }
}

// 切割颜色盒子
const cutBox = (colorBox) => {

  const colorRange = colorBox.colorRange;
  const cutSide = getCutSide(colorRange);
  const colorCountMap = {};
  const total = colorBox.total;
  const data = colorBox.data;

  // 统计出各个值的数量
  for (let i = 0; i < total; i++) {
      const color = data[i * 4 + cutSide];

      if (colorCountMap[color]) {
          colorCountMap[color] += 1;
      }
      else {
          colorCountMap[color] = 1;
      }
  }

  const medianColor = getMedianColor(colorCountMap, total);
  const cutValue = medianColor.color;
  const cutCount = medianColor.count;
  const newRange = cutRange(colorRange, cutSide, cutValue);
  const box1 = new ColorBox(newRange[0], cutCount, data.slice(0, cutCount * 4));
  const box2 = new ColorBox(newRange[1], total - cutCount, data.slice(cutCount * 4));
  return [box1, box2];
}

// 队列切割
const queueCut = (queue, num) => {
  while (queue.length < num) {
      queue.sort((a, b) => {
          return a.rank - b.rank
      });
      const colorBox = queue.pop();
      const result = cutBox(colorBox);
      queue = queue.concat(result);
  }
  return queue.slice(0, num)
}

// 颜色去重
const colorFilter = (colorArr, difference) => {
  for (let i = 0; i < colorArr.length; i++) {
      for (let j = i + 1; j < colorArr.length; j++) {
          if (Math.abs(colorArr[i][0] - colorArr[j][0]) < difference && Math.abs(colorArr[i][1] - colorArr[j][1]) < difference && Math.abs(colorArr[i][2] - colorArr[j][2]) < difference) {
              colorArr.splice(j, 1)
              j--
          }
      }
  }
  return colorArr
}

/**
* 提取颜色
* @param colorNumber 提取最大颜色数量
* @param img 需要提取的图片
* @param difference 图片颜色筛选精准度
* @param callback 回调函数
*/
const themeColor = (colorNumber, img, difference, callback) => {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  let width = 0
  let height = 0
  let imageData = null

  canvas.width = img.width;
  width = canvas.width
  canvas.height = img.height
  height = canvas.height

  ctx.drawImage(img, 0, 0, width, height);

  imageData = ctx.getImageData(0, 0, width, height).data;

  const total = imageData.length / 4;

  let rMin = 255,
      rMax = 0,
      gMin = 255,
      gMax = 0,
      bMin = 255,
      bMax = 0;

  // 获取范围
  for (let i = 0; i < total; i++) {
      const red = imageData[i * 4];
      const green = imageData[i * 4 + 1];
      const blue = imageData[i * 4 + 2];

      if (red < rMin) {
          rMin = red;
      }

      if (red > rMax) {
          rMax = red;
      }

      if (green < gMin) {
          gMin = green;
      }

      if (green > gMax) {
          gMax = green;
      }

      if (blue < bMin) {
          bMin = blue;
      }

      if (blue > bMax) {
          bMax = blue;
      }
  }

  const colorRange = [[rMin, rMax], [gMin, gMax], [bMin, bMax]];
  const colorBox = new ColorBox(colorRange, total, imageData);
  const colorBoxArr = queueCut([colorBox], colorNumber);
  let colorArr = [];

  for (let j = 0; j < colorBoxArr.length; j++) {
      colorBoxArr[j].total && colorArr.push(colorBoxArr[j].getColor())
  }

  colorArr = colorFilter(colorArr, difference)

  callback(colorArr);
}

export default themeColor
// components/CanvasStraw/canvas-straw.vue
<template>
  <div class="color-content" :style="{ '--size': boxSize + 'px', '--pix-size': '10px' }" v-if="value">
    <div class="close-icon" @click="close">close</div>
    <div class="img-box">
      <img :src="src" ref="img" crossOrigin @load="initCanvas()" alt="origin-img" />
    </div>
    <div class="pix-box" :style="pixPos">
      <div class="center" :style="{ borderColor: `rgb(${color})` }"></div>
      <div class="htmls" v-html="innerVal"></div>
    </div>
  </div>
</template>
<script>
export default {
  name: "canvas-straw",
  model: {
    event: "on-change",
    prop: "value",
  },
  props: {
    boxSize: {
      type: Number,
      default: 100,
    },
    value: {
      type: Boolean,
      default: false,
    },
    src: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      color: "153, 153, 153",
      innerVal: "",
      mouseInfo: {
        clientY: 0,
        clientX: 0,
        space: 20,
        size: 100,
      },
    };
  },
  computed: {
    pixPos() {
      const width = window.innerWidth;
      const height = window.innerHeight;
      let { clientY, clientX, space, size } = this.mouseInfo;
      let left = clientX;
      let top = clientY;
      if (clientY + size > height) {
        top = clientY - size - space;
      } else {
        top += space;
      }
      if (clientX + size > width) {
        left = clientX - size - space;
      } else {
        left += space;
      }
      return `left: ${left}px; top:${top}px`;
    },
  },
  methods: {
    close() {
      this.$emit("on-change", false);
    },
    initCanvas() {
      let oImg = this.$refs.img;
      let canvas = this.draw(oImg);
      oImg.addEventListener("click", (e) => {
        const [r, g, b] = this.color.split(",");
        console.log({ r, g, b });
        this.$emit("on-change", { r, g, b });
      });
      oImg.addEventListener("mousemove", (e) => {
        this.mouseInfo.clientY = e.clientY;
        this.mouseInfo.clientX = e.clientX;
        let x = e.offsetX;
        let y = e.offsetY;
        this.color = this.getPix(x, y, canvas.ctx);
      });
    },
    // 画图
    draw(img) {
      let style = window.getComputedStyle(img);
      let width = parseInt(style.width);
      let height = parseInt(style.height);
      img.style.width = width + "px";
      img.style.height = height + "px";
      img.style.maxWidth = width + "px";
      img.style.maxHeight = height + "px";
      let canvas = document.createElement("canvas");
      canvas.width = width;
      canvas.height = height;
      let ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0, width, height); // 这里一定要写上获取到图片的宽高,否则生成的图片和原图片大小不一样,吸取到的颜色不准
      return {
        ctx,
        canvas,
      };
    },
    // 获取16进制颜色
    gethex(r, g, b) {
      r = r.toString(16);
      g = g.toString(16);
      b = b.toString(16);
      // 补0
      if (r.length === 1) r = "0" + r;
      if (g.length === 1) g = "0" + g;
      if (b.length === 1) b = "0" + b;
      let hex = r + g + b;
      // 简化处理,如 FFEEDD 可以写为 FED
      if (r.slice(0, 1) === r.slice(1, 1) && g.slice(0, 1) === g.slice(1, 1) && b.slice(0, 1) === b.slice(1, 1)) {
        hex = r.slice(0, 1) + g.slice(0, 1) + b.slice(0, 1);
      }
      return hex;
    },
    // 获取像素颜色
    getPix(x, y, context) {
      const size = 10;
      const num = this.boxSize / 2 / size; // boxSize (必须是偶数 盒子大小) / 一半 / 每个像素大小
      x = x - num; // 减掉自身像素个数的开始坐标
      y = y - num;
      // 读取图片像素信息
      const w = num * 2 + 1; // 图片大小是 像素个数的2倍 (并多一行一列像素 为了视觉上的中心点 所以必须是奇数)
      const h = num * 2 + 1;
      const centerPos = Math.ceil(w / 2); // 获取中心点坐标 向上取整
      let imageData = context.getImageData(x, y, w, h); // 截取 当前坐标下 w,h大小画图的数据
      const pos = this.getPos(imageData.data, w); // 计算当前截取画布下的像素信息(读取像素长度控制在千万以内 否则易导致浏览器崩溃)
      // 生成矩阵数据
      let arr = [];
      Array(w)
        .fill()
        .map((item, index) => {
          let tx = index + 1;
          const inners = Array(h)
            .fill()
            .map((item2, index2) => {
              let ty = index2 + 1;
              const color = pos.get(`${tx},${ty}`);
              // 创建 10 * 10 px大小为单位的像素块
              arr.push(`<div data-set="${tx},${ty}" style="left:${index * 10}px; top:${index2 * 10}px; background: rgb(${color})"></div>`);
              return "#" + color;
            });
          return inners;
        });
      // 更新数据
      this.innerVal = arr.join("");
      // 返回当前中心坐标的 颜色值
      return pos.get(`${centerPos},${centerPos}`);
    },

    // 计算像素信息并返回
    getPos(data, imgWidth) {
      let pos = new Map();
      let length = data.length;

      for (let i = 0; i < length; i++) {
        if (i % 4 === 0) {
          // 每四个元素为一个像素数据 r,g,b,alpha
          let x = ((i / 4) % imgWidth) + 1; // 横坐标
          let y = Math.floor(i / 4 / imgWidth) + 1; // 纵坐标
          let alpha = Math.round((data[i + 3] / 255) * 100) / 100; // alpha 值

          if (data[i + 3] === 255) {
            // 没有alpha 值
            // let hex = this.gethex(data[i], data[i + 1], data[i + 2])
            let hex = `${data[i]}, ${data[i + 1]}, ${data[i + 2]}`;
            pos.set(`${x},${y}`, hex);
          } else if (alpha > 0) {
            // 有alpha 值
            let rgba = `${data[i]}, ${data[i + 1]}, ${data[i + 2]}, ${alpha}`;
            pos.set(`${x},${y}`, rgba);
          }
        }
      }
      return pos;
    },
  },
};
</script>
<style lang="less" scoped>
.color-content {
  z-index: 9999;
  position: fixed;
  left: 0px;
  top: 0px;
  width: 100%;
  height: 100%;
  text-align: center;
  padding: 20px;

  .img-box {
    display: flex;
    width: 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
  }

  img {
    cursor: crosshair;
    max-width: 100%;
    max-height: 100%;
  }
}

.close-icon {
  position: fixed;
  right: 10px;
  top: 10px;
  cursor: pointer;
  transition: ease-in-out 0.3s;
  color: #ebebeb;

  &:hover {
    color: #ff0000;
  }
}
</style>
<style lang="less">
@pix-size: 10px;

.pix-box {
  z-index: 999;
  position: absolute;
  top: 30px;
  width: calc(var(--size) + @pix-size + 2px);
  height: calc(var(--size) + @pix-size + 2px);
  box-sizing: border-box;
  border: #fff solid 1px;
  border-radius: 50%;
  overflow: hidden;
  box-shadow: 0px 0px 5px #999;

  div.htmls {
    position: absolute;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 100%;

    & > div {
      width: @pix-size;
      height: @pix-size;
      position: absolute;
      border: none;
    }
  }

  div.center {
    position: absolute;
    width: @pix-size;
    height: @pix-size;
    left: calc(var(--size) / 2);
    top: calc(var(--size) / 2);
    box-sizing: border-box;
    border: #999 solid 1px;
    z-index: 10;
    filter: invert(100%);
  }
}
</style>

使用:

// checkColor我的项目这个在data定义变量
<el-form-item label="相关图片" prop="files" class="upload person-require" v-else>
	 <image-upload v-model="form.files" :checkColor="true" :form-file-list="form.files" @fileUploadSuccess="fileUploadSuccessHandle" />
</el-form-item>


/** 文件上传成功的展示 */
fileUploadSuccessHandle(fileList) {
  this.form.files = fileList;
},
文章来源:https://blog.csdn.net/weixin_45959525/article/details/135345390
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。