add
This commit is contained in:
parent
a5552dba6b
commit
982b45bd63
|
@ -33,12 +33,13 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { handleFileUpload, uploadProgress } = useTusUpload();
|
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||||||
const [file, setFile] = useState<UploadingFile | null>(null);
|
const [file, setFile] = useState<UploadingFile | null>(null);
|
||||||
|
const avatarRef = useRef<HTMLImageElement>(null);
|
||||||
const [previewUrl, setPreviewUrl] = useState<string>(value || "");
|
const [previewUrl, setPreviewUrl] = useState<string>(value || "");
|
||||||
const [url, setUrl] = useState<string>(value || "");
|
const [url, setUrl] = useState<string>(value || "");
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
// 在组件中定义 key 状态
|
||||||
|
const [avatarKey, setAvatarKey] = useState(0);
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
|
|
||||||
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -77,7 +78,10 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
file?.fileKey
|
file?.fileKey
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
// await new Promise((resolve) => setTimeout(resolve, 5000)); // 方法1:使用 await 暂停执行
|
||||||
// 使用 resolved 的最新值调用 onChange
|
// 使用 resolved 的最新值调用 onChange
|
||||||
|
// 强制刷新 Avatar 组件
|
||||||
|
setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载
|
||||||
onChange?.(uploadedUrl);
|
onChange?.(uploadedUrl);
|
||||||
message.success("头像上传成功");
|
message.success("头像上传成功");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -111,6 +115,8 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
/>
|
/>
|
||||||
{previewUrl ? (
|
{previewUrl ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
key={avatarKey}
|
||||||
|
ref={avatarRef}
|
||||||
src={previewUrl}
|
src={previewUrl}
|
||||||
shape="square"
|
shape="square"
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
|
|
|
@ -1,29 +1,20 @@
|
||||||
// TusUploader.tsx
|
import { useCallback, useState } from "react";
|
||||||
import {
|
|
||||||
useCallback,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
forwardRef,
|
|
||||||
useImperativeHandle,
|
|
||||||
} from "react";
|
|
||||||
import {
|
import {
|
||||||
UploadOutlined,
|
UploadOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Upload, Progress, Button } from "antd";
|
import { Upload, Progress, Button } from "antd";
|
||||||
|
import type { UploadFile } from "antd";
|
||||||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { getCompressedImageUrl } from "@nice/utils";
|
||||||
|
|
||||||
export interface TusUploaderProps {
|
export interface TusUploaderProps {
|
||||||
value?: string[];
|
value?: string[];
|
||||||
onChange?: (value: string[]) => void;
|
onChange?: (value: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TusUploaderRef {
|
|
||||||
reset: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UploadingFile {
|
interface UploadingFile {
|
||||||
name: string;
|
name: string;
|
||||||
progress: number;
|
progress: number;
|
||||||
|
@ -32,50 +23,35 @@ interface UploadingFile {
|
||||||
fileKey?: string;
|
fileKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
({ value = [], onChange }, ref) => {
|
|
||||||
const { handleFileUpload, uploadProgress } = useTusUpload();
|
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||||||
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>(
|
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>(
|
const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>(
|
||||||
[]
|
() =>
|
||||||
);
|
value?.map((fileId) => ({
|
||||||
|
|
||||||
// 同步父组件value到completedFiles
|
|
||||||
useEffect(() => {
|
|
||||||
setCompletedFiles(
|
|
||||||
value.map((fileId) => ({
|
|
||||||
name: `文件 ${fileId}`,
|
name: `文件 ${fileId}`,
|
||||||
progress: 100,
|
progress: 100,
|
||||||
status: "done" as const,
|
status: "done" as const,
|
||||||
fileId,
|
fileId,
|
||||||
}))
|
})) || []
|
||||||
);
|
);
|
||||||
}, [value]);
|
const [uploadResults, setUploadResults] = useState<string[]>(value || []);
|
||||||
|
|
||||||
// 暴露重置方法
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
reset: () => {
|
|
||||||
setCompletedFiles([]);
|
|
||||||
setUploadingFiles([]);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const handleRemoveFile = useCallback(
|
const handleRemoveFile = useCallback(
|
||||||
(fileId: string) => {
|
(fileId: string) => {
|
||||||
setCompletedFiles((prev) =>
|
setCompletedFiles((prev) => prev.filter((f) => f.fileId !== fileId));
|
||||||
prev.filter((f) => f.fileId !== fileId)
|
setUploadResults((prev) => {
|
||||||
);
|
const newValue = prev.filter((id) => id !== fileId);
|
||||||
onChange?.(value.filter((id) => id !== fileId));
|
onChange?.(newValue);
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[onChange, value]
|
[onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 新增:处理删除上传中的失败文件
|
||||||
const handleRemoveUploadingFile = useCallback((fileKey: string) => {
|
const handleRemoveUploadingFile = useCallback((fileKey: string) => {
|
||||||
setUploadingFiles((prev) =>
|
setUploadingFiles((prev) => prev.filter((f) => f.fileKey !== fileKey));
|
||||||
prev.filter((f) => f.fileKey !== fileKey)
|
|
||||||
);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleBeforeUpload = useCallback(
|
const handleBeforeUpload = useCallback(
|
||||||
|
@ -95,11 +71,25 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
handleFileUpload(
|
handleFileUpload(
|
||||||
file,
|
file,
|
||||||
(result) => {
|
(result) => {
|
||||||
const newValue = [...value, result.fileId];
|
setCompletedFiles((prev) => [
|
||||||
onChange?.(newValue);
|
...prev,
|
||||||
|
{
|
||||||
|
name: file.name,
|
||||||
|
progress: 100,
|
||||||
|
status: "done",
|
||||||
|
fileId: result.fileId,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
setUploadingFiles((prev) =>
|
setUploadingFiles((prev) =>
|
||||||
prev.filter((f) => f.fileKey !== fileKey)
|
prev.filter((f) => f.fileKey !== fileKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setUploadResults((prev) => {
|
||||||
|
const newValue = [...prev, result.fileId];
|
||||||
|
onChange?.(newValue);
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error("上传错误:", error);
|
console.error("上传错误:", error);
|
||||||
|
@ -108,9 +98,7 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
);
|
);
|
||||||
setUploadingFiles((prev) =>
|
setUploadingFiles((prev) =>
|
||||||
prev.map((f) =>
|
prev.map((f) =>
|
||||||
f.fileKey === fileKey
|
f.fileKey === fileKey ? { ...f, status: "error" } : f
|
||||||
? { ...f, status: "error" }
|
|
||||||
: f
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -119,7 +107,7 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
[handleFileUpload, onChange, value]
|
[handleFileUpload, onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -129,20 +117,20 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
multiple
|
multiple
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
style={{ background: "transparent", borderStyle: "none" }}
|
style={{ background: "transparent", borderStyle: "none" }}
|
||||||
beforeUpload={handleBeforeUpload}>
|
beforeUpload={handleBeforeUpload}
|
||||||
|
>
|
||||||
<p className="ant-upload-drag-icon">
|
<p className="ant-upload-drag-icon">
|
||||||
<UploadOutlined />
|
<UploadOutlined />
|
||||||
</p>
|
</p>
|
||||||
<p className="ant-upload-text">
|
<p className="ant-upload-text">点击或拖拽文件到此区域进行上传</p>
|
||||||
点击或拖拽文件到此区域进行上传
|
|
||||||
</p>
|
|
||||||
<p className="ant-upload-hint">支持单个或批量上传文件</p>
|
<p className="ant-upload-hint">支持单个或批量上传文件</p>
|
||||||
|
|
||||||
<div className="px-2 py-0 rounded mt-1">
|
<div className="px-2 py-0 rounded mt-1">
|
||||||
{uploadingFiles.map((file) => (
|
{uploadingFiles.map((file) => (
|
||||||
<div
|
<div
|
||||||
key={file.fileKey}
|
key={file.fileKey}
|
||||||
className="flex flex-col gap-1 mb-2">
|
className="flex flex-col gap-1 mb-2"
|
||||||
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm">{file.name}</span>
|
<span className="text-sm">{file.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -151,17 +139,9 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
percent={
|
percent={
|
||||||
file.status === "done"
|
file.status === "done"
|
||||||
? 100
|
? 100
|
||||||
: Math.round(
|
: Math.round(uploadProgress?.[file.fileKey!] || 0)
|
||||||
uploadProgress?.[
|
|
||||||
file.fileKey!
|
|
||||||
] || 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
status={
|
|
||||||
file.status === "error"
|
|
||||||
? "exception"
|
|
||||||
: "active"
|
|
||||||
}
|
}
|
||||||
|
status={file.status === "error" ? "exception" : "active"}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
{file.status === "error" && (
|
{file.status === "error" && (
|
||||||
|
@ -171,10 +151,7 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (file.fileKey)
|
if (file.fileKey) handleRemoveUploadingFile(file.fileKey);
|
||||||
handleRemoveUploadingFile(
|
|
||||||
file.fileKey
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -185,7 +162,8 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
{completedFiles.map((file) => (
|
{completedFiles.map((file) => (
|
||||||
<div
|
<div
|
||||||
key={file.fileId}
|
key={file.fileId}
|
||||||
className="flex items-center justify-between gap-2 mb-2">
|
className="flex items-center justify-between gap-2 mb-2"
|
||||||
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCircleOutlined className="text-green-500" />
|
<CheckCircleOutlined className="text-green-500" />
|
||||||
<span className="text-sm">{file.name}</span>
|
<span className="text-sm">{file.name}</span>
|
||||||
|
@ -196,8 +174,7 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (file.fileId)
|
if (file.fileId) handleRemoveFile(file.fileId);
|
||||||
handleRemoveFile(file.fileId);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -206,5 +183,4 @@ export const TusUploader = forwardRef<TusUploaderRef, TusUploaderProps>(
|
||||||
</Upload.Dragger>
|
</Upload.Dragger>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
);
|
|
|
@ -121,9 +121,7 @@ export default function StaffForm() {
|
||||||
<AvatarUploader
|
<AvatarUploader
|
||||||
placeholder="点击上传头像"
|
placeholder="点击上传头像"
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
onChange={(value) => {
|
|
||||||
console.log(value);
|
|
||||||
}}
|
|
||||||
style={{
|
style={{
|
||||||
width: "120px",
|
width: "120px",
|
||||||
height: "150px",
|
height: "150px",
|
||||||
|
|
|
@ -34,6 +34,9 @@ export default function PostCommentCard({
|
||||||
layout>
|
layout>
|
||||||
<div className="flex items-start space-x-2 gap-2">
|
<div className="flex items-start space-x-2 gap-2">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
|
{/* <a href={post.author?.meta?.photoUrl}>
|
||||||
|
{post.author?.meta?.photoUrl}
|
||||||
|
</a> */}
|
||||||
<CustomAvatar
|
<CustomAvatar
|
||||||
src={post.author?.meta?.photoUrl}
|
src={post.author?.meta?.photoUrl}
|
||||||
size={50}
|
size={50}
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default function PostCommentEditor() {
|
||||||
const [signature, setSignature] = useState<string | undefined>(undefined);
|
const [signature, setSignature] = useState<string | undefined>(undefined);
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
const [fileIds, setFileIds] = useState<string[]>([]);
|
||||||
const { create } = usePost();
|
const { create } = usePost();
|
||||||
const uploaderRef = useRef<{ reset: () => void }>(null);
|
const [uploaderKey, setUploaderKey] = useState<number>(0);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -48,9 +48,7 @@ export default function PostCommentEditor() {
|
||||||
toast.success("发布成功!");
|
toast.success("发布成功!");
|
||||||
setContent("");
|
setContent("");
|
||||||
setFileIds([]); // 重置上传组件状态
|
setFileIds([]); // 重置上传组件状态
|
||||||
// if (uploaderRef.current) {
|
setUploaderKey(uploaderKey + 1);
|
||||||
// uploaderRef.current.reset();
|
|
||||||
// }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("发布失败,请稍后重试");
|
toast.error("发布失败,请稍后重试");
|
||||||
console.error("Error posting comment:", error);
|
console.error("Error posting comment:", error);
|
||||||
|
@ -90,7 +88,7 @@ export default function PostCommentEditor() {
|
||||||
<TabPane tab="附件" key="2">
|
<TabPane tab="附件" key="2">
|
||||||
<div className="relative rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
<div className="relative rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
||||||
<TusUploader
|
<TusUploader
|
||||||
ref={uploaderRef}
|
key={uploaderKey}
|
||||||
value={fileIds}
|
value={fileIds}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
console.log("ids", value);
|
console.log("ids", value);
|
||||||
|
|
|
@ -81,12 +81,12 @@ export function LetterBasicForm() {
|
||||||
<Form.Item name="resources" required={false}>
|
<Form.Item name="resources" required={false}>
|
||||||
<div className="rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
<div className="rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
||||||
<TusUploader
|
<TusUploader
|
||||||
onChange={(resources) =>
|
onChange={async (resources) => {
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
"resources",
|
"resources",
|
||||||
resources
|
resources
|
||||||
)
|
);
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
Loading…
Reference in New Issue