mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/fe-manage.git
synced 2025-12-13 04:46:46 +08:00
feat(cropper): 添加图片裁剪组件及依赖
- 新增 Cropper 组件,支持图片裁剪、缩放、旋转功能 - 集成 cropperjs 库及其相关元素组件 - 在专业模式页面中引入并使用裁剪组件 - 更新 package.json 和 lock 文件以包含新依赖 - 优化登录过期弹窗按钮文本显示
This commit is contained in:
118
package-lock.json
generated
118
package-lock.json
generated
@@ -1246,6 +1246,115 @@
|
|||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@cropper/element": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element/-/element-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-2zELddqHQNmlvkPoiYzE5nxEjPE+C8nXoTPuvV3FvLp3YjBinc7qb73Icg9UXP0o9qC4+h9q96JgGo0AyMO/Ng==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-canvas": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-canvas/-/element-canvas-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-el+rfJpZxsD2q5XxDBA4fRczcrOqB65Lb7roqXOq8LKufwf4bPWA9C6DjNJJahh/TP94dsLIEy3tSkgRMDv3Aw==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-crosshair": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-crosshair/-/element-crosshair-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-0V589dAx8uZAfvJwdINLn76gfPQEafPH94ukjJ76uX0FCUovLaAVX+VRD/MDSYn0Mza/xejzmL9Dhd1DfemvmA==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-grid": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-grid/-/element-grid-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-dEnk0rO+vp553LMvsPYgfrqVFcYXeVFrgFeavBYYEhAXtO40p7kN4rmLYLMMjaN+T/Mx2BATv6kUQpALKy2HLw==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-handle": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-handle/-/element-handle-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-8BklWA4C/2GGAULupIWleSnGutECvYt3vx9flodqDfZpDEozws4LgLqmmzVuQmVkRVUdLnXdtx28kjgWLtzkHg==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-image": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-image/-/element-image-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-mXOV8ixJvG0XtTxLebYAKDjEkFbFOQnsF02hXPZk1yQSV0J+LLhN7a2NePrtKnoTsEV19fhhX3UorMoyGGxvzg==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/element-canvas": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-selection": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-selection/-/element-selection-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-mtFtBl6HIa/s9TWohXw+Z5eJoeYTqylrIcHvS7oVv0uM7IyeRwBW65Q7z+KtLfq/LW+2Sw/XDyvR+VN/DawBPw==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/element-canvas": "^2.1.0",
|
||||||
|
"@cropper/element-image": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-shade": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-shade/-/element-shade-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-zMdyqbb0lc0Vd1oj2Z1miIZvhyZG41OXMHvrNt0hNwblh0dVdrvtw48lnFDgRv+672vt2CNx7Q04GuvCQfPlgg==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/element-canvas": "^2.1.0",
|
||||||
|
"@cropper/element-selection": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/element-viewer": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/element-viewer/-/element-viewer-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-XnxlQuqHitd1FOFZ6E0yXAF5NYd/LyIvONLLHI9p1rJw747WYKUPxQaSYtFKF7IOizJu/8mMj++Zc1dZ5ZP3YQ==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/element-canvas": "^2.1.0",
|
||||||
|
"@cropper/element-image": "^2.1.0",
|
||||||
|
"@cropper/element-selection": "^2.1.0",
|
||||||
|
"@cropper/utils": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/elements": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/elements/-/elements-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qvzlYDn3VQgPPpsCu6Gi1XUO0v3vpXQFSjjxcVijbXeNsl/eiKrN7H9/CEiRgi5vr8kXfd7ZvgYxBjUBbH+y+w==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/element": "^2.1.0",
|
||||||
|
"@cropper/element-canvas": "^2.1.0",
|
||||||
|
"@cropper/element-crosshair": "^2.1.0",
|
||||||
|
"@cropper/element-grid": "^2.1.0",
|
||||||
|
"@cropper/element-handle": "^2.1.0",
|
||||||
|
"@cropper/element-image": "^2.1.0",
|
||||||
|
"@cropper/element-selection": "^2.1.0",
|
||||||
|
"@cropper/element-shade": "^2.1.0",
|
||||||
|
"@cropper/element-viewer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cropper/utils": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@cropper/utils/-/utils-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-wLtpZ4/UWgo+fGmG8NBWge8x5ehjfDe9ovleDfLy8kpwFaw43XXOEXQtRL1UNr0u4JZxaeO8FcXcolRWUUrlRQ=="
|
||||||
|
},
|
||||||
"@ctrl/tinycolor": {
|
"@ctrl/tinycolor": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz",
|
||||||
@@ -3906,6 +4015,15 @@
|
|||||||
"readable-stream": "^3.4.0"
|
"readable-stream": "^3.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cropperjs": {
|
||||||
|
"version": "2.0.0-rc.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/cropperjs/-/cropperjs-2.0.0-rc.2.tgz",
|
||||||
|
"integrity": "sha512-BTuz+UeZphGOEnBCuQiNT4rk1uFfKJaKmTgoH9XU7Q8IMkLdodW7YPWINmXJXwWMt1nXiKze5qKADVbz9xtVFg==",
|
||||||
|
"requires": {
|
||||||
|
"@cropper/elements": "^2.0.0-rc.2",
|
||||||
|
"@cropper/utils": "^2.0.0-rc.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
|
"cropperjs": "^2.0.0-rc.2",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.4.1",
|
||||||
"element-plus": "^2.2.17",
|
"element-plus": "^2.2.17",
|
||||||
|
|||||||
@@ -43,12 +43,13 @@ jsonRequest.interceptors.response.use(
|
|||||||
{
|
{
|
||||||
type: "warning",
|
type: "warning",
|
||||||
confirmButtonText: "重新登录",
|
confirmButtonText: "重新登录",
|
||||||
|
cancelButtonText: "取消",
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
window.location.href = ReLoginUrl;
|
window.location.href = ReLoginUrl;
|
||||||
});
|
});
|
||||||
} else if (code === 403) {
|
} else if (code === 403) {
|
||||||
ElMessage.error("当前操作没有权限");
|
$message.error("当前操作没有权限");
|
||||||
} else if (code === 302) {
|
} else if (code === 302) {
|
||||||
window.location.href = ReLoginUrl;
|
window.location.href = ReLoginUrl;
|
||||||
} else {
|
} else {
|
||||||
@@ -113,6 +114,7 @@ formRequest.interceptors.response.use(
|
|||||||
ElMessageBox.confirm("登录状态无效,即将跳转至登录页", "登录已过期", {
|
ElMessageBox.confirm("登录状态无效,即将跳转至登录页", "登录已过期", {
|
||||||
type: "warning",
|
type: "warning",
|
||||||
confirmButtonText: "确认",
|
confirmButtonText: "确认",
|
||||||
|
cancelButtonText: "取消",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
window.location.href = ReLoginUrl;
|
window.location.href = ReLoginUrl;
|
||||||
});
|
});
|
||||||
|
|||||||
187
src/components/Cropper/Cropper.vue
Normal file
187
src/components/Cropper/Cropper.vue
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog class="image-cropper" v-model="show" width="800px">
|
||||||
|
<!-- 图像容器 -->
|
||||||
|
<div class="img-container">
|
||||||
|
<cropper-canvas
|
||||||
|
background
|
||||||
|
style="width: 100%; height: 100%"
|
||||||
|
ref="cropCanvas"
|
||||||
|
>
|
||||||
|
<cropper-image
|
||||||
|
:src="props.img"
|
||||||
|
alt="Picture"
|
||||||
|
rotatable
|
||||||
|
scalable
|
||||||
|
skewable
|
||||||
|
translatable
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></cropper-image>
|
||||||
|
<cropper-shade hidden></cropper-shade>
|
||||||
|
<cropper-handle action="move" plain></cropper-handle>
|
||||||
|
<cropper-selection initial-coverage="0.5" outlined>
|
||||||
|
<cropper-grid role="grid" covered></cropper-grid>
|
||||||
|
<cropper-crosshair centered></cropper-crosshair>
|
||||||
|
<cropper-handle
|
||||||
|
action="move"
|
||||||
|
theme-color="rgba(255, 255, 255, 0.35)"
|
||||||
|
></cropper-handle>
|
||||||
|
<cropper-handle action="n-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="e-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="s-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="w-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="ne-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="nw-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="se-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="sw-resize"></cropper-handle>
|
||||||
|
</cropper-selection>
|
||||||
|
</cropper-canvas>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div slot="footer">
|
||||||
|
<el-button @click="show = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="crop">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, onBeforeUnmount } from "vue";
|
||||||
|
import "cropperjs";
|
||||||
|
// import "cropperjs/dist/cropper.css";
|
||||||
|
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,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const show = ref(props.show);
|
||||||
|
const emit = defineEmits(["update:show", "crop-success"]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => show.value,
|
||||||
|
(n) => {
|
||||||
|
emit("update:show", n);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(n) => {
|
||||||
|
show.value = n;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 文件选择
|
||||||
|
const onFileChange = (e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
imgSrc.value = event.target.result;
|
||||||
|
// 延迟初始化 cropper
|
||||||
|
setTimeout(initCropper, 50);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
// 清空 input
|
||||||
|
e.target.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化 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();
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("裁剪失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 销毁实例
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (cropper.value) {
|
||||||
|
cropper.value.destroy();
|
||||||
|
cropper.value = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.image-cropper {
|
||||||
|
padding: 20px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls input[type="range"] {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -16,8 +16,10 @@ import {
|
|||||||
ElSelectV2,
|
ElSelectV2,
|
||||||
ElTreeSelect,
|
ElTreeSelect,
|
||||||
ElOption,
|
ElOption,
|
||||||
|
ElDialog,
|
||||||
} from "element-plus";
|
} from "element-plus";
|
||||||
import FieldCloud from "@/components/FileCloud/index.vue";
|
import FieldCloud from "@/components/FileCloud/index.vue";
|
||||||
|
import Cropper from "@/components/Cropper/Cropper.vue";
|
||||||
import { useUpload } from "@/hooks/useUpload";
|
import { useUpload } from "@/hooks/useUpload";
|
||||||
import { useCourseForm } from "@/hooks/useCourseForm";
|
import { useCourseForm } from "@/hooks/useCourseForm";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
@@ -66,6 +68,7 @@ const dlgFileShow = ref(false);
|
|||||||
const chooseFile = () => {
|
const chooseFile = () => {
|
||||||
dlgFileShow.value = true;
|
dlgFileShow.value = true;
|
||||||
};
|
};
|
||||||
|
const dlgCutShow = ref(false);
|
||||||
|
|
||||||
const changeCourseImage = (img) => {
|
const changeCourseImage = (img) => {
|
||||||
if (!img.path) {
|
if (!img.path) {
|
||||||
@@ -73,12 +76,17 @@ const changeCourseImage = (img) => {
|
|||||||
}
|
}
|
||||||
dlgFileShow.value = false;
|
dlgFileShow.value = false;
|
||||||
formState.coverImg = fileBaseUrl + img.path;
|
formState.coverImg = fileBaseUrl + img.path;
|
||||||
|
dlgCutShow.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const choseChoose = () => {
|
const choseChoose = () => {
|
||||||
dlgFileShow.value = false;
|
dlgFileShow.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const success = (e) => {
|
||||||
|
console.log(e);
|
||||||
|
formState.coverImg = e;
|
||||||
|
};
|
||||||
// 表单提交
|
// 表单提交
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
// formRef.value
|
// formRef.value
|
||||||
@@ -331,6 +339,13 @@ onMounted(() => {
|
|||||||
@choose="changeCourseImage"
|
@choose="changeCourseImage"
|
||||||
@close="choseChoose"
|
@close="choseChoose"
|
||||||
></field-cloud>
|
></field-cloud>
|
||||||
|
|
||||||
|
<!-- 裁切-->
|
||||||
|
<Cropper
|
||||||
|
:img="formState.coverImg"
|
||||||
|
v-model:show="dlgCutShow"
|
||||||
|
@crop-success="success"
|
||||||
|
></Cropper>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user