From 1a58ab62b3cb173d6bacd0dd8eeebc3eeff3ea0c Mon Sep 17 00:00:00 2001
From: patdelphi
Date: Thu, 21 Aug 2025 18:25:11 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=96=B0=E5=BC=80=E5=8F=91PDF?=
=?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 使用puppeteer替代html-pdf库实现真正的PDF生成
- 改进Markdown到HTML的转换逻辑,支持表格和列表
- 添加PDF专用CSS样式,优化打印效果
- 修复Buffer到字符串的转换问题
- 优化puppeteer启动参数,提高稳定性
- 支持A4格式,适当边距和分页控制
- 测试验证PDF生成功能正常工作
---
package-lock.json | 706 +++++++++++++++++++-
package.json | 1 +
server/routes/download.cjs | 70 +-
server/services/generators/pdfGenerator.cjs | 332 ++++++++-
src/components/CompleteYijingAnalysis.tsx | 20 +-
src/components/ui/DownloadButton.tsx | 164 +++--
src/pages/HistoryPage.tsx | 23 +-
7 files changed, 1162 insertions(+), 154 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a4535e5..2df94bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
- "name": "react_repo",
- "version": "0.0.0",
+ "name": "shenjige-numerology",
+ "version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "react_repo",
- "version": "0.0.0",
+ "name": "shenjige-numerology",
+ "version": "3.0.0",
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "^1.2.2",
@@ -52,6 +52,7 @@
"lucide-react": "^0.364.0",
"next-themes": "^0.4.4",
"nodemon": "^3.0.2",
+ "puppeteer": "^24.17.0",
"react": "^18.3.1",
"react-day-picker": "8.10.1",
"react-dom": "^18.3.1",
@@ -120,7 +121,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.27.1",
@@ -272,7 +272,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -1213,6 +1212,64 @@
"node": ">=14"
}
},
+ "node_modules/@puppeteer/browsers": {
+ "version": "2.10.7",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.7.tgz",
+ "integrity": "sha512-wHWLkQWBjHtajZeqCB74nsa/X70KheyOhySYBRmVQDJiNj0zjZR/naPCvdWjMhcG1LmjaMV/9WtTo5mpe8qWLw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.4.1",
+ "extract-zip": "^2.0.1",
+ "progress": "^2.0.3",
+ "proxy-agent": "^6.5.0",
+ "semver": "^7.7.2",
+ "tar-fs": "^3.1.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "browsers": "lib/cjs/main-cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/tar-fs": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
+ "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0",
+ "tar-stream": "^3.1.5"
+ },
+ "optionalDependencies": {
+ "bare-fs": "^4.0.1",
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/tar-stream": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
+ "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@@ -2863,6 +2920,12 @@
"win32"
]
},
+ "node_modules/@tootallnate/quickjs-emscripten": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
+ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
+ "license": "MIT"
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3092,7 +3155,7 @@
"version": "22.17.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz",
"integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -3186,6 +3249,16 @@
"@types/send": "*"
}
},
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
@@ -3546,6 +3619,15 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -3619,7 +3701,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
"license": "Python-2.0"
},
"node_modules/aria-hidden": {
@@ -3640,6 +3721,18 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
+ "node_modules/ast-types": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
+ "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@@ -3678,12 +3771,90 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/b4a": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
+ "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==",
+ "license": "Apache-2.0"
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
+ "node_modules/bare-events": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz",
+ "integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==",
+ "license": "Apache-2.0",
+ "optional": true
+ },
+ "node_modules/bare-fs": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.2.1.tgz",
+ "integrity": "sha512-mELROzV0IhqilFgsl1gyp48pnZsaV9xhQapHLDsvn4d4ZTfbFhcghQezl7FTEDNBcGqLUnNI3lUlm6ecrLWdFA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "bare-events": "^2.5.4",
+ "bare-path": "^3.0.0",
+ "bare-stream": "^2.6.4"
+ },
+ "engines": {
+ "bare": ">=1.16.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-os": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz",
+ "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "bare": ">=1.14.0"
+ }
+ },
+ "node_modules/bare-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
+ "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "bare-os": "^3.0.1"
+ }
+ },
+ "node_modules/bare-stream": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz",
+ "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "streamx": "^2.21.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*",
+ "bare-events": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ },
+ "bare-events": {
+ "optional": true
+ }
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -3704,6 +3875,15 @@
],
"license": "MIT"
},
+ "node_modules/basic-ftp": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
+ "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
@@ -3874,6 +4054,15 @@
"ieee754": "^1.1.13"
}
},
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@@ -3922,7 +4111,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -4016,6 +4204,19 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
+ "node_modules/chromium-bidi": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz",
+ "integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mitt": "^3.0.1",
+ "zod": "^3.24.1"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -4688,6 +4889,32 @@
"node": ">= 0.10"
}
},
+ "node_modules/cosmiconfig": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
+ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.1",
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4841,6 +5068,15 @@
"node": ">=12"
}
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -4905,6 +5141,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/degenerator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
+ "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.13.4",
+ "escodegen": "^2.1.0",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -4939,6 +5189,12 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1475386",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz",
+ "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -5068,6 +5324,24 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -5168,6 +5442,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
"node_modules/eslint": {
"version": "9.33.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz",
@@ -5300,6 +5595,19 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -5330,7 +5638,6 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@@ -5347,7 +5654,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
@@ -5438,6 +5744,26 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5454,6 +5780,12 @@
"node": ">=6.0.0"
}
},
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "license": "MIT"
+ },
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -5505,6 +5837,15 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -5749,6 +6090,35 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-uri": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
+ "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
+ "license": "MIT",
+ "dependencies": {
+ "basic-ftp": "^5.0.2",
+ "data-uri-to-buffer": "^6.0.2",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@@ -5901,6 +6271,32 @@
"node": ">= 0.8"
}
},
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -5953,7 +6349,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
@@ -6007,6 +6402,15 @@
"node": ">=12"
}
},
+ "node_modules/ip-address": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
+ "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -6016,6 +6420,12 @@
"node": ">= 0.10"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -6122,7 +6532,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -6151,6 +6560,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -6520,6 +6935,12 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -6583,6 +7004,15 @@
"node": ">= 0.6"
}
},
+ "node_modules/netmask": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
+ "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
@@ -6826,6 +7256,38 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pac-proxy-agent": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
+ "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/quickjs-emscripten": "^0.23.0",
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "get-uri": "^6.0.1",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.6",
+ "pac-resolver": "^7.0.1",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/pac-resolver": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
+ "license": "MIT",
+ "dependencies": {
+ "degenerator": "^5.0.0",
+ "netmask": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -6836,7 +7298,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
@@ -6845,6 +7306,24 @@
"node": ">=6"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -6907,6 +7386,12 @@
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -7122,6 +7607,15 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -7152,6 +7646,40 @@
"node": ">= 0.10"
}
},
+ "node_modules/proxy-agent": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
+ "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "http-proxy-agent": "^7.0.1",
+ "https-proxy-agent": "^7.0.6",
+ "lru-cache": "^7.14.1",
+ "pac-proxy-agent": "^7.1.0",
+ "proxy-from-env": "^1.1.0",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/proxy-agent/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@@ -7178,6 +7706,44 @@
"node": ">=6"
}
},
+ "node_modules/puppeteer": {
+ "version": "24.17.0",
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.17.0.tgz",
+ "integrity": "sha512-CGrmJ8WgilK3nyE73k+pbxHggETPpEvL6AQ9H5JSK1RgZRGMQVJ+iO3MocGm9yBQXQJ9U5xijyLvkYXFeb0/+g==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.10.7",
+ "chromium-bidi": "8.0.0",
+ "cosmiconfig": "^9.0.0",
+ "devtools-protocol": "0.0.1475386",
+ "puppeteer-core": "24.17.0",
+ "typed-query-selector": "^2.12.0"
+ },
+ "bin": {
+ "puppeteer": "lib/cjs/puppeteer/node/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/puppeteer-core": {
+ "version": "24.17.0",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.17.0.tgz",
+ "integrity": "sha512-RYOBKFiF+3RdwIZTEacqNpD567gaFcBAOKTT7742FdB1icXudrPI7BlZbYTYWK2wgGQUXt9Zi1Yn+D5PmCs4CA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.10.7",
+ "chromium-bidi": "8.0.0",
+ "debug": "^4.4.1",
+ "devtools-protocol": "0.0.1475386",
+ "typed-query-selector": "^2.12.0",
+ "ws": "^8.18.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -7574,7 +8140,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -7962,6 +8527,44 @@
"node": ">=10"
}
},
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.7",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
+ "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.0.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/sonner": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz",
@@ -7972,6 +8575,16 @@
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -7995,6 +8608,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamx": {
+ "version": "2.22.1",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz",
+ "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ },
+ "optionalDependencies": {
+ "bare-events": "^2.2.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -8243,6 +8869,15 @@
"node": ">=6"
}
},
+ "node_modules/text-decoder": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
+ "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -8427,11 +9062,17 @@
"node": ">= 0.6"
}
},
+ "node_modules/typed-query-selector": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz",
+ "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==",
+ "license": "MIT"
+ },
"node_modules/typescript": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -8475,7 +9116,7 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/unpipe": {
@@ -8929,6 +9570,27 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -9025,6 +9687,16 @@
"node": ">=8"
}
},
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index 084e95a..be8e007 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,7 @@
"lucide-react": "^0.364.0",
"next-themes": "^0.4.4",
"nodemon": "^3.0.2",
+ "puppeteer": "^24.17.0",
"react": "^18.3.1",
"react-day-picker": "8.10.1",
"react-dom": "^18.3.1",
diff --git a/server/routes/download.cjs b/server/routes/download.cjs
index 6904b8e..9941899 100644
--- a/server/routes/download.cjs
+++ b/server/routes/download.cjs
@@ -2,10 +2,9 @@ const express = require('express');
const { authenticate } = require('../middleware/auth.cjs');
const { dbManager } = require('../database/index.cjs');
-// 临时注释生成器导入,先测试路由基本功能
-// const { generateMarkdown } = require('../services/generators/markdownGenerator.cjs');
-// const { generatePDF } = require('../services/generators/pdfGenerator.cjs');
-// const { generatePNG } = require('../services/generators/pngGenerator.cjs');
+const { generateMarkdown } = require('../services/generators/markdownGenerator.cjs');
+const { generatePDF } = require('../services/generators/pdfGenerator.cjs');
+const { generatePNG } = require('../services/generators/pngGenerator.cjs');
const router = express.Router();
@@ -50,43 +49,60 @@ router.post('/', authenticate, async (req, res) => {
let fileExtension;
let filename;
- // 生成文件名
- const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, '');
- const analysisTypeLabel = {
- 'bazi': '八字命理',
- 'ziwei': '紫微斗数',
- 'yijing': '易经占卜'
- }[analysisType];
+ // 生成文件名 - 格式:"分析类型_用户名_日期_时间"(使用分析记录创建时间)
+ // 优先使用分析记录的创建时间,如果没有则使用当前时间
+ let analysisDate;
+ if (analysisData.created_at) {
+ analysisDate = new Date(analysisData.created_at);
+ } else if (analysisData.basic_info?.created_at) {
+ analysisDate = new Date(analysisData.basic_info.created_at);
+ } else {
+ // 如果没有创建时间,使用当前时间作为备用
+ analysisDate = new Date();
+ }
- const baseFilename = `${analysisTypeLabel}_${userName || 'user'}_${timestamp}`;
+ const year = analysisDate.getFullYear();
+ const month = String(analysisDate.getMonth() + 1).padStart(2, '0');
+ const day = String(analysisDate.getDate()).padStart(2, '0');
+ const hour = String(analysisDate.getHours()).padStart(2, '0');
+ const minute = String(analysisDate.getMinutes()).padStart(2, '0');
+ const second = String(analysisDate.getSeconds()).padStart(2, '0');
+
+ const dateStr = `${year}-${month}-${day}`;
+ const timeStr = `${hour}${minute}${second}`;
+
+ // 分析类型映射
+ const analysisTypeMap = {
+ 'bazi': '八字命理',
+ 'ziwei': '紫微斗数',
+ 'yijing': '易经占卜'
+ };
+
+ const analysisTypeName = analysisTypeMap[analysisType] || analysisType;
+ const baseFilename = `${analysisTypeName}_${userName || 'user'}_${dateStr}_${timeStr}`;
+ // 文件名格式: 八字命理_午饭_2025-08-21_133105
try {
switch (format) {
case 'markdown':
- // 临时简单实现
- const markdownContent = `# ${analysisTypeLabel}分析报告\n\n**姓名:** ${userName || '用户'}\n**生成时间:** ${new Date().toLocaleString('zh-CN')}\n\n## 分析结果\n\n这是一个测试文件。\n\n---\n\n*本报告由神机阁AI命理分析平台生成*`;
- fileBuffer = Buffer.from(markdownContent, 'utf8');
+ fileBuffer = await generateMarkdown(analysisData, analysisType, userName);
contentType = 'text/markdown';
fileExtension = 'md';
filename = `${baseFilename}.md`;
break;
case 'pdf':
- // 临时返回HTML内容
- const htmlContent = `${analysisTypeLabel}分析报告${analysisTypeLabel}分析报告
姓名:${userName || '用户'}
生成时间:${new Date().toLocaleString('zh-CN')}
分析结果
这是一个测试文件。
`;
- fileBuffer = Buffer.from(htmlContent, 'utf8');
- contentType = 'text/html';
- fileExtension = 'html';
- filename = `${baseFilename}.html`;
+ fileBuffer = await generatePDF(analysisData, analysisType, userName);
+ contentType = 'application/pdf';
+ fileExtension = 'pdf';
+ filename = `${baseFilename}.pdf`;
break;
case 'png':
- // 临时返回SVG内容
- const svgContent = ``;
- fileBuffer = Buffer.from(svgContent, 'utf8');
- contentType = 'image/svg+xml';
- fileExtension = 'svg';
- filename = `${baseFilename}.svg`;
+ fileBuffer = await generatePNG(analysisData, analysisType, userName);
+ contentType = 'image/png';
+ fileExtension = 'png';
+ filename = `${baseFilename}.png`;
break;
}
} catch (generationError) {
diff --git a/server/services/generators/pdfGenerator.cjs b/server/services/generators/pdfGenerator.cjs
index 63ceafc..b68ecee 100644
--- a/server/services/generators/pdfGenerator.cjs
+++ b/server/services/generators/pdfGenerator.cjs
@@ -1,44 +1,177 @@
/**
* PDF格式生成器
* 将分析结果转换为PDF文档
- * 使用html-pdf库进行转换
+ * 使用puppeteer进行HTML到PDF的转换
*/
+const puppeteer = require('puppeteer');
+const { generateMarkdown } = require('./markdownGenerator.cjs');
+
const generatePDF = async (analysisData, analysisType, userName) => {
+ let browser;
try {
- // 生成HTML内容
- const htmlContent = generateHTML(analysisData, analysisType, userName);
+ // 生成Markdown内容
+ const markdownBuffer = await generateMarkdown(analysisData, analysisType, userName);
- // 由于html-pdf库需要额外安装,这里先返回HTML转PDF的占位符
- // 在实际部署时需要安装 html-pdf 或 puppeteer
+ // 将Buffer转换为字符串
+ const markdownString = Buffer.isBuffer(markdownBuffer) ? markdownBuffer.toString('utf8') : String(markdownBuffer);
- // 临时解决方案:返回HTML内容作为PDF(实际应该转换为PDF)
- const Buffer = require('buffer').Buffer;
- return Buffer.from(htmlContent, 'utf8');
+ // 将Markdown转换为HTML
+ const htmlContent = convertMarkdownToHTML(markdownString, analysisType, userName);
- // 正式实现应该是:
- // const pdf = require('html-pdf');
- // return new Promise((resolve, reject) => {
- // pdf.create(htmlContent, {
- // format: 'A4',
- // border: {
- // top: '0.5in',
- // right: '0.5in',
- // bottom: '0.5in',
- // left: '0.5in'
- // }
- // }).toBuffer((err, buffer) => {
- // if (err) reject(err);
- // else resolve(buffer);
- // });
- // });
+ // 启动puppeteer浏览器
+ browser = await puppeteer.launch({
+ headless: 'new',
+ args: [
+ '--no-sandbox',
+ '--disable-setuid-sandbox',
+ '--disable-dev-shm-usage',
+ '--disable-gpu',
+ '--no-first-run',
+ '--disable-extensions',
+ '--disable-plugins',
+ '--disable-images',
+ '--disable-javascript',
+ '--run-all-compositor-stages-before-draw',
+ '--disable-background-timer-throttling',
+ '--disable-renderer-backgrounding',
+ '--disable-backgrounding-occluded-windows',
+ '--disable-ipc-flooding-protection'
+ ],
+ timeout: 30000
+ });
+
+ const page = await browser.newPage();
+
+ // 设置页面内容
+ await page.setContent(htmlContent, {
+ waitUntil: 'networkidle0'
+ });
+
+ // 生成PDF
+ const pdfBuffer = await page.pdf({
+ format: 'A4',
+ margin: {
+ top: '20mm',
+ right: '15mm',
+ bottom: '20mm',
+ left: '15mm'
+ },
+ printBackground: true,
+ preferCSSPageSize: true
+ });
+
+ return pdfBuffer;
} catch (error) {
console.error('生成PDF失败:', error);
throw error;
+ } finally {
+ if (browser) {
+ await browser.close();
+ }
}
};
+/**
+ * 将Markdown内容转换为适合PDF的HTML
+ */
+const convertMarkdownToHTML = (markdownContent, analysisType, userName) => {
+ // 预处理:分离表格
+ const lines = markdownContent.split('\n');
+ let html = '';
+ let inTable = false;
+ let tableRows = [];
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+
+ // 检测表格开始
+ if (line.includes('|') && line.includes('---')) {
+ inTable = true;
+ // 添加表格头(前一行)
+ if (i > 0 && lines[i-1].includes('|')) {
+ const headerCells = lines[i-1].split('|').map(cell => cell.trim()).filter(cell => cell);
+ tableRows.push('' + headerCells.map(cell => `| ${cell} | `).join('') + '
');
+ }
+ continue;
+ }
+
+ // 处理表格行
+ if (inTable && line.includes('|')) {
+ const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell);
+ if (cells.length > 0) {
+ tableRows.push('' + cells.map(cell => `| ${cell} | `).join('') + '
');
+ }
+ continue;
+ }
+
+ // 表格结束
+ if (inTable && !line.includes('|')) {
+ html += '' + tableRows.join('') + '
\n';
+ tableRows = [];
+ inTable = false;
+ }
+
+ // 处理非表格行
+ if (!inTable) {
+ html += line + '\n';
+ }
+ }
+
+ // 处理未结束的表格
+ if (tableRows.length > 0) {
+ html += '' + tableRows.join('') + '
\n';
+ }
+
+ // Markdown到HTML转换
+ html = html
+ // 标题转换
+ .replace(/^# (.+)$/gm, '$1
')
+ .replace(/^## (.+)$/gm, '$1
')
+ .replace(/^### (.+)$/gm, '$1
')
+ .replace(/^#### (.+)$/gm, '$1
')
+ // 加粗文本
+ .replace(/\*\*(.+?)\*\*/g, '$1')
+ // 处理列表
+ .replace(/^- (.+)$/gm, '$1')
+ // 将连续的li包装在ul中
+ .replace(/(.*<\/li>\s*)+/gs, (match) => {
+ return '';
+ })
+ // 水平分割线
+ .replace(/^---$/gm, '
')
+ // 段落处理
+ .replace(/\n\s*\n/g, '
')
+ .replace(/^(?!<[h1-6]|
$1')
+ // 清理多余的p标签
+ .replace(/<\/p>/g, '')
+ .replace(/
(<[^>]+>)/g, '$1')
+ .replace(/(<\/[^>]+>)<\/p>/g, '$1')
+ // 换行处理
+ .replace(/\n/g, '');
+
+ // 包装在完整的HTML文档中
+ return `
+
+
+
+
+
+ ${getAnalysisTypeLabel(analysisType)}分析报告
+
+
+
+
+ ${html}
+
+
+
+ `;
+};
+
/**
* 生成HTML内容
*/
@@ -617,7 +750,156 @@ const getAnalysisTypeLabel = (analysisType) => {
};
/**
- * 获取CSS样式
+ * 获取PDF专用CSS样式
+ */
+const getPDFCSS = () => {
+ return `
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ body {
+ font-family: 'Microsoft YaHei', '微软雅黑', 'SimSun', '宋体', Arial, sans-serif;
+ line-height: 1.6;
+ color: #333;
+ background-color: white;
+ font-size: 12px;
+ }
+
+ .container {
+ max-width: 100%;
+ margin: 0;
+ padding: 0;
+ }
+
+ h1 {
+ font-size: 24px;
+ color: #2c3e50;
+ text-align: center;
+ margin: 20px 0;
+ padding: 15px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border-radius: 8px;
+ }
+
+ h2 {
+ font-size: 18px;
+ color: #34495e;
+ margin: 20px 0 10px 0;
+ padding: 10px 0;
+ border-bottom: 2px solid #3498db;
+ page-break-after: avoid;
+ }
+
+ h3 {
+ font-size: 16px;
+ color: #2980b9;
+ margin: 15px 0 8px 0;
+ padding-left: 10px;
+ border-left: 4px solid #3498db;
+ page-break-after: avoid;
+ }
+
+ h4 {
+ font-size: 14px;
+ color: #27ae60;
+ margin: 12px 0 6px 0;
+ page-break-after: avoid;
+ }
+
+ p {
+ margin: 8px 0;
+ line-height: 1.6;
+ text-align: justify;
+ }
+
+ strong {
+ color: #2c3e50;
+ font-weight: bold;
+ }
+
+ ul, ol {
+ margin: 10px 0;
+ padding-left: 20px;
+ }
+
+ li {
+ margin: 4px 0;
+ line-height: 1.5;
+ }
+
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 15px 0;
+ font-size: 11px;
+ page-break-inside: avoid;
+ }
+
+ th, td {
+ border: 1px solid #bdc3c7;
+ padding: 8px;
+ text-align: left;
+ }
+
+ th {
+ background-color: #ecf0f1;
+ font-weight: bold;
+ color: #2c3e50;
+ }
+
+ tr:nth-child(even) {
+ background-color: #f8f9fa;
+ }
+
+ .section {
+ margin: 20px 0;
+ page-break-inside: avoid;
+ }
+
+ .page-break {
+ page-break-before: always;
+ }
+
+ .no-break {
+ page-break-inside: avoid;
+ }
+
+ /* 打印优化 */
+ @page {
+ margin: 20mm 15mm;
+ size: A4;
+ }
+
+ @media print {
+ body {
+ font-size: 11px;
+ }
+
+ h1 {
+ font-size: 20px;
+ }
+
+ h2 {
+ font-size: 16px;
+ }
+
+ h3 {
+ font-size: 14px;
+ }
+
+ .section {
+ break-inside: avoid;
+ }
+ }
+ `;
+};
+
+/**
+ * 获取原有CSS样式(保持兼容性)
*/
const getCSS = () => {
return `
diff --git a/src/components/CompleteYijingAnalysis.tsx b/src/components/CompleteYijingAnalysis.tsx
index 03b0b00..ec28ae6 100644
--- a/src/components/CompleteYijingAnalysis.tsx
+++ b/src/components/CompleteYijingAnalysis.tsx
@@ -266,6 +266,16 @@ const CompleteYijingAnalysis: React.FC = ({
+ {/* 下载按钮 */}
+
+
+
+
{/* 标题和基本信息 */}
@@ -722,15 +732,7 @@ const CompleteYijingAnalysis: React.FC = ({
- {/* 下载按钮 */}
-
-
-
+
{/* 免责声明 */}
diff --git a/src/components/ui/DownloadButton.tsx b/src/components/ui/DownloadButton.tsx
index f58579f..4d31b50 100644
--- a/src/components/ui/DownloadButton.tsx
+++ b/src/components/ui/DownloadButton.tsx
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
+import { createPortal } from 'react-dom';
import { Download, FileText, FileImage, File, Loader2, ChevronDown } from 'lucide-react';
import { ChineseButton } from './ChineseButton';
import { cn } from '../../lib/utils';
@@ -111,7 +112,28 @@ const DownloadButton: React.FC = ({
// 获取文件名(从响应头或生成默认名称)
const contentDisposition = response.headers.get('Content-Disposition');
- let filename = `${getAnalysisTypeLabel()}_${userName || 'user'}_${new Date().toISOString().slice(0, 10)}.${format === 'markdown' ? 'md' : format}`;
+ // 生成与后端一致的文件名格式:分析类型_用户名_日期_时间(使用分析记录创建时间)
+ // 优先使用分析记录的创建时间,如果没有则使用当前时间
+ let analysisDate;
+ if (analysisData.created_at) {
+ analysisDate = new Date(analysisData.created_at);
+ } else if (analysisData.basic_info?.created_at) {
+ analysisDate = new Date(analysisData.basic_info.created_at);
+ } else {
+ // 如果没有创建时间,使用当前时间作为备用
+ analysisDate = new Date();
+ }
+
+ const year = analysisDate.getFullYear();
+ const month = String(analysisDate.getMonth() + 1).padStart(2, '0');
+ const day = String(analysisDate.getDate()).padStart(2, '0');
+ const hour = String(analysisDate.getHours()).padStart(2, '0');
+ const minute = String(analysisDate.getMinutes()).padStart(2, '0');
+ const second = String(analysisDate.getSeconds()).padStart(2, '0');
+
+ const dateStr = `${year}-${month}-${day}`;
+ const timeStr = `${hour}${minute}${second}`;
+ let filename = `${getAnalysisTypeLabel()}_${userName || 'user'}_${dateStr}_${timeStr}.${format === 'markdown' ? 'md' : format}`;
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=(['"]?)([^'"\n]*?)\1/);
@@ -182,89 +204,91 @@ const DownloadButton: React.FC = ({
className="flex items-center space-x-2 bg-gradient-to-r from-yellow-500 to-yellow-600 hover:from-yellow-600 hover:to-yellow-700 text-white border-0 shadow-lg"
>
{isDownloading ? (
-
+
) : (
-
+
)}
-
- {isDownloading ? `正在生成${getFormatLabel(downloadingFormat!)}...` : '下载分析结果'}
+
+ {isDownloading ? `正在生成${getFormatLabel(downloadingFormat!)}...` : '下载'}
- {/* 下拉菜单 */}
- {showDropdown && (
-
-
-
选择下载格式
-
{getAnalysisTypeLabel()}分析结果
-
+ {/* 使用Portal渲染弹出层到body,脱离父容器限制 */}
+ {showDropdown && createPortal(
+ <>
+ {/* 背景遮罩 */}
+
setShowDropdown(false)}
+ />
-
- {formatOptions.map((option) => {
- const Icon = option.icon;
- const isCurrentlyDownloading = isDownloading && downloadingFormat === option.format;
-
- return (
-
-
+
handleViewReading(reading)}
+ className="px-3 sm:px-6 text-xs sm:text-sm"
>
-
- 查看
+
+ 查看
+
handleDeleteReading(reading.id)}
- className="text-red-600 hover:text-red-700 hover:bg-red-50"
+ className="text-red-600 hover:text-red-700 hover:bg-red-50 px-2 sm:px-3"
>
-
+