This commit is contained in:
longdayi 2025-01-26 19:36:54 +08:00
parent 003208a5f5
commit 5695c59a01
6 changed files with 36 additions and 24 deletions

View File

@ -9,6 +9,7 @@ import toast from "react-hot-toast";
import { isContentEmpty } from "./utils"; import { isContentEmpty } from "./utils";
import { SendOutlined } from "@ant-design/icons"; import { SendOutlined } from "@ant-design/icons";
import { TusUploader } from "@web/src/components/common/uploader/TusUploader"; import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
import { useEntity } from '../../../../../../../packages/client/src/api/hooks/useEntity';
const { TabPane } = Tabs; const { TabPane } = Tabs;
@ -17,8 +18,8 @@ export default function PostCommentEditor() {
const [content, setContent] = useState(""); const [content, setContent] = useState("");
const [isPreview, setIsPreview] = useState(false); const [isPreview, setIsPreview] = useState(false);
const [fileIds, setFileIds] = useState<string[]>([]); const [fileIds, setFileIds] = useState<string[]>([]);
const { create } = usePost(); // const { create } = usePost();
const {create}=useEntity("post")
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (isContentEmpty(content)) { if (isContentEmpty(content)) {
@ -30,7 +31,7 @@ export default function PostCommentEditor() {
await create.mutateAsync({ await create.mutateAsync({
data: { data: {
type: PostType.POST_COMMENT, type: PostType.POST_COMMENT,
parentId: post?.id, parentId: post?.id,
content: content, content: content,
resources: { resources: {
@ -38,6 +39,7 @@ export default function PostCommentEditor() {
fileId: id, fileId: id,
})), })),
}, },
}, },
}); });
toast.success("发布成功!"); toast.success("发布成功!");

View File

@ -1,7 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import { PostDetailContext } from "../context/PostDetailContext"; import { PostDetailContext } from "../context/PostDetailContext";
import { motion } from "framer-motion";
import { StatsSection } from "./StatsSection"; import { StatsSection } from "./StatsSection";
import PostResources from "../PostResources"; import PostResources from "../PostResources";
@ -10,7 +9,7 @@ export default function Content() {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const contentWrapperRef = useRef(null); const contentWrapperRef = useRef(null);
const [shouldCollapse, setShouldCollapse] = useState(false); const [shouldCollapse, setShouldCollapse] = useState(false);
const maxHeight = 125;
useEffect(() => { useEffect(() => {
if (contentWrapperRef.current) { if (contentWrapperRef.current) {
const shouldCollapse = contentWrapperRef.current.scrollHeight > 150; const shouldCollapse = contentWrapperRef.current.scrollHeight > 150;

View File

@ -4,7 +4,9 @@ import { DownloadOutlined } from "@ant-design/icons";
import { PostDto } from "@nice/common"; import { PostDto } from "@nice/common";
import { env } from "@web/src/env"; import { env } from "@web/src/env";
import { getFileIcon } from "./utils"; import { getFileIcon } from "./utils";
import { formatFileSize } from '@nice/utils'; import { formatFileSize, getCompressedImageUrl } from '@nice/utils';
export default function PostResources({ post }: { post: PostDto }) { export default function PostResources({ post }: { post: PostDto }) {
const { resources } = useMemo(() => { const { resources } = useMemo(() => {
if (!post?.resources) return { resources: [] }; if (!post?.resources) return { resources: [] };
@ -12,13 +14,16 @@ export default function PostResources({ post }: { post: PostDto }) {
const isImage = (url: string) => const isImage = (url: string) =>
/\.(png|jpg|jpeg|gif|webp)$/i.test(url); /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
const sortedResources = post.resources const sortedResources = post.resources.map(resource => {
.map((resource) => ({ const original = `http://${env.SERVER_IP}/uploads/${resource.url}`
const isImg = isImage(resource.url)
return {
...resource, ...resource,
url: `http://${env.SERVER_IP}/uploads/${resource.url}`, url: isImg ? getCompressedImageUrl(original) : original,
isImage: isImage(resource.url), originalUrl: original,
})) isImage: isImg
.sort((a, b) => (a.isImage === b.isImage ? 0 : a.isImage ? -1 : 1)); }
}).sort((a, b) => a.isImage === b.isImage ? 0 : a.isImage ? -1 : 1)
return { resources: sortedResources }; return { resources: sortedResources };
}, [post]); }, [post]);
@ -46,6 +51,7 @@ export default function PostResources({ post }: { post: PostDto }) {
src={resource.url} src={resource.url}
alt={resource.title} alt={resource.title}
preview={{ preview={{
src: resource.originalUrl,
mask: ( mask: (
<div className="flex items-center justify-center text-white"> <div className="flex items-center justify-center text-white">

View File

@ -93,7 +93,7 @@ export function LetterBasicForm() {
</TabPane> </TabPane>
</Tabs> </Tabs>
{/* Footer Actions */} {/* Footer Actions */}
<div className="flex flex-col-reverse sm:flex-row items-center justify-between gap-4 mt-6"> <div className="flex flex-col-reverse sm:flex-row items-center justify-between gap-4 ">
<Form.Item name="isPublic" valuePropName="checked"> <Form.Item name="isPublic" valuePropName="checked">
<Checkbox className="text-gray-600 hover:text-gray-900 transition-colors text-sm"> <Checkbox className="text-gray-600 hover:text-gray-900 transition-colors text-sm">

View File

@ -70,11 +70,11 @@ model TermAncestry {
} }
model Staff { model Staff {
id String @id @default(cuid()) id String @id @default(cuid())
showname String? @map("showname") showname String? @map("showname")
username String @unique @map("username") username String @unique @map("username")
avatar String? @map("avatar") avatar String? @map("avatar")
password String? @map("password") password String? @map("password")
phoneNumber String? @unique @map("phone_number") phoneNumber String? @unique @map("phone_number")
@ -196,16 +196,16 @@ model Post {
domainId String? @map("domain_id") domainId String? @map("domain_id")
terms Term[] @relation("post_term") terms Term[] @relation("post_term")
// 日期时间类型字段 // 日期时间类型字段
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime? @default(now()) @map("created_at")
updatedAt DateTime @map("updated_at") updatedAt DateTime? @map("updated_at")
deletedAt DateTime? @map("deleted_at") // 删除时间,可为空 deletedAt DateTime? @map("deleted_at") // 删除时间,可为空
// 关系类型字段 // 关系类型字段
authorId String? @map("author_id") authorId String? @map("author_id")
author Staff? @relation("post_author", fields: [authorId], references: [id]) // 帖子作者,关联 Staff 模型 author Staff? @relation("post_author", fields: [authorId], references: [id]) // 帖子作者,关联 Staff 模型
visits Visit[] // 访问记录,关联 Visit 模型 visits Visit[] // 访问记录,关联 Visit 模型
views Int @default(0) views Int? @default(0)
likes Int @default(0) likes Int? @default(0)
hates Int @default(0) hates Int? @default(0)
receivers Staff[] @relation("post_receiver") receivers Staff[] @relation("post_receiver")
parentId String? @map("parent_id") parentId String? @map("parent_id")
parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型 parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型

View File

@ -27,5 +27,10 @@ export const formatFileSize = (bytes: number) => {
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}; };
// 压缩图片路径生成函数
export const getCompressedImageUrl = (originalUrl: string): string => {
const cleanUrl = originalUrl.split(/[?#]/)[0] // 移除查询参数和哈希
const lastSlashIndex = cleanUrl.lastIndexOf('/')
return `${cleanUrl.slice(0, lastSlashIndex)}/compressed/${cleanUrl.slice(lastSlashIndex + 1).replace(/\.[^.]+$/, '.webp')}`
}
export * from "./types" export * from "./types"