add 0126
This commit is contained in:
parent
262dfade4e
commit
8ce5d689c2
|
|
@ -4,7 +4,7 @@ import {
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Upload, Progress, Button } from "antd";
|
import { Upload, Progress, Button } from "antd";
|
||||||
import type { UploadFile } from "antd";
|
import type { UploadFile } from "antd";
|
||||||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
@ -13,6 +13,7 @@ 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;
|
||||||
|
|
@ -33,106 +34,92 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
fileId,
|
fileId,
|
||||||
})) || []
|
})) || []
|
||||||
);
|
);
|
||||||
|
// 恢复使用 uploadResults 状态跟踪最新结果
|
||||||
const [uploadResults, setUploadResults] = useState<string[]>(value || []);
|
const [uploadResults, setUploadResults] = useState<string[]>(value || []);
|
||||||
|
|
||||||
const handleRemoveFile = useCallback(
|
const handleRemoveFile = useCallback(
|
||||||
(fileId: string) => {
|
(fileId: string) => {
|
||||||
setCompletedFiles((prev) =>
|
setCompletedFiles((prev) =>
|
||||||
prev.filter((f) => f.fileId !== fileId)
|
prev.filter((f) => f.fileId !== fileId)
|
||||||
);
|
);
|
||||||
const newResults = uploadResults.filter((id) => id !== fileId);
|
// 使用函数式更新保证获取最新状态
|
||||||
setUploadResults(newResults);
|
setUploadResults((prev) => {
|
||||||
onChange?.(newResults);
|
const newValue = prev.filter((id) => id !== fileId);
|
||||||
|
onChange?.(newValue); // 同步更新父组件
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[uploadResults, onChange]
|
[onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleBeforeUpload = useCallback(
|
||||||
async (fileList: UploadFile | UploadFile[]) => {
|
(file: File) => {
|
||||||
const files = Array.isArray(fileList) ? fileList : [fileList];
|
const fileKey = `${file.name}-${Date.now()}`;
|
||||||
console.log("文件", files);
|
|
||||||
|
|
||||||
if (!files.every((f) => f instanceof File)) {
|
setUploadingFiles((prev) => [
|
||||||
toast.error("无效的文件格式");
|
...prev,
|
||||||
return false;
|
{
|
||||||
}
|
name: file.name,
|
||||||
|
progress: 0,
|
||||||
|
status: "uploading",
|
||||||
|
fileKey,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const newFiles: UploadingFile[] = files.map((f) => ({
|
handleFileUpload(
|
||||||
name: f.name,
|
file,
|
||||||
progress: 0,
|
(result) => {
|
||||||
status: "uploading" as const,
|
setCompletedFiles((prev) => [
|
||||||
fileKey: `${f.name}-${Date.now()}`, // 为每个文件创建唯一标识
|
...prev,
|
||||||
}));
|
{
|
||||||
|
name: file.name,
|
||||||
|
progress: 100,
|
||||||
|
status: "done",
|
||||||
|
fileId: result.fileId,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
setUploadingFiles((prev) => [...prev, ...newFiles]);
|
setUploadingFiles((prev) =>
|
||||||
|
prev.filter((f) => f.fileKey !== fileKey)
|
||||||
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<string>(
|
|
||||||
(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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
newUploadResults.push(fileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newValue = Array.from(
|
// 正确的状态更新方式
|
||||||
new Set([...uploadResults, ...newUploadResults])
|
setUploadResults((prev) => {
|
||||||
);
|
const newValue = [...prev, result.fileId];
|
||||||
setUploadResults(newValue);
|
onChange?.(newValue); // 传递值而非函数
|
||||||
onChange?.(newValue);
|
return newValue;
|
||||||
} catch (error) {
|
});
|
||||||
console.error("上传错误详情:", error);
|
},
|
||||||
toast.error(
|
(error) => {
|
||||||
`上传失败: ${error instanceof Error ? error.message : "未知错误"}`
|
console.error("上传错误:", error);
|
||||||
);
|
toast.error(
|
||||||
setUploadingFiles((prev) =>
|
`上传失败: ${
|
||||||
prev.map((f) => ({ ...f, status: "error" }))
|
error instanceof Error ? error.message : "未知错误"
|
||||||
);
|
}`
|
||||||
}
|
);
|
||||||
|
setUploadingFiles((prev) =>
|
||||||
|
prev.map((f) =>
|
||||||
|
f.fileKey === fileKey
|
||||||
|
? { ...f, status: "error" }
|
||||||
|
: f
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
fileKey
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
[uploadResults, onChange, handleFileUpload]
|
[handleFileUpload, onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Upload.Dragger
|
<Upload.Dragger
|
||||||
name="files"
|
name="files"
|
||||||
|
multiple
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
style={{ background: "transparent", borderStyle: "none" }}
|
style={{ background: "transparent", borderStyle: "none" }}
|
||||||
beforeUpload={handleChange}>
|
beforeUpload={handleBeforeUpload}>
|
||||||
<p className="ant-upload-drag-icon">
|
<p className="ant-upload-drag-icon">
|
||||||
<UploadOutlined />
|
<UploadOutlined />
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -140,64 +127,58 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
点击或拖拽文件到此区域进行上传
|
点击或拖拽文件到此区域进行上传
|
||||||
</p>
|
</p>
|
||||||
<p className="ant-upload-hint">支持单个或批量上传文件</p>
|
<p className="ant-upload-hint">支持单个或批量上传文件</p>
|
||||||
{/* 正在上传的文件 */}
|
|
||||||
{(uploadingFiles.length > 0 || completedFiles.length > 0) && (
|
|
||||||
<div className=" px-2 py-0 rounded mt-1 ">
|
|
||||||
{uploadingFiles.map((file) => (
|
|
||||||
<div
|
|
||||||
key={file.fileKey}
|
|
||||||
className="flex flex-col gap-1">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="text-sm">{file.name}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Progress
|
{/* 上传状态展示 */}
|
||||||
className="flex-1 w-full"
|
<div className="px-2 py-0 rounded mt-1">
|
||||||
percent={
|
{/* 上传中的文件 */}
|
||||||
file.status === "done"
|
{uploadingFiles.map((file) => (
|
||||||
? 100
|
<div
|
||||||
: Math.round(
|
key={file.fileKey}
|
||||||
uploadProgress?.[
|
className="flex flex-col gap-1 mb-2">
|
||||||
file?.fileKey
|
<div className="flex items-center gap-2">
|
||||||
] || 0
|
<span className="text-sm">{file.name}</span>
|
||||||
)
|
|
||||||
}
|
|
||||||
status={
|
|
||||||
file.status === "error"
|
|
||||||
? "exception"
|
|
||||||
: file.status === "done"
|
|
||||||
? "success"
|
|
||||||
: "active"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<Progress
|
||||||
{completedFiles.length > 0 &&
|
percent={
|
||||||
completedFiles.map((file, index) => (
|
file.status === "done"
|
||||||
<div
|
? 100
|
||||||
key={index}
|
: Math.round(
|
||||||
className="flex items-center justify-between gap-2">
|
uploadProgress?.[
|
||||||
<div className="flex items-center gap-2">
|
file.fileKey!
|
||||||
<CheckCircleOutlined className="text-green-500" />
|
] || 0
|
||||||
<div className="text-sm">
|
)
|
||||||
{file.name}
|
}
|
||||||
</div>
|
status={
|
||||||
</div>
|
file.status === "error"
|
||||||
<Button
|
? "exception"
|
||||||
type="text"
|
: "active"
|
||||||
danger
|
}
|
||||||
icon={<DeleteOutlined />}
|
/>
|
||||||
onClick={(e) => {
|
</div>
|
||||||
e.stopPropagation(); // 阻止事件冒泡
|
))}
|
||||||
if (file.fileId) {
|
|
||||||
handleRemoveFile(file.fileId); // 只删除文件
|
{/* 已完成的文件 */}
|
||||||
}
|
{completedFiles.map((file) => (
|
||||||
}}
|
<div
|
||||||
/>
|
key={file.fileId}
|
||||||
</div>
|
className="flex items-center justify-between gap-2 mb-2">
|
||||||
))}
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<CheckCircleOutlined className="text-green-500" />
|
||||||
)}
|
<span className="text-sm">{file.name}</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (file.fileId)
|
||||||
|
handleRemoveFile(file.fileId);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</Upload.Dragger>
|
</Upload.Dragger>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,26 @@
|
||||||
import { Link, NavLink, useNavigate } from "react-router-dom";
|
import { Link, NavLink, useNavigate } from "react-router-dom";
|
||||||
import { memo, useMemo } from "react";
|
import { memo } from "react";
|
||||||
import { SearchBar } from "./SearchBar";
|
import { SearchBar } from "./SearchBar";
|
||||||
import Navigation from "./navigation";
|
import Navigation from "./navigation";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { UserOutlined } from "@ant-design/icons";
|
import { UserOutlined } from "@ant-design/icons";
|
||||||
import { UserMenu } from "../element/usermenu/usermenu";
|
import { UserMenu } from "../element/usermenu/usermenu";
|
||||||
import { api, useAppConfig } from "@nice/client";
|
|
||||||
import { env } from "@web/src/env";
|
|
||||||
|
|
||||||
export const Header = memo(function Header() {
|
export const Header = memo(function Header() {
|
||||||
const { isAuthenticated } = useAuth();
|
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 (
|
return (
|
||||||
<header className="sticky top-0 z-50 bg-gradient-to-br from-primary-500 to-primary-800 text-white shadow-lg">
|
<header className="sticky top-0 z-50 bg-gradient-to-br from-primary-500 to-primary-800 text-white shadow-lg">
|
||||||
<div className=" mx-auto px-4">
|
<div className=" mx-auto px-4">
|
||||||
<div className="py-2">
|
<div className="py-4">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="">
|
<div className="">
|
||||||
{/** 在这里放置logo */}
|
<span className="text-xl font-bold">
|
||||||
{isLoading ? (
|
首长机关信箱
|
||||||
<div className="w-48 h-24 bg-gray-300 animate-pulse"></div>
|
</span>
|
||||||
) : (
|
<p className=" text-sm text-secondary-50">
|
||||||
logoUrl && (
|
聆怀若水,应语如风;纾难化困,践诺成春
|
||||||
<img
|
</p>
|
||||||
src={logoUrl}
|
|
||||||
alt="Logo"
|
|
||||||
style={{ width: 192, height: 72 }}
|
|
||||||
className=" w-full h-full"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow max-w-2xl">
|
<div className="flex-grow max-w-2xl">
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@ export function LetterCard({ letter }: LetterCardProps) {
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<UserOutlined className="text-primary text-base" />
|
<UserOutlined className="text-primary text-base" />
|
||||||
<Text className="text-primary font-medium">
|
<Text className="text-primary font-medium">
|
||||||
{letter.author?.showname || "匿名用户"}
|
{letter?.meta?.signature ||
|
||||||
|
letter.author?.showname ||
|
||||||
|
"匿名用户"}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,18 @@ export default function PostCommentCard({
|
||||||
src={post.author?.avatar}
|
src={post.author?.avatar}
|
||||||
size={50}
|
size={50}
|
||||||
name={!post.author?.avatar && post.author?.showname}
|
name={!post.author?.avatar && post.author?.showname}
|
||||||
ip={post?.meta?.ip}></CustomAvatar>
|
randomString={
|
||||||
|
post?.meta?.signature || post?.meta?.ip
|
||||||
|
}></CustomAvatar>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className={`flex-1 min-w-0 `}>
|
<div className={`flex-1 min-w-0 `}>
|
||||||
<div className="flex flex-1 justify-between ">
|
<div className="flex flex-1 justify-between ">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<span className="flex font-medium text-slate-900">
|
<span className="flex font-medium text-slate-900">
|
||||||
{post.author?.showname || "匿名用户"}
|
{post?.meta?.signature ||
|
||||||
|
post.author?.showname ||
|
||||||
|
"匿名用户"}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex text-sm text-slate-500 ">
|
<span className="flex text-sm text-slate-500 ">
|
||||||
{dayjs(post?.createdAt).format(
|
{dayjs(post?.createdAt).format(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import { motion } from "framer-motion";
|
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 QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
|
||||||
import { PostDetailContext } from "./context/PostDetailContext";
|
import { PostDetailContext } from "./context/PostDetailContext";
|
||||||
import { usePost } from "@nice/client";
|
import { usePost } from "@nice/client";
|
||||||
|
|
@ -9,13 +9,14 @@ 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 { CustomAvatar } from "@web/src/components/presentation/CustomAvatar";
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
export default function PostCommentEditor() {
|
export default function PostCommentEditor() {
|
||||||
const { post } = useContext(PostDetailContext);
|
const { post } = useContext(PostDetailContext);
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
const [isPreview, setIsPreview] = useState(false);
|
const [signature, setSignature] = useState<string | undefined>(undefined);
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
const [fileIds, setFileIds] = useState<string[]>([]);
|
||||||
const { create } = usePost();
|
const { create } = usePost();
|
||||||
|
|
||||||
|
|
@ -38,10 +39,14 @@ export default function PostCommentEditor() {
|
||||||
fileId: id,
|
fileId: id,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
meta: {
|
||||||
|
signature,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
toast.success("发布成功!");
|
toast.success("发布成功!");
|
||||||
setContent("");
|
setContent("");
|
||||||
|
setFileIds([]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("发布失败,请稍后重试");
|
toast.error("发布失败,请稍后重试");
|
||||||
console.error("Error posting comment:", error);
|
console.error("Error posting comment:", error);
|
||||||
|
|
@ -82,6 +87,7 @@ export default function PostCommentEditor() {
|
||||||
<div className="relative rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
<div className="relative rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
||||||
<TusUploader
|
<TusUploader
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
console.log("ids", value);
|
||||||
setFileIds(value);
|
setFileIds(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -90,12 +96,23 @@ export default function PostCommentEditor() {
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{!isContentEmpty(content) && (
|
{!isContentEmpty(content) && (
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
<CustomAvatar randomString={signature}></CustomAvatar>
|
||||||
|
<Input
|
||||||
|
maxLength={10}
|
||||||
|
style={{
|
||||||
|
width: 150,
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSignature(e.target.value);
|
||||||
|
}}
|
||||||
|
showCount
|
||||||
|
placeholder="签名"
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
size="large"
|
|
||||||
disabled={isContentEmpty(content)}
|
disabled={isContentEmpty(content)}
|
||||||
className="flex items-center space-x-2 bg-primary"
|
className="flex items-center space-x-2 bg-primary"
|
||||||
icon={<SendOutlined />}>
|
icon={<SendOutlined />}>
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,15 @@ export default function Header() {
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|
||||||
<Space className="mr-4">
|
<Space className="mr-4">
|
||||||
|
|
||||||
<span className="text-white">发件人</span>
|
<span className="text-white">发件人</span>
|
||||||
<Text className="text-white" strong>
|
<Text className="text-white" strong>
|
||||||
{post?.author?.showname || "匿名用户"}
|
{post?.meta?.signature ||
|
||||||
|
post?.author?.showname ||
|
||||||
|
"匿名用户"}
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
<Space className="mr-4">
|
<Space className="mr-4">
|
||||||
|
|
||||||
<span className="text-white">收件人 </span>
|
<span className="text-white">收件人 </span>
|
||||||
|
|
||||||
{post?.receivers?.map((receiver, index) => (
|
{post?.receivers?.map((receiver, index) => (
|
||||||
|
|
@ -43,27 +42,23 @@ export default function Header() {
|
||||||
strong
|
strong
|
||||||
className="text-white"
|
className="text-white"
|
||||||
key={`${index}`}>
|
key={`${index}`}>
|
||||||
{receiver?.meta?.rank} {receiver?.showname || '匿名用户'}
|
{receiver?.meta?.rank}{" "}
|
||||||
|
{receiver?.showname || "匿名用户"}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
{/* Date Info Badge */}
|
{/* Date Info Badge */}
|
||||||
<Space className="mr-4">
|
<Space className="mr-4">
|
||||||
|
|
||||||
|
|
||||||
<span className="text-white">创建于</span>
|
<span className="text-white">创建于</span>
|
||||||
<Text className="text-white">
|
<Text className="text-white">
|
||||||
|
|
||||||
{dayjs(post?.createdAt).format("YYYY-MM-DD")}
|
{dayjs(post?.createdAt).format("YYYY-MM-DD")}
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
{/* Last Updated Badge */}
|
{/* Last Updated Badge */}
|
||||||
<Space className="mr-4">
|
<Space className="mr-4">
|
||||||
|
|
||||||
<span className="text-white">最后更新于</span>
|
<span className="text-white">最后更新于</span>
|
||||||
<Text className="text-white">
|
<Text className="text-white">
|
||||||
|
|
||||||
{dayjs(post?.updatedAt).format("YYYY-MM-DD")}
|
{dayjs(post?.updatedAt).format("YYYY-MM-DD")}
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ export default function PostHateButton({ post }: { post: PostDto }) {
|
||||||
type={post?.hated ? "primary" : "default"}
|
type={post?.hated ? "primary" : "default"}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: post?.hated ? "#ff4d4f" : "#fff",
|
backgroundColor: post?.hated ? "#ff4d4f" : "#fff",
|
||||||
borderColor: post?.hated ? "transparent" : "#ff4d4f",
|
borderColor: post?.hated ? "transparent" : "",
|
||||||
color: post?.hated ? "#fff" : "#ff4d4f",
|
color: post?.hated ? "#fff" : "#000",
|
||||||
boxShadow: "none", // 去除阴影
|
boxShadow: "none", // 去除阴影
|
||||||
}}
|
}}
|
||||||
shape="round"
|
shape="round"
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,7 @@ export function LetterBasicForm() {
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<TabPane tab="附件" key="2">
|
<TabPane tab="附件" key="2">
|
||||||
<Form.Item name="resources" required={false}>
|
<Form.Item name="resources" required={false}>
|
||||||
<div className="rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
<div className="rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
||||||
|
|
||||||
<TusUploader
|
<TusUploader
|
||||||
onChange={(resources) =>
|
onChange={(resources) =>
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
|
|
@ -89,8 +88,7 @@ export function LetterBasicForm() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
@ -101,14 +99,26 @@ export function LetterBasicForm() {
|
||||||
是否公开
|
是否公开
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button
|
<div className="flex gap-2 ">
|
||||||
type="primary"
|
<Form.Item name={["meta", "signature"]}>
|
||||||
onClick={() => form.submit()}
|
<Input
|
||||||
size="large"
|
maxLength={10}
|
||||||
icon={<SendOutlined />}
|
style={{
|
||||||
className="w-full sm:w-40">
|
width: 150,
|
||||||
发送信件
|
}}
|
||||||
</Button>
|
showCount
|
||||||
|
placeholder="签名"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => form.submit()}
|
||||||
|
size="large"
|
||||||
|
icon={<SendOutlined />}
|
||||||
|
className="w-full sm:w-40">
|
||||||
|
发送信件
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@ import multiavatar from "@multiavatar/multiavatar";
|
||||||
interface CustomAvatarProps extends Omit<AvatarProps, "children"> {
|
interface CustomAvatarProps extends Omit<AvatarProps, "children"> {
|
||||||
src?: string;
|
src?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
ip?: string;
|
randomString?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CustomAvatar({
|
export function CustomAvatar({
|
||||||
src,
|
src,
|
||||||
name,
|
name,
|
||||||
className = "",
|
className = "",
|
||||||
ip,
|
randomString,
|
||||||
...props
|
...props
|
||||||
}: CustomAvatarProps) {
|
}: CustomAvatarProps) {
|
||||||
// 获取名字的第一个字符,如果没有名字则显示"匿"
|
// 获取名字的第一个字符,如果没有名字则显示"匿"
|
||||||
|
|
@ -39,7 +39,7 @@ export function CustomAvatar({
|
||||||
const avatarSrc =
|
const avatarSrc =
|
||||||
src || (name && name !== "匿名用户")
|
src || (name && name !== "匿名用户")
|
||||||
? src
|
? src
|
||||||
: generateAvatarFromIp(ip || "default");
|
: generateAvatarFromIp(randomString || "default");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue