feat: 帖子收藏与用户关注
- 新增收藏功能:favorite 服务与 handler、FavoriteButton 组件 - 新增关注功能:follow 服务与 handler、FollowButton 组件 - 用户主页适配关注/粉丝与收藏展示 - flash 消息工具、广告与侧边栏组件微调
This commit is contained in:
@@ -5,6 +5,7 @@ import BoardIcon, { boardColor } from "@/components/BoardIcon";
|
||||
import Avatar from "@/components/Avatar";
|
||||
import RoleBadge from "@/components/RoleBadge";
|
||||
import LevelBadge from "@/components/LevelBadge";
|
||||
import BadgeChip from "@/components/BadgeChip";
|
||||
|
||||
// formatYMD 帖子详情页左栏「作者其他帖」日期
|
||||
function formatYMD(dateStr: string) {
|
||||
@@ -56,9 +57,14 @@ export function BoardCard({ board }: { board: Board }) {
|
||||
export function AuthorRailCard({
|
||||
profile,
|
||||
allowComments,
|
||||
boardNames,
|
||||
boardColors,
|
||||
}: {
|
||||
profile: UserProfile;
|
||||
allowComments: boolean;
|
||||
/** 板块 id → 名称/配色:板块管理员徽章展示「{板块名}板块管理员」并对齐板块色 */
|
||||
boardNames?: Record<number, string>;
|
||||
boardColors?: Record<number, number>;
|
||||
}) {
|
||||
const u = profile.user;
|
||||
const signature = (u.signature || "").trim();
|
||||
@@ -89,13 +95,23 @@ export function AuthorRailCard({
|
||||
<LevelBadge level={u.level} className="shrink-0" />
|
||||
</div>
|
||||
<p className="meta mt-0.5 truncate">@{u.username}</p>
|
||||
{u.role !== "user" && (
|
||||
<div className="mt-1">
|
||||
<RoleBadge role={u.role} boardIds={u.board_ids} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* 徽章行:站点角色 + 管理员颁布徽章统一收拢一处,悬浮显示颁布时间;窄栏最多 4 枚 */}
|
||||
{(u.role !== "user" || (u.badges ?? []).length > 0) && (
|
||||
<div className="mt-2.5 flex flex-wrap items-center gap-1.5">
|
||||
{u.role !== "user" && (
|
||||
<RoleBadge role={u.role} boardIds={u.board_ids} boardNames={boardNames} boardColors={boardColors} />
|
||||
)}
|
||||
{(u.badges ?? []).slice(0, 4).map((ub) => (
|
||||
<BadgeChip
|
||||
key={ub.id}
|
||||
badge={ub.badge}
|
||||
tip={`${ub.badge.name} · ${new Date(ub.awarded_at).toLocaleDateString("zh-CN")} 颁布`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* 有签名则展示;无则留空,不放占位文案 */}
|
||||
{signature ? (
|
||||
<p
|
||||
@@ -106,18 +122,21 @@ export function AuthorRailCard({
|
||||
{signature}
|
||||
</p>
|
||||
) : null}
|
||||
{/* 数据面板:圆角浅底 + 竖线分隔(参考作者卡布局,配色走站点令牌) */}
|
||||
<div
|
||||
className={`mt-3 grid border-t ${stats.length === 3 ? "grid-cols-3" : "grid-cols-2"}`}
|
||||
style={{ borderColor: "var(--line)" }}
|
||||
className={`mt-3 grid overflow-hidden rounded-[10px] ${
|
||||
stats.length === 3 ? "grid-cols-3" : "grid-cols-2"
|
||||
}`}
|
||||
style={{ background: "var(--panel-2)" }}
|
||||
>
|
||||
{stats.map((s, i) => (
|
||||
<div
|
||||
key={s.label}
|
||||
className="px-1.5 py-2.5 text-center"
|
||||
className="py-2.5 text-center"
|
||||
style={i > 0 ? { borderLeft: "1px solid var(--line)" } : undefined}
|
||||
>
|
||||
<p
|
||||
className="text-[17px] font-semibold leading-none tabular-nums"
|
||||
className="text-[16px] font-semibold leading-none tabular-nums"
|
||||
style={{ color: "var(--ink)" }}
|
||||
>
|
||||
{s.value}
|
||||
@@ -128,12 +147,17 @@ export function AuthorRailCard({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 连签胶囊:金色调软底,与站点 gold 令牌一致 */}
|
||||
{streak > 0 && (
|
||||
<p
|
||||
className="mt-2 flex items-center justify-center gap-1 text-[11.5px] font-medium"
|
||||
style={{ color: "var(--gold)" }}
|
||||
className="mt-2.5 flex h-9 items-center justify-center gap-1.5 rounded-full text-[12px] font-semibold"
|
||||
style={{
|
||||
background: "var(--gold-soft)",
|
||||
color: "var(--gold)",
|
||||
border: "1px solid color-mix(in srgb, var(--gold) 22%, transparent)",
|
||||
}}
|
||||
>
|
||||
<Flame size={11.5} /> 连签 {streak} 天
|
||||
<Flame size={12.5} /> 连签 {streak} 天
|
||||
</p>
|
||||
)}
|
||||
<Link href={`/u/${u.id}`} className="btn btn-line btn-sm w-full justify-center mt-3">
|
||||
|
||||
Reference in New Issue
Block a user