0.0.1.0
This commit is contained in:
+232
-29
@@ -80,26 +80,115 @@ const chinaGeoJSON = {
|
||||
]
|
||||
}
|
||||
|
||||
// 城市坐标数据
|
||||
// 主要城市坐标(包含1、2、3线城市)
|
||||
const geoCoordMap: Record<string, [number, number]> = {
|
||||
'广州': [113.23, 23.16],
|
||||
'深圳': [114.07, 22.62],
|
||||
// 一线城市
|
||||
'北京': [116.46, 39.92],
|
||||
'上海': [121.48, 31.22],
|
||||
'广州': [113.23, 23.16],
|
||||
'深圳': [114.07, 22.62],
|
||||
|
||||
// 新一线城市
|
||||
'成都': [104.06, 30.67],
|
||||
'杭州': [120.19, 30.26],
|
||||
'南京': [118.78, 32.04],
|
||||
'武汉': [114.31, 30.52],
|
||||
'重庆': [106.55, 29.56],
|
||||
'西安': [108.95, 34.27],
|
||||
'厦门': [118.10, 24.46]
|
||||
'苏州': [120.62, 31.32],
|
||||
'武汉': [114.31, 30.52],
|
||||
'南京': [118.78, 32.04],
|
||||
'天津': [117.20, 39.13],
|
||||
'郑州': [113.65, 34.76],
|
||||
'长沙': [112.94, 28.23],
|
||||
'东莞': [113.75, 23.05],
|
||||
'沈阳': [123.43, 41.80],
|
||||
'青岛': [120.38, 36.07],
|
||||
'合肥': [117.27, 31.86],
|
||||
'佛山': [113.12, 23.02],
|
||||
|
||||
// 二线城市
|
||||
'昆明': [102.73, 25.04],
|
||||
'福州': [119.30, 26.08],
|
||||
'无锡': [120.29, 31.57],
|
||||
'厦门': [118.10, 24.46],
|
||||
'哈尔滨': [126.63, 45.75],
|
||||
'长春': [125.35, 43.88],
|
||||
'南昌': [115.89, 28.68],
|
||||
'济南': [117.12, 36.65],
|
||||
'宁波': [121.55, 29.87],
|
||||
'大连': [121.62, 38.91],
|
||||
'贵阳': [106.71, 26.57],
|
||||
'温州': [120.67, 28.00],
|
||||
'石家庄': [114.52, 38.05],
|
||||
'泉州': [118.58, 24.93],
|
||||
'南宁': [108.33, 22.84],
|
||||
'金华': [119.65, 29.08],
|
||||
'常州': [119.95, 31.79],
|
||||
'珠海': [113.52, 22.30],
|
||||
'惠州': [114.42, 23.09],
|
||||
'嘉兴': [120.76, 30.77],
|
||||
'南通': [120.86, 32.01],
|
||||
'中山': [113.38, 22.52],
|
||||
'保定': [115.48, 38.85],
|
||||
'太原': [112.55, 37.87],
|
||||
'兰州': [103.79, 36.06],
|
||||
'台州': [121.43, 28.66],
|
||||
|
||||
// 三线城市
|
||||
'乌鲁木齐': [87.68, 43.77],
|
||||
'绍兴': [120.58, 30.03],
|
||||
'廊坊': [116.70, 39.54],
|
||||
'洛阳': [112.45, 34.62],
|
||||
'威海': [122.12, 37.52],
|
||||
'盐城': [120.13, 33.38],
|
||||
'临沂': [118.35, 35.05],
|
||||
'江门': [113.08, 22.61],
|
||||
'汕头': [116.69, 23.39],
|
||||
'泰州': [119.90, 32.49],
|
||||
'漳州': [117.65, 24.52],
|
||||
'邯郸': [114.47, 36.60],
|
||||
'济宁': [116.59, 35.38],
|
||||
'芜湖': [118.38, 31.33],
|
||||
'淄博': [118.05, 36.78],
|
||||
'银川': [106.27, 38.47],
|
||||
'柳州': [109.40, 24.33],
|
||||
'绵阳': [104.73, 31.48],
|
||||
'湖州': [120.10, 30.86],
|
||||
'衡阳': [112.61, 26.89],
|
||||
'莆田': [119.01, 25.43],
|
||||
'宜昌': [111.29, 30.70],
|
||||
'桂林': [110.28, 25.29],
|
||||
'三亚': [109.51, 18.25],
|
||||
'遵义': [106.93, 27.71],
|
||||
'咸阳': [108.71, 34.33],
|
||||
'上饶': [117.97, 28.45],
|
||||
'莱芜': [117.68, 36.21],
|
||||
'赣州': [114.94, 25.85],
|
||||
'揭阳': [116.35, 23.55],
|
||||
'扬州': [119.42, 32.39],
|
||||
'菏泽': [115.47, 35.23],
|
||||
'徐州': [117.20, 34.26],
|
||||
'肇庆': [112.47, 23.05],
|
||||
'海口': [110.33, 20.02],
|
||||
'拉萨': [91.13, 29.66],
|
||||
'呼和浩特': [111.75, 40.84],
|
||||
'包头': [109.84, 40.66],
|
||||
'南阳': [112.53, 33.00],
|
||||
'鞍山': [122.99, 41.12],
|
||||
'九江': [115.99, 29.71],
|
||||
'大庆': [125.11, 46.59],
|
||||
'平顶山': [113.31, 33.74],
|
||||
'抚顺': [123.92, 41.88],
|
||||
'秦皇岛': [119.57, 39.95],
|
||||
'株洲': [113.15, 27.83],
|
||||
'齐齐哈尔': [123.95, 47.33]
|
||||
}
|
||||
|
||||
export default function TimelinePage() {
|
||||
const { treeData, getMember } = useFamily()
|
||||
const { treeData, getMember, isLoading } = useFamily()
|
||||
const members = Object.values(treeData.members)
|
||||
const [mapLoaded, setMapLoaded] = useState(false)
|
||||
|
||||
// 注册中国地图 - 从外部文件加载完整数据
|
||||
// 注册中国地图 - 从外部文件加载完整数据(必须在条件渲染之前)
|
||||
useEffect(() => {
|
||||
fetch('/china.json')
|
||||
.then(response => response.json())
|
||||
@@ -115,9 +204,9 @@ export default function TimelinePage() {
|
||||
})
|
||||
}, [])
|
||||
|
||||
// -- Timeline Logic --
|
||||
// -- Timeline Logic -- (必须在条件渲染之前)
|
||||
// Flatten events: Births and Deaths
|
||||
const events = members
|
||||
const events = useMemo(() => members
|
||||
.flatMap((m) => {
|
||||
const evs = []
|
||||
if (m.birthDate) {
|
||||
@@ -138,19 +227,20 @@ export default function TimelinePage() {
|
||||
}
|
||||
return evs
|
||||
})
|
||||
.sort((a, b) => b.date.localeCompare(a.date)) // 倒序排列:最新的在上面
|
||||
.sort((a, b) => b.date.localeCompare(a.date)), [members]) // 倒序排列:最新的在上面
|
||||
|
||||
// -- Migration Logic --
|
||||
// Find moves between generations (Father -> Child location change)
|
||||
const migrations = members
|
||||
const migrations = useMemo(() => members
|
||||
.flatMap((m) => {
|
||||
if (!m.fatherId) return []
|
||||
const father = getMember(m.fatherId)
|
||||
if (!father) return []
|
||||
|
||||
// Check if location exists and is different
|
||||
const loc1 = father.ancestralHome || father.birthPlace
|
||||
const loc2 = m.ancestralHome || m.birthPlace
|
||||
// 优先使用 birthPlace(出生地)来判断迁徙,因为 ancestralHome(祖籍)通常不变
|
||||
const loc1 = father.birthPlace || father.ancestralHome
|
||||
const loc2 = m.birthPlace || m.ancestralHome
|
||||
|
||||
if (loc1 && loc2 && loc1 !== loc2) {
|
||||
return [
|
||||
@@ -165,27 +255,110 @@ export default function TimelinePage() {
|
||||
}
|
||||
return []
|
||||
})
|
||||
.sort((a, b) => (a.year || 0) - (b.year || 0))
|
||||
.sort((a, b) => (a.year || 0) - (b.year || 0)), [members, getMember])
|
||||
|
||||
// ECharts 地图配置
|
||||
const mapOption = useMemo(() => {
|
||||
// 城市坐标映射(简化城市名)
|
||||
const cityMap: Record<string, string> = {
|
||||
'广东省广州市': '广州',
|
||||
'广东省深圳市': '深圳',
|
||||
'北京市': '北京',
|
||||
'上海市': '上海',
|
||||
'四川省成都市': '成都',
|
||||
// 城市名称映射(支持省+市格式)
|
||||
const cityMap: Record<string, string> = {}
|
||||
|
||||
// 自动生成映射:将"XX省XX市"或"XX市"映射到城市名
|
||||
Object.keys(geoCoordMap).forEach(city => {
|
||||
cityMap[city] = city // 直接城市名
|
||||
cityMap[`${city}市`] = city // 城市+市
|
||||
|
||||
// 根据城市所在省份添加映射
|
||||
const provinceMap: Record<string, string[]> = {
|
||||
'广东省': ['广州', '深圳', '东莞', '佛山', '珠海', '惠州', '中山', '江门', '汕头', '肇庆'],
|
||||
'浙江省': ['杭州', '宁波', '温州', '金华', '嘉兴', '台州', '绍兴', '湖州'],
|
||||
'江苏省': ['苏州', '南京', '无锡', '常州', '南通', '盐城', '泰州', '扬州', '徐州'],
|
||||
'山东省': ['济南', '青岛', '威海', '临沂', '济宁', '淄博', '莱芜', '菏泽'],
|
||||
'四川省': ['成都', '绵阳'],
|
||||
'河北省': ['石家庄', '保定', '廊坊', '邯郸', '秦皇岛'],
|
||||
'福建省': ['福州', '厦门', '泉州', '莆田', '漳州'],
|
||||
'湖北省': ['武汉', '宜昌'],
|
||||
'湖南省': ['长沙', '衡阳', '株洲'],
|
||||
'陕西省': ['西安', '咸阳'],
|
||||
'河南省': ['郑州', '洛阳', '南阳', '平顶山'],
|
||||
'辽宁省': ['沈阳', '大连', '鞍山', '抚顺'],
|
||||
'安徽省': ['合肥', '芜湖'],
|
||||
'云南省': ['昆明'],
|
||||
'贵州省': ['贵阳', '遵义'],
|
||||
'广西壮族自治区': ['南宁', '柳州', '桂林'],
|
||||
'江西省': ['南昌', '九江', '赣州', '上饶'],
|
||||
'黑龙江省': ['哈尔滨', '大庆', '齐齐哈尔'],
|
||||
'吉林省': ['长春'],
|
||||
'山西省': ['太原'],
|
||||
'甘肃省': ['兰州'],
|
||||
'新疆维吾尔自治区': ['乌鲁木齐'],
|
||||
'宁夏回族自治区': ['银川'],
|
||||
'内蒙古自治区': ['呼和浩特', '包头'],
|
||||
'海南省': ['海口', '三亚'],
|
||||
'西藏自治区': ['拉萨'],
|
||||
'天津市': ['天津'],
|
||||
'重庆市': ['重庆'],
|
||||
'北京市': ['北京'],
|
||||
'上海市': ['上海']
|
||||
}
|
||||
|
||||
// 为每个城市添加"省+市"格式的映射
|
||||
Object.entries(provinceMap).forEach(([province, cities]) => {
|
||||
if (cities.includes(city)) {
|
||||
cityMap[`${province}${city}市`] = city
|
||||
cityMap[`${province}${city}`] = city
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 提取城市名称的辅助函数(去掉区的部分)
|
||||
const extractCity = (location: string): string => {
|
||||
// 尝试从 cityMap 中查找
|
||||
let city = cityMap[location]
|
||||
if (city && geoCoordMap[city]) return city
|
||||
|
||||
// 如果没找到,尝试提取省市部分(去掉区)
|
||||
// 例如:"山东省济南市历下区" -> "济南"
|
||||
const match = location.match(/(.+?省)?(.+?市)/)
|
||||
if (match) {
|
||||
const province = match[1] || ''
|
||||
const cityName = match[2] || ''
|
||||
|
||||
// 尝试多种组合
|
||||
const attempts = [
|
||||
location,
|
||||
province + cityName,
|
||||
cityName.replace('市', ''),
|
||||
cityName
|
||||
]
|
||||
|
||||
for (const attempt of attempts) {
|
||||
const mapped = cityMap[attempt]
|
||||
if (mapped && geoCoordMap[mapped]) return mapped
|
||||
}
|
||||
}
|
||||
|
||||
return location
|
||||
}
|
||||
|
||||
// 转换迁徙数据为 ECharts 格式(使用经纬度坐标)
|
||||
const lines = migrations.map(mig => {
|
||||
const fromCity = cityMap[mig.from] || mig.from
|
||||
const toCity = cityMap[mig.to] || mig.to
|
||||
const fromCity = extractCity(mig.from)
|
||||
const toCity = extractCity(mig.to)
|
||||
const fromCoord = geoCoordMap[fromCity]
|
||||
const toCoord = geoCoordMap[toCity]
|
||||
|
||||
if (!fromCoord || !toCoord) return null
|
||||
if (!fromCoord || !toCoord) {
|
||||
console.log('坐标未找到:', {
|
||||
from: mig.from,
|
||||
to: mig.to,
|
||||
fromCity,
|
||||
toCity,
|
||||
fromCoord,
|
||||
toCoord
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
fromName: fromCity,
|
||||
@@ -194,11 +367,13 @@ export default function TimelinePage() {
|
||||
}
|
||||
}).filter(Boolean)
|
||||
|
||||
console.log('迁徙数据:', { migrations, lines })
|
||||
|
||||
// 统计每个城市的迁入迁出
|
||||
const cityData: Record<string, number> = {}
|
||||
migrations.forEach(mig => {
|
||||
const from = cityMap[mig.from] || mig.from
|
||||
const to = cityMap[mig.to] || mig.to
|
||||
const from = extractCity(mig.from)
|
||||
const to = extractCity(mig.to)
|
||||
cityData[from] = (cityData[from] || 0) + 1
|
||||
cityData[to] = (cityData[to] || 0) + 1
|
||||
})
|
||||
@@ -251,10 +426,11 @@ export default function TimelinePage() {
|
||||
effect: {
|
||||
show: true,
|
||||
period: 3,
|
||||
trailLength: 0,
|
||||
trailLength: 0.2,
|
||||
symbol: 'arrow',
|
||||
symbolSize: 10,
|
||||
color: 'hsl(var(--primary))'
|
||||
symbolSize: 12,
|
||||
color: '#3b82f6',
|
||||
loop: true
|
||||
},
|
||||
zlevel: 1
|
||||
},
|
||||
@@ -292,6 +468,33 @@ export default function TimelinePage() {
|
||||
}
|
||||
}, [migrations])
|
||||
|
||||
// 显示加载状态
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex flex-col">
|
||||
<SiteHeader />
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
|
||||
<p className="text-muted-foreground">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 显示无数据状态
|
||||
if (members.length === 0) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex flex-col">
|
||||
<SiteHeader />
|
||||
<div className="flex-1 flex items-center justify-center text-muted-foreground">
|
||||
暂无家族成员数据
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex flex-col font-sans">
|
||||
<SiteHeader />
|
||||
|
||||
Reference in New Issue
Block a user