Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
ee087f0dbd
|
@ -3,7 +3,7 @@ import { useContext, useEffect, useState } from "react";
|
|||
import { Button, Form, Input, message, theme } from "antd";
|
||||
import { useAppConfig } from "@nice/client";
|
||||
import { useAuth } from "@web/src/providers/auth-provider";
|
||||
import MultiImageUploader from "@web/src/components/common/uploader/MultiImageUploader";
|
||||
import MultiAvatarUploader from "@web/src/components/common/uploader/MultiAvatarUploader";
|
||||
|
||||
import FixedHeader from "@web/src/components/layout/fix-header";
|
||||
import { useForm } from "antd/es/form/Form";
|
||||
|
@ -129,9 +129,9 @@ export default function BaseSettingPage() {
|
|||
</div>
|
||||
<div className="p-2 grid grid-cols-8 gap-2 border-b">
|
||||
<Form.Item
|
||||
label="运维单位"
|
||||
label="首页轮播图"
|
||||
name={["appConfig", "slides"]}>
|
||||
<MultiImageUploader></MultiImageUploader>
|
||||
<MultiAvatarUploader></MultiAvatarUploader>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{/* <div
|
||||
|
@ -174,7 +174,7 @@ export default function BaseSettingPage() {
|
|||
}
|
||||
}}
|
||||
type="primary"
|
||||
ghost>
|
||||
ghost>
|
||||
清除行模型缓存
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -12,6 +12,8 @@ export interface AvatarUploaderProps {
|
|||
onChange?: (value: string) => void;
|
||||
compressed?: boolean;
|
||||
style?: React.CSSProperties; // 添加style属性
|
||||
successText?: string;
|
||||
showCover?: boolean;
|
||||
}
|
||||
|
||||
interface UploadingFile {
|
||||
|
@ -31,6 +33,8 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
|||
className,
|
||||
placeholder = "点击上传",
|
||||
style, // 解构style属性
|
||||
successText = "上传成功",
|
||||
showCover = true,
|
||||
}) => {
|
||||
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||||
const [file, setFile] = useState<UploadingFile | null>(null);
|
||||
|
@ -92,12 +96,12 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
|||
// 使用 resolved 的最新值调用 onChange
|
||||
// 强制刷新 Avatar 组件
|
||||
setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载
|
||||
onChange?.(uploadedUrl);
|
||||
console.log(uploadedUrl);
|
||||
toast.success("头像上传成功");
|
||||
onChange?.(uploadedUrl);
|
||||
toast.success(successText);
|
||||
} catch (error) {
|
||||
console.error("上传错误:", error);
|
||||
toast.error("头像上传失败");
|
||||
toast.error("上传失败");
|
||||
setFile((prev) => ({ ...prev!, status: "error" }));
|
||||
} finally {
|
||||
setUploading(false);
|
||||
|
@ -124,7 +128,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
|||
accept="image/*"
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
{previewUrl ? (
|
||||
{(previewUrl && showCover) ? (
|
||||
<Avatar
|
||||
key={avatarKey}
|
||||
ref={avatarRef}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { useEffect, useState } from "react";
|
||||
// import UncoverAvatarUploader from "../uploader/UncoverAvatarUploader ";
|
||||
import { Upload, Progress, Button, Image, Form } from "antd";
|
||||
import { DeleteOutlined } from "@ant-design/icons";
|
||||
import AvatarUploader from "./AvatarUploader";
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
interface MultiAvatarUploaderProps {
|
||||
value?: string[];
|
||||
onChange?: (value: string[]) => void;
|
||||
}
|
||||
|
||||
export function MultiAvatarUploader({
|
||||
value,
|
||||
onChange,
|
||||
}: MultiAvatarUploaderProps) {
|
||||
const [imageList, setImageList] = useState<string[]>(value || [])
|
||||
const [previewImage, setPreviewImage] = useState<string>("");
|
||||
useEffect(() => {
|
||||
if (!isEqual(value, imageList)) {
|
||||
setImageList(value || []);
|
||||
}
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
onChange?.(imageList)
|
||||
}, [imageList])
|
||||
return <>
|
||||
<div className="flex gap-2 mb-2" style={{ width: "1200px" }}>
|
||||
{imageList.map((image, index) => {
|
||||
return (
|
||||
<div className="mr-2px relative" key={index} style={{ width: "200px", height: "100px" }} >
|
||||
<Image alt="" style={{ width: "200px", height: "100px" }} src={image}
|
||||
preview={{
|
||||
visible: previewImage === image,
|
||||
onVisibleChange: (visible) =>
|
||||
setPreviewImage(visible ? image || "" : "")
|
||||
}} >
|
||||
</Image>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined className="text-red" />}
|
||||
onClick={() => image && setImageList(imageList.filter((_, i) => i !== index))}
|
||||
style={{
|
||||
position: "absolute", // 绝对定位
|
||||
top: "0", // 顶部对齐
|
||||
right: "0", // 右侧对齐
|
||||
zIndex: 1, // 确保按钮在图片上方
|
||||
padding: "4px", // 调整按钮内边距
|
||||
backgroundColor: "rgba(255, 255, 255, 0.2)", // 半透明背景
|
||||
borderRadius: "50%", // 圆形按钮
|
||||
}}
|
||||
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<AvatarUploader showCover={false} successText={'轮播图上传成功'} onChange={(value) => {
|
||||
console.log(value);
|
||||
setImageList([...imageList, value])
|
||||
}}></AvatarUploader>
|
||||
</div>
|
||||
|
||||
</>;
|
||||
}
|
||||
export default MultiAvatarUploader;
|
|
@ -1,173 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Upload, Modal, message } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
|
||||
export interface MultiImageUploadProps {
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
onChange?: (value: string) => void;
|
||||
compressed?: boolean;
|
||||
style?: React.CSSProperties; // 添加style属性
|
||||
}
|
||||
|
||||
interface UploadFile {
|
||||
name: string;
|
||||
progress: number;
|
||||
status: "uploading" | "done" | "error";
|
||||
url?: string;
|
||||
fileKey?: string;
|
||||
uid: string;
|
||||
}
|
||||
const MultiImageUpload: React.FC<MultiImageUploadProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
compressed = false,
|
||||
className,
|
||||
placeholder = "点击上传",
|
||||
style, // 解构style属性
|
||||
}) => {
|
||||
const [fileList, setFileList] = useState<UploadFile[] | null>(null); // 存储已上传的文件列表
|
||||
const [previewVisible, setPreviewVisible] = useState(false); // 控制预览模态框的显示
|
||||
const [previewImage, setPreviewImage] = useState(''); // 当前预览的图片URL
|
||||
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||||
const [compressedUrl, setCompressedUrl] = useState<string>(value || "");
|
||||
const [url, setUrl] = useState<string>(value || "");
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
// 处理文件上传前的校验
|
||||
const beforeUpload = (file) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件!');
|
||||
}
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
if (!isLt10M) {
|
||||
message.error('图片大小不能超过10MB!');
|
||||
}
|
||||
return isImage && isLt10M;
|
||||
};
|
||||
|
||||
// 处理文件列表变化
|
||||
const handleChange = async ({ fileList }) => {
|
||||
//setFileList(fileList);
|
||||
console.log(fileList);
|
||||
const imageUrls = fileList.map(file => {
|
||||
return URL.createObjectURL(file.originFileObj)
|
||||
});
|
||||
console.log("imageUrls", imageUrls);
|
||||
|
||||
const newFileList = fileList.map(file => {
|
||||
return {
|
||||
name: file.name,
|
||||
progress: 0,
|
||||
status: "uploading",
|
||||
//uid: file.uid,
|
||||
fileKey: `${file.name}-${Date.now()}`,
|
||||
//url: file.url,
|
||||
}
|
||||
});
|
||||
console.log("newFileList", newFileList);
|
||||
setUploading(true);
|
||||
|
||||
try {
|
||||
const resFileList = newFileList.map(async (file, index) => {
|
||||
const uploadedUrl = await new Promise<string>((resolve, reject) => {
|
||||
handleFileUpload(
|
||||
fileList[index].originFileObj,
|
||||
(result) => {
|
||||
() => {
|
||||
return {
|
||||
...newFileList[index],
|
||||
progress: 100,
|
||||
status: "done",
|
||||
fileId: result.fileId,
|
||||
url: result.url,
|
||||
compressedUrl: result.compressedUrl,
|
||||
}
|
||||
}
|
||||
// setFile((prev) => ({
|
||||
// ...prev!,
|
||||
// progress: 100,
|
||||
// status: "done",
|
||||
// fileId: result.fileId,
|
||||
// url: result.url,
|
||||
// compressedUrl: result.compressedUrl,
|
||||
// }));
|
||||
setUrl(result.url);
|
||||
setCompressedUrl(result.compressedUrl);
|
||||
// 直接使用 result 中的最新值
|
||||
resolve(compressed ? result.compressedUrl : result.url);
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
},
|
||||
newFileList[index]?.fileKey
|
||||
);
|
||||
});
|
||||
})
|
||||
// await new Promise((resolve) => setTimeout(resolve,4999)); // 方法1:使用 await 暂停执行
|
||||
// 使用 resolved 的最新值调用 onChange
|
||||
// 强制刷新 Avatar 组件
|
||||
// setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载
|
||||
onChange?.(resFileList);
|
||||
console.log(resFileList);
|
||||
toast.success("图片上传成功");
|
||||
} catch (error) {
|
||||
console.error("上传错误:", error);
|
||||
toast.error("图片上传失败");
|
||||
// setFile((prev) => ({ ...prev!, status: "error" }));
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};uploadProgress
|
||||
|
||||
// 处理预览
|
||||
const handlePreview = async (file) => {
|
||||
if (!file.url && !file.preview) {
|
||||
file.preview = await getBase64(file.originFileObj);
|
||||
}
|
||||
setPreviewImage(file.url || file.preview);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
// 关闭预览模态框
|
||||
const handleCancel = () => setPreviewVisible(false);
|
||||
|
||||
// 将文件转换为Base64格式(用于预览)
|
||||
const getBase64 = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Upload.Dragger
|
||||
listType="picture-card" // 卡片样式
|
||||
fileList={fileList} // 已上传的文件列表
|
||||
beforeUpload={beforeUpload} // 上传前的校验
|
||||
onChange={handleChange} // 文件列表变化时的回调
|
||||
onPreview={handlePreview} // 预览回调
|
||||
multiple // 支持多选
|
||||
// style={{ width: '200px' }}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
</p>
|
||||
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||
<p className="ant-upload-hint">支持单个或批量上传</p>
|
||||
</Upload.Dragger>
|
||||
<Modal visible={previewVisible} footer={null} onCancel={handleCancel}>
|
||||
<img alt="预览" style={{ width: '100%' }} src={previewImage} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiImageUpload;
|
|
@ -1,18 +0,0 @@
|
|||
import AvatarUploader from "./AvatarUploader"
|
||||
|
||||
|
||||
|
||||
interface TestUploaderProps {
|
||||
value?: string[],
|
||||
onChange?: (value: string[]) => void
|
||||
}
|
||||
|
||||
export function TestUploader({
|
||||
value,
|
||||
onChange,
|
||||
}: TestUploaderProps) {
|
||||
|
||||
return <>
|
||||
<AvatarUploader></AvatarUploader>
|
||||
</>
|
||||
}
|
Loading…
Reference in New Issue