feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等

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

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
This commit is contained in:
2026-10-01 03:06:05 +08:00
parent 7fbd6ba699
commit ff2ab286fb
141 changed files with 7527 additions and 1407 deletions

View File

@@ -1,5 +1,5 @@
import Link from "next/link";
import { ArrowLeft } from "lucide-react";
import { ArrowLeft, Pin, Star } from "lucide-react";
import type { PostListItem } from "@/lib/api";
import { formatRelative } from "@/lib/format";
import { getPublicSettingsCached } from "@/lib/serverData";
@@ -44,6 +44,8 @@ export default async function PostRow({
const pt = normalizePostType(post.post_type);
const statusBadge = pt !== "discussion" ? typeStatusLabel(pt, post.type_status) : "";
const statusClass = statusBadge ? typeStatusClass(pt, post.type_status) : "";
// 移动端徽章过多会挤占标题字数:仅隐藏「进行中」(开放式状态,详情页仍可见),终态徽章保留
const hideStatusOnMobile = statusBadge === "进行中";
const authorName = post.user.nickname || post.user.username;
return (
@@ -70,8 +72,16 @@ export default async 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.pinned > 0 && (
<span className="badge-pin shrink-0" title="置顶" aria-label="置顶">
<Pin size={12} fill="currentColor" strokeWidth={2.2} aria-hidden />
</span>
)}
{post.recommended && (
<span className="badge-rec shrink-0" title="推荐" aria-label="推荐">
<Star size={12} fill="currentColor" strokeWidth={2.2} aria-hidden />
</span>
)}
<div className="min-w-0 flex items-center gap-2 flex-1 overflow-hidden">
<Link
href={postPath(urlStyle, post.id)}
@@ -88,7 +98,11 @@ export default async function PostRow({
<span className="badge-type shrink-0">{postTypeLabel(post.post_type)}</span>
)}
{statusBadge && statusClass && (
<span className={`${statusClass} shrink-0`}>{statusBadge}</span>
<span
className={`${statusClass} shrink-0${hideStatusOnMobile ? " hidden sm:inline-flex" : ""}`}
>
{statusBadge}
</span>
)}
</div>
</div>