chore: 🔨 移除 vue-picture-cropper 插件(ts兼容问题)
This commit is contained in:
@@ -65,7 +65,6 @@
|
|||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-i18n": "9.9.1",
|
"vue-i18n": "9.9.1",
|
||||||
"vue-picture-cropper": "^0.7.0",
|
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* 图片压缩类
|
|
||||||
* @param minSize
|
|
||||||
* @param maxSize
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export function PhotoCompress(minSize, maxSize) {
|
|
||||||
var nextQ = 0.5; // 压缩比例
|
|
||||||
var maxQ = 1;
|
|
||||||
var minQ = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将base64转换为文件
|
|
||||||
* @param base64Codes base64编码
|
|
||||||
* @param fileName 文件名称
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
PhotoCompress.prototype.dataUrlToFile = function (base64Codes, fileName = new Date().getTime()) {
|
|
||||||
var arr = base64Codes.split(","),
|
|
||||||
mime = arr[0].match(/:(.*?);/)[1],
|
|
||||||
bStr = atob(arr[1]),
|
|
||||||
n = bStr.length,
|
|
||||||
u8arr = new Uint8Array(n);
|
|
||||||
while (n--) {
|
|
||||||
u8arr[n] = bStr.charCodeAt(n);
|
|
||||||
}
|
|
||||||
return new File([u8arr], fileName, { type: mime });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 图片压缩
|
|
||||||
* @param file 文件
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
PhotoCompress.prototype.compress = function (file, callback) {
|
|
||||||
var self = this;
|
|
||||||
self.imgBase64(file, function (image, canvas) {
|
|
||||||
var base64Codes = canvas.toDataURL(file.type, nextQ); // y压缩
|
|
||||||
var compressFile = self.dataUrlToFile(base64Codes, file.name.split(".")[0]); // 转成file文件
|
|
||||||
var compressFileSize = compressFile.size; // 压缩后文件大小 k
|
|
||||||
// console.log("图片质量:" + nextQ);
|
|
||||||
// console.log("压缩后文件大小:" + compressFileSize / 1024);
|
|
||||||
if (compressFileSize > maxSize) {
|
|
||||||
// 压缩后文件大于最大值
|
|
||||||
maxQ = nextQ;
|
|
||||||
nextQ = (nextQ + minQ) / 2; // 质量降低
|
|
||||||
self.compress(file, callback);
|
|
||||||
} else if (compressFileSize < minSize) {
|
|
||||||
// 压缩以后文件小于最小值
|
|
||||||
minQ = nextQ;
|
|
||||||
nextQ = (nextQ + maxQ) / 2; // 质量提高
|
|
||||||
self.compress(file, callback);
|
|
||||||
} else {
|
|
||||||
callback(compressFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将图片转化为base64
|
|
||||||
* @param file 文件
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
PhotoCompress.prototype.imgBase64 = function (file, callback) {
|
|
||||||
// 看支持不支持FileReader
|
|
||||||
if (!file || !window.FileReader) return;
|
|
||||||
var image = new Image();
|
|
||||||
// 绑定 load 事件处理器,加载完成后执行
|
|
||||||
image.onload = function () {
|
|
||||||
var canvas = document.createElement("canvas");
|
|
||||||
var ctx = canvas.getContext("2d");
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
canvas.width = image.width * nextQ;
|
|
||||||
canvas.height = image.height * nextQ;
|
|
||||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
||||||
callback(image, canvas);
|
|
||||||
};
|
|
||||||
if (/^image/.test(file.type)) {
|
|
||||||
// 创建一个reader
|
|
||||||
var reader = new FileReader();
|
|
||||||
// 将图片将转成 base64 格式
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
// 读取成功后的回调
|
|
||||||
reader.onload = function () {
|
|
||||||
// self.imgUrls.push(this.result);
|
|
||||||
// 设置src属性,浏览器会自动加载。
|
|
||||||
// 记住必须先绑定事件,才能设置src属性,否则会出同步问题。
|
|
||||||
image.src = this.result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
v-model="isShowModal"
|
|
||||||
title="裁剪图片"
|
|
||||||
width="50%"
|
|
||||||
:close-on-press-escape="false"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
align-center
|
|
||||||
@close="cancel"
|
|
||||||
>
|
|
||||||
<div class="modal-content">
|
|
||||||
<VuePictureCropper
|
|
||||||
:img="pic"
|
|
||||||
:boxStyle="corpBoxStyle"
|
|
||||||
:options="corpOptions"
|
|
||||||
:presetMode="corpPresetMode"
|
|
||||||
@ready="ready"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="m-t-10 btns">
|
|
||||||
<el-button class="default-btn" @click="cancel">取消</el-button>
|
|
||||||
<el-button v-if="showClear" class="default-btn" @click="clear">关闭</el-button>
|
|
||||||
<el-button v-if="showReset" class="default-btn" @click="reset">重置</el-button>
|
|
||||||
<el-button class="default-btn primary" @click="getResult">确定</el-button>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 组件文档:https://cropper.chengpeiquan.com/zh/quick-start.html
|
|
||||||
import VuePictureCropper, { cropper } from "vue-picture-cropper";
|
|
||||||
import { PhotoCompress } from "./compressUtil.js";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
type: [String, File],
|
|
||||||
},
|
|
||||||
presetMode: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
boxStyle: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
showClear: {
|
|
||||||
// 是否显示 Clear 按钮
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
showReset: {
|
|
||||||
// 是否显示 Reset 按钮
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
isCompress: {
|
|
||||||
// 是否压缩裁剪后的图片大小
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
compressSize: {
|
|
||||||
// 压缩图片大小限制
|
|
||||||
type: Object,
|
|
||||||
default: {
|
|
||||||
maxSize: 0.3 * 1024 * 1024, // 300k
|
|
||||||
minSize: 0.03 * 1024 * 1024, // 30k
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 裁剪组件属性配置 */
|
|
||||||
const corpOptions = computed(() => {
|
|
||||||
return {
|
|
||||||
viewMode: 1, // 裁剪框范围 1-只能在图片区域内活动
|
|
||||||
dragMode: "move", // 可选值 crop(可绘制裁剪框) | move(仅移动)
|
|
||||||
// aspectRatio: 1 / 1,// 裁剪框的宽高比
|
|
||||||
cropBoxResizable: false, // 是否可以调整裁剪区的大小
|
|
||||||
...props.options,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
/** 裁剪区域的样式 */
|
|
||||||
const corpBoxStyle = computed(() => {
|
|
||||||
return {
|
|
||||||
width: "auto",
|
|
||||||
height: "400px",
|
|
||||||
backgroundColor: "#f8f8f8",
|
|
||||||
margin: "auto",
|
|
||||||
...props.boxStyle,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 预设模式配置 */
|
|
||||||
const corpPresetMode = computed(() => {
|
|
||||||
return {
|
|
||||||
mode: "fixedSize",
|
|
||||||
width: 200,
|
|
||||||
height: 200,
|
|
||||||
...props.presetMode,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const emits = defineEmits(["close"]);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.visible,
|
|
||||||
(val) => {
|
|
||||||
isShowModal.value = val;
|
|
||||||
if (!val) {
|
|
||||||
pic.value = "";
|
|
||||||
result.dataURL = "";
|
|
||||||
result.blobURL = "";
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.image,
|
|
||||||
(val) => {
|
|
||||||
pic.value = val;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const isShowModal = ref(false);
|
|
||||||
const pic = ref("");
|
|
||||||
const result = reactive({
|
|
||||||
dataURL: "",
|
|
||||||
blobURL: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getResult() {
|
|
||||||
if (!cropper) return;
|
|
||||||
const base64 = cropper.getDataURL();
|
|
||||||
const blob = await cropper.getBlob();
|
|
||||||
if (!blob) return;
|
|
||||||
|
|
||||||
let file = await cropper.getFile({
|
|
||||||
fileName: "locales.fileName",
|
|
||||||
});
|
|
||||||
|
|
||||||
result.dataURL = base64;
|
|
||||||
result.blobURL = URL.createObjectURL(blob);
|
|
||||||
const { minSize, maxSize } = props.compressSize;
|
|
||||||
// console.log(file.size, maxSize)
|
|
||||||
// 是否限制文件大小
|
|
||||||
if (props.isCompress && file.size > maxSize) {
|
|
||||||
// console.log("compress")
|
|
||||||
const photoCompress = new PhotoCompress(minSize, maxSize);
|
|
||||||
photoCompress.compress(file, function (f) {
|
|
||||||
const r = new FileReader();
|
|
||||||
r.readAsDataURL(f);
|
|
||||||
r.onload = function (e) {
|
|
||||||
emits("close", { result, file: f });
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// console.log("crop")
|
|
||||||
emits("close", { result, file });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the crop box
|
|
||||||
*/
|
|
||||||
function clear() {
|
|
||||||
if (!cropper) return;
|
|
||||||
cropper.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the default cropping area
|
|
||||||
*/
|
|
||||||
function reset() {
|
|
||||||
if (!cropper) return;
|
|
||||||
cropper.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ready event passed to Cropper.js
|
|
||||||
*/
|
|
||||||
function ready() {
|
|
||||||
// console.log('Cropper is ready.')
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
emits("close");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.modal-wrap {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 99;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-upload
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
list-type="picture-card"
|
|
||||||
:auto-upload="true"
|
|
||||||
:class="fileList.length >= 1 || !props.showUploadBtn ? 'hide' : 'show'"
|
|
||||||
:before-upload="handleBeforeUpload"
|
|
||||||
:data="props.data"
|
|
||||||
:name="props.name"
|
|
||||||
:accept="props.accept"
|
|
||||||
:limit="1"
|
|
||||||
>
|
|
||||||
<i-ep-plus />
|
|
||||||
{{ title }}
|
|
||||||
<template #file="{ file }">
|
|
||||||
<div style="width: 100%">
|
|
||||||
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
|
|
||||||
<span class="el-upload-list__item-actions">
|
|
||||||
<span class="el-upload-list__item-preview" @click="previewImg(file)">
|
|
||||||
<el-icon><zoom-in /></el-icon>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="props.showDelBtn"
|
|
||||||
class="el-upload-list__item-delete"
|
|
||||||
@click="handleRemove(file)"
|
|
||||||
>
|
|
||||||
<el-icon><Delete /></el-icon>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-upload>
|
|
||||||
|
|
||||||
<el-image-viewer
|
|
||||||
v-if="viewVisible"
|
|
||||||
:zoom-rate="1.2"
|
|
||||||
:initialIndex="initialIndex"
|
|
||||||
:url-list="[viewFile]"
|
|
||||||
@close="closePreview"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<CorpperDialog
|
|
||||||
:visible="isShowCorpper"
|
|
||||||
:image="selectPic"
|
|
||||||
:isCompress="true"
|
|
||||||
:showReset="true"
|
|
||||||
:presetMode="presetMode"
|
|
||||||
@close="hideCorpper"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import CorpperDialog from "./corpperDialog.vue";
|
|
||||||
|
|
||||||
import { UploadRawFile, UploadUserFile, UploadFile, UploadProps } from "element-plus";
|
|
||||||
const emit = defineEmits(["update:modelValue"]);
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
/**
|
|
||||||
* 文件路径
|
|
||||||
*/
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: "上传图片",
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传文件的参数名
|
|
||||||
*/
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: "file",
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 文件上传数量限制
|
|
||||||
*/
|
|
||||||
limit: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 是否显示删除按钮
|
|
||||||
*/
|
|
||||||
showDelBtn: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 是否显示上传按钮
|
|
||||||
*/
|
|
||||||
showUploadBtn: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 单张图片最大大小
|
|
||||||
*/
|
|
||||||
uploadMaxSize: {
|
|
||||||
type: Number,
|
|
||||||
default: 2 * 1024 * 1024,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 上传文件类型
|
|
||||||
*/
|
|
||||||
accept: {
|
|
||||||
type: String,
|
|
||||||
default: "image/*",
|
|
||||||
},
|
|
||||||
|
|
||||||
presetMode: {
|
|
||||||
type: Object,
|
|
||||||
default: "{ width: 200, height: 200 }",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectPic = ref("");
|
|
||||||
const isShowCorpper = ref(false);
|
|
||||||
|
|
||||||
const viewVisible = ref(false);
|
|
||||||
const initialIndex = ref(0);
|
|
||||||
|
|
||||||
const fileList = ref([] as UploadUserFile[]);
|
|
||||||
const viewFile = ref(props.modelValue as string);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(newVal: string) => {
|
|
||||||
if (!newVal) {
|
|
||||||
fileList.value = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileList.value = [{ url: newVal } as UploadUserFile];
|
|
||||||
|
|
||||||
viewFile.value = newVal;
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
/**
|
|
||||||
* 删除图片
|
|
||||||
*/
|
|
||||||
function handleRemove(removeFile: UploadFile) {
|
|
||||||
const filePath = removeFile.url;
|
|
||||||
if (filePath) {
|
|
||||||
emit("update:modelValue", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 限制用户上传文件的格式和大小
|
|
||||||
*/
|
|
||||||
function handleBeforeUpload(file: UploadRawFile) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.onload = () => {
|
|
||||||
selectPic.value = String(reader.result);
|
|
||||||
isShowCorpper.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (file.size > props.uploadMaxSize) {
|
|
||||||
ElMessage.warning("上传图片不能大于 " + Math.trunc(props.uploadMaxSize / 1024 / 1024) + "M");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预览图片
|
|
||||||
*/
|
|
||||||
const previewImg: UploadProps["onPreview"] = (uploadFile: UploadFile) => {
|
|
||||||
viewFile.value = uploadFile.url!;
|
|
||||||
initialIndex.value = 0;
|
|
||||||
viewVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭预览
|
|
||||||
*/
|
|
||||||
const closePreview = () => {
|
|
||||||
viewVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
function hideCorpper(data: any) {
|
|
||||||
isShowCorpper.value = false;
|
|
||||||
if (data) {
|
|
||||||
const {
|
|
||||||
result: { dataURL },
|
|
||||||
file,
|
|
||||||
} = data;
|
|
||||||
emit("update:modelValue", dataURL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.hide {
|
|
||||||
:deep(.el-upload--picture-card) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.show {
|
|
||||||
:deep(.el-upload--picture-card) {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
1
src/types/components.d.ts
vendored
1
src/types/components.d.ts
vendored
@@ -91,7 +91,6 @@ declare module "vue" {
|
|||||||
ThemeColorPicker: (typeof import("./../layout/components/Settings/components/ThemeColorPicker.vue"))["default"];
|
ThemeColorPicker: (typeof import("./../layout/components/Settings/components/ThemeColorPicker.vue"))["default"];
|
||||||
SingleImageUpload: (typeof import("./../components/Upload/SingleImageUpload.vue"))["default"];
|
SingleImageUpload: (typeof import("./../components/Upload/SingleImageUpload.vue"))["default"];
|
||||||
WangEditor: (typeof import("./../components/WangEditor/index.vue"))["default"];
|
WangEditor: (typeof import("./../components/WangEditor/index.vue"))["default"];
|
||||||
ImgCorpper: (typeof import("./../components/ImgCorpper/index.vue"))["default"];
|
|
||||||
}
|
}
|
||||||
export interface ComponentCustomProperties {
|
export interface ComponentCustomProperties {
|
||||||
vLoading: (typeof import("element-plus/es"))["ElLoadingDirective"];
|
vLoading: (typeof import("element-plus/es"))["ElLoadingDirective"];
|
||||||
|
|||||||
@@ -23,22 +23,6 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="单图裁剪">
|
|
||||||
<ImgCorpper
|
|
||||||
v-model="cprpperValue"
|
|
||||||
:presetMode="{ width: 295, height: 413 }"
|
|
||||||
:title="'上传图片'"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="参数说明">
|
|
||||||
<el-table :data="imageCprpperUploadArgData" border>
|
|
||||||
<el-table-column prop="argsName" label="参数名称" width="300" />
|
|
||||||
<el-table-column prop="type" label="参数类型" width="200" />
|
|
||||||
<el-table-column prop="default" label="默认值" width="400" />
|
|
||||||
<el-table-column prop="desc" label="描述" width="300" />
|
|
||||||
</el-table>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="多图上传">
|
<el-form-item label="多图上传">
|
||||||
<MultiImageUpload v-model="picUrls" />
|
<MultiImageUpload v-model="picUrls" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|||||||
Reference in New Issue
Block a user