From 741ff224899cb366e093312752f81b6acca10aa6 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Sat, 25 Jan 2025 23:19:03 +0800 Subject: [PATCH] add --- .../common/uploader/TusUploader.tsx | 73 +++++++----- .../models/post/detail/PostCommentEditor.tsx | 24 ++-- .../models/post/detail/PostHeader/Content.tsx | 8 +- .../models/post/detail/PostHeader/Header.tsx | 30 ++--- .../post/detail/PostHeader/StatsSection.tsx | 17 ++- apps/web/src/hooks/useTusUpload.ts | 112 ++++++++++-------- 6 files changed, 144 insertions(+), 120 deletions(-) diff --git a/apps/web/src/components/common/uploader/TusUploader.tsx b/apps/web/src/components/common/uploader/TusUploader.tsx index 64b9599..66259d7 100644 --- a/apps/web/src/components/common/uploader/TusUploader.tsx +++ b/apps/web/src/components/common/uploader/TusUploader.tsx @@ -12,22 +12,22 @@ export interface TusUploaderProps { value?: string[]; onChange?: (value: string[]) => void; } - interface UploadingFile { name: string; progress: number; status: "uploading" | "done" | "error"; fileId?: string; + fileKey?: string; } export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { - const { handleFileUpload } = useTusUpload(); + const { handleFileUpload, uploadProgress } = useTusUpload(); const [uploadingFiles, setUploadingFiles] = useState([]); const [completedFiles, setCompletedFiles] = useState( () => value?.map((fileId) => ({ - name: `文件 ${fileId}`, // 可以根据需要获取实际文件名 - progress: 1, + name: `文件 ${fileId}`, + progress: 100, status: "done" as const, fileId, })) || [] @@ -60,7 +60,9 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { name: f.name, progress: 0, status: "uploading" as const, + fileKey: `${f.name}-${Date.now()}`, // 为每个文件创建唯一标识 })); + setUploadingFiles((prev) => [...prev, ...newFiles]); const newUploadResults: string[] = []; @@ -69,6 +71,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { if (!f) { throw new Error(`文件 ${f.name} 无效`); } + const fileKey = newFiles[index].fileKey!; const fileId = await new Promise( (resolve, reject) => { handleFileUpload( @@ -77,7 +80,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { console.log("上传成功:", result); const completedFile = { name: f.name, - progress: 1, + progress: 100, status: "done" as const, fileId: result.fileId, }; @@ -86,14 +89,17 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { completedFile, ]); setUploadingFiles((prev) => - prev.filter((_, i) => i !== index) + prev.filter( + (file) => file.fileKey !== fileKey + ) ); resolve(result.fileId); }, (error) => { console.error("上传错误:", error); reject(error); - } + }, + fileKey ); } ); @@ -126,7 +132,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { name="files" multiple showUploadList={false} - style={{ background: "white" }} + style={{ background: "white", borderStyle: "solid" }} beforeUpload={handleChange}>

@@ -138,30 +144,35 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { {/* 正在上传的文件 */} {(uploadingFiles.length > 0 || completedFiles.length > 0) && (

- {uploadingFiles.length > 0 && - uploadingFiles.map((file, index) => ( -
-
-
- {file.name} -
-
- + {uploadingFiles.map((file) => ( +
+
+
{file.name}
- ))} + + +
+ ))} {completedFiles.length > 0 && completedFiles.map((file, index) => (
-
-
- + {!isContentEmpty(content) && ( +
+
+ +
-
+ )}
); diff --git a/apps/web/src/components/models/post/detail/PostHeader/Content.tsx b/apps/web/src/components/models/post/detail/PostHeader/Content.tsx index cf645ab..8f208fe 100644 --- a/apps/web/src/components/models/post/detail/PostHeader/Content.tsx +++ b/apps/web/src/components/models/post/detail/PostHeader/Content.tsx @@ -10,23 +10,23 @@ export default function Content() { const [isExpanded, setIsExpanded] = useState(false); const contentWrapperRef = useRef(null); const [shouldCollapse, setShouldCollapse] = useState(false); - + const maxHeight = 125; useEffect(() => { if (contentWrapperRef.current) { - const shouldCollapse = contentWrapperRef.current.scrollHeight > 100; + const shouldCollapse = contentWrapperRef.current.scrollHeight > 150; setShouldCollapse(shouldCollapse); } }, [post?.content]); return ( -
+
{/* 包装整个内容区域的容器 */}
{/* 内容区域 */} diff --git a/apps/web/src/components/models/post/detail/PostHeader/Header.tsx b/apps/web/src/components/models/post/detail/PostHeader/Header.tsx index ae78be5..b10ab9f 100644 --- a/apps/web/src/components/models/post/detail/PostHeader/Header.tsx +++ b/apps/web/src/components/models/post/detail/PostHeader/Header.tsx @@ -13,6 +13,7 @@ import { } from "@ant-design/icons"; import dayjs from "dayjs"; import { CornerBadge } from "../badge/CornerBadeg"; +import { LetterBadge } from "../../LetterBadge"; const { Title, Paragraph, Text } = Typography; export default function Header() { const { post, user } = useContext(PostDetailContext); @@ -88,22 +89,23 @@ export default function Header() {
{/* Tags Badges */} - - - - - - + + {(post?.terms || [])?.map((term, index) => { + return ( + + ); + })} {post.meta.tags.length > 0 && post.meta.tags.map((tag, index) => ( - - - + ))}
)} diff --git a/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx b/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx index 176dd77..553bab8 100644 --- a/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx +++ b/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx @@ -12,8 +12,7 @@ import { useVisitor } from "@nice/client"; import { VisitType } from "packages/common/dist"; import PostLikeButton from "./PostLikeButton"; export function StatsSection() { - const { post, user } = useContext(PostDetailContext); - const { like, unLike } = useVisitor(); + const { post } = useContext(PostDetailContext); return (
-
- - {post?.views} -
-
- - {post?.commentsCount} -
+ +
diff --git a/apps/web/src/hooks/useTusUpload.ts b/apps/web/src/hooks/useTusUpload.ts index 1ef2c66..d4d7d87 100644 --- a/apps/web/src/hooks/useTusUpload.ts +++ b/apps/web/src/hooks/useTusUpload.ts @@ -1,89 +1,101 @@ import { useState } from "react"; import * as tus from "tus-js-client"; +// useTusUpload.ts +interface UploadProgress { + fileId: string; + progress: number; +} + interface UploadResult { url: string; fileId: string; - // resource: any; } export function useTusUpload() { - const [progress, setProgress] = useState(0); + const [uploadProgress, setUploadProgress] = useState< + Record + >({}); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); + const getFileId = (url: string) => { const parts = url.split("/"); - // Find the index of the 'upload' segment const uploadIndex = parts.findIndex((part) => part === "upload"); if (uploadIndex === -1 || uploadIndex + 4 >= parts.length) { throw new Error("Invalid upload URL format"); } - // Get the date parts and file ID (4 segments after 'upload') return parts.slice(uploadIndex + 1, uploadIndex + 5).join("/"); }; + const handleFileUpload = async ( file: File, onSuccess: (result: UploadResult) => void, - onError: (error: Error) => void + onError: (error: Error) => void, + fileKey: string // 添加文件唯一标识 ) => { if (!file || !file.name || !file.type) { - const error = new Error('Invalid file provided'); + const error = new Error("不可上传该类型文件"); setUploadError(error.message); onError(error); return; } setIsUploading(true); - setProgress(0); + setUploadProgress((prev) => ({ ...prev, [fileKey]: 0 })); setUploadError(null); - + try { const upload = new tus.Upload(file, { - endpoint: "http://localhost:3000/upload", - retryDelays: [0, 1000, 3000, 5000], - metadata: { - filename: file.name, - filetype: file.type, - }, - onProgress: (bytesUploaded, bytesTotal) => { - const uploadProgress = ( - (bytesUploaded / bytesTotal) * - 100 - ).toFixed(2); - setProgress(Number(uploadProgress)); - }, - onSuccess: async () => { - try { - if (upload.url) { - const fileId = getFileId(upload.url); - // const resource = await pollResourceStatus(fileId); + endpoint: "http://localhost:3000/upload", + retryDelays: [0, 1000, 3000, 5000], + metadata: { + filename: file.name, + filetype: file.type, + }, + onProgress: (bytesUploaded, bytesTotal) => { + const progress = Number( + ((bytesUploaded / bytesTotal) * 100).toFixed(2) + ); + setUploadProgress((prev) => ({ + ...prev, + [fileKey]: progress, + })); + }, + onSuccess: async () => { + try { + if (upload.url) { + const fileId = getFileId(upload.url); + setIsUploading(false); + setUploadProgress((prev) => ({ + ...prev, + [fileKey]: 100, + })); + onSuccess({ + url: upload.url, + fileId, + }); + } + } catch (error) { + const err = + error instanceof Error + ? error + : new Error("Unknown error"); setIsUploading(false); - setProgress(100); - onSuccess({ - url: upload.url, - fileId, - // resource, - }); + setUploadError(err.message); + onError(err); } - } catch (error) { - const err = - error instanceof Error - ? error - : new Error("Unknown error"); + }, + onError: (error) => { setIsUploading(false); - setUploadError(err.message); - onError(err); - } - }, - onError: (error) => { - setIsUploading(false); - setUploadError(error.message); - onError(error); - }, - }); - upload.start(); + setUploadError(error.message); + onError(error); + }, + }); + upload.start(); } catch (error) { - const err = error instanceof Error ? error : new Error("Upload failed"); + const err = + error instanceof Error ? error : new Error("Upload failed"); setIsUploading(false); setUploadError(err.message); onError(err); @@ -91,7 +103,7 @@ export function useTusUpload() { }; return { - progress, + uploadProgress, isUploading, uploadError, handleFileUpload,