+
+
+
+
+
+ Click or drag file to this area to upload
+
+
+ Support for a single or bulk upload of files
+
+
+
+ {/* Uploading Files */}
+ {uploadingFiles.length > 0 && (
+
+
Uploading Files
+ {uploadingFiles.map((file, index) => (
+
+ ))}
+
+ )}
+
+ {/* Completed Files */}
+ {completedFiles.length > 0 && (
+
+
Uploaded Files
+ {completedFiles.map((file, index) => (
+
+
+
}
+ onClick={() =>
+ file.fileId && handleRemoveFile(file.fileId)
+ }
+ />
+
+ ))}
+
+ )}
+
+ );
};
diff --git a/apps/web/src/components/models/post/detail/PostCommentEditor.tsx b/apps/web/src/components/models/post/detail/PostCommentEditor.tsx
index 292a883..8575a74 100644
--- a/apps/web/src/components/models/post/detail/PostCommentEditor.tsx
+++ b/apps/web/src/components/models/post/detail/PostCommentEditor.tsx
@@ -14,6 +14,7 @@ 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();
@@ -26,8 +27,14 @@ export default function PostCommentEditor() {
await create.mutateAsync({
data: {
type: PostType.POST_COMMENT,
+
parentId: post?.id,
content: content,
+ resources: {
+ connect: fileIds.filter(Boolean).map((id) => ({
+ fileId: id,
+ })),
+ },
},
});
toast.success("发布成功!");
@@ -83,7 +90,10 @@ export default function PostCommentEditor() {
)}
-
+ {
+ setFileIds(value);
+ }}>
({
+ id,
+ })),
+ },
receivers: {
connect: [receiverId].filter(Boolean).map((id) => ({
id,
@@ -57,8 +62,10 @@ export function LetterFormProvider({
...data,
resources: data.resources?.length
? {
- connect: data.resources.map((id) => ({
- id,
+ connect: (
+ data.resources?.filter(Boolean) || []
+ ).map((fileId) => ({
+ fileId,
})),
}
: undefined,
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 f5327b7..0edcefc 100644
--- a/apps/web/src/components/models/post/editor/form/LetterBasicForm.tsx
+++ b/apps/web/src/components/models/post/editor/form/LetterBasicForm.tsx
@@ -11,6 +11,7 @@ import {
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
import { PostBadge } from "../../detail/badge/PostBadge";
+import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
export function LetterBasicForm() {
const { onSubmit, receiverId, termId, form } = useLetterEditor();
@@ -132,8 +133,27 @@ export function LetterBasicForm() {
-
-
+ {/* 内容输入框 */}
+
+
+
+ 附件
+
+ }
+ name="resources"
+ labelCol={{ span: 24 }}
+ wrapperCol={{ span: 24 }}>
+
+
+ form.setFieldValue("resources", resources)
+ }
+ />
+
+
+
void,
onError: (error: Error) => void
) => {
+ if (!file || !file.name || !file.type) {
+ const error = new Error('Invalid file provided');
+ setUploadError(error.message);
+ onError(error);
+ return;
+ }
+
setIsUploading(true);
setProgress(0);
setUploadError(null);
- const upload = new tus.Upload(file, {
+
+ try {
+ const upload = new tus.Upload(file, {
endpoint: "http://localhost:3000/upload",
retryDelays: [0, 1000, 3000, 5000],
metadata: {
@@ -73,7 +82,14 @@ export function useTusUpload() {
},
});
upload.start();
+ } catch (error) {
+ const err = error instanceof Error ? error : new Error("Upload failed");
+ setIsUploading(false);
+ setUploadError(err.message);
+ onError(err);
+ }
};
+
return {
progress,
isUploading,
diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma
index 4a3a901..fe37e9e 100644
--- a/packages/common/prisma/schema.prisma
+++ b/packages/common/prisma/schema.prisma
@@ -27,7 +27,8 @@ model Taxonomy {
model Term {
id String @id @default(cuid())
name String
- posts Post[]
+ // posts Post[]
+ posts Post[] @relation("post_term")
taxonomy Taxonomy? @relation(fields: [taxonomyId], references: [id])
taxonomyId String? @map("taxonomy_id")
order Float? @map("order")
@@ -193,8 +194,10 @@ model Post {
content String? // 帖子内容,可为空
domainId String? @map("domain_id")
- term Term? @relation(fields: [termId], references: [id])
- termId String? @map("term_id")
+ // term Term? @relation(fields: [termId], references: [id])
+ // termId String? @map("term_id")
+ // 添加多对多关系
+ terms Term[] @relation("post_term")
// 日期时间类型字段
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @map("updated_at")
diff --git a/packages/common/src/select.ts b/packages/common/src/select.ts
index 8208654..668bd06 100644
--- a/packages/common/src/select.ts
+++ b/packages/common/src/select.ts
@@ -12,16 +12,10 @@ export const postDetailSelect: Prisma.PostSelect = {
resources: true,
createdAt: true,
updatedAt: true,
- termId: true,
- term: {
+
+ terms: {
include: {
- taxonomy: {
- select: {
- id: true,
- slug: true,
- name: true,
- },
- },
+
},
},
authorId: true,