diff --git a/src/components/Cropper/Cropper.vue b/src/components/Cropper/Cropper.vue index d740473d..3bef4d2a 100644 --- a/src/components/Cropper/Cropper.vue +++ b/src/components/Cropper/Cropper.vue @@ -8,7 +8,7 @@ ref="cropCanvas" > - + import { ref, watch, onBeforeUnmount } from "vue"; import "cropperjs"; -// import "cropperjs/dist/cropper.css"; +import { useMediaComponent } from "@/hooks/useMediaComponent"; +import { $message } from "@/utils/useMessage"; import { ElDialog, ElButton } from "element-plus"; -const imageRef = ref(null); -const imgSrc = ref(""); -const cropper = ref(null); -const resultUrl = ref(""); -const scale = ref(1); const cropCanvas = ref(null); - const props = defineProps({ img: { type: String, @@ -66,10 +61,16 @@ const props = defineProps({ type: Boolean, default: false, }, + fileName: { + type: String, + default: "image", + }, }); +const { fileBaseUrl } = useMediaComponent(props); const show = ref(props.show); const emit = defineEmits(["update:show", "crop-success"]); +const selection = ref(null); watch( () => show.value, @@ -85,57 +86,103 @@ watch( } ); -// 文件选择 -const onFileChange = (e) => { - const file = e.target.files?.[0]; - if (!file) return; +const dataURLtoFile = (dataurl, filename) => { + let arr = dataurl.split(","), + mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), + n = bstr.length, + u8arr = new Uint8Array(n); - const reader = new FileReader(); - reader.onload = (event) => { - imgSrc.value = event.target.result; - // 延迟初始化 cropper - setTimeout(initCropper, 50); - }; - reader.readAsDataURL(file); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } - // 清空 input - e.target.value = ""; + // 创建临时 canvas 元素用于压缩图片 + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + const img = new Image(); + + return new Promise((resolve) => { + img.src = dataurl; + + img.onload = () => { + // 限制最大宽度或高度为 800px + const maxWidthOrHeight = 800; + let width = img.width; + let height = img.height; + + if (width > height) { + if (width > maxWidthOrHeight) { + height *= maxWidthOrHeight / width; + width = maxWidthOrHeight; + } + } else { + if (height > maxWidthOrHeight) { + width *= maxWidthOrHeight / height; + height = maxWidthOrHeight; + } + } + + canvas.width = width; + canvas.height = height; + ctx.drawImage(img, 0, 0, width, height); + + // 将 canvas 转换为 base64,质量设置为 0.8 + const compressedDataUrl = canvas.toDataURL(mime, 0.8); + + // 将压缩后的 base64 转换为文件 + const compressedArr = compressedDataUrl.split(","); + const compressedBstr = atob(compressedArr[1]); + const compressedN = compressedBstr.length; + const compressedU8arr = new Uint8Array(compressedN); + + for (let i = 0; i < compressedN; i++) { + compressedU8arr[i] = compressedBstr.charCodeAt(i); + } + resolve(new File([compressedU8arr], filename, { type: mime })); + }; + }); }; -// 初始化 cropper -const initCropper = () => { - const image = imageRef.value; -}; - -// 缩放 -const onScale = (e) => { - const value = parseFloat(e.target.value); - scale.value = value; - cropper.value?.zoomTo(value); -}; - -// 旋转 -const rotate = (deg) => { - cropper.value?.rotate(deg); -}; - -// 裁剪并输出 const crop = async () => { try { - const canvas = await cropCanvas.value.$toCanvas(); + // 获取裁剪区域的画布 + await selection.value.$toCanvas().then((canvas) => { + const image = canvas.toDataURL(); + dataURLtoFile(image, props.img.name).then((res) => { + // 文件生成 bolburl + const blobUrl = URL.createObjectURL(res); - console.log(canvas); - // 使用toBlob方法替代toDataURL解决跨域问题 - canvas.toBlob( - (blob) => { - const dataUrl = URL.createObjectURL(blob); - console.log(dataUrl); - emit("crop-success", dataUrl); - show.value = false; - }, - "image/jpeg", - 0.9 - ); + // 使用 FormData 格式上传文件 + const formData = new FormData(); + formData.append("file", res); + + const uploadFileUrl = "/systemapi/xboe/sys/xuploader/file/upload"; + fetch(uploadFileUrl, { + method: "POST", + body: formData, + }) + .then((r) => { + return r.json(); + }) + .then((result) => { + if (result.status !== 200) { + $message.error(result.result.message); + return; + } + + // 将结果传递给父组件 + emit("crop-success", { + path: fileBaseUrl + result.result.filePath, + name: result.result.displayName, + }); + show.value = false; + }) + .catch((error) => { + console.error("上传失败:", error); + }); + }); + }); } catch (error) { console.error("裁剪失败:", error); } @@ -143,10 +190,7 @@ const crop = async () => { // 销毁实例 onBeforeUnmount(() => { - if (cropper.value) { - cropper.value.destroy(); - cropper.value = null; - } + // cropper 实例会在组件销毁时自动清理 }); diff --git a/src/hooks/useUpload.js b/src/hooks/useUpload.js index 0cb09d12..db435ac8 100644 --- a/src/hooks/useUpload.js +++ b/src/hooks/useUpload.js @@ -1,7 +1,5 @@ import { ref } from "vue"; import { $message, ElMessage } from "@/utils/useMessage"; -import { Loading, Plus } from "@element-plus/icons-vue"; - /** * 文件上传相关hook * @returns @@ -10,7 +8,10 @@ export function useUpload() { // 上传相关 const fileList = ref([]); const loading = ref(false); - const courseCoverurl = ref(""); + const courseCover = ref({ + path: "", + name: "", + }); // 获取图片base64 const getBase64 = (img, callback) => { @@ -21,21 +22,33 @@ export function useUpload() { // 上传状态变化处理 const handleChange = (info) => { - if (info.file.status === "uploading") { - loading.value = true; - return; - } - if (info.file.status === "done") { - // Get this url from response in real world. - getBase64(info.file.originFileObj, (base64Url) => { - courseCoverurl.value = base64Url; - loading.value = false; - }); - } - if (info.file.status === "error") { + console.log(info); + courseCover.value = { + ...info, + path: info.url, + }; + + getBase64(info.raw, (base64Url) => { + courseCover.value.path = base64Url; + courseCover.value.name = info.name; loading.value = false; - $message.error("upload error"); - } + }); + // if (info.file.status === "uploading") { + // loading.value = true; + // return; + // } + // if (info.file.status === "done") { + // // Get this url from response in real world. + // getBase64(info.file.originFileObj, (base64Url) => { + // courseCover.value.path = base64Url; + // courseCover.value.name = info.file.name; + // loading.value = false; + // }); + // } + // if (info.file.status === "error") { + // loading.value = false; + // $message.error("upload error"); + // } }; // 上传前检查 @@ -48,6 +61,18 @@ export function useUpload() { if (!isLt2M) { $message.error("Image must smaller than 2MB!"); } + + if (isJpgOrPng && isLt2M) { + getBase64(file, (base64Url) => { + courseCover.value = { + raw: file, + path: base64Url, + name: file.name, + }; + loading.value = false; + }); + } + return isJpgOrPng && isLt2M; }; @@ -55,8 +80,7 @@ export function useUpload() { // 数据 fileList, loading, - courseCoverurl, - + courseCover, // 方法 getBase64, handleChange, diff --git a/src/views/courselibrary/components/createCourse.vue b/src/views/courselibrary/components/createCourse.vue index 8316aa2b..796c1a29 100644 --- a/src/views/courselibrary/components/createCourse.vue +++ b/src/views/courselibrary/components/createCourse.vue @@ -303,12 +303,12 @@ const handleNext = () => { +