This commit is contained in:
ditiqi 2025-01-25 23:19:03 +08:00
parent 7e1942fa07
commit 741ff22489
6 changed files with 144 additions and 120 deletions

View File

@ -12,22 +12,22 @@ export interface TusUploaderProps {
value?: string[]; value?: string[];
onChange?: (value: string[]) => void; onChange?: (value: string[]) => void;
} }
interface UploadingFile { interface UploadingFile {
name: string; name: string;
progress: number; progress: number;
status: "uploading" | "done" | "error"; status: "uploading" | "done" | "error";
fileId?: string; fileId?: string;
fileKey?: string;
} }
export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
const { handleFileUpload } = useTusUpload(); const { handleFileUpload, uploadProgress } = useTusUpload();
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]); const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>( const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>(
() => () =>
value?.map((fileId) => ({ value?.map((fileId) => ({
name: `文件 ${fileId}`, // 可以根据需要获取实际文件名 name: `文件 ${fileId}`,
progress: 1, progress: 100,
status: "done" as const, status: "done" as const,
fileId, fileId,
})) || [] })) || []
@ -60,7 +60,9 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
name: f.name, name: f.name,
progress: 0, progress: 0,
status: "uploading" as const, status: "uploading" as const,
fileKey: `${f.name}-${Date.now()}`, // 为每个文件创建唯一标识
})); }));
setUploadingFiles((prev) => [...prev, ...newFiles]); setUploadingFiles((prev) => [...prev, ...newFiles]);
const newUploadResults: string[] = []; const newUploadResults: string[] = [];
@ -69,6 +71,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
if (!f) { if (!f) {
throw new Error(`文件 ${f.name} 无效`); throw new Error(`文件 ${f.name} 无效`);
} }
const fileKey = newFiles[index].fileKey!;
const fileId = await new Promise<string>( const fileId = await new Promise<string>(
(resolve, reject) => { (resolve, reject) => {
handleFileUpload( handleFileUpload(
@ -77,7 +80,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
console.log("上传成功:", result); console.log("上传成功:", result);
const completedFile = { const completedFile = {
name: f.name, name: f.name,
progress: 1, progress: 100,
status: "done" as const, status: "done" as const,
fileId: result.fileId, fileId: result.fileId,
}; };
@ -86,14 +89,17 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
completedFile, completedFile,
]); ]);
setUploadingFiles((prev) => setUploadingFiles((prev) =>
prev.filter((_, i) => i !== index) prev.filter(
(file) => file.fileKey !== fileKey
)
); );
resolve(result.fileId); resolve(result.fileId);
}, },
(error) => { (error) => {
console.error("上传错误:", error); console.error("上传错误:", error);
reject(error); reject(error);
} },
fileKey
); );
} }
); );
@ -126,7 +132,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
name="files" name="files"
multiple multiple
showUploadList={false} showUploadList={false}
style={{ background: "white" }} style={{ background: "white", borderStyle: "solid" }}
beforeUpload={handleChange}> beforeUpload={handleChange}>
<p className="ant-upload-drag-icon"> <p className="ant-upload-drag-icon">
<UploadOutlined /> <UploadOutlined />
@ -138,20 +144,25 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
{/* 正在上传的文件 */} {/* 正在上传的文件 */}
{(uploadingFiles.length > 0 || completedFiles.length > 0) && ( {(uploadingFiles.length > 0 || completedFiles.length > 0) && (
<div className=" p-2 border rounded bg-white mt-1"> <div className=" p-2 border rounded bg-white mt-1">
{uploadingFiles.length > 0 && {uploadingFiles.map((file) => (
uploadingFiles.map((file, index) => (
<div <div
key={index} key={file.fileKey}
className="flex flex-col gap-1"> className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="text-sm"> <div className="text-sm">{file.name}</div>
{file.name}
</div>
</div> </div>
<Progress <Progress
percent={Math.round( className="flex-1 w-full"
file.progress * 100 percent={
)} file.status === "done"
? 100
: Math.round(
uploadProgress?.[
file?.fileKey
] || 0
)
}
status={ status={
file.status === "error" file.status === "error"
? "exception" ? "exception"

View File

@ -95,6 +95,7 @@ export default function PostCommentEditor() {
</TabPane> </TabPane>
</Tabs> </Tabs>
{!isContentEmpty(content) && (
<div className="flex items-center justify-end"> <div className="flex items-center justify-end">
<div> <div>
<Button <Button
@ -107,6 +108,7 @@ export default function PostCommentEditor() {
</Button> </Button>
</div> </div>
</div> </div>
)}
</form> </form>
</div> </div>
); );

View File

@ -10,23 +10,23 @@ 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 > 100; const shouldCollapse = contentWrapperRef.current.scrollHeight > 150;
setShouldCollapse(shouldCollapse); setShouldCollapse(shouldCollapse);
} }
}, [post?.content]); }, [post?.content]);
return ( return (
<div className="relative bg-white rounded-b-xl p-4 pt-2 shadow-lg border border-[#97A9C4]/30"> <div className="relative bg-white rounded-b-xl p-4 pt-2 border border-[#97A9C4]/30">
<div className="text-secondary-700"> <div className="text-secondary-700">
{/* 包装整个内容区域的容器 */} {/* 包装整个内容区域的容器 */}
<div <div
ref={contentWrapperRef} ref={contentWrapperRef}
className={`duration-300 ${ className={`duration-300 ${
shouldCollapse && !isExpanded shouldCollapse && !isExpanded
? "max-h-[100px] overflow-hidden relative" ? `max-h-[150px] overflow-hidden relative`
: "" : ""
}`}> }`}>
{/* 内容区域 */} {/* 内容区域 */}

View File

@ -13,6 +13,7 @@ import {
} from "@ant-design/icons"; } from "@ant-design/icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { CornerBadge } from "../badge/CornerBadeg"; import { CornerBadge } from "../badge/CornerBadeg";
import { LetterBadge } from "../../LetterBadge";
const { Title, Paragraph, Text } = Typography; const { Title, Paragraph, Text } = Typography;
export default function Header() { export default function Header() {
const { post, user } = useContext(PostDetailContext); const { post, user } = useContext(PostDetailContext);
@ -88,22 +89,23 @@ export default function Header() {
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{/* Tags Badges */} {/* Tags Badges */}
<Space> <LetterBadge type="state" value={post?.state} />
<PostBadge type="state" value={post?.state} /> {(post?.terms || [])?.map((term, index) => {
</Space> return (
<Space> <LetterBadge
<PostBadge key={`${term.name}-${index}`}
type="category" type="category"
value={post?.term?.name} value={term.name}
/> />
</Space> );
})}
{post.meta.tags.length > 0 && {post.meta.tags.length > 0 &&
post.meta.tags.map((tag, index) => ( post.meta.tags.map((tag, index) => (
<Space key={index}> <LetterBadge
<PostBadge key={`${tag}-${index}`}
type="tag" type="tag"
value={`${tag}`}></PostBadge> value={tag}
</Space> />
))} ))}
</div> </div>
)} )}

View File

@ -12,8 +12,7 @@ import { useVisitor } from "@nice/client";
import { VisitType } from "packages/common/dist"; import { VisitType } from "packages/common/dist";
import PostLikeButton from "./PostLikeButton"; import PostLikeButton from "./PostLikeButton";
export function StatsSection() { export function StatsSection() {
const { post, user } = useContext(PostDetailContext); const { post } = useContext(PostDetailContext);
const { like, unLike } = useVisitor();
return ( return (
<motion.div <motion.div
@ -22,14 +21,12 @@ export function StatsSection() {
transition={{ delay: 0.7 }} transition={{ delay: 0.7 }}
className="mt-6 flex flex-wrap gap-4 justify-between items-center"> className="mt-6 flex flex-wrap gap-4 justify-between items-center">
<div className=" flex gap-2"> <div className=" flex gap-2">
<div className="flex items-center gap-1 text-gray-500"> <Button type="default" shape="round" icon={<EyeOutlined />}>
<EyeOutlined className="text-lg" /> {post?.views}
<span className="text-sm">{post?.views}</span> </Button>
</div> <Button type="default" shape="round" icon={<CommentOutlined />}>
<div className="flex items-center gap-1 text-gray-500"> {post?.commentsCount}
<CommentOutlined className="text-lg" /> </Button>
<span className="text-sm">{post?.commentsCount}</span>
</div>
</div> </div>
<PostLikeButton post={post}></PostLikeButton> <PostLikeButton post={post}></PostLikeButton>
</motion.div> </motion.div>

View File

@ -1,40 +1,48 @@
import { useState } from "react"; import { useState } from "react";
import * as tus from "tus-js-client"; import * as tus from "tus-js-client";
// useTusUpload.ts
interface UploadProgress {
fileId: string;
progress: number;
}
interface UploadResult { interface UploadResult {
url: string; url: string;
fileId: string; fileId: string;
// resource: any;
} }
export function useTusUpload() { export function useTusUpload() {
const [progress, setProgress] = useState(0); const [uploadProgress, setUploadProgress] = useState<
Record<string, number>
>({});
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const [uploadError, setUploadError] = useState<string | null>(null); const [uploadError, setUploadError] = useState<string | null>(null);
const getFileId = (url: string) => { const getFileId = (url: string) => {
const parts = url.split("/"); const parts = url.split("/");
// Find the index of the 'upload' segment
const uploadIndex = parts.findIndex((part) => part === "upload"); const uploadIndex = parts.findIndex((part) => part === "upload");
if (uploadIndex === -1 || uploadIndex + 4 >= parts.length) { if (uploadIndex === -1 || uploadIndex + 4 >= parts.length) {
throw new Error("Invalid upload URL format"); 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("/"); return parts.slice(uploadIndex + 1, uploadIndex + 5).join("/");
}; };
const handleFileUpload = async ( const handleFileUpload = async (
file: File, file: File,
onSuccess: (result: UploadResult) => void, onSuccess: (result: UploadResult) => void,
onError: (error: Error) => void onError: (error: Error) => void,
fileKey: string // 添加文件唯一标识
) => { ) => {
if (!file || !file.name || !file.type) { if (!file || !file.name || !file.type) {
const error = new Error('Invalid file provided'); const error = new Error("不可上传该类型文件");
setUploadError(error.message); setUploadError(error.message);
onError(error); onError(error);
return; return;
} }
setIsUploading(true); setIsUploading(true);
setProgress(0); setUploadProgress((prev) => ({ ...prev, [fileKey]: 0 }));
setUploadError(null); setUploadError(null);
try { try {
@ -46,23 +54,26 @@ export function useTusUpload() {
filetype: file.type, filetype: file.type,
}, },
onProgress: (bytesUploaded, bytesTotal) => { onProgress: (bytesUploaded, bytesTotal) => {
const uploadProgress = ( const progress = Number(
(bytesUploaded / bytesTotal) * ((bytesUploaded / bytesTotal) * 100).toFixed(2)
100 );
).toFixed(2); setUploadProgress((prev) => ({
setProgress(Number(uploadProgress)); ...prev,
[fileKey]: progress,
}));
}, },
onSuccess: async () => { onSuccess: async () => {
try { try {
if (upload.url) { if (upload.url) {
const fileId = getFileId(upload.url); const fileId = getFileId(upload.url);
// const resource = await pollResourceStatus(fileId);
setIsUploading(false); setIsUploading(false);
setProgress(100); setUploadProgress((prev) => ({
...prev,
[fileKey]: 100,
}));
onSuccess({ onSuccess({
url: upload.url, url: upload.url,
fileId, fileId,
// resource,
}); });
} }
} catch (error) { } catch (error) {
@ -83,7 +94,8 @@ export function useTusUpload() {
}); });
upload.start(); upload.start();
} catch (error) { } catch (error) {
const err = error instanceof Error ? error : new Error("Upload failed"); const err =
error instanceof Error ? error : new Error("Upload failed");
setIsUploading(false); setIsUploading(false);
setUploadError(err.message); setUploadError(err.message);
onError(err); onError(err);
@ -91,7 +103,7 @@ export function useTusUpload() {
}; };
return { return {
progress, uploadProgress,
isUploading, isUploading,
uploadError, uploadError,
handleFileUpload, handleFileUpload,