feat: 新增大量社区功能与基础组件,优化站点整体体验

- 新增事件总线式toast通知系统
- 新增头像、阅读进度、404/500错误页等基础组件
- 重构页脚、布局、分页、点赞按钮等现有组件
- 新增用户个人页、通知中心、帖子编辑/发布/置顶/推荐管理功能
- 优化登录注册页面样式与交互
- 修复路径类型文件引用,更新站点元信息与字体配置
This commit is contained in:
2026-09-12 04:30:02 +08:00
parent a8f6ce4e4f
commit 1eaf3903d4
31 changed files with 2395 additions and 740 deletions

View File

@@ -0,0 +1,66 @@
"use client";
import { useEffect, useState } from "react";
import { X, AlertCircle, CheckCircle2 } from "lucide-react";
import type { ToastDetail } from "@/lib/toast";
export default function Toaster() {
const [items, setItems] = useState<ToastDetail[]>([]);
useEffect(() => {
const handler = (e: Event) => {
const detail = (e as CustomEvent<ToastDetail>).detail;
setItems((list) => [...list.slice(-2), detail]);
window.setTimeout(() => {
setItems((list) => list.filter((t) => t.id !== detail.id));
}, 3600);
};
window.addEventListener("j13-toast", handler);
return () => window.removeEventListener("j13-toast", handler);
}, []);
const dismiss = (id: number) => setItems((list) => list.filter((t) => t.id !== id));
return (
<div
className="fixed bottom-5 right-4 sm:right-6 z-[10000] flex flex-col gap-2 w-[calc(100vw-2rem)] sm:w-80"
aria-live="polite"
role="status"
>
{items.map((t) => (
<ToastCard key={t.id} toast={t} onClose={() => dismiss(t.id)} />
))}
</div>
);
}
function ToastCard({ toast, onClose }: { toast: ToastDetail; onClose: () => void }) {
const color = toast.variant === "error" ? "var(--danger)" : "var(--ok)";
return (
<div
className="j13-toast-in flex items-start gap-3 px-4 py-3.5"
style={{
background: "var(--panel)",
border: "1px solid var(--line)",
borderLeft: `3px solid ${color}`,
borderRadius: "var(--r-sm)",
boxShadow: "var(--shadow-lg)",
}}
role="alert"
>
<span className="shrink-0 mt-0.5" style={{ color }}>
{toast.variant === "error" ? <AlertCircle size={16} /> : <CheckCircle2 size={16} />}
</span>
<p className="flex-1 text-[13.5px] leading-snug" style={{ color: "var(--ink)" }}>
{toast.message}
</p>
<button
onClick={onClose}
className="shrink-0 meta hover:text-[var(--ink)] transition-colors"
aria-label="关闭提示"
>
<X size={14} />
</button>
</div>
);
}