From b8a0721358f508ffeed276ec753f71d93bbb8b74 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Sat, 25 Jan 2025 22:39:22 +0800 Subject: [PATCH] add 0125-2239 --- apps/web/package.json | 3 +- .../common/uploader/TusUploader.tsx | 162 +++++++++--------- .../models/post/detail/PostCommentCard.tsx | 78 +++++---- .../models/post/detail/PostCommentEditor.tsx | 98 ++++++----- .../models/post/detail/PostCommentList.tsx | 12 +- .../models/post/detail/PostHeader/Content.tsx | 55 +++--- .../models/post/detail/PostHeader/Header.tsx | 2 +- .../models/post/detail/PostResources.tsx | 6 +- .../post/editor/form/LetterBasicForm.tsx | 7 +- .../components/presentation/CustomAvatar.tsx | 38 +++- pnpm-lock.yaml | 24 ++- 11 files changed, 258 insertions(+), 227 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 379260d..27d6a81 100755 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -30,12 +30,13 @@ "@floating-ui/react": "^0.26.25", "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^3.9.1", + "@multiavatar/multiavatar": "^1.0.7", "@nice/client": "workspace:^", "@nice/common": "workspace:^", "@nice/iconer": "workspace:^", "@nice/theme": "workspace:^", - "@nice/utils": "workspace:^", "@nice/ui": "workspace:^", + "@nice/utils": "workspace:^", "@tanstack/query-async-storage-persister": "^5.51.9", "@tanstack/react-query": "^5.51.21", "@tanstack/react-query-persist-client": "^5.51.9", diff --git a/apps/web/src/components/common/uploader/TusUploader.tsx b/apps/web/src/components/common/uploader/TusUploader.tsx index b3ec1a3..64b9599 100644 --- a/apps/web/src/components/common/uploader/TusUploader.tsx +++ b/apps/web/src/components/common/uploader/TusUploader.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react"; +import { useCallback, useState } from "react"; import { UploadOutlined, CheckCircleOutlined, @@ -23,13 +23,14 @@ interface UploadingFile { export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { const { handleFileUpload } = useTusUpload(); const [uploadingFiles, setUploadingFiles] = useState([]); - const [completedFiles, setCompletedFiles] = useState(() => - value?.map(fileId => ({ - name: `File ${fileId}`, // We could fetch the actual filename if needed - progress: 1, - status: 'done' as const, - fileId - })) || [] + const [completedFiles, setCompletedFiles] = useState( + () => + value?.map((fileId) => ({ + name: `文件 ${fileId}`, // 可以根据需要获取实际文件名 + progress: 1, + status: "done" as const, + fileId, + })) || [] ); const [uploadResults, setUploadResults] = useState(value || []); @@ -38,7 +39,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { setCompletedFiles((prev) => prev.filter((f) => f.fileId !== fileId) ); - const newResults = uploadResults.filter(id => id !== fileId); + const newResults = uploadResults.filter((id) => id !== fileId); setUploadResults(newResults); onChange?.(newResults); }, @@ -48,10 +49,10 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { const handleChange = useCallback( async (fileList: UploadFile | UploadFile[]) => { const files = Array.isArray(fileList) ? fileList : [fileList]; - console.log("files", files); - // 验证文件对象 + console.log("文件", files); + if (!files.every((f) => f instanceof File)) { - message.error("Invalid file format"); + message.error("无效的文件格式"); return false; } @@ -61,20 +62,19 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { status: "uploading" as const, })); setUploadingFiles((prev) => [...prev, ...newFiles]); - const newUploadResults: string[] = []; + const newUploadResults: string[] = []; try { for (const [index, f] of files.entries()) { if (!f) { - throw new Error(`File ${f.name} is invalid`); + throw new Error(`文件 ${f.name} 无效`); } - const fileId = await new Promise( (resolve, reject) => { handleFileUpload( f as File, (result) => { - console.log("Upload success:", result); + console.log("上传成功:", result); const completedFile = { name: f.name, progress: 1, @@ -91,7 +91,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { resolve(result.fileId); }, (error) => { - console.error("Upload error:", error); + console.error("上传错误:", error); reject(error); } ); @@ -100,97 +100,93 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { newUploadResults.push(fileId); } - // Update with all uploaded files - const newValue = Array.from(new Set([...uploadResults, ...newUploadResults])); + const newValue = Array.from( + new Set([...uploadResults, ...newUploadResults]) + ); setUploadResults(newValue); onChange?.(newValue); - message.success(`${files.length} files uploaded successfully`); + message.success(`${files.length} 个文件上传成功`); } catch (error) { - console.error("Upload error details:", error); + console.error("上传错误详情:", error); message.error( - `Upload failed: ${error instanceof Error ? error.message : "Unknown error"}` + `上传失败: ${error instanceof Error ? error.message : "未知错误"}` ); setUploadingFiles((prev) => prev.map((f) => ({ ...f, status: "error" })) ); } - return false; }, [uploadResults, onChange, handleFileUpload] ); return ( -
+
+ style={{ background: "white" }} + beforeUpload={handleChange}>

- Click or drag file to this area to upload -

-

- Support for a single or bulk upload of files + 点击或拖拽文件到此区域进行上传

+

支持单个或批量上传文件

+ {/* 正在上传的文件 */} + {(uploadingFiles.length > 0 || completedFiles.length > 0) && ( +
+ {uploadingFiles.length > 0 && + uploadingFiles.map((file, index) => ( +
+
+
+ {file.name} +
+
+ +
+ ))} + {completedFiles.length > 0 && + completedFiles.map((file, index) => ( +
+
+ +
+ {file.name} +
+
+
+ ))} +
+ )}
- - {/* Uploading Files */} - {uploadingFiles.length > 0 && ( -
-
Uploading Files
- {uploadingFiles.map((file, index) => ( -
-
-
{file.name}
-
- -
- ))} -
- )} - - {/* Completed Files */} - {completedFiles.length > 0 && ( -
-
Uploaded Files
- {completedFiles.map((file, index) => ( -
-
- -
{file.name}
-
-
- ))} -
- )}
); }; diff --git a/apps/web/src/components/models/post/detail/PostCommentCard.tsx b/apps/web/src/components/models/post/detail/PostCommentCard.tsx index c1b5366..a4674a7 100644 --- a/apps/web/src/components/models/post/detail/PostCommentCard.tsx +++ b/apps/web/src/components/models/post/detail/PostCommentCard.tsx @@ -4,7 +4,7 @@ import dayjs from "dayjs"; import { Avatar } from "antd"; import { useVisitor } from "@nice/client"; -import { useContext, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import { PostDetailContext } from "./context/PostDetailContext"; import { LikeFilled, LikeOutlined } from "@ant-design/icons"; import PostLikeButton from "./PostHeader/PostLikeButton"; @@ -24,51 +24,55 @@ export default function PostCommentCard({ -
+
+ size={50} + name={!post.author?.avatar && post.author?.showname} + ip={post?.meta?.ip}>
-
-
-
- - {post.author?.showname || "匿名用户"} - - - {dayjs(post?.createdAt).format( - "YYYY-MM-DD HH:mm" +
+
+
+
+ + {post.author?.showname || "匿名用户"} + + + {dayjs(post?.createdAt).format( + "YYYY-MM-DD HH:mm" + )} + + {isReceiverComment && ( +
+ + 官方回复 + +
)} - - {isReceiverComment && ( -
- - 官方回复 - +
+ {/* 添加有帮助按钮 */} +
+
+ {`#${index + 1}`} +
- )} -
- {/* 添加有帮助按钮 */} -
-
- {`#${index + 1}`} -
-
-
- +
+ +
diff --git a/apps/web/src/components/models/post/detail/PostCommentEditor.tsx b/apps/web/src/components/models/post/detail/PostCommentEditor.tsx index 8575a74..1009c75 100644 --- a/apps/web/src/components/models/post/detail/PostCommentEditor.tsx +++ b/apps/web/src/components/models/post/detail/PostCommentEditor.tsx @@ -1,7 +1,6 @@ import React, { useContext, useState } from "react"; import { motion } from "framer-motion"; - -import { Button } from "antd"; +import { Button, Tabs } from "antd"; import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor"; import { PostDetailContext } from "./context/PostDetailContext"; import { usePost } from "@nice/client"; @@ -10,12 +9,16 @@ import toast from "react-hot-toast"; import { isContentEmpty } from "./utils"; import { SendOutlined } from "@ant-design/icons"; import { TusUploader } from "@web/src/components/common/uploader/TusUploader"; + +const { TabPane } = Tabs; + export default function PostCommentEditor() { const { post } = useContext(PostDetailContext); const [content, setContent] = useState(""); const [isPreview, setIsPreview] = useState(false); const [fileIds, setFileIds] = useState([]); const { create } = usePost(); + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (isContentEmpty(content)) { @@ -49,56 +52,51 @@ export default function PostCommentEditor() { }; return ( - -
-
- {!isPreview ? ( - + + + +
+ +
+
+ + { + setFileIds(value); }} - style={ - { - "--ql-border-color": "transparent", - "--ql-toolbar-bg": "rgb(248, 250, 252)", // slate-50 - } as React.CSSProperties - } /> - ) : ( - - )} -
- - { - setFileIds(value); - }}> + +
- +
- +
-
+
); } diff --git a/apps/web/src/components/models/post/detail/PostCommentList.tsx b/apps/web/src/components/models/post/detail/PostCommentList.tsx index 22486a7..58b96a3 100644 --- a/apps/web/src/components/models/post/detail/PostCommentList.tsx +++ b/apps/web/src/components/models/post/detail/PostCommentList.tsx @@ -1,10 +1,4 @@ -import React, { - useContext, - useMemo, - useEffect, - useRef, - useCallback, -} from "react"; +import { useContext, useMemo, useEffect } from "react"; import { PostDetailContext } from "./context/PostDetailContext"; import { api, useVisitor } from "@nice/client"; import { postDetailSelect, PostDto, PostType, Prisma } from "@nice/common"; @@ -12,8 +6,6 @@ import { motion, AnimatePresence } from "framer-motion"; import PostCommentCard from "./PostCommentCard"; import { useInView } from "react-intersection-observer"; import { LoadingCard } from "@web/src/components/models/post/detail/LoadingCard"; -import { Button } from "antd"; - export default function PostCommentList() { const { post } = useContext(PostDetailContext); const { ref: loadMoreRef, inView } = useInView(); @@ -155,7 +147,7 @@ export default function PostCommentList() { } return ( -
+
{items.map((comment, index) => ( { - if (contentRef.current) { - const shouldCollapse = contentRef.current.scrollHeight > 300; // 300px threshold + if (contentWrapperRef.current) { + const shouldCollapse = contentWrapperRef.current.scrollHeight > 100; setShouldCollapse(shouldCollapse); } }, [post?.content]); + return ( -
- +
+
+ {/* 包装整个内容区域的容器 */}
+ }`}> + {/* 内容区域 */} +
+ + {/* PostResources 组件 */} + + + {/* 渐变遮罩 */} + {shouldCollapse && !isExpanded && ( +
+ )} +
+ + {/* 展开/收起按钮 */} {shouldCollapse && ( )} - - +
{/* Stats Section */} 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 0e2eeea..ae78be5 100644 --- a/apps/web/src/components/models/post/detail/PostHeader/Header.tsx +++ b/apps/web/src/components/models/post/detail/PostHeader/Header.tsx @@ -17,7 +17,7 @@ const { Title, Paragraph, Text } = Typography; export default function Header() { const { post, user } = useContext(PostDetailContext); return ( -
+
{/* 右上角标签 */} {/* */} diff --git a/apps/web/src/components/models/post/detail/PostResources.tsx b/apps/web/src/components/models/post/detail/PostResources.tsx index 52bc250..c50ccd3 100644 --- a/apps/web/src/components/models/post/detail/PostResources.tsx +++ b/apps/web/src/components/models/post/detail/PostResources.tsx @@ -28,12 +28,12 @@ export default function PostResources({ post }: { post: PostDto }) { }, [post]); return ( -
-
+
+
{resources ?.filter((resource) => resource.isImage) .map((resource) => ( -
+
{resource.title} - {/* Tags Input */}