diff --git a/apps/frontend-react/index.html b/apps/frontend-react/index.html new file mode 100644 index 00000000..a730e0cb --- /dev/null +++ b/apps/frontend-react/index.html @@ -0,0 +1,12 @@ + + + + + + order-site react + + +
+ + + diff --git a/apps/frontend-react/package-lock.json b/apps/frontend-react/package-lock.json new file mode 100644 index 00000000..512f8932 --- /dev/null +++ b/apps/frontend-react/package-lock.json @@ -0,0 +1,2687 @@ +{ + "name": "order-site-frontend-react", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "order-site-frontend-react", + "version": "0.1.0", + "dependencies": { + "@ant-design/icons": "^6.1.0", + "@tanstack/react-query": "^5.90.12", + "antd": "^6.1.1", + "axios": "^1.13.2", + "qrcode": "^1.5.4", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router": "^7.10.1" + }, + "devDependencies": { + "@types/node": "^25.6.0", + "@types/qrcode": "^1.5.6", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "typescript": "~6.0.2", + "vite": "^8.0.7" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", + "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", + "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.2", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.3.2.tgz", + "integrity": "sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.5.0", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.5.0.tgz", + "integrity": "sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", + "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz", + "integrity": "sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.17.0.tgz", + "integrity": "sha512-3cVNG0zrQF1PoXq262L3wGCU+/YLEC1mGSVHDl577dQmA0ZKkXFbY6nwyXo+beCcM7buo49t24jkr+QZdL7O8w==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.8.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.1.tgz", + "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.2.tgz", + "integrity": "sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.10.0.tgz", + "integrity": "sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.3.3", + "@rc-component/portal": "^2.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.1.3", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz", + "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.5.tgz", + "integrity": "sha512-d24EYtvUOBhxEtSd/EqIu9DaMuqrWF2IRIvAFCTM6NQ/GJIYNr8DvEpUSUlv2uPxEJ0ZPwYQ+wwlGIAaiHvdrw==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^6.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.9.0.tgz", + "integrity": "sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.3.1.tgz", + "integrity": "sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==", + "license": "MIT", + "dependencies": { + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.10.0.tgz", + "integrity": "sha512-CI1njYUVY0NjHtLhNoVmXlJyy568Sfep9Wsak6vmGjtT6uazx98djGYlCXz2xkHhEm73g91Y3MTvzUyE5avI7w==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.3.0", + "@rc-component/menu": "~1.4.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.4.1.tgz", + "integrity": "sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz", + "integrity": "sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.3.tgz", + "integrity": "sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-2.0.7.tgz", + "integrity": "sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.1.tgz", + "integrity": "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.4.0.tgz", + "integrity": "sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.11.0.tgz", + "integrity": "sha512-6qXGKtoJvO8sUd17m5cyNEbEJub0zflCHnaZTBBmj63DPRZYc0WEHN8rp6hFSl+yMCJS/dJY5G+1fQ8bLCuD7A==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.1.tgz", + "integrity": "sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-2.0.0.tgz", + "integrity": "sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", + "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.8.2.tgz", + "integrity": "sha512-HQ9zuYqjfZTlcEMWlU1GAPBajd2OHIMVHyjZSGVTCVARwkfCgvXZMTEn0cduy3L+ejAKkaZluOQvxovZoaJaQw==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.1.1.tgz", + "integrity": "sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.3.tgz", + "integrity": "sha512-AiONZrgXVIewFC2Pk3GTqbov+sJhWDZZ96QbJZQGNSrX/vcMzkqoFJVcipGH4OY6EmFSKApwbOhQth6DVyB6gQ==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.11.0.tgz", + "integrity": "sha512-hA/drZYOVa/MMIb4M2fWf3yaTyTG4qVuIABmghvEhyfw2nBob5VTH69lMCDjSVKmgODjO6nWlCV+gVn3xBrj5Q==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.4.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.4.0.tgz", + "integrity": "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.3.2.tgz", + "integrity": "sha512-bJFj46wEkpBPnWyTm18XmgAgNQ/4YvprxMOPPY2a6rmhGJYxLuNKEFiL5Qej4Qctu9wHJm8WW+v2SYskafE0kA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.11.0.tgz", + "integrity": "sha512-EhS0X0wtUhBfK4S5TlpSY3MR9ndPMGgujtt1PJW3Ej+ToAlnS/6ohYURtCoXBYGqazUwHmgQGVUDsfpVwhWPkg==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.8.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.1.tgz", + "integrity": "sha512-LNsYvz60mrLJ/kRvKcHE7boUvcQfVMCfRqZ71x3Fo9AOiZ1KKIEqkzMA8DNvz2V3Bcvir/vwQNn7JF1NPODQ7Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.2.0", + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.1.tgz", + "integrity": "sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz", + "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.2.0.tgz", + "integrity": "sha512-iavRm1Jo4GDbASQwdGa7jFyk93RvSOo9xHyBT4QL1pgFJj/Fdf1G+3RErH7/7BmAMvx2AkF62mjGYxDbXsK9TQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", + "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz", + "integrity": "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz", + "integrity": "sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/node": { + "version": "25.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.4.tgz", + "integrity": "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.0.tgz", + "integrity": "sha512-9zbVc9UukfGuqCvIAov01nlpDQWfARNmZQyt21ZhqLX7ilXmi4cdkp12xA48WEmXRXwZvno8A03qQuGE9JG8fg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.2", + "@ant-design/cssinjs-utils": "^2.1.2", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.3.1", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.29.2", + "@rc-component/cascader": "~1.17.0", + "@rc-component/checkbox": "~2.0.0", + "@rc-component/collapse": "~1.2.0", + "@rc-component/color-picker": "~3.1.1", + "@rc-component/dialog": "~1.10.0", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.2", + "@rc-component/form": "~1.8.5", + "@rc-component/image": "~1.9.0", + "@rc-component/input": "~1.3.1", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.10.0", + "@rc-component/menu": "~1.4.1", + "@rc-component/motion": "^1.3.3", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~2.0.7", + "@rc-component/pagination": "~1.4.0", + "@rc-component/picker": "~1.11.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~2.0.0", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.8.2", + "@rc-component/slider": "~1.1.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.10.2", + "@rc-component/tabs": "~1.11.0", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.4.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/tree-select": "~1.11.0", + "@rc-component/trigger": "^3.9.1", + "@rc-component/upload": "~1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/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/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/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/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/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/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/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/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "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.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/rolldown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", + "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.138.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.4", + "@rolldown/binding-darwin-arm64": "1.1.4", + "@rolldown/binding-darwin-x64": "1.1.4", + "@rolldown/binding-freebsd-x64": "1.1.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", + "@rolldown/binding-linux-arm64-gnu": "1.1.4", + "@rolldown/binding-linux-arm64-musl": "1.1.4", + "@rolldown/binding-linux-ppc64-gnu": "1.1.4", + "@rolldown/binding-linux-s390x-gnu": "1.1.4", + "@rolldown/binding-linux-x64-gnu": "1.1.4", + "@rolldown/binding-linux-x64-musl": "1.1.4", + "@rolldown/binding-openharmony-arm64": "1.1.4", + "@rolldown/binding-wasm32-wasi": "1.1.4", + "@rolldown/binding-win32-arm64-msvc": "1.1.4", + "@rolldown/binding-win32-x64-msvc": "1.1.4" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/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/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", + "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/apps/frontend-react/package.json b/apps/frontend-react/package.json new file mode 100644 index 00000000..6d5474d6 --- /dev/null +++ b/apps/frontend-react/package.json @@ -0,0 +1,31 @@ +{ + "name": "order-site-frontend-react", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "typecheck": "tsc -b --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^6.1.0", + "@tanstack/react-query": "^5.90.12", + "antd": "^6.1.1", + "axios": "^1.13.2", + "qrcode": "^1.5.4", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router": "^7.10.1" + }, + "devDependencies": { + "@types/node": "^25.6.0", + "@types/qrcode": "^1.5.6", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "typescript": "~6.0.2", + "vite": "^8.0.7" + } +} diff --git a/apps/frontend-react/src/App.tsx b/apps/frontend-react/src/App.tsx new file mode 100644 index 00000000..61111624 --- /dev/null +++ b/apps/frontend-react/src/App.tsx @@ -0,0 +1,79 @@ +import { Suspense, lazy } from 'react' +import { Spin } from 'antd' +import { HashRouter, Navigate, Outlet, Route, Routes, useLocation } from 'react-router' + +import { getAdminRole, hasAdminSession } from '@/utils/admin-auth' +import AdminLayout from '@/layouts/AdminLayout' + +const AdminDashboardPage = lazy(() => import('@/pages/admin/AdminDashboardPage')) +const AdminLoginPage = lazy(() => import('@/pages/admin/AdminLoginPage')) +const AdminOrderDetailPage = lazy(() => import('@/pages/admin/AdminOrderDetailPage')) +const AdminOrdersPage = lazy(() => import('@/pages/admin/AdminOrdersPage')) +const AdminTaskDetailPage = lazy(() => import('@/pages/admin/AdminTaskDetailPage')) +const AdminTasksPage = lazy(() => import('@/pages/admin/AdminTasksPage')) +const NotFoundPage = lazy(() => import('@/pages/NotFoundPage')) + +function RequireAdmin() { + const location = useLocation() + + if (!hasAdminSession()) { + return + } + + return +} + +function RequireRole({ roles }: { roles: Array<'admin' | 'operator' | 'support'> }) { + if (!roles.includes(getAdminRole())) { + return + } + + return +} + +function LoginRoute() { + if (hasAdminSession()) { + return + } + + return +} + +export default function App() { + return ( + + }> + + } /> + } /> + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + }> + } /> + } /> + } /> + } /> + + + + } /> + } /> + + + + ) +} + +function RouteLoading() { + return ( +
+ +
+ ) +} diff --git a/apps/frontend-react/src/components/admin/JsonPreview.tsx b/apps/frontend-react/src/components/admin/JsonPreview.tsx new file mode 100644 index 00000000..f9bebf23 --- /dev/null +++ b/apps/frontend-react/src/components/admin/JsonPreview.tsx @@ -0,0 +1,7 @@ +type JsonPreviewProps = { + value: unknown +} + +export default function JsonPreview({ value }: JsonPreviewProps) { + return
{JSON.stringify(value ?? {}, null, 2)}
+} diff --git a/apps/frontend-react/src/components/admin/PageHeader.tsx b/apps/frontend-react/src/components/admin/PageHeader.tsx new file mode 100644 index 00000000..051850a8 --- /dev/null +++ b/apps/frontend-react/src/components/admin/PageHeader.tsx @@ -0,0 +1,22 @@ +import { Space, Typography } from 'antd' +import type { ReactNode } from 'react' + +type PageHeaderProps = { + title: string + description?: string + extra?: ReactNode +} + +export default function PageHeader({ title, description, extra }: PageHeaderProps) { + return ( +
+
+ + {title} + + {description ?

{description}

: null} +
+ {extra ? {extra} : null} +
+ ) +} diff --git a/apps/frontend-react/src/components/admin/StatusTag.tsx b/apps/frontend-react/src/components/admin/StatusTag.tsx new file mode 100644 index 00000000..5569fd9d --- /dev/null +++ b/apps/frontend-react/src/components/admin/StatusTag.tsx @@ -0,0 +1,75 @@ +import { Tag } from 'antd' + +type StatusTagProps = { + value?: string | null + kind?: 'task' | 'pay' | 'delivery' | 'resource' | 'customer' | 'role' | 'plain' +} + +const STATUS_LABELS: Record = { + active: '生效中', + claimed: '已创建会话', + closed: '已关闭', + completed: '已完成', + consumed: '已核销', + dispatched_pending_return: '待退号', + failed: '失败', + link_generated: '已生成链接', + manual_review: '人工处理', + paid: '已支付', + pending: '待处理', + pending_binding_prepare: '待准备资源', + pending_claim: '待领取', + pending_payment: '待支付', + redeemed: '已兑换', + redeeming: '兑换中', + refunded: '已退款', + retry_pending: '待重试', + role_confirmed: '已确认角色', + unpaid: '未支付', + waiting_binding: '待绑定', +} + +const STATUS_COLORS: Record = { + active: 'green', + claimed: 'blue', + closed: 'default', + completed: 'green', + consumed: 'green', + failed: 'red', + link_generated: 'cyan', + manual_review: 'orange', + paid: 'green', + pending: 'gold', + pending_binding_prepare: 'gold', + pending_claim: 'gold', + pending_payment: 'default', + redeemed: 'green', + redeeming: 'blue', + refunded: 'purple', + retry_pending: 'volcano', + role_confirmed: 'blue', + unpaid: 'default', + waiting_binding: 'gold', +} + +export default function StatusTag({ value, kind = 'plain' }: StatusTagProps) { + const normalized = String(value || '').trim() + + if (!normalized) { + return - + } + + const key = normalized.toLowerCase() + const label = STATUS_LABELS[key] || normalized + const color = STATUS_COLORS[key] || resolveFallbackColor(kind) + + return {label === normalized ? normalized : `${label} (${normalized})`} +} + +function resolveFallbackColor(kind: StatusTagProps['kind']) { + if (kind === 'pay' || kind === 'delivery') return 'blue' + if (kind === 'task') return 'geekblue' + if (kind === 'resource' || kind === 'customer') return 'cyan' + if (kind === 'role') return 'purple' + return 'default' +} diff --git a/apps/frontend-react/src/domain/task-status.ts b/apps/frontend-react/src/domain/task-status.ts new file mode 100644 index 00000000..796e27da --- /dev/null +++ b/apps/frontend-react/src/domain/task-status.ts @@ -0,0 +1,59 @@ +export const TASK_STATUS = { + PENDING_PAYMENT: "pending_payment", + PAID: "paid", + LINK_GENERATED: "link_generated", + CLAIMED: "claimed", + PENDING_BINDING_PREPARE: "pending_binding_prepare", + WAITING_BINDING: "waiting_binding", + ROLE_CONFIRMED: "role_confirmed", + REDEEMING: "redeeming", + DISPATCHED_PENDING_RETURN: "dispatched_pending_return", + COMPLETED: "completed", + REDEEMED: "redeemed", + RETRY_PENDING: "retry_pending", + MANUAL_REVIEW: "manual_review", + FAILED: "failed", + EXPIRED: "expired", + CLOSED: "closed", +} as const; + +export type KnownTaskStatus = (typeof TASK_STATUS)[keyof typeof TASK_STATUS]; +export type TaskStatus = KnownTaskStatus | (string & {}); + +const CLAIM_INACTIVE_STATUSES = new Set([ + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, + TASK_STATUS.EXPIRED, + TASK_STATUS.CLOSED, +]); + +const KUAISHOU_CLOUD_RESULT_STATUSES = new Set([ + TASK_STATUS.DISPATCHED_PENDING_RETURN, + TASK_STATUS.COMPLETED, + TASK_STATUS.REDEEMED, + TASK_STATUS.MANUAL_REVIEW, + TASK_STATUS.FAILED, +]); + +export function normalizeTaskStatus(value: unknown): TaskStatus { + return String(value || "").trim() as TaskStatus; +} + +export function isClaimInactiveTaskStatus(status: unknown): boolean { + return CLAIM_INACTIVE_STATUSES.has(normalizeTaskStatus(status)); +} + +export function isKuaishouCloudRoleConfirmedStatus(status: unknown): boolean { + return normalizeTaskStatus(status) === TASK_STATUS.ROLE_CONFIRMED; +} + +export function isKuaishouCloudCompletedStatus(status: unknown): boolean { + const normalized = normalizeTaskStatus(status); + return normalized === TASK_STATUS.COMPLETED || normalized === TASK_STATUS.REDEEMED; +} + +export function hasKuaishouCloudRedeemResultStatus(status: unknown): boolean { + return KUAISHOU_CLOUD_RESULT_STATUSES.has(normalizeTaskStatus(status)); +} diff --git a/apps/frontend-react/src/layouts/AdminLayout.tsx b/apps/frontend-react/src/layouts/AdminLayout.tsx new file mode 100644 index 00000000..5e09b2be --- /dev/null +++ b/apps/frontend-react/src/layouts/AdminLayout.tsx @@ -0,0 +1,149 @@ +import { + AuditOutlined, + DashboardOutlined, + FileSearchOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined, + OrderedListOutlined, + SettingOutlined, + ShopOutlined, + TeamOutlined, + UnorderedListOutlined, + UserOutlined, +} from '@ant-design/icons' +import { App, Button, Dropdown, Layout, Menu, Space, Typography } from 'antd' +import type { MenuProps } from 'antd' +import { useMemo, useState } from 'react' +import { Outlet, useLocation, useNavigate } from 'react-router' + +import { logoutAdmin } from '@/services/admin' +import { + clearAdminSession, + getAdminRole, + getAdminTokenExpiresAt, + getAdminUsername, +} from '@/utils/admin-auth' +import { formatAdminDateTime } from '@/utils/admin-time' + +const SIDEBAR_COLLAPSED_KEY = 'react-admin-sidebar-collapsed' + +export default function AdminLayout() { + const navigate = useNavigate() + const location = useLocation() + const { message } = App.useApp() + const [collapsed, setCollapsed] = useState( + () => window.localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === '1', + ) + const role = getAdminRole() + const username = getAdminUsername() || 'admin' + const expiresAt = getAdminTokenExpiresAt() + + const menuItems = useMemo(() => { + const items: MenuProps['items'] = [ + { key: '/admin/dashboard', icon: , label: '概览' }, + { key: '/admin/orders', icon: , label: '订单' }, + { key: '/admin/tasks', icon: , label: '任务' }, + ] + + if (role === 'admin') { + items.splice(1, 0, { key: '/admin/users', icon: , label: '用户' }) + items.push( + { key: '/admin/cloudtentacles-records', icon: , label: '发货记录' }, + { + key: '/admin/platform-shops', + icon: , + label: '平台配置', + }, + { key: '/admin/platform-fulfillment', icon: , label: '履约配置' }, + { key: '/admin/audit-logs', icon: , label: '审计' }, + ) + } + + return items + }, [role]) + + async function submitLogout() { + try { + await logoutAdmin() + } catch { + // 后端退出是无状态的,本地清理优先。 + } finally { + clearAdminSession() + message.success('已退出后台') + navigate('/admin/login', { replace: true }) + } + } + + function toggleCollapsed() { + const next = !collapsed + setCollapsed(next) + window.localStorage.setItem(SIDEBAR_COLLAPSED_KEY, next ? '1' : '0') + } + + const selectedKey = resolveSelectedKey(location.pathname) + + return ( + + +
+ {collapsed ? 'OS' : '运营后台'} + {!collapsed ? 订单与交付管理 : null} +
+ navigate(String(key))} + /> + + + + + + + + + + + + + ) +} + +function resolveSelectedKey(pathname: string) { + if (pathname.startsWith('/admin/orders')) return '/admin/orders' + if (pathname.startsWith('/admin/tasks')) return '/admin/tasks' + return pathname +} + +function roleLabel(role: string) { + if (role === 'admin') return '管理员' + if (role === 'operator') return '普通运营' + return '客服' +} diff --git a/apps/frontend-react/src/lib/feedback.ts b/apps/frontend-react/src/lib/feedback.ts new file mode 100644 index 00000000..ecd22e13 --- /dev/null +++ b/apps/frontend-react/src/lib/feedback.ts @@ -0,0 +1,86 @@ +import { Input, Modal, message as antdMessage } from 'antd' +import type { ModalFuncProps } from 'antd' +import { createElement } from 'react' + +type MessageOptions = { + duration?: number +} + +type PromptResult = { + value: string +} + +export function showSuccess(message: string, options: MessageOptions = {}) { + return antdMessage.success({ + content: message, + duration: options.duration, + }) +} + +export function showError(message: string, options: MessageOptions = {}) { + return antdMessage.error({ + content: message, + duration: options.duration, + }) +} + +export function showConfirm( + message: string, + title = '确认操作', + options: ModalFuncProps = {}, +) { + return new Promise((resolve, reject) => { + Modal.confirm({ + title, + content: message, + okText: '继续执行', + cancelText: '取消', + centered: true, + ...options, + onOk: () => { + options.onOk?.() + resolve() + }, + onCancel: () => { + options.onCancel?.() + reject('cancel') + }, + }) + }) +} + +export function showPrompt(message: string, title: string, options: ModalFuncProps = {}) { + let value = '' + + return new Promise((resolve, reject) => { + Modal.confirm({ + title, + content: createElement('div', { className: 'feedback-prompt' }, [ + createElement('p', { key: 'message' }, message), + createElement(Input, { + key: 'input', + autoFocus: true, + onChange: (event) => { + value = event.target.value + }, + }), + ]), + okText: '提交', + cancelText: '取消', + centered: true, + ...options, + onOk: () => { + options.onOk?.() + resolve({ value }) + }, + onCancel: () => { + options.onCancel?.() + reject('cancel') + }, + }) + }) +} + +export function isFeedbackDismissed(error: unknown) { + return error === 'cancel' || error === 'close' +} diff --git a/apps/frontend-react/src/lib/http.ts b/apps/frontend-react/src/lib/http.ts new file mode 100644 index 00000000..dfb08252 --- /dev/null +++ b/apps/frontend-react/src/lib/http.ts @@ -0,0 +1,136 @@ +import axios from 'axios' +import { clearAdminSession, getAdminToken } from '@/utils/admin-auth' + +export interface ApiEnvelope { + code: number + msg: string + data: T + errorCode?: string +} + +const http = axios.create({ + baseURL: '/', + timeout: 50_000, + headers: { + 'Content-Type': 'application/json', + }, +}) + +http.interceptors.response.use( + (response) => { + const data = response.data as ApiEnvelope + + // Business-level error: HTTP 200 but code !== 0 + if (typeof data?.code === 'number' && data.code !== 0) { + const error = new Error(data.msg || '操作失败') as Error & { + errorCode?: string + code?: number + } + error.errorCode = data.errorCode + error.code = data.code + return Promise.reject(error) + } + + return response.data + }, + (error) => { + const responseMessage = + typeof error?.response?.data?.msg === 'string' ? error.response.data.msg.trim() : '' + const fallbackMessage = resolveFallbackHttpMessage(error) + const normalizedError = new Error(responseMessage || fallbackMessage) as Error & { + errorCode?: string + status?: number + } + + if (error?.response?.data?.errorCode) { + normalizedError.errorCode = String(error.response.data.errorCode) + } + + if (typeof error?.response?.status === 'number') { + normalizedError.status = error.response.status + } + + if (error?.config?.url && String(error.config.url).startsWith('/api/v1/admin')) { + const status = Number(error?.response?.status || 0) + const errorCode = String(error?.response?.data?.errorCode || '').trim() + + if (status === 401 && shouldClearAdminSession(errorCode)) { + clearAdminSession() + + if ( + window.location.hash.startsWith('#/admin') && + !window.location.hash.startsWith('#/admin/login') + ) { + window.location.hash = '#/admin/login' + } + } + } + + return Promise.reject(normalizedError) + }, +) + +http.interceptors.request.use((config) => { + if (String(config.url || '').startsWith('/api/v1/admin')) { + const token = getAdminToken() + + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + } + + return config +}) + +function resolveFallbackHttpMessage(error: unknown) { + const message = String((error as { message?: string })?.message ?? '') + + if (message.includes('timeout')) { + return '网络超时' + } + + if (message === 'Network Error') { + return '网络连接错误' + } + + if (typeof (error as { response?: { statusText?: string } })?.response?.statusText === 'string') { + return String((error as { response: { statusText: string } }).response.statusText).trim() + } + + return '接口请求失败' +} + +function shouldClearAdminSession(errorCode: string) { + return [ + 'admin_auth_required', + 'admin_auth_invalid', + 'admin_auth_expired', + 'admin_auth_user_invalid', + 'admin_auth_stale', + ].includes(errorCode) +} + +function request(config: Parameters>>[0]) { + return http.request, ApiEnvelope>(config) +} + +export function apiGet(url: string, params?: Record) { + return request({ method: 'GET', url, params }) +} + +export function apiGetBlob(url: string, params?: Record) { + return http.request({ + method: 'GET', + url, + params, + responseType: 'blob', + }) +} + +export function apiPost(url: string, data?: unknown) { + return request({ method: 'POST', url, data: data as Record }) +} + +export function apiDelete(url: string) { + return request({ method: 'DELETE', url }) +} diff --git a/apps/frontend-react/src/main.tsx b/apps/frontend-react/src/main.tsx new file mode 100644 index 00000000..eaa672db --- /dev/null +++ b/apps/frontend-react/src/main.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { App as AntdApp, ConfigProvider } from 'antd' +import zhCN from 'antd/locale/zh_CN' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import 'antd/dist/reset.css' + +import App from './App' +import './styles/main.css' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}) + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + + + , +) diff --git a/apps/frontend-react/src/pages/NotFoundPage.tsx b/apps/frontend-react/src/pages/NotFoundPage.tsx new file mode 100644 index 00000000..d97996d9 --- /dev/null +++ b/apps/frontend-react/src/pages/NotFoundPage.tsx @@ -0,0 +1,20 @@ +import { Button, Empty } from 'antd' +import { useNavigate } from 'react-router' + +type NotFoundPageProps = { + title?: string +} + +export default function NotFoundPage({ title = '页面不存在' }: NotFoundPageProps) { + const navigate = useNavigate() + + return ( +
+ + + +
+ ) +} diff --git a/apps/frontend-react/src/pages/admin/AdminDashboardPage.tsx b/apps/frontend-react/src/pages/admin/AdminDashboardPage.tsx new file mode 100644 index 00000000..e64e58eb --- /dev/null +++ b/apps/frontend-react/src/pages/admin/AdminDashboardPage.tsx @@ -0,0 +1,51 @@ +import { Alert, Card, Col, Row, Skeleton, Statistic } from 'antd' +import { useQuery } from '@tanstack/react-query' + +import PageHeader from '@/components/admin/PageHeader' +import { fetchAdminDashboardSummary } from '@/services/admin' + +const cards = [ + { key: 'todayOrders', label: '今日订单' }, + { key: 'paidPendingClaim', label: '待领取' }, + { key: 'claimingTasks', label: '领取中' }, + { key: 'redeemedToday', label: '今日成功' }, + { key: 'abnormalTasks', label: '异常任务' }, +] as const + +export default function AdminDashboardPage() { + const query = useQuery({ + queryKey: ['admin-dashboard-summary'], + queryFn: () => fetchAdminDashboardSummary(), + }) + const summary = query.data?.data + + return ( +
+ + + {query.error ? ( + + ) : null} + + {query.isLoading ? ( + + + + ) : ( + + {cards.map((card) => ( + + + + + + ))} + + )} +
+ ) +} diff --git a/apps/frontend-react/src/pages/admin/AdminLoginPage.tsx b/apps/frontend-react/src/pages/admin/AdminLoginPage.tsx new file mode 100644 index 00000000..4b2bbfab --- /dev/null +++ b/apps/frontend-react/src/pages/admin/AdminLoginPage.tsx @@ -0,0 +1,74 @@ +import { LockOutlined, UserOutlined } from '@ant-design/icons' +import { App, Button, Card, Form, Input, Typography } from 'antd' +import { useState } from 'react' +import { useNavigate } from 'react-router' + +import { loginAdmin } from '@/services/admin' +import { setAdminSession } from '@/utils/admin-auth' + +type LoginForm = { + username: string + password: string +} + +export default function AdminLoginPage() { + const navigate = useNavigate() + const { message } = App.useApp() + const [loading, setLoading] = useState(false) + + async function submitLogin(values: LoginForm) { + setLoading(true) + + try { + const response = await loginAdmin({ + username: values.username.trim(), + password: values.password.trim(), + }) + setAdminSession(response.data.token, response.data.expiresAt, response.data.user) + message.success('登录成功') + navigate('/admin/dashboard', { replace: true }) + } catch (error) { + message.error(error instanceof Error ? error.message : '后台登录失败') + } finally { + setLoading(false) + } + } + + return ( +
+ +
+ Order Site Admin + 运营后台登录 + + 使用账号密码进入后台,并按角色开放不同操作权限。 + +
+ + layout="vertical" requiredMark={false} onFinish={submitLogin}> + + } placeholder="输入账号" autoComplete="username" /> + + + } + placeholder="输入密码" + autoComplete="current-password" + /> + + + +
+
+ ) +} diff --git a/apps/frontend-react/src/pages/admin/AdminOrderDetailPage.tsx b/apps/frontend-react/src/pages/admin/AdminOrderDetailPage.tsx new file mode 100644 index 00000000..e783d40e --- /dev/null +++ b/apps/frontend-react/src/pages/admin/AdminOrderDetailPage.tsx @@ -0,0 +1,161 @@ +import { ArrowLeftOutlined } from '@ant-design/icons' +import { Alert, Button, Card, Descriptions, Skeleton, Space, Table, Typography } from 'antd' +import type { TableColumnsType } from 'antd' +import { useQuery } from '@tanstack/react-query' +import { useNavigate, useParams } from 'react-router' + +import JsonPreview from '@/components/admin/JsonPreview' +import PageHeader from '@/components/admin/PageHeader' +import StatusTag from '@/components/admin/StatusTag' +import { fetchAdminOrderDetail } from '@/services/admin' +import type { AdminOrderDetail, AdminTaskListItem } from '@/types/admin' +import { formatAdminDateTime } from '@/utils/admin-time' + +export default function AdminOrderDetailPage() { + const navigate = useNavigate() + const { orderId = '' } = useParams() + const query = useQuery({ + queryKey: ['admin-order-detail', orderId], + queryFn: () => fetchAdminOrderDetail(orderId), + enabled: Boolean(orderId), + }) + const detail = query.data?.data + + if (query.isLoading) { + return ( +
+ } /> + + + +
+ ) + } + + if (query.error || !detail) { + return ( +
+ } /> + +
+ ) + } + + const taskColumns: TableColumnsType = [ + { + title: '任务号', + dataIndex: 'taskNo', + minWidth: 180, + render: (_, row) => ( + navigate(`/admin/tasks/${row.taskId}`)}> + {row.taskNo} + + ), + }, + { + title: '状态', + minWidth: 220, + render: (_, row) => ( + + + + + + ), + }, + { + title: '角色', + minWidth: 160, + render: (_, row) => ( +
+ {row.roleName || '-'} + {row.roleId || '-'} +
+ ), + }, + { + title: '错误', + dataIndex: 'lastError', + minWidth: 220, + render: (value) => {value || '-'}, + }, + ] + + return ( +
+ } + /> + + + + {detail.order.orderId} + + {detail.order.shopName || detail.order.shopId || '-'} + + {detail.order.platformOrderId} + + + + + + + + {detail.order.totalAmount} {detail.order.currency} + + + {formatAdminDateTime(detail.order.createdAt)} + + + {formatAdminDateTime(detail.order.updatedAt)} + + {detail.order.itemSummary || '-'} + + + + + + rowKey="orderItemId" + pagination={false} + dataSource={detail.items} + scroll={{ x: 760 }} + columns={[ + { title: '商品', dataIndex: 'itemTitle', minWidth: 240 }, + { title: 'SKU', dataIndex: 'skuCode', minWidth: 180 }, + { title: '数量', dataIndex: 'quantity', width: 90 }, + { title: '履约模式', dataIndex: 'deliveryMode', minWidth: 140 }, + ]} + /> + + + + + rowKey="taskId" + pagination={false} + dataSource={detail.tasks} + columns={taskColumns} + scroll={{ x: 900 }} + /> + + + + + +
+ ) +} + +function BackButton() { + const navigate = useNavigate() + return ( + + ) +} diff --git a/apps/frontend-react/src/pages/admin/AdminOrdersPage.tsx b/apps/frontend-react/src/pages/admin/AdminOrdersPage.tsx new file mode 100644 index 00000000..af2263eb --- /dev/null +++ b/apps/frontend-react/src/pages/admin/AdminOrdersPage.tsx @@ -0,0 +1,169 @@ +import { SearchOutlined, ReloadOutlined } from '@ant-design/icons' +import { Button, Card, Form, Input, Select, Space, Table, Typography } from 'antd' +import type { TableColumnsType } from 'antd' +import { useQuery } from '@tanstack/react-query' +import { useMemo } from 'react' +import { useNavigate, useSearchParams } from 'react-router' + +import PageHeader from '@/components/admin/PageHeader' +import StatusTag from '@/components/admin/StatusTag' +import { fetchAdminOrders } from '@/services/admin' +import type { AdminOrderListItem } from '@/types/admin' +import { adminPayStatusOptions } from '@/utils/admin-options' +import { formatAdminDateTime } from '@/utils/admin-time' + +type OrderFilterForm = { + platformOrderId?: string + payStatus?: string + skuCode?: string +} + +export default function AdminOrdersPage() { + const navigate = useNavigate() + const [searchParams, setSearchParams] = useSearchParams() + const page = Number(searchParams.get('page') || 1) || 1 + const pageSize = Number(searchParams.get('pageSize') || 20) || 20 + const filters = { + platformOrderId: searchParams.get('platformOrderId') || '', + payStatus: searchParams.get('payStatus') || '', + skuCode: searchParams.get('skuCode') || '', + } + + const queryParams = useMemo( + () => ({ page, pageSize, ...filters }), + [filters.platformOrderId, filters.payStatus, filters.skuCode, page, pageSize], + ) + const query = useQuery({ + queryKey: ['admin-orders', queryParams], + queryFn: () => fetchAdminOrders(queryParams), + }) + const data = query.data?.data + + const columns: TableColumnsType = [ + { + title: '订单信息', + dataIndex: 'platformOrderId', + minWidth: 220, + render: (_, row) => ( +
+ navigate(`/admin/orders/${row.orderId}`)}> + {row.platformOrderId} + + {row.shopName || row.shopId || row.provider} +
+ ), + }, + { + title: '商品 / 任务', + minWidth: 220, + render: (_, row) => ( +
+ {row.itemSummary || '-'} + + 商品 {row.itemCount} · 数量 {row.totalQuantity} · 任务 {row.taskCount} + +
+ ), + }, + { + title: '状态', + minWidth: 220, + render: (_, row) => ( + + + + + + ), + }, + { + title: '金额 / 时间', + minWidth: 180, + render: (_, row) => ( +
+ {row.totalAmount || '0.00'} {row.currency} + {formatAdminDateTime(row.createdAt)} +
+ ), + }, + ] + + function applyFilters(values: OrderFilterForm) { + setSearchParams(cleanParams({ ...values, page: 1, pageSize })) + } + + function resetFilters() { + setSearchParams(cleanParams({ page: 1, pageSize })) + } + + return ( +
+ + + + + layout="inline" + initialValues={filters} + onFinish={applyFilters} + className="filter-form" + > + + + + + + + + + + + + + + + + + + rowKey="orderId" + loading={query.isLoading || query.isFetching} + columns={columns} + dataSource={data?.items || []} + scroll={{ x: 900 }} + pagination={{ + current: data?.pagination.page || page, + pageSize: data?.pagination.pageSize || pageSize, + total: data?.pagination.total || 0, + showSizeChanger: true, + showTotal: (total) => `共 ${total} 条`, + onChange: (nextPage, nextPageSize) => { + setSearchParams(cleanParams({ ...filters, page: nextPage, pageSize: nextPageSize })) + }, + }} + /> + +
+ ) +} + +function cleanParams(params: Record) { + const next = new URLSearchParams() + Object.entries(params).forEach(([key, value]) => { + const normalized = String(value ?? '').trim() + if (normalized) { + next.set(key, normalized) + } + }) + return next +} diff --git a/apps/frontend-react/src/pages/admin/AdminTaskDetailPage.tsx b/apps/frontend-react/src/pages/admin/AdminTaskDetailPage.tsx new file mode 100644 index 00000000..61fe67b1 --- /dev/null +++ b/apps/frontend-react/src/pages/admin/AdminTaskDetailPage.tsx @@ -0,0 +1,237 @@ +import { ArrowLeftOutlined, CopyOutlined, ReloadOutlined } from '@ant-design/icons' +import { + Alert, + App, + Button, + Card, + Descriptions, + Empty, + Skeleton, + Space, + Table, + Tabs, + Typography, +} from 'antd' +import type { TableColumnsType } from 'antd' +import { useQuery } from '@tanstack/react-query' +import { useNavigate, useParams } from 'react-router' + +import JsonPreview from '@/components/admin/JsonPreview' +import PageHeader from '@/components/admin/PageHeader' +import StatusTag from '@/components/admin/StatusTag' +import { fetchAdminTaskDetail } from '@/services/admin' +import type { AdminTaskDetail } from '@/types/admin' +import { formatTaskEventPayload, formatTaskEventType } from '@/utils/admin-display' +import { formatAdminDateTime } from '@/utils/admin-time' + +type TaskEvent = AdminTaskDetail['events'][number] + +export default function AdminTaskDetailPage() { + const navigate = useNavigate() + const { taskId = '' } = useParams() + const { message } = App.useApp() + const query = useQuery({ + queryKey: ['admin-task-detail', taskId], + queryFn: () => fetchAdminTaskDetail(taskId), + enabled: Boolean(taskId), + }) + const detail = query.data?.data + + if (query.isLoading) { + return ( +
+ } /> + + + +
+ ) + } + + if (query.error || !detail) { + return ( +
+ } /> + +
+ ) + } + + const resolvedDetail = detail + const flow = resolvedDetail.kuaishouCloudFulfillment + const eventColumns: TableColumnsType = [ + { + title: '时间', + dataIndex: 'createdAt', + width: 180, + render: (value) => formatAdminDateTime(value), + }, + { + title: '事件', + dataIndex: 'eventType', + width: 180, + render: (value) => formatTaskEventType(value), + }, + { + title: '摘要', + render: (_, row) => formatTaskEventPayload(row.payload), + }, + ] + + async function copyClaimUrl() { + const claimUrl = resolvedDetail.claimToken?.claimUrl || '' + if (!claimUrl) return + await navigator.clipboard.writeText(claimUrl) + message.success('领取链接已复制') + } + + return ( +
+ + + + + } + /> + + + + {resolvedDetail.task.taskId} + {resolvedDetail.task.taskNo} + + {resolvedDetail.task.platformOrderId || resolvedDetail.order?.platformOrderId || '-'} + + + + + + + + + + + + {resolvedDetail.orderItem?.skuName || resolvedDetail.task.skuName || '-'} + + + {resolvedDetail.task.roleName || '-'} {resolvedDetail.task.roleId ? `(${resolvedDetail.task.roleId})` : ''} + + + {formatAdminDateTime(resolvedDetail.task.updatedAt)} + + + {resolvedDetail.task.lastError || '-'} + + + + + {resolvedDetail.claimToken ? ( + + + + {resolvedDetail.claimToken.claimUrl} + + + + + 到期:{formatAdminDateTime(resolvedDetail.claimToken.expiredAt)} + + + + + + ) : null} + + : , + }, + { + key: 'events', + label: '事件流水', + children: ( + + rowKey="eventId" + pagination={false} + columns={eventColumns} + dataSource={resolvedDetail.events} + scroll={{ x: 760 }} + /> + ), + }, + { + key: 'raw', + label: '原始数据', + children: , + }, + ]} + /> +
+ ) +} + +function KuaishouCloudPanel({ flow }: { flow: NonNullable }) { + return ( + + + + {flow.binding.resolvedSourceLabel || flow.binding.resolvedSourceKey || '-'} + + + {flow.binding.vnPhone || flow.binding.vnId || '-'} + + + + + + {flow.role.name || '-'} {flow.role.rid ? `(${flow.role.rid})` : ''} + + + + + + + + + + + + + + + {flow.binding.bindUrl ? ( + + {flow.binding.bindUrl} + + ) : ( + '-' + )} + + + + ) +} + +function BackButton() { + const navigate = useNavigate() + return ( + + ) +} diff --git a/apps/frontend-react/src/pages/admin/AdminTasksPage.tsx b/apps/frontend-react/src/pages/admin/AdminTasksPage.tsx new file mode 100644 index 00000000..e655b274 --- /dev/null +++ b/apps/frontend-react/src/pages/admin/AdminTasksPage.tsx @@ -0,0 +1,191 @@ +import { ReloadOutlined, SearchOutlined } from '@ant-design/icons' +import { Button, Card, Form, Input, Select, Space, Table, Typography } from 'antd' +import type { TableColumnsType } from 'antd' +import { useQuery } from '@tanstack/react-query' +import { useMemo } from 'react' +import { useNavigate, useSearchParams } from 'react-router' + +import PageHeader from '@/components/admin/PageHeader' +import StatusTag from '@/components/admin/StatusTag' +import { fetchAdminTasks } from '@/services/admin' +import type { AdminTaskListItem } from '@/types/admin' +import { adminTaskStatusOptions } from '@/utils/admin-options' +import { formatAdminDateTime } from '@/utils/admin-time' + +type TaskFilterForm = { + status?: string + platformOrderId?: string + taskNo?: string + skuCode?: string + roleId?: string +} + +export default function AdminTasksPage() { + const navigate = useNavigate() + const [searchParams, setSearchParams] = useSearchParams() + const page = Number(searchParams.get('page') || 1) || 1 + const pageSize = Number(searchParams.get('pageSize') || 20) || 20 + const filters = { + status: searchParams.get('status') || '', + platformOrderId: searchParams.get('platformOrderId') || '', + taskNo: searchParams.get('taskNo') || '', + skuCode: searchParams.get('skuCode') || '', + roleId: searchParams.get('roleId') || '', + } + + const queryParams = useMemo( + () => ({ page, pageSize, ...filters }), + [ + filters.platformOrderId, + filters.roleId, + filters.skuCode, + filters.status, + filters.taskNo, + page, + pageSize, + ], + ) + const query = useQuery({ + queryKey: ['admin-tasks', queryParams], + queryFn: () => fetchAdminTasks(queryParams), + }) + const data = query.data?.data + + const columns: TableColumnsType = [ + { + title: '任务 / 订单', + minWidth: 230, + render: (_, row) => ( +
+ navigate(`/admin/tasks/${row.taskId}`)}> + {row.taskNo} + + {row.platformOrderId} +
+ ), + }, + { + title: '商品', + minWidth: 210, + render: (_, row) => ( +
+ {row.skuName || '-'} + {row.skuCode || row.executorKey} +
+ ), + }, + { + title: '状态', + minWidth: 250, + render: (_, row) => ( + + + + + + ), + }, + { + title: '角色', + minWidth: 180, + render: (_, row) => ( +
+ {row.roleName || '-'} + {row.roleId || row.loginType || '-'} +
+ ), + }, + { + title: '时间 / 异常', + minWidth: 220, + render: (_, row) => ( +
+ {formatAdminDateTime(row.updatedAt)} + {row.lastError || '-'} +
+ ), + }, + ] + + function applyFilters(values: TaskFilterForm) { + setSearchParams(cleanParams({ ...values, page: 1, pageSize })) + } + + function resetFilters() { + setSearchParams(cleanParams({ page: 1, pageSize })) + } + + return ( +
+ + + + + layout="inline" + initialValues={filters} + onFinish={applyFilters} + className="filter-form" + > + + + + + + + + + + + + + + + + + + + + + rowKey="taskId" + loading={query.isLoading || query.isFetching} + columns={columns} + dataSource={data?.items || []} + scroll={{ x: 1080 }} + pagination={{ + current: data?.pagination.page || page, + pageSize: data?.pagination.pageSize || pageSize, + total: data?.pagination.total || 0, + showSizeChanger: true, + showTotal: (total) => `共 ${total} 条`, + onChange: (nextPage, nextPageSize) => { + setSearchParams(cleanParams({ ...filters, page: nextPage, pageSize: nextPageSize })) + }, + }} + /> + +
+ ) +} + +function cleanParams(params: Record) { + const next = new URLSearchParams() + Object.entries(params).forEach(([key, value]) => { + const normalized = String(value ?? '').trim() + if (normalized) { + next.set(key, normalized) + } + }) + return next +} diff --git a/apps/frontend-react/src/services/admin/audit-logs.ts b/apps/frontend-react/src/services/admin/audit-logs.ts new file mode 100644 index 00000000..66030ebe --- /dev/null +++ b/apps/frontend-react/src/services/admin/audit-logs.ts @@ -0,0 +1,9 @@ +import { apiGet } from '@/lib/http' +import type { AdminAuditLogItem, AdminPagination } from '@/types/admin' + +export function fetchAdminAuditLogs(params?: Record) { + return apiGet<{ items: AdminAuditLogItem[]; pagination: AdminPagination }>( + '/api/v1/admin/audit-logs', + params, + ) +} diff --git a/apps/frontend-react/src/services/admin/auth.ts b/apps/frontend-react/src/services/admin/auth.ts new file mode 100644 index 00000000..aab2ddd1 --- /dev/null +++ b/apps/frontend-react/src/services/admin/auth.ts @@ -0,0 +1,14 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { AdminLoginResponse, AdminSessionSummary } from '@/types/admin' + +export function loginAdmin(payload: { username: string; password: string }) { + return apiPost('/api/v1/admin/auth/login', payload) +} + +export function fetchAdminSession() { + return apiGet('/api/v1/admin/auth/session') +} + +export function logoutAdmin() { + return apiPost<{ success: boolean }>('/api/v1/admin/auth/logout', {}) +} diff --git a/apps/frontend-react/src/services/admin/dashboard.ts b/apps/frontend-react/src/services/admin/dashboard.ts new file mode 100644 index 00000000..518d3902 --- /dev/null +++ b/apps/frontend-react/src/services/admin/dashboard.ts @@ -0,0 +1,6 @@ +import { apiGet } from '@/lib/http' +import type { AdminDashboardSummary } from '@/types/admin' + +export function fetchAdminDashboardSummary() { + return apiGet('/api/v1/admin/dashboard/summary') +} diff --git a/apps/frontend-react/src/services/admin/index.ts b/apps/frontend-react/src/services/admin/index.ts new file mode 100644 index 00000000..99c97f94 --- /dev/null +++ b/apps/frontend-react/src/services/admin/index.ts @@ -0,0 +1,7 @@ +export * from './auth' +export * from './dashboard' +export * from './users' +export * from './audit-logs' +export * from './platform-config' +export * from './orders' +export * from './tasks' diff --git a/apps/frontend-react/src/services/admin/orders.ts b/apps/frontend-react/src/services/admin/orders.ts new file mode 100644 index 00000000..02de9ed5 --- /dev/null +++ b/apps/frontend-react/src/services/admin/orders.ts @@ -0,0 +1,13 @@ +import { apiGet } from '@/lib/http' +import type { AdminOrderDetail, AdminOrderListItem, AdminPagination } from '@/types/admin' + +export function fetchAdminOrders(params?: Record) { + return apiGet<{ items: AdminOrderListItem[]; pagination: AdminPagination }>( + '/api/v1/admin/orders', + params, + ) +} + +export function fetchAdminOrderDetail(orderId: number | string) { + return apiGet(`/api/v1/admin/orders/${orderId}`) +} diff --git a/apps/frontend-react/src/services/admin/platform-config/cloudtentacles.ts b/apps/frontend-react/src/services/admin/platform-config/cloudtentacles.ts new file mode 100644 index 00000000..30e5890a --- /dev/null +++ b/apps/frontend-react/src/services/admin/platform-config/cloudtentacles.ts @@ -0,0 +1,336 @@ +import { apiDelete, apiGet, apiPost } from '@/lib/http' +import type { + AdminCloudtentaclesLoginTestResult, + AdminCloudtentaclesDeliveryRecordListResult, + AdminCloudtentaclesOverrideRuleConfig, + AdminCloudtentaclesSkuListResult, + AdminCloudtentaclesSendSmsResult, + AdminCloudtentaclesSourceConfigResponse, + AdminCloudtentaclesSourceItem, + AdminCloudtentaclesSourcesConfig, + AdminCloudtentaclesValidateSessionResult, +} from '@/types/admin' + +export function fetchAdminCloudtentaclesSourceConfig() { + return apiGet( + '/api/v1/admin/platform-config/cloudtentacles-source', + ) +} + +export function saveAdminCloudtentaclesSourceConfig( + payload: + | AdminCloudtentaclesSourcesConfig + | ({ sourceKey: string } & Partial), +) { + return apiPost( + '/api/v1/admin/platform-config/cloudtentacles-source', + payload, + ) +} + +export function deleteAdminCloudtentaclesSource(sourceKey: string) { + return apiDelete<{ success: boolean }>( + `/api/v1/admin/platform-config/cloudtentacles-source/${sourceKey}`, + ) +} + +export function fetchAdminCloudtentaclesOverrideRules() { + return apiGet( + '/api/v1/admin/platform-config/cloudtentacles/override-rules', + ) +} + +export function saveAdminCloudtentaclesOverrideRules( + payload: { + enabled: boolean + rules: Array<{ + id?: string + enabled?: boolean + productName?: string + sourceKey?: string + deliveryItems?: Array<{ + cloudSkuId?: number + cloudSkuName?: string + quantity?: number + }> + notes?: string + }> + }, +) { + return apiPost( + '/api/v1/admin/platform-config/cloudtentacles/override-rules', + payload, + ) +} + +export function sendAdminCloudtentaclesSmsCode(payload: { + baseUrl?: string + username?: string + phone?: string + deviceId?: string + deviceType?: number + sourceKey?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/cloudtentacles/send-sms-code', + payload, + ) +} + +export function testAdminCloudtentaclesLogin(payload: { + baseUrl?: string + username?: string + password?: string + phone?: string + code?: string + deviceId?: string + deviceType?: number + sourceKey?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/cloudtentacles/test-login', + payload, + ) +} + +export function validateAdminCloudtentaclesSession(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + sourceKey?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/cloudtentacles/validate-session', + payload, + ) +} + +export function fetchAdminCloudtentaclesAsset(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/asset', + payload, + ) +} + +export function fetchAdminCloudtentaclesCategories(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/categories', + payload, + ) +} + +export function fetchAdminCloudtentaclesSkuList(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + sourceKey?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/cloudtentacles/sku/list', + payload, + ) +} + +export function buyAdminCloudtentaclesSku(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + id?: number + count?: number + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/sku/buy', + payload, + ) +} + +export function useAdminCloudtentaclesSku(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + id?: number + virtualNumberId?: number + phone?: string + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/sku/use', + payload, + ) +} + +export function fetchAdminCloudtentaclesKnapsack(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/knapsack', + payload, + ) +} + +export function fetchAdminCloudtentaclesDeliveryRecords(payload: { + sourceKey?: string + page?: number + size?: number + startDate?: string + endDate?: string + platformOrderId?: string +}) { + return apiPost( + '/api/v1/admin/cloudtentacles-records', + payload, + ) +} + +export function fetchAdminCloudtentaclesRecordSources() { + return apiGet<{ + sources: Array<{ + key: string + label: string + enabled: boolean + hasToken: boolean + loggedInAt: string + }> + }>('/api/v1/admin/cloudtentacles-records/sources') +} + +export function fetchAdminCloudtentaclesVnList(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/vn/list', + payload, + ) +} + +export function appointAdminCloudtentaclesVn(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/vn/appoint', + payload, + ) +} + +export function generateAdminCloudtentaclesVnLoginCode(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + id?: number + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/vn/generate-login-code', + payload, + ) +} + +export function fetchAdminCloudtentaclesVnCode(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + phone?: string + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/vn/fetch-code', + payload, + ) +} + +export function verifyAdminCloudtentaclesVnCode(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + id?: number + code?: string + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/vn/verify-code', + payload, + ) +} + +export function fetchAdminCloudtentaclesBindUrl(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + id?: number + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/vn/bind-url', + payload, + ) +} + +export function backAdminCloudtentaclesVn(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + id?: number + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/vn/back', + payload, + ) +} + +export function runAdminCloudtentaclesFullFlow(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + skuId?: number + skuCount?: number + vnKey?: string + sourceKey?: string +}) { + return apiPost>( + '/api/v1/admin/platform-config/cloudtentacles/debug/full-flow', + payload, + ) +} diff --git a/apps/frontend-react/src/services/admin/platform-config/index.ts b/apps/frontend-react/src/services/admin/platform-config/index.ts new file mode 100644 index 00000000..9f73f850 --- /dev/null +++ b/apps/frontend-react/src/services/admin/platform-config/index.ts @@ -0,0 +1,6 @@ +export * from './notifications' +export * from './scheduled-jobs' +export * from './ninetyone' +export * from './kuaishou-eticket' +export * from './kuaishou-feifei' +export * from './cloudtentacles' diff --git a/apps/frontend-react/src/services/admin/platform-config/kuaishou-eticket.ts b/apps/frontend-react/src/services/admin/platform-config/kuaishou-eticket.ts new file mode 100644 index 00000000..cd324b2e --- /dev/null +++ b/apps/frontend-react/src/services/admin/platform-config/kuaishou-eticket.ts @@ -0,0 +1,60 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { + AdminKuaishouEticketConsumeResult, + AdminKuaishouEticketDetailResult, + AdminKuaishouEticketShopInfoResult, + AdminKuaishouEticketSourceConfig, +} from '@/types/admin' + +export function fetchAdminKuaishouEticketSourceConfig() { + return apiGet<{ + filePath: string + source: AdminKuaishouEticketSourceConfig + }>('/api/v1/admin/platform-config/kuaishou-eticket-source') +} + +export function saveAdminKuaishouEticketSourceConfig(payload: AdminKuaishouEticketSourceConfig) { + return apiPost<{ + filePath: string + source: AdminKuaishouEticketSourceConfig + }>('/api/v1/admin/platform-config/kuaishou-eticket-source', payload) +} + +export function queryAdminKuaishouEticketDetail(payload: { + baseUrl?: string + shopId?: string + cookie?: string + eTicketId: string +}) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-eticket/query-detail', + payload, + ) +} + +export function queryAdminKuaishouEticketShopInfo(payload: { + baseUrl?: string + shopId?: string + cookie?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-eticket/query-shop-info', + payload, + ) +} + +export function consumeAdminKuaishouEticket(payload: { + baseUrl?: string + shopId?: string + cookie?: string + eTicketId: string + oid?: string + formToken?: string + num?: number + storeId?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-eticket/consume', + payload, + ) +} diff --git a/apps/frontend-react/src/services/admin/platform-config/kuaishou-feifei.ts b/apps/frontend-react/src/services/admin/platform-config/kuaishou-feifei.ts new file mode 100644 index 00000000..b5324f95 --- /dev/null +++ b/apps/frontend-react/src/services/admin/platform-config/kuaishou-feifei.ts @@ -0,0 +1,79 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { + AdminKuaishouFeifeiConfig, + AdminKuaishouFeifeiConfigResponse, + AdminKuaishouFeifeiMatchResult, + AdminKuaishouFeifeiOrderResult, + AdminKuaishouFeifeiProductListResult, + AdminKuaishouFeifeiProductSyncResult, +} from '@/types/admin' + +export function fetchAdminKuaishouFeifeiConfig() { + return apiGet( + '/api/v1/admin/platform-config/kuaishou-feifei', + ) +} + +export function saveAdminKuaishouFeifeiConfig(payload: AdminKuaishouFeifeiConfig) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-feifei', + payload, + ) +} + +export function matchAdminKuaishouFeifeiProduct(productName: string) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-feifei/match', + { productName }, + ) +} + +export function fetchAdminKuaishouFeifeiProducts(payload: { + page?: number + perPage?: number + status?: string + supplyProductName?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-feifei/products', + payload, + ) +} + +export function syncAdminKuaishouFeifeiProducts(payload: { + status?: string + supplyProductName?: string +} = {}) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-feifei/sync-products', + payload, + ) +} + +export function createAdminKuaishouFeifeiTestOrder(payload: { + platformOrderNo: string + productCode: string + platformBuyNum?: number + platformAmount?: number | null + playerAccount?: string + playerGameRegion?: string + playerGameSrv?: string + playerGameRole?: string + submitPlayer?: boolean + notifyUrl?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-feifei/test-order', + payload, + ) +} + +export function queryAdminKuaishouFeifeiOrder(payload: { + platformOrderNo?: string + orderNo?: string +}) { + return apiPost( + '/api/v1/admin/platform-config/kuaishou-feifei/query-order', + payload, + ) +} diff --git a/apps/frontend-react/src/services/admin/platform-config/ninetyone.ts b/apps/frontend-react/src/services/admin/platform-config/ninetyone.ts new file mode 100644 index 00000000..c5ef2aeb --- /dev/null +++ b/apps/frontend-react/src/services/admin/platform-config/ninetyone.ts @@ -0,0 +1,42 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { + AdminNinetyoneOrderActionResult, + AdminNinetyoneOrderListResult, +} from '@/types/admin' + +export function fetchAdminNinetyoneOrders( + params: { + page?: number + pageSize?: number + status?: string + } = {}, +) { + const searchParams = new URLSearchParams() + if (params.page) { + searchParams.set('page', String(params.page)) + } + if (params.pageSize) { + searchParams.set('pageSize', String(params.pageSize)) + } + if (params.status) { + searchParams.set('status', params.status) + } + const queryString = searchParams.toString() + return apiGet( + `/api/v1/admin/platform-config/ninetyone/orders${queryString ? `?${queryString}` : ''}`, + ) +} + +export function retryAdminNinetyoneOrder(orderId: number | string) { + return apiPost( + `/api/v1/admin/platform-config/ninetyone/orders/${orderId}/retry`, + {}, + ) +} + +export function failAdminNinetyoneOrder(orderId: number | string, payload: { reason?: string }) { + return apiPost( + `/api/v1/admin/platform-config/ninetyone/orders/${orderId}/fail`, + payload, + ) +} diff --git a/apps/frontend-react/src/services/admin/platform-config/notifications.ts b/apps/frontend-react/src/services/admin/platform-config/notifications.ts new file mode 100644 index 00000000..f5056037 --- /dev/null +++ b/apps/frontend-react/src/services/admin/platform-config/notifications.ts @@ -0,0 +1,23 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { AdminNotificationConfig, AdminNotificationTestResult } from '@/types/admin' + +export function fetchAdminNotificationConfig() { + return apiGet<{ + filePath: string + source: AdminNotificationConfig + }>('/api/v1/admin/platform-config/notifications') +} + +export function saveAdminNotificationConfig(payload: AdminNotificationConfig) { + return apiPost<{ + filePath: string + source: AdminNotificationConfig + }>('/api/v1/admin/platform-config/notifications', payload) +} + +export function testAdminNotification(payload: { title?: string; body?: string; url?: string }) { + return apiPost( + '/api/v1/admin/platform-config/notifications/test', + payload, + ) +} diff --git a/apps/frontend-react/src/services/admin/platform-config/scheduled-jobs.ts b/apps/frontend-react/src/services/admin/platform-config/scheduled-jobs.ts new file mode 100644 index 00000000..55089011 --- /dev/null +++ b/apps/frontend-react/src/services/admin/platform-config/scheduled-jobs.ts @@ -0,0 +1,20 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { AdminScheduledJobsConfig, AdminScheduledJobsResponse } from '@/types/admin' + +export function fetchAdminScheduledJobsConfig() { + return apiGet('/api/v1/admin/platform-config/scheduled-jobs') +} + +export function saveAdminScheduledJobsConfig(payload: AdminScheduledJobsConfig) { + return apiPost( + '/api/v1/admin/platform-config/scheduled-jobs', + payload, + ) +} + +export function runAdminScheduledJob(jobId: string) { + return apiPost<{ + result: Record + runtime: AdminScheduledJobsResponse['runtime'] + }>(`/api/v1/admin/platform-config/scheduled-jobs/${jobId}/run`, {}) +} diff --git a/apps/frontend-react/src/services/admin/tasks.ts b/apps/frontend-react/src/services/admin/tasks.ts new file mode 100644 index 00000000..4e715205 --- /dev/null +++ b/apps/frontend-react/src/services/admin/tasks.ts @@ -0,0 +1,93 @@ +import { apiGet, apiGetBlob, apiPost } from '@/lib/http' +import type { + AdminPagination, + AdminTaskActionResponse, + AdminTaskDetail, + AdminTaskListItem, +} from '@/types/admin' + +export function fetchAdminTasks(params?: Record) { + return apiGet<{ items: AdminTaskListItem[]; pagination: AdminPagination }>( + '/api/v1/admin/tasks', + params, + ) +} + +export function fetchAdminTaskDetail(taskId: number | string) { + return apiGet(`/api/v1/admin/tasks/${taskId}`) +} + +export function fetchAdminTaskScreenshot(taskId: number | string) { + return apiGetBlob(`/api/v1/admin/tasks/${taskId}/screenshot`) +} + +export function retryAdminTask(taskId: number | string) { + return apiPost(`/api/v1/admin/tasks/${taskId}/retry`, {}) +} + +export function regenerateAdminTaskClaimLink(taskId: number | string) { + return apiPost(`/api/v1/admin/tasks/${taskId}/regenerate-claim-link`, {}) +} + +export function closeAdminTask(taskId: number | string) { + return apiPost(`/api/v1/admin/tasks/${taskId}/close`, {}) +} + +export function markAdminTaskManualReview(taskId: number | string) { + return apiPost(`/api/v1/admin/tasks/${taskId}/mark-manual-review`, {}) +} + +export function completeAdminTaskManualDispatch( + taskId: number | string, + payload: { + outcome: 'delivered' | 'failed' + resultMessage?: string + deliveryReference?: string + deliveredCredential?: string + }, +) { + return apiPost( + `/api/v1/admin/tasks/${taskId}/complete-manual-dispatch`, + payload, + ) +} + +export function prepareAdminTaskKuaishouCloudFulfillment(taskId: number | string) { + return apiPost( + `/api/v1/admin/tasks/${taskId}/kuaishou-cloud/prepare`, + {}, + ) +} + +export function refreshAdminTaskKuaishouCloudRoleInfo(taskId: number | string) { + return apiPost( + `/api/v1/admin/tasks/${taskId}/kuaishou-cloud/refresh-role-info`, + {}, + ) +} + +export function rebindAdminTaskKuaishouCloudRole(taskId: number | string) { + return apiPost( + `/api/v1/admin/tasks/${taskId}/kuaishou-cloud/rebind-role`, + {}, + ) +} + +export function dispatchAdminTaskKuaishouCloudFulfillment( + taskId: number | string, + payload: { + ticketCode?: string + } = {}, +) { + return apiPost( + `/api/v1/admin/tasks/${taskId}/kuaishou-cloud/dispatch`, + payload, + ) +} + +export function returnNumberAdminTaskKuaishouCloudFulfillment(taskId: number | string) { + return apiPost( + `/api/v1/admin/tasks/${taskId}/kuaishou-cloud/return-number`, + {}, + ) +} diff --git a/apps/frontend-react/src/services/admin/users.ts b/apps/frontend-react/src/services/admin/users.ts new file mode 100644 index 00000000..039a1ddb --- /dev/null +++ b/apps/frontend-react/src/services/admin/users.ts @@ -0,0 +1,33 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { AdminPagination, AdminUserListItem } from '@/types/admin' + +export function fetchAdminUsers(params?: Record) { + return apiGet<{ items: AdminUserListItem[]; pagination: AdminPagination }>( + '/api/v1/admin/users', + params, + ) +} + +export function createAdminUser(payload: { + username: string + password: string + role: string + status?: string +}) { + return apiPost<{ user: AdminUserListItem }>('/api/v1/admin/users', payload) +} + +export function updateAdminUserRole(userId: number | string, payload: { role: string }) { + return apiPost<{ user: AdminUserListItem }>(`/api/v1/admin/users/${userId}/role`, payload) +} + +export function updateAdminUserStatus(userId: number | string, payload: { status: string }) { + return apiPost<{ user: AdminUserListItem }>(`/api/v1/admin/users/${userId}/status`, payload) +} + +export function resetAdminUserPassword(userId: number | string, payload: { password: string }) { + return apiPost<{ user: AdminUserListItem }>( + `/api/v1/admin/users/${userId}/reset-password`, + payload, + ) +} diff --git a/apps/frontend-react/src/services/claim.ts b/apps/frontend-react/src/services/claim.ts new file mode 100644 index 00000000..e5b508c2 --- /dev/null +++ b/apps/frontend-react/src/services/claim.ts @@ -0,0 +1,27 @@ +import { apiGet, apiPost } from '@/lib/http' +import type { ClaimDetailData } from '@/types/claim' + +export function fetchClaimDetail(token: string) { + return apiGet(`/api/v1/claim/${token}`) +} + +export function verifyKuaishouCloudClaimTicket( + token: string, + payload: { + ticketCode: string + }, +) { + return apiPost(`/api/v1/claim/${token}/kuaishou-cloud/verify-ticket`, payload) +} + +export function confirmKuaishouCloudClaimRole(token: string) { + return apiPost(`/api/v1/claim/${token}/kuaishou-cloud/confirm-role`, {}) +} + +export function rebindKuaishouCloudClaimRole(token: string) { + return apiPost(`/api/v1/claim/${token}/kuaishou-cloud/rebind-role`, {}) +} + +export function redeemKuaishouCloudClaim(token: string) { + return apiPost(`/api/v1/claim/${token}/kuaishou-cloud/redeem`, {}) +} diff --git a/apps/frontend-react/src/styles/main.css b/apps/frontend-react/src/styles/main.css new file mode 100644 index 00000000..d02360f0 --- /dev/null +++ b/apps/frontend-react/src/styles/main.css @@ -0,0 +1,195 @@ +:root { + color: #1f2937; + background: #f5f7fb; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; +} + +button, +input, +textarea, +select { + font: inherit; +} + +.login-page { + min-height: 100vh; + display: grid; + place-items: center; + padding: 24px; + background: + linear-gradient(135deg, rgba(22, 119, 255, 0.1), rgba(19, 194, 194, 0.08)), + #f5f7fb; +} + +.login-panel { + width: min(440px, 100%); + box-shadow: 0 18px 50px rgba(15, 23, 42, 0.12); +} + +.login-heading { + margin-bottom: 24px; +} + +.login-heading h1 { + margin: 8px 0 0; + font-size: 28px; +} + +.eyebrow { + color: #1677ff; + font-weight: 700; + letter-spacing: 0; +} + +.admin-shell { + min-height: 100vh; +} + +.admin-sider { + border-right: 1px solid #edf0f5; + box-shadow: 4px 0 18px rgba(15, 23, 42, 0.04); + z-index: 2; +} + +.admin-brand { + height: 64px; + display: grid; + align-content: center; + padding: 0 20px; + border-bottom: 1px solid #edf0f5; +} + +.admin-brand strong { + color: #111827; + font-size: 18px; +} + +.admin-brand span { + color: #6b7280; + font-size: 12px; +} + +.admin-topbar { + height: 64px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + border-bottom: 1px solid #edf0f5; +} + +.admin-user { + margin-left: auto; +} + +.admin-user-copy { + display: grid; + justify-items: end; + line-height: 1.3; +} + +.admin-content { + padding: 20px; +} + +.page-stack { + display: grid; + gap: 16px; +} + +.page-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.page-title { + margin: 0 !important; +} + +.page-description { + margin: 6px 0 0; + color: #6b7280; +} + +.filter-form { + row-gap: 12px; +} + +.cell-stack { + display: grid; + gap: 4px; +} + +.muted { + color: #6b7280; +} + +.json-preview { + max-height: 460px; + overflow: auto; + margin: 0; + padding: 14px; + border: 1px solid #edf0f5; + border-radius: 6px; + background: #0f172a; + color: #dbeafe; + font-size: 12px; + line-height: 1.6; +} + +.empty-page { + min-height: 50vh; + display: grid; + place-items: center; +} + +.route-loading { + min-height: 100vh; + display: grid; + place-items: center; +} + +.full-width { + width: 100%; +} + +.feedback-prompt p { + margin-top: 0; +} + +@media (max-width: 900px) { + .admin-sider { + position: fixed !important; + left: 0; + top: 0; + bottom: 0; + height: 100vh; + } + + .admin-content { + padding: 14px; + } + + .admin-user-copy { + display: none; + } + + .page-header { + display: grid; + } +} diff --git a/apps/frontend-react/src/types/admin/audit-logs.ts b/apps/frontend-react/src/types/admin/audit-logs.ts new file mode 100644 index 00000000..d54fc10b --- /dev/null +++ b/apps/frontend-react/src/types/admin/audit-logs.ts @@ -0,0 +1,13 @@ +import type { AdminRole } from './auth' + +export interface AdminAuditLogItem { + logId: number + actorUserId: number + actorUsername: string + actorRole: AdminRole + action: string + targetType: string + targetId: string + payload: Record + createdAt: string +} diff --git a/apps/frontend-react/src/types/admin/auth.ts b/apps/frontend-react/src/types/admin/auth.ts new file mode 100644 index 00000000..d9d23fc7 --- /dev/null +++ b/apps/frontend-react/src/types/admin/auth.ts @@ -0,0 +1,22 @@ +export type AdminRole = 'admin' | 'operator' | 'support' +export type AdminUserStatus = 'active' | 'disabled' + +export interface AdminLoginResponse { + token: string + expiresAt: string + user: { + userId: number + username: string + role: AdminRole + } +} + +export interface AdminSessionSummary { + authenticated: boolean + expiresAt: string + user: { + userId: number + username: string + role: AdminRole + } +} diff --git a/apps/frontend-react/src/types/admin/common.ts b/apps/frontend-react/src/types/admin/common.ts new file mode 100644 index 00000000..29821fba --- /dev/null +++ b/apps/frontend-react/src/types/admin/common.ts @@ -0,0 +1,5 @@ +export interface AdminPagination { + page: number + pageSize: number + total: number +} diff --git a/apps/frontend-react/src/types/admin/dashboard.ts b/apps/frontend-react/src/types/admin/dashboard.ts new file mode 100644 index 00000000..7d666bf0 --- /dev/null +++ b/apps/frontend-react/src/types/admin/dashboard.ts @@ -0,0 +1,7 @@ +export interface AdminDashboardSummary { + todayOrders: number + paidPendingClaim: number + claimingTasks: number + redeemedToday: number + abnormalTasks: number +} diff --git a/apps/frontend-react/src/types/admin/index.ts b/apps/frontend-react/src/types/admin/index.ts new file mode 100644 index 00000000..db08037a --- /dev/null +++ b/apps/frontend-react/src/types/admin/index.ts @@ -0,0 +1,81 @@ +// Common types +export type { AdminPagination } from './common' + +// Auth types +export type { AdminRole, AdminUserStatus, AdminLoginResponse, AdminSessionSummary } from './auth' + +// Users types +export type { AdminUserListItem } from './users' + +// Audit logs types +export type { AdminAuditLogItem } from './audit-logs' + +// Dashboard types +export type { AdminDashboardSummary } from './dashboard' + +// Orders types +export type { + AdminOrderFulfillmentProgress, + AdminOrderListItem, + AdminOrderDetail, +} from './orders' + +// Tasks types +export type { + AdminTaskListItem, + AdminTaskOperations, + AdminTaskActionResponse, + AdminTaskDetail, +} from './tasks' + +// Platform config types +export type { + AdminNotificationBarkRecipient, + AdminNotificationWpushRecipient, + AdminNotificationConfig, + AdminNotificationSendResult, + AdminNotificationTestResult, + AdminCloudtentaclesMonitorAccount, + AdminScheduledJobAccountRuntime, + AdminScheduledJobCloudtentaclesAccount, + AdminScheduledJobItem, + AdminScheduledJobsConfig, + AdminScheduledJobRuntimeState, + AdminScheduledJobsResponse, + AdminNinetyoneOrderItem, + AdminNinetyoneOrderListResult, + AdminNinetyoneOrderActionResult, + AdminKuaishouEticketSourceConfig, + AdminKuaishouEticketShopConfigItem, + AdminKuaishouEticketShopInfoResult, + AdminKuaishouEticketDetailResult, + AdminKuaishouEticketConsumeResult, + AdminKuaishouFeifeiProductRule, + AdminKuaishouFeifeiConfig, + AdminKuaishouFeifeiEffectiveConfig, + AdminKuaishouFeifeiConfigResponse, + AdminKuaishouFeifeiProductSyncResult, + AdminKuaishouFeifeiProductMatch, + AdminKuaishouFeifeiMatchResult, + AdminKuaishouFeifeiProductItem, + AdminKuaishouFeifeiProductListResult, + AdminKuaishouFeifeiOrderResult, + AdminCloudtentaclesSourceItem, + AdminCloudtentaclesSourcesConfig, + AdminCloudtentaclesSourceConfigResponse, + AdminCloudtentaclesOverrideDeliveryItem, + AdminCloudtentaclesOverrideRule, + AdminCloudtentaclesOverrideRuleConfig, + AdminCloudtentaclesSessionsMap, + AdminCloudtentaclesSourceConfig, + AdminCloudtentaclesPersistedSession, + AdminCloudtentaclesSessionItem, + AdminCloudtentaclesSendSmsResult, + AdminCloudtentaclesSessionSummary, + AdminCloudtentaclesLoginTestResult, + AdminCloudtentaclesValidateSessionResult, + AdminCloudtentaclesSkuItem, + AdminCloudtentaclesSkuListResult, + AdminCloudtentaclesDeliveryRecordItem, + AdminCloudtentaclesDeliveryRecordListResult, +} from './platform-config' diff --git a/apps/frontend-react/src/types/admin/orders.ts b/apps/frontend-react/src/types/admin/orders.ts new file mode 100644 index 00000000..c41d39aa --- /dev/null +++ b/apps/frontend-react/src/types/admin/orders.ts @@ -0,0 +1,72 @@ +import type { AdminTaskListItem } from './tasks' + +export interface AdminOrderFulfillmentProgress { + totalTaskCount: number + preparedTaskCount: number + completedTaskCount: number + resourceStatus: string + customerStatus: string +} + +export interface AdminOrderListItem { + orderId: number + provider: string + platform: string + shopId: string + shopName: string + platformOrderId: string + orderStatus: string + payStatus: string + buyerId: string + buyerName: string + receiverContact: string + totalAmount: string + totalAmountFen: number + currency: string + paidAt: string | null + createdAt: string + updatedAt: string + itemCount: number + totalQuantity: number + itemSummary: string + taskCount: number + resourceStatus: string + customerStatus: string + preparedTaskCount: number + completedTaskCount: number +} + +export interface AdminOrderDetail { + order: { + orderId: number + provider: string + platform: string + shopId: string + shopName: string + platformOrderId: string + orderStatus: string + payStatus: string + buyerId: string + buyerName: string + receiverContact: string + totalAmount: string + totalAmountFen: number + currency: string + paidAt: string | null + createdAt: string + updatedAt: string + itemSummary: string + rawPayload: Record + fulfillmentProgress: AdminOrderFulfillmentProgress + } + items: Array<{ + orderItemId: number + skuCode: string + skuName: string + itemTitle: string + quantity: number + deliveryMode: string + spec: Record + }> + tasks: AdminTaskListItem[] +} diff --git a/apps/frontend-react/src/types/admin/platform-config/cloudtentacles.ts b/apps/frontend-react/src/types/admin/platform-config/cloudtentacles.ts new file mode 100644 index 00000000..6442d35f --- /dev/null +++ b/apps/frontend-react/src/types/admin/platform-config/cloudtentacles.ts @@ -0,0 +1,181 @@ +export interface AdminCloudtentaclesSourceItem { + key: string + label: string + enabled: boolean + baseUrl: string + username: string + password: string + phone: string + deviceId: string + deviceType: number +} + +export interface AdminCloudtentaclesSourcesConfig { + enabled: boolean + sources: AdminCloudtentaclesSourceItem[] +} + +export interface AdminCloudtentaclesPersistedSession { + token: string + tokenMasked: string + baseUrl: string + username: string + phoneMasked: string + loggedInAt: string + deviceId: string + deviceType: number + hasToken: boolean +} + +export type AdminCloudtentaclesSessionItem = AdminCloudtentaclesPersistedSession + +export interface AdminCloudtentaclesSessionsMap { + [sourceKey: string]: AdminCloudtentaclesPersistedSession +} + +export interface AdminCloudtentaclesSourceConfigResponse { + filePath: string + sessionFilePath: string + enabled: boolean + sources: AdminCloudtentaclesSourceItem[] + sessions: AdminCloudtentaclesSessionsMap +} + +export interface AdminCloudtentaclesOverrideDeliveryItem { + cloudSkuId: number + cloudSkuName: string + quantity: number +} + +export interface AdminCloudtentaclesOverrideRule { + id: string + enabled: boolean + productName: string + normalizedProductName: string + sourceKey: string + deliveryItems: AdminCloudtentaclesOverrideDeliveryItem[] + notes: string +} + +export interface AdminCloudtentaclesOverrideRuleConfig { + filePath: string + enabled: boolean + rules: AdminCloudtentaclesOverrideRule[] +} + +export interface AdminCloudtentaclesSendSmsResult { + baseUrl: string + username: string + phone: string + phoneMasked: string + sentAt: string + responseMessage: string +} + +export interface AdminCloudtentaclesSessionSummary { + tokenMasked: string + permissionCount: number + permissions: string[] + persisted?: boolean +} + +export interface AdminCloudtentaclesLoginTestResult { + baseUrl: string + username: string + phoneMasked: string + loggedInAt: string + responseMessage: string + token: string + session: AdminCloudtentaclesSessionSummary + userInfo: Record + asset: number +} + +export interface AdminCloudtentaclesValidateSessionResult { + baseUrl: string + loggedInAt: string + session: AdminCloudtentaclesSessionSummary + userInfo: Record + asset: number +} + +export interface AdminCloudtentaclesSkuItem { + id: number + categoriesId: number + name: string + description: string + image: string + inventory: number + price: number + listingTime: string + delistingTime: string + buyLimitMin: number + buyLimitMax: number + raw: Record +} + +export interface AdminCloudtentaclesSkuListResult { + baseUrl: string + itemCount: number + items: AdminCloudtentaclesSkuItem[] + rawItems: Record[] +} + +export interface AdminCloudtentaclesDeliveryRecordItem { + createdAt: string + recordId: string + virtualNumberId: number + userId: number + count: number + cdk: string + exchangeUrl: string + name: string + skuId: number + skuName: string + skuImage: string + phone: string + status: number + fulfillUser: string + operatorName: string + platformOrderId: string + taskId: number + taskNo: string + orderShopId: string + orderShopName: string + consumeShopId: string + consumeShopName: string + roleName: string + roleId: string + localTaskStatus: string + localDeliveryStatus: string + localDispatchStatus: string + localDispatchAt: string + localResultCode: string + localResultMessage: string + matchConfidence: string + raw: Record +} + +export interface AdminCloudtentaclesDeliveryRecordListResult { + baseUrl: string + page: number + size: number + total: number + sourceKey?: string + queryStartDate?: string + queryEndDate?: string + localOrderMatched?: boolean + items: AdminCloudtentaclesDeliveryRecordItem[] + raw: Record +} + +/** @deprecated Use AdminCloudtentaclesSourceItem + AdminCloudtentaclesSourcesConfig instead */ +export interface AdminCloudtentaclesSourceConfig { + enabled: boolean + baseUrl: string + username: string + password: string + phone: string + deviceId: string + deviceType: number +} diff --git a/apps/frontend-react/src/types/admin/platform-config/index.ts b/apps/frontend-react/src/types/admin/platform-config/index.ts new file mode 100644 index 00000000..560c4c6d --- /dev/null +++ b/apps/frontend-react/src/types/admin/platform-config/index.ts @@ -0,0 +1,65 @@ +export type { + AdminNotificationBarkRecipient, + AdminNotificationWpushRecipient, + AdminNotificationConfig, + AdminNotificationSendResult, + AdminNotificationTestResult, +} from './notifications' + +export type { + AdminCloudtentaclesMonitorAccount, + AdminScheduledJobAccountRuntime, + AdminScheduledJobCloudtentaclesAccount, + AdminScheduledJobItem, + AdminScheduledJobsConfig, + AdminScheduledJobRuntimeState, + AdminScheduledJobsResponse, +} from './scheduled-jobs' + +export type { + AdminNinetyoneOrderItem, + AdminNinetyoneOrderListResult, + AdminNinetyoneOrderActionResult, +} from './ninetyone' + +export type { + AdminKuaishouEticketSourceConfig, + AdminKuaishouEticketShopConfigItem, + AdminKuaishouEticketShopInfoResult, + AdminKuaishouEticketDetailResult, + AdminKuaishouEticketConsumeResult, +} from './kuaishou-eticket' + +export type { + AdminKuaishouFeifeiProductRule, + AdminKuaishouFeifeiConfig, + AdminKuaishouFeifeiEffectiveConfig, + AdminKuaishouFeifeiConfigResponse, + AdminKuaishouFeifeiProductSyncResult, + AdminKuaishouFeifeiProductMatch, + AdminKuaishouFeifeiMatchResult, + AdminKuaishouFeifeiProductItem, + AdminKuaishouFeifeiProductListResult, + AdminKuaishouFeifeiOrderResult, +} from './kuaishou-feifei' + +export type { + AdminCloudtentaclesSourceItem, + AdminCloudtentaclesSourcesConfig, + AdminCloudtentaclesSourceConfigResponse, + AdminCloudtentaclesOverrideDeliveryItem, + AdminCloudtentaclesOverrideRule, + AdminCloudtentaclesOverrideRuleConfig, + AdminCloudtentaclesSessionsMap, + AdminCloudtentaclesSourceConfig, + AdminCloudtentaclesPersistedSession, + AdminCloudtentaclesSessionItem, + AdminCloudtentaclesSendSmsResult, + AdminCloudtentaclesSessionSummary, + AdminCloudtentaclesLoginTestResult, + AdminCloudtentaclesValidateSessionResult, + AdminCloudtentaclesSkuItem, + AdminCloudtentaclesSkuListResult, + AdminCloudtentaclesDeliveryRecordItem, + AdminCloudtentaclesDeliveryRecordListResult, +} from './cloudtentacles' diff --git a/apps/frontend-react/src/types/admin/platform-config/kuaishou-eticket.ts b/apps/frontend-react/src/types/admin/platform-config/kuaishou-eticket.ts new file mode 100644 index 00000000..046bca9c --- /dev/null +++ b/apps/frontend-react/src/types/admin/platform-config/kuaishou-eticket.ts @@ -0,0 +1,83 @@ +export interface AdminKuaishouEticketShopConfigItem { + shopId: string + kshopName: string + cookie: string + cookieMasked: string + hasCookie: boolean + userAvatar: string + enabled: boolean +} + +export interface AdminKuaishouEticketSourceConfig { + enabled: boolean + baseUrl: string + shops: AdminKuaishouEticketShopConfigItem[] +} + +export interface AdminKuaishouEticketShopInfoResult { + baseUrl: string + ok: boolean + result: number + errorMessage: string + requestId: string + shop: { + shopId: string + kshopName: string + userAvatar: string + settleStatus: number + hasCookie: boolean + } + raw: Record +} + +export interface AdminKuaishouEticketDetailResult { + baseUrl: string + eTicketId: string + ok: boolean + alreadyConsumed: boolean + result: number + errorMessage: string + requestId: string + serverTimestamp: string + detail: { + uid: string + fulfillDetailId: string + sellerId: string + formToken: string + validEndTime: string + validStartTime: string + leftReverseCount: number + eTicketId: string + oid: string + totalCount: number + leftCount: number + status: string + } | null + goods: { + itemId: string + itemPicUrl: string + itemTitle: string + price: string + skuDesc: string + skuId: string + } | null + raw: Record +} + +export interface AdminKuaishouEticketConsumeResult { + baseUrl: string + eTicketId: string + oid: string + formToken: string + num: number + storeId: string + ok: boolean + consumed: boolean + alreadyConsumed: boolean + result: number + errorMessage: string + requestId: string + serverTimestamp: string + detail: AdminKuaishouEticketDetailResult['detail'] + raw: Record +} diff --git a/apps/frontend-react/src/types/admin/platform-config/kuaishou-feifei.ts b/apps/frontend-react/src/types/admin/platform-config/kuaishou-feifei.ts new file mode 100644 index 00000000..ecea64ef --- /dev/null +++ b/apps/frontend-react/src/types/admin/platform-config/kuaishou-feifei.ts @@ -0,0 +1,104 @@ +export interface AdminKuaishouFeifeiProductRule { + id: string + enabled: boolean + productName: string + normalizedProductName: string + productCode: string + skuName: string + notes: string +} + +export interface AdminKuaishouFeifeiConfig { + enabled: boolean + baseUrl: string + appKey: string + appSecret: string + timeoutMs: number + notifyUrl: string + productRules: AdminKuaishouFeifeiProductRule[] +} + +export interface AdminKuaishouFeifeiEffectiveConfig { + enabled: boolean + baseUrl: string + timeoutMs: number + notifyUrl: string + hasAppKey: boolean + hasAppSecret: boolean + productRuleCount: number +} + +export interface AdminKuaishouFeifeiConfigResponse { + filePath: string + source: AdminKuaishouFeifeiConfig + effective: AdminKuaishouFeifeiEffectiveConfig +} + +export interface AdminKuaishouFeifeiProductSyncResult extends AdminKuaishouFeifeiConfigResponse { + sync: { + productCount: number + ruleCount: number + status: string + supplyProductName: string + } +} + +export interface AdminKuaishouFeifeiProductMatch { + matchMode: string + productName: string + normalizedProductName: string + productCode: string + skuName: string +} + +export interface AdminKuaishouFeifeiMatchResult { + productName: string + normalizedProductName: string + matched: boolean + match: AdminKuaishouFeifeiProductMatch | null +} + +export interface AdminKuaishouFeifeiProductItem { + productCode: string + name: string + channel: string + externalProductId: string + imageUrl: string + status: string + supplyStatus: string + supplyPricePoints: number + feePoints: number + unitCostPoints: number + maxOrderQuantity: number + salePricePoints: number | null + raw: Record +} + +export interface AdminKuaishouFeifeiProductListResult { + items: AdminKuaishouFeifeiProductItem[] + total: number + page: number + perPage: number + raw: Record +} + +export interface AdminKuaishouFeifeiOrderResult { + orderNo: string + platformOrderNo: string + productCode: string + productName: string + rechargeStatus: number + rechargeStatusLabel: string + pointsCharged: number + playerAccount: string + platformBuyNum: number + rechargeResultMessage: string + createdAt: string + updatedAt: string + rechargeFinishAt: string + h5: { + entryUrl: string + rechargeUrl: string + } + raw: Record +} diff --git a/apps/frontend-react/src/types/admin/platform-config/ninetyone.ts b/apps/frontend-react/src/types/admin/platform-config/ninetyone.ts new file mode 100644 index 00000000..19df7c49 --- /dev/null +++ b/apps/frontend-react/src/types/admin/platform-config/ninetyone.ts @@ -0,0 +1,32 @@ +export interface AdminNinetyoneOrderItem { + orderId: number + orderNo: string + outTradeNo: string + orderStatus: string + payStatus: string + shopId: string + shopName: string + productNo: string + productName: string + buyNum: number + taskCount: number + failReason: string + createdAt: string + updatedAt: string + items: Array> +} + +export interface AdminNinetyoneOrderListResult { + page: number + pageSize: number + total: number + items: AdminNinetyoneOrderItem[] +} + +export interface AdminNinetyoneOrderActionResult { + orderId: number + orderNo: string + orderStatus: string + orderItemCount: number + taskCount: number +} diff --git a/apps/frontend-react/src/types/admin/platform-config/notifications.ts b/apps/frontend-react/src/types/admin/platform-config/notifications.ts new file mode 100644 index 00000000..1a847acb --- /dev/null +++ b/apps/frontend-react/src/types/admin/platform-config/notifications.ts @@ -0,0 +1,50 @@ +export interface AdminNotificationBarkRecipient { + id: string + name: string + deviceKey: string + deviceKeyMasked: string + enabled: boolean +} + +export interface AdminNotificationWpushRecipient { + id: string + name: string + apiKey: string + apiKeyMasked: string + enabled: boolean +} + +export interface AdminNotificationConfig { + enabled: boolean + channels: { + bark: { + enabled: boolean + serverUrl: string + recipients: AdminNotificationBarkRecipient[] + } + wpush: { + enabled: boolean + recipients: AdminNotificationWpushRecipient[] + } + } +} + +export interface AdminNotificationSendResult { + channel: string + recipientId: string + recipientName: string + recipientKeyMasked: string + ok: boolean + status: number + errorMessage: string + response: unknown +} + +export interface AdminNotificationTestResult { + enabled: boolean + channel: string + successCount: number + failedCount: number + skippedCount: number + results: AdminNotificationSendResult[] +} diff --git a/apps/frontend-react/src/types/admin/platform-config/scheduled-jobs.ts b/apps/frontend-react/src/types/admin/platform-config/scheduled-jobs.ts new file mode 100644 index 00000000..5a0d82b1 --- /dev/null +++ b/apps/frontend-react/src/types/admin/platform-config/scheduled-jobs.ts @@ -0,0 +1,74 @@ +export interface AdminScheduledJobCloudtentaclesAccount { + sourceKey: string + label: string + enabled: boolean + assetThreshold: number +} + +export interface AdminScheduledJobItem { + id: string + type: string + enabled: boolean + intervalSeconds: number + cooldownSeconds: number + config: { + assetThreshold: number + accounts: AdminScheduledJobCloudtentaclesAccount[] + } +} + +export interface AdminScheduledJobsConfig { + enabled: boolean + jobs: AdminScheduledJobItem[] +} + +export interface AdminScheduledJobRuntimeState { + id: string + enabled: boolean + running: boolean + lastRunAt: string + lastFinishedAt: string + nextRunAt: string + lastStatus: string + lastMessage: string + lastAsset: number | null + lastThreshold: number | null + lastAccounts: AdminScheduledJobAccountRuntime[] + lastAccountCount: number + lastCheckedCount: number + lastOkCount: number + lastLowAssetCount: number + lastFailedCount: number + lastManual: boolean +} + +export interface AdminScheduledJobAccountRuntime { + sourceKey: string + label: string + enabled: boolean + hasToken: boolean + ok: boolean + skipped: boolean + status: string + message: string + asset: number | null + threshold: number + checkedAt: string +} + +export interface AdminCloudtentaclesMonitorAccount { + sourceKey: string + label: string + enabled: boolean + username: string + phoneMasked: string + hasToken: boolean + loggedInAt: string +} + +export interface AdminScheduledJobsResponse { + filePath: string + source: AdminScheduledJobsConfig + runtime: AdminScheduledJobRuntimeState[] + cloudtentaclesAccounts: AdminCloudtentaclesMonitorAccount[] +} diff --git a/apps/frontend-react/src/types/admin/tasks.ts b/apps/frontend-react/src/types/admin/tasks.ts new file mode 100644 index 00000000..20193cb5 --- /dev/null +++ b/apps/frontend-react/src/types/admin/tasks.ts @@ -0,0 +1,223 @@ +export interface AdminTaskListItem { + taskId: number + taskNo: string + platformOrderId: string + skuCode: string + skuName: string + status: string + executorKey: string + deliveryStatus: string + resultCode: string + resultMessage: string + resourceStatus: string + customerStatus: string + loginType: string + roleName: string + roleId: string + runtimeSessionId: string + claimedAt: string | null + roleConfirmedAt: string | null + redeemedAt: string | null + retryCount: number + lastError: string + createdAt: string + updatedAt: string + claimToken: string + screenshotPath: string +} + +export interface AdminTaskOperations { + canRetry: boolean + canRegenerateClaimLink: boolean + canClose: boolean + canMarkManualReview: boolean + canCompleteManualDispatch: boolean + canPrepareKuaishouCloudFulfillment: boolean + canRefreshKuaishouCloudRoleInfo: boolean + canRebindKuaishouCloudRole: boolean + canDispatchKuaishouCloudFulfillment: boolean + canReturnKuaishouCloudFulfillment: boolean + canViewSensitiveTaskData: boolean +} + +export interface AdminTaskActionResponse { + task: { + taskId: number + taskNo: string + status: string + deliveryStatus: string + resultCode: string + resultMessage: string + primaryClaimTokenId: number | null + lastError: string + updatedAt: string + } + claimUrl?: string + token?: string +} + +export interface AdminTaskDetail { + task: AdminTaskListItem + order: null | { + orderId: number + provider: string + platform: string + shopId: string + shopName: string + platformOrderId: string + payStatus: string + orderStatus: string + } + orderItem: null | { + orderItemId: number + skuCode: string + skuName: string + quantity: number + } + claimToken: null | { + primaryClaimTokenId: number + token: string + status: string + expiredAt: string + claimUrl: string + } + artifacts: Record + screenshotUrl: string + review: { + required: boolean + screenshotCapturedAt: string | null + roleId: string + roleName: string + } + redeemResolution: null | { + status: string + taskStatus: string + replacementCount: number + finishedAt: string | null + attempts: Array<{ + attempt: number + codeMasked: string + credentialType: string + outcome: string + resultCode: string + resultMessage: string + }> + } + kuaishouCloudFulfillment: null | { + flowType: string + configId: string + internalSkuCode: string + internalSkuName: string + ticket: { + code: string + capturedAt: string | null + status: string + verifiedAt: string | null + goodsTitle: string + leftCount: number + } + binding: { + prepareStatus: string + cloudSourceKeys: string[] + cloudSourceLabels: string[] + resolvedSourceKey: string + resolvedSourceLabel: string + skuId: number + skuName: string + vnKey: string + vnId: number + vnPhone: string + bindUrl: string + bindPreparedAt: string | null + bindExpiresAt: string | null + bindProbeAt: string | null + bindProbeStatus: string + bindProbeMessage: string + } + role: { + status: string + name: string + rid: string + refreshedAt: string | null + errorMessage: string + rawInfo: Record | null + } + purchase: { + autoBuyEnabled: boolean + minAssetReserve: number + usedKnapsack: boolean + purchaseTriggered: boolean + assetBefore: number + assetAfter: number + purchaseAt: string | null + } + dispatch: { + status: string + dispatchAt: string | null + sendType: number + note: string + } + returnNumber: { + status: string + returnedAt: string | null + autoReturnEnabled: boolean + } + consume: { + status: string + shopId: string + shopName: string + autoConsumeEnabled: boolean + consumedAt: string | null + errorMessage: string + } + rebind: { + currentAttempt: number + history: Array<{ + attempt: number + source: string + requestedAt: string | null + requestedBy: Record | null + status: string + errorMessage: string + oldBinding: { + vnKey: string + vnId: number + vnPhone: string + bindUrl: string + bindPreparedAt: string | null + bindExpiresAt: string | null + roleName: string + roleId: string + } + newBinding: null | { + vnKey: string + vnId: number + vnPhone: string + bindUrl: string + bindPreparedAt: string | null + bindExpiresAt: string | null + } + }> + } + notes: string + } + manualDispatch: null | { + outcome: string + deliveryReference: string + deliveredCredential: string + resultMessage: string + completedAt: string | null + completedBy: null | { + userId: number + username: string + role: string + } + } + events: Array<{ + eventId: number + eventType: string + payload: Record + createdAt: string + }> + operations: AdminTaskOperations +} diff --git a/apps/frontend-react/src/types/admin/users.ts b/apps/frontend-react/src/types/admin/users.ts new file mode 100644 index 00000000..7c5438a2 --- /dev/null +++ b/apps/frontend-react/src/types/admin/users.ts @@ -0,0 +1,10 @@ +import type { AdminRole, AdminUserStatus } from './auth' + +export interface AdminUserListItem { + userId: number + username: string + role: AdminRole + status: AdminUserStatus + createdAt: string + updatedAt: string +} diff --git a/apps/frontend-react/src/types/claim.ts b/apps/frontend-react/src/types/claim.ts new file mode 100644 index 00000000..d1370129 --- /dev/null +++ b/apps/frontend-react/src/types/claim.ts @@ -0,0 +1,168 @@ +import type { KnownTaskStatus } from '@/domain/task-status' + +export type ClaimTokenStatus = 'active' | 'used' | 'expired' | 'revoked' | (string & {}) + +export type ClaimTaskStatus = KnownTaskStatus | (string & {}) + +export interface ClaimTaskInfo { + taskId: number + taskNo: string + status: ClaimTaskStatus + executorKey: string + requiresSupportReview: boolean + expiresAt: string | null + claimedAt: string | null + roleConfirmedAt: string | null + redeemedAt: string | null + loginType: string + lastError: string + runtimeSessionId: string +} + +export interface ClaimOrderInfo { + orderId: number + platform: string + platformOrderId: string + payStatus: string + orderStatus: string + totalAmount: string + totalAmountFen: number + currency: string +} + +export interface ClaimOrderItemInfo { + orderItemId: number + skuCode: string + skuName: string + quantity: number +} + +export interface ClaimProductInfo { + title: string + skuCode: string + quantity: number + isBundle: boolean + items: Array<{ + cloudSkuId: number + name: string + quantity: number + }> +} + +export interface ClaimResultInfo { + resultCode: string + resultMessage: string + screenshotReady: boolean + screenshotUrl: string +} + +export interface ClaimKuaishouCloudFlowInfo { + flowType: 'kuaishou_cloud' + shopId: string + shopName: string + guideImages: string[] + ticket: { + code: string + status: string + capturedAt: string | null + verifiedAt: string | null + goodsTitle: string + leftCount: number + } + binding: { + prepareStatus: string + cloudSourceKeys: string[] + resolvedSourceKey: string + skuId: number + skuName: string + vnKey: string + vnId: number + bindUrl: string + bindPreparedAt: string | null + bindExpiresAt: string | null + bindProbeAt: string | null + bindProbeStatus: string + bindProbeMessage: string + vnPhone: string + roleName: string + roleId: string + } + role: { + status: string + name: string + rid: string + refreshedAt: string | null + errorMessage: string + defaultName: string + defaultRid: string + defaultCapturedAt: string | null + defaultCaptureStatus: string + defaultErrorMessage: string + isDefaultRole: boolean + } + purchase: { + autoBuyEnabled: boolean + minAssetReserve: number + usedKnapsack: boolean + purchaseTriggered: boolean + assetBefore: number + assetAfter: number + purchaseAt: string | null + } + dispatch: { + status: string + dispatchAt: string | null + failedAt: string | null + failedStage: string + errorCode: string + errorMessage: string + note: string + } + returnNumber: { + status: string + returnedAt: string | null + } + consume: { + status: string + shopId: string + shopName: string + consumedAt: string | null + errorMessage: string + } + rebind?: { + currentAttempt: number + history: Array> + } +} + +export interface ClaimKuaishouFeifeiFlowInfo { + flowType: 'kuaishou_feifei' + productCode: string + productName: string + platformOrderNo: string + orderNo: string + rechargeStatus: number + rechargeStatusLabel: string + rechargeResultMessage: string + claimUrl: string + consumeStatus: string + h5: { + entryUrl: string + rechargeUrl: string + } + lastSyncedAt: string | null +} + +export interface ClaimDetailData { + tokenStatus: ClaimTokenStatus + claimUrl: string + flowType: 'kuaishou_cloud' | 'kuaishou_ct_assisted' | 'kuaishou_feifei' | (string & {}) + task: ClaimTaskInfo + order: ClaimOrderInfo + orderItem: ClaimOrderItemInfo + product: ClaimProductInfo + session: unknown | null + kuaishouCloudFulfillment: ClaimKuaishouCloudFlowInfo | null + kuaishouFeifei: ClaimKuaishouFeifeiFlowInfo | null + result: ClaimResultInfo | null +} diff --git a/apps/frontend-react/src/utils/admin-auth.ts b/apps/frontend-react/src/utils/admin-auth.ts new file mode 100644 index 00000000..cbb374e6 --- /dev/null +++ b/apps/frontend-react/src/utils/admin-auth.ts @@ -0,0 +1,99 @@ +const ADMIN_TOKEN_KEY = 'order-site-admin-token' +const ADMIN_EXPIRES_AT_KEY = 'order-site-admin-token-expires-at' +const ADMIN_USER_ID_KEY = 'order-site-admin-user-id' +const ADMIN_USERNAME_KEY = 'order-site-admin-username' +const ADMIN_ROLE_KEY = 'order-site-admin-role' + +const ADMIN_ROLE_LEVEL: Record<'support' | 'operator' | 'admin', number> = { + support: 1, + operator: 2, + admin: 3, +} + +export function getAdminToken() { + return localStorage.getItem(ADMIN_TOKEN_KEY) || '' +} + +export function getAdminTokenExpiresAt() { + return localStorage.getItem(ADMIN_EXPIRES_AT_KEY) || '' +} + +export function getAdminUserId() { + const value = Number(localStorage.getItem(ADMIN_USER_ID_KEY) || 0) + return Number.isFinite(value) && value > 0 ? value : 0 +} + +export function getAdminUsername() { + return localStorage.getItem(ADMIN_USERNAME_KEY) || '' +} + +export function getAdminRole() { + const role = localStorage.getItem(ADMIN_ROLE_KEY) + if (role === 'admin' || role === 'operator' || role === 'support') { + return role + } + + return 'support' +} + +export function hasAdminRole(role: 'admin' | 'operator' | 'support') { + if (!hasAdminSession()) { + return false + } + + return ADMIN_ROLE_LEVEL[getAdminRole()] >= ADMIN_ROLE_LEVEL[role] +} + +export function setAdminSession( + token: string, + expiresAt: string, + user?: { userId: number; username: string; role: 'admin' | 'operator' | 'support' }, +) { + localStorage.setItem(ADMIN_TOKEN_KEY, token) + localStorage.setItem(ADMIN_EXPIRES_AT_KEY, expiresAt) + + if (user?.userId) { + localStorage.setItem(ADMIN_USER_ID_KEY, String(user.userId)) + } + + if (user?.username) { + localStorage.setItem(ADMIN_USERNAME_KEY, user.username) + } + + if (user?.role) { + localStorage.setItem(ADMIN_ROLE_KEY, user.role) + } +} + +export function clearAdminSession() { + localStorage.removeItem(ADMIN_TOKEN_KEY) + localStorage.removeItem(ADMIN_EXPIRES_AT_KEY) + localStorage.removeItem(ADMIN_USER_ID_KEY) + localStorage.removeItem(ADMIN_USERNAME_KEY) + localStorage.removeItem(ADMIN_ROLE_KEY) +} + +export function hasAdminSession() { + const token = getAdminToken() + const expiresAt = getAdminTokenExpiresAt() + + if (!token) { + return false + } + + if (isExpired(expiresAt)) { + clearAdminSession() + return false + } + + return true +} + +function isExpired(expiresAt: string) { + if (!expiresAt) { + return false + } + + const expiresAtMs = Date.parse(expiresAt) + return Number.isFinite(expiresAtMs) && expiresAtMs <= Date.now() +} diff --git a/apps/frontend-react/src/utils/admin-display.ts b/apps/frontend-react/src/utils/admin-display.ts new file mode 100644 index 00000000..c579d923 --- /dev/null +++ b/apps/frontend-react/src/utils/admin-display.ts @@ -0,0 +1,135 @@ +export function formatStatusWithRaw(status: string, labelMap: Record = {}) { + const normalized = String(status || '').trim() + + if (!normalized) { + return '-' + } + + const key = normalized.toLowerCase() + const label = labelMap[key] + + if (!label || label === normalized) { + return normalized + } + + return `${label} (${normalized})` +} + +export function formatAuditAction(action: string) { + const labelMap: Record = { + admin_user_created: '创建后台用户', + admin_user_role_updated: '修改用户角色', + admin_user_status_updated: '修改用户状态', + admin_user_password_reset: '重置用户密码', + task_regenerate_claim_link: '重发领取链接', + task_kuaishou_cloud_rebind_role: '换绑角色', + task_closed: '关闭任务', + task_mark_manual_review: '转人工处理', + task_complete_manual_dispatch: '完成人工履约', + platform_shop_config_updated: '更新店铺配置', + platform_fulfillment_config_updated: '更新履约配置', + } + + return formatStatusWithRaw(action, labelMap) +} + +export function formatAuditTargetType(targetType: string) { + const labelMap: Record = { + admin_user: '后台用户', + task: '交付任务', + platform_config: '平台配置', + } + + return formatStatusWithRaw(targetType, labelMap) +} + +export function formatTaskEventType(eventType: string) { + const labelMap: Record = { + manual_dispatch_completed: '人工履约已回写', + claim_redeem_code_used: '兑换码已使用', + claim_redeem_code_invalid: '兑换码错误', + claim_redeem_completed: '兑换链路完成', + kuaishou_cloud_rebind_requested: '发起角色换绑', + kuaishou_cloud_rebind_old_number_returned: '换绑旧号已退还', + kuaishou_cloud_rebind_prepared: '换绑资源已准备', + kuaishou_cloud_rebind_failed: '换绑失败', + } + + return formatStatusWithRaw(eventType, labelMap) +} + +export function formatRedeemOutcomeLabel(value: string) { + const normalized = String(value || '').trim() + + switch (normalized) { + case 'success': + return '兑换成功' + case 'code_used': + return '兑换码已使用' + case 'code_invalid': + return '兑换码错误' + case 'captcha_rejected': + return '验证码错误' + case 'failed': + return '兑换失败' + default: + return normalized || '-' + } +} + +export function formatRedeemResolutionStatus(value: string) { + return value === 'success' ? '已完成' : value === 'failed' ? '失败收口' : value || '-' +} + +export function formatKuaishouRoleInfoLabel(value: string) { + const normalized = String(value || '').trim() + + switch (normalized) { + case 'name': + return '角色名称' + case 'rid': + return '角色 ID' + default: + return normalized || '-' + } +} + +export function formatTaskEventPayload(payload: Record) { + const parts = [ + payload.outcome ? `结果 ${payload.outcome}` : '', + payload.resultCode ? `代码 ${payload.resultCode}` : '', + payload.resultMessage ? `说明 ${payload.resultMessage}` : '', + payload.codeMasked ? `凭据 ${payload.codeMasked}` : '', + payload.previousCodeMasked ? `旧码 ${payload.previousCodeMasked}` : '', + payload.nextCodeMasked ? `新码 ${payload.nextCodeMasked}` : '', + payload.ticketCodeMasked ? `卡券号 ${payload.ticketCodeMasked}` : '', + payload.vnId ? `虚拟号 #${payload.vnId}` : '', + payload.oldVnId ? `旧虚拟号 #${payload.oldVnId}` : '', + payload.vnPhoneMasked ? `号码 ${payload.vnPhoneMasked}` : '', + payload.oldVnPhoneMasked ? `旧号码 ${payload.oldVnPhoneMasked}` : '', + payload.oldRoleName ? `旧角色 ${payload.oldRoleName}` : '', + payload.oldRoleId ? `旧角色ID ${payload.oldRoleId}` : '', + payload.attempt ? `第 ${payload.attempt} 次` : '', + payload.bindUrl ? `绑定链接 ${payload.bindUrl}` : '', + payload.sendType ? `发送类型 ${payload.sendType}` : '', + payload.note ? `备注 ${payload.note}` : '', + payload.previousOutcome + ? `切换原因 ${formatRedeemOutcomeLabel(String(payload.previousOutcome || ''))}` + : '', + payload.deliveryReference ? `单号 ${payload.deliveryReference}` : '', + payload.platformOrderId ? `平台单 ${payload.platformOrderId}` : '', + payload.trigger ? `触发 ${payload.trigger}` : '', + payload.reason ? `原因 ${payload.reason}` : '', + payload.purchaseTriggered === true ? '已触发购买' : '', + payload.usedKnapsack === true ? '使用背包库存' : '', + payload.responseStatus ? `HTTP ${payload.responseStatus}` : '', + payload.requestId ? `请求 ${payload.requestId}` : '', + payload.errorMessage ? `错误 ${payload.errorMessage}` : '', + ].filter(Boolean) + + if (parts.length > 0) { + return parts.join(' · ') + } + + return JSON.stringify(payload || {}) +} diff --git a/apps/frontend-react/src/utils/admin-options.ts b/apps/frontend-react/src/utils/admin-options.ts new file mode 100644 index 00000000..5abb1c10 --- /dev/null +++ b/apps/frontend-react/src/utils/admin-options.ts @@ -0,0 +1,52 @@ +export const adminPayStatusOptions = [ + { label: '全部', value: '' }, + { label: '未支付', value: 'unpaid' }, + { label: '已支付', value: 'paid' }, + { label: '失败', value: 'failed' }, + { label: '已退款', value: 'refunded' }, +] + +export const adminTaskStatusOptions = [ + { label: '全部', value: '' }, + { label: '待支付', value: 'pending_payment' }, + { label: '已支付', value: 'paid' }, + { label: '已生成链接', value: 'link_generated' }, + { label: '已创建会话', value: 'claimed' }, + { label: '已确认角色', value: 'role_confirmed' }, + { label: '兑换中', value: 'redeeming' }, + { label: '已兑换', value: 'redeemed' }, + { label: '待重试', value: 'retry_pending' }, + { label: '人工处理', value: 'manual_review' }, + { label: '已过期', value: 'expired' }, + { label: '已关闭', value: 'closed' }, +] + +export const adminUserRoleOptions = [ + { label: '全部', value: '' }, + { label: '管理员', value: 'admin' }, + { label: '普通运营', value: 'operator' }, + { label: '客服', value: 'support' }, +] + +export const adminUserStatusOptions = [ + { label: '全部', value: '' }, + { label: '生效中', value: 'active' }, + { label: '已停用', value: 'disabled' }, +] + +export const adminAuditActionOptions = [ + { label: '全部', value: '' }, + { label: '创建后台用户', value: 'admin_user_created' }, + { label: '修改用户角色', value: 'admin_user_role_updated' }, + { label: '修改用户状态', value: 'admin_user_status_updated' }, + { label: '重置用户密码', value: 'admin_user_password_reset' }, + { label: '换绑角色', value: 'task_kuaishou_cloud_rebind_role' }, + { label: '关闭任务', value: 'task_closed' }, + { label: '转人工处理', value: 'task_mark_manual_review' }, +] + +export const adminAuditTargetTypeOptions = [ + { label: '全部', value: '' }, + { label: '后台用户', value: 'admin_user' }, + { label: '交付任务', value: 'task' }, +] diff --git a/apps/frontend-react/src/utils/admin-time.ts b/apps/frontend-react/src/utils/admin-time.ts new file mode 100644 index 00000000..b3696153 --- /dev/null +++ b/apps/frontend-react/src/utils/admin-time.ts @@ -0,0 +1 @@ +export { formatDateTime as formatAdminDateTime } from '@/utils/date-time' diff --git a/apps/frontend-react/src/utils/date-time.ts b/apps/frontend-react/src/utils/date-time.ts new file mode 100644 index 00000000..1567b29f --- /dev/null +++ b/apps/frontend-react/src/utils/date-time.ts @@ -0,0 +1,54 @@ +const ISO_DATE_TIME_PATTERN = + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/ + +export function isIsoDateTimeString(value: unknown): value is string { + return typeof value === 'string' && ISO_DATE_TIME_PATTERN.test(value.trim()) +} + +export function formatDateTime(value: string | null | undefined, fallback = '-') { + const normalized = String(value || '').trim() + + if (!normalized) { + return fallback + } + + const date = new Date(normalized) + + if (Number.isNaN(date.getTime())) { + return normalized.replace('T', ' ').replace('Z', '') + } + + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + const hours = String(date.getHours()).padStart(2, '0') + const minutes = String(date.getMinutes()).padStart(2, '0') + const seconds = String(date.getSeconds()).padStart(2, '0') + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` +} + +export function normalizeDateTimeValue(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map((item) => normalizeDateTimeValue(item)) + } + + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([key, currentValue]) => [ + key, + normalizeDateTimeValue(currentValue), + ]), + ) + } + + if (isIsoDateTimeString(value)) { + return formatDateTime(value, '') + } + + return value +} + +export function stringifyDisplayJson(value: unknown, space = 2) { + return JSON.stringify(normalizeDateTimeValue(value), null, space) +} diff --git a/apps/frontend-react/src/vite-env.d.ts b/apps/frontend-react/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/apps/frontend-react/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/frontend-react/tsconfig.app.json b/apps/frontend-react/tsconfig.app.json new file mode 100644 index 00000000..8d75ae3c --- /dev/null +++ b/apps/frontend-react/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "ignoreDeprecations": "6.0", + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"] +} diff --git a/apps/frontend-react/tsconfig.json b/apps/frontend-react/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/apps/frontend-react/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/frontend-react/tsconfig.node.json b/apps/frontend-react/tsconfig.node.json new file mode 100644 index 00000000..672c8363 --- /dev/null +++ b/apps/frontend-react/tsconfig.node.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "moduleResolution": "Bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/apps/frontend-react/vite.config.ts b/apps/frontend-react/vite.config.ts new file mode 100644 index 00000000..eae32342 --- /dev/null +++ b/apps/frontend-react/vite.config.ts @@ -0,0 +1,31 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +const currentDir = path.dirname(fileURLToPath(import.meta.url)) +const apiTarget = process.env.VITE_API_TARGET || 'http://127.0.0.1' + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(currentDir, 'src'), + }, + }, + server: { + host: '0.0.0.0', + port: 5174, + proxy: { + '/api': { + target: apiTarget, + changeOrigin: true, + }, + '/health': { + target: apiTarget, + changeOrigin: true, + }, + }, + }, +})