nav: 统一 NavNode schema + runtime/roadmap 分离 + 城市补父级

- navigation/nav-schema.js: 统一 NavNode schema 定义
- navigation/runtime-nav.js: MVP runtime(仅 6 城市 active)
- navigation/future-catalog.js: 路线图数据(不进 runtime)
- 旧索引文件标注 @deprecated,重定向到新路径
- 6 城市补 countryId/regionId
- game-design 明确 MVP vs V1.1+ 导航边界

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
manpengan
2026-03-28 23:31:52 +08:00
parent 18cd4d8409
commit 212b12ab6b
15 changed files with 470 additions and 439 deletions

View File

@@ -0,0 +1,50 @@
/**
* Future Catalog — 未来版本导航规划
*
* 这些节点不进入当前 runtime 消费链路。
* 当新版本上线新城市时,将对应节点移入 runtime-nav.js 并设 isEnabled: true。
* 引用的 cityId 可能尚不存在对应 CityManifest。
*/
// V1.1: 中国一线城市扩展
export const v11_china_cities = ['shanghai', 'guangzhou', 'chengdu', 'shenzhen', 'hangzhou', 'chongqing']
// V1.2: 中国二线 A
export const v12_china_cities = ['wuhan', 'xian', 'changsha', 'nanjing', 'harbin', 'xiamen']
// V1.3: 欧洲热门
export const v13_europe_cities = ['paris', 'london', 'rome', 'barcelona', 'prague', 'amsterdam']
// 中国地区划分V1.1+ 启用国家→地区→城市导航时使用)
export const chinaRegionPlan = [
{ id: 'north_china', name: '华北', cityIds: ['beijing', 'tianjin', 'shijiazhuang', 'taiyuan', 'hohhot'] },
{ id: 'northeast', name: '东北', cityIds: ['shenyang', 'changchun', 'harbin', 'dalian'] },
{ id: 'east_china', name: '华东', cityIds: ['shanghai', 'nanjing', 'hangzhou', 'hefei', 'fuzhou', 'nanchang', 'jinan', 'suzhou', 'xiamen', 'qingdao', 'ningbo'] },
{ id: 'central_china', name: '华中', cityIds: ['zhengzhou', 'wuhan', 'changsha'] },
{ id: 'south_china', name: '华南', cityIds: ['guangzhou', 'shenzhen', 'nanning', 'haikou', 'sanya'] },
{ id: 'southwest', name: '西南', cityIds: ['chengdu', 'chongqing', 'guiyang', 'kunming', 'lhasa'] },
{ id: 'northwest', name: '西北', cityIds: ['xian', 'lanzhou', 'xining', 'yinchuan', 'urumqi'] },
{ id: 'hk_macao_tw', name: '港澳台', cityIds: ['hongkong', 'macao', 'taipei'] },
]
// 全部亚洲国家V1.1+ 启用)
export const asiaCountryPlan = [
{ id: 'china', name: '中国', hasRegions: true, totalCities: 41 },
{ id: 'japan', name: '日本', cityIds: ['tokyo', 'osaka', 'kyoto', 'sapporo'] },
{ id: 'korea', name: '韩国', cityIds: ['seoul', 'busan', 'jeju'] },
{ id: 'thailand', name: '泰国', cityIds: ['bangkok', 'chiangmai', 'phuket'] },
{ id: 'singapore_country', name: '新加坡', cityIds: ['singapore'] },
{ id: 'vietnam', name: '越南', cityIds: ['hanoi', 'hochiminh'] },
{ id: 'malaysia', name: '马来西亚', cityIds: ['kualalumpur', 'malacca'] },
{ id: 'indonesia', name: '印度尼西亚', cityIds: ['jakarta', 'bali'] },
{ id: 'philippines', name: '菲律宾', cityIds: ['manila'] },
{ id: 'india', name: '印度', cityIds: ['delhi', 'mumbai'] },
{ id: 'uae', name: '阿联酋', cityIds: ['dubai'] },
{ id: 'turkey', name: '土耳其', cityIds: ['istanbul'] },
{ id: 'israel', name: '以色列', cityIds: ['jerusalem'] },
{ id: 'nepal', name: '尼泊尔', cityIds: ['kathmandu'] },
{ id: 'cambodia', name: '柬埔寨', cityIds: ['siemreap'] },
{ id: 'sri_lanka', name: '斯里兰卡', cityIds: ['colombo'] },
{ id: 'myanmar', name: '缅甸', cityIds: ['yangon'] },
{ id: 'mongolia', name: '蒙古', cityIds: ['ulaanbaatar'] },
]

View File

@@ -0,0 +1,37 @@
/**
* NavNode — 统一导航节点 schema
*
* 所有导航层级(洲/国家/地区/城市组)共用同一 shape。
* 分页通过 shared/pagination.js 的 paginate() 动态计算,不手写 page 常量。
*
* @typedef {Object} NavNode
* @property {'continent'|'country'|'region'|'city-group'} type
* @property {string} id - 全局唯一标识
* @property {string|null} parentId - 父节点 id洲级为 null
* @property {string} name - 中文名
* @property {string} nameEn - 英文名
* @property {number} sortOrder - 同级排序
* @property {string} themeColor - 主题色 HEX
* @property {'country'|'region'|'city'|null} childType - 子节点类型
* @property {string[]} childIds - 当前可消费的子节点 id仅已启用的
* @property {number} pageSize - 每页格子数,默认 9
* @property {boolean} isEnabled - 当前版本是否启用
* @property {boolean} isUnlockedByDefault - 是否默认解锁
*/
// schema 验证函数
export function validateNavNode(node) {
const required = ['type', 'id', 'name', 'nameEn', 'sortOrder', 'themeColor', 'childType', 'childIds', 'pageSize', 'isEnabled', 'isUnlockedByDefault']
const validTypes = ['continent', 'country', 'region', 'city-group']
const validChildTypes = ['country', 'region', 'city', null]
const errors = []
for (const key of required) {
if (!(key in node)) errors.push(`missing field: ${key}`)
}
if (!validTypes.includes(node.type)) errors.push(`invalid type: ${node.type}`)
if (!validChildTypes.includes(node.childType)) errors.push(`invalid childType: ${node.childType}`)
if (!Array.isArray(node.childIds)) errors.push('childIds must be array')
if (node.parentId !== null && typeof node.parentId !== 'string') errors.push('parentId must be string or null')
return errors
}

View File

@@ -0,0 +1,110 @@
/**
* Runtime Navigation — MVP
*
* 只包含当前版本已启用、childIds 均指向已存在实体的节点。
* MVP 导航直入亚洲城市页6 城市平铺。
* V1.1+ 启用完整洲→国家→地区→城市多级导航时,在此文件追加节点。
*/
export const runtimeNavNodes = [
// ── MVP: 只有 1 个洲,直接列出 6 城市 ──
{
type: 'continent',
id: 'asia',
parentId: null,
name: '亚洲',
nameEn: 'Asia',
sortOrder: 1,
themeColor: '#FF6B6B',
childType: 'city', // MVP: 直接指向城市,跳过国家层
childIds: ['beijing', 'tokyo', 'bangkok', 'seoul', 'singapore', 'istanbul'],
pageSize: 9,
isEnabled: true,
isUnlockedByDefault: true,
},
// ── 未启用的洲占位UI 显示锁定态) ──
{
type: 'continent',
id: 'europe',
parentId: null,
name: '欧洲',
nameEn: 'Europe',
sortOrder: 2,
themeColor: '#6B8CFF',
childType: 'country',
childIds: [], // 无可消费子节点
pageSize: 9,
isEnabled: false,
isUnlockedByDefault: false,
},
{
type: 'continent',
id: 'north_america',
parentId: null,
name: '北美洲',
nameEn: 'North America',
sortOrder: 3,
themeColor: '#FFB347',
childType: 'country',
childIds: [],
pageSize: 9,
isEnabled: false,
isUnlockedByDefault: false,
},
{
type: 'continent',
id: 'south_america',
parentId: null,
name: '南美洲',
nameEn: 'South America',
sortOrder: 4,
themeColor: '#4ECDC4',
childType: 'country',
childIds: [],
pageSize: 9,
isEnabled: false,
isUnlockedByDefault: false,
},
{
type: 'continent',
id: 'africa',
parentId: null,
name: '非洲',
nameEn: 'Africa',
sortOrder: 5,
themeColor: '#F7DC6F',
childType: 'country',
childIds: [],
pageSize: 9,
isEnabled: false,
isUnlockedByDefault: false,
},
{
type: 'continent',
id: 'oceania',
parentId: null,
name: '大洋洲',
nameEn: 'Oceania',
sortOrder: 6,
themeColor: '#82E0AA',
childType: 'country',
childIds: [],
pageSize: 9,
isEnabled: false,
isUnlockedByDefault: false,
},
]
// ── 便捷查询 ──
export function getNavNode(id) {
return runtimeNavNodes.find(n => n.id === id) || null
}
export function getEnabledRoots() {
return runtimeNavNodes.filter(n => n.parentId === null && n.isEnabled)
}
export function getChildren(parentId) {
return runtimeNavNodes.filter(n => n.parentId === parentId && n.isEnabled)
}