Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
119 KiB
JSON
1 line
119 KiB
JSON
{"repo": "bilybaofu/baby-growth-curve", "files": {"baby-tracker.js": {"5b07ca027b872c8c79ccdefa063fb954b92be2ac": {"t": 1786068268.397868, "n": 112807, "h": "d90ab3df4667a9cc", "c": "// 婴幼儿体检报告智能识别系统 - 百度OCR版本\n(function() {\n 'use strict';\n \n class BabyHealthTracker {\n constructor() {\n this.apiKey = localStorage.getItem('deepseek_api_key') || '';\n this.baiduOcrConfig = {\n apiKey: localStorage.getItem('baidu_ocr_api_key') || '',\n secretKey: localStorage.getItem('baidu_ocr_secret_key') || '',\n accessToken: localStorage.getItem('baidu_ocr_access_token') || '',\n tokenExpireTime: parseInt(localStorage.getItem('baidu_ocr_token_expire') || '0')\n };\n this.checkRecords = JSON.parse(localStorage.getItem('baby_check_records') || '[]');\n this.babyInfo = JSON.parse(localStorage.getItem('baby_info') || '{}');\n this.isUploading = false;\n this.lastOCRResult = null;\n this.whoData = this.initCompleteWHOData();\n this.growthChart = null;\n this.tempExtractedData = null;\n \n this.init();\n }\n\n initCompleteWHOData() {\n return {\n maleHeight: {\n 0: [44.2, 46.1, 48.0, 49.9, 51.8, 53.7, 55.6],\n 1: [48.9, 50.8, 52.8, 54.7, 56.7, 58.6, 60.6],\n 2: [52.4, 54.4, 56.4, 58.4, 60.4, 62.4, 64.4],\n 3: [55.3, 57.3, 59.4, 61.4, 63.5, 65.5, 67.6],\n 4: [57.6, 59.7, 61.8, 63.9, 66.0, 68.0, 70.1],\n 5: [59.6, 61.7, 63.8, 65.9, 68.0, 70.1, 72.2],\n 6: [61.2, 63.3, 65.5, 67.6, 69.8, 71.9, 74.0],\n 7: [62.7, 64.8, 67.0, 69.2, 71.3, 73.5, 75.7],\n 8: [64.0, 66.2, 68.4, 70.6, 72.8, 75.0, 77.2],\n 9: [65.2, 67.5, 69.7, 72.0, 74.2, 76.5, 78.7],\n 10: [66.4, 68.7, 71.0, 73.3, 75.6, 77.9, 80.1],\n 11: [67.6, 69.9, 72.2, 74.5, 76.9, 79.2, 81.5],\n 12: [68.6, 71.0, 73.4, 75.7, 78.1, 80.5, 82.9],\n 13: [69.6, 72.1, 74.5, 76.9, 79.3, 81.8, 84.2],\n 14: [70.6, 73.1, 75.6, 78.0, 80.5, 83.0, 85.5],\n 15: [71.6, 74.1, 76.6, 79.1, 81.7, 84.2, 86.7],\n 16: [72.5, 75.0, 77.6, 80.2, 82.8, 85.4, 88.0],\n 17: [73.3, 76.0, 78.6, 81.2, 83.9, 86.5, 89.2],\n 18: [74.2, 76.9, 79.6, 82.3, 85.0, 87.7, 90.4],\n 19: [75.0, 77.7, 80.5, 83.2, 86.0, 88.8, 91.5],\n 20: [75.8, 78.6, 81.4, 84.2, 87.0, 89.8, 92.6],\n 21: [76.5, 79.4, 82.3, 85.1, 88.0, 90.9, 93.8],\n 22: [77.2, 80.2, 83.1, 86.0, 89.0, 91.9, 94.9],\n 23: [78.0, 81.0, 83.9, 86.9, 89.9, 92.9, 95.9],\n 24: [78.7, 81.7, 84.8, 87.8, 90.9, 93.9, 97.0],\n 25: [78.6, 81.7, 84.9, 88.0, 91.1, 94.2, 97.3],\n 26: [79.3, 82.5, 85.6, 88.8, 92.0, 95.2, 98.3],\n 27: [79.9, 83.1, 86.4, 89.6, 92.9, 96.1, 99.3],\n 28: [80.5, 83.8, 87.1, 90.4, 93.7, 97.0, 100.3],\n 29: [81.1, 84.5, 87.8, 91.2, 94.5, 97.9, 101.2],\n 30: [81.7, 85.1, 88.5, 91.9, 95.3, 98.7, 102.1],\n 31: [82.3, 85.7, 89.2, 92.7, 96.1, 99.6, 103.0],\n 32: [82.8, 86.4, 89.9, 93.4, 96.9, 100.4, 103.9],\n 33: [83.4, 86.9, 90.5, 94.1, 97.6, 101.2, 104.8],\n 34: [83.9, 87.5, 91.1, 94.8, 98.4, 102.0, 105.6],\n 35: [84.4, 88.1, 91.8, 95.4, 99.1, 102.7, 106.4],\n 36: [85.0, 88.7, 92.4, 96.1, 99.8, 103.5, 107.2]\n },\n femaleHeight: {\n 0: [43.6, 45.4, 47.3, 49.1, 51.0, 52.9, 54.7],\n 1: [47.8, 49.8, 51.7, 53.7, 55.6, 57.6, 59.5],\n 2: [51.0, 53.0, 55.0, 57.1, 59.1, 61.1, 63.2],\n 3: [53.5, 55.6, 57.7, 59.8, 61.9, 64.0, 66.1],\n 4: [55.6, 57.8, 59.9, 62.1, 64.3, 66.4, 68.6],\n 5: [57.4, 59.6, 61.8, 64.0, 66.2, 68.5, 70.7],\n 6: [58.9, 61.2, 63.5, 65.7, 68.0, 70.3, 72.5],\n 7: [60.3, 62.7, 65.0, 67.3, 69.6, 71.9, 74.2],\n 8: [61.7, 64.0, 66.4, 68.7, 71.1, 73.5, 75.8],\n 9: [62.9, 65.3, 67.7, 70.1, 72.6, 75.0, 77.4],\n 10: [64.1, 66.5, 69.0, 71.5, 73.9, 76.4, 78.9],\n 11: [65.2, 67.7, 70.3, 72.8, 75.3, 77.8, 80.3],\n 12: [66.3, 68.9, 71.4, 74.0, 76.6, 79.2, 81.7],\n 13: [67.3, 70.0, 72.6, 75.2, 77.8, 80.5, 83.1],\n 14: [68.3, 71.0, 73.7, 76.4, 79.1, 81.7, 84.4],\n 15: [69.3, 72.0, 74.8, 77.5, 80.2, 83.0, 85.7],\n 16: [70.2, 73.0, 75.8, 78.6, 81.4, 84.2, 87.0],\n 17: [71.1, 74.0, 76.8, 79.7, 82.5, 85.4, 88.2],\n 18: [72.0, 74.9, 77.8, 80.7, 83.6, 86.5, 89.4],\n 19: [72.8, 75.8, 78.8, 81.7, 84.7, 87.6, 90.6],\n 20: [73.7, 76.7, 79.7, 82.7, 85.7, 88.7, 91.7],\n 21: [74.5, 77.5, 80.6, 83.7, 86.7, 89.8, 92.9],\n 22: [75.2, 78.4, 81.5, 84.6, 87.7, 90.8, 94.0],\n 23: [76.0, 79.2, 82.3, 85.5, 88.7, 91.9, 95.0],\n 24: [76.7, 80.0, 83.2, 86.4, 89.6, 92.9, 96.1],\n 25: [76.8, 80.0, 83.3, 86.6, 89.9, 93.1, 96.4],\n 26: [77.5, 80.8, 84.1, 87.4, 90.8, 94.1, 97.4],\n 27: [78.1, 81.5, 84.9, 88.3, 91.7, 95.0, 98.4],\n 28: [78.8, 82.2, 85.7, 89.1, 92.5, 96.0, 99.4],\n 29: [79.5, 82.9, 86.4, 89.9, 93.4, 96.9, 100.3],\n 30: [80.1, 83.6, 87.1, 90.7, 94.2, 97.7, 101.3],\n 31: [80.7, 84.3, 87.9, 91.4, 95.0, 98.6, 102.2],\n 32: [81.3, 84.9, 88.6, 92.2, 95.8, 99.4, 103.1],\n 33: [81.9, 85.6, 89.3, 92.9, 96.6, 100.3, 103.9],\n 34: [82.5, 86.2, 89.9, 93.6, 97.4, 101.1, 104.8],\n 35: [83.1, 86.8, 90.6, 94.4, 98.1, 101.9, 105.6],\n 36: [83.6, 87.4, 91.2, 95.1, 98.9, 102.7, 106.5]\n },\n maleWeight: {\n 0: [2.1, 2.5, 2.9, 3.3, 3.9, 4.4, 5.0],\n 1: [2.9, 3.4, 3.9, 4.5, 5.1, 5.8, 6.6],\n 2: [3.8, 4.3, 4.9, 5.6, 6.3, 7.1, 8.0],\n 3: [4.4, 5.0, 5.7, 6.4, 7.2, 8.0, 9.0],\n 4: [4.9, 5.6, 6.2, 7.0, 7.8, 8.7, 9.7],\n 5: [5.3, 6.0, 6.7, 7.5, 8.4, 9.3, 10.4],\n 6: [5.7, 6.4, 7.1, 7.9, 8.8, 9.8, 10.9],\n 7: [5.9, 6.7, 7.4, 8.3, 9.2, 10.3, 11.4],\n 8: [6.2, 6.9, 7.7, 8.6, 9.6, 10.7, 11.9],\n 9: [6.4, 7.1, 8.0, 8.9, 9.9, 11.0, 12.3],\n 10: [6.6, 7.4, 8.2, 9.2, 10.2, 11.4, 12.7],\n 11: [6.8, 7.6, 8.4, 9.4, 10.5, 11.7, 13.0],\n 12: [6.9, 7.7, 8.6, 9.6, 10.8, 12.0, 13.3],\n 13: [7.1, 7.9, 8.8, 9.9, 11.0, 12.3, 13.7],\n 14: [7.2, 8.1, 9.0, 10.1, 11.3, 12.6, 14.0],\n 15: [7.4, 8.3, 9.2, 10.3, 11.5, 12.8, 14.3],\n 16: [7.5, 8.4, 9.4, 10.5, 11.7, 13.1, 14.6],\n 17: [7.7, 8.6, 9.6, 10.7, 12.0, 13.4, 14.9],\n 18: [7.8, 8.8, 9.8, 10.9, 12.2, 13.7, 15.3],\n 19: [8.0, 8.9, 10.0, 11.1, 12.5, 13.9, 15.6],\n 20: [8.1, 9.1, 10.1, 11.3, 12.7, 14.2, 15.9],\n 21: [8.2, 9.2, 10.3, 11.5, 12.9, 14.5, 16.2],\n 22: [8.4, 9.4, 10.5, 11.8, 13.2, 14.7, 16.5],\n 23: [8.5, 9.5, 10.7, 12.0, 13.4, 15.0, 16.8],\n 24: [8.6, 9.7, 10.8, 12.2, 13.6, 15.3, 17.1],\n 25: [8.8, 9.8, 11.0, 12.4, 13.9, 15.5, 17.5],\n 26: [8.9, 10.0, 11.2, 12.5, 14.1, 15.8, 17.8],\n 27: [9.0, 10.1, 11.3, 12.7, 14.3, 16.1, 18.1],\n 28: [9.1, 10.2, 11.5, 12.9, 14.5, 16.3, 18.4],\n 29: [9.2, 10.4, 11.7, 13.1, 14.8, 16.6, 18.7],\n 30: [9.4, 10.5, 11.8, 13.3, 15.0, 16.9, 19.0],\n 31: [9.5, 10.7, 12.0, 13.5, 15.2, 17.1, 19.3],\n 32: [9.6, 10.8, 12.1, 13.7, 15.4, 17.4, 19.6],\n 33: [9.7, 10.9, 12.3, 13.8, 15.6, 17.6, 19.9],\n 34: [9.8, 11.0, 12.4, 14.0, 15.8, 17.8, 20.2],\n 35: [9.9, 11.2, 12.6, 14.2, 16.0, 18.1, 20.4],\n 36: [10.0, 11.3, 12.7, 14.3, 16.2, 18.3, 20.7]\n },\n femaleWeight: {\n 0: [2.0, 2.4, 2.8, 3.2, 3.7, 4.2, 4.8],\n 1: [2.7, 3.2, 3.6, 4.2, 4.8, 5.5, 6.2],\n 2: [3.4, 3.9, 4.5, 5.1, 5.8, 6.6, 7.5],\n 3: [4.0, 4.5, 5.2, 5.8, 6.6, 7.5, 8.5],\n 4: [4.4, 5.0, 5.7, 6.4, 7.3, 8.2, 9.3],\n 5: [4.8, 5.4, 6.1, 6.9, 7.8, 8.8, 10.0],\n 6: [5.1, 5.7, 6.5, 7.3, 8.2, 9.3, 10.6],\n 7: [5.3, 6.0, 6.8, 7.6, 8.6, 9.8, 11.1],\n 8: [5.6, 6.3, 7.0, 7.9, 9.0, 10.2, 11.6],\n 9: [5.8, 6.5, 7.3, 8.2, 9.3, 10.5, 12.0],\n 10: [5.9, 6.7, 7.5, 8.5, 9.6, 10.9, 12.4],\n 11: [6.1, 6.9, 7.7, 8.7, 9.9, 11.2, 12.8],\n 12: [6.3, 7.0, 7.9, 8.9, 10.1, 11.5, 13.1],\n 13: [6.4, 7.2, 8.1, 9.2, 10.4, 11.8, 13.5],\n 14: [6.6, 7.4, 8.3, 9.4, 10.6, 12.1, 13.8],\n 15: [6.7, 7.6, 8.5, 9.6, 10.9, 12.4, 14.1],\n 16: [6.9, 7.7, 8.7, 9.8, 11.1, 12.6, 14.5],\n 17: [7.0, 7.9, 8.9, 10.0, 11.4, 12.9, 14.8],\n 18: [7.2, 8.1, 9.1, 10.2, 11.6, 13.2, 15.1],\n 19: [7.3, 8.2, 9.2, 10.4, 11.8, 13.5, 15.4],\n 20: [7.5, 8.4, 9.4, 10.6, 12.1, 13.7, 15.7],\n 21: [7.6, 8.6, 9.6, 10.9, 12.3, 14.0, 16.0],\n 22: [7.8, 8.7, 9.8, 11.1, 12.5, 14.3, 16.4],\n 23: [7.9, 8.9, 10.0, 11.3, 12.8, 14.6, 16.7],\n 24: [8.1, 9.0, 10.2, 11.5, 13.0, 14.8, 17.0],\n 25: [8.2, 9.2, 10.3, 11.7, 13.3, 15.1, 17.3],\n 26: [8.4, 9.4, 10.5, 11.9, 13.5, 15.4, 17.7],\n 27: [8.5, 9.5, 10.7, 12.1, 13.7, 15.7, 18.0],\n 28: [8.6, 9.7, 10.9, 12.3, 14.0, 16.0, 18.3],\n 29: [8.8, 9.8, 11.1, 12.5, 14.2, 16.2, 18.7],\n 30: [8.9, 10.0, 11.2, 12.7, 14.4, 16.5, 19.0],\n 31: [9.0, 10.1, 11.4, 12.9, 14.7, 16.8, 19.3],\n 32: [9.1, 10.3, 11.6, 13.1, 14.9, 17.1, 19.6],\n 33: [9.3, 10.4, 11.7, 13.3, 15.1, 17.3, 20.0],\n 34: [9.4, 10.5, 11.9, 13.5, 15.4, 17.6, 20.3],\n 35: [9.5, 10.7, 12.0, 13.7, 15.6, 17.9, 20.6],\n 36: [9.6, 10.8, 12.2, 13.9, 15.8, 18.1, 20.9]\n },\n maleHeadCircumference: {\n 0: [30.4, 31.7, 33.0, 34.3, 35.6, 36.9, 38.3],\n 1: [33.4, 34.6, 35.8, 37.0, 38.2, 39.4, 40.6],\n 2: [35.7, 36.8, 37.9, 39.1, 40.2, 41.4, 42.6],\n 3: [37.1, 38.2, 39.3, 40.5, 41.6, 42.8, 44.1],\n 4: [38.1, 39.3, 40.4, 41.6, 42.8, 44.0, 45.3],\n 5: [39.0, 40.2, 41.3, 42.5, 43.8, 45.0, 46.3],\n 6: [39.8, 41.0, 42.1, 43.4, 44.6, 45.9, 47.2],\n 7: [40.5, 41.7, 42.8, 44.0, 45.3, 46.6, 47.9],\n 8: [41.1, 42.2, 43.4, 44.6, 45.9, 47.2, 48.5],\n 9: [41.5, 42.7, 43.9, 45.1, 46.4, 47.7, 49.0],\n 10: [41.9, 43.1, 44.3, 45.5, 46.8, 48.1, 49.4],\n 11: [42.3, 43.4, 44.6, 45.8, 47.1, 48.4, 49.8],\n 12: [42.5, 43.7, 44.9, 46.1, 47.4, 48.7, 50.1],\n 13: [42.8, 44.0, 45.1, 46.4, 47.7, 49.0, 50.3],\n 14: [43.0, 44.2, 45.4, 46.6, 47.9, 49.2, 50.6],\n 15: [43.2, 44.4, 45.6, 46.8, 48.1, 49.4, 50.8],\n 16: [43.4, 44.6, 45.8, 47.0, 48.3, 49.6, 51.0],\n 17: [43.6, 44.7, 45.9, 47.2, 48.5, 49.8, 51.2],\n 18: [43.8, 44.9, 46.1, 47.4, 48.7, 50.0, 51.4],\n 19: [43.9, 45.1, 46.3, 47.5, 48.8, 50.2, 51.6],\n 20: [44.1, 45.3, 46.5, 47.7, 49.0, 50.4, 51.7],\n 21: [44.3, 45.4, 46.6, 47.9, 49.2, 50.5, 51.9],\n 22: [44.4, 45.6, 46.8, 48.1, 49.4, 50.7, 52.1],\n 23: [44.6, 45.7, 47.0, 48.2, 49.5, 50.9, 52.3],\n 24: [44.7, 45.9, 47.1, 48.3, 49.6, 51.0, 52.4],\n 25: [44.8, 46.0, 47.2, 48.4, 49.7, 51.1, 52.5],\n 26: [44.9, 46.1, 47.3, 48.5, 49.8, 51.2, 52.6],\n 27: [45.0, 46.2, 47.4, 48.7, 50.0, 51.3, 52.7],\n 28: [45.1, 46.3, 47.5, 48.8, 50.1, 51.4, 52.8],\n 29: [45.2, 46.3, 47.6, 48.8, 50.2, 51.5, 52.9],\n 30: [45.3, 46.4, 47.7, 48.9, 50.3, 51.6, 53.0],\n 31: [45.4, 46.5, 47.8, 49.0, 50.4, 51.7, 53.1],\n 32: [45.4, 46.6, 47.8, 49.1, 50.4, 51.8, 53.2],\n 33: [45.5, 46.7, 47.9, 49.2, 50.5, 51.9, 53.3],\n 34: [45.6, 46.7, 48.0, 49.2, 50.6, 52.0, 53.4],\n 35: [45.6, 46.8, 48.0, 49.3, 50.6, 52.0, 53.4],\n 36: [45.7, 46.8, 48.1, 49.3, 50.7, 52.1, 53.5]\n },\n femaleHeadCircumference: {\n 0: [30.1, 31.4, 32.7, 33.9, 35.2, 36.5, 37.7],\n 1: [32.9, 34.0, 35.2, 36.3, 37.5, 38.6, 39.8],\n 2: [34.9, 36.0, 37.1, 38.2, 39.3, 40.4, 41.6],\n 3: [36.2, 37.3, 38.4, 39.5, 40.7, 41.8, 42.9],\n 4: [37.2, 38.3, 39.4, 40.6, 41.7, 42.9, 44.1],\n 5: [38.0, 39.2, 40.3, 41.5, 42.6, 43.8, 45.0],\n 6: [38.8, 39.9, 41.1, 42.2, 43.4, 44.6, 45.9],\n 7: [39.4, 40.6, 41.7, 42.9, 44.1, 45.3, 46.6],\n 8: [40.0, 41.1, 42.3, 43.5, 44.7, 45.9, 47.2],\n 9: [40.4, 41.6, 42.8, 44.0, 45.2, 46.5, 47.7],\n 10: [40.8, 42.0, 43.2, 44.4, 45.6, 46.9, 48.2],\n 11: [41.2, 42.4, 43.6, 44.8, 46.0, 47.3, 48.6],\n 12: [41.5, 42.7, 43.9, 45.1, 46.4, 47.6, 48.9],\n 13: [41.8, 42.9, 44.2, 45.4, 46.6, 47.9, 49.2],\n 14: [42.0, 43.2, 44.4, 45.6, 46.9, 48.2, 49.5],\n 15: [42.2, 43.4, 44.6, 45.9, 47.1, 48.4, 49.7],\n 16: [42.4, 43.6, 44.8, 46.1, 47.3, 48.6, 50.0],\n 17: [42.6, 43.8, 45.0, 46.2, 47.5, 48.8, 50.1],\n 18: [42.7, 43.9, 45.2, 46.4, 47.7, 49.0, 50.3],\n 19: [42.9, 44.1, 45.3, 46.6, 47.9, 49.2, 50.5],\n 20: [43.1, 44.3, 45.5, 46.7, 48.0, 49.3, 50.7],\n 21: [43.2, 44.4, 45.6, 46.9, 48.2, 49.5, 50.9],\n 22: [43.4, 44.6, 45.8, 47.1, 48.4, 49.7, 51.0],\n 23: [43.5, 44.7, 45.9, 47.2, 48.5, 49.8, 51.2],\n 24: [43.6, 44.8, 46.1, 47.3, 48.6, 50.0, 51.3],\n 25: [43.7, 44.9, 46.2, 47.4, 48.8, 50.1, 51.5],\n 26: [43.8, 45.0, 46.3, 47.5, 48.9, 50.2, 51.6],\n 27: [43.9, 45.1, 46.4, 47.6, 49.0, 50.3, 51.7],\n 28: [44.0, 45.2, 46.5, 47.7, 49.1, 50.4, 51.8],\n 29: [44.1, 45.3, 46.6, 47.8, 49.2, 50.5, 51.9],\n 30: [44.2, 45.4, 46.7, 47.9, 49.3, 50.6, 52.0],\n 31: [44.3, 45.5, 46.8, 48.0, 49.4, 50.7, 52.1],\n 32: [44.3, 45.6, 46.8, 48.1, 49.5, 50.8, 52.2],\n 33: [44.4, 45.7, 46.9, 48.2, 49.6, 50.9, 52.3],\n 34: [44.5, 45.8, 47.0, 48.3, 49.7, 51.0, 52.4],\n 35: [44.6, 45.8, 47.1, 48.4, 49.8, 51.1, 52.5],\n 36: [44.7, 45.9, 47.2, 48.5, 49.9, 51.2, 52.7]\n }\n };\n }\n\n init() {\n this.bindEvents();\n this.loadSavedData();\n this.updateUI();\n this.initializeChart();\n }\n\n validateBasicInfo() {\n const name = this.babyInfo.name ? this.babyInfo.name.trim() : '';\n const gender = this.babyInfo.gender;\n const birthDate = this.babyInfo.birthDate;\n \n console.log('验证基本信息:', { name, gender, birthDate });\n \n if (!name || !gender || !birthDate) {\n this.showMessage('🍼 请先完整填写宝宝的基本信息(姓名、性别、出生日期)', 'error');\n this.switchToBasicInfoTab();\n return false;\n }\n return true;\n }\n\n switchToBasicInfoTab() {\n const basicInfoTab = document.querySelector('[data-tab=\"basic-info\"]');\n const basicInfoContent = document.getElementById('basic-info');\n \n if (basicInfoTab && basicInfoContent) {\n document.querySelectorAll('.tab-button').forEach(tab => tab.classList.remove('active'));\n document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));\n \n basicInfoTab.classList.add('active');\n basicInfoContent.classList.add('active');\n }\n }\n\n bindEvents() {\n const self = this;\n \n // API密钥设置\n const apiKeyInput = document.getElementById('apiKeyInput');\n if (apiKeyInput) {\n apiKeyInput.addEventListener('change', function(e) {\n self.apiKey = e.target.value.trim();\n self.updateApiStatus();\n });\n }\n\n // API密钥保存按钮\n const saveApiKeyBtn = document.getElementById('saveApiKeyBtn');\n if (saveApiKeyBtn) {\n saveApiKeyBtn.addEventListener('click', function() {\n self.saveApiKey();\n });\n }\n\n // 百度OCR配置保存\n const saveBaiduOcrBtn = document.getElementById('saveBaiduOcrBtn');\n if (saveBaiduOcrBtn) {\n saveBaiduOcrBtn.addEventListener('click', function() {\n self.saveBaiduOcrConfig();\n });\n }\n const testBaiduOcrBtn = document.getElementById('testBaiduOcrBtn');\n if (testBaiduOcrBtn) {\n testBaiduOcrBtn.addEventListener('click', function() {\n console.log('🔧 用户点击了测试连接按钮');\n self.testBaiduOcrConnection();\n });\n }\n\n // 图片上传\n const reportUpload = document.getElementById('reportUpload');\n if (reportUpload) {\n reportUpload.addEventListener('change', function(e) {\n self.handleImageUpload(e.target.files[0]);\n });\n }\n\n // 手动添加记录\n const addRecordBtn = document.getElementById('addRecordBtn');\n if (addRecordBtn) {\n addRecordBtn.addEventListener('click', function() {\n self.addManualRecord();\n });\n }\n\n // 一键清除记录\n const clearAllRecordsBtn = document.getElementById('clearAllRecordsBtn');\n if (clearAllRecordsBtn) {\n clearAllRecordsBtn.addEventListener('click', function() {\n self.clearAllRecords();\n });\n }\n\n // 导出数据\n const exportBtn = document.getElementById('exportBtn');\n if (exportBtn) {\n exportBtn.addEventListener('click', function() {\n self.exportData();\n });\n }\n\n // 导入数据\n const importBtn = document.getElementById('importBtn');\n if (importBtn) {\n importBtn.addEventListener('click', function() {\n self.importData();\n });\n }\n\n // 清空表单\n const clearFormBtn = document.getElementById('clearFormBtn');\n if (clearFormBtn) {\n clearFormBtn.addEventListener('click', function() {\n self.clearForm();\n });\n }\n\n // 拖拽上传\n const uploadArea = document.querySelector('.upload-area');\n if (uploadArea) {\n uploadArea.addEventListener('dragover', function(e) {\n e.preventDefault();\n uploadArea.classList.add('dragover');\n });\n\n uploadArea.addEventListener('dragleave', function() {\n uploadArea.classList.remove('dragover');\n });\n\n uploadArea.addEventListener('drop', function(e) {\n e.preventDefault();\n uploadArea.classList.remove('dragover');\n const files = e.dataTransfer.files;\n if (files.length > 0) {\n self.handleImageUpload(files[0]);\n }\n });\n }\n\n // 实时计算百分位\n const inputs = ['ageMonths', 'height', 'weight', 'headCircumference'];\n inputs.forEach(function(id) {\n const input = document.getElementById(id);\n if (input) {\n input.addEventListener('input', function() {\n self.updatePercentiles();\n });\n }\n });\n\n // 保存宝宝基本信息\n const babyInfoInputs = ['babyName', 'babyGender', 'babyBirthDate'];\n babyInfoInputs.forEach(function(id) {\n const input = document.getElementById(id);\n if (input) {\n input.addEventListener('change', function() {\n self.saveBabyInfo();\n self.updateCurrentStatus();\n });\n }\n });\n\n // 体检日期变化时自动计算月龄\n const checkDateInput = document.getElementById('checkDate');\n if (checkDateInput) {\n checkDateInput.addEventListener('change', function() {\n self.autoCalculateAge();\n });\n }\n\n // 生成AI喂养建议按钮\n const generateFeedingBtn = document.getElementById('generateFeedingBtn');\n if (generateFeedingBtn) {\n generateFeedingBtn.addEventListener('click', function() {\n self.generateAIFeedingGuide();\n });\n }\n }\n\n autoCalculateAge() {\n const birthDate = this.babyInfo.birthDate;\n const checkDate = document.getElementById('checkDate') ? document.getElementById('checkDate').value : '';\n \n console.log('计算月龄 - 出生日期:', birthDate, '体检日期:', checkDate);\n \n if (birthDate && checkDate) {\n const birth = new Date(birthDate);\n const check = new Date(checkDate);\n \n if (check >= birth) {\n const years = check.getFullYear() - birth.getFullYear();\n const months = check.getMonth() - birth.getMonth();\n const days = check.getDate() - birth.getDate();\n \n let totalMonths = years * 12 + months;\n if (days < 0) {\n totalMonths -= 1;\n }\n \n const exactMonths = totalMonths + (days >= 0 ? days / 30 : (30 + days) / 30);\n const roundedMonths = Math.round(exactMonths * 10) / 10;\n \n const ageInput = document.getElementById('ageMonths');\n if (ageInput) {\n ageInput.value = roundedMonths;\n this.updatePercentiles();\n console.log('自动计算月龄:', roundedMonths);\n }\n }\n } else if (!birthDate) {\n this.showMessage('🍼 请先在基本信息中填写出生日期', 'warning');\n }\n }\n\n saveApiKey() {\n const apiKeyInput = document.getElementById('apiKeyInput');\n if (apiKeyInput) {\n this.apiKey = apiKeyInput.value.trim();\n if (this.apiKey) {\n localStorage.setItem('deepseek_api_key', this.apiKey);\n this.updateApiStatus();\n this.showMessage('🔑 API密钥保存成功', 'success');\n } else {\n this.showMessage('❌ 请输入有效的API密钥', 'error');\n }\n }\n }\n\n saveBaiduOcrConfig() {\n const apiKeyInput = document.getElementById('baiduOcrApiKey');\n const secretKeyInput = document.getElementById('baiduOcrSecretKey');\n \n if (apiKeyInput && secretKeyInput) {\n const apiKey = apiKeyInput.value.trim();\n const secretKey = secretKeyInput.value.trim();\n \n if (apiKey && secretKey) {\n this.baiduOcrConfig.apiKey = apiKey;\n this.baiduOcrConfig.secretKey = secretKey;\n \n localStorage.setItem('baidu_ocr_api_key', apiKey);\n localStorage.setItem('baidu_ocr_secret_key', secretKey);\n \n this.baiduOcrConfig.accessToken = '';\n this.baiduOcrConfig.tokenExpireTime = 0;\n localStorage.removeItem('baidu_ocr_access_token');\n localStorage.removeItem('baidu_ocr_token_expire');\n \n this.updateBaiduOcrStatus();\n this.showMessage('🔑 百度OCR配置保存成功', 'success');\n } else {\n this.showMessage('❌ 请输入完整的百度OCR API Key和Secret Key', 'error');\n }\n }\n }\n\n updateBaiduOcrStatus() {\n const statusIndicator = document.getElementById('baiduOcrStatus');\n const statusText = document.getElementById('baiduOcrStatusText');\n \n if (this.baiduOcrConfig.apiKey && this.baiduOcrConfig.secretKey) {\n if (statusIndicator) statusIndicator.classList.add('connected');\n if (statusText) statusText.textContent = '🤖 百度OCR已配置';\n } else {\n if (statusIndicator) statusIndicator.classList.remove('connected');\n if (statusText) statusText.textContent = '😴 未配置';\n }\n }\n // 测试百度OCR连接\nasync testBaiduOcrConnection() {\n console.log('🔧 开始测试百度OCR连接...');\n \n try {\n this.showLoadingState('🔧 正在测试连接...');\n \n // 第一步:检查配置\n if (!this.baiduOcrConfig.apiKey || !this.baiduOcrConfig.secretKey) {\n throw new Error('❌ 请先填写百度OCR的API Key和Secret Key');\n }\n console.log('✅ 配置检查通过');\n \n // 第二步:直接测试百度OCR认证(跳过代理服务器健康检查)\n this.showLoadingState('🔑 正在测试百度OCR认证...');\n console.log('🔑 测试百度OCR认证...');\n \n // 清除旧的访问令牌,强制重新获取\n this.baiduOcrConfig.accessToken = '';\n this.baiduOcrConfig.tokenExpireTime = 0;\n \n const accessToken = await this.getBaiduOcrAccessToken();\n if (!accessToken) {\n throw new Error('❌ 获取访问令牌失败');\n }\n console.log('✅ 百度OCR认证成功,令牌长度:', accessToken.length);\n \n // 第三步:测试一个简单的OCR调用\n this.showLoadingState('🧪 正在测试OCR功能...');\n console.log('🧪 测试OCR功能可用性...');\n \n // 创建一个1x1像素的测试图片\n const testImageBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';\n \n const testResponse = await fetch('https://jumpy-foil-whimsey.glitch.me/api/baidu-ocr', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n access_token: accessToken,\n image: testImageBase64,\n language_type: 'CHN_ENG'\n })\n });\n \n if (testResponse.ok) {\n console.log('✅ OCR API调用成功');\n } else {\n console.log('⚠️ OCR API调用失败,但认证成功');\n }\n \n // 测试成功\n this.hideLoadingState();\n this.showMessage('🎉 百度OCR连接测试成功!认证正常,可以进行图片识别', 'success');\n console.log('🎉 连接测试完成,认证正常!');\n \n } catch (error) {\n this.hideLoadingState();\n console.error('❌ 连接测试失败:', error);\n \n // 提供更详细的错误信息\n let errorMessage = error.message;\n if (error.message.includes('CORS')) {\n errorMessage = '网络连接问题,但这不影响图片识别功能的使用';\n }\n \n this.showMessage('❌ 连接测试失败: ' + errorMessage, 'error');\n }\n}\n\n\n\n updateApiStatus() {\n const statusIndicator = document.getElementById('apiStatus');\n const statusText = document.getElementById('apiStatusText');\n \n if (this.apiKey && this.apiKey.trim() !== '') {\n if (statusIndicator) statusIndicator.classList.add('connected');\n if (statusText) statusText.textContent = '🤖 OCR+AI模式';\n } else {\n if (statusIndicator) statusIndicator.classList.remove('connected');\n if (statusText) statusText.textContent = '😴 未连接';\n }\n }\n\n saveBabyInfo() {\n this.babyInfo = {\n name: document.getElementById('babyName') ? document.getElementById('babyName').value : '',\n gender: document.getElementById('babyGender') ? document.getElementById('babyGender').value : '',\n birthDate: document.getElementById('babyBirthDate') ? document.getElementById('babyBirthDate').value : ''\n };\n \n this.saveData();\n console.log('宝宝信息已保存:', this.babyInfo);\n }\n\n clearAllRecords() {\n if (this.checkRecords.length === 0) {\n this.showMessage('🤔 没有记录需要清除', 'info');\n return;\n }\n \n if (confirm('🗑️ 确定要清除所有 ' + this.checkRecords.length + ' 条体检记录吗?此操作不可恢复!')) {\n this.checkRecords = [];\n this.saveData();\n this.updateRecordsTable();\n this.updateStatistics();\n this.updateCurrentStatus();\n this.updateChart();\n this.showMessage('✨ 所有记录已清除', 'success');\n }\n }\n\n async handleImageUpload(file) {\n if (!file) return;\n \n if (!this.validateBasicInfo()) {\n return;\n }\n \n if (this.isUploading) {\n this.showMessage('🤖 正在处理中,请稍候...', 'info');\n return;\n }\n \n if (!file.type.startsWith('image/')) {\n this.showMessage('🖼️ 请上传图片文件 (JPG, PNG, WEBP)', 'error');\n return;\n }\n \n if (file.size > 10 * 1024 * 1024) {\n this.showMessage('📦 图片文件太大,请选择小于10MB的图片', 'error');\n return;\n }\n \n if (!this.baiduOcrConfig.apiKey || !this.baiduOcrConfig.secretKey) {\n this.showMessage('🔧 请先配置百度OCR API密钥', 'error');\n return;\n }\n \n this.isUploading = true;\n \n try {\n this.showImagePreview(file);\n this.showLoadingState('🔍 正在初始化百度OCR识别...');\n const ocrResult = await this.performBaiduOCR(file);\n this.showOCRResult(ocrResult);\n this.hideLoadingState();\n this.showMessage('✨ OCR识别完成,请查看识别结果', 'success');\n } catch (error) {\n this.hideLoadingState();\n console.error('详细错误信息:', error);\n this.showMessage('❌ 识别失败: ' + error.message, 'error');\n } finally {\n this.isUploading = false;\n this.resetUploadInput();\n }\n }\n\n async getBaiduOcrAccessToken() {\n const now = Date.now();\n if (this.baiduOcrConfig.accessToken && now < this.baiduOcrConfig.tokenExpireTime) {\n return this.baiduOcrConfig.accessToken;\n }\n \n console.log('获取新的百度OCR访问令牌...');\n \n try {\n const response = await fetch('https://jumpy-foil-whimsey.glitch.me/api/baidu-token', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n client_id: this.baiduOcrConfig.apiKey,\n client_secret: this.baiduOcrConfig.secretKey\n })\n });\n\n \n if (!response.ok) {\n throw new Error('获取访问令牌失败: ' + response.status);\n }\n \n const data = await response.json();\n \n if (data.error) {\n throw new Error('百度API错误: ' + (data.error_description || data.error));\n }\n \n this.baiduOcrConfig.accessToken = data.access_token;\n this.baiduOcrConfig.tokenExpireTime = now + (data.expires_in - 300) * 1000;\n \n localStorage.setItem('baidu_ocr_access_token', data.access_token);\n localStorage.setItem('baidu_ocr_token_expire', this.baiduOcrConfig.tokenExpireTime.toString());\n \n console.log('百度OCR访问令牌获取成功');\n return data.access_token;\n \n } catch (error) {\n console.error('获取百度OCR访问令牌失败:', error);\n throw new Error('获取访问令牌失败: ' + error.message);\n }\n }\n\n async fileToBase64(file) {\n return new Promise(function(resolve, reject) {\n const reader = new FileReader();\n reader.onload = function() {\n const base64 = reader.result.split(',')[1];\n resolve(base64);\n };\n reader.onerror = reject;\n reader.readAsDataURL(file);\n });\n }\n\n async performBaiduOCR(file) {\n try {\n console.log('开始百度OCR识别...文件大小:', file.size, '类型:', file.type);\n \n this.showLoadingState('🔑 正在获取访问令牌...');\n const accessToken = await this.getBaiduOcrAccessToken();\n \n this.showLoadingState('📷 正在处理图片...');\n const base64Image = await this.fileToBase64(file);\n \n this.showLoadingState('🔍 正在识别文字...');\n const response = await fetch('https://jumpy-foil-whimsey.glitch.me/api/baidu-ocr', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n access_token: accessToken,\n image: base64Image,\n language_type: 'CHN_ENG',\n detect_direction: 'true',\n paragraph: 'false',\n probability: 'false'\n })\n});\n\n \n if (!response.ok) {\n throw new Error('百度OCR API调用失败: ' + response.status);\n }\n \n const data = await response.json();\n \n if (data.error_code) {\n throw new Error('百度OCR错误 (' + data.error_code + '): ' + data.error_msg);\n }\n \n if (!data.words_result || data.words_result.length === 0) {\n throw new Error('未识别到任何文字,请确保图片清晰可读');\n }\n \n const recognizedText = data.words_result\n .map(function(item) { return item.words; })\n .join('\\n');\n \n console.log('百度OCR识别完成');\n console.log('识别到文字行数:', data.words_result.length);\n console.log('识别文本长度:', recognizedText.length);\n console.log('识别文本:', recognizedText);\n \n return recognizedText;\n \n } catch (error) {\n console.error('百度OCR识别失败:', error);\n throw new Error('图片文字识别失败: ' + error.message);\n }\n }\n\n showOCRResult(ocrText) {\n const self = this;\n this.lastOCRResult = ocrText;\n \n let resultDiv = document.getElementById('ocrResultDiv');\n \n if (!resultDiv) {\n resultDiv = document.createElement('div');\n resultDiv.id = 'ocrResultDiv';\n resultDiv.className = 'card cute-card';\n resultDiv.style.cssText = 'background: linear-gradient(135deg, #E5F3FF 0%, #F0FFE5 100%); border: 3px solid #4FACFE; margin: 20px 0; padding: 25px; border-radius: 20px; box-shadow: 0 8px 25px rgba(79, 172, 254, 0.3);';\n const uploadedImageDiv = document.getElementById('uploadedImage');\n if (uploadedImageDiv && uploadedImageDiv.parentNode) {\n uploadedImageDiv.parentNode.insertBefore(resultDiv, uploadedImageDiv.nextSibling);\n }\n }\n \n resultDiv.innerHTML = '<div style=\"text-align: center; margin-bottom: 20px;\"><h4 style=\"color: #4FACFE; margin-bottom: 10px; font-size: 1.4em;\">🔍 百度OCR识别结果</h4><p style=\"color: #666; font-size: 0.9em;\">✨ 文字识别完成,接下来可以进行智能解析</p></div><div style=\"background: white; padding: 20px; border-radius: 15px; border: 2px solid #B6E5FC; margin-bottom: 20px;\"><h5 style=\"color: #4FACFE; margin-bottom: 15px;\">📝 原始识别文本:</h5><pre style=\"white-space: pre-wrap; font-size: 14px; line-height: 1.4; margin: 10px 0; color: #333; background: #F8F9FA; padding: 15px; border-radius: 10px; max-height: 200px; overflow-y: auto;\">' + ocrText + '</pre></div><div style=\"background: white; padding: 20px; border-radius: 15px; border: 2px solid #B6E5FC; margin-bottom: 20px;\"><h5 style=\"color: #4FACFE; margin-bottom: 15px;\">📊 文本统计:</h5><div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; font-size: 0.9em;\"><div style=\"background: #F0F8FF; padding: 10px; border-radius: 8px; text-align: center;\"><strong>📏 字符总数:</strong><br>' + ocrText.length + '</div><div style=\"background: #F0FFF0; padding: 10px; border-radius: 8px; text-align: center;\"><strong>📄 行数:</strong><br>' + ocrText.split('\\n').length + '</div><div style=\"background: #FFF8DC; padding: 10px; border-radius: 8px; text-align: center;\"><strong>🔢 包含数字:</strong><br>' + (/\\d/.test(ocrText) ? '✅ 是' : '❌ 否') + '</div><div style=\"background: #FFE4E1; padding: 10px; border-radius: 8px; text-align: center;\"><strong>🇨🇳 包含中文:</strong><br>' + (/[\\u4e00-\\u9fff]/.test(ocrText) ? '✅ 是' : '❌ 否') + '</div></div></div><div style=\"text-align: center; display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;\"><button onclick=\"window.babyTracker.parseOCRResultManually()\" class=\"btn btn-primary cute-btn\" style=\"background: linear-gradient(135deg, #4FACFE 0%, #00F2FE 100%); padding: 12px 25px; border-radius: 25px; font-weight: 600; box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);\">🤖 智能解析数据</button><button onclick=\"window.babyTracker.hideOCRResult()\" class=\"btn btn-secondary cute-btn\" style=\"background: linear-gradient(135deg, #DDA0DD 0%, #DA70D6 100%); padding: 12px 25px; border-radius: 25px; font-weight: 600;\">❌ 关闭结果</button></div>';\n \n resultDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n\n async parseOCRResultManually() {\n if (!this.lastOCRResult) {\n this.showMessage('❌ 没有OCR识别结果', 'error');\n return;\n }\n \n try {\n this.showLoadingState('🤖 正在智能解析表格数据...');\n \n const localParsedData = this.parseTableStructure(this.lastOCRResult);\n console.log('本地解析结果:', localParsedData);\n \n const aiEnhancedData = await this.enhanceDataWithAI(this.lastOCRResult, localParsedData);\n console.log('AI增强结果:', aiEnhancedData);\n \n const finalData = this.mergeDataResults(localParsedData, aiEnhancedData);\n \n this.showParseResult(finalData);\n this.handleMultipleRecords(finalData);\n this.hideLoadingState();\n this.showMessage('🎉 成功解析 ' + (finalData.records ? finalData.records.length : 0) + ' 条体检记录', 'success');\n \n } catch (error) {\n this.hideLoadingState();\n this.showMessage('❌ 解析失败: ' + error.message, 'error');\n console.error('表格解析错误:', error);\n }\n }\n\n parseTableStructure(text) {\n console.log('=== 开始本地表格解析 ===');\n const result = {\n records: []\n };\n \n let cleanText = text\n .replace(/[|\\[\\]{}]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n \n console.log('清理后文本:', cleanText);\n \n const lines = cleanText.split('\\n').map(function(line) { return line.trim(); }).filter(function(line) { return line.length > 0; });\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const record = this.parseTableRow(line);\n if (record) {\n console.log('第' + (i+1) + '行识别到记录:', record);\n result.records.push(record);\n }\n }\n \n result.records.sort(function(a, b) { return new Date(b.date) - new Date(a.date); });\n \n console.log('本地解析完成,共', result.records.length, '条记录');\n return result;\n }\n\n parseTableRow(line) {\n if (line.includes('体检日期') || line.includes('年龄') || line.includes('身高') || line.includes('体重')) {\n return null;\n }\n \n const record = {};\n \n const datePatterns = [\n /(\\d{4}[-\\/]\\d{1,2}[-\\/]\\d{1,2})/,\n /(\\d{4}年\\d{1,2}月\\d{1,2}日)/\n ];\n \n for (let i = 0; i < datePatterns.length; i++) {\n const pattern = datePatterns[i];\n const match = line.match(pattern);\n if (match) {\n record.date = match[1].replace(/年/g, '-').replace(/月/g, '-').replace(/日/g, '');\n break;\n }\n }\n \n if (!record.date) return null;\n \n const ageMonths = this.extractAgeInMonths(line);\n if (ageMonths !== null) {\n record.ageMonths = ageMonths;\n }\n \n const weightMatch = line.match(/(\\d+(?:\\.\\d+)?)\\s*(?:kg|公斤)/i);\n if (weightMatch) {\n record.weight = parseFloat(weightMatch[1]);\n } else {\n const numbers = line.match(/\\d+(?:\\.\\d+)?/g);\n if (numbers) {\n for (let i = 0; i < numbers.length; i++) {\n const num = numbers[i];\n const val = parseFloat(num);\n if (val >= 2 && val <= 30) {\n record.weight = val;\n break;\n }\n }\n }\n }\n \n const heightMatch = line.match(/(\\d+(?:\\.\\d+)?)\\s*(?:cm|厘米)/i);\n if (heightMatch) {\n record.height = parseFloat(heightMatch[1]);\n } else {\n const numbers = line.match(/\\d+(?:\\.\\d+)?/g);\n if (numbers) {\n for (let i = 0; i < numbers.length; i++) {\n const num = numbers[i];\n const val = parseFloat(num);\n if (val >= 40 && val <= 120) {\n record.height = val;\n break;\n }\n }\n }\n }\n \n const headMatch = line.match(/(?:头围|头周)[::\\s]*(\\d+(?:\\.\\d+)?)/);\n if (headMatch) {\n record.headCircumference = parseFloat(headMatch[1]);\n } else {\n const numbers = line.match(/\\d+(?:\\.\\d+)?/g);\n if (numbers) {\n for (let i = 0; i < numbers.length; i++) {\n const num = numbers[i];\n const val = parseFloat(num);\n if (val >= 30 && val <= 60) {\n record.headCircumference = val;\n break;\n }\n }\n }\n }\n \n if (record.weight || record.height || record.headCircumference) {\n return record;\n }\n \n return null;\n }\n\n extractAgeInMonths(text) {\n const ageYearMonthMatch = text.match(/(\\d+)岁(\\d+)月/);\n if (ageYearMonthMatch) {\n const years = parseInt(ageYearMonthMatch[1]);\n const months = parseInt(ageYearMonthMatch[2]);\n return years * 12 + months;\n }\n \n const ageMonthDayMatch = text.match(/(\\d+)月(\\d+)天/);\n if (ageMonthDayMatch) {\n const months = parseInt(ageMonthDayMatch[1]);\n const days = parseInt(ageMonthDayMatch[2]);\n return months + (days / 30);\n }\n \n const ageDayMatch = text.match(/^(\\d+)天$/);\n if (ageDayMatch) {\n const days = parseInt(ageDayMatch[1]);\n return days / 30;\n }\n \n const ageMonthMatch = text.match(/(\\d+)个月/);\n if (ageMonthMatch) {\n return parseInt(ageMonthMatch[1]);\n }\n \n return null;\n }\n\n async enhanceDataWithAI(ocrText, localData) {\n if (!this.apiKey) {\n console.log('无API密钥,跳过AI增强');\n return { records: [] };\n }\n \n const prompt = '你是体检报告分析专家。请分析以下OCR识别的体检报告文本,提取所有体检记录。\\n\\nOCR文本:\\n' + ocrText + '\\n\\n本地解析已识别到 ' + localData.records.length + ' 条记录。\\n\\n请按以下JSON格式返回所有体检记录,特别注意:\\n1. 年龄格式转换:\\n \"1岁2月\" = 14个月\\n \"8月0天\" = 8个月 \\n \"6月1天\" = 6.03个月\\n2. 数据验证:体重(2-30kg)、身高(40-120cm)、头围(30-60cm)\\n3. 只返回JSON,不要其他说明\\n\\n{\\n \"records\": [\\n {\\n \"date\": \"2025-04-03\",\\n \"ageMonths\": 14.7,\\n \"weight\": 11.4,\\n \"height\": 81.5,\\n \"headCircumference\": 47.5\\n }\\n ]\\n}';\n \n try {\n const response = await fetch('https://api.deepseek.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + this.apiKey\n },\n body: JSON.stringify({\n model: \"deepseek-chat\",\n messages: [{\n role: \"user\",\n content: prompt\n }],\n max_tokens: 1500,\n temperature: 0.1\n })\n });\n \n if (!response.ok) {\n throw new Error('AI API调用失败: ' + response.status);\n }\n \n const data = await response.json();\n const aiResponse = data.choices[0].message.content;\n \n console.log('AI原始响应:', aiResponse);\n \n const jsonMatch = aiResponse.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n return JSON.parse(jsonMatch[0]);\n } else {\n throw new Error('AI返回格式错误');\n }\n \n } catch (error) {\n console.error('AI增强失败:', error);\n return { records: [] };\n }\n }\n\n mergeDataResults(localData, aiData) {\n console.log('=== 合并解析结果 ===');\n const merged = {\n records: []\n };\n \n const allRecords = localData.records.slice();\n \n if (aiData.records && Array.isArray(aiData.records)) {\n for (let i = 0; i < aiData.records.length; i++) {\n const aiRecord = aiData.records[i];\n const existing = allRecords.find(function(r) { return r.date === aiRecord.date; });\n if (!existing) {\n allRecords.push(aiRecord);\n } else {\n if (!existing.weight && aiRecord.weight) existing.weight = aiRecord.weight;\n if (!existing.height && aiRecord.height) existing.height = aiRecord.height;\n if (!existing.headCircumference && aiRecord.headCircumference) existing.headCircumference = aiRecord.headCircumference;\n if (!existing.ageMonths && aiRecord.ageMonths) existing.ageMonths = aiRecord.ageMonths;\n }\n }\n }\n \n merged.records = allRecords.filter(function(record) {\n if (!record.date) return false;\n \n if (record.weight && (record.weight < 1 || record.weight > 50)) return false;\n if (record.height && (record.height < 30 || record.height > 150)) return false;\n if (record.headCircumference && (record.headCircumference < 25 || record.headCircumference > 65)) return false;\n \n return record.weight || record.height || record.headCircumference;\n });\n \n merged.records.sort(function(a, b) { return new Date(b.date) - new Date(a.date); });\n \n console.log('合并完成,最终记录数:', merged.records.length);\n return merged;\n }\n\n showParseResult(extractedData) {\n let parseDiv = document.getElementById('parseResultDiv');\n \n if (!parseDiv) {\n parseDiv = document.createElement('div');\n parseDiv.id = 'parseResultDiv';\n parseDiv.className = 'card cute-card';\n parseDiv.style.cssText = 'background: linear-gradient(135deg, #FFE5F1 0%, #FFE5E5 100%); border: 3px solid #FF69B4; margin: 20px 0; padding: 25px; border-radius: 20px; box-shadow: 0 8px 25px rgba(255, 105, 180, 0.3);';\n const ocrDiv = document.getElementById('ocrResultDiv');\n if (ocrDiv && ocrDiv.parentNode) {\n ocrDiv.parentNode.insertBefore(parseDiv, ocrDiv.nextSibling);\n }\n }\n \n const recordsCount = extractedData.records ? extractedData.records.length : 0;\n \n parseDiv.innerHTML = '<div style=\"text-align: center; margin-bottom: 20px;\"><h4 style=\"color: #FF1493; margin-bottom: 10px; font-size: 1.4em;\">🎯 智能识别结果<span style=\"background: #FF69B4; color: white; padding: 5px 12px; border-radius: 15px; font-size: 0.8em;\">' + recordsCount + '条记录</span></h4><p style=\"color: #666; font-size: 0.9em;\">💡 可以编辑下方数据,确保准确性后再添加到记录中</p></div><div style=\"background: white; padding: 20px; border-radius: 15px; border: 2px solid #FFB6C1; margin-bottom: 20px;\"><h5 style=\"color: #FF1493; margin-bottom: 15px; display: flex; align-items: center;\"><span>📊 识别概况</span></h5><div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; font-size: 0.9em;\"><div style=\"background: #FFF0F5; padding: 10px; border-radius: 10px;\"><strong>📈 识别记录数:</strong> ' + recordsCount + '</div><div style=\"background: #F0F8FF; padding: 10px; border-radius: 10px;\"><strong>🤖 解析方式:</strong> 百度OCR + AI增强</div><div style=\"background: #F0FFF0; padding: 10px; border-radius: 10px;\"><strong>✅ 数据完整性:</strong> ' + this.getDataCompleteness(extractedData.records) + '</div></div></div>' + this.generateEditableRecordsHtml(extractedData.records) + '<div style=\"text-align: center; margin-top: 20px; display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;\"><button onclick=\"window.babyTracker.saveEditedRecords()\" class=\"btn btn-success cute-btn\" style=\"background: linear-gradient(135deg, #FF69B4 0%, #FF1493 100%); padding: 12px 25px; border-radius: 25px; font-weight: 600; box-shadow: 0 5px 15px rgba(255, 105, 180, 0.4);\">✨ 保存并添加记录</button><button onclick=\"window.babyTracker.hideOCRResult()\" class=\"btn btn-secondary cute-btn\" style=\"background: linear-gradient(135deg, #DDA0DD 0%, #DA70D6 100%); padding: 12px 25px; border-radius: 25px; font-weight: 600;\">❌ 取消</button></div>';\n \n parseDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n\n generateEditableRecordsHtml(records) {\n if (!records || records.length === 0) {\n return '<div style=\"background: white; padding: 20px; border-radius: 15px; border: 2px solid #FFB6C1; text-align: center;\"><p style=\"color: #888; font-size: 1.1em;\">🤔 未识别到有效记录</p><p style=\"color: #666; font-size: 0.9em;\">请检查图片是否清晰,或尝试手动添加记录</p></div>';\n }\n \n let html = '<div style=\"background: white; padding: 20px; border-radius: 15px; border: 2px solid #FFB6C1;\"><h5 style=\"color: #FF1493; margin-bottom: 15px; display: flex; align-items: center;\"><span>✏️ 编辑识别数据</span><span style=\"margin-left: auto; font-size: 0.8em; color: #888;\">点击表格单元格可编辑</span></h5><div style=\"overflow-x: auto;\"><table id=\"editableRecordsTable\" style=\"width: 100%; border-collapse: collapse; font-size: 0.9em;\"><thead><tr style=\"background: linear-gradient(135deg, #FFB6C1 0%, #FFC0CB 100%);\"><th style=\"padding: 12px 8px; border: 1px solid #FF69B4; color: #8B008B; font-weight: 600;\">📅 体检日期</th><th style=\"padding: 12px 8px; border: 1px solid #FF69B4; color: #8B008B; font-weight: 600;\">👶 月龄</th><th style=\"padding: 12px 8px; border: 1px solid #FF69B4; color: #8B008B; font-weight: 600;\">📏 身高(cm)</th><th style=\"padding: 12px 8px; border: 1px solid #FF69B4; color: #8B008B; font-weight: 600;\">⚖️ 体重(kg)</th><th style=\"padding: 12px 8px; border: 1px solid #FF69B4; color: #8B008B; font-weight: 600;\">🧠 头围(cm)</th><th style=\"padding: 12px 8px; border: 1px solid #FF69B4; color: #8B008B; font-weight: 600;\">🛠️ 操作</th></tr></thead><tbody>';\n \n for (let i = 0; i < records.length; i++) {\n const record = records[i];\n html += '<tr data-index=\"' + i + '\" style=\"background: ' + (i % 2 === 0 ? '#FFF8DC' : '#F0F8FF') + ';\"><td style=\"padding: 10px 8px; border: 1px solid #FFB6C1;\"><input type=\"date\" value=\"' + (record.date || '') + '\" onchange=\"window.babyTracker.updateEditedRecord(' + i + ', \\'date\\', this.value)\" style=\"width: 100%; border: none; background: transparent; text-align: center; font-size: 0.9em;\"></td><td style=\"padding: 10px 8px; border: 1px solid #FFB6C1;\"><input type=\"number\" step=\"0.1\" value=\"' + (record.ageMonths || '') + '\" onchange=\"window.babyTracker.updateEditedRecord(' + i + ', \\'ageMonths\\', this.value)\" style=\"width: 100%; border: none; background: transparent; text-align: center; font-size: 0.9em;\" placeholder=\"月\"></td><td style=\"padding: 10px 8px; border: 1px solid #FFB6C1;\"><input type=\"number\" step=\"0.1\" value=\"' + (record.height || '') + '\" onchange=\"window.babyTracker.updateEditedRecord(' + i + ', \\'height\\', this.value)\" style=\"width: 100%; border: none; background: transparent; text-align: center; font-size: 0.9em;\" placeholder=\"cm\"></td><td style=\"padding: 10px 8px; border: 1px solid #FFB6C1;\"><input type=\"number\" step=\"0.1\" value=\"' + (record.weight || '') + '\" onchange=\"window.babyTracker.updateEditedRecord(' + i + ', \\'weight\\', this.value)\" style=\"width: 100%; border: none; background: transparent; text-align: center; font-size: 0.9em;\" placeholder=\"kg\"></td><td style=\"padding: 10px 8px; border: 1px solid #FFB6C1;\"><input type=\"number\" step=\"0.1\" value=\"' + (record.headCircumference || '') + '\" onchange=\"window.babyTracker.updateEditedRecord(' + i + ', \\'headCircumference\\', this.value)\" style=\"width: 100%; border: none; background: transparent; text-align: center; font-size: 0.9em;\" placeholder=\"cm\"></td><td style=\"padding: 10px 8px; border: 1px solid #FFB6C1; text-align: center;\"><button onclick=\"window.babyTracker.deleteEditedRecord(' + i + ')\" style=\"background: linear-gradient(135deg, #FF6B6B 0%, #FF5252 100%); color: white; border: none; border-radius: 15px; padding: 5px 10px; font-size: 0.8em; cursor: pointer;\">🗑️ 删除</button></td></tr>';\n }\n \n html += '</tbody></table></div><div style=\"text-align: center; margin-top: 15px;\"><button onclick=\"window.babyTracker.addNewEditedRecord()\" style=\"background: linear-gradient(135deg, #98FB98 0%, #90EE90 100%); color: #006400; border: none; border-radius: 20px; padding: 10px 20px; font-weight: 600; cursor: pointer; box-shadow: 0 3px 10px rgba(152, 251, 152, 0.4);\">➕ 添加新记录</button></div></div>';\n \n return html;\n }\n\n updateEditedRecord(index, field, value) {\n if (!this.tempExtractedData || !this.tempExtractedData.records) {\n return;\n }\n \n if (this.tempExtractedData.records[index]) {\n if (field === 'ageMonths' || field === 'height' || field === 'weight' || field === 'headCircumference') {\n this.tempExtractedData.records[index][field] = value ? parseFloat(value) : null;\n } else {\n this.tempExtractedData.records[index][field] = value;\n }\n \n console.log('更新记录:', index, field, value);\n this.validateEditedRecord(index);\n }\n }\n\n validateEditedRecord(index) {\n const record = this.tempExtractedData.records[index];\n const row = document.querySelector('tr[data-index=\"' + index + '\"]');\n if (!row) return;\n \n const inputs = row.querySelectorAll('input');\n for (let i = 0; i < inputs.length; i++) {\n const input = inputs[i];\n input.style.border = 'none';\n input.style.background = 'transparent';\n }\n \n let hasError = false;\n \n if (record.weight && (record.weight < 1 || record.weight > 50)) {\n const weightInput = row.querySelector('input[onchange*=\"weight\"]');\n if (weightInput) {\n weightInput.style.border = '2px solid #FF6B6B';\n weightInput.style.background = '#FFE5E5';\n }\n hasError = true;\n }\n \n if (record.height && (record.height < 30 || record.height > 150)) {\n const heightInput = row.querySelector('input[onchange*=\"height\"]');\n if (heightInput) {\n heightInput.style.border = '2px solid #FF6B6B';\n heightInput.style.background = '#FFE5E5';\n }\n hasError = true;\n }\n \n if (record.headCircumference && (record.headCircumference < 25 || record.headCircumference > 65)) {\n const headInput = row.querySelector('input[onchange*=\"headCircumference\"]');\n if (headInput) {\n headInput.style.border = '2px solid #FF6B6B';\n headInput.style.background = '#FFE5E5';\n }\n hasError = true;\n }\n \n if (hasError) {\n this.showMessage('⚠️ 请检查数据范围:体重(1-50kg)、身高(30-150cm)、头围(25-65cm)', 'warning');\n }\n }\n\n deleteEditedRecord(index) {\n if (!this.tempExtractedData || !this.tempExtractedData.records) {\n return;\n }\n \n if (confirm('🤔 确定要删除这条记录吗?')) {\n this.tempExtractedData.records.splice(index, 1);\n this.showParseResult(this.tempExtractedData);\n this.showMessage('✅ 记录已删除', 'success');\n }\n }\n\n addNewEditedRecord() {\n if (!this.tempExtractedData) {\n this.tempExtractedData = { records: [] };\n }\n \n const newRecord = {\n date: new Date().toISOString().split('T')[0],\n ageMonths: null,\n height: null,\n weight: null,\n headCircumference: null\n };\n \n this.tempExtractedData.records.push(newRecord);\n this.showParseResult(this.tempExtractedData);\n this.showMessage('✨ 已添加新记录,请填写数据', 'info');\n }\n\n saveEditedRecords() {\n if (!this.tempExtractedData || !this.tempExtractedData.records) {\n this.showMessage('❌ 没有可保存的记录', 'error');\n return;\n }\n \n const validRecords = this.tempExtractedData.records.filter(function(record) {\n return record.date && (record.height || record.weight || record.headCircumference);\n });\n \n if (validRecords.length === 0) {\n this.showMessage('❌ 没有有效的记录数据', 'error');\n return;\n }\n \n this.tempExtractedData.records = validRecords;\n this.addAllRecords();\n }\n\n getDataCompleteness(records) {\n if (!records || records.length === 0) return '无数据';\n \n let completeCount = 0;\n for (let i = 0; i < records.length; i++) {\n const record = records[i];\n if (record.weight && record.height && record.headCircumference && record.ageMonths) {\n completeCount++;\n }\n }\n \n return completeCount + '/' + records.length + ' 条记录数据完整';\n }\n\n handleMultipleRecords(extractedData) {\n console.log('=== 处理多条记录 ===');\n this.tempExtractedData = extractedData;\n console.log('识别到体检记录数量:', extractedData.records ? extractedData.records.length : 0);\n }\n\n addAllRecords() {\n if (!this.tempExtractedData || !this.tempExtractedData.records) {\n this.showMessage('❌ 没有可添加的记录', 'error');\n return;\n }\n \n const gender = this.babyInfo.gender || 'male';\n let addedCount = 0;\n \n for (let i = 0; i < this.tempExtractedData.records.length; i++) {\n const record = this.tempExtractedData.records[i];\n const existing = this.checkRecords.find(function(r) { return r.checkDate === record.date; });\n if (!existing) {\n const recordWithPercentiles = {\n checkDate: record.date,\n ageMonths: record.ageMonths,\n height: record.height,\n weight: record.weight,\n headCircumference: record.headCircumference,\n id: Date.now() + Math.random(),\n addedAt: new Date().toISOString(),\n heightPercentile: record.height ? this.calculateWHOPercentileWithLinearInterpolation(record.ageMonths, record.height, 'height', gender) : null,\n weightPercentile: record.weight ? this.calculateWHOPercentileWithLinearInterpolation(record.ageMonths, record.weight, 'weight', gender) : null,\n headPercentile: record.headCircumference ? this.calculateWHOPercentileWithLinearInterpolation(record.ageMonths, record.headCircumference, 'headCircumference', gender) : null\n };\n \n this.checkRecords.push(recordWithPercentiles);\n addedCount++;\n }\n }\n \n if (addedCount > 0) {\n this.saveData();\n this.updateRecordsTable();\n this.updateCurrentStatus();\n this.updateChart();\n this.showMessage('🎉 成功添加 ' + addedCount + ' 条体检记录', 'success');\n } else {\n this.showMessage('ℹ️ 所有记录已存在,未添加新记录', 'info');\n }\n \n this.hideOCRResult();\n this.tempExtractedData = null;\n }\n\n hideOCRResult() {\n const ocrDiv = document.getElementById('ocrResultDiv');\n const parseDiv = document.getElementById('parseResultDiv');\n if (ocrDiv) ocrDiv.remove();\n if (parseDiv) parseDiv.remove();\n }\n\n resetUploadInput() {\n const reportUpload = document.getElementById('reportUpload');\n if (reportUpload) {\n reportUpload.value = '';\n }\n }\n\n showImagePreview(file) {\n const uploadedImageDiv = document.getElementById('uploadedImage');\n const previewImage = document.getElementById('previewImage');\n \n if (uploadedImageDiv && previewImage) {\n const reader = new FileReader();\n reader.onload = function(e) {\n previewImage.src = e.target.result;\n uploadedImageDiv.classList.remove('hidden');\n };\n reader.readAsDataURL(file);\n }\n }\n\n updatePercentiles() {\n const ageMonths = parseFloat(document.getElementById('ageMonths') ? document.getElementById('ageMonths').value : '') || null;\n const height = parseFloat(document.getElementById('height') ? document.getElementById('height').value : '') || null;\n const weight = parseFloat(document.getElementById('weight') ? document.getElementById('weight').value : '') || null;\n const headCircumference = parseFloat(document.getElementById('headCircumference') ? document.getElementById('headCircumference').value : '') || null;\n \n console.log('计算百分位 - 年龄:', ageMonths, '身高:', height, '体重:', weight, '头围:', headCircumference);\n \n if (!ageMonths) {\n this.updatePercentileDisplay({\n height: null,\n weight: null,\n headCircumference: null\n });\n return;\n }\n \n const gender = this.babyInfo.gender || 'male';\n const percentiles = {\n height: this.calculateWHOPercentileWithLinearInterpolation(ageMonths, height, 'height', gender),\n weight: this.calculateWHOPercentileWithLinearInterpolation(ageMonths, weight, 'weight', gender),\n headCircumference: this.calculateWHOPercentileWithLinearInterpolation(ageMonths, headCircumference, 'headCircumference', gender)\n };\n \n console.log('计算出的百分位:', percentiles);\n this.updatePercentileDisplay(percentiles);\n }\n\n calculateWHOPercentileWithLinearInterpolation(ageMonths, value, type, gender) {\n if (!value || !ageMonths) return null;\n \n console.log('\\n=== 计算' + type + '百分位 (线性插值) ===');\n console.log('年龄: ' + ageMonths + '月, ' + type + ': ' + value + ', 性别: ' + gender);\n \n let dataKey;\n if (type === 'height') {\n dataKey = gender === 'female' ? 'femaleHeight' : 'maleHeight';\n } else if (type === 'weight') {\n dataKey = gender === 'female' ? 'femaleWeight' : 'maleWeight';\n } else if (type === 'headCircumference') {\n dataKey = gender === 'female' ? 'femaleHeadCircumference' : 'maleHeadCircumference';\n }\n \n const whoDataSource = this.whoData[dataKey];\n \n const lowerAge = Math.floor(ageMonths);\n const upperAge = Math.ceil(ageMonths);\n \n console.log('年龄边界: ' + lowerAge + '月 到 ' + upperAge + '月');\n \n if (lowerAge === upperAge) {\n const whoRecord = whoDataSource[lowerAge];\n if (!whoRecord) {\n console.log('❌ 没有找到' + lowerAge + '月龄的WHO数据');\n return null;\n }\n return this.calculatePercentileFromWHOData(value, whoRecord, lowerAge + '月整数月龄');\n }\n \n const lowerWHOData = whoDataSource[lowerAge];\n const upperWHOData = whoDataSource[upperAge];\n \n if (!lowerWHOData || !upperWHOData) {\n console.log('❌ 缺少WHO数据: ' + lowerAge + '月=' + !!lowerWHOData + ', ' + upperAge + '月=' + !!upperWHOData);\n return null;\n }\n \n const ratio = ageMonths - lowerAge;\n console.log('插值比例: (' + ageMonths + ' - ' + lowerAge + ') = ' + ratio);\n \n const interpolatedWHOData = [];\n for (let i = 0; i < 7; i++) {\n const interpolatedValue = lowerWHOData[i] + ratio * (upperWHOData[i] - lowerWHOData[i]);\n interpolatedWHOData.push(parseFloat(interpolatedValue.toFixed(2)));\n }\n \n console.log(lowerAge + '月WHO数据:', lowerWHOData);\n console.log(upperAge + '月WHO数据:', upperWHOData);\n console.log('插值后WHO数据:', interpolatedWHOData);\n \n return this.calculatePercentileFromWHOData(value, interpolatedWHOData, ageMonths + '月插值数据');\n }\n\n calculatePercentileFromWHOData(value, whoData, description) {\n console.log('\\n--- 基于' + description + '计算百分位 ---');\n \n const sd3neg = whoData[0];\n const sd2neg = whoData[1];\n const sd1neg = whoData[2];\n const median = whoData[3];\n const sd1pos = whoData[4];\n const sd2pos = whoData[5];\n const sd3pos = whoData[6];\n \n console.log('WHO标准数据:');\n console.log(' -3SD: ' + sd3neg);\n console.log(' -2SD: ' + sd2neg);\n console.log(' -1SD: ' + sd1neg);\n console.log(' 中位数: ' + median);\n console.log(' +1SD: ' + sd1pos);\n console.log(' +2SD: ' + sd2pos);\n console.log(' +3SD: ' + sd3pos);\n \n let interval = '';\n let percentile = null;\n \n if (value <= sd3neg) {\n interval = '< -3SD (严重偏低)';\n percentile = 0.1;\n } else if (value <= sd2neg) {\n interval = '-3SD 到 -2SD 之间 (偏低)';\n const ratio = (value - sd3neg) / (sd2neg - sd3neg);\n percentile = 0.1 + ratio * (2.3 - 0.1);\n } else if (value <= sd1neg) {\n interval = '-2SD 到 -1SD 之间 (稍低)';\n const ratio = (value - sd2neg) / (sd1neg - sd2neg);\n percentile = 2.3 + ratio * (15.9 - 2.3);\n } else if (value <= median) {\n interval = '-1SD 到 中位数 之间 (正常偏低)';\n const ratio = (value - sd1neg) / (median - sd1neg);\n percentile = 15.9 + ratio * (50 - 15.9);\n } else if (value <= sd1pos) {\n interval = '中位数 到 +1SD 之间 (正常偏高)';\n const ratio = (value - median) / (sd1pos - median);\n percentile = 50 + ratio * (84.1 - 50);\n } else if (value <= sd2pos) {\n interval = '+1SD 到 +2SD 之间 (稍高)';\n const ratio = (value - sd1pos) / (sd2pos - sd1pos);\n percentile = 84.1 + ratio * (97.7 - 84.1);\n } else if (value <= sd3pos) {\n interval = '+2SD 到 +3SD 之间 (偏高)';\n const ratio = (value - sd2pos) / (sd3pos - sd2pos);\n percentile = 97.7 + ratio * (99.9 - 97.7);\n } else {\n interval = '> +3SD (严重偏高)';\n percentile = 99.9;\n } \n console.log('📊 标准差区间: ' + interval);\n console.log('📈 计算百分位: ' + (percentile ? percentile.toFixed(1) : 'null') + '%');\n \n const finalPercentile = Math.max(0.1, Math.min(99.9, Math.round(percentile * 10) / 10));\n console.log('✅ 最终百分位: ' + finalPercentile + '%');\n console.log('--- 计算完成 ---\\n');\n \n return finalPercentile;\n }\n\n updatePercentileDisplay(percentiles) {\n const heightPercentileEl = document.getElementById('heightPercentile');\n const weightPercentileEl = document.getElementById('weightPercentile');\n const headPercentileEl = document.getElementById('headPercentile');\n \n if (heightPercentileEl) {\n heightPercentileEl.textContent = percentiles.height !== null ? percentiles.height + '%' : '--';\n }\n if (weightPercentileEl) {\n weightPercentileEl.textContent = percentiles.weight !== null ? percentiles.weight + '%' : '--';\n }\n if (headPercentileEl) {\n headPercentileEl.textContent = percentiles.headCircumference !== null ? percentiles.headCircumference + '%' : '--';\n }\n }\n\n initializeChart() {\n const ctx = document.getElementById('growthChart');\n if (!ctx) return;\n \n if (typeof Chart === 'undefined') {\n console.warn('Chart.js未加载,跳过图表初始化');\n return;\n }\n \n const config = {\n type: 'line',\n data: {\n labels: [],\n datasets: []\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n plugins: {\n title: {\n display: true,\n text: '🌈 生长发育曲线图'\n },\n legend: {\n display: true,\n position: 'top'\n }\n },\n scales: {\n x: {\n title: {\n display: true,\n text: '年龄 (月)'\n },\n min: 0,\n max: 36\n },\n y: {\n title: {\n display: true,\n text: '数值'\n }\n }\n },\n interaction: {\n intersect: false,\n mode: 'index'\n }\n }\n };\n \n this.growthChart = new Chart(ctx, config);\n this.updateChart();\n }\n\n updateChart() {\n if (!this.growthChart) return;\n \n const gender = this.babyInfo.gender || 'male';\n const chartType = document.querySelector('input[name=\"chartType\"]:checked') ? document.querySelector('input[name=\"chartType\"]:checked').value : 'height';\n \n const whoKey = gender === 'female' ? \n 'female' + chartType.charAt(0).toUpperCase() + chartType.slice(1) : \n 'male' + chartType.charAt(0).toUpperCase() + chartType.slice(1);\n const whoData = this.whoData[whoKey] || this.whoData.maleHeight;\n \n const ageLabels = [];\n for (let i = 0; i <= 36; i++) {\n ageLabels.push(i);\n }\n \n const standardLines = [\n {\n label: '-2SD (3%)',\n data: [],\n borderColor: '#ff6b6b',\n backgroundColor: 'transparent',\n borderWidth: 1,\n pointRadius: 0\n },\n {\n label: '-1SD (15%)',\n data: [],\n borderColor: '#ffa726',\n backgroundColor: 'transparent',\n borderWidth: 1,\n pointRadius: 0\n },\n {\n label: '中位数 (50%)',\n data: [],\n borderColor: '#4caf50',\n backgroundColor: 'transparent',\n borderWidth: 2,\n pointRadius: 0\n },\n {\n label: '+1SD (85%)',\n data: [],\n borderColor: '#ffa726',\n backgroundColor: 'transparent',\n borderWidth: 1,\n pointRadius: 0\n },\n {\n label: '+2SD (97%)',\n data: [],\n borderColor: '#ff6b6b',\n backgroundColor: 'transparent',\n borderWidth: 1,\n pointRadius: 0\n }\n ];\n \n for (let i = 0; i < ageLabels.length; i++) {\n const age = ageLabels[i];\n const whoRecord = whoData[age];\n if (whoRecord) {\n standardLines[0].data.push(whoRecord[1]);\n standardLines[1].data.push(whoRecord[2]);\n standardLines[2].data.push(whoRecord[3]);\n standardLines[3].data.push(whoRecord[4]);\n standardLines[4].data.push(whoRecord[5]);\n } else {\n for (let j = 0; j < standardLines.length; j++) {\n standardLines[j].data.push(null);\n }\n }\n }\n \n const babyData = {\n label: '🍼 宝宝实际数据',\n data: new Array(37).fill(null),\n borderColor: '#2196f3',\n backgroundColor: '#2196f3',\n borderWidth: 3,\n pointRadius: 5,\n pointHoverRadius: 7\n };\n \n for (let i = 0; i < this.checkRecords.length; i++) {\n const record = this.checkRecords[i];\n const age = Math.round(record.ageMonths);\n let value = null;\n \n switch(chartType) {\n case 'height':\n value = record.height;\n break;\n case 'weight':\n value = record.weight;\n break;\n case 'headCircumference':\n value = record.headCircumference;\n break;\n }\n \n if (value && age >= 0 && age <= 36) {\n babyData.data[age] = value;\n }\n }\n \n this.growthChart.data.labels = ageLabels;\n this.growthChart.data.datasets = standardLines.concat([babyData]);\n \n let yAxisTitle = '';\n switch(chartType) {\n case 'height':\n yAxisTitle = '身高 (cm)';\n break;\n case 'weight':\n yAxisTitle = '体重 (kg)';\n break;\n case 'headCircumference':\n yAxisTitle = '头围 (cm)';\n break;\n }\n \n this.growthChart.options.scales.y.title.text = yAxisTitle;\n this.growthChart.update();\n }\n\n getLatestRecord() {\n if (this.checkRecords.length === 0) {\n return null;\n }\n \n const sortedRecords = this.checkRecords.slice().sort(function(a, b) {\n return new Date(b.checkDate) - new Date(a.checkDate);\n });\n return sortedRecords[0];\n }\n\n analyzeGrowthTrend() {\n if (this.checkRecords.length < 2) {\n return {\n trend: '数据不足',\n description: '需要至少2次体检记录才能分析生长趋势'\n };\n }\n \n const sortedRecords = this.checkRecords.slice().sort(function(a, b) {\n return new Date(a.checkDate) - new Date(b.checkDate);\n });\n const latest = sortedRecords[sortedRecords.length - 1];\n const previous = sortedRecords[sortedRecords.length - 2];\n \n const trends = [];\n \n if (latest.height && previous.height && latest.heightPercentile && previous.heightPercentile) {\n const heightChange = latest.heightPercentile - previous.heightPercentile;\n if (heightChange > 5) {\n trends.push('身高增长良好');\n } else if (heightChange < -5) {\n trends.push('身高增长放缓');\n } else {\n trends.push('身高稳步增长');\n }\n }\n \n if (latest.weight && previous.weight && latest.weightPercentile && previous.weightPercentile) {\n const weightChange = latest.weightPercentile - previous.weightPercentile;\n if (weightChange > 5) {\n trends.push('体重增长良好');\n } else if (weightChange < -5) {\n trends.push('体重增长放缓');\n } else {\n trends.push('体重稳步增长');\n }\n }\n \n if (latest.headCircumference && previous.headCircumference && latest.headPercentile && previous.headPercentile) {\n const headChange = latest.headPercentile - previous.headPercentile;\n if (headChange > 5) {\n trends.push('头围增长良好');\n } else if (headChange < -5) {\n trends.push('头围增长放缓');\n } else {\n trends.push('头围稳步增长');\n }\n }\n \n return {\n trend: trends.length > 0 ? trends.join(',') : '生长正常',\n description: '基于最近两次体检记录的对比分析(' + previous.checkDate + ' vs ' + latest.checkDate + ')'\n };\n }\n\n async generateAIFeedingGuide() {\n console.log('=== 开始生成AI喂养指导 ===');\n console.log('调试信息:');\n console.log('- 基本信息:', this.babyInfo);\n console.log('- 体检记录数量:', this.checkRecords.length);\n console.log('- API密钥状态:', !!this.apiKey);\n \n if (!this.validateBasicInfo()) {\n return;\n }\n \n if (this.checkRecords.length === 0) {\n this.showMessage('🍼 请先添加体检记录', 'error');\n return;\n }\n \n if (!this.apiKey || this.apiKey.trim() === '') {\n this.showMessage('🔑 请先设置Deepseek API密钥', 'error');\n return;\n }\n \n const latestRecord = this.getLatestRecord();\n if (!latestRecord) {\n this.showMessage('❌ 无法获取最新体检记录', 'error');\n return;\n }\n \n console.log('最新体检记录:', latestRecord);\n \n const growthTrend = this.analyzeGrowthTrend();\n console.log('生长趋势分析:', growthTrend);\n \n const concernsInput = document.getElementById('feedingConcerns');\n const concerns = concernsInput ? concernsInput.value.trim() : '';\n \n const feedingLoading = document.getElementById('feedingLoading');\n const feedingGuide = document.getElementById('feedingGuide');\n \n console.log('DOM元素检查:');\n console.log('- feedingLoading:', !!feedingLoading);\n console.log('- feedingGuide:', !!feedingGuide);\n \n if (feedingLoading) feedingLoading.classList.remove('hidden');\n if (feedingGuide) feedingGuide.classList.add('hidden');\n \n try {\n const healthAnalysis = this.analyzeHealthStatus(latestRecord);\n console.log('健康分析:', healthAnalysis);\n \n this.showLoadingState('🤖 正在生成AI喂养建议...');\n const aiGuide = await this.callDeepseekForFeedingGuide(latestRecord, healthAnalysis, growthTrend, concerns);\n console.log('AI建议生成完成,字符数:', aiGuide.length);\n \n this.displayAIFeedingGuide(aiGuide, healthAnalysis, growthTrend, concerns);\n \n if (feedingLoading) feedingLoading.classList.add('hidden');\n if (feedingGuide) feedingGuide.classList.remove('hidden');\n this.hideLoadingState();\n this.showMessage('✨ AI喂养建议生成完成', 'success');\n \n } catch (error) {\n if (feedingLoading) feedingLoading.classList.add('hidden');\n this.hideLoadingState();\n this.showMessage('❌ 生成AI喂养建议失败: ' + error.message, 'error');\n console.error('AI指导生成错误:', error);\n }\n }\n\n analyzeHealthStatus(record) {\n const issues = [];\n const recommendations = [];\n \n if (record.height && record.heightPercentile) {\n if (record.heightPercentile < 10) {\n issues.push('身高偏低');\n recommendations.push('注意营养补充,建议咨询儿科医生');\n } else if (record.heightPercentile > 90) {\n issues.push('身高偏高');\n recommendations.push('继续保持良好的营养状态');\n }\n }\n \n if (record.weight && record.weightPercentile) {\n if (record.weightPercentile < 10) {\n issues.push('体重偏轻');\n recommendations.push('需要增加营养密度,增加喂养频次');\n } else if (record.weightPercentile > 90) {\n issues.push('体重超重');\n recommendations.push('控制高热量食物,增加活动量');\n }\n }\n \n if (record.headCircumference && record.headPercentile) {\n if (record.headPercentile < 10) {\n issues.push('头围偏小');\n recommendations.push('建议咨询儿科医生,关注神经系统发育');\n } else if (record.headPercentile > 90) {\n issues.push('头围偏大');\n recommendations.push('建议咨询儿科医生,排除相关疾病');\n }\n }\n \n if (record.height && record.weight) {\n const bmi = record.weight / Math.pow(record.height / 100, 2);\n if (bmi > 18) {\n issues.push('BMI偏高');\n recommendations.push('注意饮食结构,避免过度喂养');\n } else if (bmi < 14) {\n issues.push('BMI偏低');\n recommendations.push('增加营养摄入,关注生长发育');\n }\n }\n \n return {\n issues: issues.length > 0 ? issues : ['生长发育正常'],\n recommendations: recommendations,\n riskLevel: issues.length > 0 ? '需要关注' : '正常'\n };\n }\n\n async callDeepseekForFeedingGuide(record, healthAnalysis, growthTrend, concerns) {\n console.log('开始调用Deepseek API生成喂养建议');\n \n let prompt = '作为专业的儿科营养师,请为以下婴幼儿生成个性化的喂养指导建议:\\n\\n# 宝宝基本信息\\n- 姓名:' + (this.babyInfo.name || '未知') + '\\n- 性别:' + (this.babyInfo.gender === 'female' ? '女' : '男') + '\\n- 出生日期:' + (this.babyInfo.birthDate || '未知') + '\\n\\n# 最新体检数据(' + record.checkDate + ')\\n- 年龄:' + record.ageMonths + '个月\\n- 身高:' + (record.height || '未知') + 'cm (百分位:' + (record.heightPercentile || '未知') + '%)\\n- 体重:' + (record.weight || '未知') + 'kg (百分位:' + (record.weightPercentile || '未知') + '%)\\n- 头围:' + (record.headCircumference || '未知') + 'cm (百分位:' + (record.headPercentile || '未知') + '%)\\n\\n# 健康状况分析\\n主要问题:' + healthAnalysis.issues.join('、') + '\\n风险等级:' + healthAnalysis.riskLevel + '\\n\\n# 生长趋势分析\\n趋势:' + growthTrend.trend + '\\n分析基础:' + growthTrend.description;\n \n if (concerns) {\n prompt += '\\n\\n# 家长重点关注\\n' + concerns + '\\n\\n请特别针对以上关注点提供详细的解决方案和建议。';\n }\n \n prompt += '\\n\\n请提供以下三个方面的详细建议,要求专业、实用、针对性强:\\n\\n### 1. 每日营养需求\\n根据年龄、性别、当前生长状况和趋势,提供具体的营养摄入建议,包括热量、蛋白质、维生素、矿物质等需求。\\n\\n### 2. 食物推荐\\n推荐适合的食物种类、制作方法和注意事项,考虑年龄特点和个体差异。\\n\\n### 3. 喂养技巧\\n提供实用的喂养方法、时间安排和注意事项,结合生长趋势给出调整建议。\\n\\n如果存在健康问题或生长趋势异常,请重点关注并提供针对性的改善建议。回复请用中文,条理清晰,内容详实。';\n \n console.log('发送的提示词长度:', prompt.length);\n \n try {\n const response = await fetch('https://api.deepseek.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + this.apiKey\n },\n body: JSON.stringify({\n model: \"deepseek-chat\",\n messages: [\n {\n role: \"user\",\n content: prompt\n }\n ],\n max_tokens: 2500,\n temperature: 0.7,\n stream: false\n })\n });\n \n console.log('API响应状态:', response.status);\n \n if (!response.ok) {\n const errorText = await response.text();\n console.error('API错误响应:', errorText);\n throw new Error('API调用失败: ' + response.status + ' - ' + errorText);\n }\n \n const data = await response.json();\n console.log('API响应数据结构:', Object.keys(data));\n \n if (!data.choices || !data.choices[0] || !data.choices[0].message) {\n throw new Error('API返回数据格式错误');\n }\n \n return data.choices[0].message.content;\n \n } catch (error) {\n console.error('Deepseek API调用失败:', error);\n if (error.message.includes('401')) {\n throw new Error('API密钥无效,请检查密钥是否正确');\n } else if (error.message.includes('429')) {\n throw new Error('请求过于频繁,请稍后重试');\n } else if (error.message.includes('500')) {\n throw new Error('AI服务暂时不可用,请稍后重试');\n } else {\n throw new Error('网络错误: ' + error.message);\n }\n }\n }\n\n displayAIFeedingGuide(aiGuide, healthAnalysis, growthTrend, concerns) {\n console.log('显示AI喂养建议,内容长度:', aiGuide.length);\n \n const nutritionContent = document.getElementById('nutritionContent');\n const foodContent = document.getElementById('foodContent');\n const tipsContent = document.getElementById('tipsContent');\n \n console.log('内容容器检查:');\n console.log('- nutritionContent:', !!nutritionContent);\n console.log('- foodContent:', !!foodContent);\n console.log('- tipsContent:', !!tipsContent);\n \n const sections = this.parseAIGuide(aiGuide);\n \n if (nutritionContent) {\n let nutritionHTML = '';\n \n const latestRecord = this.getLatestRecord();\n nutritionHTML += '<div style=\"background: linear-gradient(135deg, #E3F2FD 0%, #BBDEFB 100%); padding: 15px; border-radius: 15px; margin-bottom: 15px; border: 2px solid #2196F3;\"><strong>📊 最新体检数据(' + latestRecord.checkDate + '):</strong><br>年龄:' + latestRecord.ageMonths + '个月 | 身高:' + (latestRecord.height || '--') + 'cm (' + (latestRecord.heightPercentile || '--') + '%) | 体重:' + (latestRecord.weight || '--') + 'kg (' + (latestRecord.weightPercentile || '--') + '%) | 头围:' + (latestRecord.headCircumference || '--') + 'cm (' + (latestRecord.headPercentile || '--') + '%)</div>';\n \n nutritionHTML += '<div style=\"background: linear-gradient(135deg, #F3E5F5 0%, #E1BEE7 100%); padding: 15px; border-radius: 15px; margin-bottom: 15px; border: 2px solid #9C27B0;\"><strong>📈 生长趋势:</strong>' + growthTrend.trend + '<br><small>' + growthTrend.description + '</small></div>';\n \n nutritionHTML += '<div style=\"background: linear-gradient(135deg, #FFF3CD 0%, #FFEAA7 100%); padding: 15px; border-radius: 15px; margin-bottom: 15px; border: 2px solid #FFC107;\"><strong>⚠️ 健康状况:</strong>' + healthAnalysis.issues.join('、') + '</div>';\n \n if (concerns) {\n nutritionHTML += '<div style=\"background: linear-gradient(135deg, #D1ECF1 0%, #B6E3EC 100%); padding: 15px; border-radius: 15px; margin-bottom: 15px; border: 2px solid #17A2B8;\"><strong>🎯 重点关注:</strong>' + concerns + '</div>';\n }\n \n nutritionHTML += sections.nutrition || '<div style=\"white-space: pre-wrap; background: white; padding: 20px; border-radius: 15px; border: 2px solid #FFB6C1;\">' + aiGuide + '</div>';\n nutritionContent.innerHTML = nutritionHTML;\n }\n \n if (foodContent) {\n foodContent.innerHTML = sections.food || '<p style=\"text-align: center; color: #888;\">请查看上方营养需求部分的完整建议</p>';\n }\n \n if (tipsContent) {\n tipsContent.innerHTML = sections.tips || '<p style=\"text-align: center; color: #888;\">请查看上方营养需求部分的完整建议</p>';\n }\n }\n\n parseAIGuide(content) {\n const sections = {};\n \n const nutritionMatch = content.match(/(?:每日营养需求|### 1\\. 每日营养需求|1\\. 每日营养需求)[::]?\\s*([\\s\\S]*?)(?=(?:食物推荐|### 2\\. 食物推荐|2\\. 食物推荐|喂养技巧|### 3\\. 喂养技巧|3\\. 喂养技巧)|$)/);\n const foodMatch = content.match(/(?:食物推荐|### 2\\. 食物推荐|2\\. 食物推荐)[::]?\\s*([\\s\\S]*?)(?=(?:喂养技巧|### 3\\. 喂养技巧|3\\. 喂养技巧|每日营养|### 1\\. 每日营养需求|1\\. 每日营养需求)|$)/);\n const tipsMatch = content.match(/(?:喂养技巧|### 3\\. 喂养技巧|3\\. 喂养技巧)[::]?\\s*([\\s\\S]*?)(?=(?:每日营养|### 1\\. 每日营养需求|1\\. 每日营养需求|食物推荐|### 2\\. 食物推荐|2\\. 食物推荐)|$)/);\n \n if (nutritionMatch) {\n sections.nutrition = '<div style=\"white-space: pre-wrap; background: white; padding: 20px; border-radius: 15px; border: 2px solid #FFB6C1;\">' + nutritionMatch[1].trim() + '</div>';\n }\n \n if (foodMatch) {\n sections.food = '<div style=\"white-space: pre-wrap; background: white; padding: 20px; border-radius: 15px; border: 2px solid #FFB6C1;\">' + foodMatch[1].trim() + '</div>';\n }\n \n if (tipsMatch) {\n sections.tips = '<div style=\"white-space: pre-wrap; background: white; padding: 20px; border-radius: 15px; border: 2px solid #FFB6C1;\">' + tipsMatch[1].trim() + '</div>';\n }\n \n return sections;\n }\n\n showLoadingState(message) {\n const loadingEl = document.getElementById('loadingMessage');\n if (loadingEl) {\n const span = loadingEl.querySelector('span');\n if (span) span.textContent = message;\n loadingEl.classList.remove('hidden');\n }\n }\n\n hideLoadingState() {\n const loadingEl = document.getElementById('loadingMessage');\n if (loadingEl) {\n loadingEl.classList.add('hidden');\n }\n }\n\n showMessage(message, type) {\n type = type || 'info';\n const existingMessage = document.querySelector('.toast-message');\n if (existingMessage) {\n existingMessage.remove();\n }\n \n const messageEl = document.createElement('div');\n messageEl.className = 'toast-message';\n messageEl.textContent = message;\n \n let backgroundColor;\n switch(type) {\n case 'success':\n backgroundColor = 'linear-gradient(135deg, #4CAF50 0%, #45A049 100%)';\n break;\n case 'error':\n backgroundColor = 'linear-gradient(135deg, #f44336 0%, #d32f2f 100%)';\n break;\n case 'warning':\n backgroundColor = 'linear-gradient(135deg, #ff9800 0%, #f57c00 100%)';\n break;\n default:\n backgroundColor = 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)';\n }\n \n messageEl.style.cssText = 'position: fixed; top: 20px; right: 20px; padding: 15px 25px; border-radius: 25px; color: white; z-index: 1000; font-size: 14px; font-weight: 600; max-width: 350px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); background: ' + backgroundColor + '; transition: all 0.3s ease; animation: slideInRight 0.5s ease-out;';\n \n const style = document.createElement('style');\n style.textContent = '@keyframes slideInRight { from { transform: translateX(100px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }';\n document.head.appendChild(style);\n \n document.body.appendChild(messageEl);\n \n setTimeout(function() {\n if (messageEl.parentNode) {\n messageEl.style.opacity = '0';\n messageEl.style.transform = 'translateX(100px)';\n setTimeout(function() {\n if (messageEl.parentNode) {\n messageEl.remove();\n }\n }, 300);\n }\n }, 3000);\n }\n\n addManualRecord() {\n if (!this.validateBasicInfo()) {\n return;\n }\n \n const formData = this.getFormData();\n if (this.validateFormData(formData)) {\n const gender = this.babyInfo.gender || 'male';\n const recordWithPercentiles = {\n checkDate: formData.checkDate,\n ageMonths: formData.ageMonths,\n height: formData.height,\n weight: formData.weight,\n headCircumference: formData.headCircumference,\n id: Date.now(),\n addedAt: new Date().toISOString(),\n heightPercentile: this.calculateWHOPercentileWithLinearInterpolation(formData.ageMonths, formData.height, 'height', gender),\n weightPercentile: this.calculateWHOPercentileWithLinearInterpolation(formData.ageMonths, formData.weight, 'weight', gender),\n headPercentile: this.calculateWHOPercentileWithLinearInterpolation(formData.ageMonths, formData.headCircumference, 'headCircumference', gender)\n };\n \n this.checkRecords.push(recordWithPercentiles);\n this.saveData();\n this.updateRecordsTable();\n this.updateCurrentStatus();\n this.updateChart();\n this.clearForm();\n this.showMessage('🎉 记录添加成功', 'success');\n }\n }\n\n getFormData() {\n return {\n checkDate: document.getElementById('checkDate') ? document.getElementById('checkDate').value : '',\n ageMonths: parseFloat(document.getElementById('ageMonths') ? document.getElementById('ageMonths').value : '') || null,\n height: parseFloat(document.getElementById('height') ? document.getElementById('height').value : '') || null,\n weight: parseFloat(document.getElementById('weight') ? document.getElementById('weight').value : '') || null,\n headCircumference: parseFloat(document.getElementById('headCircumference') ? document.getElementById('headCircumference').value : '') || null\n };\n }\n\n validateFormData(data) {\n if (!data.checkDate) {\n this.showMessage('📅 请填写体检日期', 'error');\n return false;\n }\n if (!data.ageMonths || data.ageMonths <= 0) {\n this.showMessage('👶 请填写有效的年龄(月)', 'error');\n return false;\n }\n if (!data.height && !data.weight && !data.headCircumference) {\n this.showMessage('📏 请至少填写身高、体重或头围中的一项', 'error');\n return false;\n }\n return true;\n }\n\n saveData() {\n localStorage.setItem('baby_check_records', JSON.stringify(this.checkRecords));\n localStorage.setItem('baby_info', JSON.stringify(this.babyInfo));\n }\n\n loadSavedData() {\n this.updateApiStatus();\n this.updateBaiduOcrStatus();\n \n const apiKeyInput = document.getElementById('apiKeyInput');\n if (apiKeyInput && this.apiKey) {\n apiKeyInput.value = this.apiKey;\n }\n \n const baiduApiKeyInput = document.getElementById('baiduOcrApiKey');\n const baiduSecretKeyInput = document.getElementById('baiduOcrSecretKey');\n if (baiduApiKeyInput && this.baiduOcrConfig.apiKey) {\n baiduApiKeyInput.value = this.baiduOcrConfig.apiKey;\n }\n if (baiduSecretKeyInput && this.baiduOcrConfig.secretKey) {\n baiduSecretKeyInput.value = this.baiduOcrConfig.secretKey;\n }\n \n if (this.babyInfo.name) {\n const babyNameInput = document.getElementById('babyName');\n if (babyNameInput) babyNameInput.value = this.babyInfo.name;\n }\n if (this.babyInfo.gender) {\n const babyGenderSelect = document.getElementById('babyGender');\n if (babyGenderSelect) babyGenderSelect.value = this.babyInfo.gender;\n }\n if (this.babyInfo.birthDate) {\n const babyBirthDateInput = document.getElementById('babyBirthDate');\n if (babyBirthDateInput) babyBirthDateInput.value = this.babyInfo.birthDate;\n }\n }\n\n updateUI() {\n this.updateRecordsTable();\n this.updateStatistics();\n this.updateCurrentStatus();\n }\n\n updateCurrentStatus() {\n const latestRecord = this.getLatestRecord();\n if (latestRecord) {\n const currentAgeEl = document.getElementById('currentAge');\n const currentHeightEl = document.getElementById('currentHeight');\n const currentWeightEl = document.getElementById('currentWeight');\n \n if (currentAgeEl) currentAgeEl.textContent = latestRecord.ageMonths + '月';\n if (currentHeightEl) currentHeightEl.textContent = (latestRecord.height || '--') + 'cm';\n if (currentWeightEl) currentWeightEl.textContent = (latestRecord.weight || '--') + 'kg';\n \n const feedingAgeEl = document.getElementById('feedingAge');\n const feedingHeightEl = document.getElementById('feedingHeight');\n const feedingWeightEl = document.getElementById('feedingWeight');\n \n if (feedingAgeEl) feedingAgeEl.textContent = latestRecord.ageMonths + '月';\n if (feedingHeightEl) feedingHeightEl.textContent = (latestRecord.height || '--') + 'cm';\n if (feedingWeightEl) feedingWeightEl.textContent = (latestRecord.weight || '--') + 'kg';\n } else {\n const elementsToUpdate = [\n 'currentAge', 'currentHeight', 'currentWeight',\n 'feedingAge', 'feedingHeight', 'feedingWeight'\n ];\n \n for (let i = 0; i < elementsToUpdate.length; i++) {\n const el = document.getElementById(elementsToUpdate[i]);\n if (el) el.textContent = '--';\n }\n }\n }\n\n updateRecordsTable() {\n const tableBody = document.getElementById('recordsTableBody');\n const noRecords = document.getElementById('noRecords');\n \n if (!tableBody) return;\n \n tableBody.innerHTML = '';\n \n if (this.checkRecords.length === 0) {\n if (noRecords) noRecords.style.display = 'block';\n return;\n }\n \n if (noRecords) noRecords.style.display = 'none';\n \n const sortedRecords = this.checkRecords.slice().sort(function(a, b) {\n return new Date(b.checkDate) - new Date(a.checkDate);\n });\n \n const self = this;\n for (let i = 0; i < sortedRecords.length; i++) {\n const record = sortedRecords[i];\n const row = document.createElement('tr');\n row.innerHTML = '<td>' + record.checkDate + '</td><td>' + record.ageMonths + '个月</td><td>' + (record.height || '--') + '</td><td>' + (record.heightPercentile || '--') + '%</td><td>' + (record.weight || '--') + '</td><td>' + (record.weightPercentile || '--') + '%</td><td>' + (record.headCircumference || '--') + '</td><td>' + (record.headPercentile || '--') + '%</td><td><button onclick=\"window.babyTracker.deleteRecord(' + this.checkRecords.indexOf(record) + ')\" class=\"btn btn-danger\" style=\"padding: 8px 15px; font-size: 0.8em; border-radius: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF5252 100%);\">🗑️ 删除</button></td>';\n tableBody.appendChild(row);\n }\n }\n\n updateStatistics() {\n const statsEl = document.getElementById('statisticsInfo');\n if (statsEl && this.checkRecords.length > 0) {\n const latestRecord = this.getLatestRecord();\n statsEl.innerHTML = '<p><strong>📊 总记录数:</strong>' + this.checkRecords.length + '</p><p><strong>🗓️ 最新记录:</strong>' + latestRecord.checkDate + '</p><p><strong>👶 当前月龄:</strong>' + latestRecord.ageMonths + '个月</p><p><strong>🤖 识别模式:</strong>百度OCR+AI增强</p>';\n }\n }\n\n deleteRecord(index) {\n if (confirm('🤔 确定要删除这条记录吗?')) {\n this.checkRecords.splice(index, 1);\n this.saveData();\n this.updateRecordsTable();\n this.updateStatistics();\n this.updateCurrentStatus();\n this.updateChart();\n this.showMessage('✅ 记录已删除', 'success');\n }\n }\n\n clearForm() {\n const inputs = ['checkDate', 'ageMonths', 'height', 'weight', 'headCircumference'];\n for (let i = 0; i < inputs.length; i++) {\n const el = document.getElementById(inputs[i]);\n if (el) el.value = '';\n }\n \n const percentileEls = ['heightPercentile', 'weightPercentile', 'headPercentile'];\n for (let i = 0; i < percentileEls.length; i++) {\n const el = document.getElementById(percentileEls[i]);\n if (el) el.textContent = '--';\n }\n \n const uploadedImageDiv = document.getElementById('uploadedImage');\n if (uploadedImageDiv) uploadedImageDiv.classList.add('hidden');\n \n this.hideOCRResult();\n this.resetUploadInput();\n }\n\n exportData() {\n const data = {\n babyInfo: this.babyInfo,\n checkRecords: this.checkRecords,\n exportDate: new Date().toISOString(),\n version: '10.0-BaiduOCR'\n };\n \n const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = 'baby_health_data_' + new Date().toISOString().split('T')[0] + '.json';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n \n this.showMessage('💾 数据导出成功', 'success');\n }\n\n importData() {\n const self = this;\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = '.json';\n input.onchange = function(e) {\n const file = e.target.files[0];\n if (file) {\n const reader = new FileReader();\n reader.onload = function(e) {\n try {\n const data = JSON.parse(e.target.result);\n if (data.checkRecords && Array.isArray(data.checkRecords)) {\n self.babyInfo = data.babyInfo || {};\n self.checkRecords = data.checkRecords || [];\n self.saveData();\n self.updateUI();\n self.loadSavedData();\n self.updateChart();\n self.showMessage('📥 数据导入成功', 'success');\n } else {\n self.showMessage('❌ 数据格式不正确', 'error');\n }\n } catch (error) {\n self.showMessage('⚠️ 数据格式错误,请检查文件', 'error');\n console.error('导入错误:', error);\n }\n };\n reader.readAsDataURL(file);\n }\n };\n input.click();\n }\n }\n\n // 页面加载完成后初始化\n document.addEventListener('DOMContentLoaded', function() {\n window.babyTracker = new BabyHealthTracker();\n console.log('🎀 婴幼儿健康追踪系统已初始化 (百度OCR版 v10.0)');\n \n // 绑定图表类型切换事件\n const chartTypeInputs = document.querySelectorAll('input[name=\"chartType\"]');\n for (let i = 0; i < chartTypeInputs.length; i++) {\n const input = chartTypeInputs[i];\n input.addEventListener('change', function() {\n if (window.babyTracker && window.babyTracker.updateChart) {\n window.babyTracker.updateChart();\n }\n });\n }\n \n // 绑定标签页切换功能\n const tabButtons = document.querySelectorAll('.tab-button');\n const tabContents = document.querySelectorAll('.tab-content');\n \n for (let i = 0; i < tabButtons.length; i++) {\n const button = tabButtons[i];\n button.addEventListener('click', function() {\n const targetTab = button.getAttribute('data-tab');\n \n // 移除所有活动状态\n for (let j = 0; j < tabButtons.length; j++) {\n tabButtons[j].classList.remove('active');\n }\n for (let j = 0; j < tabContents.length; j++) {\n tabContents[j].classList.remove('active');\n }\n \n // 激活当前标签页\n button.classList.add('active');\n const targetContent = document.getElementById(targetTab);\n if (targetContent) {\n targetContent.classList.add('active');\n }\n });\n }\n \n // 初始化第一个标签页为活动状态\n if (tabButtons.length > 0) {\n tabButtons[0].classList.add('active');\n const firstTabId = tabButtons[0].getAttribute('data-tab');\n const firstTabContent = document.getElementById(firstTabId);\n if (firstTabContent) {\n firstTabContent.classList.add('active');\n }\n }\n });\n\n})();\n"}}}} |