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:
50
js/content/navigation/future-catalog.js
Normal file
50
js/content/navigation/future-catalog.js
Normal 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'] },
|
||||
]
|
||||
37
js/content/navigation/nav-schema.js
Normal file
37
js/content/navigation/nav-schema.js
Normal 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
|
||||
}
|
||||
110
js/content/navigation/runtime-nav.js
Normal file
110
js/content/navigation/runtime-nav.js
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user