"use client" import { SiteHeader } from "@/components/site-header" import { useFamily } from "@/context/family-context" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from "@/components/ui/chinese-card" import { ScrollArea } from "@/components/ui/scroll-area" import { Clock, Map as MapIcon, ArrowRight, Calendar, User, Loader2 } from 'lucide-react' import { useMemo, useEffect, useState, useRef, useCallback } from 'react' import { MemberNameWithStatus } from "@/components/member-name-with-status" import { useSession } from 'next-auth/react' import { EmptyStateBadge } from '@/components/empty-state-badge' import { useRouter } from 'next/navigation' import type { ECharts } from 'echarts' // 中国地图 GeoJSON 数据 - 完整的轮廓 const chinaGeoJSON = { type: "FeatureCollection", features: [ { type: "Feature", properties: { name: "中国" }, geometry: { type: "MultiPolygon", coordinates: [ [ [ // 中国大陆完整轮廓 - 从东北开始逆时针 [134.5, 48.5], [135, 47], [134, 45], [133, 43], [131, 42], [130, 43], [129, 44], [128, 45], [127, 46], [126, 47], [125, 48], [124, 49], [123.5, 50], [123, 51], [122.5, 52], [122, 53], [121, 53.5], [120, 53.3], [119, 53], [118.5, 52], [119, 51], [119.5, 50], [120, 49], [121, 48], [122, 47], [123, 46], [124, 45], [125, 44], [126, 43], [127, 42.5], [128, 42], [129, 41.5], [130, 41], // 东部海岸线 [130, 40], [129, 39], [128, 38.5], [127, 38], [126, 37.5], [125, 37], [124, 36.5], [123, 36], [122.5, 35.5], [122, 35], [121.5, 34.5], [121, 34], [120.5, 33.5], [120, 33], [119.5, 32.5], [119.5, 32], [120, 31.5], [120.5, 31], [121, 30.5], [121.5, 30], [122, 29.5], [122, 29], [121.5, 28.5], [121, 28], [120.5, 27.5], [120, 27], [119.5, 26.5], [119, 26], [118.5, 25.5], [118, 25], [117.5, 24.5], [117, 24], [116.5, 23.5], [116, 23], [115.5, 22.5], [115, 22], [114, 21.8], [113, 21.5], [112, 21.3], [111, 21.2], [110.5, 21], // 南部边界 [110, 20.5], [109.5, 20], [109, 20.5], [108.5, 21], [108, 21.5], [107.5, 22], [107, 22.5], [106.5, 23], [106, 23.5], [105.5, 24], [105, 24.5], [104.5, 25], [104, 25.5], [103.5, 26], [103, 26.5], [102.5, 27], [102, 27.5], [101.5, 28], [101, 28.5], [100.5, 29], [100, 29.5], [99.5, 30], [99, 30.5], [98.5, 31], [98, 31.5], [97.5, 32], [97, 32.5], // 西南边界 [96.5, 33], [96, 33.5], [95.5, 34], [95, 34.5], [94.5, 35], [94, 35.5], [93.5, 36], [93, 36.5], [92.5, 37], [92, 37.5], [91.5, 38], [91, 38.5], [90.5, 39], [90, 39.5], [89.5, 40], [89, 40.5], [88.5, 41], [88, 41.5], [87.5, 42], [87, 42.5], [86.5, 43], [86, 43.5], [85.5, 44], [85, 44.5], [84.5, 45], [84, 45.5], [83.5, 46], [83, 46.5], [82.5, 47], [82, 47.5], [81.5, 48], [81, 48.5], [80.5, 49], [80, 49.5], // 西北边界 [79.5, 49.5], [79, 49.3], [78.5, 49], [78, 48.5], [77.5, 48], [77, 47.5], [76.5, 47], [76, 46.5], [75.5, 46], [75, 45.5], [74.5, 45], [74, 44.5], [73.5, 44], [73.5, 43.5], [74, 43], [74.5, 42.5], [75, 42], [75.5, 41.5], [76, 41], [76.5, 40.5], [77, 40], [77.5, 39.5], [78, 39], [78.5, 38.5], [79, 38.5], [79.5, 39], [80, 39.5], [80.5, 40], [81, 40.5], [81.5, 41], [82, 41.5], [82.5, 42], [83, 42.5], [83.5, 43], [84, 43.5], [84.5, 44], [85, 44.5], [85.5, 45], [86, 45.5], [86.5, 46], [87, 46.5], [87.5, 47], [88, 47.5], [88.5, 48], [89, 48.5], [89.5, 49], [90, 49.5], [90.5, 50], [91, 50.5], [91.5, 51], [92, 51.5], [92.5, 52], [93, 52.5], [93.5, 53], // 北部边界 [94, 53], [95, 52.8], [96, 52.5], [97, 52.2], [98, 52], [99, 51.8], [100, 51.5], [101, 51.2], [102, 51], [103, 50.8], [104, 50.5], [105, 50.2], [106, 50], [107, 49.8], [108, 49.5], [109, 49.3], [110, 49.2], [111, 49.1], [112, 49], [113, 48.9], [114, 48.8], [115, 48.7], [116, 48.6], [117, 48.5], [118, 48.5], [119, 48.5], [120, 48.6], [121, 48.7], [122, 48.8], [123, 48.9], [124, 49], [125, 49.1], [126, 49.2], [127, 49.3], [128, 49.4], [129, 49.5], [130, 49.5], [131, 49.4], [132, 49.2], [133, 49], [134, 48.8], [134.5, 48.5] ] ], // 海南岛 [[[108.6, 18.5], [109.5, 18.3], [110.5, 18.5], [111, 19], [111, 19.5], [110.5, 20], [109.5, 20.2], [108.6, 20], [108.2, 19.5], [108.6, 18.5]]], // 台湾岛 [[[120, 22], [120.5, 21.9], [121, 22], [121.5, 22.5], [122, 23], [122, 23.5], [122, 24], [121.8, 24.5], [121.5, 25], [121, 25.2], [120.5, 25.1], [120.2, 24.8], [120, 24.5], [120, 24], [120, 23.5], [120, 23], [120, 22.5], [120, 22]]] ] } } ] } // 主要城市坐标(包含1、2、3线城市) const geoCoordMap: Record = { // 一线城市 '北京': [116.46, 39.92], '上海': [121.48, 31.22], '广州': [113.23, 23.16], '深圳': [114.07, 22.62], // 新一线城市 '成都': [104.06, 30.67], '杭州': [120.19, 30.26], '重庆': [106.55, 29.56], '西安': [108.95, 34.27], '苏州': [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], // 国际城市 - 澳大利亚 '悉尼': [151.21, -33.87], '墨尔本': [144.96, -37.81], '布里斯班': [153.03, -27.47], '珀斯': [115.86, -31.95], '阿德莱德': [138.60, -34.93], '堪培拉': [149.13, -35.28], '黄金海岸': [153.43, -28.00], 'Sydney': [151.21, -33.87], 'Melbourne': [144.96, -37.81], 'Brisbane': [153.03, -27.47], 'Perth': [115.86, -31.95], 'Adelaide': [138.60, -34.93], 'Canberra': [149.13, -35.28], // 国际城市 - 美国主要城市 '纽约': [-74.01, 40.71], '洛杉矶': [-118.24, 34.05], '旧金山': [-122.42, 37.77], '西雅图': [-122.33, 47.61], '芝加哥': [-87.63, 41.88], '波士顿': [-71.06, 42.36], '华盛顿': [-77.04, 38.91], '迈阿密': [-80.19, 25.76], '休斯顿': [-95.37, 29.76], '达拉斯': [-96.80, 32.78], '费城': [-75.17, 39.95], '凤凰城': [-112.07, 33.45], '圣安东尼奥': [-98.49, 29.42], '圣地亚哥': [-117.16, 32.72], '丹佛': [-104.99, 39.74], '拉斯维加斯': [-115.14, 36.17], '波特兰': [-122.68, 45.52], '亚特兰大': [-84.39, 33.75], '底特律': [-83.05, 42.33], '明尼阿波利斯': [-93.27, 44.98], '奥斯汀': [-97.74, 30.27], '夏洛特': [-80.84, 35.23], '印第安纳波利斯': [-86.16, 39.77], '哥伦布': [-82.99, 39.96], '盐湖城': [-111.89, 40.76], '堪萨斯城': [-94.58, 39.10], '圣路易斯': [-90.20, 38.63], '匹兹堡': [-79.99, 40.44], '辛辛那提': [-84.51, 39.10], '克利夫兰': [-81.69, 41.50], '奥兰多': [-81.38, 28.54], '坦帕': [-82.46, 27.95], '新奥尔良': [-90.07, 29.95], '纳什维尔': [-86.78, 36.16], '孟菲斯': [-90.05, 35.15], '巴尔的摩': [-76.61, 39.29], '密尔沃基': [-87.91, 43.04], '阿尔伯克基': [-106.65, 35.08], '图森': [-110.93, 32.22], '檀香山': [-157.86, 21.31], '安克雷奇': [-149.90, 61.22], // 美国各州首府 '杰克逊': [-90.18, 32.30], // 密西西比州首府 '蒙哥马利': [-86.30, 32.38], // 阿拉巴马州首府 '小石城': [-92.29, 34.75], // 阿肯色州首府 '萨克拉门托': [-121.49, 38.58], // 加州首府 '哈特福德': [-72.68, 41.76], // 康涅狄格州首府 '多佛': [-75.52, 39.16], // 特拉华州首府 '塔拉哈西': [-84.28, 30.44], // 佛罗里达州首府 '博伊西': [-116.20, 43.62], // 爱达荷州首府 '斯普林菲尔德': [-89.64, 39.80], // 伊利诺伊州首府 '得梅因': [-93.62, 41.59], // 艾奥瓦州首府 '托皮卡': [-95.68, 39.05], // 堪萨斯州首府 '法兰克福肯塔基': [-84.87, 38.20], // 肯塔基州首府 '巴吞鲁日': [-91.15, 30.45], // 路易斯安那州首府 '奥古斯塔': [-69.77, 44.31], // 缅因州首府 '安纳波利斯': [-76.49, 38.98], // 马里兰州首府 '兰辛': [-84.55, 42.73], // 密歇根州首府 '圣保罗明尼苏达': [-93.09, 44.94], // 明尼苏达州首府 '杰斐逊城': [-92.17, 38.58], // 密苏里州首府 '海伦娜': [-112.03, 46.59], // 蒙大拿州首府 '林肯': [-96.70, 40.81], // 内布拉斯加州首府 '卡森城': [-119.77, 39.16], // 内华达州首府 '康科德': [-71.54, 43.21], // 新罕布什尔州首府 '特伦顿': [-74.76, 40.22], // 新泽西州首府 '圣菲': [-105.94, 35.69], // 新墨西哥州首府 '奥尔巴尼': [-73.76, 42.65], // 纽约州首府 '罗利': [-78.64, 35.78], // 北卡罗来纳州首府 '俾斯麦': [-100.79, 46.81], // 北达科他州首府 '俄克拉荷马城': [-97.52, 35.47], // 俄克拉荷马州首府 '塞勒姆': [-123.03, 44.94], // 俄勒冈州首府 '哈里斯堡': [-76.88, 40.27], // 宾夕法尼亚州首府 '普罗维登斯': [-71.41, 41.82], // 罗德岛州首府 '哥伦比亚': [-81.03, 34.00], // 南卡罗来纳州首府 '皮尔': [-100.35, 44.37], // 南达科他州首府 '里士满': [-77.43, 37.54], // 弗吉尼亚州首府 '奥林匹亚': [-122.90, 47.04], // 华盛顿州首府 '查尔斯顿': [-81.63, 38.35], // 西弗吉尼亚州首府 '麦迪逊': [-89.40, 43.07], // 威斯康星州首府 '夏延': [-104.82, 41.14], // 怀俄明州首府 // 美国州名映射(使用州内最大城市) '密西西比': [-90.18, 32.30], // 杰克逊 '密西西比州': [-90.18, 32.30], 'Mississippi': [-90.18, 32.30], '加利福尼亚': [-118.24, 34.05], // 洛杉矶 '加利福尼亚州': [-118.24, 34.05], 'California': [-118.24, 34.05], '德克萨斯': [-95.37, 29.76], // 休斯顿 '德克萨斯州': [-95.37, 29.76], 'Texas': [-95.37, 29.76], '佛罗里达': [-80.19, 25.76], // 迈阿密 '佛罗里达州': [-80.19, 25.76], 'Florida': [-80.19, 25.76], '伊利诺伊': [-87.63, 41.88], // 芝加哥 '伊利诺伊州': [-87.63, 41.88], 'Illinois': [-87.63, 41.88], '宾夕法尼亚': [-75.17, 39.95], // 费城 '宾夕法尼亚州': [-75.17, 39.95], 'Pennsylvania': [-75.17, 39.95], '俄亥俄': [-82.99, 39.96], // 哥伦布 '俄亥俄州': [-82.99, 39.96], 'Ohio': [-82.99, 39.96], '乔治亚': [-84.39, 33.75], // 亚特兰大 '乔治亚州': [-84.39, 33.75], 'Georgia': [-84.39, 33.75], '北卡罗来纳': [-80.84, 35.23], // 夏洛特 '北卡罗来纳州': [-80.84, 35.23], 'North Carolina': [-80.84, 35.23], '密歇根': [-83.05, 42.33], // 底特律 '密歇根州': [-83.05, 42.33], 'Michigan': [-83.05, 42.33], '新泽西': [-74.01, 40.71], // 纽约附近 '新泽西州': [-74.01, 40.71], 'New Jersey': [-74.01, 40.71], '弗吉尼亚': [-77.43, 37.54], // 里士满 '弗吉尼亚州': [-77.43, 37.54], 'Virginia': [-77.43, 37.54], '华盛顿州': [-122.33, 47.61], // 西雅图 'Washington': [-122.33, 47.61], '亚利桑那': [-112.07, 33.45], // 凤凰城 '亚利桑那州': [-112.07, 33.45], 'Arizona': [-112.07, 33.45], '马萨诸塞': [-71.06, 42.36], // 波士顿 '马萨诸塞州': [-71.06, 42.36], 'Massachusetts': [-71.06, 42.36], '田纳西': [-86.78, 36.16], // 纳什维尔 '田纳西州': [-86.78, 36.16], 'Tennessee': [-86.78, 36.16], '印第安纳': [-86.16, 39.77], // 印第安纳波利斯 '印第安纳州': [-86.16, 39.77], 'Indiana': [-86.16, 39.77], '马里兰': [-76.61, 39.29], // 巴尔的摩 '马里兰州': [-76.61, 39.29], 'Maryland': [-76.61, 39.29], '明尼苏达': [-93.27, 44.98], // 明尼阿波利斯 '明尼苏达州': [-93.27, 44.98], 'Minnesota': [-93.27, 44.98], '科罗拉多': [-104.99, 39.74], // 丹佛 '科罗拉多州': [-104.99, 39.74], 'Colorado': [-104.99, 39.74], '威斯康星': [-87.91, 43.04], // 密尔沃基 '威斯康星州': [-87.91, 43.04], 'Wisconsin': [-87.91, 43.04], '密苏里': [-90.20, 38.63], // 圣路易斯 '密苏里州': [-90.20, 38.63], 'Missouri': [-90.20, 38.63], '阿拉巴马': [-86.30, 32.38], // 蒙哥马利 '阿拉巴马州': [-86.30, 32.38], 'Alabama': [-86.30, 32.38], '路易斯安那': [-90.07, 29.95], // 新奥尔良 '路易斯安那州': [-90.07, 29.95], 'Louisiana': [-90.07, 29.95], '肯塔基': [-84.87, 38.20], // 法兰克福 '肯塔基州': [-84.87, 38.20], 'Kentucky': [-84.87, 38.20], '俄勒冈': [-122.68, 45.52], // 波特兰 '俄勒冈州': [-122.68, 45.52], 'Oregon': [-122.68, 45.52], '俄克拉荷马': [-97.52, 35.47], // 俄克拉荷马城 '俄克拉荷马州': [-97.52, 35.47], 'Oklahoma': [-97.52, 35.47], '康涅狄格': [-72.68, 41.76], // 哈特福德 '康涅狄格州': [-72.68, 41.76], 'Connecticut': [-72.68, 41.76], '犹他': [-111.89, 40.76], // 盐湖城 '犹他州': [-111.89, 40.76], 'Utah': [-111.89, 40.76], '内华达': [-115.14, 36.17], // 拉斯维加斯 '内华达州': [-115.14, 36.17], 'Nevada': [-115.14, 36.17], '阿肯色': [-92.29, 34.75], // 小石城 '阿肯色州': [-92.29, 34.75], 'Arkansas': [-92.29, 34.75], '爱荷华': [-93.62, 41.59], // 得梅因 '爱荷华州': [-93.62, 41.59], 'Iowa': [-93.62, 41.59], '堪萨斯': [-94.58, 39.10], // 堪萨斯城 '堪萨斯州': [-94.58, 39.10], 'Kansas': [-94.58, 39.10], '新墨西哥': [-106.65, 35.08], // 阿尔伯克基 '新墨西哥州': [-106.65, 35.08], 'New Mexico': [-106.65, 35.08], '内布拉斯加': [-96.70, 40.81], // 林肯 '内布拉斯加州': [-96.70, 40.81], 'Nebraska': [-96.70, 40.81], '夏威夷': [-157.86, 21.31], // 檀香山 '夏威夷州': [-157.86, 21.31], 'Hawaii': [-157.86, 21.31], '阿拉斯加': [-149.90, 61.22], // 安克雷奇 '阿拉斯加州': [-149.90, 61.22], 'Alaska': [-149.90, 61.22], // 英文城市名 'New York': [-74.01, 40.71], 'Los Angeles': [-118.24, 34.05], 'San Francisco': [-122.42, 37.77], 'Seattle': [-122.33, 47.61], 'Chicago': [-87.63, 41.88], 'Boston': [-71.06, 42.36], 'Washington DC': [-77.04, 38.91], 'Miami': [-80.19, 25.76], 'Houston': [-95.37, 29.76], 'Dallas': [-96.80, 32.78], 'Philadelphia': [-75.17, 39.95], 'Phoenix': [-112.07, 33.45], 'San Diego': [-117.16, 32.72], 'Denver': [-104.99, 39.74], 'Las Vegas': [-115.14, 36.17], 'Portland': [-122.68, 45.52], 'Atlanta': [-84.39, 33.75], 'Detroit': [-83.05, 42.33], 'Jackson': [-90.18, 32.30], // 国际城市 - 加拿大 '多伦多': [-79.38, 43.65], '温哥华': [-123.12, 49.28], '蒙特利尔': [-73.57, 45.50], '卡尔加里': [-114.07, 51.05], '渥太华': [-75.70, 45.42], '埃德蒙顿': [-113.49, 53.55], 'Toronto': [-79.38, 43.65], 'Vancouver': [-123.12, 49.28], 'Montreal': [-73.57, 45.50], 'Calgary': [-114.07, 51.05], 'Ottawa': [-75.70, 45.42], // 国际城市 - 欧洲 '伦敦': [-0.13, 51.51], '巴黎': [2.35, 48.86], '柏林': [13.40, 52.52], '阿姆斯特丹': [4.90, 52.37], '罗马': [12.50, 41.90], '马德里': [-3.70, 40.42], '巴塞罗那': [2.17, 41.39], '维也纳': [16.37, 48.21], '布鲁塞尔': [4.35, 50.85], '苏黎世': [8.54, 47.38], '慕尼黑': [11.58, 48.14], '法兰克福德国': [8.68, 50.11], '米兰': [9.19, 45.46], '都柏林': [-6.26, 53.35], '斯德哥尔摩': [18.07, 59.33], '哥本哈根': [12.57, 55.68], '奥斯陆': [10.75, 59.91], '赫尔辛基': [24.94, 60.17], '华沙': [21.01, 52.23], '布拉格': [14.42, 50.08], '布达佩斯': [19.04, 47.50], '雅典': [23.73, 37.98], '里斯本': [-9.14, 38.72], '莫斯科': [37.62, 55.76], '圣彼得堡': [30.31, 59.94], 'London': [-0.13, 51.51], 'Paris': [2.35, 48.86], 'Berlin': [13.40, 52.52], 'Rome': [12.50, 41.90], 'Madrid': [-3.70, 40.42], 'Amsterdam': [4.90, 52.37], 'Munich': [11.58, 48.14], 'Frankfurt': [8.68, 50.11], // 国际城市 - 亚洲其他 '东京': [139.69, 35.69], '大阪': [135.50, 34.69], '京都': [135.77, 35.01], '新加坡': [103.82, 1.35], '首尔': [126.98, 37.57], '釜山': [129.04, 35.18], '曼谷': [100.50, 13.76], '吉隆坡': [101.69, 3.14], '雅加达': [106.85, -6.21], '马尼拉': [120.98, 14.60], '河内': [105.85, 21.03], '胡志明市': [106.63, 10.82], '新德里': [77.21, 28.61], '孟买': [72.88, 19.08], '班加罗尔': [77.59, 12.97], '迪拜': [55.27, 25.20], '特拉维夫': [34.78, 32.09], '香港': [114.17, 22.32], '澳门': [113.55, 22.20], '台北': [121.56, 25.04], '高雄': [120.31, 22.62], 'Tokyo': [139.69, 35.69], 'Osaka': [135.50, 34.69], 'Singapore': [103.82, 1.35], 'Seoul': [126.98, 37.57], 'Bangkok': [100.50, 13.76], 'Hong Kong': [114.17, 22.32], 'Dubai': [55.27, 25.20], // 国际城市 - 南美洲 '圣保罗巴西': [-46.63, -23.55], '里约热内卢': [-43.17, -22.91], '布宜诺斯艾利斯': [-58.38, -34.60], '圣地亚哥智利': [-70.65, -33.45], '利马': [-77.03, -12.05], '波哥大': [-74.07, 4.71], 'São Paulo': [-46.63, -23.55], 'Rio de Janeiro': [-43.17, -22.91], 'Buenos Aires': [-58.38, -34.60], // 国际城市 - 非洲 '开罗': [31.24, 30.04], '约翰内斯堡': [28.05, -26.20], '开普敦': [18.42, -33.93], '拉各斯': [3.39, 6.45], '内罗毕': [36.82, -1.29], 'Cairo': [31.24, 30.04], 'Johannesburg': [28.05, -26.20], 'Cape Town': [18.42, -33.93], // 国际城市 - 大洋洲 '奥克兰': [174.76, -36.85], '惠灵顿': [174.78, -41.29], 'Auckland': [174.76, -36.85], 'Wellington': [174.78, -41.29] } export default function TimelinePage() { const { treeData, currentTree, getMember, isLoading } = useFamily() const { data: session, status } = useSession() const members = Object.values(treeData.members) const [mapLoaded, setMapLoaded] = useState(false) const treeId = currentTree?.id const router = useRouter() const chartInstanceRef = useRef(null) // 未登录时重定向到登录页 useEffect(() => { if (status === 'unauthenticated') { router.push('/auth/signin') } }, [status, router]) // 注册中国地图 - 从外部文件加载完整数据(必须在条件渲染之前) useEffect(() => { let isMounted = true // 动态导入 echarts 并注册地图 const loadMap = async () => { try { const echarts = await import('echarts') try { const response = await fetch('/china.json') const geoJson = await response.json() if (isMounted) { echarts.registerMap('china', geoJson) setMapLoaded(true) } } catch (fetchError) { console.error('Failed to load China map JSON, using fallback:', fetchError) // 如果加载失败,使用简化版本 if (isMounted) { echarts.registerMap('china', chinaGeoJSON as any) setMapLoaded(true) } } } catch (importError) { console.error('Failed to import echarts:', importError) } } loadMap() return () => { isMounted = false } }, []) // -- Timeline Logic -- (必须在条件渲染之前) // Flatten events: Births and Deaths const events = useMemo(() => members .flatMap((m) => { const evs = [] if (m.birthDate) { evs.push({ date: m.birthDate, year: Number.parseInt(m.birthDate.substring(0, 4)), type: "birth", member: m, }) } if (m.deathDate) { evs.push({ date: m.deathDate, year: Number.parseInt(m.deathDate.substring(0, 4)), type: "death", member: m, }) } return evs }) .sort((a, b) => b.date.localeCompare(a.date)), [members]) // 倒序排列:最新的在上面 // -- Migration Logic -- // Find moves between generations (Father -> Child location change) // 也考虑同一个人的出生地和现居地变化 const migrations = useMemo(() => { const result: Array<{ year: number | null from: string to: string person: typeof members[0] father: typeof members[0] }> = [] // 1. 父子之间的迁徙(出生地变化) members.forEach((m) => { if (m.fatherId) { const father = getMember(m.fatherId) if (father) { // 优先使用 birthPlace(出生地),其次 ancestralHome(祖籍) const loc1 = father.birthPlace || father.ancestralHome const loc2 = m.birthPlace || m.ancestralHome if (loc1 && loc2 && loc1 !== loc2) { result.push({ year: m.birthDate ? Number.parseInt(m.birthDate.substring(0, 4)) : null, from: loc1, to: loc2, person: m, father: father, }) } } } // 2. 同一个人的出生地到现居地的迁徙 const birthLoc = m.birthPlace || m.ancestralHome const currentLoc = m.address if (birthLoc && currentLoc && birthLoc !== currentLoc) { // 避免重复:检查是否已经有相同的迁徙记录 const exists = result.some(r => r.person.id === m.id && r.from === birthLoc && r.to === currentLoc ) if (!exists) { result.push({ year: m.birthDate ? Number.parseInt(m.birthDate.substring(0, 4)) + 20 : null, // 假设20岁左右迁徙 from: birthLoc, to: currentLoc, person: m, father: m, // 自己迁徙,father 指向自己 }) } } }) return result.sort((a, b) => (a.year || 0) - (b.year || 0)) }, [members, getMember]) // ECharts 地图配置 const mapOption = useMemo(() => { // 城市坐标映射(简化城市名) // 城市名称映射(支持省+市格式) const cityMap: Record = {} // 自动生成映射:将"XX省XX市"或"XX市"映射到城市名 Object.keys(geoCoordMap).forEach(city => { cityMap[city] = city // 直接城市名 cityMap[`${city}市`] = city // 城市+市 // 根据城市所在省份添加映射 const provinceMap: Record = { '广东省': ['广州', '深圳', '东莞', '佛山', '珠海', '惠州', '中山', '江门', '汕头', '肇庆'], '浙江省': ['杭州', '宁波', '温州', '金华', '嘉兴', '台州', '绍兴', '湖州'], '江苏省': ['苏州', '南京', '无锡', '常州', '南通', '盐城', '泰州', '扬州', '徐州'], '山东省': ['济南', '青岛', '威海', '临沂', '济宁', '淄博', '莱芜', '菏泽'], '四川省': ['成都', '绵阳'], '河北省': ['石家庄', '保定', '廊坊', '邯郸', '秦皇岛'], '福建省': ['福州', '厦门', '泉州', '莆田', '漳州'], '湖北省': ['武汉', '宜昌'], '湖南省': ['长沙', '衡阳', '株洲'], '陕西省': ['西安', '咸阳'], '河南省': ['郑州', '洛阳', '南阳', '平顶山'], '辽宁省': ['沈阳', '大连', '鞍山', '抚顺'], '安徽省': ['合肥', '芜湖'], '云南省': ['昆明'], '贵州省': ['贵阳', '遵义'], '广西壮族自治区': ['南宁', '柳州', '桂林'], '江西省': ['南昌', '九江', '赣州', '上饶'], '黑龙江省': ['哈尔滨', '大庆', '齐齐哈尔'], '吉林省': ['长春'], '山西省': ['太原'], '甘肃省': ['兰州'], '新疆维吾尔自治区': ['乌鲁木齐'], '宁夏回族自治区': ['银川'], '内蒙古自治区': ['呼和浩特', '包头'], '海南省': ['海口', '三亚'], '西藏自治区': ['拉萨'], '天津市': ['天津'], '重庆市': ['重庆'], '北京市': ['北京'], '上海市': ['上海'] } // 为每个城市添加"省+市"格式的映射 Object.entries(provinceMap).forEach(([province, cities]) => { if (cities.includes(city)) { cityMap[`${province}${city}市`] = city cityMap[`${province}${city}`] = city } }) }) // 国家/地区到默认城市的映射 const countryToCityMap: Record = { '澳大利亚': '悉尼', '澳洲': '悉尼', 'Australia': '悉尼', '美国': '纽约', 'USA': '纽约', '加拿大': '多伦多', 'Canada': '多伦多', '英国': '伦敦', 'UK': '伦敦', '法国': '巴黎', 'France': '巴黎', '德国': '柏林', 'Germany': '柏林', '日本': '东京', 'Japan': '东京', '韩国': '首尔', 'Korea': '首尔', '新加坡': '新加坡', 'Singapore': '新加坡', '马来西亚': '吉隆坡', 'Malaysia': '吉隆坡', '泰国': '曼谷', 'Thailand': '曼谷', } // 提取城市名称的辅助函数(去掉区的部分) const extractCity = (location: string): string => { if (!location) return location // 1. 直接匹配 geoCoordMap if (geoCoordMap[location]) return location // 2. 尝试从 cityMap 中查找 let city = cityMap[location] if (city && geoCoordMap[city]) return city // 3. 国家/地区映射到默认城市 const countryCity = countryToCityMap[location] if (countryCity && geoCoordMap[countryCity]) return countryCity // 4. 尝试提取省市部分(去掉区) // 例如:"山东省济南市历下区" -> "济南" // 例如:"陕西西安" -> "西安" 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 } } // 5. 尝试直接在地址中查找已知城市名 for (const knownCity of Object.keys(geoCoordMap)) { if (location.includes(knownCity)) { return knownCity } } return location } // 转换迁徙数据为 ECharts 格式(使用经纬度坐标) const lines = migrations.map(mig => { const fromCity = extractCity(mig.from) const toCity = extractCity(mig.to) const fromCoord = geoCoordMap[fromCity] const toCoord = geoCoordMap[toCity] if (!fromCoord || !toCoord) { console.log('坐标未找到:', { from: mig.from, to: mig.to, fromCity, toCity, fromCoord, toCoord }) return null } return { fromName: fromCity, toName: toCity, coords: [fromCoord, toCoord] } }).filter(Boolean) console.log('迁徙数据:', { migrations, lines }) // 统计每个城市的迁入迁出 const cityData: Record = {} migrations.forEach(mig => { const from = extractCity(mig.from) const to = extractCity(mig.to) cityData[from] = (cityData[from] || 0) + 1 cityData[to] = (cityData[to] || 0) + 1 }) // 检测是否有国际城市(经度超出中国范围或纬度为负) const hasInternational = lines.some((line: any) => { if (!line) return false const [from, to] = line.coords return from[0] < 70 || from[0] > 140 || from[1] < 0 || to[0] < 70 || to[0] > 140 || to[1] < 0 }) const scatterData = Object.entries(cityData).map(([name, value]) => { const coord = geoCoordMap[name] if (!coord) return null return { name, value: [...coord, value * 10] } }).filter(Boolean) // 如果有国际城市,使用世界地图模式(不依赖 geo map) if (hasInternational) { return { backgroundColor: 'transparent', title: { text: '家族迁徙路径', subtext: '包含国际迁徙 (滚轮缩放,拖拽移动)', left: 'center', textStyle: { color: '#333', fontSize: 16 } }, tooltip: { trigger: 'item', formatter: (params: any) => { if (params.seriesType === 'lines') { return `${params.data.fromName} → ${params.data.toName}` } return params.name } }, // 添加缩放和拖拽功能 dataZoom: [ { type: 'inside', xAxisIndex: 0, filterMode: 'none' }, { type: 'inside', yAxisIndex: 0, filterMode: 'none' } ], xAxis: { type: 'value', min: -180, max: 180, show: false }, yAxis: { type: 'value', min: -60, max: 70, show: false }, series: [ { type: 'lines', coordinateSystem: 'cartesian2d', data: lines.map((line: any) => ({ ...line, coords: line.coords })), lineStyle: { color: '#3b82f6', width: 2, opacity: 0.6, curveness: 0.3 }, effect: { show: true, period: 4, trailLength: 0.3, symbol: 'arrow', symbolSize: 10, color: '#3b82f6' }, zlevel: 1 }, { type: 'scatter', coordinateSystem: 'cartesian2d', data: scatterData.map((item: any) => ({ name: item.name, value: [item.value[0], item.value[1]] })), symbolSize: 12, label: { show: true, formatter: '{b}', position: 'right', fontSize: 11, color: '#333' }, itemStyle: { color: '#ef4444', shadowBlur: 5, shadowColor: 'rgba(0,0,0,0.3)' } } ] } } // 仅中国城市,使用中国地图 return { backgroundColor: 'transparent', title: { text: '家族迁徙路径', subtext: '滚轮缩放,拖拽移动', left: 'center', textStyle: { color: '#333', fontSize: 16 } }, tooltip: { trigger: 'item' }, geo: { map: 'china', roam: true, // 支持缩放和拖拽 label: { show: true, color: '#666', fontSize: 12 }, itemStyle: { areaColor: 'rgba(128, 128, 128, 0.15)', borderColor: 'rgba(128, 128, 128, 0.5)', borderWidth: 2 }, emphasis: { itemStyle: { areaColor: 'rgba(128, 128, 128, 0.25)' }, label: { show: true, color: '#333', fontSize: 14 } } }, series: [ { type: 'lines', coordinateSystem: 'geo', data: lines, lineStyle: { color: 'transparent', width: 0, opacity: 0 }, effect: { show: true, period: 3, trailLength: 0.2, symbol: 'arrow', symbolSize: 12, color: '#3b82f6', loop: true }, zlevel: 1 }, { type: 'scatter', coordinateSystem: 'geo', data: scatterData, symbolSize: 10, label: { show: true, formatter: '{b}', position: 'right', fontSize: 12, fontWeight: 'normal', color: '#333' }, itemStyle: { color: '#000', shadowBlur: 0, borderWidth: 0 }, emphasis: { scale: true, itemStyle: { color: '#000' }, label: { show: true, fontSize: 14, fontWeight: 'bold' } } } ] } }, [migrations]) // 使用 callback ref 来确保 DOM 元素挂载后初始化图表 const initChartRef = useCallback((node: HTMLDivElement | null) => { if (!node || !mapLoaded) { console.log('initChartRef: node或mapLoaded不存在', { node: !!node, mapLoaded }) return } console.log('initChartRef: 开始初始化图表') // 动态导入并初始化图表 import('echarts').then((echarts) => { // 再次检查节点是否存在 if (!node) return // 检查是否已有实例 let chart = echarts.getInstanceByDom(node) if (!chart) { console.log('创建新的图表实例') // 创建新实例 chart = echarts.init(node) chartInstanceRef.current = chart // 监听窗口大小变化 const handleResize = () => { chart?.resize() } window.addEventListener('resize', handleResize) // 保存清理函数到节点 ;(node as any).__resizeHandler = handleResize } else { console.log('使用已有的图表实例') } // 设置选项 console.log('设置图表选项') chart.setOption(mapOption, { notMerge: true }) console.log('图表选项设置完成') }).catch(error => { console.error('Failed to initialize chart:', error) }) }, [mapLoaded, mapOption]) // 未登录时不渲染内容(已在 useEffect 中重定向) if (status === 'loading' || !session) { return null } // 显示加载状态 if (isLoading) { return (

加载中...

) } // 显示无数据状态 if (members.length === 0) { return (

时光未启

) } return (

家族历史

纵览家族发展的时间脉络与迁徙足迹。

时间轴 迁徙图
{events.map((event, idx) => (
{/* Dot */}
{/* Year Label (Mobile vs Desktop) */}
{event.year}
{/* Content Card */}
{event.type === "birth" ? "诞生" : "逝世"} {event.member.gender === "MALE" ? "男" : "女"} 第{event.member.generation}世 {/* 父母信息 */} {event.member.fatherId && ( 父: )} {event.member.motherId && ( 母: )} {/* 配偶信息 */} {event.member.spouseIds && event.member.spouseIds.length > 0 && ( 配偶: )} {/* 子女信息 */} {event.member.childrenIds && event.member.childrenIds.length > 0 && ( 子女:{event.member.childrenIds.length}人 )} {/* 出生地信息 */} {event.type === "birth" && (event.member.birthPlace || event.member.ancestralHome) && ( 📍{event.member.birthPlace || event.member.ancestralHome} )} {/* 享年 */} {event.type === "death" && event.member.birthDate && ( 享年{event.year - Number.parseInt(event.member.birthDate.substring(0, 4))}岁 )} {/* 葬地 */} {event.type === "death" && event.member.burialPlace && ( ⚰️{event.member.burialPlace} )} {event.date}
))} {events.length === 0 && (

时光留白

)}
迁徙记录
{migrations.map((mig, idx) => { const isSelfMigration = mig.father.id === mig.person.id return (
{mig.year ? `${mig.year}年` : "未知年代"} 第 {mig.person.generation} 世 {isSelfMigration && ( 个人迁徙 )}
{isSelfMigration ? '出生地' : '迁出地'}
{mig.from}
{!isSelfMigration && (
)}
{isSelfMigration ? '现居地' : '迁入地'}
{mig.to}
)})} {migrations.length === 0 && (

足迹未显

)}
迁徙路径图
{!mapLoaded ? (

加载地图中...

) : (
)}
) }