From 8d78e182671f121d642701a28f9fa45c67a9b747 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 24 Feb 2026 17:57:25 +0800 Subject: [PATCH] Initial commit: X-Server Mobile enterprise tool system - Frontend: React 18 + TypeScript + Vite + Ant Design Mobile - Backend: PHP 8.5 + Webman 2.x + MySQL + Redis - i18n support: Chinese, English, Russian, Spanish, French - Approval workflow engine - Docker development environment - Feishu integration Co-Authored-By: Claude Opus 4.6 --- .gitignore | 50 + CLAUDE.md | 223 + DEV_REVERSE_PROXY_SETUP.md | 219 + IMPLEMENTATION_PLAN.md | 601 +++ PROJECT.md | 356 ++ REVERSE_PROXY_SETUP.md | 142 + backend/.env.example | 23 + backend/Dockerfile | 34 + backend/app/Controller/ApprovalController.php | 134 + backend/app/Controller/AuthController.php | 59 + backend/app/Controller/BaseController.php | 41 + .../app/Controller/BusinessTripController.php | 60 + backend/app/Controller/UserController.php | 62 + backend/app/Model/ApprovalFlow.php | 47 + backend/app/Model/ApprovalNode.php | 79 + backend/app/Model/ApprovalOrder.php | 91 + backend/app/Model/ApprovalRecord.php | 63 + backend/app/Model/Company.php | 52 + backend/app/Model/User.php | 60 + backend/app/Service/ApprovalService.php | 302 ++ backend/app/Service/FeishuService.php | 252 + backend/app/Util/Db.php | 66 + backend/app/Util/JwtUtil.php | 47 + backend/app/Util/Lang.php | 170 + backend/app/middleware/AuthMiddleware.php | 35 + backend/app/middleware/CorsMiddleware.php | 27 + backend/composer.json | 43 + backend/composer.lock | 4750 +++++++++++++++++ backend/config/app.php | 11 + backend/config/container.php | 3 + backend/config/database.php | 24 + backend/config/log.php | 17 + backend/config/plugin/webman/console/app.php | 28 + backend/config/redis.php | 10 + backend/config/route.php | 41 + backend/config/server.php | 23 + backend/database/migrations/init.sql | 196 + backend/lang/en.php | 75 + backend/lang/es.php | 75 + backend/lang/fr.php | 75 + backend/lang/ru.php | 75 + backend/lang/zh.php | 75 + backend/runtime/webman.pid | 1 + backend/start.php | 5 + backend/support/Bootstrap.php | 16 + backend/support/bootstrap.php | 139 + backend/webman | 71 + backend/windows.php | 136 + docker-compose.yml | 26 + frontend/.gitignore | 24 + frontend/README.md | 73 + frontend/eslint.config.js | 23 + frontend/index.html | 13 + frontend/package-lock.json | 4197 +++++++++++++++ frontend/package.json | 41 + frontend/postcss.config.js | 20 + frontend/public/vite.svg | 1 + frontend/src/App.css | 42 + frontend/src/App.tsx | 2 + frontend/src/assets/react.svg | 1 + .../LanguageSwitcher.module.css | 33 + .../src/components/LanguageSwitcher/index.tsx | 49 + .../src/components/layout/Header.module.css | 36 + frontend/src/components/layout/Header.tsx | 26 + .../components/layout/MobileLayout.module.css | 24 + .../src/components/layout/MobileLayout.tsx | 31 + .../src/components/layout/TabBar.module.css | 36 + frontend/src/components/layout/TabBar.tsx | 47 + frontend/src/config/index.ts | 17 + frontend/src/i18n/index.ts | 39 + frontend/src/i18n/locales/en.json | 150 + frontend/src/i18n/locales/es.json | 150 + frontend/src/i18n/locales/fr.json | 150 + frontend/src/i18n/locales/ru.json | 150 + frontend/src/i18n/locales/zh.json | 150 + frontend/src/index.css | 68 + frontend/src/main.tsx | 25 + .../components/ApprovalAction.module.css | 59 + .../approval/components/ApprovalAction.tsx | 111 + .../components/ApprovalDetail.module.css | 149 + .../approval/components/ApprovalDetail.tsx | 108 + .../components/ApprovalFlow.module.css | 62 + .../approval/components/ApprovalFlow.tsx | 77 + .../components/ApprovalList.module.css | 70 + .../approval/components/ApprovalList.tsx | 78 + .../approval/hooks/useApprovalAction.ts | 59 + .../modules/approval/hooks/useApprovalList.ts | 12 + .../approval/hooks/useApprovalSubmit.ts | 30 + frontend/src/modules/approval/index.ts | 29 + .../src/modules/approval/services/approval.ts | 42 + frontend/src/modules/approval/types.ts | 112 + frontend/src/pages/apps/apps.module.css | 80 + frontend/src/pages/apps/index.tsx | 64 + .../src/pages/business-trip/apply.module.css | 40 + frontend/src/pages/business-trip/apply.tsx | 23 + .../src/pages/business-trip/detail.module.css | 39 + frontend/src/pages/business-trip/detail.tsx | 16 + .../src/pages/business-trip/list.module.css | 32 + frontend/src/pages/business-trip/list.tsx | 14 + .../src/pages/dashboard/dashboard.module.css | 284 + frontend/src/pages/dashboard/index.tsx | 174 + frontend/src/pages/login/index.tsx | 12 + frontend/src/pages/login/login.module.css | 30 + frontend/src/pages/profile/index.tsx | 113 + frontend/src/pages/profile/profile.module.css | 154 + frontend/src/router/index.tsx | 29 + frontend/src/services/api.ts | 99 + frontend/src/stores/user.ts | 51 + frontend/src/types/index.ts | 103 + frontend/src/utils/feishu.ts | 92 + frontend/src/utils/rem.ts | 35 + frontend/tsconfig.app.json | 34 + frontend/tsconfig.json | 7 + frontend/tsconfig.node.json | 26 + frontend/vite.config.ts | 27 + frpc/frpc-dev.ini | 47 + frpc/frpc-dev.service | 49 + frpc/frpc.ini | 35 + nginx/x-server-api.conf | 48 + package-lock.json | 968 ++++ package.json | 12 + 121 files changed, 18563 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 DEV_REVERSE_PROXY_SETUP.md create mode 100644 IMPLEMENTATION_PLAN.md create mode 100644 PROJECT.md create mode 100644 REVERSE_PROXY_SETUP.md create mode 100644 backend/.env.example create mode 100644 backend/Dockerfile create mode 100644 backend/app/Controller/ApprovalController.php create mode 100644 backend/app/Controller/AuthController.php create mode 100644 backend/app/Controller/BaseController.php create mode 100644 backend/app/Controller/BusinessTripController.php create mode 100644 backend/app/Controller/UserController.php create mode 100644 backend/app/Model/ApprovalFlow.php create mode 100644 backend/app/Model/ApprovalNode.php create mode 100644 backend/app/Model/ApprovalOrder.php create mode 100644 backend/app/Model/ApprovalRecord.php create mode 100644 backend/app/Model/Company.php create mode 100644 backend/app/Model/User.php create mode 100644 backend/app/Service/ApprovalService.php create mode 100644 backend/app/Service/FeishuService.php create mode 100644 backend/app/Util/Db.php create mode 100644 backend/app/Util/JwtUtil.php create mode 100644 backend/app/Util/Lang.php create mode 100644 backend/app/middleware/AuthMiddleware.php create mode 100644 backend/app/middleware/CorsMiddleware.php create mode 100644 backend/composer.json create mode 100644 backend/composer.lock create mode 100644 backend/config/app.php create mode 100644 backend/config/container.php create mode 100644 backend/config/database.php create mode 100644 backend/config/log.php create mode 100644 backend/config/plugin/webman/console/app.php create mode 100644 backend/config/redis.php create mode 100644 backend/config/route.php create mode 100644 backend/config/server.php create mode 100644 backend/database/migrations/init.sql create mode 100644 backend/lang/en.php create mode 100644 backend/lang/es.php create mode 100644 backend/lang/fr.php create mode 100644 backend/lang/ru.php create mode 100644 backend/lang/zh.php create mode 100644 backend/runtime/webman.pid create mode 100644 backend/start.php create mode 100644 backend/support/Bootstrap.php create mode 100644 backend/support/bootstrap.php create mode 100755 backend/webman create mode 100644 backend/windows.php create mode 100644 docker-compose.yml create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/LanguageSwitcher/LanguageSwitcher.module.css create mode 100644 frontend/src/components/LanguageSwitcher/index.tsx create mode 100644 frontend/src/components/layout/Header.module.css create mode 100644 frontend/src/components/layout/Header.tsx create mode 100644 frontend/src/components/layout/MobileLayout.module.css create mode 100644 frontend/src/components/layout/MobileLayout.tsx create mode 100644 frontend/src/components/layout/TabBar.module.css create mode 100644 frontend/src/components/layout/TabBar.tsx create mode 100644 frontend/src/config/index.ts create mode 100644 frontend/src/i18n/index.ts create mode 100644 frontend/src/i18n/locales/en.json create mode 100644 frontend/src/i18n/locales/es.json create mode 100644 frontend/src/i18n/locales/fr.json create mode 100644 frontend/src/i18n/locales/ru.json create mode 100644 frontend/src/i18n/locales/zh.json create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/modules/approval/components/ApprovalAction.module.css create mode 100644 frontend/src/modules/approval/components/ApprovalAction.tsx create mode 100644 frontend/src/modules/approval/components/ApprovalDetail.module.css create mode 100644 frontend/src/modules/approval/components/ApprovalDetail.tsx create mode 100644 frontend/src/modules/approval/components/ApprovalFlow.module.css create mode 100644 frontend/src/modules/approval/components/ApprovalFlow.tsx create mode 100644 frontend/src/modules/approval/components/ApprovalList.module.css create mode 100644 frontend/src/modules/approval/components/ApprovalList.tsx create mode 100644 frontend/src/modules/approval/hooks/useApprovalAction.ts create mode 100644 frontend/src/modules/approval/hooks/useApprovalList.ts create mode 100644 frontend/src/modules/approval/hooks/useApprovalSubmit.ts create mode 100644 frontend/src/modules/approval/index.ts create mode 100644 frontend/src/modules/approval/services/approval.ts create mode 100644 frontend/src/modules/approval/types.ts create mode 100644 frontend/src/pages/apps/apps.module.css create mode 100644 frontend/src/pages/apps/index.tsx create mode 100644 frontend/src/pages/business-trip/apply.module.css create mode 100644 frontend/src/pages/business-trip/apply.tsx create mode 100644 frontend/src/pages/business-trip/detail.module.css create mode 100644 frontend/src/pages/business-trip/detail.tsx create mode 100644 frontend/src/pages/business-trip/list.module.css create mode 100644 frontend/src/pages/business-trip/list.tsx create mode 100644 frontend/src/pages/dashboard/dashboard.module.css create mode 100644 frontend/src/pages/dashboard/index.tsx create mode 100644 frontend/src/pages/login/index.tsx create mode 100644 frontend/src/pages/login/login.module.css create mode 100644 frontend/src/pages/profile/index.tsx create mode 100644 frontend/src/pages/profile/profile.module.css create mode 100644 frontend/src/router/index.tsx create mode 100644 frontend/src/services/api.ts create mode 100644 frontend/src/stores/user.ts create mode 100644 frontend/src/types/index.ts create mode 100644 frontend/src/utils/feishu.ts create mode 100644 frontend/src/utils/rem.ts create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 frpc/frpc-dev.ini create mode 100644 frpc/frpc-dev.service create mode 100644 frpc/frpc.ini create mode 100644 nginx/x-server-api.conf create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4aa267 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Dependencies +node_modules/ +vendor/ + +# Build outputs +frontend/dist/ +frontend/build/ +frontend/.vite/ + +# Environment files +.env +.env.local +.env.production + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ +backend/runtime/logs/ +backend/runtime/cache/ + +# Cache +.cache/ +.npm/ +.eslintcache + +# Testing +coverage/ + +# Docker volumes (if any) +docker-volumes/ + +# Temporary files +*.tmp +*.temp +.claude/ + +# Lock files (optional - uncomment if you want to exclude) +# package-lock.json +# composer.lock diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b6afd49 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,223 @@ +# X-Server Mobile 项目文档 + +## 项目概述 + +这是一个基于飞书的企业内部H5移动工具平台,采用多租户架构,支持审批流程引擎。 + +**关键特性:** +- 支持5种语言:中文、英文、俄语、西语、法语 +- 多租户架构(用户全局唯一,关联多个公司) +- 审批流程引擎(单人审批、会签、或签、通知) +- 飞书免登集成 + +**技术栈:** +- 前端:React 18.3.1 + TypeScript + Vite + Ant Design Mobile +- 后端:PHP 8.5 + Webman 2.x + MySQL + Redis +- 部署:Docker + +## 国际化 (i18n) - 重要! + +### 前端 +- 配置:`frontend/src/i18n/` +- 翻译文件:`frontend/src/i18n/locales/{zh,en,ru,es,fr}.json` +- 语言切换: + - 首页右上角国旗按钮 + - 个人中心 -> 语言设置 +- 存储位置:localStorage `i18nextLng` + +使用方式: +```tsx +import { useTranslation } from 'react-i18next' +const { t } = useTranslation() +
{t('common.confirm')}
+``` + +### 后端 +- 翻译文件:`backend/lang/{zh,en,ru,es,fr}.php` +- 工具类:`backend/app/Util/Lang.php` + +使用方式: +```php +use App\Util\Lang; +$message = Lang::get('common.confirm'); +Lang::setLocale('en'); +``` + +## 目录结构 + +``` +/www/x-server-mobile/ +├── frontend/ # React 前端 (运行在 5173) +│ ├── src/ +│ │ ├── i18n/ # 国际化配置 +│ │ ├── components/ # 组件 +│ │ ├── pages/ # 页面 +│ │ ├── modules/ # 业务模块 (approval等) +│ │ └── router/ # 路由 +│ └── package.json +├── backend/ # PHP 后端 (运行在 8787) +│ ├── app/ +│ │ ├── Controller/ # 控制器 (PascalCase命名) +│ │ ├── Model/ # 模型 (PascalCase命名) +│ │ ├── Service/ # 服务层 +│ │ ├── Util/ # 工具类 +│ │ └── Middleware/ # 中间件 +│ ├── config/ # 配置 +│ ├── lang/ # 语言文件 +│ └── composer.json +├── docker-compose.yml # Docker 编排 +├── nginx/ # OpenResty 配置 +├── frpc/ # frpc 配置 +├── PROJECT.md # 项目详细文档 +└── IMPLEMENTATION_PLAN.md # 实现计划 +``` + +## 环境配置 + +### 端口 +- Frontend (Vite): 5173 +- Backend (Webman): 8787 +- OpenResty: 8080 +- MySQL: 3306 +- Redis: 6379 + +### 数据库 +- 数据库名:x_server_mobile +- 密码:mysql_xEsdPa +- 网络:1panel-network + +### Docker 容器 +- 后端容器:x-server-backend +- 使用 Dockerfile:`backend/Dockerfile` +- PHP 版本:8.5 + +## 关键文件路径 + +### 前端关键文件 +- 入口:`frontend/src/main.tsx` +- 路由:`frontend/src/router/index.tsx` +- 布局:`frontend/src/components/layout/` +- 状态管理:`frontend/src/stores/` +- rem 适配:`frontend/src/utils/rem.ts` + +### 后端关键文件 +- 入口:`backend/start.php` +- 路由:`backend/config/route.php` +- 数据库配置:`backend/config/database.php` +- Redis 配置:`backend/config/redis.php` +- 容器配置:`backend/config/container.php` + +### 重要配置文件 +- Docker 编排:`docker-compose.yml` +- OpenResty 配置:`nginx/x-server-api.conf` +- frpc 配置:`frpc/frpc-dev.ini` + +## 审批流程引擎 + +### 节点类型 +- `single_approve` - 单人审批 +- `and_sign` - 会签(所有人通过) +- `or_sign` - 或签(任一人通过) +- `notify` - 通知节点 + +### 状态 +- pending: 待审批 +- approved: 已通过 +- rejected: 已拒绝 +- cancelled: 已撤销 + +### 关键文件 +- 服务:`backend/app/Service/ApprovalService.php` +- 模型:`backend/app/Model/ApprovalOrder.php` +- 前端模块:`frontend/src/modules/approval/` + +## 飞书集成 + +### 免登接口 +- 回调:`POST /api/v1/auth/feishu-login` + +### 环境变量 +```env +FEISHU_APP_ID=cli_xxxxxxxxxxxxx +FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxx +FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxxxxxxxx +FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxxxxxxxx +``` + +## 常用命令 + +### 前端 +```bash +cd /www/x-server-mobile/frontend +npm install +npm run dev +npm run build +``` + +### 后端 +```bash +cd /www/x-server-mobile/backend +composer install +php start.php start +php start.php stop +``` + +### Docker +```bash +cd /www/x-server-mobile +docker compose up -d +docker compose restart backend +docker logs x-server-backend -f +``` + +## 反向代理配置(开发环境) + +用户已有 frpc 和 OpenResty,配置用于调试飞书登录。 + +1. OpenResty 监听 8080,代理到 8787 +2. frpc 将 8080 暴露到公网 +3. 飞书回调地址配置为公网域名 + +## 已知问题和解决方案 + +### PSR-4 自动加载 +- 目录名必须是 PascalCase(Controller、Model、Service、Util) +- 不能用小写(controller、model、service、util) + +### React 版本 +- 必须使用 React 18.3.1(不能用19) +- @types/react 和 @types/react-dom 也要用 v18 + +### 数据库连接 +- 使用自定义 Db 类 (`backend/app/Util/Db.php`) +- 不使用 `support\Db`(Webman 2.x 没有) + +### Bootstrap 接口 +- 方法签名:`public static function start(?\Workerman\Worker $worker): void` +- 需要添加 `use Workerman\Worker;` + +### Docker 容器重启 +- CMD 使用 `php start.php start`(不要用 `-d`) +- 前台运行,避免容器退出 + +## 语言切换位置 + +1. **首页** - 右上角显示当前语言国旗,点击切换 +2. **个人中心** -> 语言设置 + +## 当前开发状态 + +- ✅ 前端基础框架搭建完成 +- ✅ 后端 API 框架搭建完成 +- ✅ 5语言国际化支持完成 +- ✅ Docker 开发环境配置完成 +- ✅ 审批流程引擎基础框架完成 +- 🚧 出差申请功能开发中 + +## 下一步工作 + +1. 完成出差申请前端页面 +2. 实现审批列表和详情页 +3. 集成飞书免登 +4. 实现公司切换功能 +5. 完善权限控制 diff --git a/DEV_REVERSE_PROXY_SETUP.md b/DEV_REVERSE_PROXY_SETUP.md new file mode 100644 index 0000000..4761143 --- /dev/null +++ b/DEV_REVERSE_PROXY_SETUP.md @@ -0,0 +1,219 @@ +# 开发环境反向代理配置指南 + +本指南用于配置开发环境的 OpenResty 和 frpc,以便将本地 API 暴露到公网调试飞书登录。 + +## 前提条件 + +- 已安装并运行 OpenResty +- 已安装并运行 frpc +- Docker 中运行的后端服务 (localhost:8787) + +## 配置步骤 + +### 1. OpenResty 配置 + +在你的 OpenResty 配置目录中创建以下配置文件: + +```nginx +# /usr/local/openresty/nginx/conf/conf.d/x-server-api.conf + +server { + listen 8080; # 本地开发端口,避免与 80 端口冲突 + server_name api.x-server.local; + + # API 反向代理 + location /api/ { + proxy_pass http://127.0.0.1:8787/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # CORS 配置(飞书请求需要) + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Tenant-Key'; + + if ($request_method = 'OPTIONS') { + return 204; + } + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 健康检查 + location /health { + return 200 "OK\n"; + add_header Content-Type text/plain; + } +} +``` + +重载配置: +```bash +sudo openresty -s reload +# 或 +sudo systemctl reload openresty +``` + +### 2. frpc 配置 + +配置 frpc 将本地 OpenResty 服务暴露到公网: + +```ini +# ~/.frpc.ini 或 /etc/frp/frpc.ini + +[common] +server_addr = your-frp-server.com # 你的 frp 服务器地址 +server_port = 7000 +token = your-token + +[x-server-api] +type = http +local_ip = 127.0.0.1 +local_port = 8080 # OpenResty 监听端口 +custom_domains = api-dev.x-server.your-domain.com # 开发环境域名 + +# 可选:启用加密和压缩 +use_encryption = true +use_compression = true +``` + +启动 frpc: +```bash +frpc -c ~/.frpc.ini +``` + +### 3. 本地 hosts 配置 + +在本地开发时配置 hosts: + +```bash +# /etc/hosts +127.0.0.1 api.x-server.local +``` + +### 4. 飞书应用配置 + +在飞书开放平台配置开发环境回调 URL: + +``` +https://api-dev.x-server.your-domain.com/api/v1/auth/feishu-login +``` + +## 验证配置 + +### 1. 本地测试 +```bash +# 直接访问后端 +curl http://localhost:8787/api/v1/user/info + +# 通过 OpenResty 访问 +curl http://localhost:8080/api/v1/user/info + +# 通过本地域名访问 +curl http://api.x-server.local/api/v1/user/info +``` + +### 2. 公网测试 +```bash +# 通过 frps 域名访问 +curl https://api-dev.x-server.your-domain.com/api/v1/user/info +``` + +### 3. 飞书调试 +1. 在飞书开放平台创建测试应用 +2. 配置回调 URL 为开发环境域名 +3. 使用飞书 APP ID 和 APP SECRET 进行测试 +4. 检查后端日志查看请求详情 + +## 开发与生产环境区分 + +### 开发环境(使用 frpc) +- 域名:`api-dev.x-server.your-domain.com` +- 使用 frpc 暴露本地服务 +- CORS 允许所有来源 +- 详细日志输出 + +### 生产环境(不使用 frpc) +- 域名:`api.x-server.com` +- 直接部署到服务器 +- CORS 严格限制 +- 日志级别调整 + +## 故障排查 + +### OpenResty 无法访问后端 +1. 检查 Docker 容器是否运行:`docker ps` +2. 检查端口是否正确:`docker ps | grep 8787` +3. 检查网络连通性:`curl http://127.0.0.1:8787` + +### frpc 连接失败 +1. 检查 frp 服务器是否可访问 +2. 检查 token 是否正确 +3. 查看 frpc 日志:`frpc -c ~/.frpc.ini` + +### 飞书回调失败 +1. 检查域名 DNS 解析是否指向 frp 服务器 +2. 检查 frp 服务器是否配置了域名白名单 +3. 检查后端日志是否收到请求 +4. 验证 CORS 配置是否正确 + +## 安全建议 + +1. **开发环境使用独立的域名和 token** +2. **定期更新 frp token** +3. **不要在代码中硬编码生产环境配置** +4. **使用环境变量管理敏感信息** +5. **生产环境部署前移除 frpc 配置** + +## 常用命令 + +```bash +# 查看 Docker 容器日志 +docker logs x-server-backend -f + +# 重启后端服务 +docker compose restart backend + +# 重载 OpenResty 配置 +sudo openresty -s reload + +# 查看 frpc 状态 +frpc status -c ~/.frpc.ini + +# 测试 API 连通性 +curl -v http://localhost:8787/api/v1/user/info +``` + +## 环境变量配置 + +创建 `.env.development` 文件用于开发环境: + +```env +# 后端配置 +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=x_server_mobile +DB_USERNAME=root +DB_PASSWORD=mysql_xEsdPa + +# Redis 配置 +REDIS_HOST=redis +REDIS_PORT=6379 + +# 飞书配置(开发环境) +FEISHU_APP_ID=cli_xxxxxxxxxxxxx +FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxx +FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxxxxxxxx +FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxxxxxxxx + +# API 配置 +API_BASE_URL=http://localhost:8787 +PUBLIC_API_URL=https://api-dev.x-server.your-domain.com +``` + +生产环境使用 `.env.production` 文件,移除 frpc 相关配置。 diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..9d2af67 --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -0,0 +1,601 @@ +# 企业内部工具系统 - 实现计划 + +## 项目概述 + +开发一个企业内部工具系统平台,以出差审批为首个功能模块,后续可持续扩展新功能。系统主要支持移动 H5,嵌入飞书应用中使用。 + +### 核心目标 +- **H5 移动端优先**:专注于移动端体验,PC 端后期扩展 +- 从工具台层面设计可扩展的页面结构 +- **审批模块作为独立通用工具**:支持多样化审批场景,业务可复用 +- 良好的飞书集成能力(多公司、多应用架构) + +--- + +## 技术方案 + +### 技术架构 + +#### 前端技术栈(H5 移动端) +| 技术 | 用途 | 说明 | +|------|------|------| +| React 18 | 前端框架 | 配合 TypeScript | +| Ant Design Mobile | UI 组件库 | 移动端组件库 | +| Vant | 补充组件 | 必要时补充使用 | +| Vite | 构建工具 | 快速开发体验 | +| React Router | 路由管理 | 支持多页面应用 | +| Zustand / Jotai | 状态管理 | 轻量级方案 | +| React Query | 数据请求 | 服务端状态管理 | +| PostCSS + pxtorem | 移动端适配 | rem 转换 | +| VConsole | 调试工具 | 移动端调试 | + +#### 后端技术栈 +| 技术 | 用途 | 说明 | +|------|------|------| +| Webman | PHP 框架 | 高性能常驻内存框架 | +| MySQL | 数据库 | 关系型数据库 | +| Redis | 缓存 | 会话、缓存存储 | +| JWT | 认证 | Token 认证机制 | + +### 多租户架构设计 + +**核心概念**: +- 系统**不按公司隔离用户**,用户全局唯一 +- 用户关联所属公司,用于选择对应飞书应用配置 +- 审批流程跨公司,只识别人员 +- 飞书仅提供:系统入口 + 人员识别鉴权 + +**数据模型**: + +```sql +-- 公司表(飞书应用配置) +CREATE TABLE company ( + id BIGINT PRIMARY KEY, + name VARCHAR(100), + feishu_app_id VARCHAR(100), + feishu_app_secret VARCHAR(255), + feishu_tenant_key VARCHAR(100), + status TINYINT DEFAULT 1 +); + +-- 用户表(全局用户) +CREATE TABLE user ( + id BIGINT PRIMARY KEY, + company_id BIGINT, -- 所属公司 + feishu_user_id VARCHAR(100), -- 飞书用户ID + feishu_union_id VARCHAR(100),-- 飞书UnionID(跨应用唯一) + name VARCHAR(50), + email VARCHAR(100), + phone VARCHAR(20), + avatar VARCHAR(255), + status TINYINT DEFAULT 1 +); + +-- 用户与飞书应用关联(支持用户切换不同公司应用) +CREATE TABLE user_feishu_binding ( + id BIGINT PRIMARY KEY, + user_id BIGINT, + company_id BIGINT, + feishu_open_id VARCHAR(100), + created_at TIMESTAMP +); +``` + +### 项目目录结构(前后端分离) + +``` +x-server-mobile/ # 项目根目录 +├── frontend/ # 前端项目(React + Vite) +│ ├── src/ +│ │ ├── components/ # 通用组件 +│ │ │ ├── approval/ # 审批组件(可复用) +│ │ │ ├── layout/ # 布局组件 +│ │ │ └── common/ # 通用组件 +│ │ ├── pages/ # 页面 +│ │ │ ├── dashboard/ # 工具台首页 +│ │ │ ├── business-trip/ # 出差审批 +│ │ │ └── profile/ # 个人中心 +│ │ ├── hooks/ # 自定义 Hooks +│ │ ├── services/ # API 服务 +│ │ ├── stores/ # 状态管理 +│ │ ├── utils/ # 工具函数 +│ │ ├── types/ # TypeScript 类型 +│ │ └── config/ # 配置文件 +│ ├── public/ +│ ├── package.json +│ └── vite.config.ts +│ +└── backend/ # 后端项目(Webman) + ├── app/ + │ ├── controller/ + │ ├── model/ + │ ├── service/ + │ ├── middleware/ + │ └── util/ + ├── config/ + ├── routes/ + ├── support/ + ├── composer.json + └── start.php +``` + +### 后端项目结构(Webman) +``` +backend/ +├── app/ +│ ├── controller/ +│ │ ├── ApprovalController.php # 审批控制器 +│ │ ├── BusinessTripController.php # 出差审批控制器 +│ │ ├── UserController.php # 用户控制器 +│ │ ├── CompanyController.php # 公司管理控制器 +│ │ └── BaseController.php # 基础控制器 +│ ├── model/ +│ │ ├── Approval.php # 审批模型 +│ │ ├── ApprovalNode.php # 审批节点模型 +│ │ ├── BusinessTrip.php # 出差模型 +│ │ ├── User.php # 用户模型 +│ │ ├── Company.php # 公司模型 +│ │ └── UserFeishuBinding.php # 用户飞书绑定模型 +│ ├── service/ +│ │ ├── ApprovalService.php # 审批服务 +│ │ ├── FeishuService.php # 飞书服务(多应用) +│ │ ├── FeishuAppManager.php # 飞书应用配置管理 +│ │ ├── UserService.php # 用户服务 +│ │ └── CompanyService.php # 公司服务 +│ ├── middleware/ +│ │ ├── AuthMiddleware.php # 认证中间件 +│ │ ├── CompanyResolveMiddleware.php # 公司识别中间件 +│ │ └── CorsMiddleware.php # 跨域中间件 +│ └── util/ +│ ├── JwtUtil.php # JWT工具 +│ └── Response.php # 响应封装 +├── config/ +│ ├── route.php # 路由配置 +│ ├── database.php # 数据库配置 +│ ├── redis.php # Redis配置 +│ └── feishu.php # 飞书应用配置模板 +├── routes/ +│ └── api.php # API路由定义 +└── support/ + └── Bootstrap.php # 启动文件 +``` + +### API 接口设计 + +#### 通用 API 规范 +``` +基础URL: /api/v1 +响应格式: +{ + "code": 0, // 0表示成功 + "message": "success", + "data": {} +} +``` + +#### 审批相关接口 +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | /approval/list | 获取审批列表 | +| GET | /approval/detail/:id | 获取审批详情 | +| POST | /approval/submit | 提交审批申请 | +| POST | /approval/approve/:id | 审批通过 | +| POST | /approval/reject/:id | 审批拒绝 | +| POST | /approval/cancel/:id | 撤销申请 | +| GET | /approval/flow/:type | 获取审批流程配置 | + +#### 出差审批接口 +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | /business-trip/list | 获取出差列表 | +| GET | /business-trip/detail/:id | 获取出差详情 | +| POST | /business-trip/apply | 提交出差申请 | +| PUT | /business-trip/:id | 更新出差申请(草稿状态) | + +#### 用户认证接口(多飞书应用) +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | /auth/login | 用户登录 | +| POST | /auth/feishu-login | 飞书免登(自动识别公司应用) | +| GET | /user/info | 获取用户信息 | +| GET | /user/menu | 获取用户菜单权限 | +| GET | /user/companies | 获取用户可切换的公司列表 | +| POST | /user/switch-company | 切换当前公司 | + +#### 飞书应用管理接口(管理端) +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | /admin/company/list | 获取公司列表 | +| POST | /admin/company/add | 添加公司飞书应用配置 | +| PUT | /admin/company/:id | 更新公司飞书配置 | +| DELETE | /admin/company/:id | 删除公司配置 | + +--- + +## 功能模块设计 + +### 1. 工具台架构(Dashboard - H5 移动端) + +**设计理念**:工具台作为统一入口,支持模块化扩展,专注于移动端体验 + +``` +工具台首页(H5) +├── 顶部导航栏 +│ ├── Logo + 系统名称 +│ ├── 搜索框(全局搜索) +│ ├── 通知图标 +│ └── 用户头像 +├── 主内容区 +│ ├── 待办事项卡片(横向滚动) +│ ├── 快捷入口(九宫格/宫格) +│ ├── 数据统计卡片 +│ └── 最新动态列表 +└── 底部导航栏(TabBar) + ├── 首页 + ├── 应用(宫格展开所有工具) + ├── 消息 + └── 我的 +``` + +**移动端适配要点**: +- 使用 TabBar 替代侧边栏(符合移动端习惯) +- 750px 设计稿 → rem 自适应转换 +- 触摸手势支持(下拉刷新、上拉加载) +- 安全区域适配(刘海屏、底部横条) + +**关键文件**: +- `src/pages/dashboard/index.tsx` - 工具台主页 +- `src/components/layout/MobileLayout.tsx` - 移动端主布局 +- `src/components/layout/TabBar.tsx` - 底部导航栏 +- `src/components/layout/Header.tsx` - 顶部栏 + +### 2. 通用审批模块设计(独立工具) + +**设计理念**:审批模块作为独立的通用工具,与具体业务解耦,支持多样化的审批场景 + +**核心特性**: +- ✅ 业务无关:不绑定特定业务表单和流程 +- ✅ 流程灵活:支持单级、多级、会签、或签、抄送等任意组合 +- ✅ 动态配置:审批流程可动态配置和调整 +- ✅ 易于集成:任何业务都可快速接入审批模块 + +**核心数据模型**: + +```typescript +// 审批流程节点类型 +enum ApprovalNodeType { + APPROVER = 'approver', // 审批人(单人) + AND_APPROVE = 'and_approve', // 会签(需所有人通过) + OR_APPROVE = 'or_approve', // 或签(任一人通过) + NOTIFY = 'notify' // 抄送(通知用) +} + +// 审批单状态 +enum ApprovalStatus { + DRAFT = 'draft', // 草稿 + PENDING = 'pending', // 审批中 + APPROVED = 'approved', // 已通过 + REJECTED = 'rejected', // 已拒绝 + CANCELLED = 'cancelled' // 已撤销 +} + +// 审批流程配置(业务无关) +interface ApprovalFlow { + id: string; + businessType: string; // 业务类型标识(如:business-trip, purchase) + name: string; + nodes: ApprovalNode[]; // 流程节点 +} + +// 审批节点 +interface ApprovalNode { + id: string; + type: ApprovalNodeType; + approvers: User[]; // 审批人列表 + order: number; // 节点顺序 + conditions?: any; // 条件判断(可选,用于条件分支) +} + +// 审批单(业务通用) +interface ApprovalOrder { + id: string; + businessType: string; // 业务类型 + businessId: string; // 业务单据ID + title: string; // 审批标题 + applicant: User; // 申请人 + status: ApprovalStatus; + currentNodes: string[]; // 当前待审批节点ID + flowId: string; // 使用的流程ID + formData: any; // 业务表单数据(JSON存储) + attachments: Attachment[]; // 附件 + timeline: ApprovalTimeline[]; // 审批轨迹 +} +``` + +**审批模块架构**: + +``` +src/modules/approval/ # 审批模块(独立) +├── components/ # UI组件 +│ ├── ApprovalFlow.tsx # 流程可视化 +│ ├── ApprovalDetail.tsx # 审批详情 +│ ├── ApprovalList.tsx # 审批列表 +│ └── ApprovalAction.tsx # 审批操作 +├── hooks/ # 自定义Hooks +│ ├── useApprovalSubmit.ts # 提交审批 +│ ├── useApprovalAction.ts # 审批操作 +│ └── useApprovalList.ts # 获取列表 +├── services/ # API服务 +│ └── approval.ts +├── types/ # 类型定义 +│ └── index.ts +└── utils/ # 工具函数 + ├── flowParser.ts # 流程解析 + └── statusCalculator.ts # 状态计算 +``` + +**业务接入示例(出差审批)**: + +```typescript +// 业务模块只需要调用审批模块的Hook +import { useApprovalSubmit } from '@/modules/approval/hooks' + +function BusinessTripApply() { + const { submitApproval } = useApprovalSubmit('business-trip') + + const handleSubmit = async (formData: BusinessTripFormData) => { + // 1. 先保存业务数据 + const businessTripId = await saveBusinessTrip(formData) + + // 2. 提交审批(传入业务类型和业务ID) + await submitApproval({ + businessId: businessTripId, + title: `出差申请 - ${formData.destination}`, + formData: formData, + flowType: 'default' // 使用的流程模板 + }) + } +} +``` + +### 3. 出差审批模块 + +**页面结构**: +``` +出差审批 +├── 申请列表页 +│ ├── 我发起的 +│ ├── 待我审批 +│ ├── 我已审批 +│ └── 抄送我的 +├── 申请表单页 +│ ├── 基本信息(出差事由、时间、地点) +│ ├── 行程明细 +│ ├── 费用预算 +│ └── 附件上传 +└── 审批详情页 + ├── 表单内容展示 + ├── 流程进度可视化 + ├── 审批历史 + └── 操作按钮 +``` + +**关键文件**: +- `src/pages/business-trip/list.tsx` - 列表页 +- `src/pages/business-trip/apply.tsx` - 申请表单 +- `src/pages/business-trip/detail.tsx` - 详情页 +- `src/pages/business-trip/types.ts` - 出差相关类型 + +### 4. 飞书集成(多应用架构) + +**集成要点**: + +#### 后端 - 多飞书应用管理 +```php +// 飞书应用管理器 +class FeishuAppManager +{ + // 根据飞书租户Key获取公司配置 + public function getCompanyByTenantKey(string $tenantKey): Company + { + // 从数据库查询对应的飞书应用配置 + } + + // 获取指定公司的飞书 access_token + public function getAccessToken(int $companyId): string + { + // 缓存 access_token,过期自动刷新 + } + + // 根据用户ID获取其所属公司的飞书配置 + public function getUserFeishuConfig(int $userId): array + { + // 返回用户所属公司的飞书 AppID、AppSecret 等 + } +} +``` + +#### 前端 - 飞书 SDK 封装 +```typescript +// src/utils/feishu.ts +export const FeishuSDK = { + // 获取免登授权码 + getAuthCode: () => { /* ... */ }, + + // 判断是否在飞书环境 + isFeishuEnv: () => { /* ... */ }, + + // 获取当前租户信息(用于后端识别公司) + getTenantContext: () => { + // 从飞书容器获取 tenant_key 或其他标识 + } +} +``` + +#### 飞书免登流程(多应用) +``` +1. 用户在飞书中打开应用 +2. 前端获取 auth_code 和 tenant_key +3. 调用后端 /auth/feishu-login +4. 后端根据 tenant_key 查询对应公司配置 +5. 使用该公司的 AppSecret 换取用户信息 +6. 生成 JWT Token(包含 user_id 和 company_id) +7. 返回登录信息 +``` + +--- + +## 实施步骤 + +### 阶段一:项目初始化 +**前端:** +1. 初始化 Vite + React + TypeScript 项目 +2. 安装 Ant Design 及相关依赖 +3. 配置路由、状态管理、请求库(axios + React Query) +4. 配置 Tailwind CSS + +**后端:** +1. 安装 Webman 框架 +2. 配置 MySQL、Redis 连接 +3. 配置 JWT 认证 +4. 设置 CORS 中间件 +5. **创建多租户数据表(company, user, user_feishu_binding)** + +### 阶段二:工具台框架(H5) +**前端:** +1. 实现 MobileLayout 移动端布局组件 +2. 实现 TabBar 底部导航栏 +3. 实现 Header 组件 +4. 创建工具台首页 Dashboard(H5 适配) +5. 配置 rem 移动端适配 + +**后端:** +1. 实现 /auth/login 登录接口 +2. **实现 /auth/feishu-login 飞书免登(多应用识别)** +3. 实现 /user/info 用户信息接口 +4. 实现 /user/menu 菜单权限接口 +5. **实现 FeishuAppManager 服务(多应用配置管理)** +6. **实现 CompanyResolveMiddleware 中间件** + +### 阶段三:独立审批模块(通用工具) +**前端:** +1. 创建 `src/modules/approval/` 独立模块目录 +2. 定义审批相关 TypeScript 类型(业务无关) +3. 实现 ApprovalFlow 流程可视化组件 +4. 实现 ApprovalDetail 详情页(通用) +5. 实现 ApprovalList 列表页(通用) +6. 实现 ApprovalAction 操作组件 +7. **封装审批 Hooks(useApprovalSubmit, useApprovalAction)** +8. **提供业务接入示例** + +**后端:** +1. 设计审批流程数据表(approval_flow, approval_node) +2. 设计审批单数据表(approval_order, approval_record) +3. 实现审批服务层逻辑(单级、多级、会签、或签) +4. **确保业务无关性,通过 businessType + businessId 关联业务** +5. 实现审批相关 API 接口 + +### 阶段四:出差审批业务(作为审批模块的使用示例) +**前端:** +1. 创建出差申请表单页面 +2. **调用审批模块的 useApprovalSubmit Hook** +3. 创建出差审批列表(复用审批模块组件) +4. 创建出差审批详情(复用审批模块组件 + 业务表单展示) + +**后端:** +1. 设计出差数据表(business_trip, business_trip_itinerary) +2. 实现出差业务逻辑(与审批解耦) +3. 实现出差相关 API 接口 +4. **配置出差审批的默认流程模板** + +### 阶段五:飞书集成与 H5 适配(多应用架构) +**前端:** +1. 集成飞书 JSAPI SDK +2. 实现飞书免登逻辑(获取 tenant_key) +3. **H5 移动端样式优化** +4. **配置 pxtorem(750px 设计稿适配)** +5. **安全区域适配(刘海屏、底部横条)** +6. 集成 VConsole 调试工具 + +**后端:** +1. **实现 FeishuService(支持多应用配置)** +2. **实现飞书应用配置管理接口(增删改查)** +3. **实现用户切换公司功能** +4. **实现飞书消息推送(根据用户所属公司推送)** + +--- + +## 待确认问题 + +1. **飞书应用配置**:是否有飞书应用的 App ID、App Secret? +2. **权限设计**:是否需要 RBAC 权限系统? +3. **数据 Mock**:开发阶段是否需要 Mock 数据? +4. **部署环境**:开发/测试/生产环境配置? + +--- + +## 关键文件清单 + +### 前端核心文件(H5) +| 文件路径 | 说明 | +|---------|------| +| `src/App.tsx` | 应用入口 | +| `src/main.tsx` | React 挂载点 | +| `src/router/index.tsx` | 路由配置 | +| `src/components/layout/MobileLayout.tsx` | 移动端主布局 | +| `src/components/layout/TabBar.tsx` | 底部导航栏 | +| `src/components/layout/Header.tsx` | 顶部栏 | +| `src/pages/dashboard/index.tsx` | 工具台首页 | +| `src/modules/approval/` | **审批模块(独立)** | +| `src/pages/business-trip/` | 出差审批业务 | +| `src/services/api.ts` | API 基础配置 | +| `src/utils/feishu.ts` | 飞书 SDK 封装 | +| `src/config/rem.ts` | rem 转换配置 | +| `package.json` | 项目依赖 | +| `vite.config.ts` | Vite 配置 | +| `tsconfig.json` | TypeScript 配置 | +| `postcss.config.js` | PostCSS 配置(pxtorem) | + +### 后端核心文件 +| 文件路径 | 说明 | +|---------|------| +| `app/controller/BaseController.php` | 基础控制器 | +| `app/controller/ApprovalController.php` | 审批控制器 | +| `app/controller/BusinessTripController.php` | 出差审批控制器 | +| `app/controller/UserController.php` | 用户控制器 | +| `app/model/Approval.php` | 审批模型 | +| `app/model/ApprovalNode.php` | 审批节点模型 | +| `app/service/ApprovalService.php` | 审批服务 | +| `app/service/FeishuService.php` | 飞书服务 | +| `app/middleware/AuthMiddleware.php` | 认证中间件 | +| `config/route.php` | 路由配置 | +| `config/database.php` | 数据库配置 | +| `routes/api.php` | API 路由定义 | + +--- + +## 验证计划 + +### 前端验证(H5) +1. **运行项目**:`npm run dev` 确保项目启动 +2. **路由测试**:访问各个页面确保路由正常 +3. **审批模块测试**:验证审批组件的通用性和可复用性 +4. **H5 适配测试**: + - 测试不同屏幕尺寸(375px - 768px) + - 测试刘海屏和底部横条适配 + - 测试触摸手势交互 +5. **飞书环境测试**: + - 在飞书中测试免登 + - 在飞书中测试 H5 适配 + - 测试多公司应用切换 +6. **真机测试**:在真实移动设备上测试 + +### 后端验证 +1. **启动 Webman**:`php start.php start` 确保服务启动 +2. **API 测试**:使用 Postman/Apifox 测试各接口 +3. **数据库测试**:验证数据表创建和 CRUD 操作 +4. **认证测试**:验证 JWT 认证流程 +5. **多租户测试**: + - 测试多公司飞书应用配置 + - 测试跨公司审批流程 +6. **飞书集成测试**:验证飞书免登和消息推送 diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..616b066 --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,356 @@ +# X-Server Mobile 企业工具系统 + +## 项目概述 + +一个基于飞书的企业内部H5移动工具平台,采用多租户架构,支持审批流程引擎和多种业务应用。 + +**技术栈:** +- 前端:React 18 + TypeScript + Vite + Ant Design Mobile +- 后端:PHP 8.5 + Webman 2.x + MySQL + Redis +- 部署:Docker + OpenResty + frpc (开发环境) + +**支持语言:** +- 🇨🇳 简体中文 (zh) +- 🇬🇧 English (en) +- 🇷🇺 Русский (ru) +- 🇪🇸 Español (es) +- 🇫🇷 Français (fr) + +## 国际化 (i18n) + +### 前端国际化 + +**位置:** `frontend/src/i18n/` + +**使用方式:** +```tsx +import { useTranslation } from 'react-i18next' + +function MyComponent() { + const { t } = useTranslation() + return
{t('common.confirm')}
+} +``` + +**切换语言:** +```tsx +import { showLanguageSwitcher } from '@/components/LanguageSwitcher' + +// 显示语言选择弹窗 +showLanguageSwitcher() +``` + +**语言存储:** 浏览器 localStorage,键名为 `i18nextLng` + +### 后端国际化 + +**位置:** `backend/lang/` + +**使用方式:** +```php +use App\Util\Lang; + +// 获取翻译 +$message = Lang::get('common.confirm'); + +// 带参数的翻译 +$message = Lang::get('approval.status', ['status' => 'pending']); + +// 检查翻译是否存在 +if (Lang::has('common.submit')) { + // ... +} +``` + +**设置语言:** +```php +// 从请求检测语言 +$locale = Lang::detectFromRequest(); +Lang::setLocale($locale); +``` + +## 项目关键特性 + +### 1. 多租户架构 + +- 用户全局唯一,关联多个公司 +- 租户切换功能 +- 公司级数据隔离 + +### 2. 审批流程引擎 + +**支持节点类型:** +- 单人审批 (single_approve) +- 会签 (and_sign) - 所有人通过 +- 或签 (or_sign) - 任一人通过 +- 通知节点 (notify) + +**审批状态:** +- pending: 待审批 +- approved: 已通过 +- rejected: 已拒绝 +- cancelled: 已撤销 + +**关键文件:** +- `backend/app/Service/ApprovalService.php` - 审批核心逻辑 +- `backend/app/Model/ApprovalOrder.php` - 审批单模型 +- `frontend/src/modules/approval/` - 前端审批模块 + +### 3. 飞书集成 + +**飞书免登:** +- 回调地址:`/api/v1/auth/feishu-login` +- 配置项: + - `FEISHU_APP_ID` - 飞书应用 ID + - `FEISHU_APP_SECRET` - 飞书应用密钥 + - `FEISHU_ENCRYPT_KEY` - 加密密钥 + - `FEISHU_VERIFICATION_TOKEN` - 验证令牌 + +### 4. 数据库设计 + +**核心表:** +- `users` - 用户表(全局唯一) +- `companies` - 公司表 +- `user_company` - 用户公司关联表 +- `approval_flows` - 审批流程定义 +- `approval_nodes` - 审批节点 +- `approval_orders` - 审批单 +- `approval_records` - 审批记录 +- `business_trips` - 出差申请 +- `business_trip_items` - 出差明细 + +## 目录结构 + +``` +x-server-mobile/ +├── frontend/ # React 前端 +│ ├── src/ +│ │ ├── i18n/ # 国际化配置 +│ │ │ ├── locales/ # 翻译文件 +│ │ │ └── index.ts +│ │ ├── components/ # 通用组件 +│ │ │ ├── layout/ # 布局组件 +│ │ │ └── LanguageSwitcher/ +│ │ ├── modules/ # 业务模块 +│ │ │ └── approval/ # 审批模块 +│ │ ├── pages/ # 页面组件 +│ │ ├── stores/ # 状态管理 +│ │ ├── router/ # 路由配置 +│ │ └── utils/ # 工具函数 +│ └── package.json +├── backend/ # PHP 后端 +│ ├── app/ +│ │ ├── Controller/ # 控制器 +│ │ ├── Model/ # 数据模型 +│ │ ├── Service/ # 业务逻辑 +│ │ ├── Util/ # 工具类 +│ │ │ ├── Lang.php # 国际化工具 +│ │ │ └── Db.php # 数据库连接 +│ │ └── Middleware/ # 中间件 +│ ├── config/ # 配置文件 +│ ├── lang/ # 语言文件 +│ ├── database/migrations/ # 数据库迁移 +│ ├── composer.json +│ └── Dockerfile +├── docker-compose.yml # Docker 编排 +├── nginx/ # OpenResty 配置 +├── frpc/ # frpc 配置 +└── IMPLEMENTATION_PLAN.md # 实现计划 +``` + +## 开发环境设置 + +### 端口分配 + +| 服务 | 端口 | +|------|------| +| Frontend (Vite) | 5173 | +| Backend (Webman) | 8787 | +| OpenResty | 8080 | +| MySQL | 3306 | +| Redis | 6379 | + +### 环境变量 + +**前端 (frontend/.env):** +```env +VITE_API_BASE_URL=http://localhost:8787/api/v1 +``` + +**后端 (backend/.env):** +```env +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=x_server_mobile +DB_USERNAME=root +DB_PASSWORD=mysql_xEsdPa + +REDIS_HOST=redis +REDIS_PORT=6379 + +APP_DEBUG=true + +# 飞书配置 +FEISHU_APP_ID=cli_xxxxxxxxxxxxx +FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxx +FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxxxxxxxx +FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxxxxxxxx +``` + +### 反向代理配置(开发环境) + +**frpc 配置:** `frpc/frpc-dev.ini` +```ini +[common] +server_addr = your-frp-server.com +server_port = 7000 +token = your-frp-token + +[x-server-api-dev] +type = http +local_ip = 127.0.0.1 +local_port = 8080 +custom_domains = api-dev.x-server.your-domain.com +``` + +**OpenResty 配置:** `nginx/x-server-api.conf` +```nginx +server { + listen 8080; + server_name api.x-server.local; + + location /api/ { + proxy_pass http://127.0.0.1:8787/api/; + + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Tenant-Key'; + } +} +``` + +## API 接口 + +### 认证接口 + +- `POST /api/v1/auth/login` - 用户名密码登录 +- `POST /api/v1/auth/feishu-login` - 飞书免登 + +### 用户接口 + +- `GET /api/v1/user/info` - 获取用户信息 +- `GET /api/v1/user/menu` - 获取用户菜单 +- `GET /api/v1/user/companies` - 获取公司列表 +- `POST /api/v1/user/switch-company` - 切换公司 + +### 审批接口 + +- `GET /api/v1/approval/list` - 审批列表 +- `GET /api/v1/approval/detail/{id}` - 审批详情 +- `POST /api/v1/approval/submit` - 提交审批 +- `POST /api/v1/approval/approve/{id}` - 审批通过 +- `POST /api/v1/approval/reject/{id}` - 审批拒绝 +- `POST /api/v1/approval/cancel/{id}` - 撤销申请 + +### 出差接口 + +- `GET /api/v1/business-trip/list` - 出差列表 +- `GET /api/v1/business-trip/detail/{id}` - 出差详情 +- `POST /api/v1/business-trip/apply` - 申请出差 +- `PUT /api/v1/business-trip/{id}` - 更新申请 + +## 开发指南 + +### 前端开发 + +```bash +cd frontend +npm install # 安装依赖 +npm run dev # 启动开发服务器 +npm run build # 构建生产版本 +``` + +### 后端开发 + +```bash +cd backend +composer install # 安装依赖 +php start.php start # 启动 Webman +php start.php stop # 停止 Webman +``` + +### Docker 开发 + +```bash +# 启动所有服务 +docker compose up -d + +# 仅启动后端 +docker compose up -d backend + +# 查看日志 +docker logs x-server-backend -f + +# 重启服务 +docker compose restart backend +``` + +## 常见问题 + +### Q: 如何添加新的翻译文本? + +**前端:** 在 `frontend/src/i18n/locales/` 对应语言的 JSON 文件中添加 +```json +{ + "myModule": { + "myKey": "翻译文本" + } +} +``` + +**后端:** 在 `backend/lang/` 对应语言的 PHP 文件中添加 +```php +return [ + 'myModule' => [ + 'myKey' => '翻译文本', + ], +]; +``` + +### Q: 如何添加新的应用模块? + +1. 在 `frontend/src/pages/` 创建新页面 +2. 在 `frontend/src/router/index.tsx` 添加路由 +3. 在 `backend/app/Controller/` 创建控制器 +4. 在 `backend/config/route.php` 添加 API 路由 + +### Q: 数据库连接失败怎么办? + +1. 检查 MySQL 容器是否运行:`docker ps | grep mysql` +2. 检查数据库配置:`backend/.env` +3. 检查网络连接:`docker network ls | grep 1panel-network` + +### Q: 前端页面白屏? + +1. 检查浏览器控制台错误 +2. 检查 React 版本:确保是 18.3.1 +3. 检查路由配置 +4. 尝试清除缓存:`rm -rf frontend/node_modules/.vite` + +## 安全建议 + +1. **生产环境务必使用 HTTPS** +2. **定期更新依赖包** +3. **配置防火墙规则** +4. **使用强密码** +5. **定期备份数据库** +6. **不要在代码中硬编码敏感信息** + +## 更新日志 + +### 2024-02-24 +- ✅ 升级 PHP 到 8.5 +- ✅ 添加 5 语言国际化支持 +- ✅ 配置 Docker 开发环境 +- ✅ 实现审批流程引擎基础框架 +- ✅ 完成前端基础页面和路由 diff --git a/REVERSE_PROXY_SETUP.md b/REVERSE_PROXY_SETUP.md new file mode 100644 index 0000000..c43e235 --- /dev/null +++ b/REVERSE_PROXY_SETUP.md @@ -0,0 +1,142 @@ +# 反向代理配置指南 + +本指南用于配置 OpenResty 和 frpc 以将 API 暴露到公网,便于调试飞书登录。 + +## 环境要求 + +- 已安装 OpenResty 或 Nginx +- 已安装 frpc +- 拥有可用的 frp 服务器 +- 拥有可配置的域名 + +## 安装步骤 + +### 1. 安装 OpenResty (Ubuntu/Debian) + +```bash +# 导入 GPG 密钥 +wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - + +# 添加 OpenResty 仓库 +sudo apt-get -y install -y wget gnupg ca-certificates +wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add - +echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list + +# 安装 OpenResty +sudo apt-get update +sudo apt-get install -y openresty + +# 启动服务 +sudo systemctl start openresty +sudo systemctl enable openresty +``` + +### 2. 配置 OpenResty + +将 `nginx/x-server-api.conf` 复制到 OpenResty 配置目录: + +```bash +sudo cp nginx/x-server-api.conf /usr/local/openresty/nginx/conf/conf.d/ +sudo mkdir -p /usr/local/openresty/nginx/logs +sudo openresty -s reload +``` + +### 3. 安装 frpc + +```bash +# 下载 frpc(根据你的系统选择版本) +wget https://github.com/fatedier/frp/releases/download/v0.52.3/frp_0.52.3_linux_amd64.tar.gz + +# 解压 +tar xzf frp_0.52.3_linux_amd64.tar.gz +cd frp_0.52.3_linux_amd64 + +# 复制到系统路径 +sudo cp frpc /usr/local/bin/ +sudo chmod +x /usr/local/bin/frpc +``` + +### 4. 配置 frpc + +编辑 `frpc/frpc.ini`,填入你的 frp 服务器信息: + +```ini +[common] +server_addr = your-frp-server.com # 你的 frp 服务器地址 +server_port = 7000 # frp 服务器端口 +token = your-frp-token # 认证令牌 + +[x-server-api] +type = http +local_ip = 127.0.0.1 +local_port = 8787 +custom_domains = api.x-server.your-domain.com # 你的公网域名 +``` + +### 5. 启动 frpc + +```bash +# 前台运行(测试) +frpc -c frpc/frpc.ini + +# 后台运行(生产) +nohup frpc -c frpc/frpc.ini > /dev/null 2>&1 & +``` + +### 6. 配置 DNS + +将你的域名 `api.x-server.your-domain.com` 的 A 记录指向你的 frp 服务器 IP。 + +### 7. 配置飞书应用 + +在飞书开放平台配置回调 URL: + +``` +https://api.x-server.your-domain.com/api/v1/auth/feishu-login +``` + +## 验证配置 + +### 测试本地 API + +```bash +curl http://localhost:8787/api/v1/user/info +``` + +### 测试反向代理 + +```bash +curl http://api.x-server.local/api/v1/user/info +``` + +### 测试公网访问 + +```bash +curl https://api.x-server.your-domain.com/api/v1/user/info +``` + +## 常见问题 + +### frpc 连接失败 + +1. 检查 frp 服务器是否在线 +2. 检查 token 是否正确 +3. 检查端口是否被防火墙阻止 + +### 域名无法访问 + +1. 检查 DNS 解析是否生效 +2. 检查 frp 服务器是否配置了该域名 +3. 检查 frpc 日志 + +### CORS 错误 + +如果前端调用 API 时出现 CORS 错误,检查 nginx 配置中的 CORS 头部设置。 + +## 安全建议 + +1. **生产环境务必使用 HTTPS** +2. 配置 IP 白名单限制访问 +3. 使用强密码和 token +4. 定期更新 frp 和 OpenResty 版本 +5. 配置防火墙规则 diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..10866ca --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,23 @@ +# 应用配置 +APP_DEBUG=true +APP_ENV=local + +# 数据库配置 +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=x_server_mobile +DB_USERNAME=root +DB_PASSWORD= + +# Redis 配置 +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 +REDIS_PASSWORD= +REDIS_DATABASE=0 + +# JWT 配置 +JWT_SECRET=your-secret-key-change-in-production + +# 飞书配置 +FEISHU_APP_ID= +FEISHU_APP_SECRET= diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..9c3e8ab --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,34 @@ +FROM php:8.5-cli + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + unzip \ + git \ + libzip-dev \ + libpng-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Install PHP extensions +RUN docker-php-ext-install zip pdo pdo_mysql bcmath pcntl + +# Install Composer +COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer + +# Set working directory +WORKDIR /app + +# Copy composer files +COPY composer.json composer.lock* ./ + +# Install dependencies +RUN composer install --no-interaction --optimize-autoloader + +# Copy application files +COPY . . + +# Expose port +EXPOSE 8787 + +# Start webman in foreground mode +CMD ["php", "start.php", "start"] diff --git a/backend/app/Controller/ApprovalController.php b/backend/app/Controller/ApprovalController.php new file mode 100644 index 0000000..4ac30e0 --- /dev/null +++ b/backend/app/Controller/ApprovalController.php @@ -0,0 +1,134 @@ +approvalService = new ApprovalService(); + } + + /** + * 获取审批列表 + */ + public function list(Request $request): Response + { + $type = $request->input('type', 'my'); + $page = (int) $request->input('page', 1); + $pageSize = (int) $request->input('page_size', 20); + + $userId = $this->getUserId($request); + + if (!$userId) { + return $this->error('用户未登录', 401); + } + + $result = $this->approvalService->getList($userId, $type, $page, $pageSize); + + return $this->success($result); + } + + /** + * 获取审批详情 + */ + public function detail(Request $request): Response + { + $id = $request->route('id'); + + $order = \App\Model\ApprovalOrder::with(['flow', 'applicant', 'records.approver']) + ->findOrFail($id); + + return $this->success($order); + } + + /** + * 提交审批 + */ + public function submit(Request $request): Response + { + $data = $request->all(); + $userId = $this->getUserId($request); + + if (!$userId) { + return $this->error('用户未登录', 401); + } + + $data['applicant_id'] = $userId; + + try { + $order = $this->approvalService->submit($data); + return $this->success($order); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 审批通过 + */ + public function approve(Request $request): Response + { + $id = $request->route('id'); + $comment = $request->post('comment'); + $userId = $this->getUserId($request); + + if (!$userId) { + return $this->error('用户未登录', 401); + } + + try { + $order = $this->approvalService->approve((int) $id, $userId, $comment); + return $this->success($order); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 审批拒绝 + */ + public function reject(Request $request): Response + { + $id = $request->route('id'); + $comment = $request->post('comment'); + $userId = $this->getUserId($request); + + if (!$userId) { + return $this->error('用户未登录', 401); + } + + try { + $order = $this->approvalService->reject((int) $id, $userId, $comment); + return $this->success($order); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 撤销申请 + */ + public function cancel(Request $request): Response + { + $id = $request->route('id'); + $userId = $this->getUserId($request); + + if (!$userId) { + return $this->error('用户未登录', 401); + } + + try { + $order = $this->approvalService->cancel((int) $id, $userId); + return $this->success($order); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + } +} diff --git a/backend/app/Controller/AuthController.php b/backend/app/Controller/AuthController.php new file mode 100644 index 0000000..cbaa1b5 --- /dev/null +++ b/backend/app/Controller/AuthController.php @@ -0,0 +1,59 @@ +post('username'); + $password = $request->post('password'); + + // TODO: 实现真实的登录逻辑 + if (!$username || !$password) { + return $this->error('用户名或密码不能空'); + } + + // 模拟用户数据 + $user = [ + 'user_id' => 1, + 'username' => $username, + 'name' => '测试用户', + 'company_id' => 1, + ]; + + $token = JwtUtil::encode($user); + + return $this->success([ + 'token' => $token, + 'user' => $user, + ]); + } + + /** + * 飞书免登 + */ + public function feishuLogin(Request $request): Response + { + $authCode = $request->post('auth_code'); + $tenantKey = $request->post('tenant_key'); + + // TODO: 实现飞书免登逻辑 + // 1. 根据 tenant_key 获取公司配置 + // 2. 使用 auth_code 换取用户信息 + // 3. 创建或更新用户 + // 4. 生成 JWT Token + + return $this->success([ + 'token' => JwtUtil::encode(['user_id' => 1, 'company_id' => 1]), + 'user' => ['id' => 1, 'name' => '飞书用户', 'company_id' => 1], + ]); + } +} diff --git a/backend/app/Controller/BaseController.php b/backend/app/Controller/BaseController.php new file mode 100644 index 0000000..e092f9b --- /dev/null +++ b/backend/app/Controller/BaseController.php @@ -0,0 +1,41 @@ + 0, + 'message' => $message, + 'data' => $data, + ]); + } + + /** + * 错误响应 + */ + protected function error(string $message = 'error', int $code = 1, $data = null): Response + { + return json([ + 'code' => $code, + 'message' => $message, + 'data' => $data, + ]); + } + + /** + * 获取当前用户ID + */ + protected function getUserId(Request $request): ?int + { + return $request->userId ?? null; + } +} diff --git a/backend/app/Controller/BusinessTripController.php b/backend/app/Controller/BusinessTripController.php new file mode 100644 index 0000000..e138192 --- /dev/null +++ b/backend/app/Controller/BusinessTripController.php @@ -0,0 +1,60 @@ +input('type', 'my'); + + // TODO: 从数据库获取出差列表 + return $this->success([ + 'list' => [], + 'total' => 0, + ]); + } + + /** + * 获取出差详情 + */ + public function detail(Request $request): Response + { + $id = $request->route('id'); + + // TODO: 从数据库获取出差详情 + return $this->success([ + 'id' => $id, + 'title' => '测试出差', + ]); + } + + /** + * 提交出差申请 + */ + public function apply(Request $request): Response + { + $data = $request->all(); + + // TODO: 保存出差数据并创建审批流程 + return $this->success(); + } + + /** + * 更新出差申请 + */ + public function update(Request $request): Response + { + $id = $request->route('id'); + $data = $request->all(); + + // TODO: 更新出差数据 + return $this->success(); + } +} diff --git a/backend/app/Controller/UserController.php b/backend/app/Controller/UserController.php new file mode 100644 index 0000000..cc04b8f --- /dev/null +++ b/backend/app/Controller/UserController.php @@ -0,0 +1,62 @@ +getUserId($request); + + // TODO: 从数据库获取用户信息 + return $this->success([ + 'id' => $userId, + 'name' => '测试用户', + 'email' => 'test@example.com', + 'company_id' => 1, + ]); + } + + /** + * 获取用户菜单权限 + */ + public function menu(Request $request): Response + { + // TODO: 根据用户权限返回菜单 + return $this->success([ + ['id' => 1, 'name' => '首页', 'path' => '/dashboard'], + ['id' => 2, 'name' => '出差审批', 'path' => '/business-trip'], + ]); + } + + /** + * 获取用户可切换的公司列表 + */ + public function companies(Request $request): Response + { + $userId = $this->getUserId($request); + + // TODO: 从数据库获取用户关联的公司列表 + return $this->success([ + ['id' => 1, 'name' => '公司A'], + ]); + } + + /** + * 切换当前公司 + */ + public function switchCompany(Request $request): Response + { + $companyId = $request->post('company_id'); + + // TODO: 更新用户当前公司 + + return $this->success(); + } +} diff --git a/backend/app/Model/ApprovalFlow.php b/backend/app/Model/ApprovalFlow.php new file mode 100644 index 0000000..c6d12f0 --- /dev/null +++ b/backend/app/Model/ApprovalFlow.php @@ -0,0 +1,47 @@ + 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * 关联审批节点 + */ + public function nodes() + { + return $this->hasMany(ApprovalNode::class, 'flow_id')->orderBy('order'); + } + + /** + * 获取启用的流程 + */ + public function scopeActive($query) + { + return $query->where('status', 1); + } + + /** + * 根据业务类型获取流程 + */ + public function scopeByBusinessType($query, string $businessType) + { + return $query->where('business_type', $businessType); + } +} diff --git a/backend/app/Model/ApprovalNode.php b/backend/app/Model/ApprovalNode.php new file mode 100644 index 0000000..d429c71 --- /dev/null +++ b/backend/app/Model/ApprovalNode.php @@ -0,0 +1,79 @@ + 'array', + 'conditions' => 'array', + 'order' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * 节点类型常量 + */ + const TYPE_APPROVER = 'approver'; // 审批人(单人) + const TYPE_AND_APPROVE = 'and_approve'; // 会签(需所有人通过) + const TYPE_OR_APPROVE = 'or_approve'; // 或签(任一人通过) + const TYPE_NOTIFY = 'notify'; // 抄送(通知用) + + /** + * 关联流程 + */ + public function flow() + { + return $this->belongsTo(ApprovalFlow::class, 'flow_id'); + } + + /** + * 获取指定顺序的节点 + */ + public function scopeByOrder($query, int $order) + { + return $query->where('order', $order); + } + + /** + * 判断是否是审批节点 + */ + public function isApprovalNode(): bool + { + return in_array($this->type, [ + self::TYPE_APPROVER, + self::TYPE_AND_APPROVE, + self::TYPE_OR_APPROVE, + ]); + } + + /** + * 判断是否是会签 + */ + public function isAndApprove(): bool + { + return $this->type === self::TYPE_AND_APPROVE; + } + + /** + * 判断是否是或签 + */ + public function isOrApprove(): bool + { + return $this->type === self::TYPE_OR_APPROVE; + } +} diff --git a/backend/app/Model/ApprovalOrder.php b/backend/app/Model/ApprovalOrder.php new file mode 100644 index 0000000..0f4e25b --- /dev/null +++ b/backend/app/Model/ApprovalOrder.php @@ -0,0 +1,91 @@ + 'array', + 'form_data' => 'array', + 'attachments' => 'array', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * 审批状态常量 + */ + const STATUS_DRAFT = 'draft'; // 草稿 + const STATUS_PENDING = 'pending'; // 审批中 + const STATUS_APPROVED = 'approved'; // 已通过 + const STATUS_REJECTED = 'rejected'; // 已拒绝 + const STATUS_CANCELLED = 'cancelled'; // 已撤销 + + /** + * 关联流程 + */ + public function flow() + { + return $this->belongsTo(ApprovalFlow::class, 'flow_id'); + } + + /** + * 关联申请人 + */ + public function applicant() + { + return $this->belongsTo(User::class, 'applicant_id'); + } + + /** + * 关联审批记录 + */ + public function records() + { + return $this->hasMany(ApprovalRecord::class, 'order_id')->orderBy('created_at'); + } + + /** + * 判断是否可以编辑 + */ + public function canEdit(): bool + { + return $this->status === self::STATUS_DRAFT; + } + + /** + * 判断是否可以审批 + */ + public function canApprove(): bool + { + return $this->status === self::STATUS_PENDING; + } + + /** + * 判断是否已结束 + */ + public function isEnded(): bool + { + return in_array($this->status, [ + self::STATUS_APPROVED, + self::STATUS_REJECTED, + self::STATUS_CANCELLED, + ]); + } +} diff --git a/backend/app/Model/ApprovalRecord.php b/backend/app/Model/ApprovalRecord.php new file mode 100644 index 0000000..3440a21 --- /dev/null +++ b/backend/app/Model/ApprovalRecord.php @@ -0,0 +1,63 @@ + 'datetime', + ]; + + /** + * 操作类型常量 + */ + const ACTION_SUBMIT = 'submit'; // 提交 + const ACTION_APPROVE = 'approve'; // 通过 + const ACTION_REJECT = 'reject'; // 拒绝 + const ACTION_CANCEL = 'cancel'; // 撤销 + + /** + * 关联审批单 + */ + public function order() + { + return $this->belongsTo(ApprovalOrder::class, 'order_id'); + } + + /** + * 关联审批人 + */ + public function approver() + { + return $this->belongsTo(User::class, 'approver_id'); + } + + /** + * 判断是否是批准操作 + */ + public function isApproved(): bool + { + return $this->action === self::ACTION_APPROVE; + } + + /** + * 判断是否是拒绝操作 + */ + public function isRejected(): bool + { + return $this->action === self::ACTION_REJECT; + } +} diff --git a/backend/app/Model/Company.php b/backend/app/Model/Company.php new file mode 100644 index 0000000..704d6d7 --- /dev/null +++ b/backend/app/Model/Company.php @@ -0,0 +1,52 @@ + 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + protected $hidden = [ + 'feishu_app_secret', + ]; + + /** + * 获取启用的公司 + */ + public function scopeActive($query) + { + return $query->where('status', 1); + } + + /** + * 根据飞书租户Key查找 + */ + public function scopeByFeishuTenantKey($query, string $tenantKey) + { + return $query->where('feishu_tenant_key', $tenantKey); + } + + /** + * 根据飞书AppID查找 + */ + public function scopeByFeishuAppId($query, string $appId) + { + return $query->where('feishu_app_id', $appId); + } +} diff --git a/backend/app/Model/User.php b/backend/app/Model/User.php new file mode 100644 index 0000000..2547c4f --- /dev/null +++ b/backend/app/Model/User.php @@ -0,0 +1,60 @@ + 'integer', + 'status' => 'integer', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * 关联公司 + */ + public function company() + { + return $this->belongsTo(Company::class, 'company_id'); + } + + /** + * 获取启用的用户 + */ + public function scopeActive($query) + { + return $query->where('status', 1); + } + + /** + * 根据飞书用户ID查找 + */ + public function scopeByFeishuUserId($query, string $feishuUserId) + { + return $query->where('feishu_user_id', $feishuUserId); + } + + /** + * 根据飞书 UnionID 查找 + */ + public function scopeByFeishuUnionId($query, string $feishuUnionId) + { + return $query->where('feishu_union_id', $feishuUnionId); + } +} diff --git a/backend/app/Service/ApprovalService.php b/backend/app/Service/ApprovalService.php new file mode 100644 index 0000000..26f0d31 --- /dev/null +++ b/backend/app/Service/ApprovalService.php @@ -0,0 +1,302 @@ +db = Db::connection(); + } + + /** + * 提交审批 + */ + public function submit(array $data): ApprovalOrder + { + return $this->db->transaction(function () use ($data) { + // 1. 获取审批流程 + $flow = ApprovalFlow::active() + ->byBusinessType($data['business_type']) + ->with('nodes') + ->findOrFail($data['flow_id']); + + // 2. 创建审批单 + $order = ApprovalOrder::create([ + 'business_type' => $data['business_type'], + 'business_id' => $data['business_id'], + 'title' => $data['title'], + 'applicant_id' => $data['applicant_id'], + 'status' => ApprovalOrder::STATUS_PENDING, + 'current_nodes' => $this->getFirstNodeIds($flow), + 'flow_id' => $flow->id, + 'form_data' => $data['form_data'] ?? [], + 'attachments' => $data['attachments'] ?? [], + ]); + + // 3. 记录提交操作 + ApprovalRecord::create([ + 'order_id' => $order->id, + 'node_id' => $flow->nodes->first()->id ?? 0, + 'node_type' => 'submit', + 'approver_id' => $data['applicant_id'], + 'action' => ApprovalRecord::ACTION_SUBMIT, + 'comment' => $data['comment'] ?? null, + ]); + + return $order->load(['flow', 'applicant']); + }); + } + + /** + * 审批通过 + */ + public function approve(int $orderId, int $approverId, ?string $comment = null): ApprovalOrder + { + return $this->db->transaction(function () use ($orderId, $approverId, $comment) { + $order = ApprovalOrder::with(['flow.nodes'])->findOrFail($orderId); + + if (!$order->canApprove()) { + throw new \Exception('当前状态无法审批'); + } + + $currentNodeIds = $order->current_nodes ?? []; + $currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first(); + + if (!$currentNode) { + throw new \Exception('当前节点不存在'); + } + + // 记录审批操作 + ApprovalRecord::create([ + 'order_id' => $order->id, + 'node_id' => $currentNode->id, + 'node_type' => $currentNode->type, + 'approver_id' => $approverId, + 'action' => ApprovalRecord::ACTION_APPROVE, + 'comment' => $comment, + ]); + + // 判断节点是否完成 + if ($this->isNodeCompleted($order->id, $currentNode->id)) { + // 移动到下一节点 + $nextNodes = $this->getNextNodes($order->flow, $currentNode->order); + + if (empty($nextNodes)) { + // 审批完成 + $order->status = ApprovalOrder::STATUS_APPROVED; + $order->current_nodes = []; + } else { + $order->current_nodes = collect($nextNodes)->pluck('id')->toArray(); + } + + $order->save(); + } + + return $order->fresh(['flow', 'applicant', 'records']); + }); + } + + /** + * 审批拒绝 + */ + public function reject(int $orderId, int $approverId, ?string $comment = null): ApprovalOrder + { + return $this->db->transaction(function () use ($orderId, $approverId, $comment) { + $order = ApprovalOrder::findOrFail($orderId); + + if (!$order->canApprove()) { + throw new \Exception('当前状态无法审批'); + } + + $currentNodeIds = $order->current_nodes ?? []; + $currentNode = ApprovalNode::whereIn('id', $currentNodeIds)->first(); + + // 记录拒绝操作 + ApprovalRecord::create([ + 'order_id' => $order->id, + 'node_id' => $currentNode->id ?? 0, + 'node_type' => $currentNode->type ?? 'unknown', + 'approver_id' => $approverId, + 'action' => ApprovalRecord::ACTION_REJECT, + 'comment' => $comment, + ]); + + // 更新状态为已拒绝 + $order->status = ApprovalOrder::STATUS_REJECTED; + $order->current_nodes = []; + $order->save(); + + return $order->fresh(['flow', 'applicant', 'records']); + }); + } + + /** + * 撤销申请 + */ + public function cancel(int $orderId, int $userId): ApprovalOrder + { + $order = ApprovalOrder::findOrFail($orderId); + + if ($order->applicant_id !== $userId) { + throw new \Exception('只有申请人可以撤销'); + } + + if (!$order->canEdit() && !$order->canApprove()) { + throw new \Exception('当前状态无法撤销'); + } + + // 记录撤销操作 + ApprovalRecord::create([ + 'order_id' => $order->id, + 'node_id' => 0, + 'node_type' => 'cancel', + 'approver_id' => $userId, + 'action' => ApprovalRecord::ACTION_CANCEL, + 'comment' => '申请人撤销', + ]); + + $order->status = ApprovalOrder::STATUS_CANCELLED; + $order->current_nodes = []; + $order->save(); + + return $order->fresh(['flow', 'applicant', 'records']); + } + + /** + * 获取审批列表 + */ + public function getList(int $userId, string $type = 'my', int $page = 1, int $pageSize = 20): array + { + $query = ApprovalOrder::with(['flow', 'applicant']); + + switch ($type) { + case 'my': + $query->where('applicant_id', $userId); + break; + case 'pending': + // 获取用户待审批的订单 + $pendingOrderIds = $this->getPendingOrderIds($userId); + $query->whereIn('id', $pendingOrderIds); + break; + case 'approved': + // 获取用户已审批的订单 + $approvedOrderIds = ApprovalRecord::where('approver_id', $userId) + ->where('action', '!=', ApprovalRecord::ACTION_SUBMIT) + ->pluck('order_id') + ->unique() + ->toArray(); + $query->whereIn('id', $approvedOrderIds); + break; + } + + $total = $query->count(); + $list = $query->orderBy('created_at', 'desc') + ->offset(($page - 1) * $pageSize) + ->limit($pageSize) + ->get(); + + return [ + 'list' => $list, + 'total' => $total, + 'page' => $page, + 'page_size' => $pageSize, + ]; + } + + /** + * 获取第一个节点ID列表 + */ + private function getFirstNodeIds(ApprovalFlow $flow): array + { + return $flow->nodes + ->where('order', 0) + ->pluck('id') + ->toArray(); + } + + /** + * 判断节点是否完成 + */ + private function isNodeCompleted(int $orderId, int $nodeId): bool + { + $node = ApprovalNode::findOrFail($nodeId); + $records = ApprovalRecord::where('order_id', $orderId) + ->where('node_id', $nodeId) + ->where('action', ApprovalRecord::ACTION_APPROVE) + ->get(); + + $approvers = $node->approvers ?? []; + + if ($node->type === ApprovalNode::TYPE_AND_APPROVE) { + // 会签:需要所有人通过 + return count($records) >= count($approvers); + } elseif ($node->type === ApprovalNode::TYPE_OR_APPROVE) { + // 或签:任一人通过即可 + return count($records) >= 1; + } else { + // 单人审批:需要一人通过 + return count($records) >= 1; + } + } + + /** + * 获取下一节点 + */ + private function getNextNodes(ApprovalFlow $flow, int $currentOrder): array + { + return $flow->nodes + ->where('order', '>', $currentOrder) + ->sortBy('order') + ->values() + ->toArray(); + } + + /** + * 获取用户待审批的订单ID列表 + */ + private function getPendingOrderIds(int $userId): array + { + // 获取用户参与的待审批订单 + $pendingOrders = ApprovalOrder::where('status', ApprovalOrder::STATUS_PENDING) + ->whereNotNull('current_nodes') + ->get(); + + $result = []; + foreach ($pendingOrders as $order) { + $currentNodeIds = $order->current_nodes ?? []; + $nodes = ApprovalNode::whereIn('id', $currentNodeIds)->get(); + + foreach ($nodes as $node) { + $approvers = $node->approvers ?? []; + if (in_array($userId, $approvers)) { + // 检查是否已审批 + $hasApproved = ApprovalRecord::where('order_id', $order->id) + ->where('node_id', $node->id) + ->where('approver_id', $userId) + ->exists(); + + if (!$hasApproved) { + $result[] = $order->id; + break; + } + } + } + } + + return $result; + } +} diff --git a/backend/app/Service/FeishuService.php b/backend/app/Service/FeishuService.php new file mode 100644 index 0000000..57d2420 --- /dev/null +++ b/backend/app/Service/FeishuService.php @@ -0,0 +1,252 @@ +companyId = $companyId; + $this->accessToken = null; + return $this; + } + + /** + * 根据 tenant_key 获取公司配置 + */ + public function getCompanyByTenantKey(string $tenantKey): ?Company + { + return Company::active()->byFeishuTenantKey($tenantKey)->first(); + } + + /** + * 获取飞书 access_token + */ + public function getAccessToken(): string + { + if ($this->accessToken) { + return $this->accessToken; + } + + if (!$this->companyId) { + throw new \Exception('未设置公司ID'); + } + + $company = Company::findOrFail($this->companyId); + + // 从缓存获取 + $cacheKey = "feishu:access_token:{$company->id}"; + $token = Cache::get($cacheKey); + + if ($token) { + $this->accessToken = $token; + return $token; + } + + // 请求新的 token + $response = $this->httpPost('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', [ + 'app_id' => $company->feishu_app_id, + 'app_secret' => $company->feishu_app_secret, + ]); + + if ($response['code'] !== 0) { + throw new \Exception('获取飞书 token 失败: ' . ($response['msg'] ?? '未知错误')); + } + + $token = $response['tenant_access_token']; + $expire = $response['expire'] ?? 7200; + + // 缓存 token(提前5分钟过期) + Cache::set($cacheKey, $token, $expire - 300); + + $this->accessToken = $token; + return $token; + } + + /** + * 根据 auth_code 获取用户信息 + */ + public function getUserInfo(string $authCode): array + { + if (!$this->companyId) { + throw new \Exception('未设置公司ID'); + } + + $token = $this->getAccessToken(); + + // 获取用户 access_token + $response = $this->httpPost('https://open.feishu.cn/open-apis/authen/v1/oidc/access_token', [ + 'grant_type' => 'authorization_code', + 'client_id' => Company::findOrFail($this->companyId)->feishu_app_id, + 'client_secret' => Company::findOrFail($this->companyId)->feishu_app_secret, + 'code' => $authCode, + ], [ + 'Authorization: Bearer ' . $token, + ]); + + if ($response['code'] !== 0) { + throw new \Exception('获取用户信息失败: ' . ($response['msg'] ?? '未知错误')); + } + + return $response['data'] ?? []; + } + + /** + * 创建或更新用户 + */ + public function createOrUpdateUser(array $feishuData, int $companyId): User + { + $unionId = $feishuData['union_id'] ?? null; + $userId = $feishuData['user_id'] ?? null; + $name = $feishuData['name'] ?? '未知'; + $avatar = $feishuData['avatar_url'] ?? null; + $email = $feishuData['email'] ?? null; + + if (!$unionId) { + throw new \Exception('缺少 union_id'); + } + + // 查找或创建用户 + $user = User::byFeishuUnionId($unionId)->first(); + + if (!$user) { + $user = User::create([ + 'company_id' => $companyId, + 'feishu_user_id' => $userId, + 'feishu_union_id' => $unionId, + 'name' => $name, + 'email' => $email, + 'avatar' => $avatar, + 'status' => 1, + ]); + } else { + // 更新用户信息 + $user->update([ + 'feishu_user_id' => $userId, + 'name' => $name, + 'avatar' => $avatar, + 'email' => $email, + ]); + } + + return $user; + } + + /** + * 发送审批消息通知 + */ + public function sendApprovalMessage(int $userId, string $title, string $url): bool + { + try { + $user = User::findOrFail($userId); + $company = Company::findOrFail($user->company_id); + + $this->setCompany($company->id); + $token = $this->getAccessToken(); + + // 构建消息内容 + $message = [ + 'msg_type' => 'interactive', + 'card' => [ + 'header' => [ + 'title' => [ + 'content' => '审批通知', + 'tag' => 'plain_text', + ], + 'template' => 'blue', + ], + 'elements' => [ + [ + 'tag' => 'div', + 'text' => [ + 'content' => $title, + 'tag' => 'plain_text', + ], + ], + [ + 'tag' => 'action', + 'actions' => [ + [ + 'tag' => 'button', + 'text' => [ + 'content' => '查看详情', + 'tag' => 'plain_text', + ], + 'type' => 'default', + 'url' => $url, + ], + ], + ], + ], + ], + ]; + + // 发送消息 + $response = $this->httpPost( + "https://open.feishu.cn/open-apis/message/v4/send_messages", + [ + 'receive_id_type' => 'user_id', + 'receive_id' => $user->feishu_user_id, + 'msg_type' => 'interactive', + 'content' => json_encode(['card' => $message['card']]), + ], + [ + 'Authorization: Bearer ' . $token, + ] + ); + + return ($response['code'] ?? -1) === 0; + } catch (\Exception $e) { + // 记录错误但不中断流程 + error_log('发送飞书消息失败: ' . $e->getMessage()); + return false; + } + } + + /** + * HTTP POST 请求 + */ + private function httpPost(string $url, array $data, array $headers = []): array + { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $defaultHeaders = [ + 'Content-Type: application/json', + ]; + + curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($defaultHeaders, $headers)); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + throw new \Exception("HTTP 请求失败: {$httpCode}"); + } + + $result = json_decode($response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception('JSON 解析失败'); + } + + return $result; + } +} diff --git a/backend/app/Util/Db.php b/backend/app/Util/Db.php new file mode 100644 index 0000000..5e3fe76 --- /dev/null +++ b/backend/app/Util/Db.php @@ -0,0 +1,66 @@ +addConnection([ + 'driver' => 'mysql', + 'host' => getenv('DB_HOST') ?: '127.0.0.1', + 'port' => getenv('DB_PORT') ?: '3306', + 'database' => getenv('DB_DATABASE') ?: 'x_server_mobile', + 'username' => getenv('DB_USERNAME') ?: 'root', + 'password' => getenv('DB_PASSWORD') ?: '', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'strict' => true, + 'engine' => null, + ]); + + // Set event dispatcher + self::$capsule->setEventDispatcher(new Dispatcher(new Container())); + + // Make this Capsule instance available globally via static methods + self::$capsule->setAsGlobal(); + + // Setup the Eloquent ORM + self::$capsule->bootEloquent(); + } + + public static function connection(?string $name = null): \Illuminate\Database\Connection + { + if (self::$capsule === null) { + self::init(); + } + return self::$capsule->connection($name); + } + + public static function table(string $table): \Illuminate\Database\Query\Builder + { + return self::connection()->table($table); + } + + public static function raw(string $value): \Illuminate\Database\Query\Expression + { + return self::connection()->raw($value); + } +} diff --git a/backend/app/Util/JwtUtil.php b/backend/app/Util/JwtUtil.php new file mode 100644 index 0000000..a5c8c00 --- /dev/null +++ b/backend/app/Util/JwtUtil.php @@ -0,0 +1,47 @@ + time(), + 'exp' => time() + self::$expire, + ]); + + return JWT::encode($payload, self::$secret, 'HS256'); + } + + /** + * 解析 Token + */ + public static function decode(string $token): ?array + { + try { + $decoded = JWT::decode($token, new Key(self::$secret, 'HS256')); + return (array) $decoded; + } catch (Exception $e) { + return null; + } + } + + /** + * 验证 Token + */ + public static function verify(string $token): bool + { + return self::decode($token) !== null; + } +} diff --git a/backend/app/Util/Lang.php b/backend/app/Util/Lang.php new file mode 100644 index 0000000..993fab5 --- /dev/null +++ b/backend/app/Util/Lang.php @@ -0,0 +1,170 @@ + '简体中文', + 'en' => 'English', + 'ru' => 'Русский', + 'es' => 'Español', + 'fr' => 'Français', + ]; + + /** + * 初始化翻译 + */ + private static function loadTranslations(): void + { + if (self::$translations !== null) { + return; + } + + self::$translations = []; + foreach (self::SUPPORTED_LOCALES as $locale) { + $file = base_path() . "/lang/{$locale}.php"; + if (file_exists($file)) { + self::$translations[$locale] = require $file; + } + } + } + + /** + * 设置当前语言 + */ + public static function setLocale(string $locale): void + { + if (in_array($locale, self::SUPPORTED_LOCALES)) { + self::$locale = $locale; + } + } + + /** + * 获取当前语言 + */ + public static function getLocale(): string + { + return self::$locale; + } + + /** + * 从请求中获取语言 + */ + public static function detectFromRequest(): string + { + // 从请求头获取语言 + $request = \request(); + $acceptLanguage = $request->header('Accept-Language'); + + if ($acceptLanguage) { + // 解析 Accept-Language 头 + $locales = explode(',', $acceptLanguage); + foreach ($locales as $locale) { + $locale = trim(strstr($locale, ';', true)); + $locale = substr($locale, 0, 2); + if (in_array($locale, self::SUPPORTED_LOCALES)) { + return $locale; + } + } + } + + // 从查询参数获取 + $lang = $request->input('lang'); + if ($lang && in_array($lang, self::SUPPORTED_LOCALES)) { + return $lang; + } + + return self::$fallbackLocale; + } + + /** + * 翻译文本 + * + * @param string $key 翻译键,使用点号分隔,如 'common.confirm' + * @param array $params 替换参数 + * @param string|null $locale 指定语言,null 则使用当前语言 + * @return string 翻译后的文本 + */ + public static function get(string $key, array $params = [], ?string $locale = null): string + { + self::loadTranslations(); + + $locale = $locale ?? self::$locale; + + // 获取翻译值 + $value = self::getTranslationValue($key, $locale); + + // 替换参数 + if (!empty($params)) { + foreach ($params as $search => $replace) { + $value = str_replace(':' . $search, $replace, $value); + $value = str_replace('{' . $search . '}', $replace, $value); + } + } + + return $value; + } + + /** + * 获取翻译值 + */ + private static function getTranslationValue(string $key, string $locale): string + { + $keys = explode('.', $key); + $value = self::$translations[$locale] ?? []; + + foreach ($keys as $k) { + if (isset($value[$k])) { + $value = $value[$k]; + } else { + // 尝试使用回退语言 + if ($locale !== self::$fallbackLocale) { + return self::getTranslationValue($key, self::$fallbackLocale); + } + return $key; + } + } + + return is_string($value) ? $value : $key; + } + + /** + * 判断翻译是否存在 + */ + public static function has(string $key, ?string $locale = null): bool + { + self::loadTranslations(); + + $locale = $locale ?? self::$locale; + + $keys = explode('.', $key); + $value = self::$translations[$locale] ?? []; + + foreach ($keys as $k) { + if (isset($value[$k])) { + $value = $value[$k]; + } else { + return false; + } + } + + return true; + } +} diff --git a/backend/app/middleware/AuthMiddleware.php b/backend/app/middleware/AuthMiddleware.php new file mode 100644 index 0000000..60b91a3 --- /dev/null +++ b/backend/app/middleware/AuthMiddleware.php @@ -0,0 +1,35 @@ +header('Authorization'); + $token = str_replace('Bearer ', '', $token); + + if (!$token || !JwtUtil::verify($token)) { + return json([ + 'code' => 401, + 'message' => 'Unauthorized', + 'data' => null, + ], 401); + } + + // 解析 Token 并设置用户信息到请求 + $payload = JwtUtil::decode($token); + if ($payload) { + $request->userId = $payload['user_id'] ?? null; + $request->companyId = $payload['company_id'] ?? null; + } + + return $handler($request); + } +} diff --git a/backend/app/middleware/CorsMiddleware.php b/backend/app/middleware/CorsMiddleware.php new file mode 100644 index 0000000..d3e32f0 --- /dev/null +++ b/backend/app/middleware/CorsMiddleware.php @@ -0,0 +1,27 @@ +method() === 'OPTIONS') { + $response = response(''); + } else { + $response = $handler($request); + } + + // 添加 CORS 头 + $response->withHeader('Access-Control-Allow-Origin', '*') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') + ->withHeader('Access-Control-Max-Age', '86400'); + + return $response; + } +} diff --git a/backend/composer.json b/backend/composer.json new file mode 100644 index 0000000..998084c --- /dev/null +++ b/backend/composer.json @@ -0,0 +1,43 @@ +{ + "name": "x-server/mobile-backend", + "description": "Enterprise Tool System Backend", + "type": "project", + "require": { + "php": ">=8.1", + "workerman/webman": "^2.0", + "workerman/webman-framework": "^2.0", + "illuminate/database": "^10.0", + "illuminate/pagination": "^10.0", + "illuminate/redis": "^10.0", + "illuminate/events": "^10.0", + "firebase/php-jwt": "^6.10", + "vlucas/phpdotenv": "^5.6" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "phpunit/phpunit": "^10.0" + }, + "autoload": { + "psr-4": { + "App\\": "app/" + } + }, + "scripts": { + "post-package-install": [ + "support\\Plugin::install" + ], + "post-package-update": [ + "support\\Plugin::install" + ] + }, + "config": { + "audit": { + "ignore": { + "firebase/php-jwt": "PKSA-y2cr-5h3j-g3ys" + } + }, + "allow-plugins": { + "*": true + } + } +} diff --git a/backend/composer.lock b/backend/composer.lock new file mode 100644 index 0000000..13276c6 --- /dev/null +++ b/backend/composer.lock @@ -0,0 +1,4750 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1f33bdcee0a2bedeb8baa362e561fc69", + "packages": [ + { + "name": "brick/math", + "version": "0.12.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-02-28T13:11:00+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.7.0 || >=4.0.0" + }, + "require-dev": { + "doctrine/dbal": "^3.7.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2023-12-11T17:09:12+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v6.11.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + }, + "time": "2025-04-09T20:32:01+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "illuminate/bus", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "053f902d546d719c3f2752f7d3805a466e317312" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/053f902d546d719c3f2752f7d3805a466e317312", + "reference": "053f902d546d719c3f2752f7d3805a466e317312", + "shasum": "" + }, + "require": { + "illuminate/collections": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/pipeline": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/collections", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "php": "^8.1" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^6.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-09-08T19:05:53+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/47c700320b7a419f0d188d111f3bbed978fcbd3f", + "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/container", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "b4956de5de18524c21ef36221a8ffd7fa3b534db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/b4956de5de18524c21ef36221a8ffd7fa3b534db", + "reference": "b4956de5de18524c21ef36221a8ffd7fa3b534db", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0", + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "2393ef579e020d88e24283913c815c3e2c143323" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/2393ef579e020d88e24283913c815c3e2c143323", + "reference": "2393ef579e020d88e24283913c815c3e2c143323", + "shasum": "" + }, + "require": { + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/database", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/database.git", + "reference": "711519fa4eca9c55d4f3d6680ffca71b28317e7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/database/zipball/711519fa4eca9c55d4f3d6680ffca71b28317e7a", + "reference": "711519fa4eca9c55d4f3d6680ffca71b28317e7a", + "shasum": "" + }, + "require": { + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "ext-pdo": "*", + "illuminate/collections": "^10.0", + "illuminate/container": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "conflict": { + "carbonphp/carbon-doctrine-types": ">=3.0", + "doctrine/dbal": ">=4.0" + }, + "suggest": { + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", + "ext-filter": "Required to use the Postgres database driver.", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.21).", + "illuminate/console": "Required to use the database commands (^10.0).", + "illuminate/events": "Required to use the observers with Eloquent (^10.0).", + "illuminate/filesystem": "Required to use the migrations (^10.0).", + "illuminate/pagination": "Required to paginate the result set (^10.0).", + "symfony/finder": "Required to use Eloquent model factories (^6.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Database\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Database package.", + "homepage": "https://laravel.com", + "keywords": [ + "database", + "laravel", + "orm", + "sql" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/events", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "4a8e4fbc95c7e46aa6152fd8c900d56e5ef538cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/4a8e4fbc95c7e46aa6152fd8c900d56e5ef538cf", + "reference": "4a8e4fbc95c7e46aa6152fd8c900d56e5ef538cf", + "shasum": "" + }, + "require": { + "illuminate/bus": "^10.0", + "illuminate/collections": "^10.0", + "illuminate/container": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2023-06-05T12:46:42+00:00" + }, + { + "name": "illuminate/pagination", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pagination.git", + "reference": "616874b9607ff35925347e1710a8b5151858cdf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pagination/zipball/616874b9607ff35925347e1710a8b5151858cdf2", + "reference": "616874b9607ff35925347e1710a8b5151858cdf2", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "illuminate/collections": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pagination\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pagination package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-04-11T14:31:05+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "c12e4f1d8a1fbecdc1e0fa4dc9fe17b4315832e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/c12e4f1d8a1fbecdc1e0fa4dc9fe17b4315832e9", + "reference": "c12e4f1d8a1fbecdc1e0fa4dc9fe17b4315832e9", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/redis", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/redis.git", + "reference": "f13f1e03469598055e140a79e0cb31b3874164a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/redis/zipball/f13f1e03469598055e140a79e0cb31b3874164a4", + "reference": "f13f1e03469598055e140a79e0cb31b3874164a4", + "shasum": "" + }, + "require": { + "illuminate/collections": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "illuminate/support": "^10.0", + "php": "^8.1" + }, + "suggest": { + "ext-redis": "Required to use the phpredis connector (^4.0|^5.0).", + "predis/predis": "Required to use the predis connector (^2.0.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Redis package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/support", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "28b505e671dbe119e4e32a75c78f87189d046e39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/28b505e671dbe119e4e32a75c78f87189d046e39", + "reference": "28b505e671dbe119e4e32a75c78f87189d046e39", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^10.0", + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "nesbot/carbon": "^2.67", + "php": "^8.1", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^10.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.6).", + "ramsey/uuid": "Required to use Str::uuid() (^4.7).", + "symfony/process": "Required to use the composer class (^6.2).", + "symfony/uid": "Required to use Str::ulid() (^6.2).", + "symfony/var-dumper": "Required to use the dd function (^6.2).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-09-08T19:05:53+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "37308608e599f34a1a4845b16440047ec98a172a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/37308608e599f34a1a4845b16440047ec98a172a", + "reference": "37308608e599f34a1a4845b16440047ec98a172a", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-01T13:05:00+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.73.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "<6", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-01-08T20:10:23+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-13T11:36:38+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-12T10:54:30+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc", + "reference": "d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.4.32" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-12T19:15:33+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T13:41:35+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.3", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "955e7815d677a3eaa7075231212f2110983adecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:49:13+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + }, + { + "name": "webman/console", + "version": "v2.2.1", + "source": { + "type": "git", + "url": "https://github.com/webman-php/console.git", + "reference": "3c1a50296f7b3b3eff3a8fcda8906276dae3a18f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/console/zipball/3c1a50296f7b3b3eff3a8fcda8906276dae3a18f", + "reference": "3c1a50296f7b3b3eff3a8fcda8906276dae3a18f", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "php": ">=8.1", + "symfony/console": "^6.1 || ^7.0" + }, + "require-dev": { + "workerman/webman": "^2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Console\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "Webman console", + "homepage": "http://www.workerman.net", + "keywords": [ + "webman console" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://www.workerman.net/questions", + "issues": "https://github.com/webman-php/console/issues", + "source": "https://github.com/webman-php/console", + "wiki": "http://www.workerman.net/doc/webman" + }, + "time": "2026-02-23T02:46:47+00:00" + }, + { + "name": "workerman/coroutine", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/workerman-php/coroutine.git", + "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44", + "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "psr/log": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "src", + "Workerman\\Coroutine\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Workerman coroutine", + "support": { + "issues": "https://github.com/workerman-php/coroutine/issues", + "source": "https://github.com/workerman-php/coroutine/tree/v1.1.4" + }, + "time": "2025-10-11T15:09:08+00:00" + }, + { + "name": "workerman/webman", + "version": "v2.2.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman.git", + "reference": "8f32669772113d62c5ef515a18066883b5fcf943" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman/zipball/8f32669772113d62c5ef515a18066883b5fcf943", + "reference": "8f32669772113d62c5ef515a18066883b5fcf943", + "shasum": "" + }, + "require": { + "monolog/monolog": "^2.0", + "php": ">=8.1", + "webman/console": "^2.1", + "workerman/webman-framework": "^2.1" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "project", + "autoload": { + "psr-4": { + "": "./", + "App\\": "./app", + "app\\": "./app", + "app\\View\\Components\\": "./app/view/components" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "description": "High performance HTTP Service Framework.", + "homepage": "https://www.workerman.net", + "keywords": [ + "High Performance", + "http service" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman", + "wiki": "https://workerman.net/doc/webman" + }, + "time": "2026-02-24T06:57:19+00:00" + }, + { + "name": "workerman/webman-framework", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "2da4e49259d41925f1732f95f6fb052a3f42ceee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/2da4e49259d41925f1732f95f6fb052a3f42ceee", + "reference": "2da4e49259d41925f1732f95f6fb052a3f42ceee", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": ">=8.1", + "psr/container": ">=1.0", + "psr/log": "^2.0 || ^3.0", + "workerman/workerman": "^5.1 || dev-master" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "files": [ + "./src/support/helpers.php" + ], + "psr-4": { + "Webman\\": "./src", + "Support\\": "./src/support", + "support\\": "./src/support", + "Support\\View\\": "./src/support/view", + "Support\\Bootstrap\\": "./src/support/bootstrap", + "Support\\Exception\\": "./src/support/exception" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "description": "High performance HTTP Service Framework.", + "homepage": "https://www.workerman.net", + "keywords": [ + "High Performance", + "http service" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman-framework", + "wiki": "https://doc.workerman.net/" + }, + "time": "2026-02-20T02:37:25+00:00" + }, + { + "name": "workerman/workerman", + "version": "v5.1.9", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "fff0954628f8ceeccfe29d3e817f0fad87cfdbf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/fff0954628f8ceeccfe29d3e817f0fad87cfdbf2", + "reference": "fff0954628f8ceeccfe29d3e817f0fad87cfdbf2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=8.1", + "workerman/coroutine": "^1.1 || dev-main" + }, + "conflict": { + "ext-swow": "=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.63", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:48:37+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:25:16+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:09:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:50:56+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/backend/config/app.php b/backend/config/app.php new file mode 100644 index 0000000..fc29575 --- /dev/null +++ b/backend/config/app.php @@ -0,0 +1,11 @@ + getenv('APP_DEBUG') ?: true, + 'default_timezone' => 'Asia/Shanghai', + 'request_class' => support\Request::class, + 'public_path' => base_path() . '/public', + 'runtime_path' => base_path() . '/runtime', + 'controller_suffix' => 'Controller', + 'controller_reuse' => true, +]; diff --git a/backend/config/container.php b/backend/config/container.php new file mode 100644 index 0000000..f5774b9 --- /dev/null +++ b/backend/config/container.php @@ -0,0 +1,3 @@ + 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'mysql', + 'host' => getenv('DB_HOST') ?: 'mysql', + 'port' => getenv('DB_PORT') ?: 3306, + 'database' => getenv('DB_DATABASE') ?: 'x_server_mobile', + 'username' => getenv('DB_USERNAME') ?: 'root', + 'password' => getenv('DB_PASSWORD') ?: '123456', + 'unix_socket' => getenv('DB_SOCKET') ?: '', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'strict' => true, + 'engine' => null, + 'options' => [ + \PDO::ATTR_TIMEOUT => 5, + ], + ], + ], +]; diff --git a/backend/config/log.php b/backend/config/log.php new file mode 100644 index 0000000..29703d7 --- /dev/null +++ b/backend/config/log.php @@ -0,0 +1,17 @@ + [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\StreamHandler::class, + 'constructor' => [ + runtime_path() . '/logs/workerman.log', + Monolog\Logger::DEBUG, + ], + ], + ], + ], +]; diff --git a/backend/config/plugin/webman/console/app.php b/backend/config/plugin/webman/console/app.php new file mode 100644 index 0000000..d7bf968 --- /dev/null +++ b/backend/config/plugin/webman/console/app.php @@ -0,0 +1,28 @@ + true, + + 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build', + + 'phar_filename' => 'webman.phar', + + 'phar_format' => Phar::PHAR, // Phar archive format: Phar::PHAR, Phar::TAR, Phar::ZIP + + 'phar_compression' => Phar::NONE, // Compression method for Phar archive: Phar::NONE, Phar::GZ, Phar::BZ2 + + 'bin_filename' => 'webman.bin', + + 'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL. + + 'private_key_file' => '', // The file path for certificate or OpenSSL private key file. + + 'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#', + + 'exclude_files' => [ + '.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin' + ], + + 'custom_ini' => ' +memory_limit = 256M + ', +]; diff --git a/backend/config/redis.php b/backend/config/redis.php new file mode 100644 index 0000000..dc5b01f --- /dev/null +++ b/backend/config/redis.php @@ -0,0 +1,10 @@ + [ + 'host' => getenv('REDIS_HOST') ?: 'redis', + 'port' => getenv('REDIS_PORT') ?: 6379, + 'password' => getenv('REDIS_PASSWORD') ?: null, + 'database' => getenv('REDIS_DATABASE') ?: 0, + ], +]; diff --git a/backend/config/route.php b/backend/config/route.php new file mode 100644 index 0000000..ae0a5a0 --- /dev/null +++ b/backend/config/route.php @@ -0,0 +1,41 @@ + 404, 'message' => 'Not Found']); +}); diff --git a/backend/config/server.php b/backend/config/server.php new file mode 100644 index 0000000..0ae6dd1 --- /dev/null +++ b/backend/config/server.php @@ -0,0 +1,23 @@ + 'http://0.0.0.0:8787', + 'transport' => 'tcp', + 'context' => [], + 'name' => 'webman', + 'count' => (int)shell_exec('nproc') ?: 4, + 'user' => '', + 'group' => '', + 'reusePort' => false, + 'event_loop' => '', + 'stop_timeout' => 2, + 'pid_file' => runtime_path() . '/webman.pid', + 'max_request' => 1000000, + 'stdout_file' => runtime_path() . '/logs/stdout.log', + 'log_file' => runtime_path() . '/logs/workerman.log', + 'max_package_size' => 10 * 1024 * 1024, +]; diff --git a/backend/database/migrations/init.sql b/backend/database/migrations/init.sql new file mode 100644 index 0000000..58f8a8a --- /dev/null +++ b/backend/database/migrations/init.sql @@ -0,0 +1,196 @@ +-- 企业内部工具系统 - 数据库初始化脚本 +-- 数据库: x_server_mobile + +-- ===================================== +-- 1. 公司表(飞书应用配置) +-- ===================================== +CREATE TABLE IF NOT EXISTS `company` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '公司ID', + `name` VARCHAR(100) NOT NULL COMMENT '公司名称', + `feishu_app_id` VARCHAR(100) DEFAULT NULL COMMENT '飞书应用ID', + `feishu_app_secret` VARCHAR(255) DEFAULT NULL COMMENT '飞书应用密钥', + `feishu_tenant_key` VARCHAR(100) DEFAULT NULL COMMENT '飞书租户Key', + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=禁用', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_feishu_app_id` (`feishu_app_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司表'; + +-- ===================================== +-- 2. 用户表(全局用户) +-- ===================================== +CREATE TABLE IF NOT EXISTS `user` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `company_id` BIGINT UNSIGNED NOT NULL COMMENT '所属公司ID', + `feishu_user_id` VARCHAR(100) DEFAULT NULL COMMENT '飞书用户ID', + `feishu_union_id` VARCHAR(100) DEFAULT NULL COMMENT '飞书UnionID(跨应用唯一)', + `name` VARCHAR(50) NOT NULL COMMENT '用户名', + `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱', + `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', + `avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL', + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1=正常,0=禁用', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_company_id` (`company_id`), + KEY `idx_feishu_user_id` (`feishu_user_id`), + KEY `idx_feishu_union_id` (`feishu_union_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'; + +-- ===================================== +-- 3. 用户与飞书应用关联表 +-- ===================================== +CREATE TABLE IF NOT EXISTS `user_feishu_binding` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID', + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID', + `company_id` BIGINT UNSIGNED NOT NULL COMMENT '公司ID', + `feishu_open_id` VARCHAR(100) DEFAULT NULL COMMENT '飞书OpenID', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_company` (`user_id`, `company_id`), + KEY `idx_feishu_open_id` (`feishu_open_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户飞书应用绑定表'; + +-- ===================================== +-- 4. 审批流程配置表 +-- ===================================== +CREATE TABLE IF NOT EXISTS `approval_flow` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '流程ID', + `business_type` VARCHAR(50) NOT NULL COMMENT '业务类型标识(如:business-trip, purchase)', + `name` VARCHAR(100) NOT NULL COMMENT '流程名称', + `description` VARCHAR(255) DEFAULT NULL COMMENT '流程描述', + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=禁用', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_business_type` (`business_type`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批流程配置表'; + +-- ===================================== +-- 5. 审批流程节点表 +-- ===================================== +CREATE TABLE IF NOT EXISTS `approval_node` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '节点ID', + `flow_id` BIGINT UNSIGNED NOT NULL COMMENT '流程ID', + `type` ENUM('approver', 'and_approve', 'or_approve', 'notify') NOT NULL COMMENT '节点类型', + `name` VARCHAR(100) NOT NULL COMMENT '节点名称', + `order` INT NOT NULL DEFAULT 0 COMMENT '节点顺序', + `approvers` JSON DEFAULT NULL COMMENT '审批人列表(JSON数组)', + `conditions` JSON DEFAULT NULL COMMENT '条件判断(JSON)', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_flow_id` (`flow_id`), + KEY `idx_order` (`order`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批流程节点表'; + +-- ===================================== +-- 6. 审批单表 +-- ===================================== +CREATE TABLE IF NOT EXISTS `approval_order` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '审批单ID', + `business_type` VARCHAR(50) NOT NULL COMMENT '业务类型', + `business_id` BIGINT UNSIGNED NOT NULL COMMENT '业务单据ID', + `title` VARCHAR(200) NOT NULL COMMENT '审批标题', + `applicant_id` BIGINT UNSIGNED NOT NULL COMMENT '申请人ID', + `status` ENUM('draft', 'pending', 'approved', 'rejected', 'cancelled') NOT NULL DEFAULT 'draft' COMMENT '审批状态', + `current_nodes` JSON DEFAULT NULL COMMENT '当前待审批节点ID列表', + `flow_id` BIGINT UNSIGNED NOT NULL COMMENT '使用的流程ID', + `form_data` JSON DEFAULT NULL COMMENT '业务表单数据(JSON)', + `attachments` JSON DEFAULT NULL COMMENT '附件列表(JSON)', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_business` (`business_type`, `business_id`), + KEY `idx_applicant` (`applicant_id`), + KEY `idx_status` (`status`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批单表'; + +-- ===================================== +-- 7. 审批记录表(审批轨迹) +-- ===================================== +CREATE TABLE IF NOT EXISTS `approval_record` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `order_id` BIGINT UNSIGNED NOT NULL COMMENT '审批单ID', + `node_id` BIGINT UNSIGNED NOT NULL COMMENT '节点ID', + `node_type` VARCHAR(20) NOT NULL COMMENT '节点类型', + `approver_id` BIGINT UNSIGNED NOT NULL COMMENT '审批人ID', + `action` ENUM('submit', 'approve', 'reject', 'cancel') NOT NULL COMMENT '操作类型', + `comment` VARCHAR(500) DEFAULT NULL COMMENT '审批意见', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_approver_id` (`approver_id`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批记录表'; + +-- ===================================== +-- 8. 出差申请表 +-- ===================================== +CREATE TABLE IF NOT EXISTS `business_trip` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '出差ID', + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '申请人ID', + `reason` VARCHAR(500) NOT NULL COMMENT '出差事由', + `start_date` DATE NOT NULL COMMENT '开始日期', + `end_date` DATE NOT NULL COMMENT '结束日期', + `destination` VARCHAR(200) NOT NULL COMMENT '目的地', + `estimated_cost` DECIMAL(10,2) DEFAULT 0.00 COMMENT '预计费用', + `status` ENUM('draft', 'pending', 'approved', 'rejected', 'cancelled') NOT NULL DEFAULT 'draft' COMMENT '状态', + `approval_order_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '关联审批单ID', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_status` (`status`), + KEY `idx_dates` (`start_date`, `end_date`), + KEY `idx_approval_order_id` (`approval_order_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='出差申请表'; + +-- ===================================== +-- 9. 出差行程明细表 +-- ===================================== +CREATE TABLE IF NOT EXISTS `business_trip_itinerary` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '行程ID', + `business_trip_id` BIGINT UNSIGNED NOT NULL COMMENT '出差申请ID', + `date` DATE NOT NULL COMMENT '行程日期', + `departure` VARCHAR(100) DEFAULT NULL COMMENT '出发地', + `destination` VARCHAR(100) NOT NULL COMMENT '目的地', + `transport` VARCHAR(50) DEFAULT NULL COMMENT '交通方式', + `accommodation` VARCHAR(100) DEFAULT NULL COMMENT '住宿安排', + `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_business_trip_id` (`business_trip_id`), + KEY `idx_date` (`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='出差行程明细表'; + +-- ===================================== +-- 初始化数据 +-- ===================================== + +-- 插入默认公司 +INSERT INTO `company` (`id`, `name`, `status`) VALUES +(1, '示例公司', 1) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); + +-- 插入默认用户 +INSERT INTO `user` (`id`, `company_id`, `name`, `email`, `status`) VALUES +(1, 1, '管理员', 'admin@example.com', 1) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); + +-- 插入默认出差审批流程 +INSERT INTO `approval_flow` (`id`, `business_type`, `name`, `status`) VALUES +(1, 'business-trip', '默认出差审批流程', 1) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); + +-- 插入默认审批节点 +INSERT INTO `approval_node` (`flow_id`, `type`, `name`, `order`, `approvers`) VALUES +(1, 'approver', '部门主管审批', 1, '[]'), +(1, 'approver', '人事审批', 2, '[]') +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`); diff --git a/backend/lang/en.php b/backend/lang/en.php new file mode 100644 index 0000000..f31f2e7 --- /dev/null +++ b/backend/lang/en.php @@ -0,0 +1,75 @@ + [ + 'name' => 'Enterprise Tool System', + 'title' => 'Enterprise Internal Tool Platform', + ], + 'common' => [ + 'success' => 'Operation successful', + 'error' => 'Operation failed', + 'confirm' => 'Confirm', + 'cancel' => 'Cancel', + 'submit' => 'Submit', + 'save' => 'Save', + 'delete' => 'Delete', + 'edit' => 'Edit', + 'search' => 'Search', + 'refresh' => 'Refresh', + 'back' => 'Back', + 'close' => 'Close', + 'loading' => 'Loading...', + 'no_data' => 'No data', + 'operation' => 'Operation', + 'status' => 'Status', + 'create_time' => 'Create Time', + 'update_time' => 'Update Time', + 'remark' => 'Remark', + ], + 'auth' => [ + 'login_success' => 'Login successful', + 'login_failed' => 'Login failed', + 'username_required' => 'Username is required', + 'password_required' => 'Password is required', + 'invalid_credentials' => 'Invalid username or password', + 'token_expired' => 'Session expired, please login again', + 'unauthorized' => 'Unauthorized, please login first', + ], + 'user' => [ + 'not_found' => 'User not found', + 'info_updated' => 'Profile updated successfully', + 'company_switched' => 'Company switched successfully', + ], + 'approval' => [ + 'submit_success' => 'Approval submitted successfully', + 'approve_success' => 'Approved successfully', + 'reject_success' => 'Rejected successfully', + 'cancel_success' => 'Cancelled successfully', + 'not_found' => 'Approval not found', + 'cannot_approve' => 'Cannot approve in current status', + 'cannot_cancel' => 'Cannot cancel in current status', + 'not_owner' => 'Only the applicant can cancel', + 'status' => [ + 'pending' => 'Pending', + 'approved' => 'Approved', + 'rejected' => 'Rejected', + 'cancelled' => 'Cancelled', + ], + ], + 'business_trip' => [ + 'submit_success' => 'Business trip submitted successfully', + 'update_success' => 'Business trip updated successfully', + 'not_found' => 'Business trip not found', + ], + 'error' => [ + 'network_error' => 'Network error, please try again later', + 'server_error' => 'Server error', + 'not_found' => 'Page not found', + 'unknown_error' => 'Unknown error', + 'params_error' => 'Parameter error', + 'validation_error' => 'Validation failed', + ], +]; diff --git a/backend/lang/es.php b/backend/lang/es.php new file mode 100644 index 0000000..249c47f --- /dev/null +++ b/backend/lang/es.php @@ -0,0 +1,75 @@ + [ + 'name' => 'Sistema Empresarial', + 'title' => 'Plataforma de Herramientas Empresariales', + ], + 'common' => [ + 'success' => 'Operación exitosa', + 'error' => 'Operación fallida', + 'confirm' => 'Confirmar', + 'cancel' => 'Cancelar', + 'submit' => 'Enviar', + 'save' => 'Guardar', + 'delete' => 'Eliminar', + 'edit' => 'Editar', + 'search' => 'Buscar', + 'refresh' => 'Actualizar', + 'back' => 'Atrás', + 'close' => 'Cerrar', + 'loading' => 'Cargando...', + 'no_data' => 'Sin datos', + 'operation' => 'Operación', + 'status' => 'Estado', + 'create_time' => 'Fecha de creación', + 'update_time' => 'Fecha de actualización', + 'remark' => 'Observación', + ], + 'auth' => [ + 'login_success' => 'Inicio de sesión exitoso', + 'login_failed' => 'Error de inicio de sesión', + 'username_required' => 'Se requiere usuario', + 'password_required' => 'Se requiere contraseña', + 'invalid_credentials' => 'Usuario o contraseña inválidos', + 'token_expired' => 'Sesión expirada, inicie sesión nuevamente', + 'unauthorized' => 'No autorizado, inicie sesión primero', + ], + 'user' => [ + 'not_found' => 'Usuario no encontrado', + 'info_updated' => 'Perfil actualizado exitosamente', + 'company_switched' => 'Empresa cambiada exitosamente', + ], + 'approval' => [ + 'submit_success' => 'Aprobación enviada exitosamente', + 'approve_success' => 'Aprobado exitosamente', + 'reject_success' => 'Rechazado exitosamente', + 'cancel_success' => 'Cancelado exitosamente', + 'not_found' => 'Aprobación no encontrada', + 'cannot_approve' => 'No se puede aprobar en estado actual', + 'cannot_cancel' => 'No se puede cancelar en estado actual', + 'not_owner' => 'Solo el solicitante puede cancelar', + 'status' => [ + 'pending' => 'Pendiente', + 'approved' => 'Aprobado', + 'rejected' => 'Rechazado', + 'cancelled' => 'Cancelado', + ], + ], + 'business_trip' => [ + 'submit_success' => 'Viaje enviado exitosamente', + 'update_success' => 'Viaje actualizado exitosamente', + 'not_found' => 'Viaje no encontrado', + ], + 'error' => [ + 'network_error' => 'Error de red, inténtelo más tarde', + 'server_error' => 'Error del servidor', + 'not_found' => 'Página no encontrada', + 'unknown_error' => 'Error desconocido', + 'params_error' => 'Error de parámetros', + 'validation_error' => 'Validación fallida', + ], +]; diff --git a/backend/lang/fr.php b/backend/lang/fr.php new file mode 100644 index 0000000..e2d1c2c --- /dev/null +++ b/backend/lang/fr.php @@ -0,0 +1,75 @@ + [ + 'name' => 'Système d\'Entreprise', + 'title' => 'Plateforme d\'Outils Entreprise', + ], + 'common' => [ + 'success' => 'Opération réussie', + 'error' => 'Opération échouée', + 'confirm' => 'Confirmer', + 'cancel' => 'Annuler', + 'submit' => 'Soumettre', + 'save' => 'Enregistrer', + 'delete' => 'Supprimer', + 'edit' => 'Modifier', + 'search' => 'Rechercher', + 'refresh' => 'Actualiser', + 'back' => 'Retour', + 'close' => 'Fermer', + 'loading' => 'Chargement...', + 'no_data' => 'Aucune donnée', + 'operation' => 'Opération', + 'status' => 'Statut', + 'create_time' => 'Date de création', + 'update_time' => 'Date de mise à jour', + 'remark' => 'Remarque', + ], + 'auth' => [ + 'login_success' => 'Connexion réussie', + 'login_failed' => 'Échec de la connexion', + 'username_required' => 'Nom d\'utilisateur requis', + 'password_required' => 'Mot de passe requis', + 'invalid_credentials' => 'Nom d\'utilisateur ou mot de passe invalide', + 'token_expired' => 'Session expirée, veuillez vous reconnecter', + 'unauthorized' => 'Non autorisé, veuillez vous connecter d\'abord', + ], + 'user' => [ + 'not_found' => 'Utilisateur introuvable', + 'info_updated' => 'Profil mis à jour avec succès', + 'company_switched' => 'Entreprise changée avec succès', + ], + 'approval' => [ + 'submit_success' => 'Approbation soumise avec succès', + 'approve_success' => 'Approuvé avec succès', + 'reject_success' => 'Rejeté avec succès', + 'cancel_success' => 'Annulé avec succès', + 'not_found' => 'Approbation introuvable', + 'cannot_approve' => 'Impossible d\'approuver dans cet état', + 'cannot_cancel' => 'Impossible d\'annuler dans cet état', + 'not_owner' => 'Seul le demandeur peut annuler', + 'status' => [ + 'pending' => 'En attente', + 'approved' => 'Approuvé', + 'rejected' => 'Rejeté', + 'cancelled' => 'Annulé', + ], + ], + 'business_trip' => [ + 'submit_success' => 'Déplacement soumis avec succès', + 'update_success' => 'Déplacement mis à jour avec succès', + 'not_found' => 'Déplacement introuvable', + ], + 'error' => [ + 'network_error' => 'Erreur réseau, veuillez réessayer plus tard', + 'server_error' => 'Erreur du serveur', + 'not_found' => 'Page introuvable', + 'unknown_error' => 'Erreur inconnue', + 'params_error' => 'Erreur de paramètres', + 'validation_error' => 'Validation échouée', + ], +]; diff --git a/backend/lang/ru.php b/backend/lang/ru.php new file mode 100644 index 0000000..d7da0c1 --- /dev/null +++ b/backend/lang/ru.php @@ -0,0 +1,75 @@ + [ + 'name' => 'Корпоративная система', + 'title' => 'Корпоративная платформа инструментов', + ], + 'common' => [ + 'success' => 'Операция выполнена успешно', + 'error' => 'Операция не удалась', + 'confirm' => 'Подтвердить', + 'cancel' => 'Отмена', + 'submit' => 'Отправить', + 'save' => 'Сохранить', + 'delete' => 'Удалить', + 'edit' => 'Редактировать', + 'search' => 'Поиск', + 'refresh' => 'Обновить', + 'back' => 'Назад', + 'close' => 'Закрыть', + 'loading' => 'Загрузка...', + 'no_data' => 'Нет данных', + 'operation' => 'Операция', + 'status' => 'Статус', + 'create_time' => 'Время создания', + 'update_time' => 'Время обновления', + 'remark' => 'Примечание', + ], + 'auth' => [ + 'login_success' => 'Вход выполнен успешно', + 'login_failed' => 'Ошибка входа', + 'username_required' => 'Имя пользователя обязательно', + 'password_required' => 'Пароль обязателен', + 'invalid_credentials' => 'Неверное имя пользователя или пароль', + 'token_expired' => 'Сессия истекла, войдите снова', + 'unauthorized' => 'Не авторизован, войдите сначала', + ], + 'user' => [ + 'not_found' => 'Пользователь не найден', + 'info_updated' => 'Профиль обновлен успешно', + 'company_switched' => 'Компания изменена успешно', + ], + 'approval' => [ + 'submit_success' => 'Согласование отправлено успешно', + 'approve_success' => 'Согласовано успешно', + 'reject_success' => 'Отклонено успешно', + 'cancel_success' => 'Отменено успешно', + 'not_found' => 'Согласование не найдено', + 'cannot_approve' => 'Невозможно согласовать в текущем статусе', + 'cannot_cancel' => 'Невозможно отменить в текущем статусе', + 'not_owner' => 'Только заявитель может отменить', + 'status' => [ + 'pending' => 'Ожидает', + 'approved' => 'Согласовано', + 'rejected' => 'Отклонено', + 'cancelled' => 'Отменено', + ], + ], + 'business_trip' => [ + 'submit_success' => 'Командировка отправлена успешно', + 'update_success' => 'Командировка обновлена успешно', + 'not_found' => 'Командировка не найдена', + ], + 'error' => [ + 'network_error' => 'Ошибка сети, попробуйте позже', + 'server_error' => 'Ошибка сервера', + 'not_found' => 'Страница не найдена', + 'unknown_error' => 'Неизвестная ошибка', + 'params_error' => 'Ошибка параметров', + 'validation_error' => 'Ошибка проверки данных', + ], +]; diff --git a/backend/lang/zh.php b/backend/lang/zh.php new file mode 100644 index 0000000..7a36bd6 --- /dev/null +++ b/backend/lang/zh.php @@ -0,0 +1,75 @@ + [ + 'name' => '企业工具系统', + 'title' => '企业内部工具平台', + ], + 'common' => [ + 'success' => '操作成功', + 'error' => '操作失败', + 'confirm' => '确认', + 'cancel' => '取消', + 'submit' => '提交', + 'save' => '保存', + 'delete' => '删除', + 'edit' => '编辑', + 'search' => '搜索', + 'refresh' => '刷新', + 'back' => '返回', + 'close' => '关闭', + 'loading' => '加载中...', + 'no_data' => '暂无数据', + 'operation' => '操作', + 'status' => '状态', + 'create_time' => '创建时间', + 'update_time' => '更新时间', + 'remark' => '备注', + ], + 'auth' => [ + 'login_success' => '登录成功', + 'login_failed' => '登录失败', + 'username_required' => '用户名不能为空', + 'password_required' => '密码不能为空', + 'invalid_credentials' => '用户名或密码错误', + 'token_expired' => '登录已过期,请重新登录', + 'unauthorized' => '未授权,请先登录', + ], + 'user' => [ + 'not_found' => '用户不存在', + 'info_updated' => '个人信息更新成功', + 'company_switched' => '公司切换成功', + ], + 'approval' => [ + 'submit_success' => '审批提交成功', + 'approve_success' => '审批通过', + 'reject_success' => '已拒绝', + 'cancel_success' => '已撤销', + 'not_found' => '审批单不存在', + 'cannot_approve' => '当前状态无法审批', + 'cannot_cancel' => '当前状态无法撤销', + 'not_owner' => '只有申请人可以撤销', + 'status' => [ + 'pending' => '待审批', + 'approved' => '已通过', + 'rejected' => '已拒绝', + 'cancelled' => '已撤销', + ], + ], + 'business_trip' => [ + 'submit_success' => '出差申请提交成功', + 'update_success' => '出差申请更新成功', + 'not_found' => '出差申请不存在', + ], + 'error' => [ + 'network_error' => '网络错误,请稍后重试', + 'server_error' => '服务器错误', + 'not_found' => '页面不存在', + 'unknown_error' => '未知错误', + 'params_error' => '参数错误', + 'validation_error' => '数据验证失败', + ], +]; diff --git a/backend/runtime/webman.pid b/backend/runtime/webman.pid new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/backend/runtime/webman.pid @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/backend/start.php b/backend/start.php new file mode 100644 index 0000000..41ad7ef --- /dev/null +++ b/backend/start.php @@ -0,0 +1,5 @@ +#!/usr/bin/env php + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Dotenv\Dotenv; +use support\Log; +use Webman\Bootstrap; +use Webman\Config; +use Webman\Middleware; +use Webman\Route; +use Webman\Util; +use Workerman\Events\Select; +use Workerman\Worker; + +$worker = $worker ?? null; + +if (empty(Worker::$eventLoopClass)) { + Worker::$eventLoopClass = Select::class; +} + +set_error_handler(function ($level, $message, $file = '', $line = 0) { + if (error_reporting() & $level) { + throw new ErrorException($message, 0, $level, $file, $line); + } +}); + +if ($worker) { + register_shutdown_function(function ($startTime) { + if (time() - $startTime <= 0.1) { + sleep(1); + } + }, time()); +} + +if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) { + if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) { + Dotenv::createUnsafeMutable(base_path(false))->load(); + } else { + Dotenv::createMutable(base_path(false))->load(); + } +} + +Config::clear(); +support\App::loadAllConfig(['route']); +if ($timezone = config('app.default_timezone')) { + date_default_timezone_set($timezone); +} + +foreach (config('autoload.files', []) as $file) { + include_once $file; +} +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['autoload']['files'] ?? [] as $file) { + include_once $file; + } + } + foreach ($projects['autoload']['files'] ?? [] as $file) { + include_once $file; + } +} + +Middleware::load(config('middleware', [])); +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project) || $name === 'static') { + continue; + } + Middleware::load($project['middleware'] ?? []); + } + Middleware::load($projects['middleware'] ?? [], $firm); + if ($staticMiddlewares = config("plugin.$firm.static.middleware")) { + Middleware::load(['__static__' => $staticMiddlewares], $firm); + } +} +Middleware::load(['__static__' => config('static.middleware', [])]); + +foreach (config('bootstrap', []) as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['bootstrap'] ?? [] as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } + } + foreach ($projects['bootstrap'] ?? [] as $className) { + /** @var string $className */ + if (!class_exists($className)) { + $log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } +} + +$directory = base_path() . '/plugin'; +$paths = [config_path()]; +foreach (Util::scanDir($directory) as $path) { + if (is_dir($path = "$path/config")) { + $paths[] = $path; + } +} +Route::load($paths); + diff --git a/backend/webman b/backend/webman new file mode 100755 index 0000000..3f55062 --- /dev/null +++ b/backend/webman @@ -0,0 +1,71 @@ +#!/usr/bin/env php +load(); + } else { + Dotenv::createMutable(run_path())->load(); + } +} + +$appConfig = require $appConfigFile; +if ($timezone = $appConfig['default_timezone'] ?? '') { + date_default_timezone_set($timezone); +} + +if ($errorReporting = $appConfig['error_reporting'] ?? '') { + error_reporting($errorReporting); +} + +if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) { + require_once __DIR__ . '/support/bootstrap.php'; +} else { + if (class_exists('Support\App')) { + Support\App::loadAllConfig(['route']); + } else { + Config::reload(config_path(), ['route', 'container']); + } +} + +$cli = new Command(); +$cli->setName('webman cli'); +$cli->installInternalCommands(); +if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) { + $cli->installCommands($command_path); +} + +foreach (config('plugin', []) as $firm => $projects) { + if (isset($projects['app'])) { + foreach (['', '/app'] as $app) { + if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) { + $command_path = base_path() . "/plugin/$firm{$app}/$command_str"; + $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str"); + } + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + $project['command'] ??= []; + array_walk($project['command'], [$cli, 'createCommandInstance']); + } +} + +$cli->run(); diff --git a/backend/windows.php b/backend/windows.php new file mode 100644 index 0000000..f37a72c --- /dev/null +++ b/backend/windows.php @@ -0,0 +1,136 @@ +load(); + } else { + Dotenv::createMutable(base_path())->load(); + } +} + +App::loadAllConfig(['route']); + +$errorReporting = config('app.error_reporting'); +if (isset($errorReporting)) { + error_reporting($errorReporting); +} + +$runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows'; +$paths = [ + $runtimeProcessPath, + runtime_path('logs'), + runtime_path('views') +]; +foreach ($paths as $path) { + if (!is_dir($path)) { + mkdir($path, 0777, true); + } +} + +$processFiles = []; +if (config('server.listen')) { + $processFiles[] = __DIR__ . DIRECTORY_SEPARATOR . 'start.php'; +} +foreach (config('process', []) as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, ''); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name"); + } + } + foreach ($projects['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm); + } +} + +function write_process_file($runtimeProcessPath, $processName, $firm): string +{ + $processParam = $firm ? "plugin.$firm.$processName" : $processName; + $configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']"; + $fileContent = << true]); + if (!$resource) { + exit("Can not execute $cmd\r\n"); + } + return $resource; +} + +$resource = popen_processes($processFiles); +echo "\r\n"; +while (1) { + sleep(1); + if (!empty($monitor) && $monitor->checkAllFilesChange()) { + $status = proc_get_status($resource); + $pid = $status['pid']; + shell_exec("taskkill /F /T /PID $pid"); + proc_close($resource); + $resource = popen_processes($processFiles); + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c9fe43d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + # PHP 后端服务 + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: x-server-backend + volumes: + - ./backend:/app + ports: + - "8787:8787" + environment: + - DB_HOST=mysql + - DB_PORT=3306 + - DB_DATABASE=x_server_mobile + - DB_USERNAME=root + - DB_PASSWORD=123456 + - REDIS_HOST=redis + - REDIS_PORT=6379 + networks: + - 1panel-network + restart: unless-stopped + +networks: + 1panel-network: + external: true diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..d2e7761 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..072a57e --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..57ec6ca --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4197 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-query": "^5.62.11", + "antd-mobile": "^5.38.1", + "axios": "^1.7.9", + "dayjs": "^1.11.13", + "i18next": "^25.8.13", + "i18next-browser-languagedetector": "^8.2.1", + "postcss-pxtorem": "^6.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-i18next": "^16.5.4", + "react-router-dom": "^7.1.1", + "vconsole": "^1.0.1", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^18.3.28", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.3", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@react-spring/animated": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "license": "MIT", + "dependencies": { + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==", + "license": "MIT" + }, + "node_modules/@react-spring/shared": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "license": "MIT", + "dependencies": { + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==", + "license": "MIT" + }, + "node_modules/@react-spring/web": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.6.1.tgz", + "integrity": "sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", + "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@use-gesture/core": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.0.tgz", + "integrity": "sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.0.tgz", + "integrity": "sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.0" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ahooks": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.9.6.tgz", + "integrity": "sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@types/js-cookie": "^3.0.6", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd-mobile": { + "version": "5.42.3", + "resolved": "https://registry.npmjs.org/antd-mobile/-/antd-mobile-5.42.3.tgz", + "integrity": "sha512-HLykA0Cr2am7dgEch/TRUnr4GHgTOM59rA9CVjgBJVVJ8dk2w8uUBz//Bji19sRH2leiUObWY7KQZpsWF+WQeg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.4.2", + "@rc-component/mini-decimal": "^1.1.0", + "@react-spring/web": "~9.6.1", + "@use-gesture/react": "10.3.0", + "ahooks": "^3.7.6", + "antd-mobile-icons": "^0.3.0", + "antd-mobile-v5-count": "^1.0.1", + "classnames": "^2.3.2", + "dayjs": "^1.11.7", + "deepmerge": "^4.3.1", + "nano-memoize": "^3.0.16", + "rc-field-form": "^1.34.2", + "rc-segmented": "~2.4.1", + "rc-util": "^5.44.4", + "react-fast-compare": "^3.2.2", + "react-is": "^18.2.0", + "runes2": "^1.1.2", + "staged-components": "^1.1.3", + "tslib": "^2.5.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/antd-mobile-icons": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/antd-mobile-icons/-/antd-mobile-icons-0.3.0.tgz", + "integrity": "sha512-rqINQpJWZWrva9moCd1Ye695MZYWmqLPE+bY8d2xLRy7iSQwPsinCdZYjpUPp2zL/LnKYSyXxP2ut2A+DC+whQ==", + "license": "MIT" + }, + "node_modules/antd-mobile-v5-count": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/antd-mobile-v5-count/-/antd-mobile-v5-count-1.0.1.tgz", + "integrity": "sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/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/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.8.13", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.13.tgz", + "integrity": "sha512-E0vzjBY1yM+nsFrtgkjLhST2NBkirkvOVoQa0MSldhsuZ3jUge7ZNpuwG0Cfc74zwo5ZwRzg3uOgT+McBn32iA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.", + "license": "Apache-2.0" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/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/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nano-memoize": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/nano-memoize/-/nano-memoize-3.0.16.tgz", + "integrity": "sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-pxtorem": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-pxtorem/-/postcss-pxtorem-6.1.0.tgz", + "integrity": "sha512-ROODSNci9ADal3zUcPHOF/K83TiCgNSPXQFSbwyPHNV8ioHIE4SaC+FPOufd8jsr5jV2uIz29v1Uqy1c4ov42g==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/rc-field-form": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.44.0.tgz", + "integrity": "sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "async-validator": "^4.1.0", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.4.1.tgz", + "integrity": "sha512-KUi+JJFdKnumV9iXlm+BJ00O4NdVBp2TEexLCk6bK1x/RH83TvYKQMzIz/7m3UTRPD08RM/8VG/JNjWgWbd4cw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-i18next": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.4.tgz", + "integrity": "sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.6.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/runes2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", + "integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/staged-components": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/staged-components/-/staged-components-1.1.3.tgz", + "integrity": "sha512-9EIswzDqjwlEu+ymkV09TTlJfzSbKgEnNteUnZSTxkpMgr5Wx2CzzA9WcMFWBNCldqVPsHVnRGGrApduq2Se5A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vconsole": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/vconsole/-/vconsole-1.3.0.tgz", + "integrity": "sha512-8h2755yEssT3hxpgdFvw8bZxIPaYnJyGe3YT9fTwnBpK8XuOkgPZmUhJN8Lcoq3OWQpQH5utgrCGjTfWsvrKRQ==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..2009c3d --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,41 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.62.11", + "antd-mobile": "^5.38.1", + "axios": "^1.7.9", + "dayjs": "^1.11.13", + "i18next": "^25.8.13", + "i18next-browser-languagedetector": "^8.2.1", + "postcss-pxtorem": "^6.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-i18next": "^16.5.4", + "react-router-dom": "^7.1.1", + "vconsole": "^1.0.1", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^18.3.28", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..5e9a2ff --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,20 @@ +// 动态计算 rem 基准,适应桌面浏览器和真实移动设备 +const setRootValue = () => { + // 桌面浏览器模拟移动端时,使用较小的基准 + // 真实移动设备上,使用标准基准 + return 16 +} + +export default { + plugins: { + 'postcss-pxtorem': { + rootValue: setRootValue(), + propList: ['*'], + selectorBlackList: ['.no-rem'], + replace: true, + mediaQuery: false, + minPixelValue: 1, + exclude: /node_modules/i, + }, + }, +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..60547a6 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,2 @@ +// App.tsx 仅作为路由入口,具体内容在 router/index.tsx 中定义 +export { default } from './router' diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/LanguageSwitcher/LanguageSwitcher.module.css b/frontend/src/components/LanguageSwitcher/LanguageSwitcher.module.css new file mode 100644 index 0000000..6c89003 --- /dev/null +++ b/frontend/src/components/LanguageSwitcher/LanguageSwitcher.module.css @@ -0,0 +1,33 @@ +.container { + background: var(--adm-color-background); +} + +.header { + padding: 20px; + font-size: 16px; + font-weight: 600; + text-align: center; + border-bottom: 1px solid var(--adm-color-border); +} + +.list { + width: 100%; + padding: 10px 0; +} + +.item { + width: 100%; + padding: 12px 20px; + display: flex; + align-items: center; + gap: 12px; +} + +.flag { + font-size: 20px; +} + +.name { + font-size: 15px; + flex: 1; +} diff --git a/frontend/src/components/LanguageSwitcher/index.tsx b/frontend/src/components/LanguageSwitcher/index.tsx new file mode 100644 index 0000000..9c025b0 --- /dev/null +++ b/frontend/src/components/LanguageSwitcher/index.tsx @@ -0,0 +1,49 @@ +import { Popup, Radio, Space } from 'antd-mobile' +import { useTranslation } from 'react-i18next' +import styles from './LanguageSwitcher.module.css' + +const languages = [ + { code: 'zh', name: '简体中文', flag: '🇨🇳' }, + { code: 'en', name: 'English', flag: '🇬🇧' }, + { code: 'ru', name: 'Русский', flag: '🇷🇺' }, + { code: 'es', name: 'Español', flag: '🇪🇸' }, + { code: 'fr', name: 'Français', flag: '🇫🇷' }, +] + +export default function LanguageSwitcher({ onClose }: { onClose?: () => void }) { + const { i18n, t } = useTranslation() + const currentLang = i18n.language + + const handleLanguageChange = (langCode: string) => { + i18n.changeLanguage(langCode) + onClose?.() + } + + return ( +
+
{t('settings.language')}
+ handleLanguageChange(val)}> + + {languages.map((lang) => ( + + {lang.flag} + {lang.name} + + ))} + + +
+ ) +} + +export function showLanguageSwitcher() { + Popup.show({ + content: Popup.close()} />, + position: 'bottom', + }) +} diff --git a/frontend/src/components/layout/Header.module.css b/frontend/src/components/layout/Header.module.css new file mode 100644 index 0000000..4716980 --- /dev/null +++ b/frontend/src/components/layout/Header.module.css @@ -0,0 +1,36 @@ +.header { + position: sticky; + top: 0; + z-index: 100; + display: flex; + align-items: center; + height: 44px; + padding: 0 12px; + background: linear-gradient(180deg, #1677ff 0%, #0958d9 100%); + color: #fff; +} + +.backBtn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: none; + background: transparent; + color: #fff; + cursor: pointer; + font-size: 20px; +} + +.title { + flex: 1; + font-size: 17px; + font-weight: 500; + text-align: center; +} + +.extra { + width: 32px; +} diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx new file mode 100644 index 0000000..4303559 --- /dev/null +++ b/frontend/src/components/layout/Header.tsx @@ -0,0 +1,26 @@ +import type { ReactNode } from 'react' +import { LeftOutline } from 'antd-mobile-icons' +import { useTranslation } from 'react-i18next' +import styles from './Header.module.css' + +interface HeaderProps { + title?: string + onBack?: () => void + extra?: ReactNode +} + +export default function Header({ title, onBack, extra }: HeaderProps) { + const { t } = useTranslation() + + return ( +
+ {onBack && ( + + )} +

{title || t('app.name')}

+ {extra &&
{extra}
} +
+ ) +} diff --git a/frontend/src/components/layout/MobileLayout.module.css b/frontend/src/components/layout/MobileLayout.module.css new file mode 100644 index 0000000..0e03487 --- /dev/null +++ b/frontend/src/components/layout/MobileLayout.module.css @@ -0,0 +1,24 @@ +.layout { + display: flex; + flex-direction: column; + min-height: 100vh; + height: 100vh; + background-color: #f5f5f5; + overflow: hidden; +} + +.main { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + /* TabBar 高度 50px + 安全区域 */ + padding-bottom: calc(50px + env(safe-area-inset-bottom)); + /* 确保滚动位置在顶部 */ + scroll-behavior: smooth; +} + +/* 重置滚动位置 */ +.main::-webkit-scrollbar { + width: 0; + display: none; +} diff --git a/frontend/src/components/layout/MobileLayout.tsx b/frontend/src/components/layout/MobileLayout.tsx new file mode 100644 index 0000000..0dbcd01 --- /dev/null +++ b/frontend/src/components/layout/MobileLayout.tsx @@ -0,0 +1,31 @@ +import { Outlet, useNavigate, useLocation } from 'react-router-dom' +import { useEffect, useRef } from 'react' +import TabBar from './TabBar' +import Header from './Header' +import styles from './MobileLayout.module.css' + +export default function MobileLayout() { + const navigate = useNavigate() + const location = useLocation() + const mainRef = useRef(null) + + const showHeader = location.pathname !== '/login' + const showTabBar = !location.pathname.includes('/business-trip/') + + // 路由变化时滚动到顶部 + useEffect(() => { + if (mainRef.current) { + mainRef.current.scrollTop = 0 + } + }, [location.pathname]) + + return ( +
+ {showHeader &&
navigate(-1)} />} +
+ +
+ {showTabBar && } +
+ ) +} diff --git a/frontend/src/components/layout/TabBar.module.css b/frontend/src/components/layout/TabBar.module.css new file mode 100644 index 0000000..9fda627 --- /dev/null +++ b/frontend/src/components/layout/TabBar.module.css @@ -0,0 +1,36 @@ +.tabBar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 100; + display: flex; + height: 50px; + padding-bottom: env(safe-area-inset-bottom); + background: #fff; + border-top: 1px solid #eee; +} + +.tab { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + color: #999; + cursor: pointer; + transition: color 0.2s; +} + +.tab.active { + color: #1677ff; +} + +.icon { + font-size: 22px; +} + +.label { + font-size: 11px; +} diff --git a/frontend/src/components/layout/TabBar.tsx b/frontend/src/components/layout/TabBar.tsx new file mode 100644 index 0000000..e00a810 --- /dev/null +++ b/frontend/src/components/layout/TabBar.tsx @@ -0,0 +1,47 @@ +import { useNavigate, useLocation } from 'react-router-dom' +import { AppOutline, AppstoreOutline, UserOutline } from 'antd-mobile-icons' +import { useTranslation } from 'react-i18next' +import styles from './TabBar.module.css' + +interface TabItem { + key: string + path: string + icon: React.ReactNode + labelKey: string +} + +const tabs: TabItem[] = [ + { key: 'home', path: '/dashboard', icon: , labelKey: 'tab.home' }, + { key: 'apps', path: '/apps', icon: , labelKey: 'tab.apps' }, + { key: 'profile', path: '/profile', icon: , labelKey: 'tab.profile' }, +] + +export default function TabBar() { + const navigate = useNavigate() + const location = useLocation() + const { t } = useTranslation() + + const activeTab = tabs.find((tab) => location.pathname.startsWith(tab.path))?.key || 'home' + + const handleTabChange = (key: string) => { + const tab = tabs.find((t) => t.key === key) + if (tab) { + navigate(tab.path) + } + } + + return ( +
+ {tabs.map((tab) => ( +
handleTabChange(tab.key)} + > + {tab.icon} + {t(tab.labelKey)} +
+ ))} +
+ ) +} diff --git a/frontend/src/config/index.ts b/frontend/src/config/index.ts new file mode 100644 index 0000000..c759839 --- /dev/null +++ b/frontend/src/config/index.ts @@ -0,0 +1,17 @@ +// API 配置 +export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api/v1' +export const API_TIMEOUT = 15000 + +// 应用配置 +export const APP_NAME = '企业工具台' +export const APP_VERSION = '1.0.0' + +// 环境配置 +export const isDev = import.meta.env.DEV +export const isProd = import.meta.env.PROD + +// 飞书配置 +export const FEISHU_APP_ID = import.meta.env.VITE_FEISHU_APP_ID || '' + +// rem 配置(移动端适配) +export const REM_BASE = 37.5 // 750px 设计稿基准 diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts new file mode 100644 index 0000000..384a0db --- /dev/null +++ b/frontend/src/i18n/index.ts @@ -0,0 +1,39 @@ +/** + * 国际化配置 + * 支持:中文(zh)、英文(en)、俄语(ru)、西语(es)、法语(fr) + */ +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import zh from './locales/zh.json' +import en from './locales/en.json' +import ru from './locales/ru.json' +import es from './locales/es.json' +import fr from './locales/fr.json' + +const resources = { + zh: { translation: zh }, + en: { translation: en }, + ru: { translation: ru }, + es: { translation: es }, + fr: { translation: fr }, +} + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources, + fallbackLng: 'zh', + lng: 'zh', + debug: import.meta.env.DEV, + interpolation: { + escapeValue: false, + }, + detection: { + order: ['localStorage', 'navigator'], + caches: ['localStorage'], + }, + }) + +export default i18n diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json new file mode 100644 index 0000000..33f569b --- /dev/null +++ b/frontend/src/i18n/locales/en.json @@ -0,0 +1,150 @@ +{ + "app": { + "name": "Enterprise Tool System", + "title": "Enterprise Internal Tool Platform" + }, + "tab": { + "home": "Home", + "apps": "Apps", + "profile": "Profile" + }, + "common": { + "loading": "Loading...", + "confirm": "Confirm", + "cancel": "Cancel", + "submit": "Submit", + "save": "Save", + "delete": "Delete", + "edit": "Edit", + "search": "Search", + "refresh": "Refresh", + "back": "Back", + "close": "Close", + "select": "Select", + "all": "All", + "more": "More", + "total": "{{count}} items in total", + "noData": "No data", + "operation": "Operation", + "status": "Status", + "remark": "Remark", + "createTime": "Create Time", + "updateTime": "Update Time" + }, + "dashboard": { + "welcome": "Welcome Back", + "greeting": "Hello, {{name}}", + "quickActions": "Quick Actions", + "myTodos": "My Todos", + "myApprovals": "My Approvals", + "notifications": "Notifications", + "recentlyUsed": "Recently Used" + }, + "apps": { + "title": "App Center", + "businessTrip": "Business Trip", + "reimbursement": "Reimbursement", + "leave": "Leave Request", + "overtime": "Overtime", + "purchase": "Purchase Request", + "approval": "Approval", + "report": "Reports", + "settings": "Settings", + "addressBook": "Address Book", + "notice": "Notices", + "calendar": "Calendar", + "file": "Files" + }, + "businessTrip": { + "title": "Business Trip", + "apply": "Apply", + "myTrips": "My Trips", + "pendingApproval": "Pending", + "approved": "Approved", + "rejected": "Rejected", + "cancelled": "Cancelled", + "tripTitle": "Trip Title", + "destination": "Destination", + "startDate": "Start Date", + "endDate": "End Date", + "duration": "Duration", + "reason": "Reason", + "budget": "Budget", + "submitter": "Applicant", + "submitTime": "Submit Time", + "approver": "Approver", + "approvalStatus": "Status", + "approvalComment": "Comment", + "confirmSubmit": "Confirm to submit?", + "confirmCancel": "Confirm to cancel?", + "submitSuccess": "Submitted successfully", + "cancelSuccess": "Cancelled successfully" + }, + "approval": { + "title": "Approval", + "myApproval": "My Approval", + "pending": "Pending", + "approved": "Approved", + "rejected": "Rejected", + "detail": "Detail", + "approve": "Approve", + "reject": "Reject", + "comment": "Comment", + "submitComment": "Submit", + "confirmApprove": "Confirm to approve?", + "confirmReject": "Confirm to reject?", + "approveSuccess": "Approved", + "rejectSuccess": "Rejected", + "noPending": "No pending approvals", + "status": { + "pending": "Pending", + "approved": "Approved", + "rejected": "Rejected", + "cancelled": "Cancelled" + } + }, + "profile": { + "title": "Profile", + "myInfo": "My Info", + "myMessages": "Messages", + "settings": "Settings", + "about": "About", + "logout": "Logout", + "language": "Language", + "switchCompany": "Switch Company", + "notifications": "Notifications", + "security": "Security" + }, + "login": { + "title": "Login", + "username": "Username", + "password": "Password", + "loginBtn": "Login", + "feishuLogin": "Feishu Login", + "loginSuccess": "Login successful", + "loginFailed": "Login failed" + }, + "settings": { + "language": "Language", + "theme": "Theme", + "notifications": "Notifications" + }, + "error": { + "networkError": "Network error, please try again later", + "serverError": "Server error", + "unauthorized": "Unauthorized, please login first", + "forbidden": "Forbidden", + "notFound": "Page not found", + "unknownError": "Unknown error" + }, + "time": { + "today": "Today", + "yesterday": "Yesterday", + "thisWeek": "This Week", + "lastWeek": "Last Week", + "thisMonth": "This Month", + "lastMonth": "Last Month", + "thisYear": "This Year", + "lastYear": "Last Year" + } +} diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json new file mode 100644 index 0000000..722e630 --- /dev/null +++ b/frontend/src/i18n/locales/es.json @@ -0,0 +1,150 @@ +{ + "app": { + "name": "Sistema Empresarial", + "title": "Plataforma de Herramientas Empresariales" + }, + "tab": { + "home": "Inicio", + "apps": "Aplicaciones", + "profile": "Perfil" + }, + "common": { + "loading": "Cargando...", + "confirm": "Confirmar", + "cancel": "Cancelar", + "submit": "Enviar", + "save": "Guardar", + "delete": "Eliminar", + "edit": "Editar", + "search": "Buscar", + "refresh": "Actualizar", + "back": "Atrás", + "close": "Cerrar", + "select": "Seleccionar", + "all": "Todos", + "more": "Más", + "total": "{{count}} en total", + "noData": "Sin datos", + "operation": "Operación", + "status": "Estado", + "remark": "Observación", + "createTime": "Fecha de creación", + "updateTime": "Fecha de actualización" + }, + "dashboard": { + "welcome": "Bienvenido de nuevo", + "greeting": "Hola, {{name}}", + "quickActions": "Acciones rápidas", + "myTodos": "Mis pendientes", + "myApprovals": "Mis aprobaciones", + "notifications": "Notificaciones", + "recentlyUsed": "Usados recientemente" + }, + "apps": { + "title": "Centro de Aplicaciones", + "businessTrip": "Viaje de Negocios", + "reimbursement": "Reembolso", + "leave": "Solicitud de Permiso", + "overtime": "Horas Extra", + "purchase": "Solicitud de Compra", + "approval": "Aprobaciones", + "report": "Reportes", + "settings": "Configuración", + "addressBook": "Directorio", + "notice": "Avisos", + "calendar": "Calendario", + "file": "Archivos" + }, + "businessTrip": { + "title": "Viaje de Negocios", + "apply": "Solicitar", + "myTrips": "Mis Viajes", + "pendingApproval": "Pendiente", + "approved": "Aprobado", + "rejected": "Rechazado", + "cancelled": "Cancelado", + "tripTitle": "Título del Viaje", + "destination": "Destino", + "startDate": "Fecha de Inicio", + "endDate": "Fecha de Fin", + "duration": "Duración", + "reason": "Motivo", + "budget": "Presupuesto", + "submitter": "Solicitante", + "submitTime": "Fecha de Solicitud", + "approver": "Aprobador", + "approvalStatus": "Estado de Aprobación", + "approvalComment": "Comentario", + "confirmSubmit": "¿Confirmar envío?", + "confirmCancel": "¿Confirmar cancelación?", + "submitSuccess": "Enviado exitosamente", + "cancelSuccess": "Cancelado exitosamente" + }, + "approval": { + "title": "Aprobaciones", + "myApproval": "Mis Aprobaciones", + "pending": "Pendiente", + "approved": "Aprobado", + "rejected": "Rechazado", + "detail": "Detalle", + "approve": "Aprobar", + "reject": "Rechazar", + "comment": "Comentario", + "submitComment": "Enviar", + "confirmApprove": "¿Confirmar aprobación?", + "confirmReject": "¿Confirmar rechazo?", + "approveSuccess": "Aprobado", + "rejectSuccess": "Rechazado", + "noPending": "Sin aprobaciones pendientes", + "status": { + "pending": "Pendiente", + "approved": "Aprobado", + "rejected": "Rechazado", + "cancelled": "Cancelado" + } + }, + "profile": { + "title": "Mi Perfil", + "myInfo": "Mi Información", + "myMessages": "Mis Mensajes", + "settings": "Configuración", + "about": "Acerca de", + "logout": "Cerrar Sesión", + "language": "Idioma", + "switchCompany": "Cambiar Empresa", + "notifications": "Notificaciones", + "security": "Seguridad" + }, + "login": { + "title": "Iniciar Sesión", + "username": "Usuario", + "password": "Contraseña", + "loginBtn": "Entrar", + "feishuLogin": "Entrar con Feishu", + "loginSuccess": "Inicio de sesión exitoso", + "loginFailed": "Error de inicio de sesión" + }, + "settings": { + "language": "Idioma", + "theme": "Tema", + "notifications": "Notificaciones" + }, + "error": { + "networkError": "Error de red, inténtelo más tarde", + "serverError": "Error del servidor", + "unauthorized": "No autorizado, inicie sesión primero", + "forbidden": "Acceso prohibido", + "notFound": "Página no encontrada", + "unknownError": "Error desconocido" + }, + "time": { + "today": "Hoy", + "yesterday": "Ayer", + "thisWeek": "Esta Semana", + "lastWeek": "Semana Pasada", + "thisMonth": "Este Mes", + "lastMonth": "Mes Pasado", + "thisYear": "Este Año", + "lastYear": "Año Pasado" + } +} diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json new file mode 100644 index 0000000..f862af0 --- /dev/null +++ b/frontend/src/i18n/locales/fr.json @@ -0,0 +1,150 @@ +{ + "app": { + "name": "Système d'Entreprise", + "title": "Plateforme d'Outils Entreprise" + }, + "tab": { + "home": "Accueil", + "apps": "Applications", + "profile": "Profil" + }, + "common": { + "loading": "Chargement...", + "confirm": "Confirmer", + "cancel": "Annuler", + "submit": "Soumettre", + "save": "Enregistrer", + "delete": "Supprimer", + "edit": "Modifier", + "search": "Rechercher", + "refresh": "Actualiser", + "back": "Retour", + "close": "Fermer", + "select": "Sélectionner", + "all": "Tous", + "more": "Plus", + "total": "{{count}} au total", + "noData": "Aucune donnée", + "operation": "Opération", + "status": "Statut", + "remark": "Remarque", + "createTime": "Date de création", + "updateTime": "Date de mise à jour" + }, + "dashboard": { + "welcome": "Bienvenue", + "greeting": "Bonjour, {{name}}", + "quickActions": "Actions Rapides", + "myTodos": "Mes Tâches", + "myApprovals": "Mes Approbations", + "notifications": "Notifications", + "recentlyUsed": "Récemment Utilisés" + }, + "apps": { + "title": "Centre d'Applications", + "businessTrip": "Déplacement", + "reimbursement": "Remboursement", + "leave": "Demande de Congé", + "overtime": "Heures Supplémentaires", + "purchase": "Demande d'Achat", + "approval": "Approbations", + "report": "Rapports", + "settings": "Paramètres", + "addressBook": "Contacts", + "notice": "Annonces", + "calendar": "Calendrier", + "file": "Fichiers" + }, + "businessTrip": { + "title": "Déplacement Professionnel", + "apply": "Demander", + "myTrips": "Mes Déplacements", + "pendingApproval": "En Attente", + "approved": "Approuvé", + "rejected": "Rejeté", + "cancelled": "Annulé", + "tripTitle": "Titre du Déplacement", + "destination": "Destination", + "startDate": "Date de Début", + "endDate": "Date de Fin", + "duration": "Durée", + "reason": "Motif", + "budget": "Budget", + "submitter": "Demandeur", + "submitTime": "Date de Soumission", + "approver": "Approbateur", + "approvalStatus": "Statut d'Approbation", + "approvalComment": "Commentaire", + "confirmSubmit": "Confirmer la soumission?", + "confirmCancel": "Confirmer l'annulation?", + "submitSuccess": "Soumis avec succès", + "cancelSuccess": "Annulé avec succès" + }, + "approval": { + "title": "Approbations", + "myApproval": "Mes Approbations", + "pending": "En Attente", + "approved": "Approuvé", + "rejected": "Rejeté", + "detail": "Détail", + "approve": "Approuver", + "reject": "Rejeter", + "comment": "Commentaire", + "submitComment": "Soumettre", + "confirmApprove": "Confirmer l'approbation?", + "confirmReject": "Confirmer le rejet?", + "approveSuccess": "Approuvé", + "rejectSuccess": "Rejeté", + "noPending": "Aucune approbation en attente", + "status": { + "pending": "En Attente", + "approved": "Approuvé", + "rejected": "Rejeté", + "cancelled": "Annulé" + } + }, + "profile": { + "title": "Mon Profil", + "myInfo": "Mes Informations", + "myMessages": "Mes Messages", + "settings": "Paramètres", + "about": "À Propos", + "logout": "Déconnexion", + "language": "Langue", + "switchCompany": "Changer d'Entreprise", + "notifications": "Notifications", + "security": "Sécurité" + }, + "login": { + "title": "Connexion", + "username": "Nom d'utilisateur", + "password": "Mot de passe", + "loginBtn": "Se Connecter", + "feishuLogin": "Connexion Feishu", + "loginSuccess": "Connexion réussie", + "loginFailed": "Échec de la connexion" + }, + "settings": { + "language": "Langue", + "theme": "Thème", + "notifications": "Notifications" + }, + "error": { + "networkError": "Erreur réseau, veuillez réessayer plus tard", + "serverError": "Erreur du serveur", + "unauthorized": "Non autorisé, veuillez vous connecter d'abord", + "forbidden": "Accès interdit", + "notFound": "Page non trouvée", + "unknownError": "Erreur inconnue" + }, + "time": { + "today": "Aujourd'hui", + "yesterday": "Hier", + "thisWeek": "Cette Semaine", + "lastWeek": "Semaine Dernière", + "thisMonth": "Ce Mois", + "lastMonth": "Mois Dernier", + "thisYear": "Cette Année", + "lastYear": "Année Dernière" + } +} diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json new file mode 100644 index 0000000..6210b68 --- /dev/null +++ b/frontend/src/i18n/locales/ru.json @@ -0,0 +1,150 @@ +{ + "app": { + "name": "Корпоративная система", + "title": "Корпоративная платформа инструментов" + }, + "tab": { + "home": "Главная", + "apps": "Приложения", + "profile": "Профиль" + }, + "common": { + "loading": "Загрузка...", + "confirm": "Подтвердить", + "cancel": "Отмена", + "submit": "Отправить", + "save": "Сохранить", + "delete": "Удалить", + "edit": "Редактировать", + "search": "Поиск", + "refresh": "Обновить", + "back": "Назад", + "close": "Закрыть", + "select": "Выбрать", + "all": "Все", + "more": "Больше", + "total": "Всего {{count}}", + "noData": "Нет данных", + "operation": "Операция", + "status": "Статус", + "remark": "Примечание", + "createTime": "Время создания", + "updateTime": "Время обновления" + }, + "dashboard": { + "welcome": "Добро пожаловать", + "greeting": "Здравствуйте, {{name}}", + "quickActions": "Быстрые действия", + "myTodos": "Мои задачи", + "myApprovals": "Мои согласования", + "notifications": "Уведомления", + "recentlyUsed": "Недавно использованные" + }, + "apps": { + "title": "Центр приложений", + "businessTrip": "Командировка", + "reimbursement": "Возмещение расходов", + "leave": "Отпуск", + "overtime": "Сверхурочные", + "purchase": "Заявка на покупку", + "approval": "Согласование", + "report": "Отчеты", + "settings": "Настройки", + "addressBook": "Адресная книга", + "notice": "Объявления", + "calendar": "Календарь", + "file": "Файлы" + }, + "businessTrip": { + "title": "Командировка", + "apply": "Подать заявку", + "myTrips": "Мои командировки", + "pendingApproval": "Ожидает согласования", + "approved": "Согласовано", + "rejected": "Отклонено", + "cancelled": "Отменено", + "tripTitle": "Название командировки", + "destination": "Место назначения", + "startDate": "Дата начала", + "endDate": "Дата окончания", + "duration": "Продолжительность", + "reason": "Причина", + "budget": "Бюджет", + "submitter": "Заявитель", + "submitTime": "Время подачи", + "approver": "Согласующий", + "approvalStatus": "Статус согласования", + "approvalComment": "Комментарий", + "confirmSubmit": "Подтвердить отправку?", + "confirmCancel": "Подтвердить отмену?", + "submitSuccess": "Успешно отправлено", + "cancelSuccess": "Успешно отменено" + }, + "approval": { + "title": "Согласование", + "myApproval": "Мои согласования", + "pending": "Ожидает", + "approved": "Согласовано", + "rejected": "Отклонено", + "detail": "Детали", + "approve": "Согласовать", + "reject": "Отклонить", + "comment": "Комментарий", + "submitComment": "Отправить", + "confirmApprove": "Подтвердить согласование?", + "confirmReject": "Подтвердить отклонение?", + "approveSuccess": "Согласовано", + "rejectSuccess": "Отклонено", + "noPending": "Нет ожидающих согласований", + "status": { + "pending": "Ожидает", + "approved": "Согласовано", + "rejected": "Отклонено", + "cancelled": "Отменено" + } + }, + "profile": { + "title": "Профиль", + "myInfo": "Моя информация", + "myMessages": "Мои сообщения", + "settings": "Настройки", + "about": "О программе", + "logout": "Выйти", + "language": "Язык", + "switchCompany": "Сменить компанию", + "notifications": "Уведомления", + "security": "Безопасность" + }, + "login": { + "title": "Вход", + "username": "Имя пользователя", + "password": "Пароль", + "loginBtn": "Войти", + "feishuLogin": "Войти через Feishu", + "loginSuccess": "Успешный вход", + "loginFailed": "Ошибка входа" + }, + "settings": { + "language": "Язык", + "theme": "Тема", + "notifications": "Уведомления" + }, + "error": { + "networkError": "Ошибка сети, попробуйте позже", + "serverError": "Ошибка сервера", + "unauthorized": "Не авторизован, войдите сначала", + "forbidden": "Доступ запрещен", + "notFound": "Страница не найдена", + "unknownError": "Неизвестная ошибка" + }, + "time": { + "today": "Сегодня", + "yesterday": "Вчера", + "thisWeek": "На этой неделе", + "lastWeek": "На прошлой неделе", + "thisMonth": "В этом месяце", + "lastMonth": "В прошлом месяце", + "thisYear": "В этом году", + "lastYear": "В прошлом году" + } +} diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json new file mode 100644 index 0000000..d6fa518 --- /dev/null +++ b/frontend/src/i18n/locales/zh.json @@ -0,0 +1,150 @@ +{ + "app": { + "name": "企业工具系统", + "title": "企业内部工具平台" + }, + "tab": { + "home": "首页", + "apps": "应用", + "profile": "我的" + }, + "common": { + "loading": "加载中...", + "confirm": "确认", + "cancel": "取消", + "submit": "提交", + "save": "保存", + "delete": "删除", + "edit": "编辑", + "search": "搜索", + "refresh": "刷新", + "back": "返回", + "close": "关闭", + "select": "请选择", + "all": "全部", + "more": "更多", + "total": "共 {{count}} 条", + "noData": "暂无数据", + "operation": "操作", + "status": "状态", + "remark": "备注", + "createTime": "创建时间", + "updateTime": "更新时间" + }, + "dashboard": { + "welcome": "欢迎回来", + "greeting": "你好,{{name}}", + "quickActions": "快捷操作", + "myTodos": "我的待办", + "myApprovals": "我的审批", + "notifications": "通知消息", + "recentlyUsed": "最近使用" + }, + "apps": { + "title": "应用中心", + "businessTrip": "出差申请", + "reimbursement": "费用报销", + "leave": "请假申请", + "overtime": "加班申请", + "purchase": "采购申请", + "approval": "审批管理", + "report": "数据报表", + "settings": "系统设置", + "addressBook": "通讯录", + "notice": "通知公告", + "calendar": "日程安排", + "file": "文件管理" + }, + "businessTrip": { + "title": "出差申请", + "apply": "申请出差", + "myTrips": "我的出差", + "pendingApproval": "待审批", + "approved": "已批准", + "rejected": "已拒绝", + "cancelled": "已撤销", + "tripTitle": "出差标题", + "destination": "目的地", + "startDate": "开始日期", + "endDate": "结束日期", + "duration": "出差天数", + "reason": "出差事由", + "budget": "预算金额", + "submitter": "申请人", + "submitTime": "申请时间", + "approver": "审批人", + "approvalStatus": "审批状态", + "approvalComment": "审批意见", + "confirmSubmit": "确认提交出差申请?", + "confirmCancel": "确认撤销此申请?", + "submitSuccess": "提交成功", + "cancelSuccess": "撤销成功" + }, + "approval": { + "title": "审批管理", + "myApproval": "我的审批", + "pending": "待审批", + "approved": "已通过", + "rejected": "已拒绝", + "detail": "审批详情", + "approve": "通过", + "reject": "拒绝", + "comment": "审批意见", + "submitComment": "提交意见", + "confirmApprove": "确认通过此申请?", + "confirmReject": "确认拒绝此申请?", + "approveSuccess": "审批通过", + "rejectSuccess": "已拒绝", + "noPending": "暂无待审批事项", + "status": { + "pending": "待审批", + "approved": "已通过", + "rejected": "已拒绝", + "cancelled": "已撤销" + } + }, + "profile": { + "title": "个人中心", + "myInfo": "我的信息", + "myMessages": "我的消息", + "settings": "设置", + "about": "关于", + "logout": "退出登录", + "language": "语言设置", + "switchCompany": "切换公司", + "notifications": "消息通知", + "security": "账号安全" + }, + "login": { + "title": "登录", + "username": "用户名", + "password": "密码", + "loginBtn": "登录", + "feishuLogin": "飞书快捷登录", + "loginSuccess": "登录成功", + "loginFailed": "登录失败" + }, + "settings": { + "language": "语言", + "theme": "主题", + "notifications": "通知设置" + }, + "error": { + "networkError": "网络错误,请稍后重试", + "serverError": "服务器错误", + "unauthorized": "未授权,请先登录", + "forbidden": "无权访问", + "notFound": "页面不存在", + "unknownError": "未知错误" + }, + "time": { + "today": "今天", + "yesterday": "昨天", + "thisWeek": "本周", + "lastWeek": "上周", + "thisMonth": "本月", + "lastMonth": "上月", + "thisYear": "今年", + "lastYear": "去年" + } +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..10eed7f --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,68 @@ +/* 移动端基础样式重置 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + font-size: 16px; /* 基础字体大小 */ +} + +html, +body { + width: 100%; + height: 100%; +} + +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-size: 16px; /* 提高基础字体大小 */ + line-height: 1.5; + color: #262626; + background-color: #f5f5f5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-size-adjust: 100%; /* 防止 iOS 自动调整字体大小 */ +} + +#root { + width: 100%; + height: 100%; +} + +/* 链接样式 */ +a { + color: #1677ff; + text-decoration: none; +} + +a:active { + color: #0958d9; +} + +/* 按钮重置 */ +button { + border: none; + background: none; + padding: 0; + font: inherit; + cursor: pointer; + outline: none; +} + +input, +textarea, +select { + font: inherit; + outline: none; +} + +/* 安全区域适配 */ +@supports (bottom: env(safe-area-inset-bottom)) { + body { + padding-bottom: env(safe-area-inset-bottom); + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..97c2376 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,25 @@ +import { StrictMode, useEffect } from 'react' +import { createRoot } from 'react-dom/client' +import { RouterProvider } from 'react-router-dom' +import router from './router' +import { initRem } from './utils/rem' +import './i18n' +import './index.css' + +// 初始化 rem 适配 +initRem() + +function App() { + useEffect(() => { + // 确保在 DOM 加载后再次设置 + initRem() + }, []) + + return +} + +createRoot(document.getElementById('root')!).render( + + + +) diff --git a/frontend/src/modules/approval/components/ApprovalAction.module.css b/frontend/src/modules/approval/components/ApprovalAction.module.css new file mode 100644 index 0000000..6a745c8 --- /dev/null +++ b/frontend/src/modules/approval/components/ApprovalAction.module.css @@ -0,0 +1,59 @@ +.actions { + display: flex; + gap: 12px; + padding: 16px; + background: #fff; + border-radius: 12px; + position: sticky; + bottom: 0; + z-index: 10; +} + +.btn { + flex: 1; + padding: 12px; + border: none; + border-radius: 8px; + font-size: 15px; + font-weight: 500; + cursor: pointer; + transition: opacity 0.2s; +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.approveBtn { + background: #52c41a; + color: #fff; +} + +.rejectBtn { + background: #ff4d4f; + color: #fff; +} + +.cancelBtn { + background: #8c8c8c; + color: #fff; +} + +.modalContent { + padding: 16px 0; +} + +.textarea { + width: 100%; + padding: 10px; + border: 1px solid #e5e5e5; + border-radius: 8px; + font-size: 14px; + resize: none; +} + +.textarea:focus { + outline: none; + border-color: #1677ff; +} diff --git a/frontend/src/modules/approval/components/ApprovalAction.tsx b/frontend/src/modules/approval/components/ApprovalAction.tsx new file mode 100644 index 0000000..9029547 --- /dev/null +++ b/frontend/src/modules/approval/components/ApprovalAction.tsx @@ -0,0 +1,111 @@ +import { useApprovalAction } from '../hooks/useApprovalAction' +import type { ApprovalStatus } from '../types' +import { Modal } from 'antd-mobile' +import { useState } from 'react' +import styles from './ApprovalAction.module.css' + +interface ApprovalActionProps { + orderId: string + status: ApprovalStatus +} + +export default function ApprovalAction({ orderId, status }: ApprovalActionProps) { + const { approve, reject, cancel, isApproving, isRejecting, isCancelling } = useApprovalAction() + const [comment, setComment] = useState('') + const [showModal, setShowModal] = useState(false) + const [actionType, setActionType] = useState<'approve' | 'reject'>('approve') + + const canAction = status === 'pending' + const canCancel = status === 'pending' || status === 'draft' + + const handleApprove = () => { + approve({ orderId, comment: comment || undefined }) + setShowModal(false) + setComment('') + } + + const handleReject = () => { + reject({ orderId, comment: comment || '已拒绝' }) + setShowModal(false) + setComment('') + } + + const handleCancel = () => { + if (confirm('确定要撤销此申请吗?')) { + cancel(orderId) + } + } + + const openModal = (action: 'approve' | 'reject') => { + setActionType(action) + setShowModal(true) + } + + if (!canAction && !canCancel) { + return null + } + + return ( + <> +
+ {canAction && ( + <> + + + + )} + {canCancel && !canAction && ( + + )} +
+ + setShowModal(false)} + content={ +
+