Files
longli-mp/scripts/build-mock.mjs
2026-09-16 16:52:50 +08:00

200 lines
6.0 KiB
JavaScript
Raw 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.

/**
* 一次性数据构建脚本:把 DuohuaDiscovery 的 miniprogram_data.json
* 拆成小程序运行期使用的 mock 数据(字段与未来接口同构)。
*
* 运行node scripts/build-mock.mjs
*/
import fs from "node:fs";
import path from "node:path";
const ROOT = path.resolve(import.meta.dirname, "..");
const SRC = "/Users/gleen/OneFeel/YGChatCS/src/pages/DuohuaDiscovery/data/miniprogram_data.json";
const OUT = path.join(ROOT, "src/mock");
const data = JSON.parse(fs.readFileSync(SRC, "utf8"));
const SECTION_DEFINITIONS = [
{
key: "tourism",
title: "龙里票根经济",
subtitle: "领取龙里旅游优惠",
coverImage:
"https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/longli/miniprogram/attractions/attraction-001/01.jpg",
collections: ["attractions", "attraction_subprojects"],
supportAssetIds: ["tourism-overview-map", "tourism-guide-qr"],
},
{
key: "lodging",
title: "旅居服务",
subtitle: "酒店 · 民宿别院 · 旅居公寓 · 露营基地",
collections: ["accommodations"],
supportAssetIds: ["lodging-location-schematic", "lodging-guide-qr"],
},
{
key: "food",
title: "全域美食",
subtitle: "特色菜肴 · 地方小吃 · 甜品饮品",
collections: ["foods"],
supportAssetIds: ["food-guide-qr"],
},
{
key: "specialties",
title: "龙里特产",
subtitle: "刺梨制品 · 地方食品 · 农产品",
collections: ["specialties"],
supportAssetIds: [],
},
];
const COLLECTIONS = [
{ key: "attractions", label: "景点" },
{ key: "attraction_subprojects", label: "游乐演艺" },
{ key: "accommodations", label: "旅居" },
{ key: "foods", label: "美食" },
{ key: "specialties", label: "特产" },
];
/** 一期 mock源数据无营业时间字段统一占位待运营方按商户补充 */
const MOCK_BUSINESS_HOURS = "09:0022:00";
const DROP_FIELDS = [
"issues",
"source",
"source_no",
"images",
"main_image_object_key",
"gallery_object_keys",
"local_relative_path",
"oss_object_key",
"sha256",
"source_file",
"source_page",
"pdf_image_name",
"pdf_x0",
"pdf_top",
"pdf_x1",
"pdf_bottom",
"conversion_note",
"color_mode",
];
const trim = (obj) => {
const out = {};
for (const [k, v] of Object.entries(obj)) {
if (DROP_FIELDS.includes(k)) continue;
if (v === null || v === undefined || v === "") continue;
out[k] = v;
}
return out;
};
/** 电话归一:源数据里分隔符混用「—」「–」「-」,统一成半角连字符;另给纯数字供 makePhoneCall */
const normalizeContact = (item) => {
const raw = item?.contact?.raw || "";
if (!raw) return null;
const display = raw.replace(/[—–-~\s]/g, "-").replace(/-+/g, "-").replace(/-$/, "");
const dial = display.replace(/\D/g, "");
return { raw, display, dial };
};
/** 地址归一:源数据 address 是对象(含经纬度为 null页面只用一个字符串无地址返回空串页面整行隐藏 */
const normalizeAddress = (item) => {
const addr = item?.address;
if (!addr) return "";
if (typeof addr === "string") return addr;
if (addr.raw || addr.normalized) return addr.raw || addr.normalized;
if (addr.township_or_street) {
return [addr.province, addr.prefecture, addr.county, addr.township_or_street].filter(Boolean).join("");
}
return "";
};
const items = {};
const counts = {};
for (const { key } of COLLECTIONS) {
const list = Array.isArray(data[key]) ? data[key] : [];
let n = 0;
for (const raw of list) {
// 硬约束:过滤 enabled === false
if (raw.enabled === false) continue;
const item = trim(raw);
delete item.enabled;
delete item.address;
item.collection = key;
item.business_hours = item.business_hours || MOCK_BUSINESS_HOURS;
item.contact = normalizeContact(raw);
item.address_text = normalizeAddress(raw);
item.badge = item.subtype || item.category || item.type || "";
items[raw.id] = item;
n += 1;
}
counts[key] = n;
}
/** 分组目录:过滤 enabled=false / 空分组,按 type_order -> sort_within_type 排序 */
const catalog = {};
for (const { key } of COLLECTIONS) {
const groups = Array.isArray(data?.catalog_by_type?.[key]) ? data.catalog_by_type[key] : [];
catalog[key] = groups
.map((group) => {
const ids = (group.items || [])
.filter((it) => items[it.id])
.sort((a, b) => (a.sort_within_type ?? 0) - (b.sort_within_type ?? 0))
.map((it) => it.id);
return {
collection: key,
type_code: group.type_code,
type: group.type,
type_order: group.type_order ?? 0,
item_count: ids.length,
item_ids: ids,
};
})
.filter((group) => group.item_count > 0)
.sort((a, b) => a.type_order - b.type_order);
}
const assets = (data.assets || []).map((a) => ({
asset_id: a.asset_id,
entity_type: a.entity_type,
entity_id: a.entity_id,
entity_name: a.entity_name,
role: a.role,
sort_order: a.sort_order ?? 0,
oss_url: a.oss_url,
width_px: a.width_px,
height_px: a.height_px,
content_type: a.content_type,
}));
const sections = SECTION_DEFINITIONS.map((def) => {
const groups = def.collections.flatMap((c) =>
catalog[c].map((g) => ({ ...g, groupId: `${c}-${g.type_code}` }))
);
const ids = groups.flatMap((g) => g.item_ids);
const first = items[ids[0]];
return {
...def,
group_count: groups.length,
item_count: ids.length,
group_ids: groups.map((g) => g.groupId),
};
});
const write = (name, payload) => {
const file = path.join(OUT, name);
fs.writeFileSync(file, JSON.stringify(payload, null, 0) + "\n", "utf8");
const kb = (fs.statSync(file).size / 1024).toFixed(1);
console.log(`${name.padEnd(16)} ${kb} KB`);
};
fs.mkdirSync(OUT, { recursive: true });
write("sections.json", sections);
write("catalog.json", catalog);
write("items.json", items);
write("assets.json", assets);
console.log("\n集合条数已过滤 enabled=false:", counts);
console.log("items 总数:", Object.keys(items).length);