184 lines
6.0 KiB
TypeScript
184 lines
6.0 KiB
TypeScript
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();
|
||
}
|