Files
jiang13-bbs/frontend/components/AcceptedAnswerCard.tsx
freefire ff2ab286fb feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
2026-10-01 03:06:05 +08:00

149 lines
5.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { CheckCircle2, CircleHelp, Coins } from "lucide-react";
import type { AcceptedAnswer } from "@/lib/api";
import { userPath, commentAnchor } from "@/lib/urlStyle";
import { getPublicSettingsCached } from "@/lib/serverData";
import Avatar from "@/components/Avatar";
import MarkdownBody from "@/components/MarkdownBody";
/** 正文下方已采纳答案:对齐类型帖 typed-card;已采纳后不可重新打开 */
export default async function AcceptedAnswerCard({
answer,
variant = "question",
solvedAt,
}: {
answer: AcceptedAnswer;
/** 问答用「最佳答案」,悬赏用「已采纳」 */
variant?: "question" | "bounty";
/** 解决/采纳时刻;缺省用答案创建时间 */
solvedAt?: string;
}) {
const author = answer.user;
const name = author ? author.nickname || author.username || "用户" : "";
const isQuestion = variant === "question";
const title = isQuestion ? "问答" : "悬赏";
const statusLabel = isQuestion ? "已解决" : "已采纳";
const answerLabel = isQuestion ? "最佳答案" : "采纳答案";
const endTime = solvedAt || answer.created_at;
const endLabel = formatEndsAt(endTime);
const urlStyle = (await getPublicSettingsCached()).url_style;
const authorHref = author ? userPath(urlStyle, author.id) : "";
return (
<section
className={`j13-interact-card j13-typed-card j13-accepted-answer${isQuestion ? " is-question" : " is-bounty"}`}
aria-label={`${title} · ${statusLabel}`}
>
<div className="j13-typed-head">
<div className="j13-typed-head-main">
<h2 className={`j13-typed-title ${isQuestion ? "is-question" : "is-bounty"}`}>
{isQuestion ? (
<CircleHelp size={16} strokeWidth={2.25} aria-hidden />
) : (
<Coins size={16} strokeWidth={2.25} aria-hidden />
)}
{title}
</h2>
<span className="badge-status badge-status-solved shrink-0">{statusLabel}</span>
</div>
</div>
<div className="j13-accepted-answer__author">
<span className="j13-accepted-answer__chip">
<CheckCircle2 size={12} strokeWidth={2.25} aria-hidden />
{answerLabel}
</span>
{author ? (
<>
<Link href={authorHref} className="shrink-0" aria-label={name}>
<Avatar name={name} src={author.avatar || undefined} size={32} />
</Link>
<div className="min-w-0 flex flex-col gap-0.5">
<Link
href={authorHref}
className="truncate text-[13.5px] font-semibold hover:text-[var(--accent)] transition-colors"
style={{ color: "var(--ink)" }}
>
{name}
</Link>
<p className="meta text-[12px] flex items-center gap-1.5 flex-wrap !mb-0">
<time dateTime={answer.created_at}>
{new Date(answer.created_at).toLocaleString("zh-CN")}
</time>
{answer.floor > 0 && (
<>
<span aria-hidden>·</span>
<a
href={commentAnchor(answer.floor, answer.is_root, answer.id)}
className="hover:underline"
style={{ color: "var(--accent)" }}
>
#{answer.floor}
</a>
</>
)}
</p>
</div>
</>
) : (
<div className="min-w-0 flex flex-col gap-0.5">
<p className="meta text-[12px] flex items-center gap-1.5 flex-wrap !mb-0">
<time dateTime={answer.created_at}>
{new Date(answer.created_at).toLocaleString("zh-CN")}
</time>
{answer.floor > 0 && (
<>
<span aria-hidden>·</span>
<a
href={commentAnchor(answer.floor, answer.is_root, answer.id)}
className="hover:underline"
style={{ color: "var(--accent)" }}
>
#{answer.floor}
</a>
</>
)}
</p>
</div>
)}
</div>
<div className="j13-accepted-answer__body">
{answer.deleted ? (
<>
<p className="meta text-[14px]" style={{ color: "var(--ink-3)" }}>
该答案已被删除
</p>
{answer.content ? (
<div className="mt-2">
<MarkdownBody content={answer.content} className="prose-content prose-comment" />
</div>
) : null}
</>
) : (
<MarkdownBody content={answer.content} className="prose-content prose-comment" />
)}
</div>
<div className="j13-typed-foot">
<span className="j13-typed-foot-status">
<CheckCircle2 size={14} strokeWidth={2.25} aria-hidden />
{isQuestion ? "已采纳答案" : "悬赏已发放"}
</span>
{endLabel ? (
<span className="j13-typed-foot-time">结束时间: {endLabel}</span>
) : null}
</div>
</section>
);
}
function formatEndsAt(iso?: string) {
if (!iso) return "";
try {
return new Date(iso).toLocaleString("zh-CN");
} catch {
return iso;
}
}