feat: 类型帖互动、积分经济与发帖体验

补齐问答采纳/重开、投票匿名与有票禁编、悬赏过期退回、抽奖回帖开奖,并接入积分账本与发帖附件可见性。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 03:17:07 +08:00
parent 1ef3d8e299
commit b9ba8cb8c9
58 changed files with 6913 additions and 506 deletions

View File

@@ -2,6 +2,7 @@ import Link from "next/link";
import { ArrowLeft } from "lucide-react";
import type { PostListItem } from "@/lib/api";
import { formatRelative } from "@/lib/format";
import { normalizePostType, postTypeLabel, contentAccessLabel, typeStatusLabel } from "@/lib/postMeta";
import LikeButton from "./LikeButton";
import Avatar from "./Avatar";
import { IconComment, IconEye } from "./Icons";
@@ -28,6 +29,8 @@ export default function PostRow({
}) {
const lastReply = post.last_reply;
const replied = lastReply && new Date(lastReply.created_at).getTime() > new Date(post.created_at).getTime() + 60_000;
const pt = normalizePostType(post.post_type);
const statusBadge = pt !== "discussion" ? typeStatusLabel(pt, post.type_status) : "";
return (
<div
@@ -52,23 +55,36 @@ export default function PostRow({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 min-w-0">
{/* 仅置顶/推荐在标题左侧 */}
{post.pinned > 0 && <span className="badge-pin shrink-0">置顶</span>}
{post.recommended && <span className="badge-rec shrink-0">推荐</span>}
{post.status === "pending" && <span className="badge-pending shrink-0">待审</span>}
<Link
href={`/post/${post.id}`}
className="stretch-link min-w-0 flex-1"
aria-label={post.title}
>
<h3
className={`font-bold leading-snug truncate transition-colors group-hover:text-[var(--accent)] ${
compact ? "text-[15px]" : "text-[16px] sm:text-[17px]"
}`}
style={{ color: "var(--ink)" }}
<div className="min-w-0 flex items-center gap-2 flex-1 overflow-hidden">
<Link
href={`/post/${post.id}`}
className="stretch-link min-w-0 shrink"
aria-label={post.title}
>
{post.title}
</h3>
</Link>
<h3
className={`font-bold leading-snug truncate transition-colors group-hover:text-[var(--accent)] ${
compact ? "text-[15px]" : "text-[16px] sm:text-[17px]"
}`}
style={{ color: "var(--ink)" }}
>
{post.title}
</h3>
</Link>
{/* 其余徽章紧跟标题右侧 */}
{post.status === "pending" && <span className="badge-pending shrink-0">待审</span>}
{pt !== "discussion" && pt !== "question" && (
<span className="badge-type shrink-0">{postTypeLabel(post.post_type)}</span>
)}
{statusBadge && (
<span className="badge-access shrink-0">{statusBadge}</span>
)}
{post.content_access && post.content_access !== "public" && (
<span className="badge-access shrink-0">{contentAccessLabel(post.content_access)}</span>
)}
</div>
</div>
<div
className={`meta relative z-[2] flex items-center gap-x-2 gap-y-1 flex-wrap ${