diff --git a/docs/sections-inventory.md b/docs/sections-inventory.md index ed5f3d032..055fb12ca 100644 --- a/docs/sections-inventory.md +++ b/docs/sections-inventory.md @@ -124,8 +124,11 @@ 侧栏 = 社交图标 + CONTACT US 表单 + IoT Gateways 折叠组;正文 Elementor 直出 + pageCss + 图片 WP 尺寸变体解析(medium→-300x272 类文件,服务端目录扫描缓存)。 - page_css 导入扩展至全部文章:254 篇有 post-.css 已导入,355 篇原站 404(无自定义 CSS)。 -- 已知数据差异:/blog/ 本地 category-blog 302 篇(26 页)vs 原站当前 7 页(dump 后原站 - 调整了分类成员);文章内文逐节高度仍有数据快照/lazy 假象差。 +- 已知数据差异:/blog/ ~~本地 302 篇 vs 原站 7 页~~ ✅ v0.10.36 核实:原站分类成员实为 + 318 篇(REST categories=53 = 本地同步后一致),"7 页"系取证只抓到分页窗口尾页; + 两侧均为 27 页(page/28 原站 404 → 本站分页路由越界已改真 404); + 文章内文逐节高度仍有数据快照/lazy 假象差。分类成员同步脚本 `scripts/import-live-terms.ts` + (幂等,dry 支持)。 - P1 产品详情 `/product/$slug` v0.10.27 ✅ 产品单模板一比一(elementor_library 2741,从 legacy dump 补录入库 slug=product-template;内嵌模板 29972「IoT Gateways for Recommendation」同步补录):hero 图廊 484×617/标题/meta(Model/Category)/短描述精确、 diff --git a/package.json b/package.json index 58ad0b280..adfb4815f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dusuniot-rebuild", - "version": "0.10.35", + "version": "0.10.36", "private": true, "type": "module", "scripts": { diff --git a/scripts/import-live-terms.ts b/scripts/import-live-terms.ts new file mode 100644 index 000000000..d4ef1572d --- /dev/null +++ b/scripts/import-live-terms.ts @@ -0,0 +1,62 @@ +/** + * # 脚本 同步文章分类成员(dump 快照 → 原站当前状态) + * + * 背景:/blog/ 归档本地 302 篇(26 页)vs 原站当前 7 页——dump 之后原站调整了 + * Blog/Gateway 等分类的成员。本脚本按原站 REST API 的 categories 重建本地 + * content_terms 分类关联(仅调整 REST 返回的文章;本地有而原站已删除的不动)。 + * + * 用法:bun scripts/import-live-terms.ts [--dry] + */ +import { SQL } from "bun"; + +const LIVE = "https://www.dusuniot.com"; +const sql = new SQL(process.env.DATABASE_URL || "postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot", { max: 1 }); +const dry = process.argv.includes("--dry"); + +/** 本地分类 legacy_id → id(WP term id 与 legacy_id 一致) */ +const catRows = await sql`select id, legacy_id, slug from categories where taxonomy = 'category' and legacy_id is not null`; +const catByLegacy = new Map(catRows.map((r: any) => [Number(r.legacy_id), r.id as number])); + +/** 本地已发布文章 legacy_id → content id */ +const posts = await sql`select id, legacy_id from contents where type = 'post' and status = 'published' and legacy_id is not null`; +const contentByLegacy = new Map(posts.map((r: any) => [Number(r.legacy_id), r.id as number])); +console.log("本地已发布文章:", posts.length); + +/** 逐页拉原站文章的分类(_fields 精简返回) */ +const liveCats = new Map(); // wp post id → term ids +let page = 1; +while (true) { + const res = await fetch(`${LIVE}/wp-json/wp/v2/posts?per_page=100&page=${page}&_fields=id,categories`, { headers: { "User-Agent": "Mozilla/5.0" } }); + if (!res.ok) { + if (res.status === 400) break; // 翻页越界 + console.log("REST", res.status, "停止"); + break; + } + const items = (await res.json()) as { id: number; categories: number[] }[]; + for (const it of items) liveCats.set(it.id, it.categories || []); + if (items.length < 100) break; + page++; + await new Promise((r) => setTimeout(r, 400)); // 限速(429 防护) +} +console.log("原站文章:", liveCats.size); + +let added = 0, removed = 0; +for (const [wpId, cats] of liveCats) { + const cid = contentByLegacy.get(wpId); + if (!cid) continue; + const localRows = await sql`select category_id from content_terms where content_id = ${cid}`; + const localSet = new Set(localRows.map((r: any) => r.category_id as number)); + const liveSet = new Set(cats.map((t) => catByLegacy.get(t)).filter((x): x is number => x != null)); + for (const add of liveSet) { + if (localSet.has(add)) continue; + added++; + if (!dry) await sql`insert into content_terms (content_id, category_id) values (${cid}, ${add}) on conflict do nothing`; + } + for (const rm of localSet) { + if (liveSet.has(rm)) continue; + removed++; + if (!dry) await sql`delete from content_terms where content_id = ${cid} and category_id = ${rm}`; + } +} +console.log(`分类成员同步: 新增 ${added} 移除 ${removed}${dry ? "(dry 未写库)" : ""}`); +await sql.end(); diff --git a/src/routes/blog.page.$page.tsx b/src/routes/blog.page.$page.tsx index 7d74f72f0..b25446e37 100644 --- a/src/routes/blog.page.$page.tsx +++ b/src/routes/blog.page.$page.tsx @@ -2,13 +2,17 @@ * # 路由 博客归档分页 /blog/page/$page/ * 与原站一致的第 2+ 页归档(每页 12 篇,日期倒序)。 */ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, notFound } from "@tanstack/react-router"; import { BlogArchive, loadBlogArchive } from "~/components/website/BlogArchive"; export const Route = createFileRoute("/blog/page/$page")({ loader: async ({ params }) => { const n = Number(params.page); - return loadBlogArchive(Number.isFinite(n) && n > 1 ? Math.floor(n) : 1); + const data = await loadBlogArchive(Number.isFinite(n) && n > 1 ? Math.floor(n) : 1); + // 越界页真 404(原站 /blog/page/28/ 404;软 404 影响 SEO) + if (!Number.isFinite(n) || n < 1 || (data.page > 1 && data.page > Math.ceil(data.list.total / data.list.pageSize))) + throw notFound(); + return data; }, head: ({ loaderData }) => ({ meta: [