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