200 lines
6.0 KiB
JavaScript
200 lines
6.0 KiB
JavaScript
/**
|
||
* 一次性数据构建脚本:把 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:00–22: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);
|