v0.10.21: 产品归档页 C1-C4(原站 /shop/ 模板 1398 + WPC 筛选 + WooCommerce 网格)

- 导入 archive 模板 post 1398(slug=shop-archive),/collection/ 用 ElementorRenderer
  直出 hero/两列外壳,按 widget id 注入 ShopFilters/ShopProducts;新增 server function
  解决客户端导航时 loader 打不开 db 的问题
- C1 hero:面包屑 Home / Shop(BreadcrumbWidget + vars.breadcrumb 上下文)
- C2 筛选:12 组属性(term taxonomy 顺序,scripts/import-attribute-term-order.ts);
  长期列表 5 项 + See more,Battery/Operating/Install 默认折叠;URL query 驱动,
  SoM 筛选实测 22 条;顶部 Filters 标题/已选 chips/Clear all
- C3 网格:WooCommerce 归档 DOM(结果计数、orderby、24 卡 4 列、卡片 1:1 图/标题/
  Read more);默认排序导入 products.meta.menu_order(scripts/import-product-menu-order.ts),
  前三个商品与原站一致(DSOM-040R/110R/130N)
- C4 分页:页码/箭头、当前页样式、page2 25–48 条验证
- 修复 Tailwind .columns-4 与 WooCommerce 网格类冲突;app.css 新增 shop 归档 scoped 样式
- 1436 实测:hero 52.8、shop 节 2906.7、筛选区 2798.7、网格 2523.3、卡 257.7×409、
  分页 y2855,与原站逐项一致(±6px 内);bun test 11 pass;版本 0.10.21
This commit is contained in:
bigcode
2026-11-02 12:32:18 +08:00
parent 5ff5f9d477
commit ddeeca3293
12 changed files with 866 additions and 46 deletions
+112
View File
@@ -0,0 +1,112 @@
/**
* # 脚本 导入产品属性术语顺序(对齐原站 /shop/ 筛选列表顺序)
* 原站 WPC 筛选按 wp_term_taxonomy 中的术语顺序展示(取证:pa_product-type 顺序
* SoM → IoT Gateway → Wi-Fi Repeater → Router → Beacon,对应 term_id 756/764/845/846/862;
* pa_cpu 顺序 PX30 → RK3328 → RK3588 → PX30K …)。本项目导入时按字母序写入,需要修复。
* 做法:解析 dump 的 wp_terms(term_id→name)与 wp_term_taxonomy(插入顺序),
* 对每个 pa_<属性 slug>,按顺序把 product_attribute_values.sort 设为序号。
* 运行:bun scripts/import-attribute-term-order.ts
*/
import { SQL } from "bun";
const DUMP = "/home/bigcode/data/dev/sites/dusuniot/legacy/dusuniot-db-20261029.sql";
const db = new SQL("postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot");
/** # 函数 提取某张表的 INSERT 段文本(支持多段 INSERT) */
function tableInserts(lines: string[], table: string): string[] {
const out: string[] = [];
let buf = "";
let on = false;
for (const l of lines) {
if (l.startsWith(`INSERT INTO \`${table}\` VALUES`)) {
on = true;
buf = l;
} else if (on) {
buf += l;
}
if (on && l.trimEnd().endsWith(";")) {
out.push(buf);
buf = "";
on = false;
}
}
return out;
}
/** # 函数 解析 SQL 值元组 */
function parseTuples(s: string): string[][] {
const out: string[][] = [];
let p = s.indexOf("(");
while (p >= 0 && p < s.length) {
const values: string[] = [];
let cur = "";
let inQ = false;
let esc = false;
let depth = 0;
p++;
for (; p < s.length; p++) {
const ch = s[p]!;
if (esc) { cur += ch; esc = false; continue; }
if (inQ) {
if (ch === "\\") { esc = true; continue; }
if (ch === "'") { inQ = false; continue; }
cur += ch;
continue;
}
if (ch === "'") { inQ = true; continue; }
if (ch === "(") { depth++; cur += ch; continue; }
if (ch === ")") {
if (depth === 0) { values.push(cur); cur = ""; p++; break; }
depth--;
cur += ch;
continue;
}
if (ch === "," && depth === 0) { values.push(cur); cur = ""; continue; }
cur += ch;
}
out.push(values);
p = s.indexOf("(", p);
}
return out;
}
const lines = (await Bun.file(DUMP).text()).split("\n");
// term_id → name
const nameById = new Map<number, string>();
for (const seg of tableInserts(lines, "wp_terms")) {
for (const v of parseTuples(seg)) {
if (v[0]) nameById.set(Number(v[0]), v[1] ?? "");
}
}
console.log("terms:", nameById.size);
// taxonomy → [term names](按 dump 插入顺序)
const byTax = new Map<string, string[]>();
for (const seg of tableInserts(lines, "wp_term_taxonomy")) {
for (const v of parseTuples(seg)) {
const taxonomy = v[2] ?? "";
const termId = Number(v[1]);
if (!taxonomy.startsWith("pa_")) continue;
const name = nameById.get(termId);
if (!name) continue;
const list = byTax.get(taxonomy) ?? [];
list.push(name);
byTax.set(taxonomy, list);
}
}
let updated = 0;
for (const [taxonomy, names] of byTax) {
const attrSlug = taxonomy.slice(3);
for (let i = 0; i < names.length; i++) {
const res = await db`
UPDATE product_attribute_values v SET sort = ${i}
FROM product_attributes a
WHERE v.attribute_id = a.id AND a.slug = ${attrSlug} AND lower(trim(v.value)) = lower(trim(${names[i]!}))`;
updated += res.count;
}
console.log(taxonomy, "→", names.length, "terms");
}
console.log("updated rows:", updated);
await db.end();