From 8ce5d689c29c6f31912cfdcb3f1bb4c949a16219 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Sun, 26 Jan 2025 19:33:45 +0800 Subject: [PATCH] add 0126 --- .../common/uploader/TusUploader.tsx | 247 ++++++++---------- .../web/src/components/layout/main/Header.tsx | 44 +--- .../src/components/models/post/LetterCard.tsx | 4 +- .../models/post/detail/PostCommentCard.tsx | 8 +- .../models/post/detail/PostCommentEditor.tsx | 25 +- .../models/post/detail/PostHeader/Header.tsx | 15 +- .../post/detail/PostHeader/PostHateButton.tsx | 4 +- .../post/editor/form/LetterBasicForm.tsx | 34 ++- .../components/presentation/CustomAvatar.tsx | 6 +- 9 files changed, 185 insertions(+), 202 deletions(-) diff --git a/apps/web/src/components/common/uploader/TusUploader.tsx b/apps/web/src/components/common/uploader/TusUploader.tsx index c41ba95..3aa7ad0 100644 --- a/apps/web/src/components/common/uploader/TusUploader.tsx +++ b/apps/web/src/components/common/uploader/TusUploader.tsx @@ -4,7 +4,7 @@ import { CheckCircleOutlined, DeleteOutlined, } 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 toast from "react-hot-toast"; @@ -13,6 +13,7 @@ export interface TusUploaderProps { value?: string[]; onChange?: (value: string[]) => void; } + interface UploadingFile { name: string; progress: number; @@ -33,106 +34,92 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { fileId, })) || [] ); + // 恢复使用 uploadResults 状态跟踪最新结果 const [uploadResults, setUploadResults] = useState(value || []); - const handleRemoveFile = useCallback( (fileId: string) => { setCompletedFiles((prev) => prev.filter((f) => f.fileId !== fileId) ); - const newResults = uploadResults.filter((id) => id !== fileId); - setUploadResults(newResults); - onChange?.(newResults); + // 使用函数式更新保证获取最新状态 + setUploadResults((prev) => { + const newValue = prev.filter((id) => id !== fileId); + onChange?.(newValue); // 同步更新父组件 + return newValue; + }); }, - [uploadResults, onChange] + [onChange] ); - const handleChange = useCallback( - async (fileList: UploadFile | UploadFile[]) => { - const files = Array.isArray(fileList) ? fileList : [fileList]; - console.log("文件", files); + const handleBeforeUpload = useCallback( + (file: File) => { + const fileKey = `${file.name}-${Date.now()}`; - if (!files.every((f) => f instanceof File)) { - toast.error("无效的文件格式"); - return false; - } + setUploadingFiles((prev) => [ + ...prev, + { + name: file.name, + progress: 0, + status: "uploading", + fileKey, + }, + ]); - const newFiles: UploadingFile[] = files.map((f) => ({ - name: f.name, - progress: 0, - status: "uploading" as const, - fileKey: `${f.name}-${Date.now()}`, // 为每个文件创建唯一标识 - })); + handleFileUpload( + file, + (result) => { + setCompletedFiles((prev) => [ + ...prev, + { + name: file.name, + progress: 100, + status: "done", + fileId: result.fileId, + }, + ]); - setUploadingFiles((prev) => [...prev, ...newFiles]); - - const newUploadResults: string[] = []; - try { - for (const [index, f] of files.entries()) { - if (!f) { - throw new Error(`文件 ${f.name} 无效`); - } - const fileKey = newFiles[index].fileKey!; - const fileId = await new Promise( - (resolve, reject) => { - handleFileUpload( - f as File, - (result) => { - console.log("上传成功:", result); - const completedFile = { - name: f.name, - progress: 100, - status: "done" as const, - fileId: result.fileId, - }; - setCompletedFiles((prev) => [ - ...prev, - completedFile, - ]); - setUploadingFiles((prev) => - prev.filter( - (file) => file.fileKey !== fileKey - ) - ); - resolve(result.fileId); - }, - (error) => { - console.error("上传错误:", error); - reject(error); - }, - fileKey - ); - } + setUploadingFiles((prev) => + prev.filter((f) => f.fileKey !== fileKey) ); - newUploadResults.push(fileId); - } - const newValue = Array.from( - new Set([...uploadResults, ...newUploadResults]) - ); - setUploadResults(newValue); - onChange?.(newValue); - } catch (error) { - console.error("上传错误详情:", error); - toast.error( - `上传失败: ${error instanceof Error ? error.message : "未知错误"}` - ); - setUploadingFiles((prev) => - prev.map((f) => ({ ...f, status: "error" })) - ); - } + // 正确的状态更新方式 + setUploadResults((prev) => { + const newValue = [...prev, result.fileId]; + onChange?.(newValue); // 传递值而非函数 + return newValue; + }); + }, + (error) => { + console.error("上传错误:", error); + toast.error( + `上传失败: ${ + error instanceof Error ? error.message : "未知错误" + }` + ); + setUploadingFiles((prev) => + prev.map((f) => + f.fileKey === fileKey + ? { ...f, status: "error" } + : f + ) + ); + }, + fileKey + ); + return false; }, - [uploadResults, onChange, handleFileUpload] + [handleFileUpload, onChange] ); return (
+ beforeUpload={handleBeforeUpload}>

@@ -140,64 +127,58 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { 点击或拖拽文件到此区域进行上传

支持单个或批量上传文件

- {/* 正在上传的文件 */} - {(uploadingFiles.length > 0 || completedFiles.length > 0) && ( -
- {uploadingFiles.map((file) => ( -
-
-
{file.name}
-
- + {/* 上传状态展示 */} +
+ {/* 上传中的文件 */} + {uploadingFiles.map((file) => ( +
+
+ {file.name}
- ))} - {completedFiles.length > 0 && - completedFiles.map((file, index) => ( -
-
- -
- {file.name} -
-
-
- ))} -
- )} + +
+ ))} + + {/* 已完成的文件 */} + {completedFiles.map((file) => ( +
+
+ + {file.name} +
+
+ ))} +
); diff --git a/apps/web/src/components/layout/main/Header.tsx b/apps/web/src/components/layout/main/Header.tsx index fe05d4f..8c5ae80 100644 --- a/apps/web/src/components/layout/main/Header.tsx +++ b/apps/web/src/components/layout/main/Header.tsx @@ -1,52 +1,26 @@ import { Link, NavLink, useNavigate } from "react-router-dom"; -import { memo, useMemo } from "react"; +import { memo } from "react"; import { SearchBar } from "./SearchBar"; import Navigation from "./navigation"; import { useAuth } from "@web/src/providers/auth-provider"; import { UserOutlined } from "@ant-design/icons"; import { UserMenu } from "../element/usermenu/usermenu"; -import { api, useAppConfig } from "@nice/client"; -import { env } from "@web/src/env"; export const Header = memo(function Header() { const { isAuthenticated } = useAuth(); - const { logo } = useAppConfig(); - const { data: logoRes, isLoading } = api.resource.findFirst.useQuery( - { - where: { - fileId: logo, - }, - select: { - id: true, - url: true, - }, - }, - { - enabled: !!logo, - } - ); - const logoUrl: string = useMemo(() => { - return `http://${env.SERVER_IP}/uploads/${logoRes?.url}`; - }, [logoRes]); + return (
-
+
- {/** 在这里放置logo */} - {isLoading ? ( -
- ) : ( - logoUrl && ( - Logo - ) - )} + + 首长机关信箱 + +

+ 聆怀若水,应语如风;纾难化困,践诺成春 +

diff --git a/apps/web/src/components/models/post/LetterCard.tsx b/apps/web/src/components/models/post/LetterCard.tsx index 61b8393..39715e7 100644 --- a/apps/web/src/components/models/post/LetterCard.tsx +++ b/apps/web/src/components/models/post/LetterCard.tsx @@ -49,7 +49,9 @@ export function LetterCard({ letter }: LetterCardProps) {
- {letter.author?.showname || "匿名用户"} + {letter?.meta?.signature || + letter.author?.showname || + "匿名用户"}
diff --git a/apps/web/src/components/models/post/detail/PostCommentCard.tsx b/apps/web/src/components/models/post/detail/PostCommentCard.tsx index 7cef4fd..5a7bc30 100644 --- a/apps/web/src/components/models/post/detail/PostCommentCard.tsx +++ b/apps/web/src/components/models/post/detail/PostCommentCard.tsx @@ -37,14 +37,18 @@ export default function PostCommentCard({ src={post.author?.avatar} size={50} name={!post.author?.avatar && post.author?.showname} - ip={post?.meta?.ip}> + randomString={ + post?.meta?.signature || post?.meta?.ip + }>
- {post.author?.showname || "匿名用户"} + {post?.meta?.signature || + post.author?.showname || + "匿名用户"} {dayjs(post?.createdAt).format( diff --git a/apps/web/src/components/models/post/detail/PostCommentEditor.tsx b/apps/web/src/components/models/post/detail/PostCommentEditor.tsx index 322f9d6..74df927 100644 --- a/apps/web/src/components/models/post/detail/PostCommentEditor.tsx +++ b/apps/web/src/components/models/post/detail/PostCommentEditor.tsx @@ -1,6 +1,6 @@ import React, { useContext, useState } from "react"; import { motion } from "framer-motion"; -import { Button, Tabs } from "antd"; +import { Button, Input, Tabs } from "antd"; import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor"; import { PostDetailContext } from "./context/PostDetailContext"; import { usePost } from "@nice/client"; @@ -9,13 +9,14 @@ 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"; +import { CustomAvatar } from "@web/src/components/presentation/CustomAvatar"; const { TabPane } = Tabs; export default function PostCommentEditor() { const { post } = useContext(PostDetailContext); const [content, setContent] = useState(""); - const [isPreview, setIsPreview] = useState(false); + const [signature, setSignature] = useState(undefined); const [fileIds, setFileIds] = useState([]); const { create } = usePost(); @@ -38,10 +39,14 @@ export default function PostCommentEditor() { fileId: id, })), }, + meta: { + signature, + }, }, }); toast.success("发布成功!"); setContent(""); + setFileIds([]); } catch (error) { toast.error("发布失败,请稍后重试"); console.error("Error posting comment:", error); @@ -82,6 +87,7 @@ export default function PostCommentEditor() {
{ + console.log("ids", value); setFileIds(value); }} /> @@ -90,12 +96,23 @@ export default function PostCommentEditor() { {!isContentEmpty(content) && ( -
+
+ + { + setSignature(e.target.value); + }} + showCount + placeholder="签名" + />
- - 发件人 - {post?.author?.showname || "匿名用户"} + {post?.meta?.signature || + post?.author?.showname || + "匿名用户"} - 收件人 {post?.receivers?.map((receiver, index) => ( @@ -43,27 +42,23 @@ export default function Header() { strong className="text-white" key={`${index}`}> - {receiver?.meta?.rank} {receiver?.showname || '匿名用户'} + {receiver?.meta?.rank}{" "} + {receiver?.showname || "匿名用户"} ))} {/* Date Info Badge */} - - 创建于 - {dayjs(post?.createdAt).format("YYYY-MM-DD")} {/* Last Updated Badge */} - 最后更新于 - {dayjs(post?.updatedAt).format("YYYY-MM-DD")} diff --git a/apps/web/src/components/models/post/detail/PostHeader/PostHateButton.tsx b/apps/web/src/components/models/post/detail/PostHeader/PostHateButton.tsx index a19dfec..f93603e 100644 --- a/apps/web/src/components/models/post/detail/PostHeader/PostHateButton.tsx +++ b/apps/web/src/components/models/post/detail/PostHeader/PostHateButton.tsx @@ -36,8 +36,8 @@ export default function PostHateButton({ post }: { post: PostDto }) { type={post?.hated ? "primary" : "default"} style={{ backgroundColor: post?.hated ? "#ff4d4f" : "#fff", - borderColor: post?.hated ? "transparent" : "#ff4d4f", - color: post?.hated ? "#fff" : "#ff4d4f", + borderColor: post?.hated ? "transparent" : "", + color: post?.hated ? "#fff" : "#000", boxShadow: "none", // 去除阴影 }} shape="round" diff --git a/apps/web/src/components/models/post/editor/form/LetterBasicForm.tsx b/apps/web/src/components/models/post/editor/form/LetterBasicForm.tsx index 4252c4e..5b3312b 100644 --- a/apps/web/src/components/models/post/editor/form/LetterBasicForm.tsx +++ b/apps/web/src/components/models/post/editor/form/LetterBasicForm.tsx @@ -79,8 +79,7 @@ export function LetterBasicForm() { -
- +
form.setFieldValue( @@ -89,8 +88,7 @@ export function LetterBasicForm() { ) } /> - -
+
@@ -101,14 +99,26 @@ export function LetterBasicForm() { 是否公开 - +
+ + + + +
diff --git a/apps/web/src/components/presentation/CustomAvatar.tsx b/apps/web/src/components/presentation/CustomAvatar.tsx index 53c931f..5883497 100644 --- a/apps/web/src/components/presentation/CustomAvatar.tsx +++ b/apps/web/src/components/presentation/CustomAvatar.tsx @@ -6,14 +6,14 @@ import multiavatar from "@multiavatar/multiavatar"; interface CustomAvatarProps extends Omit { src?: string; name?: string; - ip?: string; + randomString?: string; } export function CustomAvatar({ src, name, className = "", - ip, + randomString, ...props }: CustomAvatarProps) { // 获取名字的第一个字符,如果没有名字则显示"匿" @@ -39,7 +39,7 @@ export function CustomAvatar({ const avatarSrc = src || (name && name !== "匿名用户") ? src - : generateAvatarFromIp(ip || "default"); + : generateAvatarFromIp(randomString || "default"); return (