commit c2dc89397bfaa8932f762cc2a48295b50d696eb8 Author: well <347471159@qq.com> Date: Fri Mar 27 10:38:12 2026 +0800 first commit diff --git a/.env b/.env new file mode 100644 index 0000000..9f3936c --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +# Vite 环境变量配置 +# 以 VITE_ 开头的变量会在客户端代码中通过 import.meta.env 访问 +# 在 vite.config.ts 中可以通过 process.env 访问 + +# API 基础地址 +# 开发环境默认: http://127.0.0.1:8080/admin/v1 +# 生产环境默认: https://admin.duiduiedu.com/admin/v1 +# 如果需要覆盖,取消下面的注释并设置值 +# VITE_API_BASE_URL=http://127.0.0.1:8080/admin/v1 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..6dd1895 --- /dev/null +++ b/.env.development @@ -0,0 +1,5 @@ +# 开发环境配置 +# 此文件仅在 development 模式下生效(npm run dev) + +# 开发环境 API 地址 +VITE_API_BASE_URL=http://127.0.0.1:8080/admin/v1 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..f6bbad8 --- /dev/null +++ b/.env.production @@ -0,0 +1,5 @@ +# 生产环境配置 +# 此文件仅在 production 模式下生效(npm run build) + +# 生产环境 API 地址 +VITE_API_BASE_URL=https://admin.duiduiedu.com/admin/v1 diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..95bec8e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,20 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + '@typescript-eslint/no-explicit-any': 'off', + }, +} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..135523d --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# 环境变量文件(可选,根据团队需求决定是否提交) +# .env.local +# .env.*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f72a6b6 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=https://registry.npmmirror.com + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f2b2d73 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# 使用 nginx 作为基础镜像 +FROM nginx:alpine + +# 复制 dist 目录的内容到 nginx 的默认网站目录 +COPY dist /usr/share/nginx/html + +# 复制自定义 nginx 配置文件 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 暴露 80 端口 +EXPOSE 80 + +# 启动 nginx +CMD ["nginx", "-g", "daemon off;"] + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..763f769 --- /dev/null +++ b/Makefile @@ -0,0 +1,81 @@ +.PHONY: build build-prod tar clean dev preview lint + +# 变量定义 +IMAGE_NAME := duidui-admin-web +TAR_FILE := deploy/duidui-admin-web.tar +DEPLOY_DIR := deploy + +# 生产环境 API 地址 +PROD_API_URL := https://admin.duiduiedu.com/admin/v1 + +# 构建前端项目(开发环境) +build: + @echo "构建前端项目(开发环境)..." + @npm run build + @echo "✅ 构建完成: dist/" + +# 构建前端项目(生产环境,先 clean 再构建,保证 dist 为全新) +build-prod: clean + @echo "==========================================" + @echo "步骤 1: 配置生产环境 API 地址" + @echo "==========================================" + @echo "✅ 使用生产环境 API 地址: $(PROD_API_URL)" + @echo "" + @echo "==========================================" + @echo "步骤 2: 构建前端项目" + @echo "==========================================" + @VITE_API_BASE_URL=$(PROD_API_URL) npm run build + @echo "✅ 构建完成: dist/" + @echo "" + @echo "==========================================" + @echo "步骤 3: 验证构建结果" + @echo "==========================================" + @if [ ! -d "dist" ]; then \ + echo "❌ 错误: dist 目录不存在"; \ + exit 1; \ + fi + @if grep -q "admin.duiduiedu.com" dist/assets/*.js 2>/dev/null; then \ + echo "✅ 已确认使用生产环境 API 地址"; \ + else \ + echo "⚠️ 警告: 未找到生产环境 API 地址,请检查构建配置"; \ + fi + +# 开发模式运行 +dev: + @echo "启动开发服务器..." + @npm run dev + +# 预览构建结果 +preview: + @echo "预览构建结果..." + @npm run preview + +# 代码检查 +lint: + @echo "运行代码检查..." + @npm run lint + +# 打包部署(先构建生产 dist,再打 tar 包) +tar: build-prod + @echo "" + @echo "==========================================" + @echo "步骤 4: 创建部署包" + @echo "==========================================" + @if [ ! -d "dist" ]; then \ + echo "❌ 错误: dist 目录不存在,构建失败"; \ + exit 1; \ + fi + @chmod +x build-deploy.sh + @./build-deploy.sh + @echo "" + @echo "==========================================" + @echo "✅ 部署包创建完成: $(TAR_FILE)" + @echo "==========================================" + +# 清理构建产物 +clean: + @echo "清理构建产物..." + @rm -rf dist/ + @rm -f $(TAR_FILE) + @echo "✅ 清理完成" + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e342587 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# 怼怼后台管理系统 + +基于 React + Vite + Ant Design 的现代化后台管理系统脚手架。 + +## 技术栈 + +- **框架**: React 18 +- **构建工具**: Vite 5 +- **UI 组件库**: Ant Design 5 +- **路由**: React Router 6 +- **状态管理**: Zustand +- **HTTP 客户端**: Axios +- **语言**: TypeScript +- **日期处理**: Day.js + +## 项目结构 + +``` +duidui_new_admin_web/ +├── public/ # 静态资源 +├── src/ +│ ├── assets/ # 资源文件 +│ ├── components/ # 公共组件 +│ ├── constants/ # 常量和枚举 +│ ├── layouts/ # 布局组件 +│ ├── pages/ # 页面组件 +│ │ ├── Dashboard/ # 仪表盘 +│ │ ├── Login/ # 登录页 +│ │ ├── User/ # 用户管理 +│ │ └── NotFound/ # 404页面 +│ ├── routes/ # 路由配置 +│ ├── store/ # 状态管理 +│ ├── types/ # 类型定义 +│ ├── utils/ # 工具函数 +│ ├── App.tsx # 应用入口 +│ ├── main.tsx # 主文件 +│ └── index.css # 全局样式 +├── index.html # HTML模板 +├── package.json # 依赖配置 +├── tsconfig.json # TS配置 +└── vite.config.ts # Vite配置 +``` + +## 功能特性 + +- ✅ 用户登录/登出 +- ✅ 路由守卫 +- ✅ 响应式布局 +- ✅ 侧边栏菜单 +- ✅ 用户信息管理 +- ✅ HTTP 请求拦截 +- ✅ 本地存储封装 +- ✅ TypeScript 类型支持 +- ✅ 枚举常量管理 + +## 快速开始 + +### 安装依赖 + +```bash +npm install +# 或 +yarn install +# 或 +pnpm install +``` + +### 开发模式 + +```bash +npm run dev +``` + +访问 http://localhost:3000 + +### 构建生产版本 + +```bash +npm run build +``` + +### 预览生产构建 + +```bash +npm run preview +``` + +## 默认账号 + +用户名:任意 +密码:任意 + +(当前为模拟登录,可输入任意账号密码) + +## 开发说明 + +### 路由配置 + +路由配置在 `src/routes/index.tsx` 中,使用了路由守卫来保护需要登录的页面。 + +### 状态管理 + +使用 Zustand 进行状态管理,用户信息存储在 `src/store/useUserStore.ts` 中。 + +### API 请求 + +封装了 Axios 请求,配置了请求/响应拦截器,统一处理错误和 Token。 + +### 类型定义 + +所有类型定义和枚举都在 `src/types/` 和 `src/constants/` 中,便于维护和类型检查。 + +## 后续开发建议 + +1. 接入真实的后端 API +2. 完善用户权限控制 +3. 添加更多业务页面 +4. 完善表单验证 +5. 添加数据可视化图表 +6. 优化性能和用户体验 +7. 添加单元测试 + +## License + +MIT + diff --git a/build-deploy.sh b/build-deploy.sh new file mode 100755 index 0000000..ea12f39 --- /dev/null +++ b/build-deploy.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# 构建和打包脚本 - 在本地运行 +# 用法: ./build-deploy.sh + +set -e + +IMAGE_NAME="duidui-admin-web" +TAR_FILE="deploy/duidui-admin-web.tar" +DEPLOY_DIR="deploy" +# 目标平台(默认构建 linux/amd64,避免在 x86 服务器上出现 arm64 运行警告) +PLATFORM=${1:-linux/amd64} + +echo "==========================================" +echo "开始构建和打包 (platform: $PLATFORM)" +echo "==========================================" + +# 检测可用的容器工具(优先 docker,其次 podman) +if command -v docker >/dev/null 2>&1; then + CONTAINER_CLI="docker" +elif command -v podman >/dev/null 2>&1; then + CONTAINER_CLI="podman" +else + echo "错误: 未找到 docker 或 podman,请先安装其一。" + exit 1 +fi + +echo "使用容器工具: $CONTAINER_CLI" + +# 检查 dist 目录是否存在 +if [ ! -d "dist" ]; then + echo "错误: dist 目录不存在,请先运行 npm run build" + exit 1 +fi + +# 创建 deploy 目录 +echo "创建部署目录..." +mkdir -p $DEPLOY_DIR + +# 构建 Docker 镜像(不使用缓存,确保使用最新的 dist 目录) +echo "构建 Docker 镜像(不使用缓存)..." +$CONTAINER_CLI build --no-cache --platform $PLATFORM -t $IMAGE_NAME:latest . + +# 导出镜像为 tar 包 +echo "导出镜像为 tar 包..." +$CONTAINER_CLI save $IMAGE_NAME:latest -o $TAR_FILE + +# 复制部署脚本到 deploy 目录(如果还没有) +if [ ! -f "$DEPLOY_DIR/deploy.sh" ]; then + echo "复制部署脚本..." + cp deploy.sh $DEPLOY_DIR/ + chmod +x $DEPLOY_DIR/deploy.sh +fi + +# 获取文件大小 +TAR_SIZE=$(du -h $TAR_FILE | cut -f1) + +echo "==========================================" +echo "构建完成!" +echo "==========================================" +echo "镜像名称: $IMAGE_NAME:latest" +echo "tar 包路径: $TAR_FILE" +echo "文件大小: $TAR_SIZE" +echo "" +echo "部署步骤:" +echo "1. 将 deploy 目录下的所有文件上传到服务器" +echo "2. 在服务器上执行: cd deploy && ./deploy.sh [端口号]" +echo "==========================================" + diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..c7da20e --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# 部署脚本 - 在服务器上运行 +# 用法: ./deploy.sh [端口号,默认 49999] + +set -e + +IMAGE_NAME="duidui-admin-web" +CONTAINER_NAME="duidui-admin-web-container" +TAR_FILE="duidui-admin-web.tar" +NETWORK_NAME="dd.net" +PORT=${1:-49999} + +echo "==========================================" +echo "开始部署 $IMAGE_NAME" +echo "==========================================" + +# 检查 tar 文件是否存在 +if [ ! -f "$TAR_FILE" ]; then + echo "错误: 找不到 $TAR_FILE 文件" + exit 1 +fi + +# 停止并删除旧容器(如果存在) +echo "检查并清理旧容器..." +if [ "$(docker ps -a -q -f name=$CONTAINER_NAME)" ]; then + echo "停止旧容器..." + docker stop $CONTAINER_NAME || true + echo "删除旧容器..." + docker rm $CONTAINER_NAME || true +fi + +# 删除旧镜像(如果存在) +echo "检查并清理旧镜像..." +if [ "$(docker images -q $IMAGE_NAME)" ]; then + echo "删除旧镜像..." + docker rmi $IMAGE_NAME || true +fi + +# 加载镜像 +echo "加载 Docker 镜像..." +docker load -i $TAR_FILE + +# 处理镜像标签(兼容 podman 构建时的 localhost/ 前缀) +if ! docker image inspect $IMAGE_NAME:latest >/dev/null 2>&1; then + if docker image inspect localhost/$IMAGE_NAME:latest >/dev/null 2>&1; then + echo "检测到镜像标签为 localhost/$IMAGE_NAME:latest,重新标记为 $IMAGE_NAME:latest" + docker tag localhost/$IMAGE_NAME:latest $IMAGE_NAME:latest + fi +fi + +# 检查并创建网络(如果不存在) +echo "检查 Docker 网络..." +if ! docker network ls | grep -q "$NETWORK_NAME"; then + echo "创建 Docker 网络: $NETWORK_NAME" + docker network create $NETWORK_NAME +else + echo "网络 $NETWORK_NAME 已存在" +fi + +# 运行容器 +echo "启动容器..." +docker run -d \ + --name $CONTAINER_NAME \ + --network $NETWORK_NAME \ + --restart unless-stopped \ + -p $PORT:80 \ + $IMAGE_NAME + +# 检查容器状态 +echo "等待容器启动..." +sleep 2 + +if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then + echo "==========================================" + echo "部署成功!" + echo "==========================================" + echo "容器名称: $CONTAINER_NAME" + echo "Docker 网络: $NETWORK_NAME" + echo "访问地址: http://localhost:$PORT" + echo "" + echo "查看容器状态: docker ps -a | grep $CONTAINER_NAME" + echo "查看容器日志: docker logs $CONTAINER_NAME" + echo "查看网络信息: docker network inspect $NETWORK_NAME" + echo "停止容器: docker stop $CONTAINER_NAME" + echo "启动容器: docker start $CONTAINER_NAME" + echo "删除容器: docker rm -f $CONTAINER_NAME" +else + echo "==========================================" + echo "部署失败!请检查日志" + echo "==========================================" + docker logs $CONTAINER_NAME + exit 1 +fi + diff --git a/deploy/duidui-admin-web.tar b/deploy/duidui-admin-web.tar new file mode 100644 index 0000000..0631561 Binary files /dev/null and b/deploy/duidui-admin-web.tar differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..f8bb0f0 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + 后台管理系统 + + +
+ + + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..0f7d7dc --- /dev/null +++ b/nginx.conf @@ -0,0 +1,61 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # 启用 gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript; + + # API 代理配置 - 将 /api 代理到后端服务器 + location /api { + proxy_pass https://api.duiduiedu.com; + proxy_set_header Host api.duiduiedu.com; + 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; + + # 处理跨域 + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; + + # 处理预检请求 + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain; charset=utf-8'; + add_header Content-Length 0; + return 204; + } + + # SSL 相关配置 + proxy_ssl_verify off; + proxy_ssl_server_name on; + + # 超时配置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 前端静态资源 + location / { + try_files $uri $uri/ /index.html; + + # 静态资源缓存配置 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } + + # 错误页面 + error_page 404 /index.html; +} + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ab0bd7d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5549 @@ +{ + "name": "duidui-admin-web", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "duidui-admin-web", + "version": "0.0.1", + "dependencies": { + "@ant-design/icons": "^5.2.6", + "@tiptap/extension-color": "^3.15.3", + "@tiptap/extension-image": "^3.15.3", + "@tiptap/extension-link": "^3.15.3", + "@tiptap/extension-strike": "^3.15.3", + "@tiptap/extension-text-align": "^3.15.3", + "@tiptap/extension-text-style": "^3.15.3", + "@tiptap/extension-underline": "^3.15.3", + "@tiptap/react": "^3.15.3", + "@tiptap/starter-kit": "^3.15.3", + "antd": "^5.12.0", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "quill": "^2.0.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-quill": "^2.0.0", + "react-router-dom": "^6.20.0", + "zustand": "^4.4.7" + }, + "devDependencies": { + "@types/node": "^25.0.3", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "baseline-browser-mapping": "^2.9.11", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@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/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@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.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@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-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "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", + "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.4", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "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/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT", + "optional": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.4", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@tiptap/core": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/core/-/core-3.15.3.tgz", + "integrity": "sha512-bmXydIHfm2rEtGju39FiQNfzkFx9CDvJe+xem1dgEZ2P6Dj7nQX9LnA1ZscW7TuzbBRkL5p3dwuBIi3f62A66A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-blockquote/-/extension-blockquote-3.15.3.tgz", + "integrity": "sha512-13x5UsQXtttFpoS/n1q173OeurNxppsdWgP3JfsshzyxIghhC141uL3H6SGYQLPU31AizgDs2OEzt6cSUevaZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-bold/-/extension-bold-3.15.3.tgz", + "integrity": "sha512-I8JYbkkUTNUXbHd/wCse2bR0QhQtJD7+0/lgrKOmGfv5ioLxcki079Nzuqqay3PjgYoJLIJQvm3RAGxT+4X91w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.15.3.tgz", + "integrity": "sha512-e88DG1bTy6hKxrt7iPVQhJnH5/EOrnKpIyp09dfRDgWrrW88fE0Qjys7a/eT8W+sXyXM3z10Ye7zpERWsrLZDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.15.3.tgz", + "integrity": "sha512-MGwEkNT7ltst6XaWf0ObNgpKQ4PvuuV3igkBrdYnQS+qaAx9IF4isygVPqUc9DvjYC306jpyKsNqNrENIXcosA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-code/-/extension-code-3.15.3.tgz", + "integrity": "sha512-x6LFt3Og6MFINYpsMzrJnz7vaT9Yk1t4oXkbJsJRSavdIWBEBcoRudKZ4sSe/AnsYlRJs8FY2uR76mt9e+7xAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-code-block/-/extension-code-block-3.15.3.tgz", + "integrity": "sha512-q1UB9icNfdJppTqMIUWfoRKkx5SSdWIpwZoL2NeOI5Ah3E20/dQKVttIgLhsE521chyvxCYCRaHD5tMNGKfhyw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-color": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-color/-/extension-color-3.15.3.tgz", + "integrity": "sha512-GS+LEJ7YC7J6CiQ/caTDVyKg+ZlU4B5ofzAZ0iCWPahjMyUUZImzXvoRlfMumAiPG+IUW9PC2BztSGd3SCLpGA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-text-style": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-document/-/extension-document-3.15.3.tgz", + "integrity": "sha512-AC72nI2gnogBuETCKbZekn+h6t5FGGcZG2abPGKbz/x9rwpb6qV2hcbAQ30t6M7H6cTOh2/Ut8bEV2MtMB15sw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.15.3.tgz", + "integrity": "sha512-jGI5XZpdo8GSYQFj7HY15/oEwC2m2TqZz0/Fln5qIhY32XlZhWrsMuMI6WbUJrTH16es7xO6jmRlDsc6g+vJWg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.15.3.tgz", + "integrity": "sha512-+3DVBleKKffadEJEdLYxmYAJOjHjLSqtiSFUE3RABT4V2ka1ODy2NIpyKX0o1SvQ5N1jViYT9Q+yUbNa6zCcDw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.15.3.tgz", + "integrity": "sha512-Kaw0sNzP0bQI/xEAMSfIpja6xhsu9WqqAK/puzOIS1RKWO47Wps/tzqdSJ9gfslPIb5uY5mKCfy8UR8Xgiia8w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-hard-break/-/extension-hard-break-3.15.3.tgz", + "integrity": "sha512-8HjxmeRbBiXW+7JKemAJtZtHlmXQ9iji398CPQ0yYde68WbIvUhHXjmbJE5pxFvvQTJ/zJv1aISeEOZN2bKBaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-heading/-/extension-heading-3.15.3.tgz", + "integrity": "sha512-G1GG6iN1YXPS+75arDpo+bYRzhr3dNDw99c7D7na3aDawa9Qp7sZ/bVrzFUUcVEce0cD6h83yY7AooBxEc67hA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.15.3.tgz", + "integrity": "sha512-FYkN7L6JsfwwNEntmLklCVKvgL0B0N47OXMacRk6kYKQmVQ4Nvc7q/VJLpD9sk4wh4KT1aiCBfhKEBTu5pv1fg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-image": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-image/-/extension-image-3.15.3.tgz", + "integrity": "sha512-Tjq9BHlC/0bGR9/uySA0tv6I1Ua1Q5t5P/mdbWyZi4JdUpKHRfgenzfXF5DYnklJ01QJ7uOPSp9sAGgPzBixtQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-italic/-/extension-italic-3.15.3.tgz", + "integrity": "sha512-6XeuPjcWy7OBxpkgOV7bD6PATO5jhIxc8SEK4m8xn8nelGTBIbHGqK37evRv+QkC7E0MUryLtzwnmmiaxcKL0Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-link/-/extension-link-3.15.3.tgz", + "integrity": "sha512-PdDXyBF9Wco9U1x6e+b7tKBWG+kqBDXDmaYXHkFm/gYuQCQafVJ5mdrDdKgkHDWVnJzMWZXBcZjT9r57qtlLWg==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-list/-/extension-list-3.15.3.tgz", + "integrity": "sha512-n7y/MF9lAM5qlpuH5IR4/uq+kJPEJpe9NrEiH+NmkO/5KJ6cXzpJ6F4U17sMLf2SNCq+TWN9QK8QzoKxIn50VQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-list-item/-/extension-list-item-3.15.3.tgz", + "integrity": "sha512-CCxL5ek1p0lO5e8aqhnPzIySldXRSigBFk2fP9OLgdl5qKFLs2MGc19jFlx5+/kjXnEsdQTFbGY1Sizzt0TVDw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.15.3.tgz", + "integrity": "sha512-UxqnTEEAKrL+wFQeSyC9z0mgyUUVRS2WTcVFoLZCE6/Xus9F53S4bl7VKFadjmqI4GpDk5Oe2IOUc72o129jWg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.15.3.tgz", + "integrity": "sha512-/8uhw528Iy0c9wF6tHCiIn0ToM0Ml6Ll2c/3iPRnKr4IjXwx2Lr994stUFihb+oqGZwV1J8CPcZJ4Ufpdqi4Dw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-paragraph/-/extension-paragraph-3.15.3.tgz", + "integrity": "sha512-lc0Qu/1AgzcEfS67NJMj5tSHHhH6NtA6uUpvppEKGsvJwgE2wKG1onE4isrVXmcGRdxSMiCtyTDemPNMu6/ozQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-strike/-/extension-strike-3.15.3.tgz", + "integrity": "sha512-Y1P3eGNY7RxQs2BcR6NfLo9VfEOplXXHAqkOM88oowWWOE7dMNeFFZM9H8HNxoQgXJ7H0aWW9B7ZTWM9hWli2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-text/-/extension-text-3.15.3.tgz", + "integrity": "sha512-MhkBz8ZvrqOKtKNp+ZWISKkLUlTrDR7tbKZc2OnNcUTttL9dz0HwT+cg91GGz19fuo7ttDcfsPV6eVmflvGToA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-text-align": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-text-align/-/extension-text-align-3.15.3.tgz", + "integrity": "sha512-hkLeEKm44aqimyjv+D8JUxzDG/iNjDrSCGvGrMOPcpaKn4f8C5z1EKnEufT61RitNPBAxQMXUhmGQUNrmlICmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-text-style": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-text-style/-/extension-text-style-3.15.3.tgz", + "integrity": "sha512-/M7fuGRPVkeM14rQ1bNiLZUs2N+FuVhIsLEwNKKk7GaTGKHzmkC1b2COmbICivuFYf90KWzaG0R+Pm7cnW6KaA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extension-underline/-/extension-underline-3.15.3.tgz", + "integrity": "sha512-r/IwcNN0W366jGu4Y0n2MiFq9jGa4aopOwtfWO4d+J0DyeS2m7Go3+KwoUqi0wQTiVU74yfi4DF6eRsMQ9/iHQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3" + } + }, + "node_modules/@tiptap/extensions": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/extensions/-/extensions-3.15.3.tgz", + "integrity": "sha512-ycx/BgxR4rc9tf3ZyTdI98Z19yKLFfqM3UN+v42ChuIwkzyr9zyp7kG8dB9xN2lNqrD+5y/HyJobz/VJ7T90gA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/pm/-/pm-3.15.3.tgz", + "integrity": "sha512-Zm1BaU1TwFi3CQiisxjgnzzIus+q40bBKWLqXf6WEaus8Z6+vo1MT2pU52dBCMIRaW9XNDq3E5cmGtMc1AlveA==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.24.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.5.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.38.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/react/-/react-3.15.3.tgz", + "integrity": "sha512-XvouB+Hrqw8yFmZLPEh+HWlMeRSjZfHSfWfWuw5d8LSwnxnPeu3Bg/rjHrRrdwb+7FumtzOnNWMorpb/PSOttQ==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "fast-equals": "^5.3.3", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.15.3", + "@tiptap/extension-floating-menu": "^3.15.3" + }, + "peerDependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/pm": "^3.15.3", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "3.15.3", + "resolved": "https://registry.npmmirror.com/@tiptap/starter-kit/-/starter-kit-3.15.3.tgz", + "integrity": "sha512-ia+eQr9Mt1ln2UO+kK4kFTJOrZK4GhvZXFjpCCYuHtco3rhr2fZAIxEEY4cl/vo5VO5WWyPqxhkFeLcoWmNjSw==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^3.15.3", + "@tiptap/extension-blockquote": "^3.15.3", + "@tiptap/extension-bold": "^3.15.3", + "@tiptap/extension-bullet-list": "^3.15.3", + "@tiptap/extension-code": "^3.15.3", + "@tiptap/extension-code-block": "^3.15.3", + "@tiptap/extension-document": "^3.15.3", + "@tiptap/extension-dropcursor": "^3.15.3", + "@tiptap/extension-gapcursor": "^3.15.3", + "@tiptap/extension-hard-break": "^3.15.3", + "@tiptap/extension-heading": "^3.15.3", + "@tiptap/extension-horizontal-rule": "^3.15.3", + "@tiptap/extension-italic": "^3.15.3", + "@tiptap/extension-link": "^3.15.3", + "@tiptap/extension-list": "^3.15.3", + "@tiptap/extension-list-item": "^3.15.3", + "@tiptap/extension-list-keymap": "^3.15.3", + "@tiptap/extension-ordered-list": "^3.15.3", + "@tiptap/extension-paragraph": "^3.15.3", + "@tiptap/extension-strike": "^3.15.3", + "@tiptap/extension-text": "^3.15.3", + "@tiptap/extension-underline": "^3.15.3", + "@tiptap/extensions": "^3.15.3", + "@tiptap/pm": "^3.15.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.3", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "license": "MIT" + }, + "node_modules/@types/quill": { + "version": "1.3.10", + "resolved": "https://registry.npmmirror.com/@types/quill/-/quill-1.3.10.tgz", + "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "license": "MIT", + "dependencies": { + "parchment": "^1.1.2" + } + }, + "node_modules/@types/quill/node_modules/parchment": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", + "license": "BSD-3-Clause" + }, + "node_modules/@types/react": { + "version": "18.3.26", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "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-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "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": { + "version": "5.27.5", + "resolved": "https://registry.npmmirror.com/antd/-/antd-5.27.5.tgz", + "integrity": "sha512-Ehd9mqtHvJ1clon1yJ/1BTV6eX/3SH2YXZZPTHUk8XdzXFwUioI+Lht47s+MaHIUBY77RnZrmtKwwR+VVu0l7A==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.1", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.9", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.9.2", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.3", + "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.8.9", + "caniuse-lite": "^1.0.30001746", + "electron-to-chromium": "^1.5.227", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserslist/node_modules/caniuse-lite": { + "version": "1.0.30001750", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", + "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", + "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/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "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", + "license": "MIT" + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "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.1.3", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "license": "MIT", + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "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.237", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "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/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/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/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "license": "Apache-2.0" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "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": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "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.4", + "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/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "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", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "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", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "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.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.23", + "dev": true, + "license": "MIT" + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "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/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parchment": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/parchment/-/parchment-3.0.0.tgz", + "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==", + "license": "BSD-3-Clause" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prosemirror-changeset": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz", + "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmmirror.com/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", + "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz", + "integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz", + "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==", + "license": "MIT", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmmirror.com/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmmirror.com/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.5", + "resolved": "https://registry.npmmirror.com/prosemirror-transform/-/prosemirror-transform-1.10.5.tgz", + "integrity": "sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.4", + "resolved": "https://registry.npmmirror.com/prosemirror-view/-/prosemirror-view-1.41.4.tgz", + "integrity": "sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quill": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/quill/-/quill-2.0.3.tgz", + "integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==", + "license": "BSD-3-Clause", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash-es": "^4.17.21", + "parchment": "^3.0.0", + "quill-delta": "^5.1.0" + }, + "engines": { + "npm": ">=8.2.3" + } + }, + "node_modules/quill-delta": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", + "license": "MIT", + "dependencies": { + "fast-diff": "^1.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "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-notification": { + "version": "5.6.4", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.4.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.0", + "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-select": { + "version": "14.16.8", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.9", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.54.0", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.9.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "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/rc-virtual-list": { + "version": "3.19.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "license": "MIT" + }, + "node_modules/react-quill": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/react-quill/-/react-quill-2.0.0.tgz", + "integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==", + "license": "MIT", + "dependencies": { + "@types/quill": "^1.3.10", + "lodash": "^4.17.4", + "quill": "^1.3.7" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, + "node_modules/react-quill/node_modules/eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", + "license": "MIT" + }, + "node_modules/react-quill/node_modules/fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", + "license": "Apache-2.0" + }, + "node_modules/react-quill/node_modules/parchment": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", + "license": "BSD-3-Clause" + }, + "node_modules/react-quill/node_modules/quill": { + "version": "1.3.7", + "resolved": "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz", + "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==", + "license": "BSD-3-Clause", + "dependencies": { + "clone": "^2.1.1", + "deep-equal": "^1.0.1", + "eventemitter3": "^2.0.3", + "extend": "^3.0.2", + "parchment": "^1.1.4", + "quill-delta": "^3.6.2" + } + }, + "node_modules/react-quill/node_modules/quill-delta": { + "version": "3.6.3", + "resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-3.6.3.tgz", + "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", + "license": "MIT", + "dependencies": { + "deep-equal": "^1.0.1", + "extend": "^3.0.2", + "fast-diff": "1.1.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.1", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-router-dom/node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/react-router/node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "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.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmmirror.com/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.1.3", + "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", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "5.4.20", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.20.tgz", + "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6d375fc --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "duidui-admin-web", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + }, + "dependencies": { + "@ant-design/icons": "^5.2.6", + "@tiptap/extension-color": "^3.15.3", + "@tiptap/extension-image": "^3.15.3", + "@tiptap/extension-link": "^3.15.3", + "@tiptap/extension-strike": "^3.15.3", + "@tiptap/extension-text-align": "^3.15.3", + "@tiptap/extension-text-style": "^3.15.3", + "@tiptap/extension-underline": "^3.15.3", + "@tiptap/react": "^3.15.3", + "@tiptap/starter-kit": "^3.15.3", + "antd": "^5.12.0", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "quill": "^2.0.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-quill": "^2.0.0", + "react-router-dom": "^6.20.0", + "zustand": "^4.4.7" + }, + "devDependencies": { + "@types/node": "^25.0.3", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "baseline-browser-mapping": "^2.9.11", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..9e047aa --- /dev/null +++ b/public/vite.svg @@ -0,0 +1,2 @@ + + diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..f258e6d --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,132 @@ +import { createContext, useEffect, useMemo, useState } from 'react' +import { BrowserRouter } from 'react-router-dom' +import { ConfigProvider, theme } from 'antd' +import zhCN from 'antd/locale/zh_CN' +import AppRoutes from './routes' + +export enum ThemeMode { + LIGHT = 'light', + DARK = 'dark', +} + +// 预设的主题颜色 +export const THEME_COLORS = [ + { name: '蓝色', value: '#1677ff' }, + { name: '紫色', value: '#722ed1' }, + { name: '绿色', value: '#52c41a' }, + { name: '橙色', value: '#fa8c16' }, + { name: '红色', value: '#ff4d4f' }, + { name: '青色', value: '#13c2c2' }, + { name: '粉色', value: '#eb2f96' }, + { name: '金色', value: '#faad14' }, +] as const + +export interface ThemeModeContextValue { + mode: ThemeMode + toggleMode: () => void + primaryColor: string + setPrimaryColor: (color: string) => void +} + +export const ThemeModeContext = createContext(null) + +const THEME_STORAGE_KEY = 'admin_theme_mode' +const PRIMARY_COLOR_STORAGE_KEY = 'admin_primary_color' + +function getInitialThemeMode(): ThemeMode { + if (typeof window === 'undefined') { + return ThemeMode.LIGHT + } + + const stored = window.localStorage.getItem(THEME_STORAGE_KEY) as ThemeMode | null + if (stored === ThemeMode.DARK || stored === ThemeMode.LIGHT) { + return stored + } + + const prefersDark = + window.matchMedia && + window.matchMedia('(prefers-color-scheme: dark)').matches + + return prefersDark ? ThemeMode.DARK : ThemeMode.LIGHT +} + +function getInitialPrimaryColor(): string { + if (typeof window === 'undefined') { + return THEME_COLORS[0].value + } + + const stored = window.localStorage.getItem(PRIMARY_COLOR_STORAGE_KEY) + if (stored && THEME_COLORS.some((c) => c.value === stored)) { + return stored + } + + return THEME_COLORS[0].value +} + +function App() { + const [mode, setMode] = useState(getInitialThemeMode) + const [primaryColor, setPrimaryColorState] = useState(getInitialPrimaryColor) + + useEffect(() => { + if (typeof document === 'undefined') return + document.documentElement.dataset.theme = mode + window.localStorage.setItem(THEME_STORAGE_KEY, mode) + }, [mode]) + + useEffect(() => { + if (typeof window === 'undefined') return + window.localStorage.setItem(PRIMARY_COLOR_STORAGE_KEY, primaryColor) + }, [primaryColor]) + + const isDark = mode === ThemeMode.DARK + + const antdTheme = useMemo( + () => ({ + algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm, + token: { + colorPrimary: primaryColor, + // 其他常用的主题颜色: + // colorSuccess: '#52c41a', // 成功色(绿色) + // colorWarning: '#faad14', // 警告色(橙色) + // colorError: '#ff4d4f', // 错误色(红色) + // colorInfo: '#1677ff', // 信息色(蓝色) + }, + }), + [isDark, primaryColor], + ) + + const setPrimaryColor = (color: string) => { + setPrimaryColorState(color) + } + + const contextValue = useMemo( + () => ({ + mode, + toggleMode: () => + setMode((prev) => + prev === ThemeMode.DARK ? ThemeMode.LIGHT : ThemeMode.DARK, + ), + primaryColor, + setPrimaryColor, + }), + [mode, primaryColor], + ) + + return ( + + + + + + + + ) +} + +export default App + diff --git a/src/api/admin.ts b/src/api/admin.ts new file mode 100644 index 0000000..6b7f470 --- /dev/null +++ b/src/api/admin.ts @@ -0,0 +1,163 @@ +import request from '@/utils/request' + +// 管理员用户接口 +export interface AdminUser { + id: string + username: string + phone: string + nickname: string + avatar: string + status: number // 0=禁用,1=启用 + is_super_admin: number // 0=否,1=是 + last_login_at?: string + last_login_ip?: string + created_at: string + updated_at: string +} + +// 创建管理员请求 +export interface CreateAdminUserRequest { + username?: string + phone?: string + password: string + nickname?: string + avatar?: string + status?: number + is_super_admin?: number +} + +// 更新管理员请求 +export interface UpdateAdminUserRequest { + id: string + username?: string + phone?: string + password?: string // 可选,如果为空则不更新密码 + nickname?: string + avatar?: string + status?: number + is_super_admin?: number +} + +// 管理员列表响应 +export interface AdminUserListResponse { + list: AdminUser[] + total: number + page: number + page_size: number +} + +// 获取管理员列表 +export const getAdminUserList = (params: { + keyword?: string + page?: number + page_size?: number +}) => { + return request.get('/admin-users/list', { params }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取管理员列表成功', + data: backendData.data || { + list: [], + total: 0, + page: 1, + page_size: 10, + } + } + } + }) +} + +// 获取管理员详情 +export const getAdminUser = (id: string) => { + return request.get('/admin-users/detail', { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取管理员详情成功', + data: backendData.data + } + } + }) +} + +// 创建管理员 +export const createAdminUser = (data: CreateAdminUserRequest) => { + return request.post('/admin-users/create', data).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建管理员成功', + data: backendData.data + } + } + }) +} + +// 更新管理员 +export const updateAdminUser = (data: UpdateAdminUserRequest) => { + return request.post('/admin-users/update', data).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新管理员成功', + data: backendData.data + } + } + }) +} + +// 删除管理员 +export const deleteAdminUser = (id: string) => { + return request.post('/admin-users/delete', {}, { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除管理员成功' + } + } + }) +} + +// 获取用户角色 +export const getUserRoles = (userId: string) => { + return request.get('/admin-users/roles', { params: { user_id: userId } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取用户角色成功', + data: backendData.data || [] + } + } + }) +} + +// 设置用户角色 +export const setUserRoles = (userId: string, roleIds: string[]) => { + return request.post('/admin-users/roles', { + user_id: userId, + role_ids: roleIds + }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '设置用户角色成功' + } + } + }) +} + diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..17b6dc9 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,63 @@ +import request from '@/utils/request' + +// 登录请求参数 +export interface LoginRequest { + username?: string // 用户名(可选) + phone?: string // 手机号(可选) + password: string // 密码(必填) +} + +// 登录响应 +export interface LoginResponse { + success: boolean + message: string + token?: string + user?: AdminUserInfo +} + +// 管理员用户信息 +export interface AdminUserInfo { + id: string + username: string + phone: string + nickname: string + avatar: string + is_super_admin: boolean + roles: string[] + permissions: string[] +} + +// 获取用户信息响应 +export interface GetMeResponse { + success: boolean + message: string + user?: AdminUserInfo +} + +/** + * 管理员登录(支持用户名或手机号登录) + * @param data 登录数据 + * @returns 登录响应 + */ +export const login = (data: LoginRequest): Promise => { + return request.post('/auth/login', data).then((res) => { + // 响应拦截器已经处理了 success 字段,这里直接返回 data + return res.data + }) +} + +/** + * 获取当前用户信息 + * @returns 用户信息 + */ +export const getMe = (): Promise => { + return request.get('/auth/me').then((res) => res.data) +} + +/** + * 登出 + */ +export const logout = (): Promise<{ success: boolean; message: string }> => { + return request.post('/auth/logout').then((res) => res.data) +} + diff --git a/src/api/camp.ts b/src/api/camp.ts new file mode 100644 index 0000000..f814b21 --- /dev/null +++ b/src/api/camp.ts @@ -0,0 +1,1141 @@ +import request from '@/utils/request' +import type { + Category, + Camp, + Section, + Task, + UserProgress, + ListCategoriesRequest, + ListCampsRequest, + ListSectionsRequest, + ListTasksRequest, + ListUserProgressRequest, +} from '@/types/camp' + +// ==================== 分类管理 ==================== + +export const createCategory = (data: Omit) => { + // 转换字段格式:驼峰 -> 下划线 + const apiData = { + name: data.name, + sort_order: data.sortOrder, + } + + return request.post('/camp/categories/create', apiData).then((response) => { + const backendData = response.data + + // 转换为前端期望的格式 + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: backendData.id || backendData.category_id || '', + } + } + } + }) +} + +export const getCategory = (id: string) => { + return request.get(`/camp/categories/detail`, { params: { id } }).then((response) => { + const backendData = response.data + + // 转换为前端期望的格式 + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: backendData.category?.id || backendData.id, + name: backendData.category?.name || backendData.name, + sortOrder: backendData.category?.sort_order || backendData.sort_order, + } + } + } + }) +} + +export const listCategories = (params: ListCategoriesRequest) => { + // 转换参数格式:驼峰 -> 下划线 + const apiParams: any = { + page: params.page, + page_size: params.pageSize, + } + + // 只在有值时传递 keyword + if (params.keyword) { + apiParams.keyword = params.keyword + } + + return request.get('/camp/categories', { params: apiParams }).then((response) => { + // response.data 是后端返回的原始数据 + const backendData = response.data + + // 转换为前端期望的格式 + return { + data: { + code: 200, + message: backendData.message, + data: { + list: backendData.categories?.map((item: any) => ({ + id: item.id, + name: item.name, + sortOrder: item.sort_order, + })) || [], + total: backendData.total || 0, + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +export const updateCategory = (data: Category) => { + // 转换字段格式:驼峰 -> 下划线 + const apiData = { + id: data.id, + name: data.name, + sort_order: data.sortOrder, + } + + return request.post(`/camp/categories/update`, apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +export const deleteCategory = (id: string) => { + return request.post(`/camp/categories/delete`, { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +// ==================== 打卡营管理 ==================== + +export const createCamp = (data: Omit) => { + // 转换字段格式:驼峰 -> 下划线 + const apiData = { + title: data.title, + cover_image: data.coverImage, + description: data.description, + intro_type: data.introType, + intro_content: data.introContent, + category_id: data.categoryId, + is_recommended: data.isRecommended, + } + + return request.post('/camp/camps/create', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: backendData.id || backendData.camp_id || '', + } + } + } + }) +} + +export const getCamp = (id: string) => { + return request.get(`/camp/camps/detail`, { params: { id } }).then((response) => { + const backendData = response.data + + // 转换 intro_type 字符串到数字 + const convertIntroType = (type: string | number): number => { + if (typeof type === 'number') return type + const typeMap: Record = { + 'INTRO_TYPE_NONE': 0, + 'INTRO_TYPE_IMAGE_TEXT': 1, + 'INTRO_TYPE_VIDEO': 2, + } + return typeMap[type] ?? 0 + } + + const camp = backendData.camp || backendData + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: camp.id, + title: camp.title, + coverImage: camp.cover_image, + description: camp.description, + introType: convertIntroType(camp.intro_type), + introContent: camp.intro_content, + categoryId: camp.category_id, + isRecommended: camp.is_recommended, + sectionCount: camp.section_count || 0, + } + } + } + }) +} + +export const listCamps = (params: ListCampsRequest) => { + // 转换参数格式:驼峰 -> 下划线 + const apiParams: any = { + page: params.page, + page_size: params.pageSize, + } + + // 只在有值时传递 keyword + if (params.keyword) { + apiParams.keyword = params.keyword + } + + if (params.categoryId) { + apiParams.category_id = params.categoryId + } + + if (params.recommendFilter !== undefined) { + // 0=全部, 1=仅推荐, 2=仅非推荐 + if (params.recommendFilter === 1) { + apiParams.is_recommended = true + } else if (params.recommendFilter === 2) { + apiParams.is_recommended = false + } + } + + return request.get('/camp/camps', { params: apiParams }).then((response) => { + const backendData = response.data + + // 转换 intro_type 字符串到数字 + const convertIntroType = (type: string): number => { + const typeMap: Record = { + 'INTRO_TYPE_NONE': 0, + 'INTRO_TYPE_IMAGE_TEXT': 1, + 'INTRO_TYPE_VIDEO': 2, + } + return typeMap[type] ?? 0 + } + + return { + data: { + code: 200, + message: backendData.message, + data: { + list: backendData.camps?.map((item: any) => ({ + id: item.id, + title: item.title, + coverImage: item.cover_image, + description: item.description, + introType: convertIntroType(item.intro_type), + introContent: item.intro_content, + categoryId: item.category_id, + isRecommended: item.is_recommended, + sectionCount: item.section_count || 0, + })) || [], + total: backendData.total || 0, + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +export const updateCamp = (data: Camp) => { + // 转换字段格式:驼峰 -> 下划线 + const apiData = { + id: data.id, + title: data.title, + cover_image: data.coverImage, + description: data.description, + intro_type: data.introType, + intro_content: data.introContent, + category_id: data.categoryId, + is_recommended: data.isRecommended, + } + + return request.post('/camp/camps/update', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +export const deleteCamp = (id: string) => { + return request.post('/camp/camps/delete', { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +// ==================== 小节管理 ==================== + +export const createSection = (data: Omit) => { + // 转换字段格式:驼峰 -> 下划线 + const apiData = { + camp_id: data.campId, + title: data.title, + section_number: data.sectionNumber, + price_fen: data.priceFen, + time_interval_type: data.timeIntervalType, + time_interval_value: data.timeIntervalValue, + } + + return request.post('/camp/sections/create', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: backendData.id || backendData.section_id || '', + } + } + } + }) +} + +export const getSection = (id: string) => { + return request.get(`/camp/sections/detail`, { params: { id } }).then((response) => { + const backendData = response.data + const section = backendData.section || backendData + + // 转换 time_interval_type + const convertTimeIntervalType = (type: any): number => { + if (typeof type === 'number') return type + const typeMap: Record = { + 'TIME_INTERVAL_NONE': 0, + 'TIME_INTERVAL_HOUR': 1, + 'TIME_INTERVAL_NATURAL_DAY': 2, + none: 0, + hour: 1, + natural_day: 2, + paid: 3, + PAID: 3, + } + return typeMap[type] ?? 0 + } + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: section.id, + campId: section.camp_id, + title: section.title, + sectionNumber: section.section_number ?? 0, + priceFen: section.price_fen ?? 0, + requirePreviousSection: section.require_previous_section ?? false, + timeIntervalType: convertTimeIntervalType(section.time_interval_type), + timeIntervalValue: section.time_interval_value ?? 0, + } + } + } + }) +} + +export const listSections = (params: ListSectionsRequest) => { + // 转换参数格式:驼峰 -> 下划线 + const apiParams: any = { + page: params.page, + page_size: params.pageSize, + } + + if (params.keyword) { + apiParams.keyword = params.keyword + } + + if (params.campId) { + apiParams.camp_id = params.campId + } + + return request.get('/camp/sections', { params: apiParams }).then((response) => { + const backendData = response.data + + // sections 可能是对象或数组,需要处理 + let sectionsList = [] + if (Array.isArray(backendData.sections)) { + sectionsList = backendData.sections + } else if (backendData.sections && typeof backendData.sections === 'object') { + // 如果是对象,尝试转换为数组 + sectionsList = Object.values(backendData.sections) + } + + // 转换 time_interval_type + const convertTimeIntervalType = (type: any): number => { + if (typeof type === 'number') return type + const typeMap: Record = { + 'TIME_INTERVAL_NONE': 0, + 'TIME_INTERVAL_HOUR': 1, + 'TIME_INTERVAL_NATURAL_DAY': 2, + none: 0, + hour: 1, + natural_day: 2, + paid: 3, + PAID: 3, + } + return typeMap[type] ?? 0 + } + + return { + data: { + code: 200, + message: backendData.message, + data: { + list: sectionsList.map((item: any) => ({ + id: item.id, + campId: item.camp_id, + title: item.title, + sectionNumber: item.section_number ?? 0, + priceFen: item.price_fen ?? 0, + requirePreviousSection: item.require_previous_section ?? false, + timeIntervalType: convertTimeIntervalType(item.time_interval_type), + timeIntervalValue: item.time_interval_value ?? 0, + })), + total: backendData.total || 0, + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +export const updateSection = (data: Section) => { + // 转换字段格式:驼峰 -> 下划线 + const apiData = { + id: data.id, + camp_id: data.campId, + title: data.title, + section_number: data.sectionNumber, + price_fen: data.priceFen, + time_interval_type: data.timeIntervalType, + time_interval_value: data.timeIntervalValue, + } + + return request.post('/camp/sections/update', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +export const deleteSection = (id: string) => { + return request.post(`/camp/sections/delete?id=${encodeURIComponent(id)}`).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +// ==================== 任务管理 ==================== + +export const createTask = (data: Omit) => { + // 转换前端格式到后端格式 + const convertContent = (content: any) => { + const type = content.type + const contentData = content.data + + // 转换字段名:驼峰 -> 下划线 + const convertedData: any = {} + if (type === 'imageText') { + convertedData.image_text = { + image_url: contentData.imageUrl, + text_content: contentData.textContent, + view_duration_seconds: contentData.viewDurationSeconds, + } + } else if (type === 'video') { + convertedData.video = { + video_url: contentData.videoUrl, + completion_percentage: contentData.completionPercentage, + } + } else if (type === 'subjective') { + convertedData.subjective = { + pdf_url: contentData.pdfUrl, + description: contentData.description, + } + } else if (type === 'objective') { + convertedData.objective = { + exam_id: contentData.examId, + correct_rate_percentage: contentData.correctRatePercentage, + } + } else if (type === 'essay') { + convertedData.essay = { + exam_id: contentData.examId, + description: contentData.description, + } + } + + return convertedData + } + + const convertCondition = (condition: any) => { + const type = condition.type + const conditionData = condition.data + + const convertedData: any = {} + if (type === 'imageText') { + convertedData.image_text = { + view_duration_seconds: conditionData.viewDurationSeconds, + } + } else if (type === 'video') { + convertedData.video = { + completion_percentage: conditionData.completionPercentage, + } + } else if (type === 'subjective') { + convertedData.subjective = { + need_review: conditionData.needReview, + review_status: conditionData.reviewStatus, + } + } else if (type === 'objective') { + convertedData.objective = { + correct_rate_percentage: conditionData.correctRatePercentage, + } + } else if (type === 'essay') { + convertedData.essay = { + need_review: conditionData.needReview, + review_status: conditionData.reviewStatus, + allow_next_while_reviewing: conditionData.allowNextWhileReviewing ?? true, + } + } + + return convertedData + } + + const apiData = { + camp_id: data.campId, + section_id: data.sectionId, + task_type: data.taskType, + title: data.title ?? '', + content: convertContent(data.content), + condition: convertCondition(data.condition), + prerequisite_task_id: data.prerequisiteTaskId || '', + } + + return request.post('/camp/tasks/create', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: backendData.id || backendData.task_id || '', + } + } + } + }) +} + +export const getTask = (id: string) => { + return request.get(`/camp/tasks/detail`, { params: { id } }).then((response) => { + const backendData = response.data + const task = backendData.task || backendData + + // TODO: 需要解析复杂的任务数据结构 + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: task + } + } + }) +} + +export const listTasks = (params: ListTasksRequest) => { + // 转换参数格式:驼峰 -> 下划线 + const apiParams: any = { + page: params.page, + page_size: params.pageSize, + } + + if (params.keyword) { + apiParams.keyword = params.keyword + } + if (params.campId) { + apiParams.camp_id = params.campId + } + if (params.sectionId) { + apiParams.section_id = params.sectionId + } + if (params.taskType !== undefined && params.taskType !== 0) { + apiParams.task_type = params.taskType + } + return request.get('/camp/tasks', { params: apiParams }).then((response) => { + const backendData = response.data + + // tasks 可能是对象或数组,需要处理 + let tasksList = [] + if (Array.isArray(backendData.tasks)) { + tasksList = backendData.tasks + } else if (backendData.tasks && typeof backendData.tasks === 'object') { + tasksList = Object.values(backendData.tasks) + } + + // 转换 task_type 字符串到数字 + const convertTaskType = (type: any): number => { + if (typeof type === 'number') return type + + // 处理小写下划线格式(后端返回的格式) + const lowercaseMap: Record = { + 'image_text': 1, + 'video': 2, + 'subjective': 3, + 'objective': 4, + 'essay': 5, + } + + // 处理大写枚举格式 + const uppercaseMap: Record = { + 'TASK_TYPE_UNKNOWN': 0, + 'TASK_TYPE_IMAGE_TEXT': 1, + 'TASK_TYPE_VIDEO': 2, + 'TASK_TYPE_SUBJECTIVE': 3, + 'TASK_TYPE_OBJECTIVE': 4, + 'TASK_TYPE_ESSAY': 5, + } + + // 先尝试小写格式,再尝试大写格式 + return lowercaseMap[type] ?? uppercaseMap[type] ?? 0 + } + + // 转换 review_status 字符串到数字 + const convertReviewStatus = (status: any): number => { + if (typeof status === 'number') return status + const statusMap: Record = { + 'REVIEW_STATUS_PENDING': 0, + 'REVIEW_STATUS_APPROVED': 1, + 'REVIEW_STATUS_REJECTED': 2, + } + return statusMap[status] ?? 0 + } + + // 解析后端的 content 结构 + // 后端返回格式:content 是一个对象,里面包含 task_type 对应的键(如 image_text, video 等) + const parseContent = (backendContent: any, taskType: string | number): any => { + if (!backendContent) return { type: 'imageText', data: {} } + + // 根据 task_type 判断内容类型(支持字符串和数字) + if (taskType === 'image_text' || taskType === 1 || taskType === '1') { + // 后端返回格式:content.image_text = { image_url, text_content, view_duration_seconds } + const imageTextData = backendContent.image_text || backendContent + return { + type: 'imageText', + data: { + imageUrl: imageTextData.image_url || '', + textContent: imageTextData.text_content || '', + viewDurationSeconds: imageTextData.view_duration_seconds || 0, + } + } + } else if (taskType === 'video' || taskType === 2 || taskType === '2') { + // 后端返回格式:content.video = { video_url, completion_percentage } + // 或者 content 直接包含 video_url 和 completion_percentage + const videoData = backendContent.video || backendContent + return { + type: 'video', + data: { + videoUrl: videoData.video_url || '', + completionPercentage: videoData.completion_percentage || 0, + } + } + } else if (taskType === 'subjective' || taskType === 3 || taskType === '3') { + // 后端返回格式:content.subjective = { pdf_url, description } + const subjectiveData = backendContent.subjective || backendContent + return { + type: 'subjective', + data: { + pdfUrl: subjectiveData.pdf_url || '', + description: subjectiveData.description || '', + } + } + } else if (taskType === 'objective' || taskType === 4 || taskType === '4') { + // 后端返回格式:content.objective = { exam_id, correct_rate_percentage } + const objectiveData = backendContent.objective || backendContent + return { + type: 'objective', + data: { + examId: objectiveData.exam_id || '', + correctRatePercentage: objectiveData.correct_rate_percentage || 0, + } + } + } else if (taskType === 'essay' || taskType === 5 || taskType === '5') { + // 后端返回格式:content.essay = { exam_id, description } + const essayData = backendContent.essay || backendContent + return { + type: 'essay', + data: { + examId: essayData.exam_id || '', + description: essayData.description || '', + } + } + } + + return { type: 'imageText', data: {} } + } + + // 解析后端的 condition 结构 + // 后端返回格式:condition 是一个对象,里面包含 task_type 对应的键(如 image_text, video 等) + const parseCondition = (backendCondition: any, taskType: string | number): any => { + if (!backendCondition) return { type: 'imageText', data: {} } + + // 根据 task_type 判断条件类型(支持字符串和数字) + if (taskType === 'image_text' || taskType === 1 || taskType === '1') { + // 后端返回格式:condition.image_text = { view_duration_seconds } + // 或者 condition 直接包含 view_duration_seconds + const imageTextCondition = backendCondition.image_text || backendCondition + return { + type: 'imageText', + data: { + viewDurationSeconds: imageTextCondition.view_duration_seconds || 0, + } + } + } else if (taskType === 'video' || taskType === 2 || taskType === '2') { + // 后端返回格式:condition.video = { completion_percentage } + // 或者 condition 直接包含 completion_percentage + const videoCondition = backendCondition.video || backendCondition + return { + type: 'video', + data: { + completionPercentage: videoCondition.completion_percentage || 0, + } + } + } else if (taskType === 'subjective' || taskType === 3 || taskType === '3') { + // 后端返回格式:condition.subjective = { need_review, review_status, allow_next_while_reviewing } + const subjectiveCondition = backendCondition.subjective || backendCondition + return { + type: 'subjective', + data: { + needReview: subjectiveCondition.need_review ?? false, + reviewStatus: convertReviewStatus(subjectiveCondition.review_status), + allowNextWhileReviewing: subjectiveCondition.allow_next_while_reviewing ?? true, + } + } + } else if (taskType === 'objective' || taskType === 4 || taskType === '4') { + // 后端返回格式:condition.objective = { correct_rate_percentage } + // 或者 condition 直接包含 correct_rate_percentage + const objectiveCondition = backendCondition.objective || backendCondition + return { + type: 'objective', + data: { + correctRatePercentage: objectiveCondition.correct_rate_percentage || 0, + } + } + } else if (taskType === 'essay' || taskType === 5 || taskType === '5') { + // 后端返回格式:condition.essay = { need_review, review_status, allow_next_while_reviewing } + const essayCondition = backendCondition.essay || backendCondition + return { + type: 'essay', + data: { + needReview: essayCondition.need_review ?? false, + reviewStatus: convertReviewStatus(essayCondition.review_status), + allowNextWhileReviewing: essayCondition.allow_next_while_reviewing ?? true, + } + } + } + + return { type: 'imageText', data: {} } + } + + return { + data: { + code: 200, + message: backendData.message, + data: { + list: tasksList.map((item: any) => ({ + id: item.id, + campId: item.camp_id, + sectionId: item.section_id, + taskType: convertTaskType(item.task_type), + title: item.title ?? '', + prerequisiteTaskId: item.prerequisite_task_id ?? '', + content: parseContent(item.content, item.task_type), + condition: parseCondition(item.condition, item.task_type), + })), + total: backendData.total || 0, + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +export const updateTask = (data: Task) => { + // 转换前端格式到后端格式(复用 createTask 的逻辑) + const convertContent = (content: any) => { + const type = content.type + const contentData = content.data + + const convertedData: any = {} + if (type === 'imageText') { + convertedData.image_text = { + image_url: contentData.imageUrl, + text_content: contentData.textContent, + view_duration_seconds: contentData.viewDurationSeconds, + } + } else if (type === 'video') { + convertedData.video = { + video_url: contentData.videoUrl, + completion_percentage: contentData.completionPercentage, + } + } else if (type === 'subjective') { + convertedData.subjective = { + pdf_url: contentData.pdfUrl, + description: contentData.description, + } + } else if (type === 'objective') { + convertedData.objective = { + exam_id: contentData.examId, + correct_rate_percentage: contentData.correctRatePercentage, + } + } else if (type === 'essay') { + convertedData.essay = { + exam_id: contentData.examId, + description: contentData.description, + } + } + + return convertedData + } + + const convertCondition = (condition: any) => { + const type = condition.type + const conditionData = condition.data + + const convertedData: any = {} + if (type === 'imageText') { + convertedData.image_text = { + view_duration_seconds: conditionData.viewDurationSeconds, + } + } else if (type === 'video') { + convertedData.video = { + completion_percentage: conditionData.completionPercentage, + } + } else if (type === 'subjective') { + convertedData.subjective = { + need_review: conditionData.needReview, + review_status: conditionData.reviewStatus, + } + } else if (type === 'objective') { + convertedData.objective = { + correct_rate_percentage: conditionData.correctRatePercentage, + } + } else if (type === 'essay') { + convertedData.essay = { + need_review: conditionData.needReview, + review_status: conditionData.reviewStatus, + allow_next_while_reviewing: conditionData.allowNextWhileReviewing ?? true, + } + } + + return convertedData + } + + const apiData = { + id: data.id, + camp_id: data.campId, + section_id: data.sectionId, + task_type: data.taskType, + title: data.title ?? '', + content: convertContent(data.content), + condition: convertCondition(data.condition), + prerequisite_task_id: data.prerequisiteTaskId || '', + } + + return request.post('/camp/tasks/update', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +export const deleteTask = (id: string) => { + return request.post('/camp/tasks/delete', { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +// ==================== 用户进度管理 ==================== + +export const updateUserProgress = (data: Omit) => { + // 转换 review_status 数字枚举到后端期望的字符串格式 + const convertReviewStatus = (status: number): string => { + const statusMap: Record = { + 0: 'pending', // PENDING + 1: 'approved', // APPROVED + 2: 'rejected', // REJECTED + } + return statusMap[status] ?? 'pending' + } + + // 转换字段格式:驼峰 -> 下划线 + const apiData: Record = { + user_id: data.userId, + task_id: data.taskId, + is_completed: data.isCompleted, + completed_at: data.completedAt, + review_status: convertReviewStatus(data.reviewStatus), + review_comment: data.reviewComment, + review_images: data.reviewImages, + } + + // 申论题每题审核状态:后端据此计算任务整体 review_status + if (Array.isArray(data.essayReviewStatuses) && data.essayReviewStatuses.length > 0) { + apiData.essay_review_statuses = data.essayReviewStatuses.map((s) => + convertReviewStatus(typeof s === 'number' ? s : (s as number)) + ) + } + + // 后端路由是 PUT /camp/progress,不是 POST /camp/progress/update + return request.put('/camp/progress', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +export const getUserProgress = (userId: string, taskId: string) => { + return request.get('/camp/progress', { + params: { user_id: userId, task_id: taskId }, + }).then((response) => { + const backendData = response.data + const progress = backendData.progress || backendData + + // 转换 review_status 字符串到数字 + const convertReviewStatus = (status: any): number => { + if (typeof status === 'number') return status + const s = typeof status === 'string' ? status.trim() : '' + const lowercaseMap: Record = { + 'pending': 0, + 'approved': 1, + 'rejected': 2, + } + const uppercaseMap: Record = { + 'PENDING': 0, + 'APPROVED': 1, + 'REJECTED': 2, + } + const prefixedMap: Record = { + 'REVIEW_STATUS_PENDING': 0, + 'REVIEW_STATUS_APPROVED': 1, + 'REVIEW_STATUS_REJECTED': 2, + } + return lowercaseMap[s] ?? uppercaseMap[s] ?? prefixedMap[s] ?? 0 + } + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: progress.id, + userId: progress.user_id, + taskId: progress.task_id, + isCompleted: progress.is_completed ?? false, + completedAt: progress.completed_at || '', + reviewStatus: convertReviewStatus(progress.review_status), + reviewComment: progress.review_comment || '', + reviewImages: Array.isArray(progress.review_images) + ? progress.review_images + : progress.review_images + ? Object.values(progress.review_images) + : [], + answerImages: Array.isArray(progress.answer_images) + ? progress.answer_images + : progress.answer_images + ? Object.values(progress.answer_images) + : [], + essayReviewStatuses: Array.isArray(progress.essay_review_statuses) + ? progress.essay_review_statuses.map((s: string) => convertReviewStatus(s)) + : undefined, + needReview: progress.need_review ?? false, + } + } + } + }) +} + +export const listUserProgress = (params: ListUserProgressRequest) => { + // 转换参数格式:驼峰 -> 下划线 + const apiParams: any = { + page: params.page, + page_size: params.pageSize, + } + + if (params.userId) { + apiParams.user_id = params.userId + } + + if (params.userKeyword?.trim()) { + apiParams.user_keyword = params.userKeyword.trim() + } + + if (params.taskId) { + apiParams.task_id = params.taskId + } + + if (params.sectionId) { + apiParams.section_id = params.sectionId + } + + if (params.campId) { + apiParams.camp_id = params.campId + } + + if (params.reviewStatus !== undefined && params.reviewStatus !== '') { + apiParams.review_status = + typeof params.reviewStatus === 'number' + ? (params.reviewStatus === 0 ? 'pending' : params.reviewStatus === 1 ? 'approved' : 'rejected') + : String(params.reviewStatus) + } + + return request.get('/camp/progress/list', { params: apiParams }).then((response) => { + const backendData = response.data + + // progress_list 可能是对象或数组 + let progressList = [] + if (Array.isArray(backendData.progress_list)) { + progressList = backendData.progress_list + } else if (backendData.progress_list && typeof backendData.progress_list === 'object') { + progressList = Object.values(backendData.progress_list) + } + + // 转换 review_status 字符串到数字 + const convertReviewStatus = (status: any): number => { + if (typeof status === 'number') return status + const s = typeof status === 'string' ? status.trim() : '' + // 小写(后端 review_status 常用) + const lowercaseMap: Record = { + 'pending': 0, + 'approved': 1, + 'rejected': 2, + } + // 大写(后端 essay_review_statuses 存的是 PENDING/APPROVED/REJECTED) + const uppercaseMap: Record = { + 'PENDING': 0, + 'APPROVED': 1, + 'REJECTED': 2, + } + // 带前缀的枚举(兼容) + const prefixedMap: Record = { + 'REVIEW_STATUS_PENDING': 0, + 'REVIEW_STATUS_APPROVED': 1, + 'REVIEW_STATUS_REJECTED': 2, + } + return lowercaseMap[s] ?? uppercaseMap[s] ?? prefixedMap[s] ?? 0 + } + + return { + data: { + code: 200, + message: backendData.message, + data: { + list: progressList.map((item: any) => { + // 提取并过滤空字符串 + const rawReviewImages = Array.isArray(item.review_images) + ? item.review_images + : item.review_images + ? Object.values(item.review_images) + : [] + const rawAnswerImages = Array.isArray(item.answer_images) + ? item.answer_images + : item.answer_images + ? Object.values(item.answer_images) + : [] + // 申论题按题答案:essay_answer_images 为二维数组 [[url,url],[url],...] + let rawEssayAnswerImages: string[][] = [] + if (Array.isArray(item.essay_answer_images) && item.essay_answer_images.length > 0) { + rawEssayAnswerImages = item.essay_answer_images.map((arr: any) => + Array.isArray(arr) ? arr.filter((url: string) => url && String(url).trim() !== '') : [] + ) + } + return { + id: item.id, + userId: item.user_id, + taskId: item.task_id, + campId: item.camp_id || item.campId, + sectionId: item.section_id || item.sectionId, + isCompleted: item.is_completed ?? false, + completedAt: item.completed_at || '', + reviewStatus: convertReviewStatus(item.review_status), + reviewComment: item.review_comment || '', + reviewImages: rawReviewImages.filter((url: string) => url && url.trim() !== ''), + answerImages: rawAnswerImages.filter((url: string) => url && url.trim() !== ''), + essayAnswerImages: rawEssayAnswerImages, + essayReviewStatuses: Array.isArray(item.essay_review_statuses) + ? item.essay_review_statuses.map((s: string) => convertReviewStatus(s)) + : undefined, + needReview: item.need_review ?? false, + } + }), + total: backendData.total || 0, + userIds: Array.isArray(backendData.user_ids) ? backendData.user_ids : [], + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +/** 获取待审核任务数量(后台右上角提示用) */ +export const getPendingReviewCount = () => { + return request.get<{ success: boolean; count: number }>('/camp/progress/pending-count').then((response) => { + const data = response.data as { success?: boolean; count?: number } + return { data: { code: 200, count: data?.count ?? 0 } } + }) +} diff --git a/src/api/document.ts b/src/api/document.ts new file mode 100644 index 0000000..bdedc2e --- /dev/null +++ b/src/api/document.ts @@ -0,0 +1,66 @@ +import request from '@/utils/request' + +export interface DocFolder { + id: string + name: string + sort_order: number + created_at?: string + updated_at?: string +} + +export interface DocFile { + id: string + folder_id: string + name: string + file_name: string + file_url: string + file_size: number + mime_type: string + created_at?: string + updated_at?: string +} + +export const listFolders = () => + request.get<{ success: boolean; list: DocFolder[] }>('/document/folders').then((res) => res.data) + +export const createFolder = (data: { name: string; sort_order?: number }) => + request.post<{ success: boolean; folder: DocFolder }>('/document/folders/create', { + name: data.name, + sort_order: data.sort_order ?? 0, + }).then((res) => res.data) + +export const updateFolder = (data: { id: string; name: string; sort_order?: number }) => + request.post<{ success: boolean }>('/document/folders/update', { + id: data.id, + name: data.name, + sort_order: data.sort_order ?? 0, + }).then((res) => res.data) + +export const deleteFolder = (id: string) => + request.post<{ success: boolean }>('/document/folders/delete', null, { params: { id } }).then((res) => res.data) + +export const listFiles = (folderId: string) => + request.get<{ success: boolean; list: DocFile[] }>('/document/files', { params: { folder_id: folderId } }).then((res) => res.data) + +export const createFile = (data: { + folder_id: string + name?: string + file_name: string + file_url: string + file_size: number + mime_type: string +}) => + request.post<{ success: boolean; file: DocFile }>('/document/files/create', { + folder_id: data.folder_id, + name: data.name ?? data.file_name, + file_name: data.file_name, + file_url: data.file_url, + file_size: data.file_size, + mime_type: data.mime_type, + }).then((res) => res.data) + +export const updateFile = (data: { id: string; name: string }) => + request.post<{ success: boolean }>('/document/files/update', { id: data.id, name: data.name }).then((res) => res.data) + +export const deleteFile = (id: string) => + request.post<{ success: boolean }>('/document/files/delete', null, { params: { id } }).then((res) => res.data) diff --git a/src/api/oss.ts b/src/api/oss.ts new file mode 100644 index 0000000..365f385 --- /dev/null +++ b/src/api/oss.ts @@ -0,0 +1,28 @@ +import request from '@/utils/request' + +/** + * OSS 上传凭证响应 + */ +export interface PolicyToken { + policy: string + security_token: string + x_oss_signature_version: string + x_oss_credential: string + x_oss_date: string + signature: string + host: string + dir: string +} + +/** + * 获取 OSS 上传凭证 + * @param dir 上传目录,默认为 'camp' + * @returns OSS 上传凭证 + */ +export async function getUploadSignature(dir: string = 'camp'): Promise { + const response = await request.get('/oss/upload/signature', { + params: { dir }, + }) + return response.data +} + diff --git a/src/api/permission.ts b/src/api/permission.ts new file mode 100644 index 0000000..71aa82b --- /dev/null +++ b/src/api/permission.ts @@ -0,0 +1,140 @@ +import request from '@/utils/request' + +// 权限接口 +export interface Permission { + id: string + name: string + code: string + resource: string + action: string + description: string + created_at: string + updated_at: string +} + +// 创建权限请求 +export interface CreatePermissionRequest { + name: string + code: string + resource: string + action: string + description?: string +} + +// 更新权限请求 +export interface UpdatePermissionRequest { + id: string + name: string + code: string + resource: string + action: string + description?: string +} + +// 权限列表响应 +export interface PermissionListResponse { + list: Permission[] + total: number + page: number + page_size: number +} + +// 获取权限列表 +export const getPermissionList = (params: { + keyword?: string + resource?: string + page?: number + page_size?: number +}) => { + return request.get('/permissions/list', { params }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取权限列表成功', + data: backendData.data || { + list: [], + total: 0, + page: 1, + page_size: 10, + } + } + } + }) +} + +// 获取权限详情 +export const getPermission = (id: string) => { + return request.get('/permissions/detail', { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取权限详情成功', + data: backendData.data + } + } + }) +} + +// 创建权限 +export const createPermission = (data: CreatePermissionRequest) => { + return request.post('/permissions/create', data).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建权限成功', + data: backendData.data + } + } + }) +} + +// 更新权限 +export const updatePermission = (data: UpdatePermissionRequest) => { + return request.post('/permissions/update', data).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新权限成功', + data: backendData.data + } + } + }) +} + +// 删除权限 +export const deletePermission = (id: string) => { + return request.post('/permissions/delete', {}, { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除权限成功' + } + } + }) +} + +// 获取资源列表 +export const getResources = () => { + return request.get('/permissions/resources').then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取资源列表成功', + data: backendData.data || [] + } + } + }) +} + diff --git a/src/api/question.ts b/src/api/question.ts new file mode 100644 index 0000000..4adc12a --- /dev/null +++ b/src/api/question.ts @@ -0,0 +1,910 @@ +import request from '@/utils/request' +import type { + Question, + Paper, + Material, + KnowledgeTreeNode, + CreateKnowledgeTreeNodeRequest, + UpdateKnowledgeTreeNodeRequest, + SearchQuestionsRequest, + SearchPapersRequest, + SearchMaterialsRequest, + CreateAnswerRecordsRequest, + AddQuestionToPaperRequest, + RemoveQuestionFromPaperRequest, +} from '@/types/question' +import { MaterialType } from '@/types/question' + +// ==================== 题目管理 ==================== + +export const createQuestion = (data: Omit) => { + const apiData: any = { + type: data.type, + content: data.content, + options: data.options, + answer: data.answer, + explanation: data.explanation, + } + + if (data.name) { + apiData.name = data.name + } + + if (data.source) { + apiData.source = data.source + } + + if (data.materialId) { + apiData.material_id = data.materialId + } + + // 始终发送 knowledge_tree_ids,即使是空数组 + apiData.knowledge_tree_ids = Array.isArray(data.knowledgeTreeIds) ? data.knowledgeTreeIds : [] + + return request.post('/questions', apiData).then((response) => { + const backendData = response.data + + // 后端返回的数据结构可能是: + // { question: { id: ... }, ... } 或 { id: ..., question_id: ... } + const questionId = backendData.question?.id || backendData.id || backendData.question_id || '' + + console.log('createQuestion API - 后端响应:', backendData) + console.log('createQuestion API - 提取的题目ID:', questionId) + + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: questionId, + question: backendData.question, // 保留完整的问题对象,方便调试 + } + } + } + }).catch((error: any) => { + console.error('创建题目失败:', error) + const errorMsg = error?.response?.data?.message || error?.message || '创建失败' + throw new Error(errorMsg) + }) +} + +export const getQuestion = (id: string) => { + return request.get(`/questions/detail`, { params: { id } }).then((response) => { + const backendData = response.data + const question = backendData.question || backendData + + // 转换题目类型字符串到数字 + const convertQuestionType = (type: any): number => { + if (typeof type === 'number') return type + const typeMap: Record = { + 'QUESTION_TYPE_UNSPECIFIED': 0, + 'QUESTION_TYPE_SUBJECTIVE': 1, + 'QUESTION_TYPE_SINGLE_CHOICE': 2, + 'QUESTION_TYPE_MULTIPLE_CHOICE': 3, + 'QUESTION_TYPE_TRUE_FALSE': 4, + } + return typeMap[type] ?? 0 + } + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: question.id, + type: convertQuestionType(question.type), + content: question.content, + options: question.options || [], + answer: question.answer, + explanation: question.explanation, + knowledgeTreeIds: question.knowledge_tree_ids || [], + createdAt: question.created_at || 0, + updatedAt: question.updated_at || 0, + } + } + } + }) +} + +export const searchQuestions = (params: SearchQuestionsRequest) => { + const apiData: any = { + page: params.page, + page_size: params.pageSize, + } + + if (params.query) { + apiData.query = params.query + } + + if (params.type !== undefined && params.type !== 0) { + apiData.type = params.type + } + + if (params.tags && params.tags.length > 0) { + apiData.tags = params.tags + } + + return request.get('/questions/search', { params: apiData }).then((response) => { + const backendData = response.data + + let questionsList = [] + if (Array.isArray(backendData.questions)) { + questionsList = backendData.questions + } else if (backendData.questions && typeof backendData.questions === 'object') { + questionsList = Object.values(backendData.questions) + } + + // 转换题目类型字符串到数字 + const convertQuestionType = (type: any): number => { + if (typeof type === 'number') return type + const typeMap: Record = { + 'QUESTION_TYPE_UNSPECIFIED': 0, + 'QUESTION_TYPE_SUBJECTIVE': 1, + 'QUESTION_TYPE_SINGLE_CHOICE': 2, + 'QUESTION_TYPE_MULTIPLE_CHOICE': 3, + 'QUESTION_TYPE_TRUE_FALSE': 4, + } + return typeMap[type] ?? 0 + } + + return { + data: { + code: 200, + message: backendData.message, + data: { + list: questionsList.map((item: any) => { + // 调试:检查原始数据(包括所有字段) + console.log('API原始数据 - 完整对象:', item) + console.log('API原始数据 - 题目ID:', item.id) + console.log('API原始数据 - knowledge_tree_ids字段:', item.knowledge_tree_ids) + console.log('API原始数据 - knowledge_tree_ids类型:', typeof item.knowledge_tree_ids) + console.log('API原始数据 - knowledge_tree_ids是否为数组:', Array.isArray(item.knowledge_tree_ids)) + + const knowledgeTreeIds = Array.isArray(item.knowledge_tree_ids) ? item.knowledge_tree_ids : [] + const knowledgeTreeNames = Array.isArray(item.knowledge_tree_names) ? item.knowledge_tree_names : [] + console.log('API转换后 - 知识树IDs:', knowledgeTreeIds) + + return { + id: item.id, + type: convertQuestionType(item.type), + content: item.content, + options: Array.isArray(item.options) ? item.options : [], + answer: item.answer, + explanation: item.explanation, + knowledgeTreeIds: knowledgeTreeIds, + knowledgeTreeNames: knowledgeTreeNames, + name: item.name || '', + source: item.source || '', + materialId: item.material_id || '', + createdAt: item.created_at || 0, + updatedAt: item.updated_at || 0, + } + }), + total: backendData.total || 0, + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +export const updateQuestion = (data: Question) => { + const apiData: any = { + id: data.id, + type: data.type, + content: data.content, + options: data.options, + answer: data.answer, + explanation: data.explanation, + } + + if (data.name) { + apiData.name = data.name + } + + if (data.source) { + apiData.source = data.source + } + + if (data.materialId) { + apiData.material_id = data.materialId + } + + // 始终发送 knowledge_tree_ids,即使是空数组 + apiData.knowledge_tree_ids = Array.isArray(data.knowledgeTreeIds) ? data.knowledgeTreeIds : [] + + console.log('API发送的完整数据:', JSON.stringify(apiData, null, 2)) + console.log('API发送的知识树IDs:', apiData.knowledge_tree_ids) + + return request.post('/questions/update', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }).catch((error: any) => { + console.error('更新题目失败:', error) + const errorMsg = error?.response?.data?.message || error?.message || '更新失败' + throw new Error(errorMsg) + }) +} + +export const deleteQuestion = (id: string) => { + return request.post(`/questions/delete?id=${id}`).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +export const batchDeleteQuestions = (questionIds: string[]) => { + return request.post('/questions/batch_delete', { question_ids: questionIds }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '批量删除成功', + data: { + deletedCount: backendData.deleted_count || 0, + failedQuestionIds: backendData.failed_question_ids || [], + } + } + } + }) +} + +// ==================== 试卷管理 ==================== + +export const createPaper = (data: Omit) => { + const apiData: any = { + title: data.title, + description: data.description, + question_ids: data.questionIds || [], + } + + if (data.source) { + apiData.source = data.source + } + + if (data.materialIds && data.materialIds.length > 0) { + apiData.material_ids = data.materialIds + } + + console.log('API createPaper - 发送的数据:', apiData) + console.log('API createPaper - question_ids:', apiData.question_ids) + + return request.post('/papers/create', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: backendData.id || backendData.paper_id || '', + } + } + } + }) +} + +export const getPaper = (id: string) => { + return request.get(`/papers/detail`, { params: { id } }).then((response) => { + const backendData = response.data + const paper = backendData.paper || backendData + + // questions 可能是对象或数组,需要处理 + let questionsList = [] + if (Array.isArray(paper.questions)) { + questionsList = paper.questions + } else if (paper.questions && typeof paper.questions === 'object') { + questionsList = Object.values(paper.questions) + } + + // question_ids 可能是对象或数组 + let questionIds = [] + if (Array.isArray(paper.question_ids)) { + questionIds = paper.question_ids + } else if (paper.question_ids && typeof paper.question_ids === 'object') { + questionIds = Object.values(paper.question_ids) + } + + // material_ids 可能是对象或数组 + let materialIds = [] + if (Array.isArray(paper.material_ids)) { + materialIds = paper.material_ids + } else if (paper.material_ids && typeof paper.material_ids === 'object') { + materialIds = Object.values(paper.material_ids) + } + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: paper.id, + title: paper.title, + description: paper.description, + source: paper.source || '', + questionIds, + materialIds: materialIds, + knowledgeTreeIds: paper.knowledge_tree_ids || [], + questions: questionsList, + createdAt: paper.created_at || 0, + updatedAt: paper.updated_at || 0, + } + } + } + }) +} + +export const searchPapers = (params: SearchPapersRequest) => { + const apiData: any = { + page: params.page, + page_size: params.pageSize, + } + + if (params.query) { + apiData.query = params.query + } + + return request.get('/papers/search', { params: apiData }).then((response) => { + const backendData = response.data + + let papersList = [] + if (Array.isArray(backendData.papers)) { + papersList = backendData.papers + } else if (backendData.papers && typeof backendData.papers === 'object') { + papersList = Object.values(backendData.papers) + } + + return { + data: { + code: 200, + message: backendData.message, + data: { + list: papersList.map((item: any) => ({ + id: item.id, + title: item.title, + description: item.description, + source: item.source || '', + questionIds: item.question_ids || [], + materialIds: item.material_ids || [], + knowledgeTreeIds: item.knowledge_tree_ids || [], + knowledgeTreeNames: item.knowledge_tree_names || [], + questions: item.questions || [], + createdAt: item.created_at || 0, + updatedAt: item.updated_at || 0, + })), + total: backendData.total || 0, + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +export const updatePaper = (data: Paper) => { + const apiData: any = { + id: data.id, + title: data.title, + description: data.description, + question_ids: data.questionIds || [], + } + + if (data.source) { + apiData.source = data.source + } + + if (data.materialIds && data.materialIds.length > 0) { + apiData.material_ids = data.materialIds + } + + console.log('API updatePaper - 发送的数据:', apiData) + console.log('API updatePaper - question_ids:', apiData.question_ids) + + return request.post('/papers/update', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +export const deletePaper = (id: string) => { + return request.post(`/papers/delete?id=${id}`).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +export const batchDeletePapers = (paperIds: string[]) => { + return request.post('/papers/batch_delete', { paper_ids: paperIds }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '批量删除成功', + data: { + deletedCount: backendData.deleted_count || 0, + failedPaperIds: backendData.failed_paper_ids || [], + } + } + } + }) +} + +export const addQuestionToPaper = (data: AddQuestionToPaperRequest) => { + const apiData = { + paper_id: data.paperId, + question_ids: data.questionIds, + } + + return request.post('/papers/add_question', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '添加成功', + data: { + addedCount: backendData.added_count || 0, + failedQuestionIds: backendData.failed_question_ids || [], + } + } + } + }) +} + +export const removeQuestionFromPaper = (data: RemoveQuestionFromPaperRequest) => { + const apiData = { + paper_id: data.paperId, + question_ids: data.questionIds, + } + + return request.post('/papers/remove_question', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '移除成功', + data: { + removedCount: backendData.removed_count || 0, + failedQuestionIds: backendData.failed_question_ids || [], + } + } + } + }) +} + +// ==================== 答题记录管理 ==================== + +export const createAnswerRecords = (data: CreateAnswerRecordsRequest) => { + const apiData = { + user_id: data.userId, + paper_id: data.paperId, + answers: data.answers.map((a) => ({ + question_id: a.questionId, + user_answer: a.userAnswer, + })), + start_time: data.startTime, + end_time: data.endTime, + } + + return request.post('/answer-records/create', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '提交成功', + data: { + results: backendData.results || [], + totalQuestions: backendData.total_questions || 0, + correctCount: backendData.correct_count || 0, + wrongCount: backendData.wrong_count || 0, + } + } + } + }) +} + +export const getAnswerRecord = (id: string) => { + return request.get(`/answer-records/detail`, { params: { id } }).then((response) => { + const backendData = response.data + const record = backendData.record || backendData + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: record.id, + userId: record.user_id, + questionId: record.question_id, + paperId: record.paper_id, + userAnswer: record.user_answer, + correctAnswer: record.correct_answer, + isCorrect: record.is_correct ?? false, + startTime: record.start_time || 0, + endTime: record.end_time || 0, + createdAt: record.created_at || 0, + updatedAt: record.updated_at || 0, + } + } + } + }) +} + +export const getUserAnswerRecord = (userId: string, questionId: string) => { + return request.get('/answer-records/user', { + params: { user_id: userId, question_id: questionId }, + }).then((response) => { + const backendData = response.data + const record = backendData.record || backendData + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: record ? { + id: record.id, + userId: record.user_id, + questionId: record.question_id, + paperId: record.paper_id, + userAnswer: record.user_answer, + correctAnswer: record.correct_answer, + isCorrect: record.is_correct ?? false, + startTime: record.start_time || 0, + endTime: record.end_time || 0, + createdAt: record.created_at || 0, + updatedAt: record.updated_at || 0, + } : null + } + } + }) +} + +export const getPaperAnswerStatistics = (userId: string, paperId: string) => { + return request.get('/answer-records/statistics', { + params: { + user_id: userId, + paper_id: paperId, + } + }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + userId: backendData.user_id, + paperId: backendData.paper_id, + totalQuestions: backendData.total_questions || 0, + answeredQuestions: backendData.answered_questions || 0, + correctAnswers: backendData.correct_answers || 0, + wrongAnswers: backendData.wrong_answers || 0, + } + } + } + }) +} + +export const deleteAnswerRecord = (userId: string) => { + return request.post(`/answer-records/delete?user_id=${userId}`).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +// ==================== 材料管理 ==================== + +export const createMaterial = (data: Omit) => { + const apiData = { + type: data.type || MaterialType.OBJECTIVE, + name: data.name || '', + content: data.content, + } + + return request.post('/materials', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: backendData.id || backendData.material_id || '', + } + } + } + }) +} + +export const getMaterial = (id: string) => { + return request.get(`/materials/detail`, { params: { id } }).then((response) => { + const backendData = response.data + const material = backendData.material || backendData + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: material.id || material.material_id || '', + type: material.type || MaterialType.OBJECTIVE, + name: material.name || '', + content: material.content || '', + createdAt: material.created_at || material.createdAt || 0, + updatedAt: material.updated_at || material.updatedAt || 0, + } + } + } + }) +} + +export const searchMaterials = (params: SearchMaterialsRequest) => { + const apiParams: any = { + page: params.page, + page_size: params.pageSize, + } + + if (params.query) { + apiParams.query = params.query + } + + if (params.type) { + apiParams.type = params.type + } + + return request.get('/materials/search', { params: apiParams }).then((response) => { + const backendData = response.data + const materialsList = backendData.materials || backendData.list || [] + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + list: materialsList.map((item: any) => ({ + id: item.id || item.material_id || '', + type: item.type || MaterialType.OBJECTIVE, + name: item.name || '', + content: item.content || '', + createdAt: item.created_at || item.createdAt || 0, + updatedAt: item.updated_at || item.updatedAt || 0, + })), + total: backendData.total || 0, + page: params.page, + pageSize: params.pageSize, + } + } + } + }) +} + +export const updateMaterial = (data: Material) => { + const apiData = { + id: data.id, + type: data.type || MaterialType.OBJECTIVE, + name: data.name || '', + content: data.content, + } + + return request.put(`/materials/${data.id}`, apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +export const deleteMaterial = (id: string) => { + return request.delete(`/materials/${id}`).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} + +// ==================== 知识树管理 ==================== + +// 创建知识树节点 +export const createKnowledgeTreeNode = (data: CreateKnowledgeTreeNodeRequest) => { + const apiData = { + type: data.type, + title: data.title, + parent_id: data.parentId !== undefined ? data.parentId : '', + } + + return request.post('/knowledge-trees', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建成功', + data: { + id: backendData.node?.id || '', + } + } + } + }) +} + +// 获取知识树节点详情 +export const getKnowledgeTreeNode = (id: string) => { + return request.get('/knowledge-trees/detail', { params: { id } }).then((response) => { + const backendData = response.data + const node = backendData.node || backendData + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + id: node.id || '', + type: node.type || '', + title: node.title || '', + parentId: node.parent_id || node.parentId || '', + createdAt: node.created_at || node.createdAt || 0, + updatedAt: node.updated_at || node.updatedAt || 0, + } + } + } + }) +} + +// 获取所有知识树节点(扁平列表) +export const getAllKnowledgeTreeNodes = (type: 'objective' | 'subjective') => { + return request.get('/knowledge-trees/list', { params: { type } }).then((response) => { + const backendData = response.data + const nodes = backendData.nodes || [] + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + list: nodes.map((item: any) => ({ + id: item.id || '', + type: item.type || '', + title: item.title || '', + parentId: item.parent_id || item.parentId || '', + createdAt: item.created_at || item.createdAt || 0, + updatedAt: item.updated_at || item.updatedAt || 0, + })), + } + } + } + }) +} + +// 根据父节点ID获取子节点列表 +export const getKnowledgeTreeByParentID = (parentId: string = '', type: 'objective' | 'subjective') => { + return request.get('/knowledge-trees/children', { params: { parent_id: parentId, type } }).then((response) => { + const backendData = response.data + const nodes = backendData.nodes || [] + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + list: nodes.map((item: any) => ({ + id: item.id || '', + type: item.type || '', + title: item.title || '', + parentId: item.parent_id || item.parentId || '', + createdAt: item.created_at || item.createdAt || 0, + updatedAt: item.updated_at || item.updatedAt || 0, + })), + } + } + } + }) +} + +// 获取完整知识树 +export const getKnowledgeTree = (type: 'objective' | 'subjective') => { + return request.get('/knowledge-trees/tree', { params: { type } }).then((response) => { + const backendData = response.data + const tree = backendData.tree || [] + + // 递归转换树形结构 + const convertTree = (nodes: any[]): KnowledgeTreeNode[] => { + return nodes.map((item: any) => ({ + id: item.id || '', + type: item.type || '', + title: item.title || '', + parentId: item.parent_id || item.parentId || '', + children: item.children ? convertTree(item.children) : undefined, + createdAt: item.created_at || item.createdAt || 0, + updatedAt: item.updated_at || item.updatedAt || 0, + })) + } + + return { + data: { + code: 200, + message: backendData.message || '获取成功', + data: { + tree: convertTree(tree), + } + } + } + }) +} + +// 更新知识树节点 +export const updateKnowledgeTreeNode = (data: UpdateKnowledgeTreeNodeRequest) => { + const apiData = { + id: data.id, + title: data.title, + parent_id: data.parentId || '', + } + + return request.post('/knowledge-trees/update', apiData).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新成功', + } + } + }) +} + +// 删除知识树节点 +export const deleteKnowledgeTreeNode = (id: string) => { + return request.delete(`/knowledge-trees/${id}`).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除成功', + } + } + }) +} diff --git a/src/api/role.ts b/src/api/role.ts new file mode 100644 index 0000000..680b6ae --- /dev/null +++ b/src/api/role.ts @@ -0,0 +1,153 @@ +import request from '@/utils/request' + +// 角色接口 +export interface Role { + id: string + name: string + code: string + description: string + status: number // 0=禁用,1=启用 + created_at: string + updated_at: string +} + +// 创建角色请求 +export interface CreateRoleRequest { + name: string + code?: string // 可选,如果不提供则由后端自动生成 + description?: string + status?: number +} + +// 更新角色请求 +export interface UpdateRoleRequest { + id: string + name: string + code: string + description?: string + status?: number +} + +// 角色列表响应 +export interface RoleListResponse { + list: Role[] + total: number + page: number + page_size: number +} + +// 获取角色列表 +export const getRoleList = (params: { + keyword?: string + page?: number + page_size?: number +}) => { + return request.get('/roles/list', { params }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取角色列表成功', + data: backendData.data || { + list: [], + total: 0, + page: 1, + page_size: 10, + } + } + } + }) +} + +// 获取角色详情 +export const getRole = (id: string) => { + return request.get('/roles/detail', { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取角色详情成功', + data: backendData.data + } + } + }) +} + +// 创建角色 +export const createRole = (data: CreateRoleRequest) => { + return request.post('/roles/create', data).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '创建角色成功', + data: backendData.data + } + } + }) +} + +// 更新角色 +export const updateRole = (data: UpdateRoleRequest) => { + return request.post('/roles/update', data).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '更新角色成功', + data: backendData.data + } + } + }) +} + +// 删除角色 +export const deleteRole = (id: string) => { + return request.post('/roles/delete', {}, { params: { id } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '删除角色成功' + } + } + }) +} + +// 获取角色权限 +export const getRolePermissions = (roleId: string) => { + return request.get('/roles/permissions', { params: { role_id: roleId } }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '获取角色权限成功', + data: backendData.data || [] + } + } + }) +} + +// 设置角色权限 +export const setRolePermissions = (roleId: string, permissionIds: string[]) => { + return request.post('/roles/permissions', { + role_id: roleId, + permission_ids: permissionIds + }).then((response) => { + const backendData = response.data + + return { + data: { + code: 200, + message: backendData.message || '设置角色权限成功' + } + } + }) +} + diff --git a/src/api/statistics.ts b/src/api/statistics.ts new file mode 100644 index 0000000..733cac8 --- /dev/null +++ b/src/api/statistics.ts @@ -0,0 +1,31 @@ +import request from '@/utils/request' + +// 统计数据接口 +export interface Statistics { + user_count: number // 用户总数 + camp_count: number // 打卡营数量 + question_count: number // 题目数量 + paper_count: number // 试卷数量 +} + +// 获取仪表盘统计数据 +export const getDashboardStatistics = () => { + return request.get('/statistics/dashboard').then((response) => { + const backendData = response.data + + // 转换为前端期望的格式 + return { + data: { + code: 200, + message: backendData.message || '获取统计数据成功', + data: backendData.data || { + user_count: 0, + camp_count: 0, + question_count: 0, + paper_count: 0, + } + } + } + }) +} + diff --git a/src/components/ImageUpload.tsx b/src/components/ImageUpload.tsx new file mode 100644 index 0000000..2f57883 --- /dev/null +++ b/src/components/ImageUpload.tsx @@ -0,0 +1,212 @@ +import { useState } from 'react' +import { Upload, Button, message, Image } from 'antd' +import { UploadOutlined, DeleteOutlined } from '@ant-design/icons' +import type { UploadProps } from 'antd' +import { getUploadSignature, type PolicyToken } from '@/api/oss' +import request from '@/utils/request' + +interface ImageUploadProps { + value?: string + onChange?: (url: string) => void + placeholder?: string + maxSize?: number // MB + accept?: string +} + +const ImageUpload: React.FC = ({ + value, + onChange, + placeholder = '请选择图片', + maxSize = 5, + accept = 'image/*', +}) => { + const [uploading, setUploading] = useState(false) + + // 获取OSS上传凭证 + const getUploadCredentials = async (dir: string = 'camp'): Promise => { + try { + return await getUploadSignature(dir) + } catch (error) { + throw new Error('获取上传凭证失败') + } + } + + // 通过后端代理上传(备用方案) + const uploadViaBackend = async (file: File) => { + const formData = new FormData() + formData.append('file', file) + formData.append('dir', 'camp') + + try { + const response = await request.post('/upload_to_oss', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + return response.data.url + } catch (error) { + throw new Error('上传失败,请重试') + } + } + + // 上传到OSS + const uploadToOSS = async (file: File, credentials: PolicyToken) => { + // 使用后端返回的 host + const host = credentials.host + + // 生成唯一的文件名(使用时间戳 + 随机数) + const timestamp = Date.now() + const random = Math.random().toString(36).substring(2, 8) + const fileExtension = file.name.split('.').pop() + const fileName = `${timestamp}_${random}.${fileExtension}` + const key = `${credentials.dir}${fileName}` + + const formData = new FormData() + formData.append('success_action_status', '200') + formData.append('policy', credentials.policy) + formData.append('x-oss-signature', credentials.signature) + formData.append('x-oss-signature-version', credentials.x_oss_signature_version) + formData.append('x-oss-credential', credentials.x_oss_credential) + formData.append('x-oss-date', credentials.x_oss_date) + formData.append('key', key) + formData.append('x-oss-security-token', credentials.security_token) + formData.append('file', file) + + console.log('上传参数:', { + host: host, + key: key, + fileSize: file.size, + fileName: fileName + }) + + const response = await fetch(host, { + method: 'POST', + body: formData, + }) + + console.log('上传响应:', { + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()) + }) + + if (!response.ok) { + const errorText = await response.text() + console.error('上传失败详情:', errorText) + throw new Error(`上传失败: ${response.status} - ${errorText}`) + } + + // 返回完整的图片URL + return `${host}/${key}` + } + + const handleUpload: UploadProps['customRequest'] = async ({ file, onSuccess, onError }) => { + const fileObj = file as File + + // 检查文件大小 + if (fileObj.size > maxSize * 1024 * 1024) { + message.error(`文件大小不能超过 ${maxSize}MB`) + onError?.(new Error(`文件大小不能超过 ${maxSize}MB`)) + return + } + + // 检查文件类型 + if (!fileObj.type.startsWith('image/')) { + message.error('只能上传图片文件') + onError?.(new Error('只能上传图片文件')) + return + } + + setUploading(true) + + try { + // 1. 获取上传凭证 + const credentials = await getUploadCredentials('camp') + + // 2. 尝试直接上传到OSS + try { + const imageUrl = await uploadToOSS(fileObj, credentials) + + // 3. 更新表单值 + onChange?.(imageUrl) + onSuccess?.(imageUrl) + message.success('图片上传成功') + } catch (corsError: any) { + // 如果CORS错误,尝试后端代理上传 + if (corsError.message.includes('CORS') || corsError.message.includes('Access-Control-Allow-Origin')) { + console.log('检测到CORS错误,尝试后端代理上传...') + const imageUrl = await uploadViaBackend(fileObj) + + onChange?.(imageUrl) + onSuccess?.(imageUrl) + message.success('图片上传成功(通过后端代理)') + } else { + throw corsError + } + } + } catch (error: any) { + console.error('上传失败:', error) + message.error(error.message || '上传失败,请重试') + onError?.(error) + } finally { + setUploading(false) + } + } + + const handleRemove = () => { + onChange?.('') + } + + return ( +
+ {/* 图片预览 */} + {value && ( +
+ 预览 +
+ )} + + {/* 上传按钮:始终显示,允许重新上传 */} + + + +
+ ) +} + +export default ImageUpload diff --git a/src/components/KnowledgeTree/index.tsx b/src/components/KnowledgeTree/index.tsx new file mode 100644 index 0000000..abe4a60 --- /dev/null +++ b/src/components/KnowledgeTree/index.tsx @@ -0,0 +1,315 @@ +import { useState, useEffect } from 'react' +import { Tree, Button, Input, Modal, Form, message, Space, Popconfirm } from 'antd' +import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons' +import type { DataNode } from 'antd/es/tree' +import type { KnowledgeTreeNode, CreateKnowledgeTreeNodeRequest, UpdateKnowledgeTreeNodeRequest } from '@/types/question' +import { + getKnowledgeTree, + createKnowledgeTreeNode, + updateKnowledgeTreeNode, + deleteKnowledgeTreeNode, +} from '@/api/question' + +interface KnowledgeTreeProps { + type: 'objective' | 'subjective' // 知识树类型 + onSelect?: (node: KnowledgeTreeNode | null) => void +} + +const STORAGE_KEY_PREFIX = 'knowledge_tree_expanded_' + +const KnowledgeTree: React.FC = ({ type, onSelect }) => { + const [treeData, setTreeData] = useState([]) + const [loading, setLoading] = useState(false) + const [selectedKeys, setSelectedKeys] = useState([]) + const [expandedKeys, setExpandedKeys] = useState([]) + const [modalVisible, setModalVisible] = useState(false) + const [editingNode, setEditingNode] = useState(null) + const [parentNodeId, setParentNodeId] = useState('') + const [form] = Form.useForm() + + // 将知识树节点转换为 Ant Design Tree 的 DataNode 格式 + const convertToTreeData = (nodes: KnowledgeTreeNode[]): DataNode[] => { + return nodes.map((node) => ({ + title: node.title, + key: node.id, + children: node.children ? convertToTreeData(node.children) : undefined, + })) + } + + // 从 localStorage 读取上次展开的 key 列表(仅保留当前树中仍存在的 key) + const loadExpandedKeys = (validKeys: React.Key[]): React.Key[] => { + try { + const raw = localStorage.getItem(STORAGE_KEY_PREFIX + type) + if (!raw) return [] + const saved = JSON.parse(raw) as React.Key[] + if (!Array.isArray(saved)) return [] + return saved.filter((k) => validKeys.includes(k)) + } catch { + return [] + } + } + + const saveExpandedKeys = (keys: React.Key[]) => { + try { + localStorage.setItem(STORAGE_KEY_PREFIX + type, JSON.stringify(keys)) + } catch { + // ignore + } + } + + // 获取知识树数据 + const fetchTree = async () => { + setLoading(true) + try { + const res = await getKnowledgeTree(type) + if (res.data.code === 200) { + const tree = res.data.data.tree || [] + const convertedTree = convertToTreeData(tree) + setTreeData(convertedTree) + const validKeys = getAllKeys(convertedTree) + const restored = loadExpandedKeys(validKeys) + setExpandedKeys(restored) + } else { + message.error(res.data.message || '获取知识树失败') + } + } catch (error: any) { + message.error('获取知识树失败') + } finally { + setLoading(false) + } + } + + // 获取所有节点的key(用于展开) + const getAllKeys = (nodes: DataNode[]): React.Key[] => { + let keys: React.Key[] = [] + nodes.forEach((node) => { + keys.push(node.key) + if (node.children) { + keys = keys.concat(getAllKeys(node.children)) + } + }) + return keys + } + + useEffect(() => { + fetchTree() + }, []) + + // 处理节点选择 + const handleSelect = (selectedKeys: React.Key[]) => { + setSelectedKeys(selectedKeys) + if (onSelect && selectedKeys.length > 0) { + // 这里需要从原始数据中找到对应的节点 + // 为了简化,我们只传递ID,让父组件自己处理 + onSelect({ id: selectedKeys[0] as string } as KnowledgeTreeNode) + } else if (onSelect) { + onSelect(null) + } + } + + // 打开创建节点弹窗 + const handleAddNode = (parentId: string = '') => { + setParentNodeId(parentId) + setEditingNode(null) + form.resetFields() + form.setFieldsValue({ title: '' }) + setModalVisible(true) + } + + // 打开编辑节点弹窗 + const handleEditNode = (nodeId: string) => { + // 从树数据中查找节点信息 + const findNode = (nodes: KnowledgeTreeNode[], id: string): KnowledgeTreeNode | null => { + for (const node of nodes) { + if (node.id === id) { + return node + } + if (node.children) { + const found = findNode(node.children, id) + if (found) return found + } + } + return null + } + + // 需要重新获取完整树数据来找到节点 + getKnowledgeTree(type).then((res) => { + if (res.data.code === 200) { + const tree = res.data.data.tree || [] + const node = findNode(tree, nodeId) + if (node) { + setEditingNode(node) + setParentNodeId(node.parentId) + form.setFieldsValue({ title: node.title }) + setModalVisible(true) + } + } + }) + } + + // 删除节点 + const handleDeleteNode = async (nodeId: string) => { + try { + const res = await deleteKnowledgeTreeNode(nodeId) + if (res.data.code === 200) { + message.success('删除成功') + fetchTree() + if (selectedKeys.includes(nodeId)) { + setSelectedKeys([]) + if (onSelect) { + onSelect(null) + } + } + } else { + message.error(res.data.message || '删除失败') + } + } catch (error: any) { + message.error(error.response?.data?.message || '删除失败') + } + } + + // 提交表单(创建或更新) + const handleSubmit = async () => { + try { + const values = await form.validateFields() + + if (editingNode) { + // 更新节点 + const updateData: UpdateKnowledgeTreeNodeRequest = { + id: editingNode.id, + title: values.title, + parentId: parentNodeId, + } + const res = await updateKnowledgeTreeNode(updateData) + if (res.data.code === 200) { + message.success('更新成功') + setModalVisible(false) + fetchTree() + } else { + message.error(res.data.message || '更新失败') + } + } else { + // 创建节点 + const createData: CreateKnowledgeTreeNodeRequest = { + type: type, + title: values.title, + parentId: parentNodeId || '', // 确保根节点时 parentId 为空字符串 + } + const res = await createKnowledgeTreeNode(createData) + if (res.data.code === 200) { + message.success('创建成功') + setModalVisible(false) + fetchTree() + } else { + message.error(res.data.message || '创建失败') + } + } + } catch (error) { + // 表单验证失败 + } + } + + // 自定义树节点标题(添加操作按钮) + const renderTitle = (node: DataNode) => { + // 确保 title 是字符串类型 + const title = typeof node.title === 'string' ? node.title : String(node.title || '') + return ( +
+ {title} + e.stopPropagation()}> +
+ ) + } + + return ( +
+
+

知识树

+ + + + +
+ + { + setExpandedKeys(keys) + saveExpandedKeys(keys) + }} + titleRender={renderTitle} + showLine + blockNode + /> + + { + setModalVisible(false) + form.resetFields() + }} + okText="确定" + cancelText="取消" + > +
+ + + +
+
+
+ ) +} + +export default KnowledgeTree diff --git a/src/components/Permission/PermissionWrapper.tsx b/src/components/Permission/PermissionWrapper.tsx new file mode 100644 index 0000000..815d941 --- /dev/null +++ b/src/components/Permission/PermissionWrapper.tsx @@ -0,0 +1,44 @@ +import { ReactNode } from 'react' +import { useHasPermission } from '@/utils/permission' + +interface PermissionWrapperProps { + /** + * 需要的权限代码 + */ + permission: string + /** + * 子组件 + */ + children: ReactNode + /** + * 无权限时显示的内容(可选) + */ + fallback?: ReactNode + /** + * 是否反向显示(有权限时隐藏,无权限时显示) + */ + reverse?: boolean +} + +/** + * 权限包装组件 + * 根据用户权限决定是否显示子组件 + */ +export const PermissionWrapper = ({ + permission, + children, + fallback = null, + reverse = false, +}: PermissionWrapperProps) => { + const hasPermission = useHasPermission(permission) + + const shouldShow = reverse ? !hasPermission : hasPermission + + return shouldShow ? <>{children} : <>{fallback} +} + +/** + * 权限检查组件(简化版) + */ +export const RequirePermission = PermissionWrapper + diff --git a/src/components/Permission/index.ts b/src/components/Permission/index.ts new file mode 100644 index 0000000..052d340 --- /dev/null +++ b/src/components/Permission/index.ts @@ -0,0 +1,2 @@ +export { PermissionWrapper, RequirePermission } from './PermissionWrapper' + diff --git a/src/components/RichTextEditor/RichTextEditor.css b/src/components/RichTextEditor/RichTextEditor.css new file mode 100644 index 0000000..bb885ed --- /dev/null +++ b/src/components/RichTextEditor/RichTextEditor.css @@ -0,0 +1,173 @@ +/* Tiptap 富文本编辑器样式 */ +.rich-text-editor-tiptap { + border: 1px solid #d9d9d9; + border-radius: 6px; + background: #fff; + transition: all 0.3s; +} + +.rich-text-editor-tiptap:hover { + border-color: #4096ff; +} + +.rich-text-editor-tiptap:focus-within { + border-color: #4096ff; + box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1); +} + +/* 工具栏 */ +.rich-text-editor-toolbar { + padding: 8px 12px; + border-bottom: 1px solid #f0f0f0; + background: #fafafa; + border-radius: 6px 6px 0 0; + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +/* 编辑器内容区 */ +.rich-text-editor-wrapper { + padding: 12px; + min-height: 200px; +} + +/* Tiptap 编辑器内容样式 */ +.rich-text-editor-content { + outline: none; + min-height: 150px; +} + +.rich-text-editor-content p { + margin: 0.5em 0; +} + +.rich-text-editor-content p.is-editor-empty:first-child::before { + content: attr(data-placeholder); + float: left; + color: #bfbfbf; + pointer-events: none; + height: 0; +} + +.rich-text-editor-content h1 { + font-size: 2em; + font-weight: bold; + margin: 0.5em 0; +} + +.rich-text-editor-content h2 { + font-size: 1.5em; + font-weight: bold; + margin: 0.5em 0; +} + +.rich-text-editor-content h3 { + font-size: 1.17em; + font-weight: bold; + margin: 0.5em 0; +} + +.rich-text-editor-content ul, +.rich-text-editor-content ol { + padding-left: 1.5em; + margin: 0.5em 0; +} + +.rich-text-editor-content li { + margin: 0.25em 0; +} + +.rich-text-editor-content a { + color: #1890ff; + text-decoration: underline; + cursor: pointer; +} + +.rich-text-editor-content a:hover { + color: #40a9ff; +} + +.rich-text-editor-content img { + max-width: 100%; + height: auto; + display: block; + margin: 0.5em 0; + border-radius: 4px; +} + +.rich-text-editor-content blockquote { + border-left: 3px solid #d9d9d9; + padding-left: 1em; + margin: 0.5em 0; + color: #666; +} + +.rich-text-editor-content code { + background: #f5f5f5; + padding: 2px 4px; + border-radius: 3px; + font-family: 'Courier New', monospace; + font-size: 0.9em; +} + +.rich-text-editor-content pre { + background: #f5f5f5; + padding: 1em; + border-radius: 4px; + overflow-x: auto; + margin: 0.5em 0; +} + +.rich-text-editor-content pre code { + background: none; + padding: 0; +} + +/* 暗色模式支持 */ +[data-theme='dark'] .rich-text-editor-tiptap { + background: #141414; + border-color: #434343; +} + +[data-theme='dark'] .rich-text-editor-tiptap:hover { + border-color: #4096ff; +} + +[data-theme='dark'] .rich-text-editor-toolbar { + background: #1f1f1f; + border-bottom-color: #434343; +} + +[data-theme='dark'] .rich-text-editor-content { + color: rgba(255, 255, 255, 0.85); +} + +[data-theme='dark'] .rich-text-editor-content p.is-editor-empty:first-child::before { + color: #8c8c8c; +} + +[data-theme='dark'] .rich-text-editor-content blockquote { + border-left-color: #434343; + color: rgba(255, 255, 255, 0.65); +} + +[data-theme='dark'] .rich-text-editor-content code, +[data-theme='dark'] .rich-text-editor-content pre { + background: #262626; + color: rgba(255, 255, 255, 0.85); +} + +/* Tiptap 特定样式 */ +.ProseMirror { + outline: none; +} + +.ProseMirror-focused { + outline: none; +} + +/* 选中文本样式 */ +.ProseMirror-selectednode { + outline: 2px solid #1890ff; +} diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx new file mode 100644 index 0000000..dbe20f5 --- /dev/null +++ b/src/components/RichTextEditor/RichTextEditor.tsx @@ -0,0 +1,248 @@ +import { useEditor, EditorContent } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import Image from '@tiptap/extension-image' +import Link from '@tiptap/extension-link' +import Underline from '@tiptap/extension-underline' +import Strike from '@tiptap/extension-strike' +import { TextStyle } from '@tiptap/extension-text-style' +import Color from '@tiptap/extension-color' +import TextAlign from '@tiptap/extension-text-align' +import { memo, useEffect } from 'react' +import { message, Button, Space } from 'antd' +import { + BoldOutlined, + ItalicOutlined, + UnderlineOutlined, + StrikethroughOutlined, + OrderedListOutlined, + UnorderedListOutlined, + LinkOutlined, + PictureOutlined, + UndoOutlined, + RedoOutlined, +} from '@ant-design/icons' +import { getUploadSignature } from '@/api/oss' +import './RichTextEditor.css' + +interface RichTextEditorProps { + value?: string + onChange?: (value: string) => void + placeholder?: string + rows?: number +} + +// 富文本编辑器(基于 Tiptap) +const RichTextEditor: React.FC = memo(({ + value = '', + onChange, + placeholder = '请输入内容', + rows = 4, +}) => { + const editor = useEditor({ + extensions: [ + StarterKit.configure({ + heading: { + levels: [1, 2, 3], + }, + // 禁用 StarterKit 中已包含的扩展,使用单独的扩展以支持更多配置 + strike: false, + link: false, + }), + Underline, + Strike, + Image.configure({ + inline: true, + allowBase64: false, + }), + Link.configure({ + openOnClick: false, + HTMLAttributes: { + target: '_blank', + rel: 'noopener noreferrer', + }, + }), + TextStyle, + Color, + TextAlign.configure({ + types: ['heading', 'paragraph'], + }), + ], + content: value, + onUpdate: ({ editor }) => { + const html = editor.getHTML() + onChange?.(html) + }, + editorProps: { + attributes: { + class: 'rich-text-editor-content', + 'data-placeholder': placeholder, + }, + }, + }) + + // 当外部 value 变化时更新编辑器内容 + useEffect(() => { + if (editor && value !== editor.getHTML()) { + editor.commands.setContent(value, { emitUpdate: false }) + } + }, [value, editor]) + + // 图片上传处理 + const handleImageUpload = async () => { + const input = document.createElement('input') + input.setAttribute('type', 'file') + input.setAttribute('accept', 'image/*') + input.click() + + input.onchange = async () => { + const file = input.files?.[0] + if (!file || !editor) return + + try { + message.loading({ content: '正在上传图片...', key: 'upload', duration: 0 }) + + // 获取上传凭证 + const credentials = await getUploadSignature('question') + + // 生成唯一文件名 + const fileExtension = file.name.split('.').pop() || 'jpg' + const fileName = `${Date.now()}_${Math.random().toString(36).substring(7)}.${fileExtension}` + const key = `${credentials.dir}${fileName}` + + // 构建 FormData + const formData = new FormData() + formData.append('success_action_status', '200') + formData.append('policy', credentials.policy) + formData.append('x-oss-signature', credentials.signature) + formData.append('x-oss-signature-version', credentials.x_oss_signature_version) + formData.append('x-oss-credential', credentials.x_oss_credential) + formData.append('x-oss-date', credentials.x_oss_date) + formData.append('key', key) + formData.append('x-oss-security-token', credentials.security_token) + formData.append('file', file) + + // 上传到 OSS + const uploadRes = await fetch(credentials.host, { + method: 'POST', + body: formData, + }) + + if (!uploadRes.ok) { + throw new Error('上传失败') + } + + // 构建图片 URL + const imageUrl = `${credentials.host}/${key}` + + // 插入图片到编辑器 + editor.chain().focus().setImage({ src: imageUrl }).run() + + message.success({ content: '图片上传成功', key: 'upload' }) + } catch (error: any) { + console.error('图片上传失败:', error) + message.error({ content: error.message || '图片上传失败', key: 'upload' }) + } + } + } + + // 添加链接 + const handleAddLink = () => { + if (!editor) return + + const url = window.prompt('请输入链接地址:') + if (url) { + editor.chain().focus().setLink({ href: url }).run() + } + } + + if (!editor) { + return null + } + + return ( +
+ {/* 工具栏 */} +
+ +
+ + {/* 编辑器内容区 */} +
+ +
+
+ ) +}) + +RichTextEditor.displayName = 'RichTextEditor' + +export default RichTextEditor diff --git a/src/components/SubjectiveQuestionForm/index.tsx b/src/components/SubjectiveQuestionForm/index.tsx new file mode 100644 index 0000000..db73934 --- /dev/null +++ b/src/components/SubjectiveQuestionForm/index.tsx @@ -0,0 +1,187 @@ +import { useEffect, useState } from 'react' +import { Modal, Form, Input, TreeSelect, message } from 'antd' +import type { Question, KnowledgeTreeNode } from '@/types/question' +import { QuestionType } from '@/types/question' +import { createQuestion, updateQuestion, getKnowledgeTree } from '@/api/question' +import RichTextEditor from '@/components/RichTextEditor/RichTextEditor' + +interface SubjectiveQuestionFormProps { + visible: boolean + editingRecord?: Question | null + onCancel: () => void + onSuccess?: (questionId: string) => void // 创建/编辑成功后回调,传递题目ID +} + +const SubjectiveQuestionForm = ({ + visible, + editingRecord, + onCancel, + onSuccess, +}: SubjectiveQuestionFormProps) => { + const [form] = Form.useForm() + const [knowledgeTreeData, setKnowledgeTreeData] = useState([]) + const [loading, setLoading] = useState(false) + + // 获取知识树 + useEffect(() => { + if (visible) { + const fetchKnowledgeTree = async () => { + try { + const res = await getKnowledgeTree('subjective') + if (res.data.code === 200) { + const tree = res.data.data?.tree || [] + setKnowledgeTreeData(tree) + } + } catch (error: any) { + console.error('获取知识树错误:', error) + } + } + fetchKnowledgeTree() + } + }, [visible]) + + // 设置表单初始值 + useEffect(() => { + if (visible) { + if (editingRecord) { + form.setFieldsValue({ + name: editingRecord.name || '', + source: editingRecord.source || '', + content: editingRecord.content || '', + explanation: editingRecord.explanation || '', + knowledgeTreeIds: editingRecord.knowledgeTreeIds || [], + }) + } else { + form.resetFields() + form.setFieldsValue({ + knowledgeTreeIds: [], + }) + } + } + }, [visible, editingRecord, form]) + + // 将知识树转换为 TreeSelect 格式:有下级的节点展开但不可选,仅最低层级(叶子)可选 + const convertKnowledgeTreeToTreeData = (nodes: KnowledgeTreeNode[]): any[] => { + return nodes.map((node) => { + const children = node.children ?? [] + const hasChildren = children.length > 0 + return { + title: node.title, + value: node.id, + key: node.id, + disabled: hasChildren, // 有下级则不可选,只有叶子节点可选 + children: hasChildren ? convertKnowledgeTreeToTreeData(children) : undefined, + } + }) + } + + // 提交表单 + const handleSubmit = async () => { + try { + const values = await form.validateFields() + + const payload: Omit = { + id: editingRecord?.id || '', + type: QuestionType.SUBJECTIVE, + name: values.name || '', + source: values.source || '', + materialId: undefined, + content: values.content, + options: [], + answer: '', + explanation: values.explanation || '', + knowledgeTreeIds: values.knowledgeTreeIds || [], + createdAt: 0, + updatedAt: 0, + } as any + + setLoading(true) + + if (editingRecord) { + // 编辑模式 + await updateQuestion({ + ...editingRecord, + ...payload, + } as Question) + message.success('编辑题目成功') + if (onSuccess && editingRecord.id) { + onSuccess(editingRecord.id) + } + } else { + // 新增模式 + const res = await createQuestion(payload) + // 提取题目ID + const questionId = + res.data.data?.id || + res.data.data?.question?.id || + '' + + if (questionId) { + message.success('新增题目成功') + if (onSuccess) { + onSuccess(questionId) + } + } else { + message.error('创建题目失败:无法获取题目ID') + } + } + + form.resetFields() + onCancel() + } catch (error: any) { + const errorMsg = error?.message || '操作失败' + message.error(errorMsg) + } finally { + setLoading(false) + } + } + + return ( + +
+ + + + + + + + + + + + + + + + + + + +
+
+ ) +} + +export default SubjectiveQuestionForm diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..7cc4679 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,60 @@ +// 本地存储键名 +export const STORAGE_KEYS = { + TOKEN: 'admin_token', + USER_INFO: 'admin_user_info', +} as const + +// API 响应码 +export enum ApiCode { + SUCCESS = 200, + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + FORBIDDEN = 403, + NOT_FOUND = 404, + SERVER_ERROR = 500, +} + +// 默认分页配置 +export const DEFAULT_PAGE_SIZE = 10 +export const PAGE_SIZE_OPTIONS = ['10', '20', '50', '100'] + +// 路由路径 +export enum RoutePath { + LOGIN = '/login', + HOME = '/', + DASHBOARD = '/dashboard', + USER_LIST = '/user/list', + USER_DETAIL = '/user/detail/:id', + ROLE_LIST = '/admin/role/list', + PERMISSION_LIST = '/admin/permission/list', + // 打卡营管理 + CAMP_CATEGORY_LIST = '/camp/category', + CAMP_LIST = '/camp/list', + CAMP_SECTION_LIST = '/camp/section', + CAMP_TASK_LIST = '/camp/task', + CAMP_PROGRESS_LIST = '/camp/progress', + CAMP_PENDING_TASK_LIST = '/camp/task-review', + // 客观题管理 + OBJECTIVE_QUESTION_LIST = '/objective/question/list', // 题目管理 + OBJECTIVE_MATERIAL_LIST = '/objective/material/list', // 材料管理 + OBJECTIVE_PAPER_LIST = '/objective/paper/list', // 组卷管理 + OBJECTIVE_PAPER_EDIT = '/objective/paper/edit', // 组卷编辑(新增/编辑) + OBJECTIVE_KNOWLEDGE_TREE = '/objective/knowledge-tree', // 知识树 + // 主观题管理 + SUBJECTIVE_QUESTION_LIST = '/subjective/question/list', + SUBJECTIVE_MATERIAL_LIST = '/subjective/material/list', // 材料管理 + SUBJECTIVE_PAPER_LIST = '/subjective/paper/list', // 组卷管理 + SUBJECTIVE_KNOWLEDGE_TREE = '/subjective/knowledge-tree', // 知识树 + // 文档管理 + DOCUMENT_LIST = '/document', + // 兼容旧路由 + QUESTION_LIST = '/question/list', + PAPER_LIST = '/question/paper/list', + PAPER_QUESTIONS = '/question/paper/:paperId/questions', + ANSWER_RECORD_LIST = '/question/answer-record', + NOT_FOUND = '*', +} + +// 从 types 中重新导出常用枚举,方便使用 +export { UserRole, MenuType } from '@/types' + diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..47651f7 --- /dev/null +++ b/src/index.css @@ -0,0 +1,248 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #root { + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + background-color: #f0f2f5; +} + +[data-theme='dark'] body { + background-color: #000000; + color: #ffffff; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +/* 响应式全局样式 */ +@media (max-width: 768px) { + /* 移动端字体大小调整 */ + body { + font-size: 14px; + } + + /* 页面容器响应式 - 处理所有直接子 div 的 padding */ + .layout-content > div { + padding: 16px !important; + } + + /* 页面标题区域响应式 */ + .layout-content > div > div:first-child { + margin-bottom: 16px !important; + } + + .layout-content > div > div:first-child > h2 { + font-size: 18px !important; + margin-bottom: 8px !important; + } + + .layout-content > div > div:first-child > div { + font-size: 12px !important; + margin-top: 4px !important; + } + + /* 页面标题响应式 */ + h2 { + font-size: 18px !important; + } + + /* 表格响应式 */ + .ant-table-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .ant-table { + min-width: 600px; + } + + .ant-table-thead > tr > th, + .ant-table-tbody > tr > td { + padding: 8px 4px !important; + font-size: 12px; + } + + /* 表单响应式 */ + .ant-form-item { + margin-bottom: 16px; + } + + .ant-form-item-label { + padding-bottom: 4px !important; + } + + /* Modal 响应式 */ + .ant-modal { + max-width: calc(100vw - 32px) !important; + margin: 16px auto !important; + top: 0; + padding-bottom: 0; + } + + .ant-modal-content { + max-height: calc(100vh - 32px); + overflow-y: auto; + } + + .ant-modal-header { + padding: 16px !important; + } + + .ant-modal-body { + padding: 16px !important; + } + + .ant-modal-footer { + padding: 12px 16px !important; + } + + /* Card 响应式 */ + .ant-card { + margin-bottom: 16px; + } + + .ant-card-head { + padding: 12px 16px !important; + min-height: 48px; + } + + .ant-card-body { + padding: 16px !important; + } + + /* Space 组件响应式 */ + .ant-space { + flex-wrap: wrap; + width: 100%; + } + + .ant-space-item { + width: 100%; + } + + /* 搜索和筛选区域响应式 */ + .ant-space-horizontal > .ant-space-item { + width: 100%; + margin-bottom: 8px; + } + + .ant-space-horizontal > .ant-space-item:last-child { + margin-bottom: 0; + } + + /* Input.Search 在移动端 */ + .ant-input-search.ant-input-search-enter-button > .ant-input-group > .ant-input { + width: 100%; + } + + /* 按钮组响应式 */ + .ant-btn-group { + display: flex; + flex-wrap: wrap; + } + + /* 搜索框响应式 */ + .ant-input-search { + width: 100% !important; + margin-bottom: 8px; + } + + /* Select 下拉框响应式 */ + .ant-select { + width: 100% !important; + margin-bottom: 8px; + } + + /* 按钮响应式 */ + .ant-btn { + font-size: 14px; + height: 36px; + padding: 4px 12px; + } + + /* 输入框响应式 */ + .ant-input, + .ant-input-number { + font-size: 14px; + } + + /* 标签响应式 */ + .ant-tag { + font-size: 12px; + padding: 2px 8px; + margin: 2px; + } + + /* 分页响应式 */ + .ant-pagination { + margin: 16px 0 !important; + } + + .ant-pagination-item, + .ant-pagination-prev, + .ant-pagination-next { + min-width: 32px; + height: 32px; + line-height: 32px; + } + + /* Transfer 组件响应式 */ + .ant-transfer { + width: 100% !important; + } + + .ant-transfer-list { + width: calc(50% - 8px) !important; + } + + /* ImageUpload 组件响应式 */ + .ant-upload { + width: 100%; + } + + /* Drawer 响应式 */ + .ant-drawer-content-wrapper { + width: 280px !important; + } + + /* Statistic 统计卡片响应式 */ + .ant-statistic-title { + font-size: 12px !important; + } + + .ant-statistic-content { + font-size: 20px !important; + } + + /* Row 和 Col 响应式 */ + .ant-row { + margin-left: -8px !important; + margin-right: -8px !important; + } + + .ant-col { + padding-left: 8px !important; + padding-right: 8px !important; + } +} + +/* 平板样式 */ +@media (min-width: 769px) and (max-width: 1024px) { + body { + font-size: 14px; + } +} + diff --git a/src/layouts/BasicLayout.css b/src/layouts/BasicLayout.css new file mode 100644 index 0000000..97c2f4d --- /dev/null +++ b/src/layouts/BasicLayout.css @@ -0,0 +1,122 @@ +.basic-layout { + min-height: 100vh; +} + +.logo { + height: 64px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); +} + +.logo h1 { + color: white; + margin: 0; + font-size: 20px; + font-weight: bold; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + height: 100%; + padding: 0 24px; +} + +.header-left { + display: flex; + align-items: center; +} + +.trigger { + padding: 0 24px; + font-size: 18px; + cursor: pointer; + transition: color 0.3s; +} + +.trigger:hover { + color: #1890ff; +} + +.header-right { + display: flex; + align-items: center; + gap: 16px; +} + +.theme-toggle { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + padding: 6px 10px; + border-radius: 16px; + transition: background-color 0.2s, color 0.2s; + font-size: 13px; +} + +.theme-toggle-text { + user-select: none; +} + +.user-info { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 8px 12px; + border-radius: 4px; + transition: background-color 0.3s; +} + +.user-info:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +.username { + font-size: 14px; +} + +/* 移动端样式 */ +@media (max-width: 768px) { + .header-content { + padding: 0 16px; + } + + .trigger { + padding: 0 16px; + font-size: 20px; + } + + .user-info { + padding: 4px 8px; + } + + .theme-toggle { + padding: 4px 8px; + } + + .layout-content { + overflow-x: auto; + } + + /* 隐藏移动端侧边栏 */ + .mobile-sider { + display: none !important; + } +} + +/* 平板样式 */ +@media (min-width: 769px) and (max-width: 1024px) { + .header-content { + padding: 0 20px; + } + + .trigger { + padding: 0 20px; + } +} + diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx new file mode 100644 index 0000000..b736136 --- /dev/null +++ b/src/layouts/BasicLayout.tsx @@ -0,0 +1,485 @@ +import { useState, useEffect, useContext, useMemo } from 'react' +import { Outlet, useNavigate, useLocation } from 'react-router-dom' +import { Layout, Menu, Dropdown, Avatar, theme, Drawer, Tooltip, Popover, Space, Badge } from 'antd' +import { + MenuFoldOutlined, + MenuUnfoldOutlined, + DashboardOutlined, + UserOutlined, + LogoutOutlined, + TrophyOutlined, + QuestionCircleOutlined, + BulbOutlined, + BgColorsOutlined, + AuditOutlined, + FolderOutlined, +} from '@ant-design/icons' +import type { MenuProps } from 'antd' +import { useUserStore } from '@/store/useUserStore' +import { RoutePath } from '@/constants' +import { ThemeModeContext, ThemeMode, THEME_COLORS } from '@/App' +import { logout as logoutApi } from '@/api/auth' +import { getPendingReviewCount } from '@/api/camp' +import { getUserPermissions } from '@/utils/permission' +import './BasicLayout.css' + +const { Header, Sider, Content } = Layout + +const BasicLayout = () => { + const [collapsed, setCollapsed] = useState(false) + const [mobileMenuVisible, setMobileMenuVisible] = useState(false) + const [isMobile, setIsMobile] = useState(false) + const [pendingReviewCount, setPendingReviewCount] = useState(0) + const navigate = useNavigate() + const location = useLocation() + const { userInfo, logout } = useUserStore() + const userPermissions = getUserPermissions() + const canReadProgress = userInfo?.is_super_admin || userPermissions.includes('camp:progress:read') + const themeModeContext = useContext(ThemeModeContext) + const currentMode = themeModeContext?.mode ?? ThemeMode.LIGHT + const isDarkMode = currentMode === ThemeMode.DARK + const currentPrimaryColor = themeModeContext?.primaryColor ?? THEME_COLORS[0].value + const { + token: { colorBgContainer, borderRadiusLG, colorText, colorBorder }, + } = theme.useToken() + + // 菜单项配置(带权限要求) + // 使用 any 类型来支持自定义的 requiredPermission 属性 + const menuItemsWithPermissions: any[] = [ + { + key: RoutePath.DASHBOARD, + icon: , + label: '仪表盘', + requiredPermission: 'statistics:dashboard:read', + }, + { + key: '/admin', + icon: , + label: '管理员管理', + requiredPermission: 'admin:user:read', // 至少需要查看管理员的权限 + children: [ + { + key: RoutePath.USER_LIST, + label: '管理员列表', + requiredPermission: 'admin:user:read', + }, + { + key: RoutePath.ROLE_LIST, + label: '角色管理', + requiredPermission: 'admin:role:read', + }, + { + key: RoutePath.PERMISSION_LIST, + label: '权限管理', + requiredPermission: 'admin:permission:read', + }, + ], + }, + { + key: '/camp', + icon: , + label: '打卡营管理', + requiredPermission: 'camp:camp:read', // 至少需要查看打卡营的权限 + children: [ + { + key: RoutePath.CAMP_CATEGORY_LIST, + label: '分类管理', + requiredPermission: 'camp:category:read', + }, + { + key: RoutePath.CAMP_LIST, + label: '打卡营列表', + requiredPermission: 'camp:camp:read', + }, + { + key: RoutePath.CAMP_SECTION_LIST, + label: '小节管理', + requiredPermission: 'camp:section:read', + }, + { + key: RoutePath.CAMP_TASK_LIST, + label: '任务管理', + requiredPermission: 'camp:task:read', + }, + { + key: RoutePath.CAMP_PROGRESS_LIST, + label: '打卡营进度管理', + requiredPermission: 'camp:progress:read', + }, + { + key: RoutePath.CAMP_PENDING_TASK_LIST, + label: '任务审批列表', + requiredPermission: 'camp:progress:read', + }, + ], + }, + { + key: '/objective', + icon: , + label: '客观题管理', + requiredPermission: 'question:read', // 至少需要查看题目的权限 + children: [ + { + key: RoutePath.OBJECTIVE_QUESTION_LIST, + label: '题目管理', + requiredPermission: 'question:read', + }, + { + key: RoutePath.OBJECTIVE_MATERIAL_LIST, + label: '材料管理', + requiredPermission: 'question:read', // 暂时使用相同权限 + }, + { + key: RoutePath.OBJECTIVE_PAPER_LIST, + label: '组卷管理', + requiredPermission: 'paper:read', + }, + { + key: RoutePath.OBJECTIVE_KNOWLEDGE_TREE, + label: '知识树', + requiredPermission: 'knowledge_tree:read', + }, + ], + }, + { + key: '/subjective', + icon: , + label: '主观题管理', + requiredPermission: 'question:read', + children: [ + // { + // key: RoutePath.SUBJECTIVE_QUESTION_LIST, + // label: '题目管理', + // requiredPermission: 'question:read', + // }, + { + key: RoutePath.SUBJECTIVE_MATERIAL_LIST, + label: '资料管理', + requiredPermission: 'material:read', + }, + { + key: RoutePath.SUBJECTIVE_PAPER_LIST, + label: '组卷管理', + requiredPermission: 'paper:read', + }, + { + key: RoutePath.SUBJECTIVE_KNOWLEDGE_TREE, + label: '题型分类', + requiredPermission: 'knowledge_tree:read', + }, + ], + }, + { + key: RoutePath.DOCUMENT_LIST, + icon: , + label: '文档管理', + requiredPermission: 'document:manage', + }, + ] + + // 根据权限过滤菜单项 + const menuItems = useMemo(() => { + const userPermissions = getUserPermissions() + const isSuper = userInfo?.is_super_admin + + const filterMenuItems = (items: any[]): MenuProps['items'] => { + if (!items) return [] + + return items + .map(item => { + if (!item) return null + + // 检查是否有权限 + const requiredPermission = item.requiredPermission + if (requiredPermission) { + // 超级管理员或拥有权限的用户可以访问 + if (!isSuper && !userPermissions.includes(requiredPermission)) { + return null + } + } + + // 创建新的菜单项,移除 requiredPermission 属性,避免传递到 DOM + const { requiredPermission: _, ...menuItem } = item + + // 如果有子菜单,递归过滤 + if (menuItem.children) { + const filteredChildren = filterMenuItems(menuItem.children) + // 如果子菜单全部被过滤掉,则隐藏父菜单 + if (!filteredChildren || filteredChildren.length === 0) { + return null + } + return { + ...menuItem, + children: filteredChildren, + } + } + + return menuItem + }) + .filter(item => item !== null) as MenuProps['items'] + } + + return filterMenuItems(menuItemsWithPermissions) + }, [userInfo?.permissions, userInfo?.is_super_admin]) + + // 用户下拉菜单 + const userMenuItems: MenuProps['items'] = [ + { + key: 'logout', + icon: , + label: '退出登录', + onClick: async () => { + try { + // 调用后端登出 API(可选,即使失败也清除本地数据) + await logoutApi().catch(() => { + // 忽略登出 API 错误,继续清除本地数据 + }) + } catch { + // 忽略错误 + } finally { + logout() + navigate(RoutePath.LOGIN) + } + }, + }, + ] + + // 菜单点击事件 + const onMenuClick: MenuProps['onClick'] = ({ key }) => { + navigate(key) + // 移动端点击菜单后关闭抽屉 + if (isMobile) { + setMobileMenuVisible(false) + } + } + + // 检测屏幕尺寸 + useEffect(() => { + const checkMobile = () => { + const isMobileScreen = window.innerWidth < 768 + setIsMobile(isMobileScreen) + // 移动端默认收起侧边栏 + if (isMobileScreen) { + setCollapsed(true) + } + } + + checkMobile() + window.addEventListener('resize', checkMobile) + return () => window.removeEventListener('resize', checkMobile) + }, []) + + // 待审核任务数量(有进度读权限时拉取) + useEffect(() => { + if (!canReadProgress) return + getPendingReviewCount() + .then((res) => { + const count = res?.data?.count ?? 0 + setPendingReviewCount(typeof count === 'number' ? count : 0) + }) + .catch(() => setPendingReviewCount(0)) + }, [canReadProgress]) + + // 侧边栏配置 + const siderProps = { + trigger: null, + collapsible: true, + collapsed: isMobile ? true : collapsed, + breakpoint: 'lg' as const, + collapsedWidth: isMobile ? 0 : 80, + className: isMobile ? 'mobile-sider' : '', + } + + // 根据当前路径自动展开对应父菜单,使「待审核」跳转后左侧菜单联动高亮 + const pathBasedOpenKeys = useMemo(() => { + const path = location.pathname + const keys: string[] = [] + if (path.startsWith('/admin')) keys.push('/admin') + if (path.startsWith('/camp')) keys.push('/camp') + if (path.startsWith('/objective')) keys.push('/objective') + if (path.startsWith('/subjective')) keys.push('/subjective') + return keys + }, [location.pathname]) + + const [menuOpenKeys, setMenuOpenKeys] = useState(pathBasedOpenKeys) + + useEffect(() => { + setMenuOpenKeys(pathBasedOpenKeys) + }, [pathBasedOpenKeys]) + + // 菜单组件 + const menuComponent = ( + + ) + + return ( + + {/* 桌面端侧边栏 */} + {!isMobile && ( + +
+

{collapsed ? '怼怼' : '怼怼后台'}

+
+ {menuComponent} +
+ )} + + {/* 移动端抽屉菜单 */} + setMobileMenuVisible(false)} + open={mobileMenuVisible} + styles={{ body: { padding: 0 } }} + width={200} + > +
+

怼怼后台

+
+ {menuComponent} +
+ + +
+
+
+ {isMobile ? ( + setMobileMenuVisible(true)} + /> + ) : collapsed ? ( + setCollapsed(!collapsed)} + /> + ) : ( + setCollapsed(!collapsed)} + /> + )} +
+
+ {canReadProgress && ( + + + navigate(RoutePath.CAMP_PENDING_TASK_LIST)} + > + + {!isMobile && 待审核} + + + + )} + {themeModeContext && ( + <> + +
+ 选择主题颜色 +
+ + {THEME_COLORS.map((color) => ( +
themeModeContext.setPrimaryColor(color.value)} + style={{ + width: 32, + height: 32, + borderRadius: 4, + backgroundColor: color.value, + cursor: 'pointer', + border: + currentPrimaryColor === color.value + ? `2px solid ${colorBorder}` + : '2px solid transparent', + boxShadow: + currentPrimaryColor === color.value + ? `0 0 0 2px ${colorBorder}40` + : 'none', + transition: 'all 0.2s', + opacity: currentPrimaryColor === color.value ? 1 : 0.8, + }} + onMouseEnter={(e) => { + e.currentTarget.style.opacity = '1' + e.currentTarget.style.transform = 'scale(1.1)' + }} + onMouseLeave={(e) => { + e.currentTarget.style.opacity = + currentPrimaryColor === color.value ? '1' : '0.8' + e.currentTarget.style.transform = 'scale(1)' + }} + title={color.name} + /> + ))} + +
+ } + placement="bottomRight" + trigger="click" + > + +
+ + 颜色 +
+
+
+ +
+ + + {isDarkMode ? '深色' : '亮色'} + +
+
+ + )} + +
+ } /> + {!isMobile && ( + + {userInfo?.nickname || userInfo?.username || '管理员'} + + )} +
+
+
+
+
+ + + +
+
+ ) +} + +export default BasicLayout + diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..a31c2f2 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' +import App from './App' +import './index.css' + +dayjs.locale('zh-cn') + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) + diff --git a/src/pages/Admin/AdminUserList.tsx b/src/pages/Admin/AdminUserList.tsx new file mode 100644 index 0000000..0e91a9f --- /dev/null +++ b/src/pages/Admin/AdminUserList.tsx @@ -0,0 +1,501 @@ +import { useState, useEffect } from 'react' +import { Table, Button, Space, Tag, Input, message, Modal, Form, Radio, Checkbox, Divider } from 'antd' +import { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined, UserOutlined, PhoneOutlined, SafetyOutlined } from '@ant-design/icons' +import type { ColumnsType } from 'antd/es/table' +import { getAdminUserList, createAdminUser, updateAdminUser, deleteAdminUser, getUserRoles, setUserRoles, type AdminUser, type CreateAdminUserRequest, type UpdateAdminUserRequest } from '@/api/admin' +import { getRoleList, type Role } from '@/api/role' + +const AdminUserList = () => { + const [searchText, setSearchText] = useState('') + const [loading, setLoading] = useState(false) + const [data, setData] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [form] = Form.useForm() + const [modalVisible, setModalVisible] = useState(false) + const [editingUser, setEditingUser] = useState(null) + const [roleModalVisible, setRoleModalVisible] = useState(false) + const [currentUser, setCurrentUser] = useState(null) + const [allRoles, setAllRoles] = useState([]) + const [selectedRoleIds, setSelectedRoleIds] = useState([]) + const [roleLoading, setRoleLoading] = useState(false) + + // 获取管理员列表 + const fetchAdminUsers = async () => { + setLoading(true) + try { + const response = await getAdminUserList({ + keyword: searchText || undefined, + page, + page_size: pageSize, + }) + + if (response.data.code === 200 && response.data.data) { + setData(response.data.data.list || []) + setTotal(response.data.data.total || 0) + } else { + message.error(response.data.message || '获取管理员列表失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '获取管理员列表失败') + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchAdminUsers() + }, [page, pageSize]) + + // 搜索 + const handleSearch = () => { + setPage(1) + fetchAdminUsers() + } + + // 新增管理员 + const handleAdd = () => { + setEditingUser(null) + form.resetFields() + setModalVisible(true) + } + + // 编辑管理员 + const handleEdit = (record: AdminUser) => { + setEditingUser(record) + form.setFieldsValue({ + username: record.username, + phone: record.phone, + nickname: record.nickname, + avatar: record.avatar, + status: record.status, + is_super_admin: record.is_super_admin, + }) + setModalVisible(true) + } + + // 删除管理员 + const handleDelete = (record: AdminUser) => { + Modal.confirm({ + title: '确认删除', + content: `确定要删除管理员 "${record.username || record.phone || record.nickname}" 吗?`, + onOk: async () => { + try { + const response = await deleteAdminUser(record.id) + if (response.data.code === 200) { + message.success('删除成功') + fetchAdminUsers() + } else { + message.error(response.data.message || '删除失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '删除失败') + } + }, + }) + } + + // 打开角色分配Modal + const handleAssignRoles = async (record: AdminUser) => { + setCurrentUser(record) + setRoleModalVisible(true) + setRoleLoading(true) + setSelectedRoleIds([]) + + try { + // 获取所有角色 + const allRolesResponse = await getRoleList({ page: 1, page_size: 1000 }) + if (allRolesResponse.data.code === 200 && allRolesResponse.data.data) { + setAllRoles(allRolesResponse.data.data.list || []) + } + + // 获取当前用户的角色 + const userRolesResponse = await getUserRoles(record.id) + if (userRolesResponse.data.code === 200 && userRolesResponse.data.data) { + setSelectedRoleIds(userRolesResponse.data.data || []) + } + } catch (error: any) { + message.error(error?.response?.data?.message || '获取角色列表失败') + } finally { + setRoleLoading(false) + } + } + + // 保存角色分配 + const handleSaveRoles = async () => { + if (!currentUser) return + + try { + const response = await setUserRoles(currentUser.id, selectedRoleIds) + if (response.data.code === 200) { + message.success('角色分配成功') + setRoleModalVisible(false) + } else { + message.error(response.data.message || '角色分配失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '角色分配失败') + } + } + + // 提交表单 + const handleSubmit = async () => { + try { + const values = await form.validateFields() + + if (editingUser) { + // 更新 + const updateData: UpdateAdminUserRequest = { + id: editingUser.id, + username: values.username, + phone: values.phone, + nickname: values.nickname, + avatar: values.avatar, + status: values.status, + is_super_admin: values.is_super_admin, + } + + // 如果密码不为空,则更新密码 + if (values.password) { + updateData.password = values.password + } + + const response = await updateAdminUser(updateData) + if (response.data.code === 200) { + message.success('更新成功') + setModalVisible(false) + fetchAdminUsers() + } else { + message.error(response.data.message || '更新失败') + } + } else { + // 创建 + if (!values.password) { + message.error('密码不能为空') + return + } + + const createData: CreateAdminUserRequest = { + username: values.username, + phone: values.phone, + password: values.password, + nickname: values.nickname, + avatar: values.avatar, + status: values.status ?? 1, + is_super_admin: values.is_super_admin ?? 0, + } + + const response = await createAdminUser(createData) + if (response.data.code === 200) { + message.success('创建成功') + setModalVisible(false) + fetchAdminUsers() + } else { + message.error(response.data.message || '创建失败') + } + } + } catch (error: any) { + if (error?.errorFields) { + // 表单验证错误 + return + } + message.error(error?.response?.data?.message || '操作失败') + } + } + + const columns: ColumnsType = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 100, + }, + { + title: '用户名', + dataIndex: 'username', + key: 'username', + render: (text: string) => text || '-', + }, + { + title: '手机号', + dataIndex: 'phone', + key: 'phone', + render: (text: string) => text || '-', + }, + { + title: '昵称', + dataIndex: 'nickname', + key: 'nickname', + render: (text: string) => text || '-', + }, + { + title: '超级管理员', + dataIndex: 'is_super_admin', + key: 'is_super_admin', + width: 120, + render: (isSuperAdmin: number) => ( + + {isSuperAdmin === 1 ? '是' : '否'} + + ), + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + render: (status: number) => ( + + {status === 1 ? '启用' : '禁用'} + + ), + }, + { + title: '最后登录', + dataIndex: 'last_login_at', + key: 'last_login_at', + width: 180, + render: (text: string) => text || '-', + }, + { + title: '创建时间', + dataIndex: 'created_at', + key: 'created_at', + width: 180, + }, + { + title: '操作', + key: 'action', + width: 320, + fixed: 'right', + render: (_: any, record: AdminUser) => ( + + + + + + ), + }, + ] + + return ( +
+
+ + } + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + onPressEnter={handleSearch} + style={{ width: 300 }} + allowClear + /> + + + +
+ + `共 ${total} 条`, + onChange: (page, pageSize) => { + setPage(page) + setPageSize(pageSize) + }, + }} + scroll={{ x: 1200 }} + /> + + { + setModalVisible(false) + form.resetFields() + }} + width={600} + okText="确定" + cancelText="取消" + > +
+ { + const phone = form.getFieldValue('phone') + if (!value && !phone) { + return Promise.reject(new Error('用户名和手机号至少填写一个')) + } + return Promise.resolve() + }, + }, + ]} + > + } placeholder="请输入用户名(可选)" /> + + + { + const username = form.getFieldValue('username') + if (!value && !username) { + return Promise.reject(new Error('用户名和手机号至少填写一个')) + } + if (value && !/^1[3-9]\d{9}$/.test(value)) { + return Promise.reject(new Error('请输入正确的手机号')) + } + return Promise.resolve() + }, + }, + ]} + > + } placeholder="请输入手机号(可选)" /> + + + {!editingUser && ( + + + + )} + + {editingUser && ( + + + + )} + + + + + + + + + + + + 启用 + 禁用 + + + + + + + + + + +
+ + {/* 角色分配Modal */} + { + setRoleModalVisible(false) + setCurrentUser(null) + setSelectedRoleIds([]) + }} + width={600} + okText="保存" + cancelText="取消" + confirmLoading={roleLoading} + > +
+ {roleLoading ? ( +
加载中...
+ ) : ( + <> +
+ 0 && selectedRoleIds.length === allRoles.length} + indeterminate={selectedRoleIds.length > 0 && selectedRoleIds.length < allRoles.length} + onChange={(e) => { + if (e.target.checked) { + setSelectedRoleIds(allRoles.map(r => r.id)) + } else { + setSelectedRoleIds([]) + } + }} + > + 全选 + + + 已选择 {selectedRoleIds.length} / {allRoles.length} 个角色 + +
+ + {allRoles.map(role => ( +
+ { + if (e.target.checked) { + setSelectedRoleIds([...selectedRoleIds, role.id]) + } else { + setSelectedRoleIds(selectedRoleIds.filter(id => id !== role.id)) + } + }} + > + {role.name} + {role.code} + {role.status === 0 && 已禁用} + {role.description && ( + {role.description} + )} + +
+ ))} + + )} +
+
+ + ) +} + +export default AdminUserList + diff --git a/src/pages/Admin/PermissionList.tsx b/src/pages/Admin/PermissionList.tsx new file mode 100644 index 0000000..2a8c1b4 --- /dev/null +++ b/src/pages/Admin/PermissionList.tsx @@ -0,0 +1,340 @@ +import { useState, useEffect } from 'react' +import { Table, Button, Space, Tag, Input, message, Modal, Form, Select } from 'antd' +import { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons' +import type { ColumnsType } from 'antd/es/table' +import { getPermissionList, createPermission, updatePermission, deletePermission, getResources, type Permission, type CreatePermissionRequest, type UpdatePermissionRequest } from '@/api/permission' + +const PermissionList = () => { + const [searchText, setSearchText] = useState('') + const [resourceFilter, setResourceFilter] = useState('') + const [resources, setResources] = useState([]) + const [loading, setLoading] = useState(false) + const [data, setData] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [form] = Form.useForm() + const [modalVisible, setModalVisible] = useState(false) + const [editingPermission, setEditingPermission] = useState(null) + + // 获取资源列表 + const fetchResources = async () => { + try { + const response = await getResources() + if (response.data.code === 200 && response.data.data) { + setResources(response.data.data || []) + } + } catch (error) { + // 忽略错误 + } + } + + useEffect(() => { + fetchResources() + }, []) + + // 获取权限列表 + const fetchPermissions = async () => { + setLoading(true) + try { + const response = await getPermissionList({ + keyword: searchText || undefined, + resource: resourceFilter || undefined, + page, + page_size: pageSize, + }) + + if (response.data.code === 200 && response.data.data) { + setData(response.data.data.list || []) + setTotal(response.data.data.total || 0) + } else { + message.error(response.data.message || '获取权限列表失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '获取权限列表失败') + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchPermissions() + }, [page, pageSize, resourceFilter]) + + // 搜索 + const handleSearch = () => { + setPage(1) + fetchPermissions() + } + + // 新增权限 + const handleAdd = () => { + setEditingPermission(null) + form.resetFields() + setModalVisible(true) + } + + // 编辑权限 + const handleEdit = (record: Permission) => { + setEditingPermission(record) + form.setFieldsValue({ + name: record.name, + code: record.code, + resource: record.resource, + action: record.action, + description: record.description, + }) + setModalVisible(true) + } + + // 删除权限 + const handleDelete = (record: Permission) => { + Modal.confirm({ + title: '确认删除', + content: `确定要删除权限 "${record.name}" 吗?`, + onOk: async () => { + try { + const response = await deletePermission(record.id) + if (response.data.code === 200) { + message.success('删除成功') + fetchPermissions() + fetchResources() // 刷新资源列表 + } else { + message.error(response.data.message || '删除失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '删除失败') + } + }, + }) + } + + // 提交表单 + const handleSubmit = async () => { + try { + const values = await form.validateFields() + + if (editingPermission) { + // 更新 + const updateData: UpdatePermissionRequest = { + id: editingPermission.id, + name: values.name, + code: values.code, + resource: values.resource, + action: values.action, + description: values.description, + } + + const response = await updatePermission(updateData) + if (response.data.code === 200) { + message.success('更新成功') + setModalVisible(false) + fetchPermissions() + fetchResources() // 刷新资源列表 + } else { + message.error(response.data.message || '更新失败') + } + } else { + // 创建 + const createData: CreatePermissionRequest = { + name: values.name, + code: values.code, + resource: values.resource, + action: values.action, + description: values.description, + } + + const response = await createPermission(createData) + if (response.data.code === 200) { + message.success('创建成功') + setModalVisible(false) + fetchPermissions() + fetchResources() // 刷新资源列表 + } else { + message.error(response.data.message || '创建失败') + } + } + } catch (error: any) { + if (error?.errorFields) { + // 表单验证错误 + return + } + message.error(error?.response?.data?.message || '操作失败') + } + } + + const columns: ColumnsType = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 100, + }, + { + title: '权限名称', + dataIndex: 'name', + key: 'name', + }, + { + title: '权限代码', + dataIndex: 'code', + key: 'code', + }, + { + title: '资源', + dataIndex: 'resource', + key: 'resource', + render: (text: string) => {text}, + }, + { + title: '操作', + dataIndex: 'action', + key: 'action', + render: (text: string) => {text}, + }, + { + title: '描述', + dataIndex: 'description', + key: 'description', + render: (text: string) => text || '-', + }, + { + title: '创建时间', + dataIndex: 'created_at', + key: 'created_at', + width: 180, + }, + { + title: '操作', + key: 'action', + width: 180, + fixed: 'right', + render: (_: any, record: Permission) => ( + + + + + ), + }, + ] + + return ( +
+
+ + } + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + onPressEnter={handleSearch} + style={{ width: 250 }} + allowClear + /> + + + + +
+ +
`共 ${total} 条`, + onChange: (page, pageSize) => { + setPage(page) + setPageSize(pageSize) + }, + }} + scroll={{ x: 1200 }} + /> + + { + setModalVisible(false) + form.resetFields() + }} + width={600} + okText="确定" + cancelText="取消" + > +
+ + + + + + + + + + + + + + + + + + + + +
+ + ) +} + +export default PermissionList + diff --git a/src/pages/Admin/RoleList.tsx b/src/pages/Admin/RoleList.tsx new file mode 100644 index 0000000..2471fb8 --- /dev/null +++ b/src/pages/Admin/RoleList.tsx @@ -0,0 +1,445 @@ +import { useState, useEffect } from 'react' +import { Table, Button, Space, Tag, Input, message, Modal, Form, Radio, Checkbox, Divider } from 'antd' +import { SearchOutlined, PlusOutlined, EditOutlined, DeleteOutlined, SafetyOutlined } from '@ant-design/icons' +import type { ColumnsType } from 'antd/es/table' +import { getRoleList, createRole, updateRole, deleteRole, getRolePermissions, setRolePermissions, type Role, type CreateRoleRequest, type UpdateRoleRequest } from '@/api/role' +import { getPermissionList, type Permission } from '@/api/permission' + +const RoleList = () => { + const [searchText, setSearchText] = useState('') + const [loading, setLoading] = useState(false) + const [data, setData] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [form] = Form.useForm() + const [modalVisible, setModalVisible] = useState(false) + const [editingRole, setEditingRole] = useState(null) + const [permissionModalVisible, setPermissionModalVisible] = useState(false) + const [currentRole, setCurrentRole] = useState(null) + const [allPermissions, setAllPermissions] = useState([]) + const [selectedPermissionIds, setSelectedPermissionIds] = useState([]) + const [permissionLoading, setPermissionLoading] = useState(false) + + // 获取角色列表 + const fetchRoles = async () => { + setLoading(true) + try { + const response = await getRoleList({ + keyword: searchText || undefined, + page, + page_size: pageSize, + }) + + if (response.data.code === 200 && response.data.data) { + setData(response.data.data.list || []) + setTotal(response.data.data.total || 0) + } else { + message.error(response.data.message || '获取角色列表失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '获取角色列表失败') + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchRoles() + }, [page, pageSize]) + + // 搜索 + const handleSearch = () => { + setPage(1) + fetchRoles() + } + + // 新增角色 + const handleAdd = () => { + setEditingRole(null) + form.resetFields() + setModalVisible(true) + } + + // 编辑角色 + const handleEdit = (record: Role) => { + setEditingRole(record) + form.setFieldsValue({ + name: record.name, + description: record.description, + status: record.status, + }) + setModalVisible(true) + } + + // 删除角色 + const handleDelete = (record: Role) => { + Modal.confirm({ + title: '确认删除', + content: `确定要删除角色 "${record.name}" 吗?`, + onOk: async () => { + try { + const response = await deleteRole(record.id) + if (response.data.code === 200) { + message.success('删除成功') + fetchRoles() + } else { + message.error(response.data.message || '删除失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '删除失败') + } + }, + }) + } + + // 打开权限分配Modal + const handleAssignPermissions = async (record: Role) => { + setCurrentRole(record) + setPermissionModalVisible(true) + setPermissionLoading(true) + setSelectedPermissionIds([]) + + try { + // 获取所有权限 + const allPermsResponse = await getPermissionList({ page: 1, page_size: 1000 }) + if (allPermsResponse.data.code === 200 && allPermsResponse.data.data) { + setAllPermissions(allPermsResponse.data.data.list || []) + } + + // 获取当前角色的权限 + const rolePermsResponse = await getRolePermissions(record.id) + if (rolePermsResponse.data.code === 200 && rolePermsResponse.data.data) { + setSelectedPermissionIds(rolePermsResponse.data.data || []) + } + } catch (error: any) { + message.error(error?.response?.data?.message || '获取权限列表失败') + } finally { + setPermissionLoading(false) + } + } + + // 保存权限分配 + const handleSavePermissions = async () => { + if (!currentRole) return + + try { + const response = await setRolePermissions(currentRole.id, selectedPermissionIds) + if (response.data.code === 200) { + message.success('权限分配成功') + setPermissionModalVisible(false) + } else { + message.error(response.data.message || '权限分配失败') + } + } catch (error: any) { + message.error(error?.response?.data?.message || '权限分配失败') + } + } + + // 按资源分组权限 + const groupPermissionsByResource = () => { + const grouped: Record = {} + allPermissions.forEach(perm => { + if (!grouped[perm.resource]) { + grouped[perm.resource] = [] + } + grouped[perm.resource].push(perm) + }) + return grouped + } + + // 提交表单 + const handleSubmit = async () => { + try { + const values = await form.validateFields() + + if (editingRole) { + // 更新(不更新代码,代码由后端维护) + const updateData: UpdateRoleRequest = { + id: editingRole.id, + name: values.name, + code: editingRole.code, // 保持原有代码不变 + description: values.description, + status: values.status, + } + + const response = await updateRole(updateData) + if (response.data.code === 200) { + message.success('更新成功') + setModalVisible(false) + fetchRoles() + } else { + message.error(response.data.message || '更新失败') + } + } else { + // 创建(不传 code,由后端自动生成) + const createData: CreateRoleRequest = { + name: values.name, + description: values.description, + status: values.status ?? 1, + } + + const response = await createRole(createData) + if (response.data.code === 200) { + message.success('创建成功') + setModalVisible(false) + fetchRoles() + } else { + message.error(response.data.message || '创建失败') + } + } + } catch (error: any) { + if (error?.errorFields) { + // 表单验证错误 + return + } + message.error(error?.response?.data?.message || '操作失败') + } + } + + const columns: ColumnsType = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 100, + }, + { + title: '角色名称', + dataIndex: 'name', + key: 'name', + }, + { + title: '角色代码', + dataIndex: 'code', + key: 'code', + }, + { + title: '描述', + dataIndex: 'description', + key: 'description', + render: (text: string) => text || '-', + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + render: (status: number) => ( + + {status === 1 ? '启用' : '禁用'} + + ), + }, + { + title: '创建时间', + dataIndex: 'created_at', + key: 'created_at', + width: 180, + }, + { + title: '操作', + key: 'action', + width: 320, + fixed: 'right', + render: (_: any, record: Role) => ( + + + + + + ), + }, + ] + + return ( +
+
+ + } + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + onPressEnter={handleSearch} + style={{ width: 300 }} + allowClear + /> + + + +
+ +
`共 ${total} 条`, + onChange: (page, pageSize) => { + setPage(page) + setPageSize(pageSize) + }, + }} + scroll={{ x: 1000 }} + /> + + { + setModalVisible(false) + form.resetFields() + }} + width={600} + okText="确定" + cancelText="取消" + > +
+ + + + + {editingRole && ( + + +
+ 角色代码由系统自动生成,不可修改 +
+
+ )} + + + + + + + + 启用 + 禁用 + + + +
+ + {/* 权限分配Modal */} + { + setPermissionModalVisible(false) + setCurrentRole(null) + setSelectedPermissionIds([]) + }} + width={800} + okText="保存" + cancelText="取消" + confirmLoading={permissionLoading} + > +
+ {permissionLoading ? ( +
加载中...
+ ) : ( + <> +
+ 0 && selectedPermissionIds.length === allPermissions.length} + indeterminate={selectedPermissionIds.length > 0 && selectedPermissionIds.length < allPermissions.length} + onChange={(e) => { + if (e.target.checked) { + setSelectedPermissionIds(allPermissions.map(p => p.id)) + } else { + setSelectedPermissionIds([]) + } + }} + > + 全选 + + + 已选择 {selectedPermissionIds.length} / {allPermissions.length} 个权限 + +
+ + {Object.entries(groupPermissionsByResource()).map(([resource, permissions]) => ( +
+
+ {resource} + selectedPermissionIds.includes(p.id))} + indeterminate={ + permissions.some(p => selectedPermissionIds.includes(p.id)) && + !permissions.every(p => selectedPermissionIds.includes(p.id)) + } + onChange={(e) => { + const resourcePermIds = permissions.map(p => p.id) + if (e.target.checked) { + setSelectedPermissionIds([...new Set([...selectedPermissionIds, ...resourcePermIds])]) + } else { + setSelectedPermissionIds(selectedPermissionIds.filter(id => !resourcePermIds.includes(id))) + } + }} + > + 全选该资源 + +
+
+ {permissions.map(perm => ( +
+ { + if (e.target.checked) { + setSelectedPermissionIds([...selectedPermissionIds, perm.id]) + } else { + setSelectedPermissionIds(selectedPermissionIds.filter(id => id !== perm.id)) + } + }} + > + {perm.name} + {perm.action} + {perm.description && ( + {perm.description} + )} + +
+ ))} +
+
+ ))} + + )} +
+
+ + ) +} + +export default RoleList + diff --git a/src/pages/Camp/CampList.tsx b/src/pages/Camp/CampList.tsx new file mode 100644 index 0000000..c24b80c --- /dev/null +++ b/src/pages/Camp/CampList.tsx @@ -0,0 +1,487 @@ +import { useState, useEffect } from 'react' +import { + Table, + Button, + Form, + Input, + Modal, + message, + Space, + Popconfirm, + Select, + Switch, + Image, + Tag, +} from 'antd' +import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined, UnorderedListOutlined, UserOutlined, ClearOutlined } from '@ant-design/icons' +import { useNavigate, useSearchParams } from 'react-router-dom' +import type { ColumnsType } from 'antd/es/table' +import type { Camp, Category } from '@/types/camp' +import { IntroType, RecommendFilter } from '@/types/camp' +import { listCamps, createCamp, updateCamp, deleteCamp, listCategories } from '@/api/camp' +import { DEFAULT_PAGE_SIZE, RoutePath } from '@/constants' +import ImageUpload from '@/components/ImageUpload' + +const { TextArea } = Input + +const CampList = () => { + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const [loading, setLoading] = useState(false) + const [dataSource, setDataSource] = useState([]) + const [categories, setCategories] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE) + const [keyword, setKeyword] = useState('') + const [categoryFilter, setCategoryFilter] = useState() + const [recommendFilter, setRecommendFilter] = useState(RecommendFilter.ALL) + const [modalVisible, setModalVisible] = useState(false) + const [editingRecord, setEditingRecord] = useState(null) + + const [form] = Form.useForm() + const introTypeValue = Form.useWatch('introType', form) + + // 从 URL 参数中读取分类筛选 + useEffect(() => { + const categoryId = searchParams.get('categoryId') + if (categoryId) { + setCategoryFilter(categoryId) + } + }, [searchParams]) + + // 获取分类列表 + const fetchCategories = async () => { + try { + const res = await listCategories({ page: 1, pageSize: 100 }) + if (res.data.code === 200) { + setCategories(res.data.data.list) + } + } catch (error) { + console.error('获取分类列表失败') + } + } + + // 获取列表数据 + const fetchData = async () => { + setLoading(true) + try { + const res = await listCamps({ + page, + pageSize, + keyword, + categoryId: categoryFilter, + recommendFilter, + }) + if (res.data.code === 200) { + const list = res.data.data.list || [] + const total = res.data.data.total || 0 + setDataSource(list) + setTotal(total) + } + } catch (error) { + console.error('获取打卡营列表错误:', error) + // 设置空列表,不弹出提示 + setDataSource([]) + setTotal(0) + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchCategories() + }, []) + + useEffect(() => { + fetchData() + }, [page, pageSize, keyword, categoryFilter, recommendFilter]) + + // 监听 Modal 打开,确保表单值正确设置(作为备用方案) + useEffect(() => { + if (modalVisible && editingRecord) { + console.log('编辑打卡营数据:', editingRecord) + // 确保表单值已设置(如果 handleOpenModal 中已设置,这里不会重复设置) + const currentValues = form.getFieldsValue() + if (!currentValues.coverImage && editingRecord.coverImage) { + form.setFieldsValue({ + coverImage: editingRecord.coverImage, + introContent: editingRecord.introContent, + }) + } + } + }, [modalVisible, editingRecord, form]) + + // 打开新增/编辑弹窗 + const handleOpenModal = (record?: Camp) => { + console.log('handleOpenModal 被调用', { record }) + + // 先打开 Modal + setModalVisible(true) + + // 然后设置编辑记录和表单值 + if (record) { + setEditingRecord(record) + // 使用 setTimeout 确保 Modal 完全打开后再设置表单值 + setTimeout(() => { + try { + form.setFieldsValue({ + title: record.title, + coverImage: record.coverImage, + description: record.description, + categoryId: record.categoryId, + introType: record.introType, + introContent: record.introContent, + isRecommended: record.isRecommended, + }) + console.log('表单值已设置', { + coverImage: record.coverImage, + introContent: record.introContent, + }) + } catch (error) { + console.error('设置表单值失败:', error) + } + }, 100) + } else { + setEditingRecord(null) + form.resetFields() + const initialValues: any = { + introType: IntroType.NONE, + isRecommended: false, + } + if (categoryFilter) { + initialValues.categoryId = categoryFilter + } + setTimeout(() => { + form.setFieldsValue(initialValues) + }, 100) + } + } + + // 提交表单 + const handleSubmit = async () => { + try { + const values = await form.validateFields() + + if (editingRecord) { + // 编辑 + await updateCamp({ ...editingRecord, ...values }) + message.success('编辑打卡营成功') + } else { + // 新增 + await createCamp(values) + message.success('新增打卡营成功') + } + + setModalVisible(false) + setEditingRecord(null) + form.resetFields() + fetchData() + } catch (error: any) { + const errorMsg = error?.message || '操作失败' + message.error(errorMsg) + } + } + + // 删除打卡营 + const handleDelete = async (id: string) => { + try { + await deleteCamp(id) + message.success('删除成功') + fetchData() + } catch (error: any) { + const errorMsg = error?.message || '删除失败' + message.error(errorMsg) + } + } + + // 搜索 + const handleSearch = (value: string) => { + setKeyword(value) + setPage(1) + } + + // 重置筛选 + const handleResetFilters = () => { + setKeyword('') + setCategoryFilter(undefined) + setRecommendFilter(RecommendFilter.ALL) + setPage(1) + } + + // 简介类型标签 + const getIntroTypeTag = (type: IntroType) => { + const tagMap = { + [IntroType.NONE]: { color: 'default', text: '无简介' }, + [IntroType.IMAGE_TEXT]: { color: 'blue', text: '图文简介' }, + [IntroType.VIDEO]: { color: 'green', text: '视频简介' }, + } + const config = tagMap[type] + return {config.text} + } + + const columns: ColumnsType = [ + { + title: '封面图', + dataIndex: 'coverImage', + key: 'coverImage', + width: 100, + render: (url: string) => , + }, + { + title: '标题', + dataIndex: 'title', + key: 'title', + width: 200, + }, + { + title: '描述', + dataIndex: 'description', + key: 'description', + ellipsis: true, + width: 250, + }, + { + title: '分类', + dataIndex: 'categoryId', + key: 'categoryId', + width: 120, + render: (categoryId: string) => { + const category = categories.find((c) => c.id === categoryId) + return category?.name || '-' + }, + }, + { + title: '简介类型', + dataIndex: 'introType', + key: 'introType', + width: 100, + render: getIntroTypeTag, + }, + { + title: '小节数', + dataIndex: 'sectionCount', + key: 'sectionCount', + width: 80, + }, + { + title: '推荐', + dataIndex: 'isRecommended', + key: 'isRecommended', + width: 80, + render: (recommended: boolean) => ( + {recommended ? '是' : '否'} + ), + }, + { + title: '操作', + key: 'action', + width: 280, + fixed: 'right', + render: (_, record) => ( + + + + + handleDelete(record.id)} + okText="确定" + cancelText="取消" + > + + + + ), + }, + ] + + return ( +
+ {/* 页面标题 */} +
+

打卡营列表

+
+ 管理所有打卡营,支持按分类和推荐状态筛选,可查看打卡营下的小节列表 +
+
+ +
+ + setKeyword(e.target.value)} + onSearch={handleSearch} + /> + { + setRecommendFilter(value) + setPage(1) + }} + options={[ + { label: '全部', value: RecommendFilter.ALL }, + { label: '仅推荐', value: RecommendFilter.ONLY_TRUE }, + { label: '仅非推荐', value: RecommendFilter.ONLY_FALSE }, + ]} + /> + + + + +
+ +
`共 ${total} 条`, + onChange: (page, pageSize) => { + setPage(page) + setPageSize(pageSize) + }, + }} + /> + + { + setModalVisible(false) + setEditingRecord(null) + form.resetFields() + }} + width={700} + > +
+ + + + + + + +