完善初始化更新

This commit is contained in:
2026-03-20 20:42:33 +08:00
parent 568ccb08fa
commit e6866feb29
39 changed files with 6986 additions and 2379 deletions

View File

@@ -0,0 +1,127 @@
import React, { useEffect, useState } from "react";
import { API_BASE, marked, formatWebsiteLabel, formatUserRegisteredAt } from "../config";
import icons from "../icons";
import { InfoRow, StatItem } from "./common";
export default function PublicUserPage({ account, onReady, onPreviewImage }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
let cancelled = false;
const loadPublicUser = async () => {
if (!account) {
setError("缺少账户名");
setLoading(false);
if (!cancelled && onReady) onReady();
return;
}
setLoading(true);
setError("");
setUser(null);
try {
const res = await fetch(`${API_BASE}/api/public/users/${encodeURIComponent(account)}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error || "加载公开主页失败");
setUser(data.user || null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
if (!cancelled && onReady) onReady();
}
};
loadPublicUser();
return () => { cancelled = true; };
}, [account, onReady]);
const avatarUrl = user?.avatarUrl || "https://dummyimage.com/160x160/ddd/fff&text=Avatar";
const displayName = user?.username || user?.account || "未命名用户";
return (
<section className="panel">
{loading && <div className="unified-page-loading">加载公开主页中</div>}
{!loading && error && (
<div className="unified-page-miss">
<h2>未找到用户</h2>
<div className="error">{error}</div>
<div className="actions">
<a className="primary" href="/">返回首页</a>
</div>
</div>
)}
{!loading && user && (
<div className="card profile">
<div className="profile-header">
<img
src={avatarUrl}
alt={displayName}
className="previewable-image"
onClick={() => onPreviewImage?.(avatarUrl, displayName)}
role="button"
tabIndex={0}
/>
<div>
<h2>{displayName}</h2>
</div>
</div>
<div className="profile-section-title profile-section-title--lead">基本信息</div>
<div className="profile-info-rows">
<InfoRow icon={icons.account} label="账户" value={user.account} />
<InfoRow icon={icons.username} label="用户名" value={user.username || "未填写"} />
<InfoRow icon={icons.calendar} label="注册时间" value={formatUserRegisteredAt(user.createdAt)} />
{user.websiteUrl ? (
<InfoRow icon={icons.link} label="个人主页">
<a
href={user.websiteUrl}
target="_blank"
rel="noopener noreferrer"
className="profile-external-link"
>
{formatWebsiteLabel(user.websiteUrl)}
</a>
</InfoRow>
) : null}
</div>
<div className="profile-section-title">统计信息</div>
<div className="profile-stats-flow">
<StatItem icon={icons.level} label="等级" value={`${user.level ?? 0}`} />
<StatItem icon={icons.coins} label="萌芽币" value={user.sproutCoins ?? 0} />
<StatItem icon={icons.calendar} label="签到天数" value={`${user.checkInDays ?? 0}`} />
<StatItem icon={icons.statLightning} label="连续签到" value={`${user.checkInStreak ?? 0}`} />
<StatItem icon={icons.statClock} label="访问天数" value={`${user.visitDays ?? 0}`} />
<StatItem icon={icons.statRepeat} label="连续访问" value={`${user.visitStreak ?? 0}`} />
</div>
<div className="profile-section-title">活动记录</div>
<div className="profile-activity-row">
<span>最后签到 <strong>{user.lastCheckInAt || user.lastCheckInDate || "未签到"}</strong></span>
<span>最后访问 <strong>{user.lastVisitAt || user.lastVisitDate || "未访问"}</strong></span>
</div>
<div className="profile-activity-row profile-visit-meta">
<span>
最后访问 IP{" "}
<strong className="mono">{user.lastVisitIp || "暂无"}</strong>
</span>
<span>
最后位置 <strong>{user.lastVisitDisplayLocation || "暂无"}</strong>
</span>
</div>
<div className="profile-section-title">个人简介</div>
<div className="markdown profile-markdown">
<div
className="markdown-body"
dangerouslySetInnerHTML={{ __html: marked.parse(user.bio || "暂无简介") }}
/>
</div>
</div>
)}
</section>
);
}