329 lines
11 KiB
JavaScript
329 lines
11 KiB
JavaScript
"use client";
|
|
|
|
import React, { useEffect, useState, useRef } from "react";
|
|
import { DotLoading, Toast, Modal } from "antd-mobile";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { faAdd, faClose, faPlay } from "@fortawesome/free-solid-svg-icons";
|
|
import { getVideoBase64 } from "@/utils/tools";
|
|
import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd";
|
|
import ImagesMask from "@/components/ImagesMask";
|
|
import OwnImage from "../OwnImage";
|
|
export default function UploadImgs({
|
|
assets,
|
|
getImgs,
|
|
accept = "image/png, image/jpeg, image/jpg",
|
|
existImages = [],
|
|
type = 1,
|
|
videoSrc = null,
|
|
getVideoCover,
|
|
id = "uploadAvatarBtn",
|
|
maxLength = 9,
|
|
}) {
|
|
const imagesMaskRef = useRef(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [filesUrls, setFilesUrls] = useState([]);
|
|
const [videoUrl, setVideoUrl] = useState(null);
|
|
const [frameImage, setFrameImage] = useState({ src: null, h: 0, w: 0 });
|
|
const [columns, setColumns] = useState([]);
|
|
useEffect(() => {
|
|
if (existImages.length > 0) {
|
|
setFilesUrls(
|
|
existImages.map((it, index) => ({ url: it.url, id: `${index}` }))
|
|
);
|
|
}
|
|
if (videoSrc) {
|
|
setVideoUrl(videoSrc);
|
|
}
|
|
}, [existImages]);
|
|
useEffect(() => {
|
|
getVideoCover && getVideoCover(frameImage);
|
|
}, [frameImage]);
|
|
useEffect(() => {
|
|
if (assets && assets.length > 0) {
|
|
const list = assets.map((it, index) => ({
|
|
url: it.url || URL.createObjectURL(it),
|
|
id: `${index}`,
|
|
}));
|
|
setFilesUrls(list);
|
|
// 将filesUrls转变为每组四个的二维数组
|
|
handleMakeColumns(list);
|
|
} else {
|
|
setFilesUrls([]);
|
|
setColumns([]);
|
|
}
|
|
}, [assets]);
|
|
|
|
const handleUploadImage = async (e) => {
|
|
let file = e.target.files[0];
|
|
if (!file) return;
|
|
if (e.target.files.length + assets.length > maxLength) {
|
|
Toast.show({
|
|
icon: "fail",
|
|
content: "最多上传" + maxLength + "张图片",
|
|
position: "top",
|
|
});
|
|
return;
|
|
}
|
|
var videoUrl = URL.createObjectURL(file);
|
|
var videoObj = document.createElement("video");
|
|
const eles = Array.from(e.target.files);
|
|
if (type == 1) {
|
|
const newFils = eles.map((it, index) => ({
|
|
url: URL.createObjectURL(it),
|
|
id: it.name,
|
|
}));
|
|
setFilesUrls((old) => [...old, ...newFils]);
|
|
} else {
|
|
videoObj.onloadedmetadata = function (evt) {
|
|
URL.revokeObjectURL(videoUrl);
|
|
const target = evt.target;
|
|
setFrameImage({
|
|
src: videoUrl,
|
|
w: target.videoWidth,
|
|
h: target.videoHeight,
|
|
});
|
|
setLoading(true);
|
|
// let newFilesUrls = [...filesUrls];
|
|
|
|
if (type == 2) {
|
|
if (typeof window == "undefined") return;
|
|
// newFiles = [...assets, file];
|
|
// setFileList(newAssets);
|
|
creatVideoCanvas(file);
|
|
setVideoUrl(URL.createObjectURL(file));
|
|
}
|
|
// setFileList(newFiles);
|
|
setLoading(false);
|
|
};
|
|
|
|
videoObj.src = videoUrl;
|
|
videoObj.load();
|
|
}
|
|
// console.log("assets", [...assets, ...eles]);
|
|
getImgs([...assets, ...eles]);
|
|
};
|
|
const handleRemoveItem = (index) => {
|
|
let newArr = [...filesUrls];
|
|
let newAssets = [...assets];
|
|
newArr.splice(index, 1);
|
|
newAssets.splice(index, 1);
|
|
setFilesUrls(newAssets);
|
|
getImgs(newAssets);
|
|
};
|
|
const showPhotos = (images, index) => {
|
|
if (type == 1) {
|
|
imagesMaskRef.current.show(images, index);
|
|
} else {
|
|
Modal.show({
|
|
content: (
|
|
<video
|
|
autoPlay
|
|
playsInline
|
|
controls
|
|
muted={true}
|
|
controlsList="nodownload"
|
|
>
|
|
<source src={videoUrl} />
|
|
</video>
|
|
),
|
|
closeOnMaskClick: true,
|
|
bodyStyle: {
|
|
background: "none",
|
|
maxHeight: "100vh",
|
|
},
|
|
});
|
|
}
|
|
};
|
|
const creatVideoCanvas = (file) => {
|
|
if (typeof window == "undefined") return;
|
|
// const videoD = document.getElementById("video_upload");
|
|
const url = URL.createObjectURL(file);
|
|
// videoD.src = url;
|
|
getVideoBase64(url).then((src) => {
|
|
setFrameImage((old) => ({ ...old, src }));
|
|
const list = [{ url: src, id: "0" }];
|
|
setFilesUrls(list);
|
|
handleMakeColumns(list);
|
|
});
|
|
};
|
|
const handleMakeColumns = (list) => {
|
|
const newList = [];
|
|
for (let i = 0; i < list.length; i += 4) {
|
|
newList.push(list.slice(i, i + 4));
|
|
}
|
|
const newColumns = newList.map((it, index) => {
|
|
return {
|
|
id: "column-" + index,
|
|
title: "Column " + index,
|
|
items: it,
|
|
};
|
|
});
|
|
setColumns(newColumns);
|
|
};
|
|
const reorder = (list, startIndex, endIndex) => {
|
|
const result = Array.from(list);
|
|
const removing = result[startIndex];
|
|
const removed = result[endIndex];
|
|
result[startIndex] = removed;
|
|
result[endIndex] = removing;
|
|
return result;
|
|
};
|
|
const onDragEnd = (result) => {
|
|
const { source, destination, draggableId } = result;
|
|
|
|
if (!destination) return;
|
|
|
|
if (
|
|
source.droppableId === destination.droppableId &&
|
|
source.index === destination.index
|
|
) {
|
|
return;
|
|
}
|
|
const newList = reorder(
|
|
assets,
|
|
parseInt(source.droppableId.match(/\d+/)[0], 10) * 4 + source.index,
|
|
parseInt(destination.droppableId.match(/\d+/)[0], 10) * 4 +
|
|
destination.index
|
|
);
|
|
const start = columns.find((column) => column.id === source.droppableId);
|
|
const end = columns.find((column) => column.id === destination.droppableId);
|
|
const startItems = Array.from(start.items);
|
|
let endItems = Array.from(end.items);
|
|
if (destination.droppableId === source.droppableId) {
|
|
const itemToMove = startItems[source.index];
|
|
startItems.splice(source.index, 1);
|
|
startItems.splice(destination.index, 0, itemToMove);
|
|
const newColumns = columns.map((column) => {
|
|
if (column.id === start.id) {
|
|
return { ...column, items: startItems };
|
|
}
|
|
if (column.id === end.id) {
|
|
return { ...column, items: endItems };
|
|
}
|
|
return column;
|
|
});
|
|
setColumns(newColumns);
|
|
getImgs([...newList]);
|
|
} else {
|
|
const newColumns = columns.map((column) => {
|
|
if (column.id === start.id) {
|
|
startItems.splice(source.index, 1, end.items[destination.index]);
|
|
return { ...column, items: startItems };
|
|
}
|
|
if (column.id === end.id) {
|
|
endItems.splice(destination.index, 1, start.items[source.index]);
|
|
return { ...column, items: endItems };
|
|
}
|
|
|
|
return column;
|
|
});
|
|
setColumns(newColumns);
|
|
// console.log("newList", newList);
|
|
getImgs([...newList]);
|
|
}
|
|
};
|
|
return (
|
|
<div className="event-none overflow-hidden">
|
|
<DragDropContext onDragEnd={onDragEnd}>
|
|
{columns.map((column, index) => (
|
|
<Droppable
|
|
direction="horizontal"
|
|
key={column.id}
|
|
droppableId={column.id}
|
|
>
|
|
{(provided) => (
|
|
<div
|
|
className="droppable-area grid grid-cols-4 gap-1"
|
|
style={{ height: "calc(25vw - 0.75rem)" }}
|
|
{...provided.droppableProps}
|
|
ref={provided.innerRef}
|
|
>
|
|
{column.items.map((item, ind) => (
|
|
<Draggable key={item.id} draggableId={item.id} index={ind}>
|
|
{(provided) => (
|
|
<div
|
|
ref={provided.innerRef}
|
|
{...provided.draggableProps}
|
|
{...provided.dragHandleProps}
|
|
>
|
|
<div
|
|
key={ind}
|
|
className="rounded relative mr-1 mb-1 h-full"
|
|
>
|
|
<div
|
|
onClick={() =>
|
|
showPhotos(filesUrls, index * 4 + ind)
|
|
}
|
|
style={{ height: "calc(25vw - 0.75rem)" }}
|
|
className="h-full pb-1"
|
|
>
|
|
<OwnImage
|
|
src={item.url}
|
|
outClassName="w-full h-full"
|
|
className="w-full h-full"
|
|
rounded="rounded"
|
|
fit="cover"
|
|
/>
|
|
</div>
|
|
<div
|
|
className="h-6 w-6 bg-[#33333380] absolute top-0 right-0 flex justify-center items-center rounded-bl z-50"
|
|
onClick={() => handleRemoveItem(index * 4 + ind)}
|
|
>
|
|
<FontAwesomeIcon icon={faClose} size="xl" />
|
|
</div>
|
|
{type == 2 && (
|
|
<div
|
|
className="absolute top-1/2 left-1/2 flex justify-center items-center -mt-2 -ml-1 z-50"
|
|
onClick={() => showPhotos(filesUrls)}
|
|
>
|
|
<FontAwesomeIcon icon={faPlay} size="xl" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Draggable>
|
|
))}
|
|
{provided.placeholder}
|
|
</div>
|
|
)}
|
|
</Droppable>
|
|
))}
|
|
</DragDropContext>
|
|
{loading && (
|
|
<div className="rounded border-[#ffffff80] text-[#ffffff80] flex flex-col justify-center items-center">
|
|
<DotLoading />
|
|
<p>上传中</p>
|
|
</div>
|
|
)}
|
|
<div className="grid grid-cols-4 gap-1">
|
|
{type == 2 && filesUrls.length > 0 ? null : (
|
|
<>
|
|
<label htmlFor={id}>
|
|
<div
|
|
className="border-2 border-[#ffffff80] text-[#ffffff80] rounded border-dashed w-full h-full flex justify-center items-center"
|
|
style={{ minHeight: "calc(25vw - 0.75rem)" }}
|
|
>
|
|
<div style={{ maxWidth: "24px" }}>
|
|
<FontAwesomeIcon icon={faAdd} size="2xl" />
|
|
</div>
|
|
</div>
|
|
</label>
|
|
<input
|
|
type="file"
|
|
multiple={type == 1}
|
|
id={id}
|
|
style={{ display: "none" }}
|
|
// accept="image/png, image/jpeg, video/*"
|
|
accept={accept}
|
|
// capture="camera"
|
|
onChange={handleUploadImage}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
<ImagesMask ref={imagesMaskRef} isEditing={true} />
|
|
</div>
|
|
);
|
|
}
|