feat: 完成头像上传、站点主题配置与UI优化

- 新增附件模型与上传服务,拆分头像上传/管理逻辑
- 重构用户资料接口,移除直接修改头像字段,改用专用上传接口
- 添加站点公开设置API与管理员外观配置页
- 全局替换hover背景为accent-soft,优化交互一致性
- 完善头像组件,支持图片加载失败回退与src更新重置
- 新增用户状态事件总线,解决同页头像更新不及时问题
- 调整依赖包顺序与CI依赖版本
- 重构板块列表接口,新增帖子计数
- 迁移账号设置到用户主页tab,合并重复代码
This commit is contained in:
2026-09-15 01:06:12 +08:00
parent f58b2af993
commit 24c6dea38c
48 changed files with 2513 additions and 432 deletions

View File

@@ -0,0 +1,108 @@
import Link from "next/link";
import type { Metadata } from "next";
import { ChevronRight, MessagesSquare, LayoutGrid } from "lucide-react";
import { fetchBoards } from "@/lib/api";
export const metadata: Metadata = {
title: "板块目录 · 姜十三论坛",
description: "浏览论坛全部板块,找到感兴趣的话题社区",
};
// 板块汇总列表页:首页左栏「板块目录」标题的落地页,
// 展示全部板块(介绍 + 讨论量),点击进入板块独立页
export default async function BoardsPage() {
const { boards } = await fetchBoards();
const totalPosts = boards.reduce((sum, b) => sum + (b.post_count || 0), 0);
return (
<div>
{/* 面包屑 */}
<nav className="meta mb-5 flex items-center gap-1.5 text-[13px]">
<Link href="/" className="hover:text-[var(--accent)] transition-colors">首页</Link>
<ChevronRight size={13} />
<span style={{ color: "var(--accent)" }}>板块目录</span>
</nav>
{/* 页头 */}
<div className="panel p-6 sm:p-7 mb-6 relative overflow-hidden">
<span
aria-hidden
className="absolute -top-16 -right-12 w-48 h-48 rounded-full"
style={{ background: "var(--accent-soft)" }}
/>
<div className="relative flex items-center gap-4">
<span
className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl flex items-center justify-center shrink-0"
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
>
<LayoutGrid size={28} />
</span>
<div>
<h1 className="text-2xl sm:text-[28px] font-extrabold tracking-tight" style={{ color: "var(--ink)" }}>
板块目录
</h1>
<p className="meta mt-1.5">
共 {boards.length} 个板块 · {totalPosts} 篇讨论
</p>
</div>
</div>
</div>
{/* 板块卡片网格 */}
{boards.length === 0 ? (
<div className="panel text-center py-20 px-6">
<span
className="inline-flex w-14 h-14 rounded-full items-center justify-center mb-4"
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
>
<MessagesSquare size={26} />
</span>
<p className="font-bold text-lg" style={{ color: "var(--ink)" }}>还没有任何板块</p>
<p className="meta mt-1.5">板块由管理员创建,敬请期待</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{boards.map((b, i) => (
<Link
key={b.id}
href={`/board/${b.id}`}
className="j13-module group flex items-start gap-3.5 transition-[border-color,background] duration-150 hover:border-[var(--nav-active-line)] hover:bg-[var(--accent-soft)] focus-visible:bg-[var(--accent-soft)]"
aria-label={`${b.name}板块,${b.post_count} 篇讨论`}
>
<span className={`av-${i % 8} j13-bcard-ico !w-11 !h-11 !text-lg`} aria-hidden="true">
{b.name.slice(0, 1)}
</span>
<span className="min-w-0 flex-1">
<span className="flex items-center justify-between gap-2">
<span className="truncate text-[15px] font-extrabold" style={{ color: "var(--ink)" }}>
{b.name}
</span>
<span className="meta shrink-0 text-[11.5px] tabular-nums">{b.post_count} 篇</span>
</span>
{b.description?.trim() && (
<span
className="block mt-1 text-[12.5px] leading-relaxed line-clamp-2"
style={{ color: "var(--ink-3)" }}
>
{b.description.trim()}
</span>
)}
<span
className="mt-2.5 inline-flex items-center gap-0.5 text-[12px] font-bold transition-colors group-hover:text-[var(--accent)]"
style={{ color: "var(--ink-2)" }}
>
进入板块
<ChevronRight
size={12}
className="transition-transform group-hover:translate-x-0.5"
aria-hidden="true"
/>
</span>
</span>
</Link>
))}
</div>
)}
</div>
);
}