@cmstops/pro-compo
Version:
[物料平台文档中心](https://arco.design/docs/material/guide)
499 lines (498 loc) • 18.4 kB
JavaScript
import { defineComponent, ref, watch, onBeforeUnmount, openBlock, createBlock, unref, withCtx, createTextVNode, toDisplayString, createElementVNode, createVNode, withDirectives, vShow, normalizeClass, createElementBlock, createCommentVNode, Fragment, renderList } from "vue";
import { Modal, Tooltip, Button, Space, Spin } from "@arco-design/web-vue";
import { IconZoomIn, IconZoomOut, IconRotateLeft, IconRotateRight, IconSync } from "@arco-design/web-vue/es/icon";
import Cropper from "cropperjs";
import ColorThief from "colorthief";
import { GifToCanvas } from "gif-to-canvas";
import GIF from "gif.js/dist/gif";
import { uploadByTUS } from "../utils/tusUpload.js";
import { DEFAULT_BASE_API, DEFAULT_UPLOAD_URL } from "../config.js";
import { replaceSuffix } from "../utils/index.js";
import { getFileRealUrl } from "./script/api.js";
import { useGenerateAssets } from "../hooks/assets.js";
const _hoisted_1 = { class: "edit-cropper" };
const _hoisted_2 = ["src"];
const _hoisted_3 = {
key: 0,
class: "tool"
};
const _hoisted_4 = ["onClick"];
const _hoisted_5 = {
key: 1,
class: "tool"
};
const _hoisted_6 = ["onClick"];
const _hoisted_7 = { class: "image-crop-modal-footer" };
const _hoisted_8 = { class: "left" };
const _hoisted_9 = { class: "right" };
const _sfc_main = defineComponent({
__name: "component",
props: {
BASE_API: {},
visible: { type: Boolean },
aspectRatioProp: {},
corpData: {},
userInfo: {},
generateAssets: { type: Boolean }
},
emits: ["update:visible", "confirm"],
setup(__props, { emit: __emit }) {
const emit = __emit;
const props = __props;
const BASE_API = props.BASE_API || DEFAULT_BASE_API;
const saveLoading = ref(false);
const editImage = ref("");
const setVisible = (bool) => {
emit("update:visible", bool);
};
const cancelDialog = () => {
setVisible(false);
};
const croppering = ref(true);
const aspectRatioList = [
{ label: "\u81EA\u7531", value: null },
{ label: "1:1", value: 1 / 1 },
{ label: "4:3", value: 4 / 3 },
{ label: "3:4", value: 3 / 4 },
{ label: "16:9", value: 16 / 9 },
{ label: "9:16", value: 9 / 16 }
];
const customizedAspectRatioList = ref([]);
const aspectRatioActive = ref(null);
const mosaicObj = ref(null);
const cropperIndex = ref(0);
const selectedData = ref([]);
const loadImage = () => {
const cropperImg = document.getElementById("cropper_img");
const editCropper = document.querySelector(".edit-cropper");
cropperImg.classList.remove("position");
if (cropperImg && editCropper) {
editCropper.style.width = `${cropperImg.offsetWidth}px`;
editCropper.style.height = `${cropperImg.offsetHeight}px`;
}
};
const getBase64 = (imgUrl) => {
window.URL = window.URL || window.webkitURL;
const xhr = new XMLHttpRequest();
const sparator = imgUrl.includes("?") ? "&" : "?";
xhr.open("get", `${imgUrl}${sparator}_t=${new Date().getTime()}`, true);
xhr.responseType = "blob";
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
const blob = xhr.response;
const oFileReader = new FileReader();
oFileReader.onloadend = (e) => {
const base64 = e.target.result;
editImage.value = base64;
setTimeout(() => {
if (props.aspectRatioProp) {
if (Array.isArray(props.aspectRatioProp)) {
cropper(props.aspectRatioProp[0], 0);
} else {
cropper(props.aspectRatioProp, null);
}
}
}, 300);
};
oFileReader.onerror = (e) => {
};
oFileReader.readAsDataURL(blob);
}
};
};
const cropper = (aspectRatio, index) => {
const image = document.getElementById("cropper_img");
aspectRatioActive.value = index;
cropperDestroy();
mosaicObj.value = new Cropper(image, {
crop() {
croppering.value = false;
},
initialAspectRatio: aspectRatio,
aspectRatio,
touchDragZoom: false,
autoCropArea: 1,
rotatable: true,
strict: false,
guides: false
});
};
const cropperZoom = (ratio) => {
mosaicObj.value.zoom(ratio);
};
const cropperRotate = (degree) => {
mosaicObj.value.rotate(degree);
};
const cropperReset = () => {
mosaicObj.value.reset();
};
const cropperDestroy = () => {
mosaicObj.value && mosaicObj.value.destroy();
};
const dataURLtoBlob = (dataurl) => {
const arr = dataurl.split(",");
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
};
const save = () => {
saveLoading.value = true;
if (mosaicObj.value) {
if (mosaicObj.value.url.includes("data:image/gif")) {
const url = URL.createObjectURL(dataURLtoBlob(mosaicObj.value.url));
const cropBoxData = mosaicObj.value.getCropBoxData();
const canvasData = mosaicObj.value.getCanvasData();
const gifToCanvas = new GifToCanvas(url, {
targetOffset: {
dx: cropBoxData.left - canvasData.left,
dy: cropBoxData.top - canvasData.top,
width: canvasData.width,
height: canvasData.height,
sWidth: cropBoxData.width,
sHeight: cropBoxData.height
}
});
gifToCanvas.init();
let gifWorker = "";
if (window.location.href.includes("localhost")) {
gifWorker = "./gif.worker.js";
} else {
gifWorker = `${BASE_API}/static/js/gif.worker.js`;
}
const gifObj = new GIF({
workers: 4,
quality: 10,
width: cropBoxData.width,
height: cropBoxData.height,
workerScript: gifWorker
});
const addFrame = (canvas, delay) => {
gifObj.addFrame(canvas, { copy: true, delay });
};
gifToCanvas.on("progress", (canvas, delay) => {
addFrame(canvas, delay);
});
gifToCanvas.on("finished", (canvas, delay) => {
addFrame(canvas, delay);
gifObj.render();
});
gifObj.on("finished", function(blob) {
handleUploadCropData(blob, "gif");
});
} else {
const CroppedCanvas = mosaicObj.value.getCroppedCanvas({
fillColor: "#fff"
});
CroppedCanvas.toBlob(
async (blob) => {
handleUploadCropData(blob, "png");
},
"image/jpeg",
0.75
);
}
} else {
saveLoading.value = false;
if (selectedData.value.length === cropperIndex.value + 1) {
emit("confirm", JSON.parse(JSON.stringify(selectedData.value)));
cancelDialog();
} else {
cropperIndex.value += 1;
cropperDestroy();
getMediaRealUrl();
}
}
};
const getUploadFileUrl = (file) => {
const pattern = /\+.*$/;
const result = file.url.replace(pattern, "").replace("upload", "static");
return { url: result, size: file.size || file.file.size };
};
const handleUploadCropData = async (blob, fileType) => {
const themeColors = getThemeColor();
const name = `\u88C1\u5207\u526F\u672C${new Date().getTime()}.${fileType}`;
const file = new File([blob], name, { type: blob.type });
const result = await uploadByTUS(file);
const resImg = getUploadFileUrl(result);
const target = selectedData.value[cropperIndex.value];
if (props.userInfo) {
const param = {
url: resImg.url,
size: resImg.size,
alias: replaceSuffix(target.alias || target.url, "\u88C1\u5207\u526F\u672C"),
repository_id: props.userInfo.repository_id,
directory_id: target.directory_id,
banner_theme_color: themeColors,
parent_id: target.id || 0
};
if (target.source === "aigc") {
param.made_by_ai = true;
}
if (!props.generateAssets) {
afterCropperAndUploadFile({
url: resImg.url,
banner_theme_color: themeColors
});
}
useGenerateAssets(BASE_API, param).then((response) => {
response.message.banner_theme_color = themeColors;
afterCropperAndUploadFile(response.message);
});
} else if (!props.generateAssets) {
afterCropperAndUploadFile({
url: resImg.url,
banner_theme_color: themeColors
});
}
};
const afterCropperAndUploadFile = (data) => {
selectedData.value[cropperIndex.value] = { ...data };
saveLoading.value = false;
if (selectedData.value.length === cropperIndex.value + 1) {
emit("confirm", JSON.parse(JSON.stringify(selectedData.value)));
cancelDialog();
} else {
cropperIndex.value += 1;
cropperDestroy();
getMediaRealUrl();
}
};
const getThemeColor = () => {
const colorThief = new ColorThief();
const img = document.getElementById("cropper_img");
let themeColors = [];
const Palette = colorThief.getPalette(img);
if (Palette) {
themeColors = Palette.slice(0, 8);
} else {
themeColors = [[255, 255, 255]];
}
return themeColors;
};
const getMediaRealUrl = () => {
const item = selectedData.value[cropperIndex.value];
const flag = !item.url.includes("/poplar/v2");
const cape = item.url.includes("cape/v1/upload");
const extented = !item.url.includes(BASE_API) && !item.url.includes(DEFAULT_UPLOAD_URL);
croppering.value = true;
if (item.realUrl && flag || cape || extented) {
getBase64(item.url);
} else {
getFileRealUrl(BASE_API, item.url).then((res) => {
getBase64(res.message);
});
}
};
watch(
() => props.visible,
(newVal) => {
if (newVal) {
init();
}
}
);
const init = () => {
cropperIndex.value = 0;
selectedData.value = props.corpData;
if (props.aspectRatioProp && Array.isArray(props.aspectRatioProp)) {
const aspectRatioLists = aspectRatioList.map((item) => {
item.buttonIndex = cropperIndex.value++;
return item;
}).filter((item) => {
return props.aspectRatioProp.includes(item.value);
});
if (aspectRatioLists.length) {
customizedAspectRatioList.value = aspectRatioLists;
}
}
getMediaRealUrl();
};
onBeforeUnmount(() => {
cropperDestroy();
});
return (_ctx, _cache) => {
return openBlock(), createBlock(unref(Modal), {
visible: _ctx.visible,
width: "986px",
"mask-closable": false,
"title-align": "center",
"unmount-on-close": "",
"modal-class": "image-crop-modal-wrapper",
"body-class": "image-crop-modal-body",
onCancel: _cache[4] || (_cache[4] = ($event) => setVisible(false))
}, {
title: withCtx(() => [
createTextVNode(toDisplayString(_ctx.aspectRatioProp ? "\u88C1\u526A\u56FE\u7247" : "\u8BF7\u9009\u62E9\u88C1\u526A\u6BD4\u4F8B"), 1)
]),
footer: withCtx(() => [
createElementVNode("span", _hoisted_7, [
createElementVNode("div", _hoisted_8, [
createVNode(unref(Tooltip), { content: "\u653E\u5927" }, {
default: withCtx(() => [
createVNode(unref(Button), {
class: "cropper-btn",
shape: "circle",
onClick: _cache[0] || (_cache[0] = ($event) => cropperZoom(0.1))
}, {
default: withCtx(() => [
createVNode(unref(IconZoomIn))
]),
_: 1
})
]),
_: 1
}),
createVNode(unref(Tooltip), { content: "\u7F29\u5C0F" }, {
default: withCtx(() => [
createVNode(unref(Button), {
class: "cropper-btn",
shape: "circle",
onClick: _cache[1] || (_cache[1] = ($event) => cropperZoom(-0.1))
}, {
default: withCtx(() => [
createVNode(unref(IconZoomOut))
]),
_: 1
})
]),
_: 1
}),
createVNode(unref(Tooltip), { content: "\u5411\u5DE6\u65CB\u8F6C45\u5EA6" }, {
default: withCtx(() => [
createVNode(unref(Button), {
class: "cropper-btn",
shape: "circle",
onClick: _cache[2] || (_cache[2] = ($event) => cropperRotate(-45))
}, {
default: withCtx(() => [
createVNode(unref(IconRotateLeft))
]),
_: 1
})
]),
_: 1
}),
createVNode(unref(Tooltip), { content: "\u5411\u53F3\u65CB\u8F6C45\u5EA6" }, {
default: withCtx(() => [
createVNode(unref(Button), {
class: "cropper-btn",
shape: "circle",
onClick: _cache[3] || (_cache[3] = ($event) => cropperRotate(45))
}, {
default: withCtx(() => [
createVNode(unref(IconRotateRight))
]),
_: 1
})
]),
_: 1
}),
createVNode(unref(Tooltip), { content: "\u91CD\u7F6E" }, {
default: withCtx(() => [
createVNode(unref(Button), {
class: "cropper-btn",
shape: "circle",
onClick: cropperReset
}, {
default: withCtx(() => [
createVNode(unref(IconSync))
]),
_: 1
})
]),
_: 1
})
]),
createElementVNode("div", _hoisted_9, [
createVNode(unref(Space), null, {
default: withCtx(() => [
createVNode(unref(Button), {
size: "medium",
onClick: cancelDialog
}, {
default: withCtx(() => _cache[5] || (_cache[5] = [
createTextVNode("\u53D6\u6D88")
])),
_: 1,
__: [5]
}),
withDirectives(createVNode(unref(Button), {
size: "medium",
type: "primary",
loading: saveLoading.value,
onClick: save
}, {
default: withCtx(() => _cache[6] || (_cache[6] = [
createTextVNode("\u88C1\u526A")
])),
_: 1,
__: [6]
}, 8, ["loading"]), [
[vShow, !croppering.value]
])
]),
_: 1
})
])
])
]),
default: withCtx(() => [
createVNode(unref(Spin), {
loading: croppering.value,
class: "image-cropper",
style: { "width": "100%" }
}, {
default: withCtx(() => [
createElementVNode("div", {
class: normalizeClass(["edit", {
fullHeight: _ctx.aspectRatioProp && !Array.isArray(_ctx.aspectRatioProp)
}])
}, [
createElementVNode("div", _hoisted_1, [
editImage.value ? (openBlock(), createElementBlock("img", {
key: 0,
id: "cropper_img",
class: "position",
crossorigin: "anonymous",
src: editImage.value,
onLoad: loadImage
}, null, 40, _hoisted_2)) : createCommentVNode("v-if", true)
])
], 2),
!_ctx.aspectRatioProp ? (openBlock(), createElementBlock("div", _hoisted_3, [
(openBlock(), createElementBlock(Fragment, null, renderList(aspectRatioList, (item, i) => {
return createElementVNode("div", {
key: item.lable,
class: normalizeClass(["btn btn" + (i + 1), { active: aspectRatioActive.value === i }]),
onClick: ($event) => cropper(item.value, i)
}, toDisplayString(item.label), 11, _hoisted_4);
}), 64))
])) : createCommentVNode("v-if", true),
_ctx.aspectRatioProp && Array.isArray(_ctx.aspectRatioProp) && customizedAspectRatioList.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_5, [
(openBlock(true), createElementBlock(Fragment, null, renderList(customizedAspectRatioList.value, (item, i) => {
return openBlock(), createElementBlock("div", {
key: item.lable,
class: normalizeClass([
"btn btn" + (item.buttonIndex + 1),
{ active: aspectRatioActive.value === i }
]),
onClick: ($event) => cropper(item.value, i)
}, toDisplayString(item.label), 11, _hoisted_6);
}), 128))
])) : createCommentVNode("v-if", true)
]),
_: 1
}, 8, ["loading"])
]),
_: 1
}, 8, ["visible"]);
};
}
});
export { _sfc_main as default };