From c522b772f66d0baf3e00c5aa8484daf445432204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Tue, 31 Mar 2026 21:04:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E5=A4=9A=E4=BD=99=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E5=92=8C=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin-frontend/index.html | 359 ----- admin-frontend/package-lock.json | 1957 ------------------------ admin-frontend/package.json | 19 - admin-frontend/src/App.jsx | 105 -- admin-frontend/src/index.css | 25 - admin-frontend/src/main.jsx | 10 - admin-frontend/src/pages/Users.jsx | 207 --- admin-frontend/vite.config.js | 17 - admin-html/index.html | 85 - admin-stable.html | 136 -- backend_old/.dockerignore | 9 - backend_old/.env.example | 26 - backend_old/Dockerfile | 25 - backend_old/README.md | 42 - backend_old/app/core/__init__.py | 0 backend_old/app/core/auth.py | 95 -- backend_old/app/core/database.py | 32 - backend_old/app/core/error_handler.py | 174 --- backend_old/app/core/logging_config.py | 113 -- backend_old/app/main.py | 103 -- backend_old/app/middleware/logging.py | 88 -- backend_old/app/models/__init__.py | 0 backend_old/app/models/models.py | 131 -- backend_old/app/routers/__init__.py | 0 backend_old/app/routers/auth.py | 167 -- backend_old/app/routers/collections.py | 685 --------- backend_old/app/routers/ocr.py | 376 ----- backend_old/app/routers/operations.py | 104 -- backend_old/app/routers/users.py | 225 --- backend_old/app/schemas/__init__.py | 0 backend_old/app/schemas/schemas.py | 212 --- backend_old/app/services/oss.py | 126 -- backend_old/app/services/sms.py | 106 -- backend_old/requirements.txt | 13 - backend_old/uploads/.gitkeep | 0 frontend/package_new.json | 0 frontend/src/test.js | 1 - start.sh | 12 - user_agreement.html | 115 -- 39 files changed, 5900 deletions(-) delete mode 100644 admin-frontend/index.html delete mode 100644 admin-frontend/package-lock.json delete mode 100644 admin-frontend/package.json delete mode 100644 admin-frontend/src/App.jsx delete mode 100644 admin-frontend/src/index.css delete mode 100644 admin-frontend/src/main.jsx delete mode 100644 admin-frontend/src/pages/Users.jsx delete mode 100644 admin-frontend/vite.config.js delete mode 100644 admin-html/index.html delete mode 100644 admin-stable.html delete mode 100644 backend_old/.dockerignore delete mode 100644 backend_old/.env.example delete mode 100644 backend_old/Dockerfile delete mode 100644 backend_old/README.md delete mode 100644 backend_old/app/core/__init__.py delete mode 100644 backend_old/app/core/auth.py delete mode 100644 backend_old/app/core/database.py delete mode 100644 backend_old/app/core/error_handler.py delete mode 100644 backend_old/app/core/logging_config.py delete mode 100644 backend_old/app/main.py delete mode 100644 backend_old/app/middleware/logging.py delete mode 100644 backend_old/app/models/__init__.py delete mode 100644 backend_old/app/models/models.py delete mode 100644 backend_old/app/routers/__init__.py delete mode 100644 backend_old/app/routers/auth.py delete mode 100644 backend_old/app/routers/collections.py delete mode 100644 backend_old/app/routers/ocr.py delete mode 100644 backend_old/app/routers/operations.py delete mode 100644 backend_old/app/routers/users.py delete mode 100644 backend_old/app/schemas/__init__.py delete mode 100644 backend_old/app/schemas/schemas.py delete mode 100644 backend_old/app/services/oss.py delete mode 100644 backend_old/app/services/sms.py delete mode 100644 backend_old/requirements.txt delete mode 100644 backend_old/uploads/.gitkeep delete mode 100644 frontend/package_new.json delete mode 100644 frontend/src/test.js delete mode 100755 start.sh delete mode 100644 user_agreement.html diff --git a/admin-frontend/index.html b/admin-frontend/index.html deleted file mode 100644 index d9119d2..0000000 --- a/admin-frontend/index.html +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - 甲辰管理后台 - - - - - - -
- - - diff --git a/admin-frontend/package-lock.json b/admin-frontend/package-lock.json deleted file mode 100644 index c07543d..0000000 --- a/admin-frontend/package-lock.json +++ /dev/null @@ -1,1957 +0,0 @@ -{ - "name": "admin-standalone", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "admin-standalone", - "version": "1.0.0", - "dependencies": { - "axios": "^1.6.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.2.0", - "vite": "^5.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", - "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", - "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", - "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", - "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", - "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", - "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", - "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", - "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", - "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", - "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", - "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", - "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", - "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", - "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", - "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", - "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", - "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", - "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", - "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", - "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", - "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", - "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", - "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", - "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", - "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.6", - "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz", - "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.10", - "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", - "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001781", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", - "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.325", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz", - "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==", - "dev": true, - "license": "ISC" - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.60.0", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.0.tgz", - "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.0", - "@rollup/rollup-android-arm64": "4.60.0", - "@rollup/rollup-darwin-arm64": "4.60.0", - "@rollup/rollup-darwin-x64": "4.60.0", - "@rollup/rollup-freebsd-arm64": "4.60.0", - "@rollup/rollup-freebsd-x64": "4.60.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", - "@rollup/rollup-linux-arm-musleabihf": "4.60.0", - "@rollup/rollup-linux-arm64-gnu": "4.60.0", - "@rollup/rollup-linux-arm64-musl": "4.60.0", - "@rollup/rollup-linux-loong64-gnu": "4.60.0", - "@rollup/rollup-linux-loong64-musl": "4.60.0", - "@rollup/rollup-linux-ppc64-gnu": "4.60.0", - "@rollup/rollup-linux-ppc64-musl": "4.60.0", - "@rollup/rollup-linux-riscv64-gnu": "4.60.0", - "@rollup/rollup-linux-riscv64-musl": "4.60.0", - "@rollup/rollup-linux-s390x-gnu": "4.60.0", - "@rollup/rollup-linux-x64-gnu": "4.60.0", - "@rollup/rollup-linux-x64-musl": "4.60.0", - "@rollup/rollup-openbsd-x64": "4.60.0", - "@rollup/rollup-openharmony-arm64": "4.60.0", - "@rollup/rollup-win32-arm64-msvc": "4.60.0", - "@rollup/rollup-win32-ia32-msvc": "4.60.0", - "@rollup/rollup-win32-x64-gnu": "4.60.0", - "@rollup/rollup-win32-x64-msvc": "4.60.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - } - } -} diff --git a/admin-frontend/package.json b/admin-frontend/package.json deleted file mode 100644 index b106b57..0000000 --- a/admin-frontend/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "admin-standalone", - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "axios": "^1.6.0" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.2.0", - "vite": "^5.0.0" - } -} \ No newline at end of file diff --git a/admin-frontend/src/App.jsx b/admin-frontend/src/App.jsx deleted file mode 100644 index 28d4358..0000000 --- a/admin-frontend/src/App.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { useState, useEffect } from 'react' -import axios from 'axios' -import Users from './pages/Users' - -const API_BASE = '' - -// 登录页面 -function Login({ onLogin }) { - const [username, setUsername] = useState('') - const [password, setPassword] = useState('') - const [error, setError] = useState('') - const [loading, setLoading] = useState(false) - - const handleLogin = async () => { - if (!username || !password) { setError('请输入用户名和密码'); return } - setLoading(true); setError('') - try { - const res = await axios.post(`${API_BASE}/api/auth/login`, - new URLSearchParams({ username, password }), - { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } - ) - if (res.data.access_token) { - localStorage.setItem('adminToken', res.data.access_token) - localStorage.setItem('adminUser', JSON.stringify(res.data)) - onLogin(res.data) - } else { setError(res.data.detail || '登录失败') } - } catch (e) { setError('网络错误') } - setLoading(false) - } - - return ( -
-
-

甲辰管理后台

- setUsername(e.target.value)} - style={{ width: '100%', padding: '14px 16px', marginBottom: '16px', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.2)', background: 'rgba(0,0,0,0.3)', color: '#fff', fontSize: '15px' }} /> - setPassword(e.target.value)} onKeyPress={e => e.key === 'Enter' && handleLogin()} - style={{ width: '100%', padding: '14px 16px', marginBottom: '20px', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.2)', background: 'rgba(0,0,0,0.3)', color: '#fff', fontSize: '15px' }} /> - {error &&
{error}
} - -
-
- ) -} - -export default function App() { - const [user, setUser] = useState(null) - const [activeTab, setActiveTab] = useState('users') - - useEffect(() => { - const token = localStorage.getItem('adminToken') - const userStr = localStorage.getItem('adminUser') - if (token && userStr) { - try { setUser(JSON.parse(userStr)) } catch (e) {} - } - }, []) - - const handleLogout = () => { - localStorage.removeItem('adminToken') - localStorage.removeItem('adminUser') - setUser(null) - } - - if (!user) return - - const tabs = [ - { key: 'users', label: '👥 用户管理', icon: '👥' }, - { key: 'stats', label: '📊 统计分析', icon: '📊' }, - { key: 'info', label: '📝 资讯管理', icon: '📝' }, - ] - - return ( -
- {/* 顶部栏 */} -
-

甲辰管理后台

-
- {user.username || user.name || '管理员'} - -
-
- - {/* 主内容 */} -
- {activeTab === 'users' && } - {activeTab === 'stats' &&
统计分析功能开发中...
} - {activeTab === 'info' &&
资讯管理功能开发中...
} -
- - {/* 底部导航 */} -
- {tabs.map(tab => ( -
setActiveTab(tab.key)} - style={{ padding: '12px 20px', borderRadius: '10px', background: activeTab === tab.key ? '#3b82f6' : 'transparent', - color: activeTab === tab.key ? '#fff' : '#94a3b8', cursor: 'pointer', fontSize: '14px', fontWeight: '500' }}> - {tab.label} -
- ))} -
-
- ) -} diff --git a/admin-frontend/src/index.css b/admin-frontend/src/index.css deleted file mode 100644 index ba1a7ca..0000000 --- a/admin-frontend/src/index.css +++ /dev/null @@ -1,25 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} -html, body, #root { - min-height: 100vh; - width: 100%; -} -body { - font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif; - background: #1a1a2e; - color: #fff; -} -input, textarea, select, button { - font-family: inherit; -} -::-webkit-scrollbar { - width: 6px; - height: 6px; -} -::-webkit-scrollbar-thumb { - background: rgba(255,255,255,0.2); - border-radius: 3px; -} \ No newline at end of file diff --git a/admin-frontend/src/main.jsx b/admin-frontend/src/main.jsx deleted file mode 100644 index 54ebda4..0000000 --- a/admin-frontend/src/main.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' - -ReactDOM.createRoot(document.getElementById('root')).render( - - - -) \ No newline at end of file diff --git a/admin-frontend/src/pages/Users.jsx b/admin-frontend/src/pages/Users.jsx deleted file mode 100644 index 3075d7f..0000000 --- a/admin-frontend/src/pages/Users.jsx +++ /dev/null @@ -1,207 +0,0 @@ -import React, { useState, useEffect } from 'react' - -export default function Users() { - const [users, setUsers] = useState([]) - const [loading, setLoading] = useState(true) - const [error, setError] = useState('') - const [showAddModal, setShowAddModal] = useState(false) - const [editingUser, setEditingUser] = useState(null) - const [newUser, setNewUser] = useState({ username: '', password: '', email: '', role: 'user' }) - - const API_BASE = localStorage.getItem('API_BASE') || '' - - useEffect(() => { fetchUsers() }, []) - - const fetchUsers = async () => { - try { - const token = localStorage.getItem('adminToken') - const res = await fetch(`${API_BASE}/api/admin/users?page=1&limit=100`, { - headers: { 'Authorization': `Bearer ${token}` } - }) - if (!res.ok) throw new Error('获取用户列表失败') - const data = await res.json() - setUsers(data) - } catch (err) { - setError(err.message) - } finally { - setLoading(false) - } - } - - const handleDeleteUser = async (userId, username) => { - if (!confirm(`确定要删除用户 "${username}" 吗?`)) return - try { - const token = localStorage.getItem('adminToken') - const res = await fetch(`${API_BASE}/api/admin/users/${userId}`, { - method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } - }) - if (!res.ok) throw new Error('删除失败') - fetchUsers() - } catch (err) { alert(err.message) } - } - - const handleAddUser = async () => { - if (!newUser.username || !newUser.password) { alert('用户名和密码为必填项'); return } - const payload = { ...newUser } - if (!payload.email) delete payload.email - try { - const token = localStorage.getItem('adminToken') - const res = await fetch(`${API_BASE}/api/auth/register`, { - method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, - body: JSON.stringify(payload) - }) - if (!res.ok) throw new Error('添加失败') - alert('添加成功!') - setShowAddModal(false) - setNewUser({ username: '', password: '', email: '', role: 'user' }) - fetchUsers() - } catch (err) { alert(err.message) } - } - - const handleEditUser = async () => { - if (!editingUser.username) { alert('用户名不能为空'); return } - if (editingUser.newPassword || editingUser.confirmPassword) { - if (!editingUser.newPassword || !editingUser.confirmPassword) { alert('请填写完整密码信息'); return } - if (editingUser.newPassword !== editingUser.confirmPassword) { alert('两次输入的密码不一致'); return } - if (editingUser.newPassword.length < 6) { alert('密码至少 6 个字符'); return } - } - try { - const token = localStorage.getItem('adminToken') - const updateData = { username: editingUser.username, email: editingUser.email, role: editingUser.role } - if (editingUser.newPassword && editingUser.newPassword.trim()) updateData.password = editingUser.newPassword - const res = await fetch(`${API_BASE}/api/admin/users/${editingUser.id}`, { - method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, - body: JSON.stringify(updateData) - }) - if (!res.ok) throw new Error('更新失败') - alert('更新成功!') - setEditingUser(null) - fetchUsers() - } catch (err) { alert(err.message) } - } - - if (loading) return
加载中...
- if (error) return
⚠️ {error}
- - return ( -
-
-

⚙️ 用户管理

- -
- - {/* 用户列表 */} -
- {users.map(user => ( -
-
-
-
-
{user.username}
-
- {user.role === 'admin' ? '👑 管理员' : user.role === 'editor' ? '📝 信息员' : '👤 用户'} -
-
-
📧 {user.email || '未设置'} | 📱 {user.phone || '未设置'}
-
-
-
藏品数
-
{user.collectionCount || 0}
-
-
-
-
注册时间:{user.created_at ? new Date(user.created_at).toLocaleDateString('zh-CN') : '-'}
-
- - {user.role !== 'admin' && ( - - )} -
-
-
- ))} -
- - {users.length === 0 &&
暂无用户数据
} - - {/* 添加用户模态框 */} - {showAddModal && ( -
-
-

添加用户

-
- - setNewUser({ ...newUser, username: e.target.value })} style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} /> -
-
- - setNewUser({ ...newUser, password: e.target.value })} style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} /> -
-
- - setNewUser({ ...newUser, email: e.target.value })} style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} /> -
-
- - -
-
- - -
-
-
- )} - - {/* 编辑用户模态框 */} - {editingUser && ( -
-
-

编辑用户

- -
-

📋 基本信息

-
- - setEditingUser({ ...editingUser, username: e.target.value })} style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} /> -
-
- - setEditingUser({ ...editingUser, email: e.target.value })} style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} /> -
-
- - -
-
- -
-

🔐 修改密码(可选)

-
- - setEditingUser({ ...editingUser, newPassword: e.target.value })} placeholder="请输入新密码(至少 6 位)" style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} /> -
-
- - setEditingUser({ ...editingUser, confirmPassword: e.target.value })} placeholder="请再次输入新密码" style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} /> -
-
- -
- - -
-
-
- )} -
- ) -} diff --git a/admin-frontend/vite.config.js b/admin-frontend/vite.config.js deleted file mode 100644 index f179c36..0000000 --- a/admin-frontend/vite.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -export default defineConfig({ - plugins: [react()], - build: { - rollupOptions: { - output: { - manualChunks: undefined - } - } - }, - server: { - port: 3001, - host: '0.0.0.0' - } -}) diff --git a/admin-html/index.html b/admin-html/index.html deleted file mode 100644 index d7a8509..0000000 --- a/admin-html/index.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - 甲辰管理后台 - - - - - - -
- - - diff --git a/admin-stable.html b/admin-stable.html deleted file mode 100644 index b777e68..0000000 --- a/admin-stable.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - 甲辰管理后台 - - - - - - -
- - - diff --git a/backend_old/.dockerignore b/backend_old/.dockerignore deleted file mode 100644 index 73f416f..0000000 --- a/backend_old/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -__pycache__/ -*.pyc -*.pyo -.git -.env -uploads/* -!uploads/.gitkeep -logs/* -*.log diff --git a/backend_old/.env.example b/backend_old/.env.example deleted file mode 100644 index 9f5443e..0000000 --- a/backend_old/.env.example +++ /dev/null @@ -1,26 +0,0 @@ -# 数据库配置 -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/zodiac - -# JWT配置 -SECRET_KEY=your-production-secret-key-change-this -ACCESS_TOKEN_EXPIRE_MINUTES=60 - -# 阿里云 DashScope OCR API(必须配置!) -DASHSCOPE_API_KEY=sk-9389024a37da4f7bb455ac9a6b28776f - -# 阿里云 OSS配置 -OSS_ACCESS_KEY_ID=LTAI5t6HUnpFBLEK9194kPVG -OSS_ACCESS_KEY_SECRET=LEr4Q8yRxb8D5b24cKfCwlt4MMoke1 -OSS_BUCKET_NAME=jiachenlong-oss -OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com -OSS_PUBLIC_URL=https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com - -# 阿里云短信配置(必须配置!) -SMS_ACCESS_KEY_ID=LTAI5tQAx5niD7JQVqGE5acE -SMS_ACCESS_KEY_SECRET=QsQFAEKBkaNynIoKyvdIi3BUyWVZu1 -SMS_SIGN_NAME=苏州算力 -SMS_TEMPLATE_CODE=SMS_501590956 - -# 服务配置 -PORT=3000 -HOST=0.0.0.0 diff --git a/backend_old/Dockerfile b/backend_old/Dockerfile deleted file mode 100644 index f4c2a67..0000000 --- a/backend_old/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# FastAPI 后端 Docker 镜像 -FROM python:3.12-slim - -WORKDIR /app - -# 安装系统依赖 -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# 安装 Python 依赖 -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# 复制应用代码 -COPY . . - -# 创建上传目录 -RUN mkdir -p uploads - -# 暴露端口 -EXPOSE 3000 - -# 启动命令 -CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "3000"] diff --git a/backend_old/README.md b/backend_old/README.md deleted file mode 100644 index 834be5c..0000000 --- a/backend_old/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# 后端服务 - FastAPI - -## 启动方式 - -### 开发环境 - -```bash -# 安装依赖 -pip install -r requirements.txt - -# 启动服务 -python -m uvicorn app.main:app --port 3000 --host 0.0.0.0 --reload -``` - -### 生产环境 - -```bash -# 后台运行 -nohup python -m uvicorn app.main:app --port 3000 --host 0.0.0.0 > /tmp/uvicorn.log 2>&1 & - -# 或使用 systemd -sudo systemctl start zodiac-backend -``` - -## 配置说明 - -编辑 `.env` 文件: - -```ini -# 数据库 -DATABASE_URL=postgresql://postgres:密码@localhost:5432/zodiac - -# JWT -SECRET_KEY=你的密钥 - -# OCR API -DASHSCOPE_API_KEY=sk-你的密钥 -``` - -## API 文档 - -启动后访问:http://localhost:3000/docs diff --git a/backend_old/app/core/__init__.py b/backend_old/app/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend_old/app/core/auth.py b/backend_old/app/core/auth.py deleted file mode 100644 index 3702b7e..0000000 --- a/backend_old/app/core/auth.py +++ /dev/null @@ -1,95 +0,0 @@ -# 认证模块 -import os -import bcrypt -from datetime import datetime, timedelta -from typing import Optional -from fastapi import Depends, HTTPException, status -from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials -from jose import JWTError, jwt -from sqlalchemy.orm import Session -from app.core.database import SessionLocal -from app.models.models import User - -# 配置 -SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-in-production") -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60")) - -# HTTP Bearer 认证 -security = HTTPBearer(auto_error=False) - - -def verify_password(plain_password: str, hashed_password: str) -> bool: - """验证密码""" - try: - return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8')) - except Exception: - return False - - -def get_password_hash(password: str) -> str: - """生成密码哈希""" - return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') - - -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: - """创建访问令牌""" - to_encode = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -def decode_access_token(token: str) -> Optional[dict]: - """解码访问令牌""" - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - return payload - except JWTError: - return None - - -def get_current_user( - credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), - db: Session = Depends(lambda: SessionLocal()) -) -> User: - """获取当前用户""" - if not credentials: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="未提供认证信息", - headers={"WWW-Authenticate": "Bearer"}, - ) - - token = credentials.credentials - payload = decode_access_token(token) - - if not payload: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="无效的令牌", - headers={"WWW-Authenticate": "Bearer"}, - ) - - user_id: str = payload.get("sub") - if not user_id: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="无效的令牌", - headers={"WWW-Authenticate": "Bearer"}, - ) - - user = db.query(User).filter(User.f99_90_id == user_id).first() - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="用户不存在", - headers={"WWW-Authenticate": "Bearer"}, - ) - - return user diff --git a/backend_old/app/core/database.py b/backend_old/app/core/database.py deleted file mode 100644 index 789c23c..0000000 --- a/backend_old/app/core/database.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -# 支持 MySQL, PostgreSQL -DATABASE_URL = os.getenv( - "DATABASE_URL", - "postgresql://postgres:postgres@localhost:5432/zodiac" -) - -# 数据库引擎配置 -engine = create_engine( - DATABASE_URL, - pool_pre_ping=True, - pool_size=10, - max_overflow=20, - echo=False -) - -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() - - -def get_db(): - """获取数据库会话""" - db = SessionLocal(expire_on_commit=False) - try: - yield db - finally: - db.close() diff --git a/backend_old/app/core/error_handler.py b/backend_old/app/core/error_handler.py deleted file mode 100644 index c3d29ca..0000000 --- a/backend_old/app/core/error_handler.py +++ /dev/null @@ -1,174 +0,0 @@ -# 统一错误处理 -from fastapi import FastAPI, Request, HTTPException, status -from fastapi.responses import JSONResponse -from fastapi.exceptions import RequestValidationError -from pydantic import ValidationError -from app.core.logging_config import logger - -# 错误码定义 -ERROR_CODES = { - # 认证错误 (10-19) - 401: "E00010", # 未授权 - 403: "E00014", # 禁止访问 - - # 验证错误 (20-29) - 400: "E00000", # 请求错误 - 422: "E00000", # 验证错误 - - # 资源错误 (30-39) - 404: "E00033", # 资源不存在 - - # 服务器错误 (50-59) - 500: "E00003", # 服务器内部错误 -} - -# 错误信息映射 -ERROR_MESSAGES = { - "E00010": "未登录或登录已过期", - "E00011": "用户名或密码错误", - "E00012": "验证码错误", - "E00014": "无权访问此资源", - "E00015": "令牌无效或已过期", - "E00020": "请输入用户名和密码", - "E00021": "用户名至少 3 个字符", - "E00022": "密码至少 6 个字符", - "E00023": "用户名已存在", - "E00024": "邮箱已被注册", - "E00030": "藏品名称不能为空", - "E00031": "藏品名称至少 2 个字符", - "E00032": "藏品分类不能为空", - "E00033": "藏品不存在", - "E00034": "禁止重复:此冠字号已存在", - "E00035": "成本价格必须>=0", - "E00036": "目标价格必须>=0", - "E00037": "发行年份必须是 4 位数字", - "E00040": "请选择图片文件", - "E00041": "图片尺寸太小,无法识别", - "E00042": "OCR 识别失败,请重试", - "E00050": "仅管理员可访问", - "E00051": "用户不存在", - "E00000": "请求失败", - "E00001": "网络连接失败", - "E00003": "服务器内部错误", -} - -def setup_error_handlers(app: FastAPI): - """设置全局错误处理器""" - - @app.exception_handler(HTTPException) - async def http_exception_handler(request: Request, exc: HTTPException): - """处理 HTTP 异常""" - # 从 detail 中提取错误码 - detail = exc.detail - error_code = ERROR_CODES.get(exc.status_code, "E00000") - - # 如果 detail 已经包含错误码,直接使用 - if isinstance(detail, str) and detail.startswith("E"): - parts = detail.split(":", 1) - error_code = parts[0] - message = parts[1].strip() if len(parts) > 1 else ERROR_MESSAGES.get(error_code, detail) - else: - message = ERROR_MESSAGES.get(error_code, detail if isinstance(detail, str) else "请求失败") - - return JSONResponse( - status_code=exc.status_code, - content={ - "error": { - "code": error_code, - "message": message, - "status": exc.status_code - } - } - ) - - @app.exception_handler(RequestValidationError) - async def validation_exception_handler(request: Request, exc: RequestValidationError): - """处理请求验证错误""" - errors = exc.errors() - if errors: - error = errors[0] - field = ".".join(str(x) for x in error.get("loc", [])) - msg = error.get("msg", "验证失败") - - # 根据字段和消息匹配错误码 - # 先检查请求路径,区分用户接口和藏品接口 - path = request.url.path - - if "cost_price" in field or "价格" in msg: - error_code = "E00035" - message = "成本价格必须>=0" - elif "target_price" in field: - error_code = "E00036" - message = "目标价格必须>=0" - elif "issue_year" in field or "年份" in msg: - error_code = "E00037" - message = "发行年份必须是 4 位数字" - elif "name" in field: - # 根据路径区分用户 name 和藏品 name - if "/auth/" in path or "/users/" in path or "/admin/users/" in path: - error_code = "E00021" - message = "用户名至少 3 个字符" - else: - error_code = "E00031" - message = "藏品名称至少 2 个字符" - elif "category" in field: - error_code = "E00032" - message = "藏品分类不能为空" - else: - error_code = "E00000" - message = f"{field}: {msg}" - - return JSONResponse( - status_code=422, - content={ - "error": { - "code": error_code, - "message": message, - "status": 422, - "field": field - } - } - ) - - return JSONResponse( - status_code=422, - content={ - "error": { - "code": "E00000", - "message": "验证失败", - "status": 422 - } - } - ) - - @app.exception_handler(404) - async def not_found_handler(request: Request, exc: Exception): - """处理 404 错误""" - return JSONResponse( - status_code=404, - content={ - "error": { - "code": "E00033", - "message": "接口不存在", - "status": 404 - } - } - ) - - @app.exception_handler(Exception) - async def general_exception_handler(request: Request, exc: Exception): - """处理未捕获的异常""" - import traceback - error_trace = traceback.format_exc() - logger.error(f"未捕获异常:{str(exc)}\n{error_trace}") - - return JSONResponse( - status_code=500, - content={ - "error": { - "code": "E00003", - "message": "服务器内部错误", - "status": 500 - } - } - ) diff --git a/backend_old/app/core/logging_config.py b/backend_old/app/core/logging_config.py deleted file mode 100644 index d5b2960..0000000 --- a/backend_old/app/core/logging_config.py +++ /dev/null @@ -1,113 +0,0 @@ -# 日志系统配置 -import logging -import json -import os -from datetime import datetime -from typing import Optional -import uuid -from logging.handlers import RotatingFileHandler - -class JSONFormatter(logging.Formatter): - """JSON 格式日志处理器""" - - def format(self, record): - log_data = { - 'timestamp': datetime.utcnow().isoformat() + 'Z', - 'level': record.levelname, - 'logger': record.name, - 'message': record.getMessage(), - 'trace_id': getattr(record, 'trace_id', str(uuid.uuid4())), - 'user_id': getattr(record, 'user_id', None), - 'request_id': getattr(record, 'request_id', str(uuid.uuid4())), - 'ip_address': getattr(record, 'ip_address', None), - 'duration_ms': getattr(record, 'duration_ms', None), - } - - # 添加额外字段 - if hasattr(record, 'data'): - log_data['data'] = record.data - - # 添加异常信息 - if record.exc_info: - log_data['exception'] = self.formatException(record.exc_info) - - return json.dumps(log_data, ensure_ascii=False, default=str) - - -def setup_logging( - log_file: str = 'logs/app.log', - level: str = 'INFO', - max_bytes: int = 10*1024*1024, # 10MB - backup_count: int = 5 -): - """配置日志系统""" - - # 创建日志目录 - os.makedirs(os.path.dirname(log_file), exist_ok=True) - - # 根日志器 - logger = logging.getLogger() - logger.setLevel(level) - - # 清空现有处理器 - logger.handlers.clear() - - # 文件处理器(带轮转) - file_handler = RotatingFileHandler( - log_file, - maxBytes=max_bytes, - backupCount=backup_count, - encoding='utf-8' - ) - file_handler.setFormatter(JSONFormatter()) - logger.addHandler(file_handler) - - # 控制台处理器(仅开发环境) - if os.getenv('ENV', 'development') == 'development': - console_handler = logging.StreamHandler() - console_handler.setFormatter(JSONFormatter()) - logger.addHandler(console_handler) - - return logger - - -# 创建日志器实例 -logger = setup_logging() - - -# 日志装饰器 -def log_operation(operation_name: str): - """记录操作日志的装饰器""" - def decorator(func): - import functools - @functools.wraps(func) - def wrapper(*args, **kwargs): - import time - start_time = time.time() - - try: - result = func(*args, **kwargs) - duration = (time.time() - start_time) * 1000 - - logger.info( - f"{operation_name} 成功", - extra={ - 'duration_ms': duration, - 'data': {'function': func.__name__} - } - ) - return result - - except Exception as e: - duration = (time.time() - start_time) * 1000 - logger.error( - f"{operation_name} 失败:{str(e)}", - extra={ - 'duration_ms': duration, - 'data': {'function': func.__name__, 'error': str(e)} - }, - exc_info=True - ) - raise - return wrapper - return decorator diff --git a/backend_old/app/main.py b/backend_old/app/main.py deleted file mode 100644 index 3dc746d..0000000 --- a/backend_old/app/main.py +++ /dev/null @@ -1,103 +0,0 @@ -# FastAPI 应用入口 -import os -from pathlib import Path -from dotenv import load_dotenv -load_dotenv() - -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from fastapi.staticfiles import StaticFiles - -from app.core.database import engine, Base -from app.core.logging_config import logger, setup_logging -from app.core.error_handler import setup_error_handlers -from app.middleware.logging import logging_middleware -from app.routers import auth, collections, operations -from app.routers import ocr as ocr_router -from app.routers import users as users_router - -# 版本信息 - 从 config/VERSION 文件读取 -def get_version(): - """从 config/VERSION 文件读取版本号""" - try: - version_file = Path(__file__).parent.parent.parent / "config" / "VERSION" - if version_file.exists(): - with open(version_file, 'r', encoding='utf-8') as f: - for line in f: - if line.startswith('VERSION='): - return line.strip().split('=', 1)[1] - except Exception as e: - logger.error(f"读取 VERSION 文件失败:{e}") - return "0.0.0" # 默认版本号 - -__version__ = get_version() -__app_name__ = "甲辰收藏系统 FastAPI 后端" - -# 启动时创建数据库表 -Base.metadata.create_all(bind=engine) - -# 初始化日志系统 -setup_logging() -logger.info(f"{__app_name__} v{__version__} 启动成功") - -# 创建 FastAPI 应用 -app = FastAPI( - title=__app_name__, - version=__version__, - description="生肖纪念钞收藏管理系统后端 API" -) - -# 设置全局错误处理器 -setup_error_handlers(app) - -# CORS 配置 -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # 生产环境应该限制域名 - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# 挂载静态文件目录(图片上传和项目静态资源) -uploads_dir = "uploads" -os.makedirs(uploads_dir, exist_ok=True) -app.mount("/uploads", StaticFiles(directory=uploads_dir), name="uploads") - -# 挂载项目静态资源目录(可选,生产环境建议用 Nginx) -# static_dir = Path(__file__).parent.parent.parent / "static" -# if static_dir.exists(): -# app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") - -# 添加日志中间件 -app.middleware("http")(logging_middleware) - -# 注册路由 -app.include_router(auth.router) -app.include_router(collections.router) -app.include_router(operations.router) -app.include_router(ocr_router.router) # OCR 识别 -app.include_router(users_router.router) # 当前用户接口 -app.include_router(users_router.admin_router) # 管理员用户管理 - - -@app.get("/") -def root(): - """根路径""" - return { - "name": __app_name__, - "version": __version__, - "status": "running" - } - - -@app.get("/health") -def health_check(): - """健康检查""" - return {"status": "healthy"} - - -if __name__ == "__main__": - import uvicorn - port = int(os.getenv("PORT", "3000")) - uvicorn.run(app, host="0.0.0.0", port=port) diff --git a/backend_old/app/middleware/logging.py b/backend_old/app/middleware/logging.py deleted file mode 100644 index 684a55a..0000000 --- a/backend_old/app/middleware/logging.py +++ /dev/null @@ -1,88 +0,0 @@ -# 请求日志中间件 - 优化版 -import time -import uuid -from fastapi import Request, Response -from app.core.logging_config import logger - - -async def logging_middleware(request: Request, call_next): - """记录所有 API 请求的日志 - 优化版""" - - # 生成请求 ID - request_id = str(uuid.uuid4()) - start_time = time.time() - - # 获取用户信息(如果已登录) - user_id = None - try: - auth_header = request.headers.get('Authorization', '') - if auth_header.startswith('Bearer '): - user_id = "authenticated" - except Exception as e: - # 静默失败,不影响主流程 - pass - - # 执行请求 - response_status = 500 - try: - response = await call_next(request) - response_status = response.status_code - except Exception as e: - duration = (time.time() - start_time) * 1000 - try: - logger.error( - f"API 请求异常:{request.method} {request.url.path}", - extra={ - 'request_id': request_id, - 'user_id': user_id, - 'ip_address': request.client.host if request.client else None, - 'duration_ms': duration, - 'data': { - 'method': request.method, - 'path': request.url.path, - 'query': str(request.query_params), - 'error': str(e) - } - }, - exc_info=True - ) - except: - pass # 日志记录失败不影响主流程 - raise - - # 记录响应 - duration = (time.time() - start_time) * 1000 - - try: - log_level = 'INFO' - if response_status >= 500: - log_level = 'ERROR' - elif response_status >= 400: - log_level = 'WARNING' - - getattr(logger, log_level)( - f"API 请求完成:{request.method} {request.url.path}", - extra={ - 'request_id': request_id, - 'user_id': user_id, - 'ip_address': request.client.host if request.client else None, - 'duration_ms': duration, - 'data': { - 'method': request.method, - 'path': request.url.path, - 'query': str(request.query_params), - 'status_code': response_status - } - } - ) - except Exception as e: - # 日志记录失败不影响主流程 - pass - - # 在响应头中添加请求 ID - try: - response.headers['X-Request-ID'] = request_id - except: - pass - - return response diff --git a/backend_old/app/models/__init__.py b/backend_old/app/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend_old/app/models/models.py b/backend_old/app/models/models.py deleted file mode 100644 index 5f9df82..0000000 --- a/backend_old/app/models/models.py +++ /dev/null @@ -1,131 +0,0 @@ -# 数据库模型 - 使用字段编码 -from sqlalchemy import Column, String, Float, Boolean, DateTime, Integer, Text, ForeignKey -from sqlalchemy.orm import relationship -from sqlalchemy.sql import func -from app.core.database import Base -import uuid - - -def generate_uuid(): - """生成 UUID 字符串""" - return str(uuid.uuid4()) - - -class User(Base): - __tablename__ = "users" - - # f99 系统字段 - f99_90_id = Column(String(36), primary_key=True, default=generate_uuid) - f99_91_user_id = Column(String(36), unique=True, nullable=False, index=True) - f01_01_name = Column(String(255), unique=True, nullable=False, index=True) # username - email = Column(String(255), unique=True, nullable=True, index=True) - phone = Column(String(50), nullable=True) - avatar = Column(String(500), nullable=True) - address = Column(String(500), nullable=True) - bio = Column(Text, nullable=True) - password = Column(String(255), nullable=False) - role = Column(String(50), default="user") - # last_login = Column(DateTime(timezone=True), nullable=True) - f99_92_created_at = Column(DateTime(timezone=True), server_default=func.now()) - f99_93_updated_at = Column(DateTime(timezone=True), onupdate=func.now()) - - collections = relationship("Collection", back_populates="user", cascade="all, delete-orphan") - operations = relationship("Operation", back_populates="user", cascade="all, delete-orphan") - custom_fields = relationship("CustomField", back_populates="user", cascade="all, delete-orphan") - - -class Collection(Base): - __tablename__ = "collections" - - # f99 系统字段 - f99_90_id = Column(String(36), primary_key=True, default=generate_uuid) - f99_91_user_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="CASCADE"), nullable=False, index=True) - f99_92_created_at = Column(DateTime(timezone=True), server_default=func.now(), index=True) - f99_93_updated_at = Column(DateTime(timezone=True), onupdate=func.now()) - - # f01 基本信息 - f01_01_name = Column(String(255), nullable=False) - f01_02_code = Column(String(50), nullable=True, index=True) - f01_03_category = Column(String(100), nullable=False, index=True) - f01_04_status = Column(String(50), default="in_collection", index=True) - f01_05_remark = Column(Text, nullable=True) - - # f02 详细字段 - f02_10_prefix_serial = Column(String(50), nullable=True, index=True) - f02_11_version = Column(String(100), nullable=True, index=True) - f02_12_packaging = Column(String(100), nullable=True, index=True) - f02_13_rarity = Column(String(50), nullable=True, index=True) # 珍惜度 - f02_14_number_category = Column(String(20), nullable=True, index=True) # 号码分类 - - # f03 评级信息 - f03_20_is_graded = Column(Boolean, default=False, index=True) - f03_21_grading_company = Column(String(100), nullable=True, index=True) - f03_22_grading_score = Column(String(20), nullable=True, index=True) - f03_23_three_star = Column(Boolean, default=False, index=True) - - # f04 特殊信息 - f04_30_special_mark = Column(String(200), nullable=True, index=True) - f04_31_serial_feature = Column(String(100), nullable=True, index=True) - f04_32_issuer = Column(String(100), nullable=True, index=True) - f04_33_issue_year = Column(String(20), nullable=True, index=True) - f04_34_material = Column(String(50), nullable=True) - f04_35_denomination = Column(String(20), nullable=True) - f04_36_issue_quantity = Column(String(50), nullable=True) - - # f05 价格信息 - f05_40_cost_price = Column(Float, nullable=True, index=True) - f05_41_target_price = Column(Float, nullable=True, index=True) - f05_42_goal_price = Column(Float, nullable=True) - f05_43_repair_fee = Column(Float, nullable=True) - f05_44_grading_fee = Column(Float, nullable=True) - - # f06 其他信息 - f06_50_purpose = Column(String(100), nullable=True) - - user = relationship("User", back_populates="collections") - images = relationship("CollectionImage", back_populates="collection", cascade="all, delete-orphan") - operations = relationship("Operation", back_populates="collection", cascade="all, delete-orphan") - - -class CollectionImage(Base): - __tablename__ = "collection_images" - - id = Column(String(36), primary_key=True, default=generate_uuid) - collection_id = Column(String(36), ForeignKey("collections.f99_90_id", ondelete="CASCADE"), nullable=False, index=True) - filename = Column(String(255), nullable=False) - original_name = Column(String(255), nullable=True) - path = Column(String(500), nullable=True) - created_at = Column(DateTime(timezone=True), server_default=func.now()) - - collection = relationship("Collection", back_populates="images") - - -class Operation(Base): - __tablename__ = "operations" - - f99_90_id = Column(String(36), primary_key=True, default=generate_uuid) - f99_91_user_id = Column(String(36), ForeignKey("collections.f99_90_id", ondelete="CASCADE"), nullable=False, index=True) - f99_92_user_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="CASCADE"), nullable=False, index=True) - type = Column(String(50), nullable=False, index=True) - price = Column(Float, nullable=True) - note = Column(Text, nullable=True) - f99_93_created_at = Column(DateTime(timezone=True), server_default=func.now(), index=True) - - collection = relationship("Collection", back_populates="operations") - user = relationship("User", back_populates="operations") - - -class CustomField(Base): - __tablename__ = "custom_fields" - - id = Column(String(36), primary_key=True, default=generate_uuid) - user_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="CASCADE"), nullable=False, index=True) - name = Column(String(100), nullable=False) - field_type = Column(String(50), default="text") - options = Column(Text, nullable=True) - required = Column(Boolean, default=False) - visible = Column(Boolean, default=True) - sort_order = Column(Integer, default=0) - created_at = Column(DateTime(timezone=True), server_default=func.now()) - - user = relationship("User", back_populates="custom_fields") diff --git a/backend_old/app/routers/__init__.py b/backend_old/app/routers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend_old/app/routers/auth.py b/backend_old/app/routers/auth.py deleted file mode 100644 index c962983..0000000 --- a/backend_old/app/routers/auth.py +++ /dev/null @@ -1,167 +0,0 @@ -# 认证路由 - 使用字段编码 -from fastapi import APIRouter, Depends, HTTPException, status, Body -from fastapi.security import OAuth2PasswordRequestForm -from sqlalchemy.orm import Session -from app.core.database import get_db -from app.core.auth import verify_password, create_access_token, get_password_hash, get_current_user -from app.models.models import User -from app.schemas.schemas import Token, UserCreate, UserResponse - -router = APIRouter(prefix="/api/auth", tags=["认证"]) - - -@router.post("/register", response_model=UserResponse) -def register(user_data: UserCreate, db: Session = Depends(get_db)): - """用户注册""" - # 检查用户名是否已存在 - existing_user = db.query(User).filter(User.f01_01_name == user_data.f01_01_name).first() - if existing_user: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="f01_01_name: 用户名已存在" - ) - - # 检查邮箱是否已存在 - if user_data.email: - existing_email = db.query(User).filter(User.email == user_data.email).first() - if existing_email: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="邮箱已被注册" - ) - - # 创建用户 - import uuid - hashed_password = get_password_hash(user_data.password) - user = User( - f99_90_id=str(uuid.uuid4()), - f99_91_user_id=str(uuid.uuid4()), # 生成唯一 user_id - f01_01_name=user_data.f01_01_name, - email=user_data.email, - phone=user_data.phone, - avatar=user_data.avatar, - address=user_data.address, - bio=user_data.bio, - password=hashed_password, - role="user" - ) - - db.add(user) - db.commit() - db.refresh(user) - - return user - - -@router.post("/login", response_model=Token) -def login( - form_data: OAuth2PasswordRequestForm = Depends(), - db: Session = Depends(get_db) -): - """用户登录""" - # 查找用户 - user = db.query(User).filter(User.f01_01_name == form_data.username).first() - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="E00011: 用户名或密码错误", - headers={"WWW-Authenticate": "Bearer"}, - ) - - # 验证密码 - if not verify_password(form_data.password, user.password): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="E00011: 用户名或密码错误", - headers={"WWW-Authenticate": "Bearer"}, - ) - - # 生成 token - access_token = create_access_token(data={"sub": user.f99_90_id}) - - return { - "access_token": access_token, - "token_type": "bearer" - } - - -@router.get("/me", response_model=UserResponse) -def get_current_user_info( - current_user: User = Depends(lambda: None) -): - """获取当前用户信息""" - raise HTTPException( - status_code=status.HTTP_501_NOT_IMPLEMENTED, - detail="请使用正确的依赖注入" - ) - - -@router.post("/change-password") -def change_password( - old_password: str = Body(...), - new_password: str = Body(...), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """修改当前用户密码""" - from app.core.auth import verify_password, get_password_hash - - # 在当前session中重新查询用户 - user = db.query(User).filter(User.f99_90_id == current_user.f99_90_id).first() - if not user: - raise HTTPException(status_code=404, detail="用户不存在") - - # 验证旧密码 - if not verify_password(old_password, user.password): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="当前密码错误" - ) - - # 更新密码 - user.password = get_password_hash(new_password) - db.commit() - - return {"message": "密码修改成功"} - - -# ============ 短信验证码接口 ============ - -@router.post("/send-verification-code") -def send_verification_code( - phone: str = Body(..., min_length=11, max_length=11), - purpose: str = Body("register") # register | login | reset_password -): - """发送短信验证码""" - from app.services.sms import send_verification_code as send_sms - - # 验证手机号格式 - if not phone.startswith("1") or len(phone) != 11: - return {"success": False, "message": "手机号格式不正确"} - - result = send_sms(phone) - - if result["success"]: - return { - "success": True, - "message": f"验证码已发送到 {phone[:3]}****{phone[7:]}", - "expire": result.get("expire", 300) - } - else: - return result - - -@router.post("/verify-code") -def verify_code( - phone: str = Body(...), - code: str = Body(..., min_length=6, max_length=6) -): - """验证短信验证码(仅验证,不执行后续操作)""" - from app.services.sms import verify_code as check_code - - is_valid = check_code(phone, code) - - if is_valid: - return {"success": True, "message": "验证成功"} - else: - return {"success": False, "message": "验证码错误或已过期"} diff --git a/backend_old/app/routers/collections.py b/backend_old/app/routers/collections.py deleted file mode 100644 index 5d91468..0000000 --- a/backend_old/app/routers/collections.py +++ /dev/null @@ -1,685 +0,0 @@ -# 藏品路由 - 使用字段编码 -import os -import uuid -import re -from typing import Optional, List -from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File -from sqlalchemy import func, text -from sqlalchemy.orm import Session -from app.core.database import get_db -from app.core.auth import get_current_user -from app.core.logging_config import logger -from app.models.models import User, Collection, CollectionImage, Operation -from app.schemas.schemas import ( - CollectionCreate, CollectionUpdate, CollectionResponse, - CollectionListResponse, CollectionImageResponse -) -from app.services.oss import upload_to_oss, get_oss_path, delete_from_oss, get_public_url - -router = APIRouter(prefix="/api/collections", tags=["藏品"]) - - -def to_camel_case(data: dict) -> dict: - """将字段编码转换为 camelCase 格式""" - if not data: - return data - - mapping = { - 'f99_90_id': 'id', - 'f99_91_user_id': 'userId', - 'f99_92_created_at': 'createdAt', - 'f99_93_updated_at': 'updatedAt', - 'f01_01_name': 'name', - 'f01_02_code': 'code', - 'f01_03_category': 'category', - 'f01_04_status': 'status', - 'f01_05_remark': 'remark', - 'f02_10_prefix_serial': 'prefixSerial', - 'f02_11_version': 'version', - 'f02_12_packaging': 'packaging', - 'f02_13_rarity': 'rarity', - 'f02_14_number_category': 'numberCategory', - 'f03_20_is_graded': 'isGraded', - 'f03_21_grading_company': 'gradingCompany', - 'f03_22_grading_score': 'gradingScore', - 'f03_23_three_star': 'threeStar', - 'f04_30_special_mark': 'specialMark', - 'f04_31_serial_feature': 'serialFeature', - 'f04_32_issuer': 'issuer', - 'f04_33_issue_year': 'issueYear', - 'f04_34_material': 'material', - 'f04_35_denomination': 'denomination', - 'f04_36_issue_quantity': 'issueQuantity', - 'f05_40_cost_price': 'costPrice', - 'f05_41_target_price': 'targetPrice', - 'f05_42_goal_price': 'goalPrice', - 'f05_43_repair_fee': 'repairFee', - 'f05_44_grading_fee': 'gradingFee', - 'f06_50_purpose': 'purpose', - } - - return {mapping.get(k, k): v for k, v in data.items()} - - -# 编码生成函数 -def generate_code(version: str, user_id: str, db: Session) -> str: - """自动生成藏品编号 - 按用户独立编码""" - import re - - # 查询当前用户的非空编码(不与其他用户混算) - user_codes = db.query(Collection.f01_02_code).filter( - Collection.f01_02_code.isnot(None), - Collection.f99_91_user_id == user_id - ).all() - - max_num = 0 - for (code,) in user_codes: - # 处理纯数字编码(支持4位和5位) - if re.match(r'^\d{4,5}$', code): - try: - num = int(code) - if num > max_num: - max_num = num - except (ValueError, TypeError): - pass - - # 当前用户最大号 +1 - next_num = max_num + 1 - - # 如果超过9999,使用5位;否则使用4位 - if next_num > 9999: - return str(next_num).zfill(5) - else: - return str(next_num).zfill(4) - - -@router.get("/next-code") -def get_next_code( - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取下一个藏品编号""" - next_code = generate_code("2024 龙", current_user.f99_90_id, db) - return {"code": 200, "data": {"nextCode": next_code}} - - -@router.get("") -def get_collections( - category: Optional[str] = None, - status: Optional[str] = None, - search: Optional[str] = None, - page: int = Query(1, ge=1), - limit: int = Query(20, ge=1, le=100), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取藏品列表""" - # admin 用户可以看到所有藏品,普通用户只能看到自己的 - from sqlalchemy.orm import joinedload - - if current_user.role == "admin": - # 联表查询获取用户名 - query = db.query(Collection, User.f01_01_name.label('owner_name')).join( - User, Collection.f99_91_user_id == User.f99_90_id, isouter=True - ) - else: - query = db.query(Collection).filter(Collection.f99_91_user_id == current_user.f99_90_id) - - if category: - query = query.filter(Collection.f01_03_category == category) - if status: - query = query.filter(Collection.f01_04_status == status) - if search: - query = query.filter( - (Collection.f01_01_name.contains(search)) | - (Collection.f01_05_remark.contains(search)) - ) - - # 总数(分开查询避免join影响count) - if current_user.role == "admin": - total = db.query(Collection).count() - else: - total = query.count() - - # 分页 - data = query.order_by(Collection.f99_92_created_at.desc()) \ - .offset((page - 1) * limit) \ - .limit(limit) \ - .all() - - # 转换为字典列表并转为 camelCase - data_list = [] - for item in data: - # 处理联表查询结果 - if current_user.role == "admin": - collection_item, owner_name = item - else: - collection_item = item - owner_name = None - - item_dict = { - 'f99_90_id': collection_item.f99_90_id, - 'f99_91_user_id': collection_item.f99_91_user_id, - 'owner_name': owner_name, # 所属用户名(仅管理员可见) - 'f01_01_name': collection_item.f01_01_name, - 'f01_02_code': collection_item.f01_02_code, - 'f01_03_category': collection_item.f01_03_category, - 'f01_04_status': collection_item.f01_04_status, - 'f01_05_remark': collection_item.f01_05_remark, - 'f02_10_prefix_serial': collection_item.f02_10_prefix_serial, - 'f02_11_version': collection_item.f02_11_version, - 'f02_12_packaging': collection_item.f02_12_packaging, - 'f02_13_rarity': collection_item.f02_13_rarity, - 'f02_14_number_category': collection_item.f02_14_number_category, - 'f03_20_is_graded': collection_item.f03_20_is_graded, - 'f03_21_grading_company': collection_item.f03_21_grading_company, - 'f03_22_grading_score': collection_item.f03_22_grading_score, - 'f03_23_three_star': collection_item.f03_23_three_star, - 'f04_30_special_mark': collection_item.f04_30_special_mark, - 'f04_31_serial_feature': collection_item.f04_31_serial_feature, - 'f04_32_issuer': collection_item.f04_32_issuer, - 'f04_33_issue_year': collection_item.f04_33_issue_year, - 'f04_34_material': collection_item.f04_34_material, - 'f04_35_denomination': collection_item.f04_35_denomination, - 'f04_36_issue_quantity': collection_item.f04_36_issue_quantity, - 'f05_40_cost_price': float(collection_item.f05_40_cost_price) if collection_item.f05_40_cost_price else None, - 'f05_41_target_price': float(collection_item.f05_41_target_price) if collection_item.f05_41_target_price else None, - 'f05_42_goal_price': float(collection_item.f05_42_goal_price) if collection_item.f05_42_goal_price else None, - 'f05_43_repair_fee': float(collection_item.f05_43_repair_fee) if collection_item.f05_43_repair_fee else None, - 'f05_44_grading_fee': float(collection_item.f05_44_grading_fee) if collection_item.f05_44_grading_fee else None, - 'f06_50_purpose': collection_item.f06_50_purpose, - 'f99_92_created_at': collection_item.f99_92_created_at.isoformat() if collection_item.f99_92_created_at else None, - 'images': [] - } - - # 加载图片数据 - from app.models.models import CollectionImage - images = db.query(CollectionImage).filter( - CollectionImage.collection_id == collection_item.f99_90_id - ).all() - - for img in images: - item_dict['images'].append({ - 'id': img.id, - 'filename': img.filename, - 'original_name': img.original_name, - 'path': img.path, - 'created_at': img.created_at.isoformat() if img.created_at else None - }) - - data_list.append(to_camel_case(item_dict)) - - return { - "data": data_list, - "pagination": { - "page": page, - "limit": limit, - "total": total, - "pages": (total + limit - 1) // limit - } - } - - -@router.get("/stats") -def get_stats( - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取藏品统计""" - # 获取所有藏品 - if current_user.role == "admin": - all_collections = db.query(Collection).all() - else: - all_collections = db.query(Collection).filter( - Collection.f99_91_user_id == current_user.f99_90_id - ).all() - - # 总数 - total_count = len(all_collections) - - # 按分类统计 - from collections import Counter - by_category = Counter(c.f01_03_category for c in all_collections).items() - - # 按状态统计 - by_status = Counter(c.f01_04_status for c in all_collections).items() - - # 按是否评级统计 - by_graded = Counter(c.f03_20_is_graded for c in all_collections).items() - - # 新增:8 个分布统计 - by_packaging = Counter(c.f02_12_packaging for c in all_collections if c.f02_12_packaging).items() - by_rarity = Counter(c.f02_13_rarity for c in all_collections if c.f02_13_rarity).items() - by_version = Counter(c.f02_11_version for c in all_collections if c.f02_11_version).items() - by_grading_company = Counter(c.f03_21_grading_company for c in all_collections if c.f03_20_is_graded and c.f03_21_grading_company).items() - by_grading_score = Counter(c.f03_22_grading_score for c in all_collections if c.f03_20_is_graded and c.f03_22_grading_score).items() - by_special_mark = Counter(c.f04_30_special_mark for c in all_collections if c.f04_30_special_mark).items() - by_number_category = Counter(c.f02_14_number_category for c in all_collections if c.f02_14_number_category).items() - - # 总成本: SUM(cost_price + repair_fee + grading_fee) for ALL collections - total_cost = sum( - (c.f05_40_cost_price or 0) + (c.f05_43_repair_fee or 0) + (c.f05_44_grading_fee or 0) - for c in all_collections - ) - - # 预期利润: SUM(target_price - cost_price) for collections with target_price > 0 - expected_profit = sum( - (c.f05_41_target_price or 0) - (c.f05_40_cost_price or 0) - for c in all_collections - if c.f05_41_target_price and c.f05_41_target_price > 0 - ) - - # 已售商品:状态为 sold 且出售价 > 0 - sold_collections = [ - c for c in all_collections - if c.f01_04_status == 'sold' and c.f05_42_goal_price and c.f05_42_goal_price > 0 - ] - - # 总收入:SUM(出售价) for 已售商品(售价>0) - total_revenue = sum( - c.f05_42_goal_price or 0 - for c in sold_collections - ) - - # 总利润(已实现利润):SUM(出售价 - 成本价 - 修复费 - 评级费) for 已售商品 - # 单藏品总成本 = 成本价 + 修复费 + 评级费 - total_profit = sum( - (c.f05_42_goal_price or 0) - (c.f05_40_cost_price or 0) - (c.f05_43_repair_fee or 0) - (c.f05_44_grading_fee or 0) - for c in sold_collections - ) - - return { - "totalCount": total_count, - "byCategory": [{"category": c, "count": n} for c, n in by_category], - "byStatus": [{"status": s, "count": n} for s, n in by_status], - "byGrading": [{"isGraded": g, "count": n} for g, n in by_graded], - "byPackaging": [{"packaging": p, "count": n} for p, n in by_packaging], - "byRarity": [{"rarity": r, "count": n} for r, n in by_rarity], - "byVersion": [{"version": v, "count": n} for v, n in by_version], - "byGradingCompany": [{"company": c, "count": n} for c, n in by_grading_company], - "byGradingScore": [{"score": s, "count": n} for s, n in by_grading_score], - "bySpecialMark": [{"mark": m, "count": n} for m, n in by_special_mark], - "byNumberCategory": [{"numberCategory": n, "count": c} for n, c in by_number_category], - # 盈亏统计(只统计已售且有价格的藏品) - "byProfitLoss": [ - {"type": "profit", "label": "盈利", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price > c.f05_40_cost_price)}, - {"type": "loss", "label": "亏损", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price <= c.f05_40_cost_price)} - ], - "totalCost": total_cost, - "totalTarget": sum(c.f05_41_target_price or 0 for c in all_collections), - "expectedProfit": expected_profit, - "totalRevenue": total_revenue, - "totalProfit": total_profit - } - - -@router.get("/{collection_id}") -def get_collection( - collection_id: str, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取单个藏品详情""" - result = db.execute( - text("SELECT * FROM collections WHERE f99_90_id = :id"), - {"id": collection_id} - ).fetchone() - - if not result: - raise HTTPException(status_code=404, detail="E00033: 藏品不存在") - - collection = dict(result._mapping) - - # 非管理员只能查看自己的藏品 - if current_user.role != "admin" and collection.get('f99_91_user_id') != current_user.f99_90_id: - raise HTTPException(status_code=403, detail="无权访问") - - result_dict = { - 'f99_90_id': collection.get('f99_90_id'), - 'f99_91_user_id': collection.get('f99_91_user_id'), - 'f01_01_name': collection.get('f01_01_name'), - 'f01_02_code': collection.get('f01_02_code'), - 'f01_03_category': collection.get('f01_03_category'), - 'f01_04_status': collection.get('f01_04_status'), - 'f01_05_remark': collection.get('f01_05_remark'), - 'f02_10_prefix_serial': collection.get('f02_10_prefix_serial'), - 'f02_11_version': collection.get('f02_11_version'), - 'f02_12_packaging': collection.get('f02_12_packaging'), - 'f02_13_rarity': collection.get('f02_13_rarity'), - 'f02_14_number_category': collection.get('f02_14_number_category'), - 'f03_20_is_graded': collection.get('f03_20_is_graded'), - 'f03_21_grading_company': collection.get('f03_21_grading_company'), - 'f03_22_grading_score': collection.get('f03_22_grading_score'), - 'f03_23_three_star': collection.get('f03_23_three_star'), - 'f04_30_special_mark': collection.get('f04_30_special_mark'), - 'f04_31_serial_feature': collection.get('f04_31_serial_feature'), - 'f04_32_issuer': collection.get('f04_32_issuer'), - 'f04_33_issue_year': collection.get('f04_33_issue_year'), - 'f04_34_material': collection.get('f04_34_material'), - 'f04_35_denomination': collection.get('f04_35_denomination'), - 'f04_36_issue_quantity': collection.get('f04_36_issue_quantity'), - 'f05_40_cost_price': float(collection.get('f05_40_cost_price')) if collection.get('f05_40_cost_price') else None, - 'f05_41_target_price': float(collection.get('f05_41_target_price')) if collection.get('f05_41_target_price') else None, - 'f05_42_goal_price': float(collection.get('f05_42_goal_price')) if collection.get('f05_42_goal_price') else None, - 'f05_43_repair_fee': float(collection.get('f05_43_repair_fee')) if collection.get('f05_43_repair_fee') else None, - 'f05_44_grading_fee': float(collection.get('f05_44_grading_fee')) if collection.get('f05_44_grading_fee') else None, - 'f06_50_purpose': collection.get('f06_50_purpose'), - 'f99_92_created_at': collection.get('f99_92_created_at').isoformat() if collection.get('f99_92_created_at') else None, - 'images': [] - } - - # 加载图片数据 - images = db.query(CollectionImage).filter( - CollectionImage.collection_id == collection_id - ).all() - - for img in images: - result_dict['images'].append({ - 'id': img.id, - 'filename': img.filename, - 'original_name': img.original_name, - 'path': img.path, - 'created_at': img.created_at.isoformat() if img.created_at else None - }) - - return to_camel_case(result_dict) - - -@router.post("") -def create_collection( - collection_data: CollectionCreate, - force: bool = False, # 是否强制保存(忽略重复警告) - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """创建藏品 - 支持冠字号查重""" - from app.core.logging_config import logger - - # 自动生成编码 - final_code = collection_data.f01_02_code or generate_code( - collection_data.f02_11_version or '2024 龙', - current_user.f99_90_id, - db - ) - - # 编号查重(如果提供了编号且不是强制保存) - if not force and final_code: - existing_code = db.query(Collection).filter( - Collection.f01_02_code == final_code, - Collection.f99_91_user_id == current_user.f99_90_id - ).first() - - if existing_code: - logger.warning(f"发现重复编号:{final_code}, 已存在藏品 ID: {existing_code.f99_90_id}") - return { - "error": { - "code": "DUPLICATE_CODE", - "message": f"藏品编号 {final_code} 已存在,请使用其他编号" - } - } - - # 冠字号查重(如果提供了冠字号且不是强制保存) - if not force and collection_data.f02_10_prefix_serial: - # 查询当前用户是否有相同冠字号的藏品 - existing = db.query(Collection).filter( - Collection.f02_10_prefix_serial == collection_data.f02_10_prefix_serial, - Collection.f99_91_user_id == current_user.f99_90_id - ).first() - - if existing: - logger.warning(f"发现重复冠字号:{collection_data.f02_10_prefix_serial}, 已存在藏品 ID: {existing.f99_90_id}") - # 返回警告信息,让前端询问用户是否继续 - return { - "warning": { - "code": "DUPLICATE_SERIAL", - "message": f"发现重复冠字号:{collection_data.f02_10_prefix_serial}", - "existing_collection": { - "id": existing.f99_90_id, - "name": existing.f01_01_name, - "code": existing.f01_02_code, - "prefix_serial": existing.f02_10_prefix_serial - } - }, - "data": { - "ask_continue": True - } - } - - collection = Collection( - f99_91_user_id=current_user.f99_90_id, - f01_01_name=collection_data.f01_01_name, - f01_02_code=final_code, - f01_03_category=collection_data.f01_03_category, - f01_04_status=collection_data.f01_04_status or "in_collection", - f01_05_remark=collection_data.f01_05_remark, - f02_10_prefix_serial=collection_data.f02_10_prefix_serial, - f02_11_version=collection_data.f02_11_version, - f02_12_packaging=collection_data.f02_12_packaging, - f02_13_rarity=collection_data.f02_13_rarity, - f02_14_number_category=collection_data.f02_14_number_category, - f03_20_is_graded=collection_data.f03_20_is_graded or False, - f03_21_grading_company=collection_data.f03_21_grading_company, - f03_22_grading_score=collection_data.f03_22_grading_score, - f03_23_three_star=collection_data.f03_23_three_star or False, - f04_30_special_mark=collection_data.f04_30_special_mark, - f04_31_serial_feature=collection_data.f04_31_serial_feature, - f04_32_issuer=collection_data.f04_32_issuer, - f04_33_issue_year=collection_data.f04_33_issue_year, - f04_34_material=collection_data.f04_34_material, - f04_35_denomination=collection_data.f04_35_denomination, - f04_36_issue_quantity=collection_data.f04_36_issue_quantity, - f05_40_cost_price=collection_data.f05_40_cost_price, - f05_41_target_price=collection_data.f05_41_target_price, - f05_42_goal_price=collection_data.f05_42_goal_price, - f05_43_repair_fee=collection_data.f05_43_repair_fee, - f05_44_grading_fee=collection_data.f05_44_grading_fee, - f06_50_purpose=collection_data.f06_50_purpose - ) - - db.add(collection) - db.commit() - db.refresh(collection) - - return { - 'f99_90_id': collection.f99_90_id, - 'f99_91_user_id': collection.f99_91_user_id, - 'f01_01_name': collection.f01_01_name, - 'f01_02_code': collection.f01_02_code, - 'f01_03_category': collection.f01_03_category, - 'f01_04_status': collection.f01_04_status, - 'f01_05_remark': collection.f01_05_remark, - 'f99_92_created_at': collection.f99_92_created_at.isoformat() if collection.f99_92_created_at else None, - 'message': '创建成功' - } - - -@router.put("/{collection_id}") -def update_collection( - collection_id: str, - collection_data: CollectionUpdate, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """更新藏品""" - collection = db.query(Collection).filter( - Collection.f99_90_id == collection_id, - Collection.f99_91_user_id == current_user.f99_90_id - ).first() - - if not collection: - raise HTTPException(status_code=404, detail="E00033: 藏品不存在") - - # 更新字段 - 使用 model_fields_set 检查哪些字段被设置 - for field_name in collection_data.model_fields_set: - value = getattr(collection_data, field_name) - if value is not None: - setattr(collection, field_name, value) - - db.commit() - db.refresh(collection) - - return { - "f99_90_id": collection.f99_90_id, - "message": "更新成功" - } - - -@router.delete("/{collection_id}") -def delete_collection( - collection_id: str, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """删除藏品""" - # 验证权限并检查是否存在 - collection = db.query(Collection).filter( - Collection.f99_90_id == collection_id, - Collection.f99_91_user_id == current_user.f99_90_id - ).first() - - if not collection: - raise HTTPException(status_code=404, detail="E00033: 藏品不存在") - - # 使用原生 SQL 删除(避免 ORM 级联查询字段不匹配问题) - from sqlalchemy import text - # 1. 删除关联的 operations(f99_91_user_id 关联到 collections.f99_90_id) - db.execute(text("DELETE FROM operations WHERE f99_91_user_id = :id"), {"id": collection_id}) - # 2. 删除关联的图片 - db.execute(text("DELETE FROM collection_images WHERE collection_id = :id"), {"id": collection_id}) - # 3. 删除藏品本身 - db.execute(text("DELETE FROM collections WHERE f99_90_id = :id"), {"id": collection_id}) - db.commit() - - return {"message": "删除成功"} - - -@router.post("/upload-image") -async def upload_image( - collection_id: str = None, - file: UploadFile = File(...), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """上传藏品图片 - 文件名格式:用户名 - 藏品编号 - 冠字号""" - try: - # 验证藏品是否存在 - collection = db.query(Collection).filter( - Collection.f99_90_id == collection_id - ).first() - - if not collection: - raise HTTPException(status_code=404, detail="E00033: 藏品不存在") - - # 获取用户信息(用于文件名) - owner = db.query(User).filter(User.f99_90_id == collection.f99_91_user_id).first() - username = owner.f01_01_name if owner else "unknown" - - # 获取藏品信息(用于文件名) - code = collection.f01_02_code or "0000" - prefix_serial = collection.f02_10_prefix_serial or "" - - # 检查文件类型 - if not file.content_type.startswith('image/'): - raise HTTPException(status_code=400, detail="E00038: 只能上传图片文件") - - # 检查文件大小(限制 10MB) - file_size = 0 - content = await file.read() - file_size = len(content) - if file_size > 10 * 1024 * 1024: # 10MB - raise HTTPException(status_code=400, detail=f"E00039: 图片大小不能超过 10MB(当前{file_size // 1024 // 1024}MB)") - - # 生成OSS存储路径 - user_id = collection.f99_91_user_id - file_extension = file.filename.split('.')[-1] if '.' in file.filename else 'jpg' - - # 清理特殊字符 - clean_username = re.sub(r'[^\w\u4e00-\u9fff\-]', '', username) - clean_serial = re.sub(r'[^\w\u4e00-\u9fff\-]', '', prefix_serial) - - # 文件名格式:用户名-藏品编号-冠字号.jpg - if clean_serial: - filename = f"{clean_username}-{code}-{clean_serial}.{file_extension}" - else: - filename = f"{clean_username}-{code}.{file_extension}" - - # 生成OSS key - oss_key, unique_name = get_oss_path("collections", user_id=user_id, filename=filename) - - # 上传到OSS - image_url = upload_to_oss(content, oss_key) - - # 创建图片记录(保存OSS URL) - image = CollectionImage( - id=str(uuid.uuid4()), - collection_id=collection_id, - filename=unique_name, - original_name=file.filename, - path=image_url # 保存OSS URL - ) - - db.add(image) - db.commit() - db.refresh(image) - - logger.info(f"图片上传成功:{image_url}, collection_id={collection_id}") - - return { - "message": "上传成功", - "image_id": image.id, - "filename": unique_name, - "url": image_url - } - except HTTPException: - raise - except Exception as e: - logger.error(f"图片上传失败:{str(e)}") - raise HTTPException(status_code=500, detail="上传失败") - - -@router.delete("/images/{image_id}") -async def delete_image( - image_id: str, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """删除藏品图片""" - try: - # 查找图片记录 - image = db.query(CollectionImage).filter( - CollectionImage.id == image_id - ).first() - - if not image: - raise HTTPException(status_code=404, detail="E00033: 图片不存在") - - # 检查权限 - collection = db.query(Collection).filter( - Collection.f99_90_id == image.collection_id - ).first() - - if collection and current_user.role != "admin" and collection.f99_91_user_id != current_user.f99_90_id: - raise HTTPException(status_code=403, detail="E00014: 无权删除此图片") - - # 删除OSS文件(如果path是OSS URL) - if image.path and image.path.startswith("https://"): - # 从OSS URL提取key - try: - oss_key = image.path.replace("https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com/", "") - delete_from_oss(oss_key) - except Exception as e: - logger.warning(f"OSS文件删除失败: {e}") - elif image.path and os.path.exists(image.path): - # 兼容旧的本地上传 - os.remove(image.path) - - # 删除数据库记录 - db.delete(image) - db.commit() - - return {"message": "删除成功"} - except HTTPException: - raise - except Exception as e: - logger.error(f"图片删除失败:{str(e)}") - raise HTTPException(status_code=500, detail="删除失败") diff --git a/backend_old/app/routers/ocr.py b/backend_old/app/routers/ocr.py deleted file mode 100644 index c49155d..0000000 --- a/backend_old/app/routers/ocr.py +++ /dev/null @@ -1,376 +0,0 @@ -# OCR 识别路由 - 专业人民币生肖纪念钞鉴定 -import os -import uuid -import base64 -import httpx -from fastapi import APIRouter, Depends, HTTPException, UploadFile, File -from sqlalchemy.orm import Session -from app.core.database import get_db -from app.core.auth import get_current_user -from app.models.models import User - -router = APIRouter(prefix="/api/ocr", tags=["OCR 识别"]) - -# 阿里云 DashScope API 配置 -DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "sk-9389024a37da4f7bb455ac9a6b28776f") - -# 阿里云 OSS 配置 -OSS_CONFIG = { - "access_key_id": os.getenv("OSS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"), - "access_key_secret": os.getenv("OSS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"), - "bucket_name": "jiachenlong-oss", - "endpoint": "oss-cn-hangzhou.aliyuncs.com", - "public_url": "https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com" -} - -# 临时上传目录(用于OCR识别本地备选) -UPLOAD_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "temp") -os.makedirs(UPLOAD_DIR, exist_ok=True) - - -def get_oss_path(file_type: str, user_id: str = None, collection_id: str = None, filename: str = None): - """生成OSS路径 - 按年/月/日分类""" - from datetime import datetime - now = datetime.now() - year = now.strftime("%Y") - month = now.strftime("%m") - day = now.strftime("%d") - - if file_type == "temp": - # 临时文件: temp/{year}/{month}/{day}/{uuid}.{ext} - import uuid - unique_id = str(uuid.uuid4()) - ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg' - return f"temp/{year}/{month}/{day}/{unique_id}.{ext}", unique_id - - elif file_type == "collection": - # 藏品文件: collections/{user_id}/{year}/{collection_id}/{filename} - if not user_id or not collection_id: - raise ValueError("user_id and collection_id required for collection") - return f"collections/{user_id}/{year}/{collection_id}/{filename}" - - elif file_type == "avatar": - # 头像: avatars/{user_id}/avatar.{ext} - if not user_id: - raise ValueError("user_id required for avatar") - ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg' - return f"avatars/{user_id}/avatar.{ext}" - - return None - - -# 上传图片到OSS - 使用服务层(带压缩) -from app.services.oss import upload_to_oss as oss_upload - -def upload_to_oss(file_data, oss_key): - """上传文件到阿里云OSS(带自动压缩)""" - return oss_upload(file_data, oss_key) - -# 专业提示词 -PROFESSIONAL_PROMPT = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。 - -【识别流程】 -1. 判断类型是否评级钞:首先确认是否为裸钞还是评级钞(有封装盒和标签) -2. 验证纪念钞特征:对照生肖纪念钞特征进行确认 -3. 验证评级类型:有'标十'字眼的为标十,有'百连'字眼的为标百,其他为单张 -4. 提取信息:仔细阅读标签上的所有文字内容 - -【版别格式要求】 -只需要:年份 + 属相,例如: -- 2024 龙 -- 2025 蛇 -- 2026 马 - -【输出要求】 -严格按照以下格式输出,每个字段必须填写具体值: -✅ 1 发行机构:中国人民银行 -✅ 2 发行版别:2024 龙 -✅ 3 面额:贰拾圆 -✅ 4 是否评级:是/否 -✅ 5 封装类型:裸钞/单张/标十/标百 -✅ 6 冠字序号:J0xxxxxxxx -✅ 7 评级机构:ACG/PCGS/PMG -✅ 8 评级分数:67/68/69 -✅ 9 是否三星:是/否 -✅ 10 特殊标识:金山标/天马标/红绳版等 -✅ 11 号码特征:金山号 2 张,天马号 3 张等 - -现在请仔细分析提供的图片,按上述格式输出结果。""" - - -@router.post("/recognize") -async def recognize_image( - image: UploadFile = File(...), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """OCR 图片识别 - 识别后自动保存图片到OSS临时目录""" - try: - # 读取图片数据 - image_data = await image.read() - image_base64 = base64.b64encode(image_data).decode('utf-8') - - # 生成OSS临时路径: temp/{year}/{month}/{day}/{uuid}.{ext} - oss_key, temp_id = get_oss_path("temp", filename=image.filename) - - # 上传到OSS - try: - image_url = upload_to_oss(image_data, oss_key) - except Exception as oss_err: - # OSS失败时保存到本地作为备选 - temp_path = os.path.join(UPLOAD_DIR, oss_key.split('/')[-1]) - os.makedirs(os.path.dirname(temp_path), exist_ok=True) - with open(temp_path, 'wb') as f: - f.write(image_data) - image_url = f"/uploads/temp/{oss_key.split('/')[-1]}" - - headers = { - "Authorization": f"Bearer {DASHSCOPE_API_KEY}", - "Content-Type": "application/json" - } - - # 阿里云 DashScope API 格式 (qwen-vl-plus 视觉模型) - payload = { - "model": "qwen-vl-plus", - "input": { - "messages": [{ - "role": "user", - "content": [ - { - "image": f"data:{image.content_type};base64,{image_base64}" - }, - { - "text": PROFESSIONAL_PROMPT - } - ] - }] - }, - "parameters": { - "max_tokens": 1000 - } - } - - async with httpx.AsyncClient(timeout=60.0) as client: - response = await client.post( - "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation", - json=payload, - headers=headers - ) - - if response.status_code != 200: - # 识别失败,删除临时文件 - if os.path.exists(temp_path): - os.remove(temp_path) - raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}") - - ocr_result = response.json() - text_content = "" - # 新版API返回格式 - if "output" in ocr_result and "choices" in ocr_result["output"]: - choices = ocr_result["output"]["choices"] - if choices and len(choices) > 0: - content = choices[0].get("message", {}).get("content", []) - if content and len(content) > 0: - text_content = content[0].get("text", "") - - fields = extract_fields(text_content) - - # 返回识别结果和临时图片路径 - return { - "success": True, - "text": text_content, - "fields": fields, - "temp_image": { - "id": temp_id, - "filename": oss_key.split('/')[-1], - "path": image_url, - "original_name": image.filename, - "is_oss": image_url.startswith("https://") - } - } - - except Exception as e: - import traceback - error_detail = f"识别失败:{str(e)}\n{traceback.format_exc()}" - raise HTTPException(status_code=500, detail=error_detail) - - -def extract_fields(text: str) -> dict: - """从 OCR 文本中提取字段 - 直接返回 AI 识别结果""" - import re - fields = {} - - # 解析结构化输出 - patterns = { - 'issuer': r'✅.*?1.*?发行机构.*?[::]\s*(.+?)(?:\n|$)', - 'version': r'✅.*?2.*?发行版别.*?[::]\s*(.+?)(?:\n|$)', - 'denomination': r'✅.*?3.*?面额.*?[::]\s*(.+?)(?:\n|$)', - 'is_graded_text': r'✅.*?4.*?是否评级.*?[::]\s*(.+?)(?:\n|$)', - 'packaging': r'✅.*?5.*?封装类型.*?[::]\s*(.+?)(?:\n|$)', - 'prefix_serial': r'✅.*?6.*?冠字序号.*?[::]\s*(.+?)(?:\n|$)', - 'grading_company': r'✅.*?7.*?评级机构.*?[::]\s*(.+?)(?:\n|$)', - 'grading_score': r'✅.*?8.*?评级分数.*?[::]\s*(.+?)(?:\n|$)', - 'three_star_text': r'✅.*?9.*?是否三星.*?[::]\s*(.+?)(?:\n|$)', - 'special_mark': r'✅.*?10.*?特殊标识.*?[::]\s*(.+?)(?:\n|$)', - 'serial_feature': r'✅.*?11.*?号码特征.*?[::]\s*(.+?)(?:\n|$)' - } - - for field, pattern in patterns.items(): - match = re.search(pattern, text, re.IGNORECASE | re.DOTALL) - if match: - value = match.group(1).strip() - # 保留所有值,包括"无"和"未识别",让前端处理 - fields[field] = value - - # 处理是否评级 - if 'is_graded_text' in fields: - fields['is_graded'] = '是' in fields.pop('is_graded_text') - - # 处理是否三星 - if 'three_star_text' in fields: - fields['three_star'] = '是' in fields.pop('three_star_text') - - # 简化版别字段(2024 龙年贺岁纪念钞(标十) → 2024 龙) - if 'version' in fields: - version = fields['version'] - # 提取年份和生肖 - year_match = re.search(r'(20\d{2})', version) - animal = '' - if '龙' in version: - animal = '龙' - elif '蛇' in version: - animal = '蛇' - elif '马' in version: - animal = '马' - elif '羊' in version: - animal = '羊' - elif '猴' in version: - animal = '猴' - elif '鸡' in version: - animal = '鸡' - elif '狗' in version: - animal = '狗' - elif '猪' in version: - animal = '猪' - elif '鼠' in version: - animal = '鼠' - elif '牛' in version: - animal = '牛' - elif '虎' in version: - animal = '虎' - elif '兔' in version: - animal = '兔' - - if year_match and animal: - fields['version'] = f"{year_match.group(1)}{animal}" - - return fields - - -@router.post("/claim-temp-image") -async def claim_temp_image( - temp_id: str, - collection_id: str, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """将临时图片移动到正式藏品目录 - 按用户/年/藏品ID分类""" - from app.models.models import Collection, CollectionImage - from datetime import datetime - - # 验证藏品是否存在 - collection = db.query(Collection).filter( - Collection.f99_90_id == collection_id, - Collection.f99_91_user_id == current_user.f99_90_id - ).first() - - if not collection: - raise HTTPException(status_code=404, detail="藏品不存在") - - # 生成正式文件名和OSS路径: collections/{user_id}/{year}/{collection_id}/{filename} - code = collection.f01_02_code or "0000" - prefix = collection.f02_10_prefix_serial or "" - username = current_user.f01_01_name - import time - final_filename = f"{username}-{code}-{prefix}-{int(time.time())}.jpg" - oss_key = get_oss_path("collection", user_id=current_user.f99_90_id, collection_id=collection_id, filename=final_filename) - - # 尝试从OSS获取临时图片 - 尝试多种扩展名和日期路径 - temp_extensions = ['jpg', 'jpeg', 'png', 'gif', 'JPG', 'JPEG', 'PNG', 'GIF'] - temp_content = None - found_key = None - - # 尝试最近7天的路径 - from datetime import timedelta - for i in range(7): - date = datetime.now() - timedelta(days=i) - year = date.strftime("%Y") - month = date.strftime("%m") - day = date.strftime("%d") - - for ext in temp_extensions: - try: - temp_oss_key = f"temp/{year}/{month}/{day}/{temp_id}.{ext}" - import oss2 - auth = oss2.Auth(OSS_CONFIG["access_key_id"], OSS_CONFIG["access_key_secret"]) - bucket = oss2.Bucket(auth, OSS_CONFIG["endpoint"], OSS_CONFIG["bucket_name"]) - temp_content = bucket.get_object(temp_oss_key).read() - found_key = temp_oss_key - break - except: - continue - if temp_content: - break - - if temp_content: - # 上传到正式目录 - bucket.put_object(oss_key, temp_content) - - # 删除临时图片 - try: - bucket.delete_object(found_key) - except: - pass - - # OSS URL - image_path = f"{OSS_CONFIG['public_url']}/{oss_key}" - - else: - # OSS失败,使用本地文件 - temp_path = None - for ext in temp_extensions: - temp_path = os.path.join(UPLOAD_DIR, f"{temp_id}.{ext}") - if os.path.exists(temp_path): - break - - if not temp_path or not os.path.exists(temp_path): - raise HTTPException(status_code=404, detail="临时图片不存在或已过期") - - # 保存到本地 - collection_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "collections") - os.makedirs(collection_dir, exist_ok=True) - - new_path = os.path.join(collection_dir, final_filename) - import shutil - shutil.move(temp_path, new_path) - image_path = f"uploads/collections/{final_filename}" - - # 创建图片记录 - image_record = CollectionImage( - id=str(uuid.uuid4()), - collection_id=collection.f99_90_id, - filename=final_filename, - original_name=temp_id, - path=image_path - ) - db.add(image_record) - db.commit() - - return { - "success": True, - "image": { - "id": image_record.id, - "filename": image_record.filename, - "path": image_record.path - } - } diff --git a/backend_old/app/routers/operations.py b/backend_old/app/routers/operations.py deleted file mode 100644 index 0881599..0000000 --- a/backend_old/app/routers/operations.py +++ /dev/null @@ -1,104 +0,0 @@ -# 操作路由 -from typing import List, Optional -from fastapi import APIRouter, Depends, HTTPException, status, Query -from sqlalchemy.orm import Session -from app.core.database import get_db -from app.core.auth import get_current_user -from app.models.models import User, Collection, Operation -from app.schemas.schemas import OperationCreate, OperationResponse - -router = APIRouter(prefix="/api", tags=["操作"]) - - -@router.get("/operations", response_model=List[OperationResponse]) -def get_operations( - collection_id: Optional[str] = None, - page: int = Query(1, ge=1), - limit: int = Query(50, ge=1, le=100), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取操作历史""" - query = db.query(Operation).filter(Operation.user_id == current_user.id) - - if collection_id: - query = query.filter(Operation.collection_id == collection_id) - - operations = query.order_by(Operation.created_at.desc()) \ - .offset((page - 1) * limit) \ - .limit(limit) \ - .all() - - return operations - - -@router.get("/operations/history") -def get_operation_history( - collection_id: Optional[str] = None, - type: Optional[str] = None, - start_date: Optional[str] = None, - end_date: Optional[str] = None, - page: int = Query(1, ge=1), - limit: int = Query(50, ge=1, le=100), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取操作历史(带统计)""" - query = db.query(Operation).filter(Operation.user_id == current_user.id) - - if collection_id: - query = query.filter(Operation.collection_id == collection_id) - if type: - query = query.filter(Operation.type == type) - if start_date: - query = query.filter(Operation.created_at >= start_date) - if end_date: - query = query.filter(Operation.created_at <= end_date) - - total = query.count() - - data = query.order_by(Operation.created_at.desc()) \ - .offset((page - 1) * limit) \ - .limit(limit) \ - .all() - - return { - "data": data, - "pagination": { - "page": page, - "limit": limit, - "total": total, - "pages": (total + limit - 1) // limit - } - } - - -@router.post("/operations", response_model=OperationResponse) -def create_operation( - operation_data: OperationCreate, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """创建操作记录""" - # 验证藏品存在 - collection = db.query(Collection).filter( - Collection.id == operation_data.collection_id, - Collection.user_id == current_user.id - ).first() - - if not collection: - raise HTTPException(status_code=404, detail="藏品不存在") - - operation = Operation( - collection_id=operation_data.collection_id, - user_id=current_user.id, - type=operation_data.type, - price=operation_data.price, - note=operation_data.note - ) - - db.add(operation) - db.commit() - db.refresh(operation) - - return operation diff --git a/backend_old/app/routers/users.py b/backend_old/app/routers/users.py deleted file mode 100644 index 2215970..0000000 --- a/backend_old/app/routers/users.py +++ /dev/null @@ -1,225 +0,0 @@ -# 用户管理路由 -from typing import Optional -from fastapi import APIRouter, Depends, HTTPException, status, Query, Body -from sqlalchemy.orm import Session -from app.core.database import get_db -from app.core.auth import get_current_user -from app.models.models import User, Collection -from app.schemas.schemas import UserResponse, UserUpdate - -router = APIRouter(prefix="/api", tags=["用户"]) - -# ============ 当前用户接口 ============ - -@router.get("/users/me", response_model=UserResponse) -def get_current_user_info( - current_user: User = Depends(get_current_user) -): - """获取当前登录用户信息""" - return { - "f99_90_id": current_user.f99_90_id, - "f01_01_name": current_user.f01_01_name, - "email": current_user.email, - "phone": current_user.phone, - "avatar": current_user.avatar, - "address": current_user.address, - "bio": current_user.bio, - "role": current_user.role, - "f99_92_created_at": current_user.f99_92_created_at.isoformat() if current_user.f99_92_created_at else None, - "f99_93_updated_at": current_user.f99_93_updated_at.isoformat() if current_user.f99_93_updated_at else None - } - -@router.put("/users/me", response_model=UserResponse) -def update_current_user( - user_update: UserUpdate, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """更新当前用户信息""" - import logging - logger = logging.getLogger(__name__) - - # 获取用户ID - user_id = current_user.f99_90_id - logger.info(f"Updating user {user_id}, data={user_update.model_dump()}") - - # 在当前session中重新查询用户 - user = db.query(User).filter(User.f99_90_id == user_id).first() - if not user: - raise HTTPException(status_code=404, detail="用户不存在") - - # 更新字段 - update_data = user_update.model_dump(exclude_unset=True) - for field, value in update_data.items(): - if field == 'f01_01_name': - user.f01_01_name = value - elif field == 'username': - pass # skip, already handled as f01_01_name - elif hasattr(user, field): - setattr(user, field, value) - - # 强制刷新以确保更新被提交 - db.flush() - db.commit() - db.refresh(user) - logger.info(f"After commit, user email={user.email}") - - return user - -# ============ 管理员用户管理 ============ - -admin_router = APIRouter(prefix="/api/admin/users", tags=["用户管理"]) - - -@admin_router.get("") -def get_users( - page: int = Query(1, ge=1), - limit: int = Query(20, ge=1, le=100), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取用户列表(仅管理员)""" - if current_user.role != "admin": - raise HTTPException(status_code=403, detail="无权访问") - - total = db.query(User).count() - users = db.query(User).offset((page-1)*limit).limit(limit).all() - - user_list = [] - for u in users: - # 统计每个用户的藏品数量 - count = db.query(Collection).filter(Collection.f99_91_user_id == u.f99_90_id).count() - user_list.append({ - "id": u.f99_90_id, - "username": u.f01_01_name, - "email": u.email, - "phone": u.phone, - "role": u.role, - "created_at": u.f99_92_created_at.isoformat() if u.f99_92_created_at else None, - "collection_count": count - }) - - return user_list - - -@admin_router.get("/{user_id}") -def get_user( - user_id: str, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取单个用户信息""" - if current_user.role != "admin": - raise HTTPException(status_code=403, detail="无权访问") - - user = db.query(User).filter(User.id == user_id).first() - if not user: - raise HTTPException(status_code=404, detail="用户不存在") - - return { - "id": user.id, - "username": user.username, - "email": user.email, - "phone": user.phone, - "role": user.role, - "created_at": user.created_at.isoformat() if user.created_at else None - } - - -@admin_router.get("/{user_id}/collections") -def get_user_collections( - user_id: str, - limit: int = Query(100, ge=1, le=100), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取指定用户的藏品列表""" - if current_user.role != "admin": - raise HTTPException(status_code=403, detail="无权访问") - - collections = db.query(Collection).filter( - Collection.user_id == user_id - ).limit(limit).all() - - return [c.code for c in collections] - - -@admin_router.get("/{user_id}/count") -def get_user_collection_count( - user_id: str, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """获取指定用户的藏品数量""" - if current_user.role != "admin": - raise HTTPException(status_code=403, detail="E00050: 仅管理员可访问") - - count = db.query(Collection).filter(Collection.user_id == user_id).count() - return {"count": count} - - -@admin_router.put("/{user_id}") -def update_user( - user_id: str, - username: Optional[str] = Body(None), - email: Optional[str] = Body(None), - role: Optional[str] = Body(None), - password: Optional[str] = Body(None), - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """更新用户信息(仅管理员)""" - if current_user.role != "admin": - raise HTTPException(status_code=403, detail="E00050: 仅管理员可访问") - - user = db.query(User).filter(User.f99_90_id == user_id).first() - if not user: - raise HTTPException(status_code=404, detail="E00051: 用户不存在") - - # 更新基本信息 - if username: - user.f01_01_name = username - if email: - user.email = email - if role is not None: - user.role = role - - # 更新密码 - if password and password.strip(): - from app.core.auth import get_password_hash - user.password = get_password_hash(password) - - db.commit() - db.refresh(user) - - return { - "id": user.f99_90_id, - "username": user.f01_01_name, - "email": user.email, - "role": user.role, - "message": "更新成功" - } - - -@admin_router.delete("/{user_id}") -def delete_user( - user_id: str, - current_user: User = Depends(get_current_user), - db: Session = Depends(get_db) -): - """删除用户(仅管理员)""" - if current_user.role != "admin": - raise HTTPException(status_code=403, detail="E00050: 仅管理员可访问") - - # 不能删除自己 - if user_id == str(current_user.f99_90_id): - raise HTTPException(status_code=400, detail="E00052: 不能删除自己") - - user = db.query(User).filter(User.f99_90_id == user_id).first() - if not user: - raise HTTPException(status_code=404, detail="E00051: 用户不存在") - - db.delete(user) - db.commit() - - return {"message": "删除成功"} diff --git a/backend_old/app/schemas/__init__.py b/backend_old/app/schemas/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend_old/app/schemas/schemas.py b/backend_old/app/schemas/schemas.py deleted file mode 100644 index 0ba1bae..0000000 --- a/backend_old/app/schemas/schemas.py +++ /dev/null @@ -1,212 +0,0 @@ -# Pydantic Schema - 使用字段编码并支持 camelCase -from typing import Optional, List -from pydantic import BaseModel, EmailStr, Field, ConfigDict -from datetime import datetime - - -# ============ 用户相关 ============ - -class UserBase(BaseModel): - f01_01_name: str = Field(..., min_length=3, max_length=255, alias="username") - email: Optional[EmailStr] = None - phone: Optional[str] = None - avatar: Optional[str] = None - address: Optional[str] = None - bio: Optional[str] = None - - model_config = ConfigDict(from_attributes=True, populate_by_name=True) - - -class UserCreate(UserBase): - password: str = Field(..., min_length=6) - - -class UserUpdate(BaseModel): - f01_01_name: Optional[str] = Field(None, alias="username") - email: Optional[EmailStr] = None - phone: Optional[str] = None - avatar: Optional[str] = None - address: Optional[str] = None - bio: Optional[str] = None - password: Optional[str] = None - - model_config = ConfigDict(from_attributes=True, populate_by_name=True) - - -class UserResponse(UserBase): - f99_90_id: str = Field(..., alias="id") - f01_01_name: str = Field(..., alias="username") - role: str - f99_92_created_at: Optional[datetime] = Field(None, alias="created_at") - f99_93_updated_at: Optional[datetime] = Field(None, alias="updated_at") - - model_config = ConfigDict(from_attributes=True, populate_by_name=True) - - -# ============ 藏品相关 ============ - -class CollectionBase(BaseModel): - # f01 基本信息 - f01_01_name: str = Field(..., min_length=2, max_length=255, alias="name") - f01_02_code: Optional[str] = Field(None, max_length=50, alias="code") - f01_03_category: str = Field(..., max_length=100, alias="category") - f01_04_status: Optional[str] = Field("in_collection", alias="status") - f01_05_remark: Optional[str] = Field(None, alias="remark") - - # f02 详细字段 - f02_10_prefix_serial: Optional[str] = Field(None, alias="prefixSerial") - f02_11_version: Optional[str] = Field(None, alias="version") - f02_12_packaging: Optional[str] = Field(None, alias="packaging") - f02_13_rarity: Optional[str] = Field(None, alias="rarity") - f02_14_number_category: Optional[str] = Field(None, alias="numberCategory") - - # f03 评级信息 - f03_20_is_graded: Optional[bool] = Field(False, alias="isGraded") - f03_21_grading_company: Optional[str] = Field(None, alias="gradingCompany") - f03_22_grading_score: Optional[str] = Field(None, alias="gradingScore") - f03_23_three_star: Optional[bool] = Field(False, alias="threeStar") - - # f04 特殊信息 - f04_30_special_mark: Optional[str] = Field(None, alias="specialMark") - f04_31_serial_feature: Optional[str] = Field(None, alias="serialFeature") - f04_32_issuer: Optional[str] = Field(None, alias="issuer") - f04_33_issue_year: Optional[str] = Field(None, alias="issueYear") - f04_34_material: Optional[str] = Field(None, alias="material") - f04_35_denomination: Optional[str] = Field(None, alias="denomination") - f04_36_issue_quantity: Optional[str] = Field(None, alias="issueQuantity") - - # f05 价格信息 - f05_40_cost_price: Optional[float] = Field(None, ge=0, alias="costPrice") - f05_41_target_price: Optional[float] = Field(None, ge=0, alias="targetPrice") - f05_42_goal_price: Optional[float] = Field(None, ge=0, alias="goalPrice") - f05_43_repair_fee: Optional[float] = Field(None, ge=0, alias="repairFee") - f05_44_grading_fee: Optional[float] = Field(None, ge=0, alias="gradingFee") - - # f06 其他信息 - f06_50_purpose: Optional[str] = Field(None, alias="purpose") - - model_config = ConfigDict(from_attributes=True, populate_by_name=True) - - -class CollectionCreate(CollectionBase): - pass - - -class CollectionUpdate(BaseModel): - # f01 基本信息 - f01_01_name: Optional[str] = Field(None, alias="name") - f01_02_code: Optional[str] = Field(None, alias="code") - f01_03_category: Optional[str] = Field(None, alias="category") - f01_04_status: Optional[str] = Field(None, alias="status") - f01_05_remark: Optional[str] = Field(None, alias="remark") - - # f02 详细字段 - f02_10_prefix_serial: Optional[str] = Field(None, alias="prefixSerial") - f02_11_version: Optional[str] = Field(None, alias="version") - f02_12_packaging: Optional[str] = Field(None, alias="packaging") - f02_13_rarity: Optional[str] = Field(None, alias="rarity") - f02_14_number_category: Optional[str] = Field(None, alias="numberCategory") - - # f03 评级信息 - f03_20_is_graded: Optional[bool] = Field(None, alias="isGraded") - f03_21_grading_company: Optional[str] = Field(None, alias="gradingCompany") - f03_22_grading_score: Optional[str] = Field(None, alias="gradingScore") - f03_23_three_star: Optional[bool] = Field(None, alias="threeStar") - - # f04 特殊信息 - f04_30_special_mark: Optional[str] = Field(None, alias="specialMark") - f04_31_serial_feature: Optional[str] = Field(None, alias="serialFeature") - f04_32_issuer: Optional[str] = Field(None, alias="issuer") - f04_33_issue_year: Optional[str] = Field(None, alias="issueYear") - f04_34_material: Optional[str] = Field(None, alias="material") - f04_35_denomination: Optional[str] = Field(None, alias="denomination") - f04_36_issue_quantity: Optional[str] = Field(None, alias="issueQuantity") - - # f05 价格信息 - f05_40_cost_price: Optional[float] = Field(None, ge=0, alias="costPrice") - f05_41_target_price: Optional[float] = Field(None, ge=0, alias="targetPrice") - f05_42_goal_price: Optional[float] = Field(None, ge=0, alias="goalPrice") - f05_43_repair_fee: Optional[float] = Field(None, ge=0, alias="repairFee") - f05_44_grading_fee: Optional[float] = Field(None, ge=0, alias="gradingFee") - - # f06 其他信息 - f06_50_purpose: Optional[str] = Field(None, alias="purpose") - - model_config = ConfigDict(from_attributes=True, populate_by_name=True) - - -class CollectionImageResponse(BaseModel): - f99_90_id: str - filename: str - original_name: Optional[str] = None - path: Optional[str] = None - f99_92_created_at: Optional[datetime] = None - - model_config = ConfigDict(from_attributes=True) - - -class CollectionResponse(CollectionBase): - f99_90_id: str = Field(..., alias="id") - f99_91_user_id: str = Field(..., alias="userId") - f99_92_created_at: Optional[datetime] = Field(None, alias="createdAt") - f99_93_updated_at: Optional[datetime] = Field(None, alias="updatedAt") - images: List[CollectionImageResponse] = [] - - model_config = ConfigDict(from_attributes=True, populate_by_name=True) - - -class CollectionListResponse(BaseModel): - data: List[CollectionResponse] - pagination: dict - - -# ============ 操作日志相关 ============ - -class OperationBase(BaseModel): - type: str = Field(..., max_length=50) - price: Optional[float] = None - note: Optional[str] = None - - -class OperationCreate(OperationBase): - f99_91_user_id: str - - -class OperationResponse(OperationBase): - f99_90_id: str - f99_91_user_id: str - f99_93_created_at: Optional[datetime] = None - - model_config = ConfigDict(from_attributes=True) - - -# ============ OCR 相关 ============ - -class OCRRequest(BaseModel): - image: str - ocr_provider: Optional[str] = "aliyun" - - -class OCRResponse(BaseModel): - text: str - confidence: float - fields: Optional[dict] = None - - -# ============ 通用响应 ============ - -class Token(BaseModel): - access_token: str - token_type: str = "bearer" - - -class TokenData(BaseModel): - f99_90_user_id: Optional[str] = None - - -class MessageResponse(BaseModel): - message: str - - -class ErrorResponse(BaseModel): - detail: str diff --git a/backend_old/app/services/oss.py b/backend_old/app/services/oss.py deleted file mode 100644 index 6c52c87..0000000 --- a/backend_old/app/services/oss.py +++ /dev/null @@ -1,126 +0,0 @@ -# 阿里云OSS服务 -import os -import uuid -import datetime -from typing import Optional -import oss2 -from PIL import Image -import io - -# OSS配置 -OSS_CONFIG = { - "access_key_id": os.getenv("OSS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"), - "access_key_secret": os.getenv("OSS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"), - "bucket_name": "jiachenlong-oss", - "endpoint": "oss-cn-hangzhou.aliyuncs.com", - "public_url": "https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com" -} - -# 图片压缩配置 -IMAGE_CONFIG = { - "max_size": 1024 * 1024, # 1MB - "max_width": 2048, - "max_height": 2048, - "quality": 85, - "format": "JPEG" -} - -# 初始化OSS -auth = oss2.Auth(OSS_CONFIG["access_key_id"], OSS_CONFIG["access_key_secret"]) -bucket = oss2.Bucket(auth, OSS_CONFIG["endpoint"], OSS_CONFIG["bucket_name"]) - - -def get_oss_path(prefix: str, user_id: str = None, filename: str = None) -> str: - """生成OSS存储路径""" - now = datetime.datetime.now() - year = now.strftime("%Y") - month = now.strftime("%m") - day = now.strftime("%d") - - if filename: - ext = filename.split('.')[-1] if '.' in filename else 'jpg' - unique_name = f"{uuid.uuid4().hex}.{ext}" - else: - unique_name = f"{uuid.uuid4().hex}.jpg" - - if user_id: - path = f"{prefix}/{user_id}/{year}/{month}/{unique_name}" - else: - path = f"{prefix}/{year}/{month}/{day}/{unique_name}" - - return path, unique_name - - -def upload_to_oss(file_data: bytes, oss_key: str, compress: bool = True) -> str: - """上传文件到OSS,返回公网URL""" - try: - # 如果是图片,进行压缩 - if compress and any(oss_key.lower().endswith(ext) for ext in ['.jpg', '.jpeg', '.png', '.webp']): - file_data = compress_image(file_data) - - # 上传文件 - result = bucket.put_object(oss_key, file_data) - - if result.status == 200: - # 返回公网URL - return f"{OSS_CONFIG['public_url']}/{oss_key}" - else: - raise Exception(f"OSS上传失败: {result.status}") - - except Exception as e: - raise Exception(f"OSS上传失败: {str(e)}") - - -def delete_from_oss(oss_key: str) -> bool: - """从OSS删除文件""" - try: - result = bucket.delete_object(oss_key) - return result.status == 204 - except Exception as e: - print(f"OSS删除失败: {str(e)}") - return False - - -def get_public_url(oss_key: str) -> str: - """获取公网URL""" - return f"{OSS_CONFIG['public_url']}/{oss_key}" - - -def compress_image(image_data: bytes, max_size: int = None) -> bytes: - """压缩图片到指定大小以内""" - if max_size is None: - max_size = IMAGE_CONFIG["max_size"] - - # 如果已经小于限制,直接返回 - if len(image_data) <= max_size: - return image_data - - # 打开图片 - img = Image.open(io.BytesIO(image_data)) - - # 如果是PNG且有透明通道,转换为RGB - if img.mode in ('RGBA', 'P'): - img = img.convert('RGB') - - # 逐步降低质量直到达到目标大小 - quality = 95 - compressed_data = image_data - - while quality > 30 and len(compressed_data) > max_size: - output = io.BytesIO() - img.save(output, format=IMAGE_CONFIG["format"], quality=quality, optimize=True) - compressed_data = output.getvalue() - quality -= 10 - - # 如果还是太大,缩小尺寸 - if len(compressed_data) > max_size: - width, height = img.size - while len(compressed_data) > max_size and width > 400: - width = int(width * 0.8) - height = int(height * 0.8) - img_resized = img.resize((width, height), Image.Resampling.LANCZOS) - output = io.BytesIO() - img_resized.save(output, format=IMAGE_CONFIG["format"], quality=80, optimize=True) - compressed_data = output.getvalue() - - return compressed_data diff --git a/backend_old/app/services/sms.py b/backend_old/app/services/sms.py deleted file mode 100644 index e8a0a7b..0000000 --- a/backend_old/app/services/sms.py +++ /dev/null @@ -1,106 +0,0 @@ -# 阿里云短信服务 -import os -import random -import string -import time -from datetime import datetime, timedelta -from typing import Optional - -# 阿里云短信配置 -SMS_CONFIG = { - "access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"), - "access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"), - "sign_name": "阿里云", - "template_code": "100001", -} - -# 验证码缓存(生产环境建议用Redis) -# 格式: { phone: { code: "123456", expire: 1234567890 } } -VERIFICATION_CODES = {} - - -def generate_code(length: int = 6) -> str: - """生成6位数字验证码""" - return ''.join(random.choices(string.digits, k=length)) - - -def send_verification_code(phone: str) -> dict: - """发送短信验证码""" - from alibabacloud_dysmsapi20170525 import models - from alibabacloud_dysmsapi20170525.client import Client - from alibabacloud_tea_openapi import models as open_models - - try: - # 生成验证码 - code = generate_code(6) - - # 配置客户端 - config = open_models.Config( - access_key_id=SMS_CONFIG["access_key_id"], - access_key_secret=SMS_CONFIG["access_key_secret"], - ) - config.endpoint = "dysmsapi.aliyuncs.com" - config.region_id = "cn-hangzhou" - - client = Client(config) - - # 构造请求 - request = models.SendSmsRequest( - phone_numbers=phone, - sign_name=SMS_CONFIG["sign_name"], - template_code=SMS_CONFIG["template_code"], - template_param=f'{{"code":"{code}"}}' - ) - - # 发送 - response = client.send_sms(request) - - # 检查结果 - if response.body.code == "OK": - # 保存验证码 - VERIFICATION_CODES[phone] = { - "code": code, - "expire": int(time.time()) + 300 # 5分钟有效 - } - return { - "success": True, - "message": "验证码已发送", - "expire": 300 - } - else: - return { - "success": False, - "message": f"发送失败: {response.body.message}" - } - - except Exception as e: - return { - "success": False, - "message": f"发送失败: {str(e)}" - } - - -def verify_code(phone: str, code: str) -> bool: - """验证验证码""" - if phone not in VERIFICATION_CODES: - return False - - stored = VERIFICATION_CODES[phone] - - # 检查是否过期 - if int(time.time()) > stored["expire"]: - del VERIFICATION_CODES[phone] - return False - - # 验证码匹配 - if stored["code"] == code: - # 验证成功,删除验证码 - del VERIFICATION_CODES[phone] - return True - - return False - - -def check_code_exists(phone: str) -> bool: - """检查是否已发送过验证码""" - return phone in VERIFICATION_CODES diff --git a/backend_old/requirements.txt b/backend_old/requirements.txt deleted file mode 100644 index bda1a26..0000000 --- a/backend_old/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -# 甲辰藏品管理系统后端 - Python 依赖 -# 版本:v1.0.0 - -fastapi==0.109.0 -uvicorn[standard]==0.27.0 -sqlalchemy==2.0.25 -psycopg2-binary==2.9.9 -pydantic==2.5.3 -python-jose[cryptography]==3.3.0 -bcrypt==4.1.2 -python-multipart==0.0.6 -pillow==10.2.0 -dashscope==1.14.1 diff --git a/backend_old/uploads/.gitkeep b/backend_old/uploads/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/package_new.json b/frontend/package_new.json deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/test.js b/frontend/src/test.js deleted file mode 100644 index 2703c10..0000000 --- a/frontend/src/test.js +++ /dev/null @@ -1 +0,0 @@ -export const TEST_CONST = "test_value_12345"; diff --git a/start.sh b/start.sh deleted file mode 100755 index d326932..0000000 --- a/start.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -cd /root/jiachenlong/backend -export DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/zodiac -export SECRET_KEY=production-secret-key-c-env -export OSS_ACCESS_KEY_ID=LTAI5t6HUnpFBLEK9194kPVG -export OSS_ACCESS_KEY_SECRET=LEr4Q8yRxb8D5b24cKfCwlt4MMoke1 -export SMS_ACCESS_KEY_ID=LTAI5tQAx5niD7JQVqGE5acE -export SMS_ACCESS_KEY_SECRET=QsQFAEKBkaNynIoKyvdIi3BUyWVZu1 -export SMS_SIGN_NAME=苏州算力 -export SMS_TEMPLATE_CODE=SMS_501590956 -nohup python3 -m uvicorn app.main:app --host 0.0.0.0 --port 3000 > /tmp/uvicorn.log 2>&1 & -echo '后端服务已启动' diff --git a/user_agreement.html b/user_agreement.html deleted file mode 100644 index f06cf22..0000000 --- a/user_agreement.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - 用户协议 - 甲辰收藏 - - - -
-

甲辰收藏平台用户协议

-
版本生效日期:自注册之日起
- -

特别提示:本协议涉及您的重大权利义务,加粗条款请您重点阅读。您完成注册、登录或继续使用本平台服务,即视为已充分阅读、理解并同意本协议全部内容。本协议适用《中华人民共和国民法典》《网络安全法》《个人信息保护法》《电子商务法》等法律法规。

- -

一、协议主体与适用范围

-
    -
  1. 本协议由您与甲辰收藏平台运营主体(以下简称"平台")签订,约束您使用平台藏品管理,信息展示,数据存储,社区互动等全部服务。
  2. -
  3. 平台发布的隐私政策、藏品发布规则、交易规范等均为本协议组成部分,与本协议具有同等法律效力。
  4. -
  5. 您承诺为具备完全民事行为能力的自然人/法人;未成年人使用需监护人同意并陪同。
  6. -
- -

二、个人信息收集与使用

-

(一)收集原则

-

平台遵循合法、正当、必要、诚信、最小必要原则收集信息,仅为实现服务功能所必需,不强制收集非必要信息。

- -

(二)收集范围与目的

-
    -
  1. 注册与身份信息:用户名、手机号、邮箱(用于账号创建,安全验证、客服联系)。
  2. -
  3. 藏品与行为信息:藏品上传内容、收藏记录、浏览操作、发布评论、交易数据(用于藏品管理,服务优化、风控核验)。
  4. -
  5. 设备与网络信息:设备型号、系统版本、IP 地址、日志信息(用于安全防护、故障排查,防作弊)。
  6. -
  7. 位置信息:仅在您主动开启定位权限时收集,用于同城展示等可选功能,关闭不影响基础服务。
  8. -
- -

(三)信息使用规则

-
    -
  1. 平台仅在您授权范围内使用信息,不超出约定目的、范围与期限处理。
  2. -
  3. 向第三方共享信息时,将单独告知并取得您明确同意,法律法规要求除外。
  4. -
  5. 您有权查阅、更正、删除个人信息,申请注销账号,平台在核验身份后依法处理。
  6. -
  7. 平台采取加密、去标识化、权限管控等措施保护信息安全,制定安全事件应急预案。
  8. -
- -

三、公开信息使用与免责条款

-
    -
  1. 公开信息定义:您通过平台主动发布、设置为公开可见的藏品资料、图文、评论、动态等内容,均属于您自行公开的信息。
  2. -
  3. 授权使用:您同意平台可在合理范围内使用您的公开信息,用于藏品展示、平台运营、合规审核、服务推广等,不侵犯您合法权益。
  4. -
  5. 法定免责依据:根据《个人信息保护法》第二十七条,平台处理您自行合法公开的信息,在无明确拒绝且不对您权益造成重大影响的情形下,无需另行取得单独同意。
  6. -
  7. 第三方行为免责: -
      -
    • 公开信息可被其他用户浏览、复制、存储、转发,平台无法完全控制第三方使用行为。
    • -
    • 因第三方擅自使用、转载、篡改您公开信息引发的纠纷、损失,平台不承担责任。
    • -
    -
  8. -
  9. 内容责任自负: -
      -
    • 您对公开信息的真实性、合法性、原创性承担全部责任,不得侵犯第三方知识产权、肖像权、名誉权等。
    • -
    • 如因您发布违法、侵权内容导致平台受损,您应承担全部赔偿责任。
    • -
    -
  10. -
  11. 平台管理边界:平台仅依据法律法规与平台规则进行内容审核,对用户自主发布的公开信息不做修改、篡改,不承担真实性担保责任。
  12. -
- -

四、用户权利与义务

-
    -
  1. 您有权使用平台基础服务,对账号与密码安全负责,不得转借、出售账号。
  2. -
  3. 不得发布违法违规、侵权、虚假、低俗等违反法律法规与公序良俗的内容。
  4. -
  5. 不得利用平台从事洗钱、诈骗、非法交易等违法活动。
  6. -
  7. 您对自行上传的藏品素材、文字等享有知识产权,授权平台在服务范围内非独占使用。
  8. -
- -

五、知识产权条款

-
    -
  1. 平台所有文字、图标、界面设计、软件代码等知识产权归平台所有,受法律保护。
  2. -
  3. 您上传的原创内容知识产权归您所有;您授权平台为提供服务之目的,使用、存储、展示、传播该内容。
  4. -
  5. 未经权利人书面许可,任何主体不得复制、改编、传播、商用平台内容或用户原创内容。
  6. -
- -

六、服务变更、中断与终止

-
    -
  1. 平台因维护、升级、政策调整需变更或暂停服务的,将提前公示;因不可抗力、监管要求、第三方故障导致服务中断的,平台不承担违约责任。
  2. -
  3. 平台有权对违规账号采取警示、限流、删帖、封禁等处理措施。
  4. -
  5. 您可随时停止使用服务;账号注销后,平台按法律法规留存相关数据,逾期依法删除。
  6. -
- -

七、免责声明(法律允许范围内)

-
    -
  1. 平台按"现状"提供服务,对服务及时性、安全性、稳定性不作绝对担保。
  2. -
  3. 平台不对藏品真伪、价值、权属作明示或暗示保证,藏品鉴定与价值判断由您自行负责。
  4. -
  5. 法律允许的最大范围内,平台不对间接损失、利润损失、数据丢失等承担赔偿责任;因平台故意或重大过失导致的损失除外。
  6. -
  7. 因您自身操作不当、账号保管不善、第三方侵权等导致的损失,由您自行承担。
  8. -
- -

八、协议修改与争议解决

-
    -
  1. 平台修改协议将提前7日公示,您继续使用视为接受修订;如不同意,可停止使用并注销账号。
  2. -
  3. 因本协议产生争议,双方协商解决;协商不成,提交平台运营主体所在地有管辖权的人民法院诉讼解决。
  4. -
  5. 本协议适用中华人民共和国大陆地区法律(不含港澳台法律)。
  6. -
- -

九、联系与通知

-

平台联系方式:aicoolbot@163.com;您可通过客服渠道咨询协议、隐私、投诉等相关事宜。

-
- -