diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..883dce1 --- /dev/null +++ b/website/README.md @@ -0,0 +1,43 @@ +# OPC 培训站(website) + +> 📍 现位于 `code/training/website/`(业务 Web 前端,原 `code/opc-training/website/`)。课程内容见 `materials/培训计划/`;关联小程序 `code/training/miniprogram/`;**后端已并入 `code/server-core/app/training/`**(`/api/*` 由 server-core `dispatcher.py` 对外提供)。 + +课程体系 0.1 的对外展示与内部运营站。React + Vite SPA,暗色粒子品牌语言。 + +## 运行方式 + +```bash +npm install +npm run dev # 开发(含 /api 模拟认证) +npm run build # 构建 dist +npm run preview # 预览构建产物(含 /api 模拟认证) +node server/start.mjs # 独立服务:托管 dist + /api(默认 8091,生产/演示用) +``` + +## 路由与权限 + +| 路由 | 内容 | 权限 | +|------|------|------| +| `#/` | 首页营销落地页 | 公开 | +| `#/opc-test` | OPC 创业基因测评 | 公开 | +| `#/pine` 及 `#/pine/*` | 内部工具(课程体系/结构/课程表/卡片/工具) | 需登录 | + +**内部工具登录**:访问 `#/pine/*` 时路由守卫触发登录门。账号 `pine` / 密码 `123456`(**模拟**,见下)。 + +内部工具**不进公开导航**,仅通过 URL 直达;公开导航只含「首页 / OPC测评」。 + +## 模拟认证与迁移 + +- 模拟接口:`server/mock-api.mjs`(`POST /api/auth/login`、`POST /api/auth/logout`、`GET /api/auth/verify`),账号密码暂时写死 `pine/123456`。 +- 已挂载到 Vite dev / preview / 独立服务三处。 +- **迁移为真实 API**:只需改 `src/services/auth.js` 顶部的 `API_BASE` 指向真实后端,并替换 `server/` 下的 mock 实现即可;SPA 其余代码无需改动。 + +## 目录速览 + +``` +server/ 模拟认证 API + 独立启动服务 +src/pages/ 页面(Home/OPCTest 公开;System/Structures/Schedule/Cards/Tools 内部) +src/data/ 内容数据(home/system/structures/schedule/cards/timetable/opcTest/site) +src/components/ 原子/分子/组织/模板组件 +src/services/ auth 认证服务 +``` diff --git a/website/_ssr2.mjs b/website/_ssr2.mjs new file mode 100644 index 0000000..15f7859 --- /dev/null +++ b/website/_ssr2.mjs @@ -0,0 +1,16 @@ +import { createServer } from 'vite'; +import React from 'react'; +import { renderToString } from 'react-dom/server'; +globalThis.window = globalThis.window || { matchMedia: () => ({ matches: false }), innerWidth: 1024, scrollTo() {} }; +globalThis.document = globalThis.document || { getElementById: () => null, querySelector: () => null }; +const server = await createServer({ server: { middlewareMode: true }, appType: 'custom', logLevel: 'error' }); +const results = []; +try { + const home = await server.ssrLoadModule('/src/pages/Home.jsx'); + const h = renderToString(React.createElement(home.default)); + results.push(['Home(公开)渲染', h.includes('ONE PERSON') && h.includes('DESIGNED TO EVOLVE') && h.includes('滚动开班') && h.includes('先测评,再报名')]); + results.push(['Home 不再指向内部页', !h.includes('#/system') && !h.includes('#/tools') && !h.includes('#/schedule') && !h.includes('#/pine')]); +} catch (e) { console.error('SSR ERROR:', e.message); process.exit(1); } +finally { await server.close(); } +results.forEach(([n, ok]) => console.log(ok ? '✓' : '✗', n)); +process.exit(results.every(([, ok]) => ok) ? 0 : 1); diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..0108e34 --- /dev/null +++ b/website/index.html @@ -0,0 +1,28 @@ + + + + + + OPC 培训 · 为进化而设计 + + + + + + + + + +
+ + + diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..ec4ec70 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,1740 @@ +{ + "name": "opc-training-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opc-training-web", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^5.4.11" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.15", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz", + "integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.411", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz", + "integrity": "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "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.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..55865d6 --- /dev/null +++ b/website/package.json @@ -0,0 +1,20 @@ +{ + "name": "opc-training-web", + "private": true, + "version": "0.1.0", + "type": "module", + "description": "OPC 培训体系 · 课程体系0.1 站点(React + 原子设计)", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^5.4.11" + } +} diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml new file mode 100644 index 0000000..fdfba4b --- /dev/null +++ b/website/pnpm-lock.yaml @@ -0,0 +1,1076 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@5.4.11) + vite: + specifier: ^5.4.11 + version: 5.4.11 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.3': + resolution: {integrity: sha512-yTgnwQpFVYfvvo4SvRFB0SwrW8YjOxEoT7wfMT7Ol5v7v5LDNvSGo67aExmxOb87nQNeWPVvaGBNfQ7BXcrZ9w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.3': + resolution: {integrity: sha512-c+ty9necz3zB1Y+d/N+mC6KVVkGUUOcm4ZmT5i/Fk5arOaY3i6CA3P5wo/7+XzV8cb4GrI/Zjp8NuOQ9Lfsosw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.3': + resolution: {integrity: sha512-bviJOLMgurLJtF1/mAoJLxDZDL6oU5/ztMHnJQRejbJrSc9FFu0QoUoFhvi6qSKJEw9y5oGyvr9fuDtzJ30rNQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.3': + resolution: {integrity: sha512-JReHfYCRK3FVX4Ra+y5EBH1b9e16TV2OxrPAvzMsGeES0X2Ndm9ImQRI4Ket757vhc5XBOuGperw63upesclRw==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.3': + resolution: {integrity: sha512-U3fuQ0xNiAkXOmQ6w5dKpEvXQRSpHOnbw7gEfHCRXPeTKW9sBzVck6C5Yneb8LfJm0l6le4NQfkNPnWMSlTFUQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.3': + resolution: {integrity: sha512-3m1CEB7F07s19wmaMNI2KANLcnaqryJxO1fXHUV5j1rWn+wMxdUYoPyO2TnAbfRZdi7ADRwJClmOwgT13qlP3Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.3': + resolution: {integrity: sha512-fsNAAl5pU6wmKHq91cHWQT0Fz0vtyE1JauMzKotrwqIKAswwP5cpHUCxZNSTuA/JlqtScq20/5KZ+TxQdovU/g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.3': + resolution: {integrity: sha512-tci+UJ4zP5EGF4rp8XlZIdq1q1a/1h9XuronfxTMCNBslpCtmk97Q/5qqy1Mu4zIc0yswN/yP/BLX+NTUC1bXA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.3': + resolution: {integrity: sha512-vvG6R5g5ieB4eCJBQevyDMb31LMHthLpXTc2IGkFnPWS/GzIFDnaYFp558O+XybTmYrVjxnryru7QRleJvmZ6Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.3': + resolution: {integrity: sha512-f6kz2QpSuyHHg01cDawj0vkyMwuIvN62UAguQfnNVzbge2uWLhA7TCXOn83DT0ZvyJmBI943MItgTovUob36SQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.3': + resolution: {integrity: sha512-HjCWhH7K96Na+66TacDLJmOI9R8iDWDDiqe17C7znGvvE4sW1ECt9ly0AJ3dJH62jHyVqW9xpxZEU1jKdt+29A==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.3': + resolution: {integrity: sha512-BGpimEccmHBZRcAhdlRIxMp7x9PyJxUtj7apL2IuoG9VxvU/l/v1z015nFs7Si7tXUwEsvjc1rOJdZCn4QTU+Q==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.3': + resolution: {integrity: sha512-5rMOWkp7FQGtAH3QJddP4w3s47iT20hwftqdm7b+loe95o8JU8ro3qZbhgMRy0VuFU0DizymF1pBKkn3YHWtsw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.3': + resolution: {integrity: sha512-h0zj1ldel89V5sjPLo5H1SyMzp4VrgN1tPkN29TmjvO1/r0MuMRwJxL8QY05SmfsZRs6TF0c/IDH3u7XYYmbAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.3': + resolution: {integrity: sha512-dkAKcTsTJ+CRX6bnO17qDJbLoW37npd5gSNtSzjYQr0svghLJYGYB0NF1SNcU1vDcjXLYS5pO4qOW4YbFama4A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.3': + resolution: {integrity: sha512-vnD1YUkovEdnZWEuMmy2X2JmzsHQqPpZElXx6dxENcIwTu+Cu5ERax6+Ke1QsE814Zf3c6rxCfwQdCTQ7tPuXA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.3': + resolution: {integrity: sha512-IOXOIm9WaK7plL2gMhsWJd+l2bfrhfilv0uPTptoRoSb2p09RghhQQp9YY6ZJhk/kqmeRt6siRdMSLLwzuT0KQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.3': + resolution: {integrity: sha512-uTgCwsvQ5+vCQnqM//EfDSuomo2LhdWhFPS8VL8xKf+PKTCrcT/2kPPoWMTs22aB63MLdGMJiE3f1PHvCDmUOw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.3': + resolution: {integrity: sha512-vNAkR17Ub2MgEud2Wag/OE4HTSI6zlb291UYzHez/psiKarp0J8PKGDnAhMBcHFoOHMXHfExzmjMojJNbAStrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.3': + resolution: {integrity: sha512-W8H9jlGiSBomkgmouaRoTXo49j4w4Kfbl6I1bIdO/vT0+0u4f20ko3ELzV3hPI6XV6JNBVX+8BC+ajHkvffIJA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.3': + resolution: {integrity: sha512-EjEomwyLSCg8Ag3LDILIqYCZAq/y3diJ04PnqGRgq8/4O3VNlXyMd54j/saShaN4h5o5mivOjAzmU6C3X4v0xw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.3': + resolution: {integrity: sha512-WGiE/GgbsEwR33++5rzjiYsKyHywE8QSZPF7Rfx9EBfK3Qn3xyR6IjyCr5Uk38Kg8fG4/2phN7sXp4NPWd3fcw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.3': + resolution: {integrity: sha512-xRxC0jaJWDLYvcUvjQmHCJSfMrgmUuvsoXgDeU/wTorQ1ngDdUBuFtgY3W1Pc5sprGAvZBtWdJX7RPg/iZZUqA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.10': + resolution: {integrity: sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg==} + + '@jridgewell/sourcemap-codec@1.4.14': + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-android-arm-eabi@4.62.5': + resolution: {integrity: sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.5': + resolution: {integrity: sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.5': + resolution: {integrity: sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.5': + resolution: {integrity: sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.5': + resolution: {integrity: sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.5': + resolution: {integrity: sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + resolution: {integrity: sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.5': + resolution: {integrity: sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.5': + resolution: {integrity: sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.5': + resolution: {integrity: sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.5': + resolution: {integrity: sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.5': + resolution: {integrity: sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.5': + resolution: {integrity: sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.5': + resolution: {integrity: sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.5': + resolution: {integrity: sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.5': + resolution: {integrity: sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.5': + resolution: {integrity: sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.5': + resolution: {integrity: sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.5': + resolution: {integrity: sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.5': + resolution: {integrity: sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.5': + resolution: {integrity: sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.5': + resolution: {integrity: sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.5': + resolution: {integrity: sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.5': + resolution: {integrity: sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.5': + resolution: {integrity: sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + + baseline-browser-mapping@2.11.16: + resolution: {integrity: sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + electron-to-chromium@1.5.412: + resolution: {integrity: sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==} + + esbuild@0.21.3: + resolution: {integrity: sha512-Kgq0/ZsAPzKrbOjCQcjoSmPoWhlcVnGAUo7jvaLHoxW1Drto0KGkR1xBNg2Cp43b9ImvxmPEJZ9xkfcnqPsfBw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + js-tokens@3.0.0: + resolution: {integrity: sha512-poXEQHPMmTrYZuJgNRll2sbc3kJsSU1m/g1Q93IE6txNj3p6xOOOmdj1G/zCVGawYSPzTkSoWGg1otqbeqKJeg==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + rollup@4.62.5: + resolution: {integrity: sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.21.3': + optional: true + + '@esbuild/android-arm64@0.21.3': + optional: true + + '@esbuild/android-arm@0.21.3': + optional: true + + '@esbuild/android-x64@0.21.3': + optional: true + + '@esbuild/darwin-arm64@0.21.3': + optional: true + + '@esbuild/darwin-x64@0.21.3': + optional: true + + '@esbuild/freebsd-arm64@0.21.3': + optional: true + + '@esbuild/freebsd-x64@0.21.3': + optional: true + + '@esbuild/linux-arm64@0.21.3': + optional: true + + '@esbuild/linux-arm@0.21.3': + optional: true + + '@esbuild/linux-ia32@0.21.3': + optional: true + + '@esbuild/linux-loong64@0.21.3': + optional: true + + '@esbuild/linux-mips64el@0.21.3': + optional: true + + '@esbuild/linux-ppc64@0.21.3': + optional: true + + '@esbuild/linux-riscv64@0.21.3': + optional: true + + '@esbuild/linux-s390x@0.21.3': + optional: true + + '@esbuild/linux-x64@0.21.3': + optional: true + + '@esbuild/netbsd-x64@0.21.3': + optional: true + + '@esbuild/openbsd-x64@0.21.3': + optional: true + + '@esbuild/sunos-x64@0.21.3': + optional: true + + '@esbuild/win32-arm64@0.21.3': + optional: true + + '@esbuild/win32-ia32@0.21.3': + optional: true + + '@esbuild/win32-x64@0.21.3': + optional: true + + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.10 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.4.10': {} + + '@jridgewell/sourcemap-codec@1.4.14': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@rollup/rollup-android-arm-eabi@4.62.5': + optional: true + + '@rollup/rollup-android-arm64@4.62.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.5': + optional: true + + '@rollup/rollup-darwin-x64@4.62.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.5': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.5': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/estree@1.0.9': {} + + '@vitejs/plugin-react@4.3.4(vite@5.4.11)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.11 + transitivePeerDependencies: + - supports-color + + baseline-browser-mapping@2.11.16: {} + + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.16 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.412 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + + caniuse-lite@1.0.30001809: {} + + convert-source-map@2.0.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + electron-to-chromium@1.5.412: {} + + esbuild@0.21.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.3 + '@esbuild/android-arm': 0.21.3 + '@esbuild/android-arm64': 0.21.3 + '@esbuild/android-x64': 0.21.3 + '@esbuild/darwin-arm64': 0.21.3 + '@esbuild/darwin-x64': 0.21.3 + '@esbuild/freebsd-arm64': 0.21.3 + '@esbuild/freebsd-x64': 0.21.3 + '@esbuild/linux-arm': 0.21.3 + '@esbuild/linux-arm64': 0.21.3 + '@esbuild/linux-ia32': 0.21.3 + '@esbuild/linux-loong64': 0.21.3 + '@esbuild/linux-mips64el': 0.21.3 + '@esbuild/linux-ppc64': 0.21.3 + '@esbuild/linux-riscv64': 0.21.3 + '@esbuild/linux-s390x': 0.21.3 + '@esbuild/linux-x64': 0.21.3 + '@esbuild/netbsd-x64': 0.21.3 + '@esbuild/openbsd-x64': 0.21.3 + '@esbuild/sunos-x64': 0.21.3 + '@esbuild/win32-arm64': 0.21.3 + '@esbuild/win32-ia32': 0.21.3 + '@esbuild/win32-x64': 0.21.3 + + escalade@3.2.0: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + js-tokens@3.0.0: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 3.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + ms@2.1.3: {} + + nanoid@3.3.18: {} + + node-releases@2.0.53: {} + + picocolors@1.1.1: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + rollup@4.62.5: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.5 + '@rollup/rollup-android-arm64': 4.62.5 + '@rollup/rollup-darwin-arm64': 4.62.5 + '@rollup/rollup-darwin-x64': 4.62.5 + '@rollup/rollup-freebsd-arm64': 4.62.5 + '@rollup/rollup-freebsd-x64': 4.62.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.5 + '@rollup/rollup-linux-arm-musleabihf': 4.62.5 + '@rollup/rollup-linux-arm64-gnu': 4.62.5 + '@rollup/rollup-linux-arm64-musl': 4.62.5 + '@rollup/rollup-linux-loong64-gnu': 4.62.5 + '@rollup/rollup-linux-loong64-musl': 4.62.5 + '@rollup/rollup-linux-ppc64-gnu': 4.62.5 + '@rollup/rollup-linux-ppc64-musl': 4.62.5 + '@rollup/rollup-linux-riscv64-gnu': 4.62.5 + '@rollup/rollup-linux-riscv64-musl': 4.62.5 + '@rollup/rollup-linux-s390x-gnu': 4.62.5 + '@rollup/rollup-linux-x64-gnu': 4.62.5 + '@rollup/rollup-linux-x64-musl': 4.62.5 + '@rollup/rollup-openbsd-x64': 4.62.5 + '@rollup/rollup-openharmony-arm64': 4.62.5 + '@rollup/rollup-win32-arm64-msvc': 4.62.5 + '@rollup/rollup-win32-ia32-msvc': 4.62.5 + '@rollup/rollup-win32-x64-gnu': 4.62.5 + '@rollup/rollup-win32-x64-msvc': 4.62.5 + fsevents: 2.3.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + source-map-js@1.2.1: {} + + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + + vite@5.4.11: + dependencies: + esbuild: 0.21.3 + postcss: 8.5.26 + rollup: 4.62.5 + optionalDependencies: + fsevents: 2.3.3 + + yallist@3.1.1: {} diff --git a/website/pnpm-workspace.yaml b/website/pnpm-workspace.yaml new file mode 100644 index 0000000..00f6fc4 --- /dev/null +++ b/website/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: set this to true or false diff --git a/website/public/assets/logo.svg b/website/public/assets/logo.svg new file mode 100644 index 0000000..e04b66b --- /dev/null +++ b/website/public/assets/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/website/server/mock-api.mjs b/website/server/mock-api.mjs new file mode 100644 index 0000000..ccc4c77 --- /dev/null +++ b/website/server/mock-api.mjs @@ -0,0 +1,589 @@ +/** + * OPC 培训站 · 模拟后端 API(mock-api) + * ------------------------------------------------------------- + * 结构: + * - 认证:POST /api/auth/login · POST /api/auth/logout · GET /api/auth/verify + * - 公开(无需登录): + * POST /api/bookings 提交预约 / 报名 + * GET /api/events?current=1 当期(最近的公益课 + 沙龙)排期 + * POST /api/tests 上报一次 OPC 测评结果 + * - 管理(需 Bearer token,供 /pine 后台): + * GET /api/ops/stats 概览统计 + * GET /api/bookings 预约 / 报名列表(可 ?status= 过滤) + * PATCH /api/bookings/:id 更新预约状态(pending/confirmed/arrived/converted) + * DELETE /api/bookings/:id 删除预约 + * GET /api/events 全部排期 + * POST /api/events 新增排期(公益课 / 沙龙) + * PUT /api/events/:id 更新排期 + * DELETE /api/events/:id 删除排期 + * GET /api/tests 测评记录 + * + * 数据落盘:server/store.mjs(bookings.json / events.json / tests.json)。 + * 同时作为 connect 风格中间件(Vite dev/preview 挂载),也可被 start.mjs 复用。 + */ + +import { loadAll, add, update, remove, seedIfEmpty } from './store.mjs'; +import { createHash } from 'node:crypto'; +import { currentQuestions, calculate, expandResult, SECTIONS, AXIS_NAMES, ADAPT_LABELS, PROFILES } from './opcTest.js'; + +// 模拟账号(写死;迁移后由真实用户库替代) +const CREDENTIALS = { username: 'pine', password: '123456' }; +const SECRET = 'pine-mock-secret-2026'; +const TOKEN_TTL_MS = 1000 * 60 * 60 * 12; // 12 小时 +const hash = (s) => createHash('sha256').update(String(s)).digest('hex'); + +// 排期种子(对齐 宣传与运营/07 主题表;首次运行写入 events.json) +seedIfEmpty('events', [ + { id: 'E-F001', type: 'free', mode: 'online', title: 'AI 让民宿主每月省 10 小时', subtitle: '民宿 / 文旅创业 · 公益课', desc: '拆解民宿经营里的重复劳动,AI 怎么接住(点到即止,不卖课)', location: '视频号直播', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-08-27T19:30:00+08:00', durationMin: 90, capacity: 0, status: 'open' }, + { id: 'E-S001', type: 'salon', mode: 'offline', title: '民宿 AI 化改造实战', subtitle: '交流沙龙 · 12–30 人小场', desc: '主题深谈 + AI 现场演示 + 1 对 1 快诊', location: '昆明市大学生创业园', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-08-29T14:00:00+08:00', durationMin: 180, capacity: 30, status: 'open' }, + { id: 'E-F002', type: 'free', mode: 'offline', title: '用 AI 做云南咖啡品牌', subtitle: '咖啡 / 农特产创业 · 公益课', desc: '云南咖啡现状 + 品牌从 0 到 1 + AI 内容打法', location: '昆明市大学生创业园', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-09-03T19:30:00+08:00', durationMin: 90, capacity: 60, status: 'open' }, + { id: 'E-F003', type: 'free', mode: 'online', title: '一人公司财税避坑入门', subtitle: '自由职业 / 小老板 · 公益课', desc: '常见财税坑、发票、成本票、申报节点', location: '视频号直播', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-09-10T19:30:00+08:00', durationMin: 90, capacity: 0, status: 'open' }, + { id: 'E-S002', type: 'salon', mode: 'offline', title: '技能打包:把手艺变成产品', subtitle: '交流沙龙 · 副业转型专场', desc: '工作坊 + 快诊,把你的技能变成可定价产品', location: '昆明市大学生创业园', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-09-12T14:00:00+08:00', durationMin: 180, capacity: 24, status: 'open' }, + { id: 'E-F004', type: 'free', mode: 'offline', title: '你适不适合创业?先做一次 OPC 测评', subtitle: '大众 · 公益课', desc: '适配 6 维速览 + 现场 30 题快速测评', location: '昆明市大学生创业园', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-09-17T19:30:00+08:00', durationMin: 90, capacity: 60, status: 'open' }, + { id: 'E-S003', type: 'salon', mode: 'offline', title: 'OPC 测评报告 1 对 1 解读专场', subtitle: '交流沙龙 · 测评者组场', desc: '每人 15 分钟快诊,读懂你的类型码与赛道', location: '昆明市大学生创业园', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-09-26T14:00:00+08:00', durationMin: 180, capacity: 20, status: 'open' }, + { id: 'E-S004', type: 'salon', mode: 'offline', title: '资源对接夜:能提供 × 需要', subtitle: '交流沙龙 · 综合场', desc: '供需现场配对,本地人脉面对面', location: '昆明市大学生创业园', host: '云超服 · OPC 培训', image: '', link: '', startAt: '2026-09-27T14:00:00+08:00', durationMin: 180, capacity: 30, status: 'open' } +]); + +const b64url = (buf) => Buffer.from(buf).toString('base64url'); + +function makeToken(username) { + const payload = b64url(JSON.stringify({ username, exp: Date.now() + TOKEN_TTL_MS })); + const sig = b64url(SECRET + '.' + payload); + return `${payload}.${sig}`; +} + +export function verifyToken(token) { + if (!token || typeof token !== 'string') return false; + const [payloadPart, sigPart] = token.split('.'); + if (!payloadPart || !sigPart) return false; + const expectSig = b64url(SECRET + '.' + payloadPart); + if (sigPart !== expectSig) return false; + try { + const payload = JSON.parse(Buffer.from(payloadPart, 'base64url').toString('utf8')); + if (!payload.exp || payload.exp < Date.now()) return false; + return { username: payload.username }; + } catch { + return false; + } +} + +function readBody(req) { + return new Promise((resolve) => { + let data = ''; + req.on('data', (chunk) => { data += chunk; }); + req.on('end', () => { + try { resolve(JSON.parse(data || '{}')); } + catch { resolve({}); } + }); + req.on('error', () => resolve({})); + }); +} + +function json(res, status, obj) { + res.statusCode = status; + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.end(JSON.stringify(obj)); +} + +function requireAuth(req, res) { + const auth = req.headers.authorization || ''; + const token = auth.startsWith('Bearer ') ? auth.slice(7) : ''; + const payload = verifyToken(token); + if (!payload) { + json(res, 401, { ok: false, error: '未授权或登录已过期' }); + return null; + } + return payload; +} + +const genId = (prefix) => prefix + Date.now().toString(36).toUpperCase() + Math.floor(Math.random() * 36).toString(36).toUpperCase(); + +/* ---------------- 短信验证码(演示环境) ---------------- + 内存存储 手机号 → {code, exp};响应返回验证码供演示(真实环境:接短信服务商,删除 debugCode)。 + 报名 / 登录 / 注册统一走「手机号 + 验证码」,手机号即账号,未注册自动注册。 */ +const smsCodes = new Map(); +const PHONE_RE = /^1\d{10}$/; +const CODE_TTL = 5 * 60 * 1000; // 5 分钟 + +function sendSmsCode(phone) { + const code = String(Math.floor(100000 + Math.random() * 900000)); + smsCodes.set(phone, { code, exp: Date.now() + CODE_TTL }); + return code; +} + +/** 手机号 + 验证码 → 校验并确保账号存在(自动注册或登录) */ +function authByPhone(body) { + const phone = String(body.phone || '').trim(); + const code = String(body.code || '').trim(); + if (!PHONE_RE.test(phone)) return { error: '请输入正确的 11 位手机号' }; + const rec = smsCodes.get(phone); + if (!rec || rec.code !== code || Date.now() > rec.exp) { + return { error: '验证码错误或已过期,请重新获取' }; + } + smsCodes.delete(phone); + const accounts = loadAll('accounts'); + let acct = accounts.find((a) => a.username === phone); + let isNew = false; + if (!acct) { + acct = { + id: genId('U'), + username: phone, + name: String(body.name || '').trim() || phone, + contact: phone, + identities: ['opc_member|certified'], + createdAt: new Date().toISOString() + }; + add('accounts', acct); + isNew = true; + } + return { acct, isNew }; +} + +/** + * connect 风格中间件:(req, res, next) + * 命中 /api/* 则处理并结束响应;否则 next()。 + */ +export async function mockApi(req, res, next) { + if (typeof next !== 'function') next = () => {}; + const url = (req.url || '').split('?')[0]; + const segs = url.split('/').filter(Boolean); // ['api', ...rest] + if (segs[0] !== 'api') return next(); + + const method = (req.method || 'GET').toUpperCase(); + const rest = segs.slice(1); // 去掉 'api' + const resName = rest[0]; // 'auth' | 'bookings' | 'events' | 'ops' | 'tests' + const sub = rest[1]; // 'login' | ':id' | 'stats' + const id = rest.length >= 2 ? rest[1] : undefined; + + /* ---------------- 认证 ---------------- */ + // 注册(创建账号 → 返回 token;报名页"注册即报名"走这里) + if (resName === 'auth' && sub === 'register' && method === 'POST') { + const body = (await readBody(req)) || {}; + const username = String(body.username || '').trim(); + const password = String(body.password || ''); + const name = String(body.name || '').trim(); + const contact = String(body.contact || '').trim(); + if (!username || !password) return json(res, 400, { ok: false, error: '请填写账号与密码' }); + if (username.length < 2) return json(res, 400, { ok: false, error: '账号至少 2 个字符' }); + if (password.length < 4) return json(res, 400, { ok: false, error: '密码至少 4 个字符' }); + if (loadAll('accounts').some((a) => a.username === username)) { + return json(res, 400, { ok: false, error: '账号已存在,请直接登录' }); + } + const entry = { + id: genId('U'), + username, + password: hash(password), + name, + contact, + identities: ['opc_member|certified'], + createdAt: new Date().toISOString() + }; + add('accounts', entry); + return json(res, 200, { ok: true, token: makeToken(username), username, name: name || username }); + } + // 登录(支持注册账号 + 内置超级账号;区分"账号不存在 / 密码错误") + if (resName === 'auth' && sub === 'login' && method === 'POST') { + const body = await readBody(req); + const { username, password } = body || {}; + // 内置超级账号 + if (username === CREDENTIALS.username) { + if (password === CREDENTIALS.password) { + return json(res, 200, { ok: true, token: makeToken(username), username, name: 'Pine' }); + } + return json(res, 401, { ok: false, error: '密码错误,请重新输入' }); + } + // 注册账号 + const acct = loadAll('accounts').find((a) => a.username === username); + if (!acct) return json(res, 404, { ok: false, error: '账号不存在,请先注册' }); + if (acct.password !== hash(password)) return json(res, 401, { ok: false, error: '密码错误,请重新输入' }); + return json(res, 200, { ok: true, token: makeToken(username), username, name: acct.name || username }); + } + // 发送短信验证码(演示环境:响应返回验证码,供前端直接展示;真实环境接短信服务商) + if (resName === 'auth' && sub === 'send-code' && method === 'POST') { + const body = await readBody(req); + const phone = String(body.phone || '').trim(); + if (!PHONE_RE.test(phone)) return json(res, 400, { ok: false, error: '请输入正确的 11 位手机号' }); + const code = sendSmsCode(phone); + return json(res, 200, { ok: true, sent: true, debugCode: code }); + } + // 手机号 + 验证码登录 / 注册(未注册自动注册,返回 token) + if (resName === 'auth' && sub === 'phone-login' && method === 'POST') { + const body = await readBody(req); + const r = authByPhone(body); + if (r.error) return json(res, 401, { ok: false, error: r.error }); + return json(res, 200, { ok: true, token: makeToken(r.acct.username), username: r.acct.username, name: r.acct.name, isNew: r.isNew }); + } + if (resName === 'auth' && sub === 'logout' && method === 'POST') return json(res, 200, { ok: true }); + if (resName === 'auth' && sub === 'verify' && method === 'GET') { + const payload = requireAuth(req, res); + if (!payload) return; + return json(res, 200, { ok: true, username: payload.username }); + } + + /* ---------------- OPC 测评:题目下发 + 计分(测评逻辑与评语全在后端) ---------------- */ + if (resName === 'tests' && sub === 'opc') { + // GET /api/tests/opc/questions?version=quick|full —— 下发题目与渲染元数据 + if (rest[2] === 'questions' && method === 'GET') { + const q = new URLSearchParams((req.url || '').split('?')[1] || ''); + const version = q.get('version') === 'quick' ? 'quick' : 'full'; + return json(res, 200, { + ok: true, + version, + questions: currentQuestions(version).map((x) => ({ id: x.id, part: x.part, question: x.question, A: x.A, B: x.B })), + sections: SECTIONS, + axisNames: AXIS_NAMES, + dimLabels: ADAPT_LABELS, + disclaimer: PROFILES.disclaimer, + quickNote: PROFILES.quickNote + }); + } + // POST /api/tests/opc/calculate { answers, version } —— 后端计分 + 返回完整报告/评语 + if (rest[2] === 'calculate' && method === 'POST') { + const body = (await readBody(req)) || {}; + const version = body.version === 'quick' ? 'quick' : 'full'; + const answers = body.answers && typeof body.answers === 'object' ? body.answers : {}; + try { + const result = expandResult(calculate(answers, version)); + return json(res, 200, { ok: true, result }); + } catch (e) { + return json(res, 400, { ok: false, error: '测评计算失败,请稍后重试' }); + } + } + return json(res, 404, { ok: false, error: 'Not Found' }); + } + + /* ---------------- 公开:提交预约 / 报名(报名即注册/登录) ---------------- */ + if (resName === 'bookings' && method === 'POST' && !id) { + const body = (await readBody(req)) || {}; + // 手机号 + 验证码认证:已注册→登录,未注册→自动注册 + const auth = authByPhone(body); + if (auth.error) return json(res, 401, { ok: false, error: auth.error }); + const acct = auth.acct; + const name = String(body.name || '').trim() || acct.name || acct.username; + const entry = { + id: genId('B'), + createdAt: new Date().toISOString(), + status: 'pending', // pending / confirmed / arrived / converted + username: acct.username, + name, + contact: acct.username, // 手机号即联系方式 + statusLabel: String(body.status || '').trim(), + want: body.want || '', + eventId: String(body.eventId || '').trim(), + eventTitle: String(body.eventTitle || '').trim(), + eventStart: String(body.eventStart || '').trim(), + topics: Array.isArray(body.topics) ? body.topics : [], + question: body.question || '', + source: body.source || '' + }; + add('bookings', entry); + // 新注册用户:用报名时填写的姓名补全账号资料 + if (auth.isNew && body.name) update('accounts', acct.id, { name }); + return json(res, 200, { + ok: true, + id: entry.id, + createdAt: entry.createdAt, + token: makeToken(acct.username), + username: acct.username, + name, + isNew: auth.isNew + }); + } + + /* ---------------- 公开:当期排期 ---------------- */ + if (resName === 'events' && method === 'GET' && !id) { + const q = new URLSearchParams((req.url || '').split('?')[1] || ''); + const all = loadAll('events'); + const now = Date.now(); + const upcoming = all + .filter((e) => new Date(e.startAt).getTime() >= now - 1000 * 60 * 30) + .sort((a, b) => new Date(a.startAt) - new Date(b.startAt)); + if (q.get('current')) { + const free = upcoming.find((e) => e.type === 'free') || null; + const salon = upcoming.find((e) => e.type === 'salon') || null; + const next = upcoming[0] || null; + return json(res, 200, { ok: true, next, free, salon, upcoming: upcoming.slice(0, 6) }); + } + // 可报名场次:未结束/未关闭的最近场次(报名页选择用) + if (q.get('bookable')) { + const list = upcoming.filter((e) => e.status !== 'closed' && e.status !== 'done').slice(0, 30); + return json(res, 200, { ok: true, list }); + } + return json(res, 200, { ok: true, list: all }); + } + + /* ---------------- 公开:活动详情 ---------------- */ + if (resName === 'events' && method === 'GET' && id) { + const e = loadAll('events').find((x) => x.id === id); + if (!e) return json(res, 404, { ok: false, error: '活动不存在' }); + return json(res, 200, { ok: true, event: e }); + } + + /* ---------------- 公开:上报测评结果 ---------------- */ + if (resName === 'tests' && method === 'POST' && !id) { + const body = (await readBody(req)) || {}; + if (!body.typeCode) return json(res, 400, { ok: false, error: '缺少测评结果' }); + const entry = { + id: genId('T'), + createdAt: new Date().toISOString(), + username: String(body.username || '').trim(), + typeCode: body.typeCode, + persona: body.persona || '', + adaptIndex: Number(body.adaptIndex) || 0, + adaptLevel: body.adaptLevel || '', + tracks: Array.isArray(body.tracks) ? body.tracks : [], + version: body.version || '' + }; + add('tests', entry); + return json(res, 200, { ok: true, id: entry.id }); + } + + /* ---------------- 公开:上报政策测评结果 ---------------- */ + if (resName === 'policy-logs' && method === 'POST' && !id) { + const body = (await readBody(req)) || {}; + if (!body.answers) return json(res, 400, { ok: false, error: '缺少测评结果' }); + const entry = { + id: genId('P'), + createdAt: new Date().toISOString(), + username: String(body.username || '').trim(), + answers: body.answers, + policiesCount: Number(body.policiesCount) || 0, + subsidiesCount: Number(body.subsidiesCount) || 0, + loansCount: Number(body.loansCount) || 0, + summary: String(body.summary || '').slice(0, 200) + }; + add('policyLogs', entry); + return json(res, 200, { ok: true, id: entry.id }); + } + + /* ---------------- 公开:上报启动流程生成 ---------------- */ + if (resName === 'plan-logs' && method === 'POST' && !id) { + const body = (await readBody(req)) || {}; + if (!body.region) return json(res, 400, { ok: false, error: '缺少流程信息' }); + const entry = { + id: genId('L'), + createdAt: new Date().toISOString(), + username: String(body.username || '').trim(), + region: String(body.region || '').trim(), + status: String(body.status || '').trim(), + needPark: !!body.needPark, + hasStaff: !!body.hasStaff, + stepsCount: Number(body.stepsCount) || 0 + }; + add('planLogs', entry); + return json(res, 200, { ok: true, id: entry.id }); + } + + /* ---------------- 公开:上报 OPC 创业伙伴调研 ---------------- */ + if (resName === 'survey-logs' && method === 'POST' && !id) { + const body = (await readBody(req)) || {}; + if (!body.answers || typeof body.answers !== 'object') { + return json(res, 400, { ok: false, error: '缺少调研答案' }); + } + const entry = { + id: genId('S'), + createdAt: new Date().toISOString(), + username: String(body.username || '').trim(), + source: String(body.source || '').trim() || 'web', + answers: body.answers + }; + add('surveyLogs', entry); + return json(res, 200, { ok: true, id: entry.id, createdAt: entry.createdAt }); + } + + /* ---------------- 用户端:微信登录 / 我的 / 绑定手机 ---------------- */ + // POST /api/auth/wx-login { code, nickName, avatarUrl } → 微信一键登录 + // 演示:未接微信 appid/secret,用 code 哈希作伪 openid(真实环境换成 code→openid 换号) + if (resName === 'auth' && sub === 'wx-login' && method === 'POST') { + const body = (await readBody(req)) || {}; + const code = String(body.code || ''); + if (!code) return json(res, 400, { ok: false, error: '缺少微信登录凭证' }); + const wxId = 'wx_' + hash(code + '::wx').slice(0, 24); + let acct = loadAll('accounts').find((a) => a.wxId === wxId); + if (!acct) { + const name = String(body.nickName || '').trim() || '微信用户'; + acct = { + id: genId('U'), username: wxId, wxId, phone: '', name, avatar: String(body.avatarUrl || ''), + phoneBound: false, identities: ['wx'], createdAt: new Date().toISOString() + }; + add('accounts', acct); + } + return json(res, 200, { + ok: true, token: makeToken(acct.username), username: acct.username, + name: acct.name || '微信用户', avatar: acct.avatar || '', phoneBound: !!acct.phoneBound + }); + } + // GET /api/auth/me { auth } → 当前账号信息 + if (resName === 'auth' && sub === 'me' && method === 'GET') { + const payload = requireAuth(req, res); if (!payload) return; + const acct = loadAll('accounts').find((a) => a.username === payload.username); + if (!acct) return json(res, 404, { ok: false, error: '账号不存在' }); + return json(res, 200, { + ok: true, + user: { username: acct.username, name: acct.name || acct.username, avatar: acct.avatar || '', phone: acct.phone || '', phoneBound: !!acct.phoneBound } + }); + } + // POST /api/auth/bind-phone { phone, code } { auth } → 绑定手机 + if (resName === 'auth' && sub === 'bind-phone' && method === 'POST') { + const payload = requireAuth(req, res); if (!payload) return; + const body = (await readBody(req)) || {}; + const phone = String(body.phone || '').trim(); + const code = String(body.code || '').trim(); + if (!PHONE_RE.test(phone)) return json(res, 400, { ok: false, error: '请输入正确的 11 位手机号' }); + const rec = smsCodes.get(phone); + if (!rec || rec.code !== code || Date.now() > rec.exp) return json(res, 401, { ok: false, error: '验证码错误或已过期' }); + smsCodes.delete(phone); + const acct = loadAll('accounts').find((a) => a.username === payload.username); + if (!acct) return json(res, 404, { ok: false, error: '账号不存在' }); + update('accounts', acct.id, { phone, phoneBound: true }); + return json(res, 200, { ok: true, phoneBound: true, phone }); + } + + /* ---------------- 用户端:我的报名 / 签到(需登录) ---------------- */ + if (resName === 'bookings' && sub === 'mine' && method === 'GET') { + const payload = requireAuth(req, res); if (!payload) return; + const acct = loadAll('accounts').find((a) => a.username === payload.username); + // 匹配:微信账号本身 + 绑定手机号(两者都可能产生报名) + const ids = new Set([payload.username]); + if (acct && acct.phone) ids.add(acct.phone); + const list = loadAll('bookings') + .filter((b) => ids.has(b.username) || ids.has(b.contact || '')) + .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + return json(res, 200, { ok: true, list }); + } + if (resName === 'checkins' && method === 'POST' && !id) { + const payload = requireAuth(req, res); if (!payload) return; + const body = (await readBody(req)) || {}; + const acct = loadAll('accounts').find((a) => a.username === payload.username); + const ids = new Set([payload.username]); + if (acct && acct.phone) ids.add(acct.phone); + const b = loadAll('bookings').find((x) => x.id === body.bookingId); + if (!b) return json(res, 404, { ok: false, error: '报名记录不存在' }); + if (!ids.has(b.username) && !ids.has(b.contact || '')) return json(res, 403, { ok: false, error: '无权操作该记录' }); + if (b.checkinAt) return json(res, 400, { ok: false, error: '该场次已签到' }); + const updated = update('bookings', b.id, { checkinAt: new Date().toISOString() }); + return json(res, 200, { ok: true, entry: updated }); + } + + /* ================ 以下为管理端点(需登录) ================ */ + const auth = requireAuth(req, res); + if (!auth) return; + + /* ---- 概览统计 ---- */ + if (resName === 'ops' && sub === 'stats' && method === 'GET') { + const bookings = loadAll('bookings'); + const events = loadAll('events'); + const tests = loadAll('tests'); + const policyLogs = loadAll('policyLogs'); + const planLogs = loadAll('planLogs'); + const surveyLogs = loadAll('surveyLogs'); + const now = Date.now(); + return json(res, 200, { + ok: true, + stats: { + bookings: bookings.length, + pending: bookings.filter((b) => b.status === 'pending').length, + confirmed: bookings.filter((b) => b.status === 'confirmed').length, + arrived: bookings.filter((b) => b.status === 'arrived').length, + converted: bookings.filter((b) => b.status === 'converted').length, + events: events.length, + upcomingEvents: events.filter((e) => new Date(e.startAt).getTime() >= now).length, + free: events.filter((e) => e.type === 'free' && new Date(e.startAt).getTime() >= now).length, + salon: events.filter((e) => e.type === 'salon' && new Date(e.startAt).getTime() >= now).length, + tests: tests.length, + policyLogs: policyLogs.length, + planLogs: planLogs.length, + surveyLogs: surveyLogs.length + } + }); + } + + /* ---- 预约 / 报名管理 ---- */ + if (resName === 'bookings' && method === 'GET' && !id) { + const q = new URLSearchParams((req.url || '').split('?')[1] || ''); + let list = loadAll('bookings').slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + if (q.get('status')) list = list.filter((b) => b.status === q.get('status')); + return json(res, 200, { ok: true, list }); + } + if (resName === 'bookings' && method === 'PATCH' && id) { + const body = (await readBody(req)) || {}; + const VALID = ['pending', 'confirmed', 'arrived', 'converted']; + const patch = {}; + if (body.status && VALID.includes(body.status)) patch.status = body.status; + const entry = update('bookings', id, patch); + if (!entry) return json(res, 404, { ok: false, error: '预约不存在' }); + return json(res, 200, { ok: true, entry }); + } + if (resName === 'bookings' && method === 'DELETE' && id) { + remove('bookings', id); + return json(res, 200, { ok: true }); + } + + /* ---- 排期管理 ---- */ + if (resName === 'events' && method === 'POST' && !id) { + const body = (await readBody(req)) || {}; + if (!body.type || !body.title || !body.startAt) return json(res, 400, { ok: false, error: '请填写类型 / 主题 / 开始时间' }); + const entry = { + id: genId(body.type === 'salon' ? 'E-S' : 'E-F'), + type: body.type === 'salon' ? 'salon' : 'free', + mode: body.mode === 'online' ? 'online' : 'offline', + title: String(body.title).trim(), + subtitle: body.subtitle || '', + desc: body.desc || '', + location: body.location || '', + host: String(body.host || '').trim(), + image: String(body.image || '').trim(), + link: body.link || '', + startAt: body.startAt, + durationMin: Number(body.durationMin) || 90, + capacity: Number(body.capacity) || 0, + status: body.status || 'open' + }; + add('events', entry); + return json(res, 200, { ok: true, entry }); + } + if (resName === 'events' && method === 'PUT' && id) { + const body = (await readBody(req)) || {}; + const patch = {}; + for (const k of ['title', 'subtitle', 'desc', 'location', 'link', 'startAt', 'status', 'host', 'image']) { + if (body[k] !== undefined) patch[k] = body[k]; + } + if (body.type === 'salon' || body.type === 'free') patch.type = body.type; + if (body.mode === 'online' || body.mode === 'offline') patch.mode = body.mode; + if (body.durationMin !== undefined) patch.durationMin = Number(body.durationMin); + if (body.capacity !== undefined) patch.capacity = Number(body.capacity); + const entry = update('events', id, patch); + if (!entry) return json(res, 404, { ok: false, error: '排期不存在' }); + return json(res, 200, { ok: true, entry }); + } + if (resName === 'events' && method === 'DELETE' && id) { + remove('events', id); + return json(res, 200, { ok: true }); + } + + /* ---- 测评记录 ---- */ + if (resName === 'tests' && method === 'GET' && !id) { + const list = loadAll('tests').slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + return json(res, 200, { ok: true, list }); + } + + /* ---- 政策测评记录 ---- */ + if (resName === 'policy-logs' && method === 'GET' && !id) { + const list = loadAll('policyLogs').slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + return json(res, 200, { ok: true, list }); + } + + /* ---- 启动流程记录 ---- */ + if (resName === 'plan-logs' && method === 'GET' && !id) { + const list = loadAll('planLogs').slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + return json(res, 200, { ok: true, list }); + } + + /* ---- 调研记录 ---- */ + if (resName === 'survey-logs' && method === 'GET' && !id) { + const list = loadAll('surveyLogs').slice().sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + return json(res, 200, { ok: true, list }); + } + + return json(res, 404, { ok: false, error: '接口不存在' }); +} diff --git a/website/server/opcTest.js b/website/server/opcTest.js new file mode 100644 index 0000000..7239c7c --- /dev/null +++ b/website/server/opcTest.js @@ -0,0 +1,92 @@ +// OPC 创业基因测评 · 数据与计分逻辑(单一数据源,由 OPC创业测评/scripts/*.json 生成) +// 生成脚本见:OPC创业测评/scripts/_generate_opcTest.js(本地一次性生成) + +export const QUESTIONS = [{"id":1,"part":"P1","quick":true,"question":"面对一个没人给方向的任务,你通常","A":"自己定标准和节奏,直接把它做完","B":"希望有人先给个框架,心里才踏实"},{"id":2,"part":"P1","quick":false,"question":"发现一个好机会时,你更可能","A":"先拉上伙伴一起评估、一起决定","B":"先自己干出个样子,再考虑找别人"},{"id":3,"part":"P1","quick":false,"question":"关于未来收入的预期,你更能接受哪种","A":"前几个月不稳定,后期慢慢起来","B":"一开始就要有稳定的保底收入"},{"id":4,"part":"P1","quick":true,"question":"一笔可支配的钱,你更倾向","A":"尽量存着,心里更安稳","B":"拿出一部分去试错一个可能赚钱的新项目"},{"id":5,"part":"P1","quick":true,"question":"没人督促你的时候,你做事","A":"依然会按自己的节奏持续推进","B":"容易拖到有截止日才动手"},{"id":6,"part":"P1","quick":false,"question":"学一项新技能时,你更常","A":"需要有课程、有人带才学得进去","B":"自己主动查资料、边做边折腾会"},{"id":7,"part":"P1","quick":false,"question":"如果创业第一年只有你一个人,你更可能","A":"接受「什么都自己上手」并乐在其中","B":"觉得分身乏术、难以接受"},{"id":8,"part":"P1","quick":true,"question":"同时要写文案、谈客户、做交付,你更倾向","A":"觉得必须有人分工才专业","B":"一个人全扛下来,边做边学"},{"id":9,"part":"P1","quick":true,"question":"对待 AI 工具,你的态度更像","A":"愿意主动尝试用 AI 给自己提效","B":"觉得现用工具够用,学新的麻烦"},{"id":10,"part":"P1","quick":false,"question":"听说「用 AI 搭一套自动流程能省你每周 10 小时」,你会","A":"觉得太复杂,先放一放再说","B":"立刻想知道怎么搭、想马上用起来"},{"id":11,"part":"P1","quick":false,"question":"假设你接下来半年没有稳定收入,你","A":"有存款或其他收入兜底,能顶住","B":"会立刻陷入财务紧张"},{"id":12,"part":"P1","quick":true,"question":"你每周能稳定投入创业的时间","A":"只能挤零碎时间,很难保证","B":"能保证固定的一大块时间(如每天 2 小时以上)"},{"id":13,"part":"P2","quick":true,"question":"你更享受哪种状态","A":"和客户、同行、陌生人热络地聊想法,从中获得能量","B":"独自专注打磨一个作品,沉浸在自己的世界"},{"id":14,"part":"P2","quick":true,"question":"做内容时你更顺手","A":"直播、面对面,直接讲出来","B":"写长文、做深度研究,慢慢打磨"},{"id":15,"part":"P2","quick":false,"question":"做成第一单生意,最让你有成就感的瞬间是","A":"当面把客户聊动、顺利签下","B":"交付后客户给出真诚的认可"},{"id":16,"part":"P2","quick":true,"question":"面对一个还没人做过的全新品类,你会","A":"很兴奋,想第一个冲进去试试","B":"很谨慎,想先看有没有人验证过"},{"id":17,"part":"P2","quick":true,"question":"你更愿意把精力放在","A":"还没火起来的概念,可能成为下一个风口","B":"已经被验证、有稳定需求的市场"},{"id":18,"part":"P2","quick":false,"question":"选项目时你更看重","A":"成长空间大,哪怕当下需求还不明确","B":"现在就有明确的人付费,哪怕竞争激烈"},{"id":19,"part":"P2","quick":true,"question":"做生意的成就感更多来自","A":"帮一个客户真正解决了问题、赢得信任","B":"把流程做快、把规模做上去"},{"id":20,"part":"P2","quick":true,"question":"你更愿意卖","A":"有温度的一对一服务(咨询/陪跑/定制)","B":"标准化的产品或课程(做一次、可复制)"},{"id":21,"part":"P2","quick":false,"question":"客户提需求时,你更习惯","A":"深入了解他这个人,按他的情况定制","B":"提炼共性问题,做成标准方案复制"},{"id":22,"part":"P2","quick":true,"question":"你经营生意的方式更像","A":"有一套流程和标准,按计划推进","B":"跟着感觉和热点,随机应变"},{"id":23,"part":"P2","quick":true,"question":"面对临时变化,你更擅长","A":"迅速调整节奏、见招拆招","B":"坚持原计划,不喜欢被打乱"},{"id":24,"part":"P2","quick":false,"question":"你更愿意把一天过成","A":"有固定日程、按表推进","B":"看当天状态和机会,灵活安排"},{"id":25,"part":"P3","quick":true,"question":"「把云南在地文化/民宿做成体验生意」和「做一个知识类账号」,你更想做","A":"在地体验生意(民宿/活动/在地游)","B":"知识内容账号"},{"id":26,"part":"P3","quick":false,"question":"你更享受","A":"线下把人组织起来做体验、做活动","B":"线上完成咨询和交付"},{"id":27,"part":"P3","quick":false,"question":"连续做内容 3 个月没人看,你会","A":"愿意持续输出,相信有积累","B":"很快觉得没劲,想换个方式"},{"id":28,"part":"P3","quick":true,"question":"你更愿意做的方向","A":"打造个人 IP,靠内容吸引同频的人","B":"抓住信息差,做跨境资源对接"},{"id":29,"part":"P3","quick":false,"question":"你更愿意靠什么赚钱","A":"用专业知识和判断帮人解决问题(顾问/咨询)","B":"一家家上门服务本地商家"},{"id":30,"part":"P3","quick":true,"question":"面对「把一套方法论做成线上课程/付费产品」","A":"有信心做,享受这种价值沉淀","B":"更想做落地体验,不太想做课程"},{"id":31,"part":"P3","quick":false,"question":"你更擅长、更愿意投入","A":"钻研选品、货源、供应链这些硬功夫","B":"研究内容、创意这些软功夫"},{"id":32,"part":"P3","quick":true,"question":"「把普洱/咖啡/鲜花/特产卖向全国」和「做高端顾问」,你更愿意","A":"做特产电商,把货卖出去","B":"做高端顾问,卖专业判断"},{"id":33,"part":"P3","quick":true,"question":"面对东南亚/南亚的跨境机会,你","A":"觉得是巨大机会,愿意去闯","B":"觉得太麻烦,语言政策搞不定"},{"id":34,"part":"P3","quick":false,"question":"你更愿意做","A":"信息差/资源对接的生意(撮合、代运营)","B":"踏踏实实的在地体验生意"},{"id":35,"part":"P3","quick":true,"question":"你更愿意服务","A":"本地商家(帮他们做私域、代运营、企微)","B":"面向全国消费者的电商生意"},{"id":36,"part":"P3","quick":false,"question":"你更擅长","A":"上门谈、落地执行、当面服务","B":"远程交付、标准化输出"},{"id":37,"part":"P4","quick":true,"question":"你最有心流的时刻是","A":"把一个产品/交付打磨到极致","B":"写出、剪出一条自己满意的内容"},{"id":38,"part":"P4","quick":true,"question":"客户夸你「交付质量高」,你","A":"很受用,愿意继续钻研产品","B":"更想去谈更多客户、做更大的单"},{"id":39,"part":"P4","quick":false,"question":"你更愿意花时间在","A":"研究怎么做出世界级的好东西","B":"研究怎么把流程理顺、让重复的事自动化"},{"id":40,"part":"P4","quick":false,"question":"你的表达欲更多通过什么释放","A":"写作、视频、内容创作","B":"把想法做成产品、流程、方案"},{"id":41,"part":"P4","quick":true,"question":"你更擅长","A":"把复杂的东西讲清楚、让人愿意看","B":"想清楚整体方向、搭建全局框架"},{"id":42,"part":"P4","quick":false,"question":"让你连发 30 天内容,你","A":"有内容可发,乐在其中","B":"会发愁,更愿意做运营和优化"},{"id":43,"part":"P4","quick":false,"question":"连续被 10 个客户拒绝,你","A":"复盘一下,继续谈下一个","B":"会受挫,需要缓一缓再做"},{"id":44,"part":"P4","quick":true,"question":"你更享受","A":"主动出击找客户、把单谈下来","B":"把后台流程和交付安排得井井有条"},{"id":45,"part":"P4","quick":false,"question":"面对重复性工作,你更倾向","A":"做成流程/模板/自动化,一劳永逸","B":"每次现做,做的时候再想"},{"id":46,"part":"P4","quick":false,"question":"你更擅长","A":"把杂乱的事理出流程、安排得有条不紊","B":"产出有感染力的内容、抓住注意力"},{"id":47,"part":"P4","quick":false,"question":"面对整盘生意,你更习惯","A":"先想清楚全局,再分配各环节","B":"先埋头做自己最擅长的环节"},{"id":48,"part":"P4","quick":true,"question":"你更愿意","A":"拍板定方向、统筹资源、承担最终结果","B":"专注执行、把细节做好"},{"id":49,"part":"P5","quick":true,"question":"用 AI 时你更习惯","A":"像聊天一样和它一句句打磨,过程自己掌控","B":"一次性把任务交给它,它自己搞定"},{"id":50,"part":"P5","quick":true,"question":"你对 AI 的期待更多是","A":"当个得力助手,帮我把想法理得更好","B":"当条流水线,批量产出内容"},{"id":51,"part":"P5","quick":false,"question":"你更相信","A":"自己一步步亲手做出来的东西","B":"把活交出去(AI/外包)带来的效率"},{"id":52,"part":"P5","quick":false,"question":"你要持续做内容,你更倾向","A":"搭一套「选题→生成→发布」的自动流水线","B":"每一条都自己认真写"},{"id":53,"part":"P5","quick":true,"question":"对「AI 批量生成内容、人来把关选题」","A":"认可,就想这么干","B":"担心质量,想自己亲力亲为"},{"id":54,"part":"P5","quick":false,"question":"你更愿意把时间花在","A":"设置流程,让内容自动跑起来","B":"逐字打磨每一条内容"},{"id":55,"part":"P5","quick":false,"question":"你更愿意","A":"搭几个智能体(获客/客服/复盘)替自己跑链路","B":"自己亲手处理每一环"},{"id":56,"part":"P5","quick":false,"question":"对「把一个环节完全交给 AI 自动跑」","A":"放心,只要规则设好","B":"不放心,必须自己盯着"},{"id":57,"part":"P5","quick":true,"question":"你更享受","A":"设计一套系统,让生意自动运转","B":"亲手完成每件事的掌控感"},{"id":58,"part":"P5","quick":false,"question":"忙不过来时,你更倾向","A":"花钱、资源外包出去,自己抓重点","B":"自己加班硬扛"},{"id":59,"part":"P5","quick":true,"question":"对「AI 提效 + 外包人力 + 供应链合作」的组合打法","A":"认同,愿意去组这个局","B":"更想自己一竿子插到底"},{"id":60,"part":"P5","quick":false,"question":"你更愿意当","A":"一个调配资源、组团队的老板","B":"一个事事亲力亲为的匠人"}]; + +export const RULES = [{"id":1,"A":"IND","B":null},{"id":2,"A":null,"B":"IND"},{"id":3,"A":"RISK","B":null},{"id":4,"A":null,"B":"RISK"},{"id":5,"A":"DRIVE","B":null},{"id":6,"A":null,"B":"DRIVE"},{"id":7,"A":"SOLO","B":null},{"id":8,"A":null,"B":"SOLO"},{"id":9,"A":"AI","B":null},{"id":10,"A":null,"B":"AI"},{"id":11,"A":"STABLE","B":null},{"id":12,"A":null,"B":"STABLE"},{"id":13,"A":"E","B":"I"},{"id":14,"A":"E","B":"I"},{"id":15,"A":"E","B":"I"},{"id":16,"A":"V","B":"G"},{"id":17,"A":"V","B":"G"},{"id":18,"A":"V","B":"G"},{"id":19,"A":"R","B":"T"},{"id":20,"A":"R","B":"T"},{"id":21,"A":"R","B":"T"},{"id":22,"A":"P","B":"F"},{"id":23,"A":"F","B":"P"},{"id":24,"A":"P","B":"F"},{"id":25,"A":"TOUR","B":"CONTENT"},{"id":26,"A":"TOUR","B":"DIGITAL"},{"id":27,"A":"CONTENT","B":"ECOMM"},{"id":28,"A":"CONTENT","B":"CROSS"},{"id":29,"A":"DIGITAL","B":"LOCAL"},{"id":30,"A":"DIGITAL","B":"TOUR"},{"id":31,"A":"ECOMM","B":"CONTENT"},{"id":32,"A":"ECOMM","B":"DIGITAL"},{"id":33,"A":"CROSS","B":"LOCAL"},{"id":34,"A":"CROSS","B":"TOUR"},{"id":35,"A":"LOCAL","B":"ECOMM"},{"id":36,"A":"LOCAL","B":"DIGITAL"},{"id":37,"A":"MAKER","B":"CREATOR"},{"id":38,"A":"MAKER","B":"HUNTER"},{"id":39,"A":"MAKER","B":"RUNNER"},{"id":40,"A":"CREATOR","B":"MAKER"},{"id":41,"A":"CREATOR","B":"ARCH"},{"id":42,"A":"CREATOR","B":"RUNNER"},{"id":43,"A":"HUNTER","B":"MAKER"},{"id":44,"A":"HUNTER","B":"RUNNER"},{"id":45,"A":"RUNNER","B":"HUNTER"},{"id":46,"A":"RUNNER","B":"CREATOR"},{"id":47,"A":"ARCH","B":"MAKER"},{"id":48,"A":"ARCH","B":"RUNNER"},{"id":49,"A":"DIALOG","B":"AGENT"},{"id":50,"A":"DIALOG","B":"AUTOCON"},{"id":51,"A":"DIALOG","B":"OUTSOURCE"},{"id":52,"A":"AUTOCON","B":"DIALOG"},{"id":53,"A":"AUTOCON","B":"AGENT"},{"id":54,"A":"AUTOCON","B":"OUTSOURCE"},{"id":55,"A":"AGENT","B":"AUTOCON"},{"id":56,"A":"AGENT","B":"DIALOG"},{"id":57,"A":"AGENT","B":"OUTSOURCE"},{"id":58,"A":"OUTSOURCE","B":"AGENT"},{"id":59,"A":"OUTSOURCE","B":"DIALOG"},{"id":60,"A":"OUTSOURCE","B":"AUTOCON"}]; + +export const PROFILES = {"meta":{"name":"OPC 创业基因测评画像数据","version":"1.0","description":"赛道/角色/工具模式/适配等级/16人设/特质轴的完整描述数据。报告渲染与解读均依赖本文件。","pairs":{"EI":["E","I"],"VG":["V","G"],"RT":["R","T"],"PF":["P","F"]}},"axes":[{"code":"EI","name":"能量轴","left":"E","right":"I","leftLabel":"对外连接","rightLabel":"对内深耕","leftDesc":"从与人沟通、连接、谈单中获得能量;适合获客与连接型生意","rightDesc":"从独自专注、深度产出中获得能量;适合创作与产品型生意"},{"code":"VG","name":"视野轴","left":"V","right":"G","leftLabel":"探索新机会","rightLabel":"把握成熟机会","leftDesc":"喜欢新品类、新概念、可能的风口;适合内容IP/跨境/数字产品","rightDesc":"相信已验证的需求和稳定市场;适合特色电商/本地服务/文旅"},{"code":"RT","name":"价值轴","left":"R","right":"T","leftLabel":"关系服务","rightLabel":"效率交易","leftDesc":"重人、重信任、重定制化服务;适合数字咨询/文旅/本地服务","rightDesc":"重效率、规模、标准化复制;适合电商/内容IP/跨境"},{"code":"PF","name":"节奏轴","left":"P","right":"F","leftLabel":"计划系统","rightLabel":"灵活应变","leftDesc":"重 SOP、流程、自动化、可复制;适合本地服务/电商/数字产品","rightDesc":"重应变、热点、现场体验;适合内容IP/文旅/跨境"}],"personas":[{"code":"EVRP","name":"资源整合者","tagline":"组局的人","desc":"向外连接 + 敢想新机会 + 重人重信任 + 有系统。你能把不同的人和资源撮合到同一张牌桌上,天生适合做平台、生态和整合型生意。","openPath":"你的 OPC 打开方式:做「连接者」,把资源组织成平台或生态生意,让系统替你转。"},{"code":"EVRF","name":"风口捕手","tagline":"追新的人","desc":"向外连接 + 敢想新机会 + 重人重信任 + 应变快。你对新机会嗅觉灵敏,又擅长带动人,适合社群型、体验型、快节奏的新赛道。","openPath":"你的 OPC 打开方式:小步快跑做社群与体验,靠关系和热度滚动起来。"},{"code":"EVTP","name":"战略操盘手","tagline":"布局的人","desc":"向外连接 + 敢想新机会 + 重效率 + 有系统。你能把新机会拆成可复制的系统,适合做平台架构、自动化放大的生意。","openPath":"你的 OPC 打开方式:顶层设计 + 系统复制,用智能体团队把规模做上去。"},{"code":"EVTF","name":"机会猎头","tagline":"抢滩的人","desc":"向外连接 + 敢想新机会 + 重效率 + 应变快。你善于捕捉窗口期、快速变现,适合流量打法、新品类抢滩。","openPath":"你的 OPC 打开方式:小步快跑、流量打法,抓住窗口快速试错、快速迭代。"},{"code":"EGRP","name":"连锁店长","tagline":"扎根的人","desc":"向外连接 + 落地存量 + 重人重信任 + 有系统。你适合把一件事在地域内做深做透,用口碑和标准复制扩张。","openPath":"你的 OPC 打开方式:在本地把一家店、一个产品做成标准,再一家家复制。"},{"code":"EGRF","name":"现场运营家","tagline":"搞事的人","desc":"向外连接 + 落地存量 + 重人重信任 + 应变快。你把人气组织起来是天生强项,适合本地活动、体验、社群运营。","openPath":"你的 OPC 打开方式:把人组织起来搞事,用一场场活动沉淀口碑与复购。"},{"code":"EGTP","name":"系统经理人","tagline":"做标准的人","desc":"向外连接 + 落地存量 + 重效率 + 有系统。你能把本地生意做成可复制标准,适合代运营、连锁化、SOP 生意。","openPath":"你的 OPC 打开方式:把服务 SOP 化、标准化,用系统去服务更多本地商家。"},{"code":"EGTF","name":"渠道掮客","tagline":"周转的人","desc":"向外连接 + 落地存量 + 重效率 + 应变快。你擅长信息差、撮合、渠道周转,适合分销、代运营、对接型生意。","openPath":"你的 OPC 打开方式:做资源对接与渠道周转,快进快出、以小博大。"},{"code":"IVRP","name":"走心创作者","tagline":"治愈的人","desc":"对内深耕 + 探索 + 重人重信任 + 有系统。你能把深度洞察沉淀成有温度的内容与体系,适合知识 IP、陪伴型服务。","openPath":"你的 OPC 打开方式:以内容建立信任,把洞察沉淀成体系化产品。"},{"code":"IVRF","name":"灵感写手","tagline":"表达的人","desc":"对内深耕 + 探索 + 重人重信任 + 应变快。你的表达有感染力,适合自媒体、个人 IP、故事型内容生意。","openPath":"你的 OPC 打开方式:跟着灵感持续输出,把个人表达变成个人 IP。"},{"code":"IVTP","name":"深度架构师","tagline":"建体系的人","desc":"对内深耕 + 探索 + 重效率 + 有系统。你能把复杂专业做成可复制的产品体系,适合课程、数字产品、深度研究变现。","openPath":"你的 OPC 打开方式:把专业做成体系化产品,一次做透、无限复制。"},{"code":"IVTF","name":"数字游侠","tagline":"造工具的人","desc":"对内深耕 + 探索 + 重效率 + 应变快。你能一个人做出可用的数字产品,适合独立开发、AI 工具、SaaS 化小产品。","openPath":"你的 OPC 打开方式:一个人写代码做产品,用工具自动化放大自己。"},{"code":"IGRP","name":"匠心交付师","tagline":"把手艺做透的人","desc":"对内深耕 + 落地存量 + 重人重信任 + 有系统。你沉得住气把交付做精,适合定制服务、一对一深度交付。","openPath":"你的 OPC 打开方式:用品质和口碑换复购,做高客单的深度服务。"},{"code":"IGRF","name":"慢匠人","tagline":"做深的人","desc":"对内深耕 + 落地存量 + 重人重信任 + 应变快。你能把一件事越做越深,适合在地文化记录、深度内容与精品创作。","openPath":"你的 OPC 打开方式:把一件事做深做透,慢即是快。"},{"code":"IGTP","name":"标准品大师","tagline":"做模板的人","desc":"对内深耕 + 落地存量 + 重效率 + 有系统。你能把产品做成标准模板、规模复制,适合电商标准品、模板化产品。","openPath":"你的 OPC 打开方式:把产品标准化、规模化,用模板和供应链放大。"},{"code":"IGTF","name":"精密运营师","tagline":"精算的人","desc":"对内深耕 + 落地存量 + 重效率 + 应变快。你对数字和细节敏感,适合电商精细化运营、数据驱动的优化生意。","openPath":"你的 OPC 打开方式:精算每一笔账、持续用数据优化,把效率抠到极致。"}],"tracks":[{"code":"TOUR","name":"文旅体验","icon":"fa-solid fa-mountain-sun","desc":"民宿 / 定制游 / 在地体验 / 非遗文创。吃透云南文旅红利与在地资源。","why":"你偏「对外连接 + 落地存量」:重人、重现场、重口碑,适合把在地资源做成体验生意。","firstStep":"起步第一小步:选一个本地体验产品(如民宿的 1 条定制游 / 1 场非遗体验),先做线上获客。","courseRef":"课程模块一(云南生态)· 模块二(赛道选择)"},{"code":"CONTENT","name":"内容IP","icon":"fa-solid fa-clapperboard","desc":"自媒体 / 知识博主 / 个人 IP。靠表达与持续输出建立影响力和信任。","why":"你偏「探索 + 表达 + 自驱」:耐得住冷启动,适合用内容把个人变成 IP。","firstStep":"起步第一小步:选定 1 个垂直话题,连发 30 天内容,验证反馈。","courseRef":"课程模块四(AI 内容)· 模块六(流量获客)"},{"code":"DIGITAL","name":"数字咨询","icon":"fa-solid fa-brain","desc":"顾问 / 1v1 服务 / 知识产品。用专业判断帮人解决问题,沉淀可复制的产品。","why":"你偏「重人重信任 + 专业积累」:适合把能力打包成付费咨询或知识产品。","firstStep":"起步第一小步:把最擅长的一项能力,打包成一次可定价的付费咨询。","courseRef":"课程模块三(商业模式)· 模块二(赛道选择)"},{"code":"ECOMM","name":"特色电商","icon":"fa-solid fa-bag-shopping","desc":"普洱 / 咖啡 / 鲜花 / 农特产。钻研选品、货源、供应链与运营硬功夫。","why":"你偏「落地 + 效率 + 执行」:务实、能抠成本和细节,适合把云南特产卖向全国。","firstStep":"起步第一小步:选 1 个单品做货源验证,小批量上架测真实销量。","courseRef":"课程模块三(商业模式)· 模块六(流量获客)"},{"code":"CROSS","name":"跨境边贸","icon":"fa-solid fa-earth-asia","desc":"面向东南亚 / 南亚的代运营、资源对接与信息差生意,吃透 25 个边境县区位。","why":"你偏「探索 + 胆识 + 应变」:敢闯新市场、善做信息差,适合跨境红利。","firstStep":"起步第一小步:调研 1 个边境口岸/跨境渠道,找出一个具体可做的小切口。","courseRef":"课程模块一(政策)· 模块八(跨境专题)"},{"code":"LOCAL","name":"本地服务","icon":"fa-solid fa-shop","desc":"本地商家代运营 / 私域 / 企微。把标准化服务卖给本地商家,重落地与关系。","why":"你偏「落地 + 关系 + 系统」:适合上门谈、落地执行,把服务做成可复制标准。","firstStep":"起步第一小步:免费为 1 家本地商家做一次试点(如私域/企微搭建),跑通再收费。","courseRef":"课程模块六(流量获客)· 模块三(商业模式)"}],"roles":[{"code":"MAKER","name":"产品交付型","icon":"fa-solid fa-hammer","duty":"做产品、保质量、做交付","evidence":"你把产品/交付打磨到极致时最投入","split":"你的主战场在「交付」:把内容与获客交给 AI 智能体辅助,自己专注打磨可复制的交付物。"},{"code":"CREATOR","name":"内容表达型","icon":"fa-solid fa-pen-nib","duty":"创作内容、立人设、做流量","evidence":"你通过表达释放能量,连发内容不愁","split":"你的主战场在「流量」:用 AI 内容流水线放大产量,自己把握选题与调性,交付尽量标准化。"},{"code":"HUNTER","name":"获客商务型","icon":"fa-solid fa-handshake","duty":"谈单、成交、维护客户","evidence":"你把客户谈下来时最有成就感","split":"你的主战场在「转化」:让 AI 负责内容获客与线索,你集中精力成交与维护关键客户。"},{"code":"RUNNER","name":"系统运营型","icon":"fa-solid fa-gears","duty":"建流程、SOP、自动化、运营","evidence":"你擅长把杂事理成系统","split":"你的主战场在「系统」:用 Agent 自动化放大整个生意,让流程替人跑。"},{"code":"ARCH","name":"架构统筹型","icon":"fa-solid fa-puzzle-piece","duty":"顶层设计、拍板、整合资源、带 AI 团队","evidence":"你习惯先想全局、敢拍板","split":"你的主战场在「架构」:抓方向、配资源、做决策,让多智能体团队替你跑执行。"}],"toolModes":[{"code":"DIALOG","name":"对话协创型","icon":"fa-solid fa-comments","desc":"AI 当你的对话助手,人主导每一步。适合喜欢掌控过程、爱逐句打磨的你。","division":"你主导每一步,AI 帮你想、帮你写、帮你查。","agents":["起步智能体:1 个「内容协创智能体」——对话式帮你打磨选题与文案,人拍板。"],"fit":"你偏好亲手掌控,AI 是放大器而非替代者。"},{"code":"AUTOCON","name":"内容自动化型","icon":"fa-solid fa-wand-magic-sparkles","desc":"AI 批量产出内容资产,人做选题把关。适合需要持续输出的内容 IP 与电商。","division":"你定选题方向和调性,AI 批量生成初稿,你筛选发布。","agents":["起步智能体:①选题智能体 ②内容生产智能体(对齐 T04,人做最终把关)。"],"fit":"你要的是持续供给,AI 是内容流水线。"},{"code":"AGENT","name":"Agent 委托型","icon":"fa-solid fa-robot","desc":"搭智能体团队自动跑链路。适合有系统思维、想规模化的人。","division":"你设计规则,AI 自动完成获客、客服、复盘全链路。","agents":["起步智能体:获客智能体 + 客服智能体 + 复盘智能体(对齐 T04_智能体配置清单)。"],"fit":"你要的是自动运转,AI 是你的虚拟团队。"},{"code":"OUTSOURCE","name":"外包杠杆型","icon":"fa-solid fa-link","desc":"AI 提效 + 外包人力 + 供应链合作。适合重交付、想放大规模的人。","division":"AI 提效重复劳动,外包非核心环节,供应链合作放大产能,你抓核心。","agents":["起步智能体:①外包任务管理智能体 ②供应链数据智能体。"],"fit":"你要的是杠杆,AI 是撬动资源的那根杠杆。"}],"adaptLevels":[{"level":"high","range":[78,100],"label":"高适配","color":"#16a34a","verdict":"你的心态与条件相当适合一个人做 OPC,值得认真启动。","advice":["心态内核(独立/自驱/敢试错)与作战条件(单兵/AI/安全垫)都较齐备","可直接进入赛道/角色/工具模式深度推荐,全职启动"],"nextStep":"建议报名线下深度服务:2 天现场定稿方案 + 跑通智能体 + 资源对接,30 天陪跑到首单。"},{"level":"mid","range":[55,77],"label":"中适配","color":"#d97706","verdict":"你适合从副业 / 小规模慢启动,先补短板再放大。","advice":["报告中分数最低的维度是你的首要补齐项(见下方 6 维图)","先以副业形式小步验证,保住现有收入再切换"],"nextStep":"建议报名线上标准服务:2 周系统学习 + 30 天陪跑,边保主业边把生意跑起来。"},{"level":"low","range":[0,54],"label":"低适配","color":"#b91c1c","verdict":"现在不建议立即全职 OPC,先做 3 个月准备再决定。","advice":["先补安全垫:稳定收入 / 一笔启动储备金","用 30 天做一个最小实验(见推荐赛道的第一小步),用真实反馈检验意愿","练 1 项能独当一面的技能 + 学会用 AI 提效"],"nextStep":"建议从公益课建立认知开始,或先做 30 天小实验,再决定是否报名系统课程。"}],"personaOpen":"你的 OPC 打开方式","disclaimer":"本测评参考 MBTI 的自评逻辑开发,测量的是「偏好」而非「能力」,结果仅供参考,不构成任何职业或投资决策的硬性依据。创业没有标准答案,你的行动与迭代比类型更真实。","quickNote":"本结果来自快速版(30 题),仅供方向参考;如需更准确画像,建议完成完整版(60 题)。"}; + +const RULE_MAP = {}; +RULES.forEach((r) => { RULE_MAP[r.id] = r; }); + +export const ADAPT_DIMS = ['IND','RISK','DRIVE','SOLO','AI','STABLE']; +export const ADAPT_LABELS = { IND:'独立自主', RISK:'风险承受', DRIVE:'自驱动力', SOLO:'单兵多面', AI:'AI 意愿', STABLE:'安全垫' }; +export const AXIS_PAIRS = [['E','I'],['V','G'],['R','T'],['P','F']]; +export const AXIS_NAMES = { EI:'能量', VG:'视野', RT:'价值', PF:'节奏' }; +export const SECTIONS = { + P1: '第一部分 · 创业内核(独立 / 风险 / 自驱 / 单兵 / AI / 安全垫)', + P2: '第二部分 · 特质倾向(能量 / 视野 / 价值 / 节奏)', + P3: '第三部分 · 赛道偏好(文旅 / 内容IP / 咨询 / 电商 / 跨境 / 本地)', + P4: '第四部分 · 角色偏向(产品 / 内容 / 商务 / 运营 / 架构)', + P5: '第五部分 · 人机协作(对话 / 自动化 / 智能体 / 外包)' +}; + +export function currentQuestions(version) { + return version === 'quick' ? QUESTIONS.filter((x) => x.quick) : QUESTIONS; +} + +/** 计分:与设计文档一致 —— 适配指数(6维等权) + 类型码(4轴) + 赛道/角色/工具(share of votes) */ +export function calculate(answers, version = 'full') { + const ids = currentQuestions(version).map((x) => x.id); + const counts = {}; + ids.forEach((id) => { + const ans = answers[id]; + if (ans !== 'A' && ans !== 'B') return; + const rule = RULE_MAP[id]; + if (!rule) return; + const code = ans === 'A' ? rule.A : rule.B; + if (code) counts[code] = (counts[code] || 0) + 1; + }); + + const adaptDims = []; + let adaptSum = 0; + ADAPT_DIMS.forEach((dim) => { + let max = 0; + ids.forEach((id) => { const r = RULE_MAP[id]; if ((r && r.A === dim) || (r && r.B === dim)) max++; }); + const votes = counts[dim] || 0; + const score = max > 0 ? Math.round((votes / max) * 100) : 0; + adaptSum += score; + adaptDims.push({ code: dim, label: ADAPT_LABELS[dim], score, votes, max }); + }); + const adaptIndex = Math.round(adaptSum / ADAPT_DIMS.length); + let adaptLevel = null; + PROFILES.adaptLevels.forEach((lv) => { if (adaptIndex >= lv.range[0] && adaptIndex <= lv.range[1]) adaptLevel = lv; }); + const weakestDims = [...adaptDims].sort((a, b) => a.score - b.score).slice(0, 2); + + let typeCode = ''; + const axesDetail = []; + AXIS_PAIRS.forEach(([left, right]) => { + const l = counts[left] || 0, r = counts[right] || 0, total = l + r; + const letter = l >= r ? left : right; + typeCode += letter; + axesDetail.push({ pair: left + right, left, right, leftPct: total > 0 ? Math.round((l / total) * 100) : 50, rightPct: total > 0 ? Math.round((r / total) * 100) : 50, winner: letter, lCount: l, rCount: r }); + }); + const persona = PROFILES.personas.find((x) => x.code === typeCode) || null; + + function rank(dims) { + let total = 0; + dims.forEach((d) => { total += counts[d] || 0; }); + return dims.map((d) => ({ code: d, votes: counts[d] || 0, pct: total > 0 ? Math.round(((counts[d] || 0) / total) * 100) : 0 })) + .sort((a, b) => b.votes - a.votes || a.code.localeCompare(b.code)); + } + const tracks = rank(PROFILES.tracks.map((t) => t.code)).filter((t) => t.pct >= 10).slice(0, 3); + const roles = rank(PROFILES.roles.map((t) => t.code)).filter((t) => t.pct >= 5).slice(0, 2); + const tools = rank(PROFILES.toolModes.map((t) => t.code)).filter((t) => t.pct >= 5).slice(0, 2); + + return { version, answeredCount: Object.keys(answers).filter((id) => ids.includes(Number(id))).length, typeCode, persona, adaptIndex, adaptLevel, adaptDims, weakestDims, axesDetail, tracks, roles, tools }; +} + +/** + * 展开报告:把 tracks / roles / tools 从「code + 票数」补全为包含全部评语/描述的完整对象, + * 这样客户端渲染报告时无需再持有 PROFILES 本地副本(评语全部来自后端)。 + */ +export function expandResult(r) { + const find = (list, code) => list.find((x) => x.code === code) || {}; + return { + ...r, + tracks: (r.tracks || []).map((t) => ({ ...t, ...find(PROFILES.tracks, t.code) })), + roles: (r.roles || []).map((x) => ({ ...x, ...find(PROFILES.roles, x.code) })), + tools: (r.tools || []).map((x) => ({ ...x, ...find(PROFILES.toolModes, x.code) })) + }; +} diff --git a/website/server/start.mjs b/website/server/start.mjs new file mode 100644 index 0000000..4a2fbbb --- /dev/null +++ b/website/server/start.mjs @@ -0,0 +1,66 @@ +/** + * OPC 培训站 · 独立启动服务 + * ------------------------------------------------------------- + * 用途:把构建产物 dist/ 静态托管 + 挂载模拟认证 API,一条命令跑起来。 + * 启动:npm run build && node server/start.mjs + * 默认端口 8091(避开 PineAgentsServer 的 8090)。 + * 迁移:后期接真实后端后,本文件可替换为生产服务器/网关。 + */ +import http from 'node:http'; +import { createReadStream, existsSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import { mockApi } from './mock-api.mjs'; + +const PORT = process.env.PORT || 8091; +const DIST = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../dist'); + +const MIME = { + '.html': 'text/html; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.webp': 'image/webp', + '.json': 'application/json; charset=utf-8', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ico': 'image/x-icon' +}; + +function serveStatic(req, res) { + let p = (req.url || '/').split('?')[0]; + if (p === '/' || p === '') p = '/index.html'; + // 防路径穿越 + const file = path.normalize(path.join(DIST, p)); + if (!file.startsWith(DIST)) return notFound(res); + if (!existsSync(file) || statSync(file).isDirectory()) { + // SPA 回退:非文件请求一律给 index.html(仅当请求的不是带扩展名的资源) + if (!path.extname(file)) return sendFile(res, path.join(DIST, 'index.html')); + return notFound(res); + } + sendFile(res, file); +} + +function sendFile(res, file) { + const ext = path.extname(file).toLowerCase(); + res.setHeader('Content-Type', MIME[ext] || 'application/octet-stream'); + res.setHeader('Cache-Control', ext === '.html' ? 'no-cache' : 'public, max-age=31536000, immutable'); + createReadStream(file).pipe(res); +} + +function notFound(res) { + res.statusCode = 404; + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.end('Not Found'); +} + +const server = http.createServer((req, res) => { + mockApi(req, res, () => serveStatic(req, res)); +}); + +server.listen(PORT, () => { + console.log(`▶ OPC 培训站已启动: http://localhost:${PORT}`); + console.log(` 静态资源: ${DIST}`); + console.log(` 模拟登录: POST /api/auth/login (pine / 123456)`); +}); diff --git a/website/server/store.mjs b/website/server/store.mjs new file mode 100644 index 0000000..a0f74c4 --- /dev/null +++ b/website/server/store.mjs @@ -0,0 +1,84 @@ +/** + * 数据存储层(JSON 文件落盘) + * ------------------------------------------------------------- + * 集合: + * bookings —— 公益课报名 / 沙龙预约(前端预约表单) + * events —— 公益课 + 沙龙排期(后台可增删改) + * tests —— OPC 测评记录(前端测评上报) + * 说明: + * - 同步读写在数据量小的阶段足够;迁移真实数据库时替换本文件实现即可。 + * - 带内存缓存,避免每次请求都读文件。 + */ +import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const DIR = path.dirname(fileURLToPath(import.meta.url)); +const FILES = { + accounts: path.join(DIR, 'accounts.json'), + bookings: path.join(DIR, 'bookings.json'), + events: path.join(DIR, 'events.json'), + tests: path.join(DIR, 'tests.json'), + policyLogs: path.join(DIR, 'policyLogs.json'), + planLogs: path.join(DIR, 'planLogs.json'), + surveyLogs: path.join(DIR, 'surveyLogs.json') +}; + +const CACHE = {}; + +function ensureFile(key) { + const file = FILES[key]; + if (!file) throw new Error('unknown store: ' + key); + if (!existsSync(file)) { + mkdirSync(path.dirname(file), { recursive: true }); + writeFileSync(file, '[]', 'utf8'); + } + return file; +} + +export function loadAll(key) { + if (CACHE[key] !== undefined) return CACHE[key]; + const file = ensureFile(key); + try { + CACHE[key] = JSON.parse(readFileSync(file, 'utf8') || '[]'); + } catch { + CACHE[key] = []; + } + if (!Array.isArray(CACHE[key])) CACHE[key] = []; + return CACHE[key]; +} + +export function save(key, list) { + CACHE[key] = Array.isArray(list) ? list : []; + const file = ensureFile(key); + writeFileSync(file, JSON.stringify(CACHE[key], null, 2), 'utf8'); +} + +export function add(key, entry) { + const list = loadAll(key); + list.push(entry); + save(key, list); + return entry; +} + +export function update(key, id, patch) { + const list = loadAll(key); + const i = list.findIndex((x) => x.id === id); + if (i < 0) return null; + list[i] = { ...list[i], ...patch, id }; + save(key, list); + return list[i]; +} + +export function remove(key, id) { + const list = loadAll(key); + const next = list.filter((x) => x.id !== id); + if (next.length === list.length) return false; + save(key, next); + return true; +} + +/** 首次运行注入种子数据(仅当集合为空时) */ +export function seedIfEmpty(key, seed) { + if (loadAll(key).length === 0) save(key, seed); +} diff --git a/website/src/App.jsx b/website/src/App.jsx new file mode 100644 index 0000000..3e85726 --- /dev/null +++ b/website/src/App.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import Home from './user/Home'; +import System from './platform/System'; +import Structures from './platform/Structures'; +import Schedule from './platform/Schedule'; +import Cards from './platform/Cards'; +import Tools from './platform/Tools'; +import OPCTest from './user/OPCTest'; +import Events from './user/Events'; +import EventDetail from './user/EventDetail'; +import PolicyTest from './user/PolicyTest'; +import StartPlan from './user/StartPlan'; +import OPCSurvey from './user/OPCSurvey'; +import Profile from './user/Profile'; +import PineHome from './platform/PineHome'; +import PineOps from './platform/PineOps'; +import PineBookings from './platform/PineBookings'; +import PineEvents from './platform/PineEvents'; +import PineTests from './platform/PineTests'; +import ReportHome from './platform/report/ReportHome'; +import ReportBiz from './platform/report/ReportBiz'; +import ReportPolicy from './platform/report/ReportPolicy'; +import ReportData from './platform/report/ReportData'; +import PineLogs from './platform/PineLogs'; +import Login from './platform/Login'; +import { ParticleField } from './components/organisms'; +import { isAuthed } from './services/auth'; + +/* 公开路由(无需登录) */ +const PUBLIC_ROUTES = [ + { path: '', Page: Home }, + { path: 'events', Page: Events }, + { path: 'event-detail', Page: EventDetail }, + { path: 'opc-test', Page: OPCTest }, + { path: 'policy-test', Page: PolicyTest }, + { path: 'start-plan', Page: StartPlan }, + { path: 'survey', Page: OPCSurvey }, + { path: 'profile', Page: Profile } +]; + +/* 内部路由(/pine,需登录) */ +const PINE_ROUTES = [ + { path: 'pine', Page: PineHome }, + { path: 'pine/ops', Page: PineOps }, + { path: 'pine/bookings', Page: PineBookings }, + { path: 'pine/events', Page: PineEvents }, + { path: 'pine/tests', Page: PineTests }, + { path: 'pine/logs', Page: PineLogs }, + { path: 'pine/report', Page: ReportHome }, + { path: 'pine/report/biz', Page: ReportBiz }, + { path: 'pine/report/policy', Page: ReportPolicy }, + { path: 'pine/report/data', Page: ReportData }, + { path: 'pine/system', Page: System }, + { path: 'pine/structures', Page: Structures }, + { path: 'pine/schedule', Page: Schedule }, + { path: 'pine/cards', Page: Cards }, + { path: 'pine/tools', Page: Tools } +]; + +const ALL_ROUTES = [...PUBLIC_ROUTES, ...PINE_ROUTES]; +const isPine = (p) => p === 'pine' || p.startsWith('pine/'); + +function parseHash() { + return window.location.hash.replace(/^#\/?/, '').split('?')[0].trim(); +} + +export default function App() { + const [path, setPath] = React.useState(parseHash); + + React.useEffect(() => { + const onHash = () => setPath(parseHash()); + window.addEventListener('hashchange', onHash); + return () => window.removeEventListener('hashchange', onHash); + }, []); + + const route = ALL_ROUTES.find((r) => r.path === path) || PUBLIC_ROUTES[0]; + + // 路由守卫:内部路由未登录 → 登录门(目标路径回传,登录后跳回) + if (isPine(route.path) && !isAuthed()) { + return ( + <> + + + + ); + } + + const { Page } = route; + return ( + <> + + + + ); +} diff --git a/website/src/components/AuthGate.jsx b/website/src/components/AuthGate.jsx new file mode 100644 index 0000000..27d4cfc --- /dev/null +++ b/website/src/components/AuthGate.jsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { sendCode, phoneLogin } from '@/services/auth'; +import '@/styles/auth.css'; + +/** + * 登录 / 注册墙(AuthGate)· 手机号 + 短信验证码 + * 用于"测评结果需登录"等场景。手机号即账号,未注册自动注册,无密码。 + * props: + * title — 卡片标题(覆盖默认) + * subtitle — 一句话说明 + * onAuthed(user) — 登录 / 注册成功回调(已写入 token 与用户信息) + */ +export default function AuthGate({ title, subtitle, onAuthed }) { + const [phone, setPhone] = React.useState(''); + const [code, setCode] = React.useState(''); + const [debug, setDebug] = React.useState(''); + const [err, setErr] = React.useState(''); + const [busy, setBusy] = React.useState(false); + const [cd, setCd] = React.useState(0); // 发送倒计时 + + const send = async () => { + if (!/^1\d{10}$/.test(phone)) { setErr('请输入正确的 11 位手机号'); return; } + setErr(''); + setBusy(true); + try { + const r = await sendCode(phone.trim()); + setDebug(`验证码已发送${r.debugCode ? ',演示验证码:' + r.debugCode : ''}(5 分钟内有效)`); + setCd(60); + const t = setInterval(() => setCd((v) => { if (v <= 1) { clearInterval(t); return 0; } return v - 1; }), 1000); + } catch (ex) { + setErr(ex && ex.message ? ex.message : '发送失败,请重试'); + } finally { + setBusy(false); + } + }; + + const submit = async (e) => { + e.preventDefault(); + setErr(''); + if (!/^1\d{10}$/.test(phone)) { setErr('请输入正确的 11 位手机号'); return; } + if (!code.trim()) { setErr('请输入验证码'); return; } + setBusy(true); + try { + const user = await phoneLogin(phone.trim(), code.trim()); + if (onAuthed) onAuthed(user); + } catch (ex) { + setErr(ex && ex.message ? ex.message : '登录失败,请重试'); + } finally { + setBusy(false); + } + }; + + return ( +
+
+

{title || '手机号登录 / 注册'}

+ {subtitle &&

{subtitle}

} + +
+ + setPhone(e.target.value)} + inputMode="numeric" + maxLength={11} + placeholder="11 位手机号" + autoComplete="tel" + /> +
+
+ +
+ setCode(e.target.value)} + inputMode="numeric" + maxLength={6} + placeholder="6 位验证码" + style={{ flex: 1 }} + autoComplete="one-time-code" + /> + +
+
+ + {debug &&
{debug}
} + {err &&
{err}
} + +
未注册的手机号将自动注册为账号
+ + ); +} diff --git a/website/src/components/Skeleton.jsx b/website/src/components/Skeleton.jsx new file mode 100644 index 0000000..3cd0883 --- /dev/null +++ b/website/src/components/Skeleton.jsx @@ -0,0 +1,34 @@ +import React from 'react'; + +/** + * 骨架屏组件(纯本地占位,不依赖网络)。 + * 用法: + * 样式见 global.css 的 .sk 系列(暗色 + 流光)。 + */ +const Skeleton = ({ className = '', style = {}, children }) => ( + +); + +Skeleton.Line = ({ w = '100%', h = 12, className = '', style = {} }) => ( +
+); + +Skeleton.Box = ({ h = 120, className = '', style = {}, children }) => ( +
{children}
+); + +Skeleton.Circle = ({ s = 44, className = '', style = {} }) => ( +
+); + +Skeleton.Rows = ({ n = 3, gap = 12, last = '70%', className = '' }) => ( +
+ {Array.from({ length: n }).map((_, i) => ( + + ))} +
+); + +export default Skeleton; diff --git a/website/src/components/atoms/Icon.jsx b/website/src/components/atoms/Icon.jsx new file mode 100644 index 0000000..cf79c77 --- /dev/null +++ b/website/src/components/atoms/Icon.jsx @@ -0,0 +1,67 @@ +/** + * Icon —— 内联 SVG 图标(Appica / Tabler 线稿风格) + * 统一:viewBox 24、stroke=currentColor、stroke-width 1.5、圆头圆角、fill=none、aria-hidden。 + * 用法:;className 追加。 + * 装饰性图标默认 aria-hidden;独立可交互图标请外层给 aria-label。 + */ +import React from 'react'; + +/* 线稿路径(1.5 线宽下清晰) */ +const ICONS = { + 'arrow-right': <>, + 'arrow-left': <>, + 'chevron-down': , + 'chevron-right': , + 'close': , + 'check': , + 'check-circle': <>, + 'plus': , + 'calendar': <>, + 'clock': <>, + 'map-pin': <>, + 'user': <>, + 'users': <>, + 'download': <>, + 'upload': <>, + 'search': <>, + 'star': , + 'sparkles': <>, + 'home': <>, + 'phone': , + 'link': <>, + 'route': <>, + 'users-check': <>, + 'layers': <>, + 'trending-up': <>, + 'book': <>, + 'file': <>, + 'shield': <>, + 'flask': <>, + 'compass': <>, + 'tools': <>, + 'sliders': <>, + 'trash': <>, +}; + +export function Icon({ name, size, className = '', ...rest }) { + const glyph = ICONS[name] || null; + if (!glyph) return null; + const cls = ['icon', size ? `icon--${size}` : '', className].filter(Boolean).join(' '); + return ( + + ); +} + +export default Icon; diff --git a/website/src/components/atoms/index.jsx b/website/src/components/atoms/index.jsx new file mode 100644 index 0000000..12c40e1 --- /dev/null +++ b/website/src/components/atoms/index.jsx @@ -0,0 +1,134 @@ +import React from 'react'; + +/* ---------- 原子:Button ---------- */ +export function Button({ variant = 'white', href, children, className = '', ...rest }) { + const cls = `btn btn-${variant} ${className}`.trim(); + if (href) return {children}; + return ; +} + +/* ---------- 原子:Tag ---------- */ +export function Tag({ ghost, children }) { + return {children}; +} + +/* ---------- 原子:Kicker ---------- */ +export function Kicker({ children }) { + return
{children}
; +} + +/* ---------- 原子:Card(面) ---------- */ +export function Card({ tag, ghost, title, children, className = '', ...rest }) { + return ( +
+ {tag && {tag}} + {title &&
{title}
} + {children} +
+ ); +} + +/* ---------- 原子:SectionHead ---------- */ +export function SectionHead({ no, title, en }) { + return ( +
+ {no} +
+

{title}

+ {en &&
{en}
} +
+
+ ); +} + +/* ---------- 原子:StatGlyph ---------- */ +export function StatGlyph({ children }) { + return
{children}
; +} + +/* ---------- 原子:Logo ---------- */ +export function Logo() { + return ( + + + + ); +} + +/* ---------- 原子:PageTitle / PageDesc ---------- */ +export function PageTitle({ children }) { + return

{children}

; +} +export function PageDesc({ children }) { + return

{children}

; +} + +/* ---------- 原子:表单字段 ---------- */ +export function FieldText({ label, placeholder = '', value, onChange, type = 'text' }) { + return ( +
+ + onChange(e.target.value)} /> +
+ ); +} + +export function FieldTextarea({ label, placeholder = '', value, onChange, rows = 3 }) { + return ( +
+ +