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

@@ -43,6 +43,7 @@ import {
AD_TIP_DELAY_MS,
adHoverTip,
} from "@/lib/ads";
import { densitySrcSet, isOwnImage, pickVariantWidth, variantURL } from "@/lib/responsiveImage";
/* ---------- 站点数据(2×2 bento 统计瓦片) ---------- */
@@ -428,9 +429,11 @@ export function FriendLinks({ links }: { links: FriendLink[] }) {
title={l.description || l.name}
>
{l.logo_url ? (
// 14px 显示位:本站图走 48/96 变体(PageSpeed 典型场景);外链 logo 保持原地址
// eslint-disable-next-line @next/next/no-img-element
<img
src={l.logo_url}
src={isOwnImage(l.logo_url) ? variantURL(l.logo_url, pickVariantWidth(14)) : l.logo_url}
srcSet={isOwnImage(l.logo_url) ? densitySrcSet(l.logo_url, 14) : undefined}
alt=""
width={14}
height={14}
@@ -511,9 +514,11 @@ export function AdsPanel({
data-tip={adHoverTip(ad.ends_at)}
data-tip-delay={AD_TIP_DELAY_MS}
>
{/* 固定 248×60 位:本站图走 256/512 密度变体 */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={ad.image_url}
src={isOwnImage(ad.image_url ?? "") ? variantURL(ad.image_url ?? "", pickVariantWidth(AD_IMAGE_WIDTH)) : ad.image_url}
srcSet={isOwnImage(ad.image_url ?? "") ? densitySrcSet(ad.image_url ?? "", AD_IMAGE_WIDTH) : undefined}
alt="推广"
className="j13-ad-image"
width={AD_IMAGE_WIDTH}
@@ -579,8 +584,15 @@ export function SponsorsPanel({
const tip = (sp.hover_text || sp.name || "").trim();
const href = sp.link_url?.trim() || undefined;
const inner = (
// logo ≤52px 高:本站图走 96/160 变体
// eslint-disable-next-line @next/next/no-img-element
<img src={sp.logo_url} alt={sp.name} className="j13-sponsor-logo" loading="lazy" />
<img
src={isOwnImage(sp.logo_url) ? variantURL(sp.logo_url, pickVariantWidth(52)) : sp.logo_url}
srcSet={isOwnImage(sp.logo_url) ? densitySrcSet(sp.logo_url, 52) : undefined}
alt={sp.name}
className="j13-sponsor-logo"
loading="lazy"
/>
);
return (
<li key={sp.id} className="j13-sponsor">