feat(project): init

This commit is contained in:
zhangheng
2026-04-25 11:26:33 +08:00
commit 9bf212f67c
63 changed files with 11141 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
import type { ImageHistoryItem, PublicUser } from "@/lib/types";
interface HistoryGalleryProps {
currentUser: PublicUser;
items: ImageHistoryItem[];
}
const dateTimeFormatter = new Intl.DateTimeFormat("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
export default function HistoryGallery({
currentUser,
items,
}: HistoryGalleryProps) {
const totalUsers = new Set(items.map((item) => item.userId)).size;
const latestItem = items[0] ?? null;
return (
<div className="history-grid">
<div className="history-overview-grid">
<section className="card history-overview-card">
<div className="section-heading">
<div>
<p className="section-kicker">Archive</p>
<h3>
{currentUser.role === "admin" ? "全站生成历史" : "我的生成历史"}
</h3>
</div>
<span className="subtle-pill">{items.length} </span>
</div>
<div className="history-stat-grid">
<article className="history-stat-card">
<span></span>
<strong>{items.length}</strong>
</article>
<article className="history-stat-card">
<span>{currentUser.role === "admin" ? "涉及用户" : "当前身份"}</span>
<strong>
{currentUser.role === "admin" ? `${totalUsers}` : "普通用户"}
</strong>
</article>
<article className="history-stat-card">
<span></span>
<strong>
{latestItem
? dateTimeFormatter.format(new Date(latestItem.createdAt))
: "暂无记录"}
</strong>
</article>
</div>
</section>
<section className="card history-overview-card history-overview-card-muted">
<div className="section-heading">
<div>
<p className="section-kicker">Access</p>
<h3></h3>
</div>
</div>
<p className="muted block-copy">
{currentUser.role === "admin"
? "管理员可以查看所有用户的历史记录、原始提示词和生成参数,页面会额外标注每张图所属的账号。"
: "普通用户只能查看自己的历史记录和图片内容,接口会按当前登录账号做隔离。"}
</p>
<div className="meta-pills history-info-pills">
<span>Prompt </span>
<span></span>
<span></span>
</div>
</section>
</div>
{items.length === 0 ? (
<section className="card history-empty-card">
<div className="placeholder-orb" />
<h4></h4>
<p>
</p>
</section>
) : (
<section className="history-gallery-list">
{items.map((item) => (
<article key={item.id} className="card history-card">
<div className="history-card-media">
<img src={item.imageUrl} alt={item.fileName} loading="lazy" />
</div>
<div className="history-card-header">
<div>
<h4>{item.fileName}</h4>
<p className="muted">
{dateTimeFormatter.format(new Date(item.createdAt))}
</p>
</div>
{currentUser.role === "admin" ? (
<span className="subtle-pill history-owner-pill">
{item.username}
</span>
) : null}
</div>
<div className="meta-pills history-meta-pills">
<span>{item.model ?? "未知模型"}</span>
<span>{item.size ?? "未知尺寸"}</span>
<span>{item.quality ?? "未知质量"}</span>
</div>
<div className="history-meta-list">
<div>
<span></span>
<strong>{formatOutputFormatLabel(item)}</strong>
</div>
<div>
<span></span>
<strong>{item.background ?? "auto"}</strong>
</div>
<div>
<span></span>
<strong>{formatFileSize(item.fileSize)}</strong>
</div>
</div>
<details className="history-disclosure" open>
<summary></summary>
<p>{item.prompt}</p>
</details>
{item.revisedPrompt ? (
<details className="history-disclosure">
<summary></summary>
<p>{item.revisedPrompt}</p>
</details>
) : null}
<div className="history-actions">
<a
className="ghost-button"
href={item.imageUrl}
target="_blank"
rel="noreferrer"
>
</a>
<a className="primary-button history-download-button" href={item.downloadUrl}>
</a>
</div>
</article>
))}
</section>
)}
</div>
);
}
function formatFileSize(fileSize: number): string {
if (fileSize < 1024) {
return `${fileSize} B`;
}
if (fileSize < 1024 * 1024) {
return `${(fileSize / 1024).toFixed(1)} KB`;
}
return `${(fileSize / (1024 * 1024)).toFixed(2)} MB`;
}
function formatOutputFormatLabel(item: ImageHistoryItem): string {
if (item.outputFormat) {
return item.outputFormat.toUpperCase();
}
return item.mimeType.replace(/^image\//, "").toUpperCase();
}