From 63fa1620814a280394f114df8fd174ca28e3a42e Mon Sep 17 00:00:00 2001 From: zxiaosi <2692159310@qq.com> Date: Fri, 1 Jul 2022 15:08:28 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96FastAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 95 +- backend/Dockerfile | 24 - backend/README.md | 194 - backend/apis/__init__.py | 5 +- backend/apis/api_router.py | 32 - backend/apis/common/__init__.py | 5 - backend/apis/common/course.py | 65 - backend/apis/common/dashboard.py | 43 - backend/apis/common/department.py | 76 - backend/apis/common/elective.py | 98 - backend/apis/common/login.py | 77 - backend/apis/common/major.py | 66 - backend/apis/common/redis_check.py | 23 - backend/apis/common/student.py | 72 - backend/apis/common/taught.py | 72 - backend/apis/common/teacher.py | 67 - backend/apis/common/upload.py | 40 - backend/apis/debug.py | 18 + backend/apis/deps.py | 55 - backend/core/__init__.py | 6 +- backend/core/config.py | 42 +- backend/core/logger.py | 37 - backend/core/security.py | 64 - backend/crud/__init__.py | 14 - backend/crud/admin.py | 51 - backend/crud/base.py | 124 - backend/crud/course.py | 15 - backend/crud/department.py | 15 - backend/crud/elective.py | 53 - backend/crud/major.py | 15 - backend/crud/student.py | 61 - backend/crud/taught.py | 32 - backend/crud/teacher.py | 52 - backend/db/__init__.py | 8 - backend/db/data.py | 120 - backend/db/init_db.py | 53 - backend/db/redis.py | 58 - backend/db/session.py | 29 - backend/main.py | 38 +- backend/models/__init__.py | 13 +- backend/models/admin.py | 23 - backend/models/base.py | 31 - backend/models/course.py | 25 - backend/models/department.py | 25 - backend/models/elective.py | 20 - backend/models/major.py | 25 - backend/models/student.py | 35 - backend/models/taught.py | 18 - backend/models/teacher.py | 38 - backend/register/__init__.py | 10 - backend/register/cors.py | 21 - backend/register/exception.py | 102 - backend/register/middleware.py | 32 - backend/register/mount.py | 16 - backend/register/router.py | 25 - backend/requirement.txt | 18 + backend/requirements.txt | 37 - backend/schemas/__init__.py | 17 +- backend/schemas/admin.py | 35 - backend/schemas/common.py | 18 - backend/schemas/course.py | 32 - backend/schemas/department.py | 34 - backend/schemas/elective.py | 37 - backend/schemas/login.py | 12 - backend/schemas/major.py | 36 - backend/schemas/result.py | 30 - backend/schemas/student.py | 39 - backend/schemas/taught.py | 34 - backend/schemas/teacher.py | 41 - backend/schemas/todo.py | 17 - backend/schemas/token.py | 19 - backend/sql_app.db | Bin 94208 -> 0 bytes backend/static/author.jpg | Bin 45621 -> 0 bytes backend/test_main.http | 11 + backend/utils/__init__.py | 10 +- backend/utils/check_enum.py | 23 - backend/utils/create_dir.py | 20 - backend/utils/custom_exc.py | 52 - backend/utils/ip_address.py | 32 - backend/utils/obj_dict.py | 16 - backend/utils/permission_assign.py | 42 - backend/utils/resp_code.py | 79 - frontend/.gitignore | 28 - frontend/.vscode/extensions.json | 3 - frontend/README.md | 133 - frontend/env.d.ts | 1 - frontend/index.html | 15 - frontend/package-lock.json | 3256 ----------------- frontend/package.json | 27 - frontend/src/App.vue | 18 - frontend/src/api/index.ts | 96 - frontend/src/api/login.ts | 30 - frontend/src/api/model.ts | 72 - frontend/src/assets/css/color-dark.css | 28 - frontend/src/assets/css/icon.css | 9 - frontend/src/assets/css/main.css | 116 - frontend/src/assets/img/img.jpg | Bin 45621 -> 0 bytes frontend/src/assets/js/global.ts | 15 - frontend/src/assets/logo.svg | 1 - frontend/src/components/BaseTable/index.ts | 13 - frontend/src/components/BaseTable/index.vue | 390 -- frontend/src/components/Breadcrumb/index.vue | 33 - frontend/src/components/LoadingBar/index.vue | 56 - frontend/src/components/Pagination/index.vue | 45 - frontend/src/layout/Header/index.vue | 182 - frontend/src/layout/Home/index.vue | 46 - frontend/src/layout/Sidebar/index.vue | 116 - frontend/src/layout/Tags/index.vue | 193 - frontend/src/main.ts | 19 - frontend/src/request/auth.ts | 28 - frontend/src/request/http.ts | 135 - frontend/src/request/index.ts | 20 - frontend/src/request/statusCode.ts | 41 - frontend/src/router/index.ts | 146 - frontend/src/stores/data.ts | 25 - frontend/src/stores/index.ts | 57 - frontend/src/types/index.ts | 35 - frontend/src/types/table.ts | 115 - frontend/src/utils/clickRecover.ts | 11 - frontend/src/utils/handleArray.ts | 32 - frontend/src/utils/handleInject.ts | 17 - frontend/src/utils/handleTime.ts | 8 - frontend/src/utils/spanMethod.ts | 66 - frontend/src/views/dashboard/index.ts | 27 - frontend/src/views/dashboard/index.vue | 430 --- frontend/src/views/error/403/index.vue | 55 - frontend/src/views/error/404/index.vue | 55 - frontend/src/views/login/index.ts | 4 - frontend/src/views/login/index.vue | 158 - frontend/src/views/messages/index.ts | 8 - frontend/src/views/messages/index.vue | 166 - frontend/src/views/settings/course/index.ts | 7 - frontend/src/views/settings/course/index.vue | 144 - .../src/views/settings/department/index.ts | 7 - .../src/views/settings/department/index.vue | 132 - frontend/src/views/settings/elective/index.ts | 10 - .../src/views/settings/elective/index.vue | 120 - frontend/src/views/settings/index.vue | 3 - frontend/src/views/settings/major/index.ts | 9 - frontend/src/views/settings/major/index.vue | 160 - .../src/views/settings/myElective/index.ts | 11 - .../src/views/settings/myElective/index.vue | 54 - frontend/src/views/settings/myTaught/index.ts | 13 - .../src/views/settings/myTaught/index.vue | 134 - frontend/src/views/settings/student/index.ts | 20 - frontend/src/views/settings/student/index.vue | 204 -- frontend/src/views/settings/taught/index.ts | 10 - frontend/src/views/settings/taught/index.vue | 114 - frontend/src/views/settings/teacher/index.ts | 34 - frontend/src/views/settings/teacher/index.vue | 242 -- frontend/src/views/user/index.ts | 35 - frontend/src/views/user/index.vue | 134 - frontend/tsconfig.json | 21 - frontend/tsconfig.vite-config.json | 8 - frontend/vite.config.ts | 27 - redis&&nginx.md | 89 - 156 files changed, 82 insertions(+), 11454 deletions(-) delete mode 100644 backend/Dockerfile delete mode 100644 backend/README.md delete mode 100644 backend/apis/api_router.py delete mode 100644 backend/apis/common/__init__.py delete mode 100644 backend/apis/common/course.py delete mode 100644 backend/apis/common/dashboard.py delete mode 100644 backend/apis/common/department.py delete mode 100644 backend/apis/common/elective.py delete mode 100644 backend/apis/common/login.py delete mode 100644 backend/apis/common/major.py delete mode 100644 backend/apis/common/redis_check.py delete mode 100644 backend/apis/common/student.py delete mode 100644 backend/apis/common/taught.py delete mode 100644 backend/apis/common/teacher.py delete mode 100644 backend/apis/common/upload.py create mode 100644 backend/apis/debug.py delete mode 100644 backend/apis/deps.py delete mode 100644 backend/core/logger.py delete mode 100644 backend/core/security.py delete mode 100644 backend/crud/__init__.py delete mode 100644 backend/crud/admin.py delete mode 100644 backend/crud/base.py delete mode 100644 backend/crud/course.py delete mode 100644 backend/crud/department.py delete mode 100644 backend/crud/elective.py delete mode 100644 backend/crud/major.py delete mode 100644 backend/crud/student.py delete mode 100644 backend/crud/taught.py delete mode 100644 backend/crud/teacher.py delete mode 100644 backend/db/__init__.py delete mode 100644 backend/db/data.py delete mode 100644 backend/db/init_db.py delete mode 100644 backend/db/redis.py delete mode 100644 backend/db/session.py delete mode 100644 backend/models/admin.py delete mode 100644 backend/models/base.py delete mode 100644 backend/models/course.py delete mode 100644 backend/models/department.py delete mode 100644 backend/models/elective.py delete mode 100644 backend/models/major.py delete mode 100644 backend/models/student.py delete mode 100644 backend/models/taught.py delete mode 100644 backend/models/teacher.py delete mode 100644 backend/register/__init__.py delete mode 100644 backend/register/cors.py delete mode 100644 backend/register/exception.py delete mode 100644 backend/register/middleware.py delete mode 100644 backend/register/mount.py delete mode 100644 backend/register/router.py create mode 100644 backend/requirement.txt delete mode 100644 backend/requirements.txt delete mode 100644 backend/schemas/admin.py delete mode 100644 backend/schemas/common.py delete mode 100644 backend/schemas/course.py delete mode 100644 backend/schemas/department.py delete mode 100644 backend/schemas/elective.py delete mode 100644 backend/schemas/login.py delete mode 100644 backend/schemas/major.py delete mode 100644 backend/schemas/result.py delete mode 100644 backend/schemas/student.py delete mode 100644 backend/schemas/taught.py delete mode 100644 backend/schemas/teacher.py delete mode 100644 backend/schemas/todo.py delete mode 100644 backend/schemas/token.py delete mode 100644 backend/sql_app.db delete mode 100644 backend/static/author.jpg create mode 100644 backend/test_main.http delete mode 100644 backend/utils/check_enum.py delete mode 100644 backend/utils/create_dir.py delete mode 100644 backend/utils/custom_exc.py delete mode 100644 backend/utils/ip_address.py delete mode 100644 backend/utils/obj_dict.py delete mode 100644 backend/utils/permission_assign.py delete mode 100644 backend/utils/resp_code.py delete mode 100644 frontend/.gitignore delete mode 100644 frontend/.vscode/extensions.json delete mode 100644 frontend/README.md delete mode 100644 frontend/env.d.ts delete mode 100644 frontend/index.html delete mode 100644 frontend/package-lock.json delete mode 100644 frontend/package.json delete mode 100644 frontend/src/App.vue delete mode 100644 frontend/src/api/index.ts delete mode 100644 frontend/src/api/login.ts delete mode 100644 frontend/src/api/model.ts delete mode 100644 frontend/src/assets/css/color-dark.css delete mode 100644 frontend/src/assets/css/icon.css delete mode 100644 frontend/src/assets/css/main.css delete mode 100644 frontend/src/assets/img/img.jpg delete mode 100644 frontend/src/assets/js/global.ts delete mode 100644 frontend/src/assets/logo.svg delete mode 100644 frontend/src/components/BaseTable/index.ts delete mode 100644 frontend/src/components/BaseTable/index.vue delete mode 100644 frontend/src/components/Breadcrumb/index.vue delete mode 100644 frontend/src/components/LoadingBar/index.vue delete mode 100644 frontend/src/components/Pagination/index.vue delete mode 100644 frontend/src/layout/Header/index.vue delete mode 100644 frontend/src/layout/Home/index.vue delete mode 100644 frontend/src/layout/Sidebar/index.vue delete mode 100644 frontend/src/layout/Tags/index.vue delete mode 100644 frontend/src/main.ts delete mode 100644 frontend/src/request/auth.ts delete mode 100644 frontend/src/request/http.ts delete mode 100644 frontend/src/request/index.ts delete mode 100644 frontend/src/request/statusCode.ts delete mode 100644 frontend/src/router/index.ts delete mode 100644 frontend/src/stores/data.ts delete mode 100644 frontend/src/stores/index.ts delete mode 100644 frontend/src/types/index.ts delete mode 100644 frontend/src/types/table.ts delete mode 100644 frontend/src/utils/clickRecover.ts delete mode 100644 frontend/src/utils/handleArray.ts delete mode 100644 frontend/src/utils/handleInject.ts delete mode 100644 frontend/src/utils/handleTime.ts delete mode 100644 frontend/src/utils/spanMethod.ts delete mode 100644 frontend/src/views/dashboard/index.ts delete mode 100644 frontend/src/views/dashboard/index.vue delete mode 100644 frontend/src/views/error/403/index.vue delete mode 100644 frontend/src/views/error/404/index.vue delete mode 100644 frontend/src/views/login/index.ts delete mode 100644 frontend/src/views/login/index.vue delete mode 100644 frontend/src/views/messages/index.ts delete mode 100644 frontend/src/views/messages/index.vue delete mode 100644 frontend/src/views/settings/course/index.ts delete mode 100644 frontend/src/views/settings/course/index.vue delete mode 100644 frontend/src/views/settings/department/index.ts delete mode 100644 frontend/src/views/settings/department/index.vue delete mode 100644 frontend/src/views/settings/elective/index.ts delete mode 100644 frontend/src/views/settings/elective/index.vue delete mode 100644 frontend/src/views/settings/index.vue delete mode 100644 frontend/src/views/settings/major/index.ts delete mode 100644 frontend/src/views/settings/major/index.vue delete mode 100644 frontend/src/views/settings/myElective/index.ts delete mode 100644 frontend/src/views/settings/myElective/index.vue delete mode 100644 frontend/src/views/settings/myTaught/index.ts delete mode 100644 frontend/src/views/settings/myTaught/index.vue delete mode 100644 frontend/src/views/settings/student/index.ts delete mode 100644 frontend/src/views/settings/student/index.vue delete mode 100644 frontend/src/views/settings/taught/index.ts delete mode 100644 frontend/src/views/settings/taught/index.vue delete mode 100644 frontend/src/views/settings/teacher/index.ts delete mode 100644 frontend/src/views/settings/teacher/index.vue delete mode 100644 frontend/src/views/user/index.ts delete mode 100644 frontend/src/views/user/index.vue delete mode 100644 frontend/tsconfig.json delete mode 100644 frontend/tsconfig.vite-config.json delete mode 100644 frontend/vite.config.ts delete mode 100644 redis&&nginx.md diff --git a/README.md b/README.md index 3713112..5beab7e 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,17 @@ -# 学生选课系统 +## Vue3+FastAPI 小 demo -## 预览 +### frontend -+ [Vue3++TS+ElementPlus+Vite](http://8.136.82.204:8001/) -+ [FastAPI接口预览](http://8.136.82.204:8000/) -+ 🎉🎉🎉感谢 [**wendingming**](https://gitee.com/wendingming) 整理的 [项目部署的准备工作](https://gitee.com/zxiaosi/fast-api/issues/I4V6WV) -+ 🎉🎉🎉感谢 [**dreamrise**](https://gitee.com/dreamrise) 整理的 [运行配置介绍](https://gitee.com/zxiaosi/fast-api/issues/I56HPN) +- 框架:[Vue3.js](https://staging-cn.vuejs.org/) +- UI 库:[Naive UI](https://www.naiveui.com/zh-CN/os-theme) _待定_ -## 安装 +### backend -+ **后端安装**:[FastAPI](https://gitee.com/zxiaosi/fast-api/tree/master/backend#安装)(代码参考[CharmCode](https://www.charmcode.cn/category/FastAPI?page=1)) -+ **前端安装**:[Vue3+Ts](https://gitee.com/zxiaosi/fast-api/tree/master/frontend#安装) (代码参考[Vue-Manage-System](https://github.com/lin-xin/vue-manage-system)) +- 框架:[FastAPI](https://fastapi.tiangolo.com/zh/) +- ORM:[Tortoise ORM ](https://tortoise.github.io/) _待定_ +- Redis:[aioredis](https://aioredis.readthedocs.io/en/latest/) -## 版本 +### deploy -+ `1.0` 测试数据的增删改查已完成 -+ `1.1` 院系表的增删改查已完成(见`信息表格`) -+ `1.2` 首页仪表盘信息的优化 -+ `1.3` 院系表的增删改查初步完成 -+ `1.4` 整理代码 -+ `1.5` 添加了教师表 -+ `1.6` 添加了学生表、课程表、选课表 -+ `1.7` 重构前端代码 -+ `1.8` 封装组件,取出冗余代码 -+ `1.9` 自定义表格组件 -+ `2.0` 部署项目 -+ `2.1` 重构FastAPI -+ `2.2` 配置nginx以及SSL证书(域名未备案,ssl证书未生效) -+ `2.3` 添加Redis -+ `2.4` 加入TS -+ `2.5` 支持PostgreSQL,实现图片上传 -+ `2.6` 前端文件分离(vue与ts),后端实现权限管理 -+ `2.7` 简单实现权限管理 -+ `2.8` 调整数据库结构&&简单实现学生选课 -+ `2.9` 简单实现教师讲授课程 - ->TODO:优化代码 - -## 开启服务 - -1. 后端 - - + 进入到 `backend` 项目下 - + 找到 `main.py` 右键运行(建议用Pycharm启动) - - >接口文档:http://127.0.0.1:8000/docs - -2. 前端 - - + 进到 `frontend` 目录下 - + `npm run dev` 运行项目(建议用Vscode) - - >服务接口:http://localhost:3000/ - -3. 效果 - -+ 登录界面 - - + `用户名`:`admin` - - + `密码`:`123` - - + 如图 - - ![](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/frontend-login.png) - -+ 首页(假数据) - - ![home](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/home.png) - -+ 数据的`增` - - ![add](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/add.gif) - -+ 数据的`删` - - ![delete](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/delete.gif) - -+ 数据的`改` - - ![update](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/update.gif) - -+ 搜索数据 - - ![](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/search.gif) - -+ 多选删除 - - ![selectedDelete](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/selectedDelete.gif) +- nginx +- docker diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 9d18d6d..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# Python版本 -FROM python:3.9 - -# 工作路径 -WORKDIR /code - -# 方便下面使用缓存加载 -COPY ./requirements.txt /code/requirements.txt - -# 使用缓存下载安装包 -#RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -# 使用缓存下载安装包(镜像) -RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --default-timeout=1000 --no-cache-dir --upgrade -r /code/requirements.txt - -# docker部署(https://fastapi.tiangolo.com/zh/deployment/docker/) -# .表示同级目录下 -COPY . /code/ - -# 启动命令 -#CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] - -# main.py启动命令 -CMD ["python", "main.py"] \ No newline at end of file diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 74be6c0..0000000 --- a/backend/README.md +++ /dev/null @@ -1,194 +0,0 @@ -# FastAPI - -## 版本迭代 - -+ `V1.0` FastAPI学习 -+ `V2.0` 搭建FastAPI脚手架 -+ `V2.1` 创建所需的表 -+ `V2.2` 已成功调试Mysql、Sqlite, 未调试Postgresql -+ `V2.3` 初始化表数据(调试) -+ `V2.4` 优化创建表问题 -+ `V2.5` 初始化所有表数据 -+ `V2.6` 封装日志模块 -+ `V2.7` 封装多进程日志模块(线程锁) -+ `V2.8` 优化了目录结构 -+ `V2.9` 优化代码&&调试了user表的增删查接口 -+ `V3.0` 添加了防止跨域请求代码&&调试了接口 -+ `V3.1` 去除了测试表的自增 -+ `V3.2` 添加了department表的接口 -+ `V3.3` 添加了major表的接口 -+ `V3.4` 更新了major表接口的部分代码 -+ `V3.5` 更换了日志模块(loguru)&&添加了后端数据验证 -+ `V3.5` 添加了teacher表的接口 -+ `V3.6` 添加了student表的接口 -+ `V3.7` 添加了course表和selectCourse表的接口 -+ `V3.8` 更新了所需的包 -+ `V3.9` 修改了查询单个信息的数据 -+ `V4.0` 删除了获取所有数据以及获取单个数据的方法 -+ `V4.1` 修改了校验规则 -+ `V4.1.1` 删除了整型和浮点型的正则校验规则 -+ `v4.2` 尝试部署中。。。 -+ `v4.3` 部署成功,修复了部分bug -+ `v4.4` 测试token -+ `v4.5` 调试token成功(admin, 123) -+ `v4.6` 重构FastAPI -+ `v4.7` 添加redis -+ `v4.8` 重构后端 -+ `v4.9` 支持PostgreSQL,以及图片上传 -+ `v5.0` 后台实现权限管理模块 -+ `v5.1` 整理接口,简单实现权限管理模块 -+ `v5.2` 调整数据库结构 - -## 安装 - -1. 配置Python3.9(及以上)的虚拟环境 - -2. 安装运行所需的包 - - ```python - # 默认装了Mysql与ProgreSQL - pip install requirements.txt - - # 或者 - pip install fastapi - pip install uvicorn[fastapi] - pip install loguru - pip install SQLAlchemy - pip install aioredis - pip install python-jose - pip install passlib - pip install bcrypt - pip install python-multipart - pip install orjson - - # 使用Sqlite(异步), 请安装下面包 - pip install aiosqlite - - # 使用MySQL(异步), 请安装下面包 - pip install asyncmy - - # 使用ProgreSQL(异步), 请安装下面包 - pip install asyncpg - ``` - - -3. 启动服务 - - + 进入到 `backend` 项目下 - + 找到 `main.py` 右键运行 - + `core/config` 配置文件(默认数据库是sqlite) - - > 接口文档:http://127.0.0.1:8000/docs - -## 项目截图 - -+ 成功运行的图片 - - ![](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/image-20211021164103094.png) - -+ 接口图 - - ![](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/backend-%E6%8E%A5%E5%8F%A3.png) - -## 项目目录(待整理) - -```sh -|-- backend - - |-- api # 接口 - |-- admin - |-- __init__.py # 管理员接口 - |-- course.py # 课程表接口 - |-- department.py # 院系表接口 - |-- major.py # 专业表接口 - |-- index.py # 管理员首页 - |-- elective.py # 选课表接口 - |-- student.py # 学生表接口 - |-- teacher.py # 教师表接口 - |-- common - |-- __init__.py # 共用接口 - |-- login.py # 登录接口 - |-- redis_check.py # 检查redis是否连接成功 - |-- upload.py # 图片上传 - |-- __init__.py - |-- deps.py # 依赖项 - |-- api_router.py # admin接口汇总 - - |-- core - |-- __init__.py # 核心内容 - |-- config.py # 配置文件 - |-- security.py # 安全配置 - - |-- crud - |-- __init__.py # 数据库的增删改查操作 - |-- base.py # 封装数据库增删改查方法 - |-- course.py # 课程表 - |-- department.py # 院系表 - |-- major.py # 专业表 - |-- elective.py # 选课表 - |-- teacher.py # 教师表 - |-- student.py # 学生表 - - |-- db - |-- __init__.py # 初始数据库以及表数据 - |-- data.py # 所有数据 - |-- init_data.py # 两种初始化表数据的方式 - |-- init_db.py # 创建和删除base中的表 - |-- session.py # 创建数据库连接会话 - |-- redis.py # 注册Redis - - |-- logs # 日志模块(自动生成) - |-- 2021-10-06_23-46-45.log - |-- 2021-10-06_23-46-47.log - |-- 2021-10-06_23-46-49.log - - |-- models - |-- __init__.py # ORM模型映射 - |-- base.py # 自动生成 表名 - |-- index.py # 管理员表 - |-- course.py # 课程表 - |-- department.py # 院系表 - |-- major.py # 专业表 - |-- elective.py # 选课表 - |-- student.py # 学生表 - |-- teacher.py # 教师表 - - |-- register - |-- __init__.py # 注册中心 - |-- cors.py # 注册跨域请求 - |-- exception.py # 注册全局异常 - |-- middleware.py # 注册请求响应拦截 - |-- router.py # 注册路由 - - |-- schemas - |-- __init__.py # 数据模型 - |-- admin.py # 管理员表模型 - |-- common.py # 公用表模型 - |-- course.py # 课程表模型 - |-- department.py # 院系表模型 - |-- login.py # 登录模型 - |-- major.py # 专业表模型 - |-- result.py # 返回数据模型 - |-- elective.py # 选课表模型 - |-- student.py # 学生表模型 - |-- teacher.py # 教师表模型 - |-- todo.py # 待办模型 - |-- token.py # token模型 - - |-- utils # 工具 - |-- __init__.py # 抛出工具类 - |-- create_dir.py # 创建文件夹类(位置勿动) - |-- custon_exc.py # 自定义异常 - |-- ip_address.py # 根据ip获取位置 - |-- logger.py # 日志模块 - |-- permission_assign.py # 权限管理 - |-- resp_code.py # 状态码 - - |-- __init__.py - |-- main.py # 主程序 - |-- Dockerfile # Dockerfile文件 - |-- README.md # Readme文件 - |-- requirements.txt # 所需的包 - |-- sql_app.db # sqlite数据库 -``` - diff --git a/backend/apis/__init__.py b/backend/apis/__init__.py index fdb36b7..9803b20 100644 --- a/backend/apis/__init__.py +++ b/backend/apis/__init__.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 # _*_ coding: utf-8 _*_ -# @Time : 2021/10/15 19:57 # @Author : zxiaosi -# @desc : 所有接口 -from .api_router import app_router +# @Time : 2022/7/1 14:41 +# @desc : diff --git a/backend/apis/api_router.py b/backend/apis/api_router.py deleted file mode 100644 index b7ce22f..0000000 --- a/backend/apis/api_router.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/10/15 20:01 -# @Author : zxiaosi -# @desc : 接口汇总 -from fastapi import APIRouter - -from apis.common import upload, student, teacher, department, major, course, taught, elective - -app_router = APIRouter() - -# include_in_schema=False 隐藏属性 -# deprecated=True 弃用属性 - -# 上传图片 -app_router.include_router(upload.router, tags=["Upload"]) - -# common -app_router.include_router(elective.router, prefix="/elective", tags=["elective"]) -app_router.include_router(department.router, prefix="/department", tags=["Department"]) -app_router.include_router(major.router, prefix="/major", tags=["major"]) -app_router.include_router(course.router, prefix="/course", tags=["course"]) -app_router.include_router(teacher.router, prefix="/teacher", tags=["teacher"]) -app_router.include_router(student.router, prefix="/student", tags=["student"]) -app_router.include_router(taught.router, prefix="/taught", tags=["taught"]) - - -# Admin - -# teacher - -# student diff --git a/backend/apis/common/__init__.py b/backend/apis/common/__init__.py deleted file mode 100644 index 0f878d9..0000000 --- a/backend/apis/common/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/2/24 9:43 -# @Author : zxiaosi -# @desc : 共用接口 diff --git a/backend/apis/common/course.py b/backend/apis/common/course.py deleted file mode 100644 index 845e1ec..0000000 --- a/backend/apis/common/course.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/17 16:42 -# @Author : zxiaosi -# @desc : 课程表接口 -from typing import List -from fastapi import APIRouter, Depends, Security -from sqlalchemy.ext.asyncio import AsyncSession - -from apis.deps import get_db, get_current_user -from schemas import CourseCreate, CourseUpdate, CourseOut as Course, Result, ResultPlus -from crud import course -from utils import resp_200, IdNotExist - -router = APIRouter() - - -@router.get("/{id}", response_model=Result[Course], summary='根据 id 查询课程信息') -async def read_course(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=[])): - _course = await course.get(db, id) - if not _course: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的课程.") - return resp_200(data=_course, msg=f"查询到了 id 为 {id} 的课程.") - - -@router.get("/", response_model=ResultPlus[Course], summary='根据页码 pageIndex 和每页个数 pageSize 查询所有课程') -async def read_courses(pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - """ 查询所有院系 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await course.get_number(db) - _courses = await course.get_multi(db, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _courses}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个课程信息.") - - -@router.post("/", response_model=Result, summary='添加课程信息') -async def create_course(course_in: CourseCreate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - await course.create(db, obj_in=course_in) - return resp_200(msg=f"添加了 id 为 {course_in.id} 的课程信息.") - - -@router.put("/{id}", response_model=Result, summary='通过 id 更新课程信息') -async def update_course(id: int, course_in: CourseUpdate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - rowcount = await course.update(db, id=id, obj_in=course_in) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的课程.") - return resp_200(msg=f"更新了 id 为 {id} 的课程信息.") - - -@router.delete("/{id}", response_model=Result, summary='通过 id 删除课程信息') -async def delete_course(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=["admin"])): - rowcount = await course.remove(db, id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的课程.") - return resp_200(msg=f"删除了 id 为 {id} 的课程信息.") - - -@router.post("/del/", response_model=Result, summary='同时删除多个课程信息') -async def delete_courses(idList: List, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - rowcount = await course.remove_multi(db, id_list=idList) - if not rowcount: - raise IdNotExist(err_desc="系统中不存在列表中的id.") - return resp_200(msg='成功删除多个课程信息.') diff --git a/backend/apis/common/dashboard.py b/backend/apis/common/dashboard.py deleted file mode 100644 index 69b7915..0000000 --- a/backend/apis/common/dashboard.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/17 15:15 -# @Author : zxiaosi -# @desc : 首页 -import json -from fastapi import APIRouter, Depends -from fastapi.responses import ORJSONResponse - -from db import MyRedis -from apis.deps import get_redis -from schemas import Todo, TodoId -from utils import resp_200 - -router = APIRouter() - - -@router.get("/dashboard", response_class=ORJSONResponse, summary='访问量 && 待办数 && 请求数 && 待办事项') -async def get_visit_todo_request(redis: MyRedis = Depends(get_redis)): - """ 查询首页数据(访问量 && 待办事项 && 请求数 && 待办事项) """ - visit_num = await redis.get('visit_num') - todo_num = await redis.llen('todo_list') - request_num = await redis.get('request_num') - todo_list = await redis.list_loads('todo_list', 6) - data = {'visit_num': visit_num, 'todo_num': todo_num, 'request_num': request_num, 'todo_list': todo_list} - return resp_200(data=data, msg='查询了访问量 && 待办事项 && 请求数 && 待办事项') - - -@router.post("/todo/add", summary='添加待办') -async def add_todo(todo_in: Todo, redis: MyRedis = Depends(get_redis)): - """ 添加待办 """ - text = {'title': todo_in.title, 'status': todo_in.status} - await redis.cus_lpush('todo_list', text) - return resp_200(msg='添加待办成功!') - - -@router.post("/todo/update", response_class=ORJSONResponse, summary='根据索引更新待办') -async def update_todo(todo_in: TodoId, redis: MyRedis = Depends(get_redis)): - """ 更新待办 """ - obj = await redis.get_list_by_index('todo_list', todo_in.id) - obj["status"] = bool(1 - obj["status"]) # noqa 取反 - await redis.lset('todo_list', todo_in.id, json.dumps(obj)) - return resp_200(msg='更新待办成功!') diff --git a/backend/apis/common/department.py b/backend/apis/common/department.py deleted file mode 100644 index 8bf2251..0000000 --- a/backend/apis/common/department.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/10/28 19:18 -# @Author : zxiaosi -# @desc : 院系表接口 -from typing import List - -from fastapi import APIRouter, Depends, Security -from sqlalchemy.ext.asyncio import AsyncSession - -from apis.deps import get_db, get_current_user -from schemas import DepartmentUpdate, DepartmentCreate, DepartmentOut as Department, Result, ResultPlus -from crud import department -from utils import resp_200, IdNotExist - -router = APIRouter() - - -@router.get("/{id}", response_model=Result[Department], summary='根据 id 查询院系信息') -async def read_department(id: int, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - _department = await department.get(db, id) - if not _department: - raise IdNotExist(f"系统中不存在 id 为 {id} 的院系.") - return resp_200(data=_department, msg=f"查询到了 id 为 {id} 的院系.") - - -@router.get("/", response_model=ResultPlus[Department], summary='根据页码 pageIndex 和每页个数 pageSize 查询所有院系') -async def read_departments(pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - """ 查询所有院系 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await department.get_number(db) - _departments = await department.get_multi(db, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _departments}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个院系信息.") - - -@router.post("/", response_model=Result, summary='添加院系信息') -async def create_department(department_in: DepartmentCreate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - await department.create(db, obj_in=department_in) - return resp_200(msg=f"添加了 id 为 {department_in.id} 的院系信息.") - - -@router.put("/{id}", response_model=Result, summary='通过 id 更新院系信息') -async def update_department(id: int, department_in: DepartmentUpdate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - rowcount = await department.update(db, id=id, obj_in=department_in) - if not rowcount: # 每次更新, 当前数据的更新时间会变, 只要id存在, 就会一直返回1 - raise IdNotExist(f"系统中不存在 id 为 {id} 的院系.") - return resp_200(msg=f"更新了 id 为 {id} 的院系信息.") - - -@router.delete("/{id}", response_model=Result, summary='通过 id 删除院系信息') -async def delete_department(id: int, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - rowcount = await department.remove(db, id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的院系.") - return resp_200(msg=f'成功删除 id 为 {id} 的院系信息.') - - -@router.post("/del/", response_model=Result, summary='同时删除多个院系信息') -async def delete_departments(idList: List, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - rowcount = await department.remove_multi(db, id_list=idList) - if not rowcount: - raise IdNotExist(err_desc="系统中不存在列表中的id.") - return resp_200(msg='成功删除多个院系信息.') - - -@router.get("/sort/{name}", summary='根据字段排序') -async def get_select_courses(name: str, pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db)): - """ 查询所有院系 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await department.get_number(db) - _departments = await department.sort(db, name, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _departments}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个院系信息.") diff --git a/backend/apis/common/elective.py b/backend/apis/common/elective.py deleted file mode 100644 index 4e72e44..0000000 --- a/backend/apis/common/elective.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/18 10:25 -# @Author : zxiaosi -# @desc : 选课表接口 -from typing import List -from fastapi import APIRouter, Depends, Security -from sqlalchemy.ext.asyncio import AsyncSession - -from apis.deps import get_db, get_current_user -from models import Student -from schemas import ElectiveCreate, ElectiveUpdate, ElectiveOut as Elective, Result, ResultPlus -from crud import elective -from utils import resp_200, IdNotExist - -router = APIRouter() - - -@router.get("/{id}", response_model=Result[Elective], summary='根据 id 查询选课信息') -async def read_elective(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=[])): - _elective = await elective.get(db, id) - if not _elective: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的选课.") - return resp_200(data=_elective, msg=f"查询到了 id 为 {id} 的选课.") - - -@router.get("/", response_model=ResultPlus[Elective], summary='根据页码 pageIndex 和每页个数 pageSize 查询所有选课') -async def read_electives(pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - """ 查询所有院系 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await elective.get_number(db) - _electives = await elective.get_multi(db, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _electives}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个选课信息.") - - -@router.post("/", response_model=Result, summary='添加选课信息') -async def create_elective(elective_in: ElectiveCreate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - await elective.create(db, obj_in=elective_in) - return resp_200(msg='添加了选课信息.') - - -@router.put("/{id}", response_model=Result, summary='通过 id 更新选课信息') -async def update_elective(id: int, elective_in: ElectiveUpdate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - rowcount = await elective.update(db, id=id, obj_in=elective_in) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的选课.") - return resp_200(msg=f"更新了 id 为 {id} 的选课信息.") - - -@router.delete("/{id}", response_model=Result, summary='通过 id 删除选课信息') -async def delete_elective(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=[])): - rowcount = await elective.remove(db, id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的选课.") - return resp_200(msg=f"删除了 id 为 {id} 的选课信息.") - - -@router.post("/del/", response_model=Result, summary='同时删除多个选课信息') -async def delete_electives(idList: List, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - rowcount = await elective.remove_multi(db, id_list=idList) - if not rowcount: - raise IdNotExist(err_desc="系统中不存在列表中的id.") - return resp_200(msg='成功删除多个选课信息.') - - -@router.post("/add/{courseId}", response_model=Result, summary='添加选课信息') -async def create_elective_by_course_id(courseId: int, db: AsyncSession = Depends(get_db), - user: Student = Security(get_current_user, scopes=[])): - obj = await elective.is_exist(db, courseId=courseId, studentId=user.id) - if obj: - data, msg = 0, '数据已存在.' - else: - data, msg = 1, '添加了选课信息.' - await elective.create(db, obj_in={'grade': 0, 'studentId': user.id, 'courseId': courseId}) - return resp_200(data=data, msg=msg) - - -@router.post("/del/{courseId}", response_model=Result, summary='通过其他字段删除选课信息') -async def del_elective_by_filed(courseId: int, db: AsyncSession = Depends(get_db), - user: Student = Security(get_current_user, scopes=[])): - obj = await elective.is_exist(db, courseId=courseId, studentId=user.id) - if obj: - data, msg = 1, '数据存在, 退课成功' - rowcount = await elective.remove(db, id=obj.id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的选课.") - else: - data, msg = 0, '数据不存在, 退课失败!' - return resp_200(data=data, msg=msg) - - -@router.get("/detail/", response_model=Result, summary='获取学生选课信息详情') -async def get_course_detail(db: AsyncSession = Depends(get_db), user: Student = Security(get_current_user, scopes=[])): - data = await elective.get_course(db, id=user.id) - return resp_200(data=data, msg='获取学生选课信息详情.') diff --git a/backend/apis/common/login.py b/backend/apis/common/login.py deleted file mode 100644 index 729219c..0000000 --- a/backend/apis/common/login.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/12/28 19:16 -# @Author : zxiaosi -# @desc : 登录 -import json -from datetime import timedelta - -from sqlalchemy.ext.asyncio import AsyncSession -from fastapi.encoders import jsonable_encoder -from fastapi import APIRouter, Depends, Request, Security -from fastapi.security import OAuth2PasswordRequestForm - -from core import settings, create_access_token -from db import MyRedis -from models import Admin, Teacher, Student -from schemas import Result, Token, AdminOut, TeacherOut, StudentOut -from apis.deps import get_redis, get_db, get_current_user -from utils import resp_200, SetRedis, ErrorUser, by_ip_get_address -from utils.permission_assign import by_scopes_get_crud - -router = APIRouter() - - -# 用户登录推荐看一下这个库: https://fastapi-users.github.io/fastapi-users/ -# 这里的登录借助的是OAuth2,存储用的JWT,与redis存储的token已经没有关系 (前端请求要发送 表单请求) -# OAuth2中token过期时间与 设置的时间 以及 服务的开启关闭 有关, 时间到期或者服务关闭token过期 -@router.post("/login", response_model=Token, summary="docs接口文档登录 && 登录接口") -async def login_access_token( - request: Request, - db: AsyncSession = Depends(get_db), - form_data: OAuth2PasswordRequestForm = Depends() -): - """ 兼容OAuth2的令牌登录,为接口文档的请求获取访问令牌 """ - crud_obj = by_scopes_get_crud(form_data.scopes) # 权限分配 - _user = await crud_obj.authenticate(db, username=form_data.username, password=form_data.password) - if not _user: - raise ErrorUser() - - address = by_ip_get_address(request.client.host) # 根据ip获取地址 - await crud_obj.update(db, id=_user.id, obj_in={'address': address}) - - access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - token = create_access_token({"sub": str(_user.id), "scopes": form_data.scopes}, access_token_expires) - - try: - await request.app.state.redis.incr('visit_num') # 用户访问量 自增1 - await request.app.state.redis.set(token, json.dumps(jsonable_encoder(_user)), access_token_expires) - except Exception as e: - raise SetRedis(f'Redis存储 token 失败!-- {e}') - - # 这里'access_token'和'token_type'一定要写,否则get_current_user依赖拿不到token - # 可添加字段(先修改schemas/token里面的Token返回模型) - return {"access_token": token, "token_type": "bearer"} - - -@router.get("/admin/index", response_model=Result[AdminOut], summary="获取当前管理员") -def get_current_admin(current_user: Admin = Security(get_current_user, scopes=["admin"])): - return resp_200(data=jsonable_encoder(current_user), msg='获取当前管理员信息!') - - -@router.get("/teacher/index", response_model=Result[TeacherOut], summary="获取当前教师") -def get_current_teacher(current_user: Teacher = Security(get_current_user, scopes=["teacher"])): - return resp_200(data=jsonable_encoder(current_user), msg='获取当前教师信息!') - - -@router.get("/student/index", response_model=Result[StudentOut], summary="获取当前学生") -def get_current_student(current_user: Student = Security(get_current_user, scopes=["student"])): - return resp_200(data=jsonable_encoder(current_user), msg='获取当前学生信息!') - - -@router.post("/logout", response_model=Result, summary="退出登录(已隐藏)", include_in_schema=False) -async def logout_token(request: Request, redis: MyRedis = Depends(get_redis)): - if 'authorization' in request.headers.keys(): - token = request.headers.get('authorization')[7:] # 去除token前面的 Bearer - await redis.delete(token) - return resp_200(msg='退出登录') diff --git a/backend/apis/common/major.py b/backend/apis/common/major.py deleted file mode 100644 index 0ed3ee4..0000000 --- a/backend/apis/common/major.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/1 21:17 -# @Author : zxiaosi -# @desc : 专业表接口 -from typing import List - -from fastapi import APIRouter, Depends, Security -from sqlalchemy.ext.asyncio import AsyncSession - -from apis.deps import get_db, get_current_user -from schemas import MajorCreate, MajorUpdate, MajorOut as Major, Result, ResultPlus -from crud import major -from utils import resp_200, IdNotExist - -router = APIRouter() - - -@router.get("/{id}", response_model=Result[Major], summary='根据 id 查询专业信息') -async def read_major(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=[])): - _major = await major.get(db, id) - if not _major: - raise IdNotExist(f"系统中不存在 id 为 {id} 的专业.") - return resp_200(data=_major, msg=f"查询到了 id 为 {id} 的专业.") - - -@router.get("/", response_model=ResultPlus[Major], summary='根据页码 pageIndex 和每页个数 pageSize 查询所有专业') -async def read_majors(pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - """ 查询所有专业 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await major.get_number(db) - _majors = await major.get_multi(db, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _majors}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个专业信息.") - - -@router.post("/", response_model=Result, summary='添加专业信息') -async def create_major(major_in: MajorCreate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - await major.create(db, obj_in=major_in) - return resp_200(msg=f"添加了 id 为 {major_in.id} 的专业信息.") - - -@router.put("/{id}", response_model=Result, summary='通过 id 更新专业信息') -async def update_major(id: int, major_in: MajorUpdate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - rowcount = await major.update(db, id=id, obj_in=major_in) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的专业.") - return resp_200(msg=f"更新了 id 为 {id} 的专业信息.") - - -@router.delete("/{id}", response_model=Result, summary='通过 id 删除专业信息') -async def delete_major(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=["admin"])): - rowcount = await major.remove(db, id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的专业.") - return resp_200(msg=f"删除了 id 为 {id} 的专业信息.") - - -@router.post("/del/", response_model=Result, summary='同时删除多个专业信息') -async def delete_majors(idList: List, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=["admin"])): - rowcount = await major.remove_multi(db, id_list=idList) - if not rowcount: - raise IdNotExist(err_desc="系统中不存在列表中的id.") - return resp_200(msg='同时删除多个专业信息.') diff --git a/backend/apis/common/redis_check.py b/backend/apis/common/redis_check.py deleted file mode 100644 index 86ab789..0000000 --- a/backend/apis/common/redis_check.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/5 13:55 -# @Author : zxiaosi -# @desc : 检查redis -from fastapi import APIRouter, Depends - -from db import MyRedis -from apis.deps import get_redis - -router = APIRouter() - - -@router.get("/health-check") -async def health_check(redis: MyRedis = Depends(get_redis)): - try: - # key:test:keys => key/test/keys - value = await redis.get('request_num') - msg = "开启Redis成功!!!" - except Exception as e: # noqa: E722 - value = 0 - msg = f"对不起,不能打开Redis!!! {e}" - return {"request": f"总计请求次数 {value}", "msg": msg} diff --git a/backend/apis/common/student.py b/backend/apis/common/student.py deleted file mode 100644 index 23c781d..0000000 --- a/backend/apis/common/student.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/17 11:12 -# @Author : zxiaosi -# @desc : 学生表接口 -from typing import List -from fastapi import APIRouter, Depends, Security -from sqlalchemy.ext.asyncio import AsyncSession - -from apis.deps import get_db, get_current_user -from schemas import StudentCreate, StudentUpdate, StudentOut as Student, Result, ResultPlus -from crud import student -from utils import resp_200, IdNotExist - -router = APIRouter() - - -@router.get("/{id}", response_model=Result[Student], summary='根据 id 查询学生信息') -async def read_student(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=[])): - _student = await student.get(db, id) - if not _student: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的学生.") - return resp_200(data=_student, msg=f"查询到了 id 为 {id} 的学生.") - - -@router.get("/", response_model=ResultPlus[Student], summary='根据页码 pageIndex 和每页个数 pageSize 查询所有学生') -async def read_students(pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - """ 查询所有院系 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await student.get_number(db) - _students = await student.get_multi(db, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _students}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个学生信息.") - - -@router.post("/", response_model=Result, summary='添加学生信息') -async def create_student(student_in: StudentCreate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - await student.create(db, obj_in=student_in) - return resp_200(msg=f"添加了 id 为 {student_in.id} 的学生信息.") - - -@router.put("/{id}", response_model=Result, summary='通过 id 更新学生信息') -async def update_student(id: int, student_in: StudentUpdate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await student.update(db, id=id, obj_in=student_in) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的学生.") - return resp_200(msg=f"更新了 id 为 {id} 的学生信息.") - - -@router.delete("/{id}", response_model=Result, summary='通过 id 删除学生信息') -async def delete_student(id: int, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await student.remove(db, id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的学生.") - return resp_200(msg=f"删除了 id 为 {id} 的学生信息.") - - -@router.post("/del/", response_model=Result, summary='同时删除多个学生信息') -async def delete_students(idList: List, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await student.remove_multi(db, id_list=idList) - if not rowcount: - raise IdNotExist(err_desc="系统中不存在列表中的id.") - return resp_200(msg='成功删除多个学生信息.') - - -# @router.get("/detail/{id}", summary='得到详细信息') -# async def get_detail(id: int, db: AsyncSession = Depends(get_db)): -# _data = await student.get_detail(db, id) -# return _data diff --git a/backend/apis/common/taught.py b/backend/apis/common/taught.py deleted file mode 100644 index 28f6059..0000000 --- a/backend/apis/common/taught.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/5/10 21:08 -# @Author : zxiaosi -# @desc : 讲授表接口 -from typing import List -from fastapi import APIRouter, Depends, Security -from sqlalchemy.ext.asyncio import AsyncSession - -from apis.deps import get_db, get_current_user -from models import Teacher -from schemas import TaughtCreate, TaughtUpdate, TaughtOut as Taught, Result, ResultPlus -from crud import taught -from utils import resp_200, IdNotExist - -router = APIRouter() - - -@router.get("/{id}", response_model=Result[Taught], summary='根据 id 查询讲授信息') -async def read_taught(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=[])): - _taught = await taught.get(db, id) - if not _taught: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的讲授.") - return resp_200(data=_taught, msg=f"查询到了 id 为 {id} 的讲授.") - - -@router.get("/", response_model=ResultPlus[Taught], summary='根据页码 pageIndex 和每页个数 pageSize 查询所有讲授') -async def read_taughts(pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - """ 查询所有院系 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await taught.get_number(db) - _taughts = await taught.get_multi(db, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _taughts}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个讲授信息.") - - -@router.post("/", response_model=Result, summary='添加讲授信息') -async def create_taught(taught_in: TaughtCreate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - await taught.create(db, obj_in=taught_in) - return resp_200(msg="添加了讲授信息.") - - -@router.put("/{id}", response_model=Result, summary='通过 id 更新讲授信息') -async def update_taught(id: int, taught_in: TaughtUpdate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await taught.update(db, id=id, obj_in=taught_in) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的讲授.") - return resp_200(msg=f"更新了 id 为 {id} 的讲授信息.") - - -@router.delete("/{id}", response_model=Result, summary='通过 id 删除讲授信息') -async def delete_taught(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=['admin'])): - rowcount = await taught.remove(db, id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的讲授.") - return resp_200(msg=f"删除了 id 为 {id} 的讲授信息.") - - -@router.post("/del/", response_model=Result, summary='同时删除多个讲授信息') -async def delete_taughts(idList: List, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await taught.remove_multi(db, id_list=idList) - if not rowcount: - raise IdNotExist(err_desc="系统中不存在列表中的id.") - return resp_200(msg='成功删除多个讲授信息.') - - -@router.get("/detail/", response_model=Result, summary='获取教师讲授信息详情') -async def get_course_detail(db: AsyncSession = Depends(get_db), user: Teacher = Security(get_current_user, scopes=[])): - data = await taught.get_course(db, id=user.id) - return resp_200(data=data, msg='获取教师讲授信息详情.') diff --git a/backend/apis/common/teacher.py b/backend/apis/common/teacher.py deleted file mode 100644 index 29cc25a..0000000 --- a/backend/apis/common/teacher.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/16 9:48 -# @Author : zxiaosi -# @desc : 教师表接口 -from typing import List - -from fastapi import APIRouter, Depends, Security -from sqlalchemy.ext.asyncio import AsyncSession - -from apis.deps import get_db, get_current_user -from schemas import TeacherCreate, TeacherUpdate, TeacherOut as Teacher, Result, ResultPlus -from crud import teacher -from utils import resp_200, IdNotExist - -router = APIRouter() - - -@router.get("/{id}", response_model=Result[Teacher], summary='根据 id 查询教师信息') -async def read_teacher(id: int, db: AsyncSession = Depends(get_db), user=Security(get_current_user, scopes=[])): - _teacher = await teacher.get(db, id) - if not _teacher: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的教师.") - return resp_200(data=_teacher, msg=f"查询到了 id 为 {id} 的教师.") - - -@router.get("/", response_model=ResultPlus[Teacher], summary='根据页码 pageIndex 和每页个数 pageSize 查询所有教师') -async def read_teachers(pageIndex: int = 1, pageSize: int = 10, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=[])): - """ 查询所有教师 (pageIndex = -1 && pageSize = -1 表示查询所有) """ - _count = await teacher.get_number(db) - _teachers = await teacher.get_multi(db, pageIndex, pageSize) - return resp_200(data={"count": _count, "list": _teachers}, msg=f"查询了第 {pageIndex} 页中的 {pageSize} 个教师信息.") - - -@router.post("/", response_model=Result, summary='添加教师信息') -async def create_teacher(teacher_in: TeacherCreate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - await teacher.create(db, obj_in=teacher_in) - return resp_200(msg=f"添加了 id 为 {teacher_in.id} 的教师信息.") - - -@router.put("/{id}", response_model=Result, summary='通过 id 更新教师信息') -async def update_teacher(id: int, teacher_in: TeacherUpdate, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await teacher.update(db, id=id, obj_in=teacher_in) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的教师.") - return resp_200(msg=f"更新了 id 为 {id} 的教师信息.") - - -@router.delete("/{id}", response_model=Result, summary='通过 id 删除教师信息') -async def delete_teacher(id: int, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await teacher.remove(db, id) - if not rowcount: - raise IdNotExist(err_desc=f"系统中不存在 id 为 {id} 的教师.") - return resp_200(msg=f"删除了 id 为 {id} 的教师信息.") - - -@router.post("/del/", response_model=Result, summary='同时删除多个教师信息') -async def delete_teachers(idList: List, db: AsyncSession = Depends(get_db), - user=Security(get_current_user, scopes=['admin'])): - rowcount = await teacher.remove_multi(db, id_list=idList) - if not rowcount: - raise IdNotExist(err_desc="系统中不存在列表中的id.") - return resp_200(msg='成功删除多个教师信息.') diff --git a/backend/apis/common/upload.py b/backend/apis/common/upload.py deleted file mode 100644 index 191a372..0000000 --- a/backend/apis/common/upload.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/3/4 11:42 -# @Author : zxiaosi -# @desc : 上传图片 https://fastapi.tiangolo.com/zh/tutorial/request-files/?h=up#uploadfile -import shutil -from pathlib import Path -from tempfile import NamedTemporaryFile -from fastapi import APIRouter, Depends, File, UploadFile -from sqlalchemy.orm import Session - -import crud -from core import settings -from core.logger import logger -from apis.deps import get_db, get_current_user -from models import Admin -from utils import resp_200 -from utils.create_dir import create_dir - -router = APIRouter() - - -@router.post("/upload/file/", summary="上传头像") -async def upload_image( - file: UploadFile = File(...), - db: Session = Depends(get_db), - current_user: Admin = Depends(get_current_user) -): - static_path = create_dir(settings.STATIC_DIR) - logger.info(f"用户 {current_user.name} 正在上传图片 {file.filename}.") - try: - suffix = Path(file.filename).suffix - with NamedTemporaryFile(delete=False, suffix=suffix, dir=static_path) as tmp: - shutil.copyfileobj(file.file, tmp) - tmp_file_name = Path(tmp.name).name - finally: - file.file.close() - user = crud.admin.update(db, db_obj=current_user, - obj_in={'image': f"{settings.BASE_URL}/{settings.STATIC_DIR}/{tmp_file_name}"}) - return resp_200(data=user, msg='上传成功!') diff --git a/backend/apis/debug.py b/backend/apis/debug.py new file mode 100644 index 0000000..0871067 --- /dev/null +++ b/backend/apis/debug.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +# @Author : zxiaosi +# @Time : 2022/7/1 14:46 +# @desc : 调试接口 +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") +async def root(): + return {"message": "Hello World"} + + +@router.get("/hello/{name}") +async def say_hello(name: str): + return {"message": f"Hello {name}"} diff --git a/backend/apis/deps.py b/backend/apis/deps.py deleted file mode 100644 index 0c74d5a..0000000 --- a/backend/apis/deps.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/10/15 20:10 -# @Author : zxiaosi -# @desc : 依赖项 -from typing import AsyncGenerator - -from fastapi import Depends, Security, HTTPException -from fastapi.security import SecurityScopes, OAuth2PasswordBearer -from sqlalchemy.ext.asyncio import AsyncSession -from starlette.requests import Request - -from core import check_jwt_token, settings -from db import async_session, MyRedis -from schemas import TokenData -from utils import UserNotExist, PermissionNotEnough -from utils.permission_assign import by_scopes_get_crud, handle_oauth2_scopes - -get_token = OAuth2PasswordBearer(tokenUrl=f"{settings.API_PREFIX}/login", scopes=handle_oauth2_scopes()) - - -async def get_db() -> AsyncGenerator[AsyncSession, None]: - """ sql连接会话 """ - async with async_session() as session: - yield session - - -async def get_redis(request: Request) -> MyRedis: - """ redis连接对象 """ - return await request.app.state.redis - - -async def get_current_user( - security_scopes: SecurityScopes, - db: AsyncSession = Depends(get_db), - token: str = Depends(get_token) -): - """ 得到当前用户(docs接口文档) """ - payload = await check_jwt_token(token) # 检验token是否过期 - token_scopes = payload.get("scopes", []) # 得不到值,返回[] - token_data = TokenData(scopes=token_scopes, sub=payload.get("sub")) # token存储的用户权限 - crud_obj = by_scopes_get_crud(token_scopes) # 验证用户是否存在 - user = await crud_obj.get(db, id=payload.get("sub")) - if not user: - raise UserNotExist() - for scope in security_scopes.scopes: # 勾选的用户权限 - if scope not in token_data.scopes: - raise PermissionNotEnough('权限不足,拒绝访问') - return user - -# def get_current_active_user(current_user: Admin = Security(get_current_user, scopes=["admin"])): -# """ 得到当前登录用户 """ -# if not admin.is_active_def(current_user): -# raise HTTPException(status_code=400, detail="用户未登录!!!") -# return current_user diff --git a/backend/core/__init__.py b/backend/core/__init__.py index d32b14e..9803b20 100644 --- a/backend/core/__init__.py +++ b/backend/core/__init__.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 # _*_ coding: utf-8 _*_ -# @Time : 2021/10/18 19:35 # @Author : zxiaosi -# @desc : 核心文件 -from .config import settings -from .security import create_access_token, verify_password, get_password_hash, check_jwt_token +# @Time : 2022/7/1 14:41 +# @desc : diff --git a/backend/core/config.py b/backend/core/config.py index 251fcb8..785de3e 100644 --- a/backend/core/config.py +++ b/backend/core/config.py @@ -1,50 +1,16 @@ #!/usr/bin/env python3 # _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 17:02 # @Author : zxiaosi +# @Time : 2022/7/1 14:47 # @desc : 配置文件 -import secrets -from typing import Union, List -from pydantic import BaseSettings, AnyHttpUrl - -project_desc = """ - 🎉 管理员接口汇总 🎉 - ✨ 账号: admin ✨ - ✨ 密码: 123456 ✨ - ✨ 权限(scopes): admin ✨ -""" +from pydantic import BaseSettings class Settings(BaseSettings): - PROJECT_DESC: str = project_desc # 描述 - PROJECT_VERSION: Union[int, str] = 5.0 # 版本 - BASE_URL: AnyHttpUrl = "http://127.0.0.1:8000" # 开发环境 + PROJECT_DESC: str = "接口文档" # 描述 + PROJECT_VERSION: int | str = 1.0 # 版本 API_PREFIX: str = "/api" # 接口前缀 - STATIC_DIR: str = 'static' # 静态文件目录 - GLOBAL_ENCODING: str = 'utf-8' # 全局编码 - CORS_ORIGINS: List[AnyHttpUrl] = ["http://localhost:3000", "http://8.136.82.204:8001"] # 跨域请求 - - SECRET_KEY: str = secrets.token_urlsafe(32) # 密钥(每次重启服务密钥都会改变, token解密失败导致过期, 可设置为常量) - ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 1 # token过期时间: 60 minutes * 24 hours * 1 days = 1 days - - REDIS_URI: str = "redis://:123456@localhost:6379/1" # redis - # DATABASE_URI: str = "sqlite+aiosqlite:///./sql_app.db?check_same_thread=False" # Sqlite(异步) - DATABASE_URI: str = "mysql+asyncmy://root:123456@localhost:3306/elective_system?charset=utf8" # MySQL(异步) - # DATABASE_URI: str = "postgresql+asyncpg://postgres:123456@localhost:5432/postgres" # PostgreSQL(异步) - DATABASE_ECHO: bool = False # 是否打印数据库日志 (可看到创建表、表数据增删改查的信息) - - LOGGER_DIR: str = "logs" # 日志文件夹名 - LOGGER_NAME: str = '{time:YYYY-MM-DD_HH-mm-ss}.log' # 日志文件名 (时间格式) - LOGGER_LEVEL: str = 'DEBUG' # 日志等级: ['DEBUG' | 'INFO'] - LOGGER_ROTATION: str = "12:00" # 日志分片: 按 时间段/文件大小 切分日志. 例如 ["500 MB" | "12:00" | "1 week"] - LOGGER_RETENTION: str = "7 days" # 日志保留的时间: 超出将删除最早的日志. 例如 ["1 days"] - - # 权限数据表 (格式务必为 {'名称', '描述'}) - PERMISSION_DATA: List[dict] = [{'admin': '管理员权限'}, {'teacher': '教师权限'}, {'student': '学生权限'}] - - class Config: - case_sensitive = True # 区分大小写 settings = Settings() diff --git a/backend/core/logger.py b/backend/core/logger.py deleted file mode 100644 index 773c64e..0000000 --- a/backend/core/logger.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/8 10:44 -# @Author : zxiaosi -# @desc : 日志 -import os - -from loguru import logger - -from core import settings -from utils import create_dir - - -# 创建日志文件名 -def logger_file() -> str: - """ 创建日志文件名 """ - log_path = create_dir(settings.LOGGER_DIR) - - """ 保留日志文件夹下最大个数(本地调试用) - 本地调式需要多次重启, 日志轮转片不会生效 """ - file_list = os.listdir(log_path) - if len(file_list) > 3: - os.remove(os.path.join(log_path, file_list[0])) - - # 日志输出路径 - return os.path.join(log_path, settings.LOGGER_NAME) - - -# 详见: https://loguru.readthedocs.io/en/stable/overview.html#features -logger.add( - logger_file(), - encoding=settings.GLOBAL_ENCODING, - level=settings.LOGGER_LEVEL, - rotation=settings.LOGGER_ROTATION, - retention=settings.LOGGER_RETENTION, - enqueue=True -) diff --git a/backend/core/security.py b/backend/core/security.py deleted file mode 100644 index ac998a4..0000000 --- a/backend/core/security.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/10/19 19:49 -# @Author : zxiaosi -# @desc : 安全配置 https://fastapi.tiangolo.com/zh/advanced/security/oauth2-scopes/#global-view -from typing import Any, Union, Optional -from datetime import datetime, timedelta -from fastapi import Header -from jose import jwt -from passlib.context import CryptContext - -from core import settings -from utils import AccessTokenFail - -ALGORITHM = "HS256" # 加密算法 - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # 加密密码 - - -def get_password_hash(password: str) -> str: - """ 加密明文密码 """ - return pwd_context.hash(password) - - -def verify_password(password: str, hashed_password: str) -> bool: - """ 验证明文密码 与 加密后的密码 是否一致 """ - return pwd_context.verify(password, hashed_password) - - -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: - """ - 生成token - - :param data: 存储数据 - :param expires_delta: 有效时间 - :return: 加密后的token - """ - to_encode = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - to_encode.update({"exp": expire}) # eg: {'sub': '1', scopes: ['items'] 'exp': '123'} - encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -# https://www.cnblogs.com/CharmCode/p/14191112.html?ivk_sa=1024320u -async def check_jwt_token(token: Optional[str] = Header(...)) -> Union[str, Any]: - """ 解密token """ - try: - payload = jwt.decode(token=token, key=settings.SECRET_KEY, algorithms=[ALGORITHM]) - return payload - except Exception as e: # jwt.JWTError, jwt.ExpiredSignatureError, AttributeError - raise AccessTokenFail(f'token已过期! -- {e}') - - -if __name__ == '__main__': - # 对 '123456' 加密后得到的值不相同 - print(get_password_hash('123456')) - - # 但 加密前 和 加密后 验证是一致的 - print(verify_password('123456', '$2b$12$I5lfn4eO8M0oH4yYQWjSQ.t4VJz9cGKXA.ht6syIG6tAXmbnQywqa')) # True - print(verify_password('123456', '$2b$12$h58wHhABGgNSRfQCqYFod.0mycfuLZIWQmtvKgP9s0VyYs78In6b.')) # True diff --git a/backend/crud/__init__.py b/backend/crud/__init__.py deleted file mode 100644 index cfdf617..0000000 --- a/backend/crud/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/10/14 16:59 -# @Author : zxiaosi -# @desc : 数据库的增删改查操作(dao层) -from .base import ModelType, CRUDBase -from .department import department -from .major import major -from .teacher import teacher -from .student import student -from .course import course -from .elective import elective -from .taught import taught -from .admin import admin diff --git a/backend/crud/admin.py b/backend/crud/admin.py deleted file mode 100644 index 4e7bbf7..0000000 --- a/backend/crud/admin.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/17 11:05 -# @Author : zxiaosi -# @desc : 操作管理员表 -from typing import Union, Dict, Any -from sqlalchemy import insert, update -from sqlalchemy.ext.asyncio import AsyncSession - -from core import get_password_hash -from crud import CRUDBase -from models import Admin -from schemas import AdminCreate, AdminUpdate - - -class CRUDAdmin(CRUDBase[Admin, AdminCreate, AdminUpdate]): - async def create(self, db: AsyncSession, obj_in: AdminCreate) -> int: - """ 添加管理员信息 """ - setattr(obj_in, 'id', int(obj_in.id)) # postgresql 字段类型限制 - obj_in_data = {} - for k, v in obj_in.dict().items(): # 排除空值 - if v: - if k == 'password': - obj_in_data['hashed_password'] = get_password_hash(obj_in.password) - else: - obj_in_data[k] = v - sql = insert(self.model).values(obj_in_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - async def update(self, db: AsyncSession, id: int, obj_in: Union[AdminUpdate, Dict[str, Any]]) -> int: - """ 更新管理员信息 """ - if isinstance(obj_in, dict): # 判断对象是否为字典类型(更新部分字段) - teacher_data = obj_in - else: - teacher_data = obj_in.dict(exclude_unset=True) - obj_data = {} - for k, v in teacher_data.items(): # 排除空值 - if v: - if k == 'password': - obj_data['hashed_password'] = get_password_hash(obj_in.password) - else: - obj_data[k] = v - sql = update(self.model).where(self.model.id == id).values(obj_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - -admin = CRUDAdmin(Admin) diff --git a/backend/crud/base.py b/backend/crud/base.py deleted file mode 100644 index ea37dbf..0000000 --- a/backend/crud/base.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/10/14 17:00 -# @Author : zxiaosi -# @desc : 封装数据库增删改查方法 -from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union -from pydantic import BaseModel -from sqlalchemy import func, distinct, select, insert, update, desc, delete -from sqlalchemy.ext.asyncio import AsyncSession - -from core import verify_password -from models import Base -from utils import obj_as_dict, list_obj_as_dict - -ModelType = TypeVar("ModelType", bound=Base) -CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) -UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) - - -# db.scalar(sql) 返回的是标量(原始数据) -# db.execute(sql) 返回的是元组 () -# db.scalars(sql).all() [, , ] -# db.execute(sql).fetchall() [(,), (,), (,)] -class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): - """ CRUD 增 查 改 删 """ - - def __init__(self, model: Type[ModelType]): - self.model = model - - async def get(self, db: AsyncSession, id: Any) -> Optional[ModelType]: - """ 通过 id 获取对象 """ - sql = select(self.model).where(self.model.id == id) - result = await db.scalar(sql) - # print(obj_as_dict(await db.scalar(sql))) - await db.close() # 释放会话 - return result - - async def get_multi(self, db: AsyncSession, pageIndex: int = 1, pageSize: int = 10) -> List[ModelType]: - """ 获取第 pageIndex 页的 pageSize 数据 """ - if pageIndex == -1 and pageSize == -1: - sql = select(self.model).order_by(desc(self.model.id)) - else: - sql = select(self.model).offset((pageIndex - 1) * pageSize).limit(pageSize).order_by(desc(self.model.id)) - result = await db.scalars(sql) - # print(list_obj_as_dict(await db.scalars(sql))) - await db.close() # 释放会话 - return result.all() - - async def get_number(self, db: AsyncSession) -> int: - """ 获取表的总条数 """ - sql = select(func.count(distinct(self.model.id))) - result = await db.scalar(sql) - await db.close() # 释放会话 - return result - - async def create(self, db: AsyncSession, obj_in: CreateSchemaType) -> int: - """ 添加对象 """ - if self.model.__tablename__ not in ['taught', 'elective']: - setattr(obj_in, 'id', int(obj_in.id)) # postgresql 字段类型限制 - sql = insert(self.model).values(obj_in.dict()) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - async def update(self, db: AsyncSession, id: int, obj_in: Union[UpdateSchemaType, Dict[str, Any]]) -> int: - """ 通过 id 更新对象 """ - if isinstance(obj_in, dict): # 判断对象是否为字典类型(更新部分字段) - obj_data = obj_in - else: - obj_data = obj_in.dict() - sql = update(self.model).where(self.model.id == id).values(obj_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - async def remove(self, db: AsyncSession, id: int) -> int: - """ 通过 id 删除对象 """ - sql = delete(self.model).where(self.model.id == id) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - async def remove_multi(self, db: AsyncSession, id_list: list): - """ 同时删除多个对象 """ - id_list = [int(i) for i in id_list] # postgresql 字段类型限制 - sql = delete(self.model).where(self.model.id.in_(id_list)) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - async def get_multi_relation(self, db: AsyncSession): - """ 获取关系字段 """ - sql = select(self.model.id, self.model.name).order_by(desc(self.model.id)).distinct() - result = await db.execute(sql) - await db.close() - return result.fetchall() - - async def get_by_name(self, db: AsyncSession, name: str) -> Optional[ModelType]: - """ 通过名字得到用户 """ - sql = select(self.model).where(self.model.name == name) - result = await db.scalar(sql) - await db.close() # 释放会话 - return result - - async def authenticate(self, db: AsyncSession, username: str, password: str) -> Optional[ModelType]: - """ 验证用户 """ - user = await self.get_by_name(db, name=username) - if not user: - return None - if not verify_password(password, user.hashed_password): - return None - return user - - async def sort(self, db: AsyncSession, name: str, pageIndex: int = 1, pageSize: int = 10) -> List[ModelType]: - """ 验证用户 """ - filed_name = self.model.__table__.c[name] - if pageIndex == -1 and pageSize == -1: - sql = select(self.model).order_by(desc(filed_name)) - else: - sql = select(self.model).offset((pageIndex - 1) * pageSize).limit(pageSize).order_by(desc(filed_name)) - result = await db.scalars(sql) - # print(list_obj_as_dict(await db.scalars(sql))) - await db.close() # 释放会话 - return result.all() diff --git a/backend/crud/course.py b/backend/crud/course.py deleted file mode 100644 index 3f02f4c..0000000 --- a/backend/crud/course.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/17 16:29 -# @Author : zxiaosi -# @desc : 操作课程表 -from crud.base import CRUDBase -from models import Course -from schemas import CourseCreate, CourseUpdate - - -class CRUDCourse(CRUDBase[Course, CourseCreate, CourseUpdate]): - pass - - -course = CRUDCourse(Course) diff --git a/backend/crud/department.py b/backend/crud/department.py deleted file mode 100644 index 8ec4aea..0000000 --- a/backend/crud/department.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/10/28 19:01 -# @Author : zxiaosi -# @desc : 操作院系表 -from crud import CRUDBase -from models import Department -from schemas import DepartmentCreate, DepartmentUpdate - - -class CRUDDepartment(CRUDBase[Department, DepartmentCreate, DepartmentUpdate]): - pass - - -department = CRUDDepartment(Department) diff --git a/backend/crud/elective.py b/backend/crud/elective.py deleted file mode 100644 index 2af6690..0000000 --- a/backend/crud/elective.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/18 10:09 -# @Author : zxiaosi -# @desc : 操作选课表 -from typing import Any, Union - -from sqlalchemy import select, insert -from sqlalchemy.ext.asyncio import AsyncSession - -from crud.base import CRUDBase -from models import Elective, Taught, Student, Teacher, Course -from schemas import ElectiveCreate, ElectiveUpdate -from utils import obj_as_dict, list_obj_as_dict - - -class CRUDElective(CRUDBase[Elective, ElectiveCreate, ElectiveUpdate]): - async def get_course(self, db: AsyncSession, id: Any) -> Any: - """ 得到课程详情 """ - fields = [self.model.courseId, Course.name.label('courseName'), - Teacher.id.label('teacherId'), Teacher.name.label('teacherName'), - self.model.grade, self.model.create_time] - sql = select(*fields).where(self.model.studentId == int(id)) \ - .join(Course, self.model.courseId == Course.id) \ - .join(Taught, self.model.courseId == Taught.courseId, isouter=True) \ - .join(Teacher, Taught.teacherId == Teacher.id, isouter=True) - print(id, sql) - result = await db.execute(sql) - # print(list_obj_as_dict(result.all())) - await db.close() # 释放会话 - return result.fetchall() - - async def is_exist(self, db: AsyncSession, studentId: int, courseId: int) -> int: - """ 判断数据是否已经存在 """ - sql1 = select(self.model) \ - .where(self.model.studentId == studentId) \ - .where(self.model.courseId == courseId) - result = await db.scalar(sql1) - return result - - async def create(self, db: AsyncSession, obj_in: Union[Elective]) -> int: - """ 添加对象 """ - if isinstance(obj_in, dict): # 判断对象是否为字典类型(更新部分字段) - obj_data = obj_in - else: - obj_data = obj_in.dict() - sql = insert(self.model).values(obj_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - -elective = CRUDElective(Elective) diff --git a/backend/crud/major.py b/backend/crud/major.py deleted file mode 100644 index 3403143..0000000 --- a/backend/crud/major.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/1 21:03 -# @Author : zxiaosi -# @desc : 操作专业表 -from crud import CRUDBase -from models import Major -from schemas import MajorCreate, MajorUpdate - - -class CRUDMajor(CRUDBase[Major, MajorCreate, MajorUpdate]): - pass - - -major = CRUDMajor(Major) diff --git a/backend/crud/student.py b/backend/crud/student.py deleted file mode 100644 index 74baeaa..0000000 --- a/backend/crud/student.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/17 11:05 -# @Author : zxiaosi -# @desc : 操作学生表 -from typing import Union, Dict, Any - -from sqlalchemy import update, insert, select -from sqlalchemy.ext.asyncio import AsyncSession - -from core import get_password_hash -from crud import CRUDBase -from models import Student, Major -from schemas import StudentCreate, StudentUpdate -from utils import obj_as_dict - - -class CRUDStudent(CRUDBase[Student, StudentCreate, StudentUpdate]): - async def create(self, db: AsyncSession, obj_in: StudentCreate) -> int: - """ 添加学生信息 """ - setattr(obj_in, 'id', int(obj_in.id)) # postgresql 字段类型限制 - obj_in_data = {} - for k, v in obj_in.dict().items(): # 排除空值 - if v: - if k == 'password': - obj_in_data['hashed_password'] = get_password_hash(obj_in.password) - else: - obj_in_data[k] = v - sql = insert(self.model).values(obj_in_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - async def update(self, db: AsyncSession, id: int, obj_in: Union[StudentUpdate, Dict[str, Any]]) -> int: - """ 更新学生信息 """ - if isinstance(obj_in, dict): # 判断对象是否为字典类型(更新部分字段) - teacher_data = obj_in - else: - teacher_data = obj_in.dict(exclude_unset=True) - obj_data = {} - for k, v in teacher_data.items(): # 排除空值 - if v: - if k == 'password': - obj_data['hashed_password'] = get_password_hash(obj_in.password) - else: - obj_data[k] = v - sql = update(self.model).where(self.model.id == id).values(obj_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - # async def get_detail(self, db: AsyncSession, id: int): - # sql = select(self.model, Major.name.label("major_name")) \ - # .join(Major, self.model.majorId == Major.id) \ - # .where(self.model.id == id) - # result = await db.execute(sql) - # await db.close() # 释放会话 - # return result.fetchone() - - -student = CRUDStudent(Student) diff --git a/backend/crud/taught.py b/backend/crud/taught.py deleted file mode 100644 index 99f560a..0000000 --- a/backend/crud/taught.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/5/10 21:00 -# @Author : zxiaosi -# @desc : 操作讲授表 -from typing import Any - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from crud import CRUDBase -from models import Taught, Course, Student, Elective -from schemas import TaughtCreate, TaughtUpdate - - -class CRUDTaught(CRUDBase[Taught, TaughtCreate, TaughtUpdate]): - async def get_course(self, db: AsyncSession, id: Any) -> Any: - """ 得到课程详情 """ - fields = [self.model.courseId, Course.name.label('courseName'), - Elective.studentId, Student.name.label('studentName'), - Elective.id, Elective.grade, Elective.update_time] - sql = select(*fields).where(self.model.teacherId == int(id)) \ - .join(Elective, self.model.courseId == Elective.courseId, isouter=True) \ - .join(Course, self.model.courseId == Course.id) \ - .join(Student, Elective.studentId == Student.id) - result = await db.execute(sql) - # print(list_obj_as_dict(result.all())) - await db.close() # 释放会话 - return result.fetchall() - - -taught = CRUDTaught(Taught) diff --git a/backend/crud/teacher.py b/backend/crud/teacher.py deleted file mode 100644 index 9352fb2..0000000 --- a/backend/crud/teacher.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/15 19:53 -# @Author : zxiaosi -# @desc : 操作教师表 -from typing import Union, Dict, Any - -from sqlalchemy import insert, update -from sqlalchemy.ext.asyncio import AsyncSession - -from core import get_password_hash -from crud.base import CRUDBase -from models import Teacher -from schemas import TeacherCreate, TeacherUpdate - - -class CRUDTeacher(CRUDBase[Teacher, TeacherCreate, TeacherUpdate]): - async def create(self, db: AsyncSession, obj_in: TeacherCreate) -> int: - """ 添加教师信息 """ - setattr(obj_in, 'id', int(obj_in.id)) # postgresql 字段类型限制 - obj_in_data = {} - for k, v in obj_in.dict().items(): # 排除空值 - if v: - if k == 'password': - obj_in_data['hashed_password'] = get_password_hash(obj_in.password) - else: - obj_in_data[k] = v - sql = insert(self.model).values(obj_in_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - async def update(self, db: AsyncSession, id: int, obj_in: Union[TeacherUpdate, Dict[str, Any]]) -> int: - """ 更新教师信息 """ - if isinstance(obj_in, dict): # 判断对象是否为字典类型(更新部分字段) - teacher_data = obj_in - else: - teacher_data = obj_in.dict(exclude_unset=True) - obj_data = {} - for k, v in teacher_data.items(): # 排除空值 - if v: - if k == 'password': - obj_data['hashed_password'] = get_password_hash(obj_in.password) - else: - obj_data[k] = v - sql = update(self.model).where(self.model.id == id).values(obj_data) - result = await db.execute(sql) - await db.commit() - return result.rowcount - - -teacher = CRUDTeacher(Teacher) diff --git a/backend/db/__init__.py b/backend/db/__init__.py deleted file mode 100644 index 485a4b0..0000000 --- a/backend/db/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 17:14 -# @Author : zxiaosi -# @desc : 初始数据库以及表数据 -from .session import engine, async_session -from .redis import MyRedis, init_redis_pool -from .init_db import init_db, drop_db, init_data diff --git a/backend/db/data.py b/backend/db/data.py deleted file mode 100644 index 6743991..0000000 --- a/backend/db/data.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/25 23:00 -# @Author : zxiaosi -# @desc : 假数据 -""" 免责声明: 数据来自网络,如有侵权,请联系删除!!! """ -from datetime import date - -departmentData = [ - {'id': 1001, 'name': '计算机工程学院', 'chairman': '张强', 'phone': '13312312312'}, - {'id': 1002, 'name': '医学院', 'chairman': '马建国', 'phone': '13312312312'}, -] - -majorData = [ - # 计算机工程学院 - {'id': 100101, 'name': '计算机科学与技术', 'assistant': '钱伟', 'phone': '13312312312', 'departmentId': 1001}, - {'id': 100102, 'name': '物联网工程', 'assistant': '吴晓红', 'phone': '13312312312', 'departmentId': 1001}, - {'id': 100103, 'name': '软件工程技术', 'assistant': '柯刚', 'phone': '13312312312', 'departmentId': 1001}, - # 医学院 - {'id': 100201, 'name': '中药学专业', 'assistant': '赵露', 'phone': '13312312312', 'departmentId': 1002}, - {'id': 100202, 'name': '护理学专业', 'assistant': '朱壮', 'phone': '13312312312', 'departmentId': 1002}, - {'id': 100203, 'name': '针灸推拿学专业', 'assistant': '李勇', 'phone': '13312312312', 'departmentId': 1002}, -] - -studentData = [ - # Sqlite: 'birthday': date(1999, 9, 9) - # Mysql: 'birthday': '1999-09-09' - - # 计算机科学与技术 - {'id': 1810010101, 'name': '王芳', 'sex': '1', 'birthday': date(1999, 9, 9), 'majorId': 100101}, - {'id': 1810010102, 'name': '孙悟空', 'sex': '0', 'birthday': date(1999, 5, 23), 'majorId': 100101}, - {'id': 1810010103, 'name': '猪悟能', 'sex': '0', 'birthday': date(1998, 2, 12), 'majorId': 100101}, - {'id': 1810010104, 'name': '唐三葬', 'sex': '0', 'birthday': date(2000, 1, 7), 'majorId': 100101}, - {'id': 1810010105, 'name': '张悟本能', 'sex': '0', 'birthday': date(2000, 3, 29), 'majorId': 100101}, - # 物联网工程 - {'id': 1810010201, 'name': '宋清江', 'sex': '1', 'birthday': date(2000, 2, 10), 'majorId': 100102}, - {'id': 1810010202, 'name': '甄无用', 'sex': '0', 'birthday': date(1999, 6, 21), 'majorId': 100102}, - {'id': 1810010203, 'name': '关不胜', 'sex': '0', 'birthday': date(1999, 7, 19), 'majorId': 100102}, - {'id': 1810010204, 'name': '秦复明', 'sex': '1', 'birthday': date(2001, 1, 26), 'majorId': 100102}, - {'id': 1810010205, 'name': '花繁荣', 'sex': '1', 'birthday': date(2000, 5, 27), 'majorId': 100102}, - # 软件工程技术 - {'id': 1810010301, 'name': '柴进门', 'sex': '0', 'birthday': date(1999, 4, 26), 'majorId': 100103}, - {'id': 1810010302, 'name': '李天应', 'sex': '1', 'birthday': date(1999, 6, 4), 'majorId': 100103}, - {'id': 1810010303, 'name': '董不平', 'sex': '0', 'birthday': date(1999, 9, 30), 'majorId': 100103}, - {'id': 1810010304, 'name': '徐宁静', 'sex': '1', 'birthday': date(2000, 8, 20), 'majorId': 100103}, - {'id': 1810010305, 'name': '索超越', 'sex': '0', 'birthday': date(1999, 7, 20), 'majorId': 100103}, - # 中药学专业 - {'id': 1810020101, 'name': '刘唐氏', 'sex': '1', 'birthday': date(1999, 1, 17), 'majorId': 100201}, - {'id': 1810020102, 'name': '李逵悟', 'sex': '0', 'birthday': date(1999, 5, 13), 'majorId': 100201}, - {'id': 1810020103, 'name': '阮小三', 'sex': '0', 'birthday': date(1999, 9, 16), 'majorId': 100201}, - {'id': 1810020104, 'name': '石秀气', 'sex': '1', 'birthday': date(2000, 1, 18), 'majorId': 100201}, - {'id': 1810020105, 'name': '谢宝庆', 'sex': '0', 'birthday': date(2000, 2, 25), 'majorId': 100201}, - # 护理学专业 - {'id': 1810020201, 'name': '燕青青', 'sex': '1', 'birthday': date(2000, 7, 29), 'majorId': 100202}, - {'id': 1810020202, 'name': '朱文武', 'sex': '0', 'birthday': date(1999, 2, 12), 'majorId': 100202}, - {'id': 1810020203, 'name': '鲍旭日', 'sex': '0', 'birthday': date(1999, 8, 25), 'majorId': 100202}, - {'id': 1810020204, 'name': '孔明亮', 'sex': '1', 'birthday': date(1999, 3, 12), 'majorId': 100202}, - {'id': 1810020205, 'name': '童威猛', 'sex': '0', 'birthday': date(1999, 4, 16), 'majorId': 100202}, - # 针灸推拿学专业 - {'id': 1810020301, 'name': '朱富贵', 'sex': '0', 'birthday': date(1998, 2, 15), 'majorId': 100203}, - {'id': 1810020302, 'name': '孙大嫂', 'sex': '1', 'birthday': date(1999, 3, 27), 'majorId': 100203}, - {'id': 1810020303, 'name': '王小二', 'sex': '0', 'birthday': date(1999, 6, 28), 'majorId': 100203}, - {'id': 1810020304, 'name': '白胜利', 'sex': '1', 'birthday': date(1999, 8, 10), 'majorId': 100203}, - {'id': 1810020305, 'name': '段金柱', 'sex': '0', 'birthday': date(1999, 2, 12), 'majorId': 100203}, -] - -teacherData = [ - # 计算机工程学院 - {'id': 180101, 'name': '陈江川', 'sex': '0', 'birthday': date(1988, 9, 9), 'education': '1', 'title': '3', - 'departmentId': 1001}, - {'id': 180102, 'name': '李小平', 'sex': '0', 'birthday': date(1993, 8, 17), 'education': '2', 'title': '2', - 'departmentId': 1001}, - {'id': 180103, 'name': '赵希明', 'sex': '1', 'birthday': date(1988, 3, 28), 'education': '3', 'title': '4', - 'departmentId': 1001}, - {'id': 180104, 'name': '张红', 'sex': '1', 'birthday': date(1995, 6, 15), 'education': '1', 'title': '1', - 'departmentId': 1001}, - # 医学院 - {'id': 180201, 'name': '吴小龚', 'sex': '1', 'birthday': date(1988, 4, 20), 'education': '2', 'title': '2', - 'departmentId': 1002}, - {'id': 180202, 'name': '张进明', 'sex': '0', 'birthday': date(1989, 10, 29), 'education': '1', 'title': '1', - 'departmentId': 1002}, - {'id': 180203, 'name': '李历宁', 'sex': '0', 'birthday': date(1996, 3, 19), 'education': '1', 'title': '3', - 'departmentId': 1002}, -] - -courseData = [ - {'id': 1101, 'name': '计算机导论', 'credit': 2, 'period': 16}, - {'id': 1102, 'name': '计算机网络', 'credit': 2, 'period': 16}, - {'id': 1103, 'name': 'Java', 'credit': 4, 'period': 16}, - {'id': 1104, 'name': 'C语言', 'credit': 4, 'period': 16}, - {'id': 1201, 'name': '基础化学', 'credit': 2, 'period': 16}, - {'id': 1202, 'name': '生理学', 'credit': 3, 'period': 16}, - {'id': 1203, 'name': '中医药基础', 'credit': 4, 'period': 64}, - {'id': 1301, 'name': '有机化学实验', 'credit': 2, 'period': 32}, - {'id': 1302, 'name': '方剂学', 'credit': 4, 'period': 64}, - {'id': 1303, 'name': '药用化学', 'credit': 2, 'period': 32}, - {'id': 1401, 'name': '马克思主义基本原理概论', 'credit': 2, 'period': 32}, - {'id': 1402, 'name': '大学生心理健康教育', 'credit': 2, 'period': 32}, - {'id': 1403, 'name': '职业生涯与发展规划', 'credit': 1, 'period': 16}, - {'id': 1404, 'name': '形式与政策', 'credit': 1, 'period': 16}, -] - -taughtData = [ - {'teacherId': 180101, 'courseId': 1101}, - {'teacherId': 180101, 'courseId': 1102}, - {'teacherId': 180102, 'courseId': 1103}, - {'teacherId': 180102, 'courseId': 1104}, -] - -electiveData = [ - {'studentId': 1810010101, 'courseId': 1101}, - {'studentId': 1810010101, 'courseId': 1102}, - {'studentId': 1810010102, 'courseId': 1102}, - {'studentId': 1810010201, 'courseId': 1103}, - {'studentId': 1810010202, 'courseId': 1104}, - {'studentId': 1810020301, 'courseId': 1201}, - {'studentId': 1810020302, 'courseId': 1202}, -] - -adminData = [{'name': 'admin'}] diff --git a/backend/db/init_db.py b/backend/db/init_db.py deleted file mode 100644 index b537e87..0000000 --- a/backend/db/init_db.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/25 22:08 -# @Author : zxiaosi -# @desc : 创建与删除所有表, 初始化数据 -from core.logger import logger -from db import engine -from db.data import * -from models import Base, Department, Major, Student, Teacher, Admin, Course, Taught, Elective - - -async def init_db(): - """ 创建 models/__init__ 下的所有表 """ - try: - await drop_db() # 删除所有的表 - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) - logger.info("创建表成功!!!") - except Exception as e: - logger.error(f"创建表失败!!! -- 错误信息如下:\n{e}") - finally: - await engine.dispose() - - -async def drop_db(): - """ 删除 models/__init__ 下的所有表 """ - try: - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.drop_all) - logger.info("删除表成功!!!") - except Exception as e: - logger.error(f"删除表失败!!! -- 错误信息如下:\n{e}") - finally: - await engine.dispose() - - -async def init_data(): - """ 初始化表数据 """ - try: - async with engine.begin() as conn: - await conn.execute(Department.__table__.insert(), [department for department in departmentData]) - await conn.execute(Major.__table__.insert(), [major for major in majorData]) - await conn.execute(Student.__table__.insert(), [student for student in studentData]) - await conn.execute(Teacher.__table__.insert(), [teacher for teacher in teacherData]) - await conn.execute(Admin.__table__.insert(), [admin for admin in adminData]) - await conn.execute(Course.__table__.insert(), [course for course in courseData]) - await conn.execute(Taught.__table__.insert(), [selectCourse for selectCourse in taughtData]) - await conn.execute(Elective.__table__.insert(), [selectCourse for selectCourse in electiveData]) - logger.info(f"成功初始化表数据!!!") - except Exception as e: - logger.error(f"初始化表数据失败!!! -- 错误信息如下:\n{e}") - finally: - await engine.dispose() diff --git a/backend/db/redis.py b/backend/db/redis.py deleted file mode 100644 index 019f776..0000000 --- a/backend/db/redis.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/2/24 10:09 -# @Author : zxiaosi -# @desc : redis -import json -from typing import Union -from aioredis import Redis - -from core import settings - - -class MyRedis(Redis): - """ 继承Redis,并添加自己的方法 """ - - # 写 __init__ 的话就取消下面注释 - # def __init__(self, connection_pool): - # # print(connection_pool) - # super(RedisPlus, self).__init__(connection_pool=connection_pool) - - async def list_loads(self, key: str, num: int = -1) -> list: - """ - 将列表字符串转为对象 - - :param key: 列表的key - :param num: 最大长度(默认值 0-全部) - :return: 列表对象 - """ - todo_list = await self.lrange(key, 0, (num - 1) if num > -1 else num) - return [json.loads(todo) for todo in todo_list] - - async def cus_lpush(self, key: str, value: Union[str, list, dict]): - """ - 向列表右侧插入数据 - - :param key: 列表的key - :param value: 插入的值 - """ - text = json.dumps(value) - await self.lpush(key, text) - - async def get_list_by_index(self, key: str, id: int) -> object: - """ - 根据索引得到列表值 - - :param key: 列表的值 - :param id: 索引值 - :return: - """ - value = await self.lindex(key, id) - return json.loads(value) - - -# 参考: https://github.com/grillazz/fastapi-redis/tree/main/app -async def init_redis_pool() -> MyRedis: - """ 连接redis """ - redis = await MyRedis.from_url(url=settings.REDIS_URI, encoding=settings.GLOBAL_ENCODING, decode_responses=True) - return redis diff --git a/backend/db/session.py b/backend/db/session.py deleted file mode 100644 index 955484d..0000000 --- a/backend/db/session.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 17:27 -# @Author : zxiaosi -# @desc : 数据库会话 -# https://www.osgeo.cn/sqlalchemy/orm/extensions/asyncio.html?highlight=async#asynchronous-i-o-asyncio -from asyncio import current_task - -from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_scoped_session -from sqlalchemy.orm import sessionmaker - -from core import settings - -# 创建表引擎 -engine = create_async_engine( - url=settings.DATABASE_URI, # 数据库uri - echo=settings.DATABASE_ECHO, # 是否打印日志 - # pool_size=10, # 队列池个数 - # max_overflow=20, # 队列池最大溢出个数 -) - -# 操作表会话 -async_session_factory = sessionmaker( - bind=engine, - class_=AsyncSession, - expire_on_commit=False # 防止提交后属性过期 -) - -async_session = async_scoped_session(async_session_factory, scopefunc=current_task) diff --git a/backend/main.py b/backend/main.py index 07e59bb..8a58b2e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,47 +1,29 @@ #!/usr/bin/env python3 # _*_ coding: utf-8 _*_ -# @Time : 2022/4/17 17:08 # @Author : zxiaosi +# @Time : 2022/7/1 14:33 # @desc : 主函数 import uvicorn from fastapi import FastAPI +from loguru import logger -from core import settings -from core.logger import logger -from db import init_db, init_data, init_redis_pool -from register import register_mount, register_exception, register_cors, register_middleware, register_router +from apis import debug +from core.config import settings -app = FastAPI(description=settings.PROJECT_DESC, version=settings.PROJECT_VERSION) +app = FastAPI(title=settings.PROJECT_DESC, version=settings.PROJECT_VERSION) - -def create_app(): - """ 注册中心 """ - register_mount(app) # 挂载静态文件 - - register_exception(app) # 注册捕获全局异常 - - register_router(app) # 注册路由 - - register_middleware(app) # 注册请求响应拦截 - - register_cors(app) # 注册跨域请求 - - logger.info("日志初始化成功!!!") # 初始化日志 +app.include_router(debug.router) @app.on_event("startup") -async def startup(): - create_app() # 加载注册中心 - # await init_db() # 初始化表 - # await init_data() # 初始化数据 - app.state.redis = await init_redis_pool() # redis +async def startup_event(): + logger.success('🎉🎉🎉 项目启动 🎉🎉🎉') @app.on_event("shutdown") -async def shutdown(): - await app.state.redis.close() # 关闭 redis +async def shutdown_event(): + logger.success('项目关闭') if __name__ == '__main__': uvicorn.run(app='main:app', host="127.0.0.1", port=8000) - # uvicorn.run(app='main:app', host="127.0.0.1", port=8000, debug=True, reload=True) diff --git a/backend/models/__init__.py b/backend/models/__init__.py index a5b212a..3e50d3b 100644 --- a/backend/models/__init__.py +++ b/backend/models/__init__.py @@ -1,14 +1,5 @@ #!/usr/bin/env python3 # _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 17:12 # @Author : zxiaosi -# @desc : ORM模型对象(创建/删除表的顺序, 慎动) -from .base import Base -from .department import Department -from .major import Major -from .teacher import Teacher -from .student import Student -from .admin import Admin -from .course import Course -from .taught import Taught -from .elective import Elective +# @Time : 2022/7/1 14:40 +# @desc : diff --git a/backend/models/admin.py b/backend/models/admin.py deleted file mode 100644 index 3182ef9..0000000 --- a/backend/models/admin.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 22:24 -# @Author : zxiaosi -# @desc : 管理员表 -from sqlalchemy import Column, Integer, String, text - -from core import settings, get_password_hash -from models import Base - - -class Admin(Base): - """ 管理员表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='编号') - - name = Column(String(10), nullable=False, index=True, comment='姓名') - - address = Column(String(20), server_default='广东省广州市', comment='上次登录地址') - - image = Column(String(60), server_default=f'{settings.BASE_URL}/{settings.STATIC_DIR}/author.jpg', comment='头像') - - hashed_password = Column(String(60), server_default=get_password_hash('123456'), comment='密码') diff --git a/backend/models/base.py b/backend/models/base.py deleted file mode 100644 index bb508c5..0000000 --- a/backend/models/base.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 17:14 -# @Author : zxiaosi -# @desc : ORM父类 -from sqlalchemy import Column, DateTime, func -from sqlalchemy.ext.declarative import as_declarative, declared_attr - - -# id 也能写在这里, 不过为了方便看字段, 就在每个表内创建了 -# https://www.osgeo.cn/sqlalchemy/orm/mapping_api.html#sqlalchemy.orm.as_declarative -@as_declarative() -class Base: - """ 基本表 """ - - __name__: str # 表名 - __table_args__ = {"mysql_charset": "utf8"} # 设置表的字符集 - # __mapper_args__ = {"eager_defaults": True} # 防止 insert 插入后不刷新 - - # 将类名小写并转化为表名 __tablename__ - @declared_attr - def __tablename__(cls) -> str: - return cls.__name__.lower() - - @declared_attr - def create_time(cls): # 创建时间 - return Column(DateTime(timezone=True), server_default=func.now(), comment='创建时间') - - @declared_attr - def update_time(cls): # 更新时间 - return Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), comment='更新时间') diff --git a/backend/models/course.py b/backend/models/course.py deleted file mode 100644 index fb04476..0000000 --- a/backend/models/course.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 22:24 -# @Author : zxiaosi -# @desc : 课程表 -from sqlalchemy import Column, String, Float, SmallInteger, Integer -from sqlalchemy.orm import relationship - -from models import Base - - -class Course(Base): - """ 课程表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='编号') - - name = Column(String(20), unique=True, nullable=False, comment='名字') - - credit = Column(Float, default=0, server_default='0', comment='学分') - - period = Column(SmallInteger, server_default='0', comment='课时') - - taught = relationship('Taught') # 不是字段, 可以通过 course ORM对象引用 taught 表的类集合 - - elective = relationship('Elective') # 不是字段, 可以通过 elective ORM对象引用 taught 表的类集合 diff --git a/backend/models/department.py b/backend/models/department.py deleted file mode 100644 index a9df470..0000000 --- a/backend/models/department.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 22:23 -# @Author : zxiaosi -# @desc : 院系表 -from sqlalchemy import Column, Integer, String -from sqlalchemy.orm import relationship - -from models import Base - - -class Department(Base): - """ 院系表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='编号') - - name = Column(String(20), unique=True, nullable=False, index=True, comment='名字') - - chairman = Column(String(10), nullable=False, comment='主任姓名') - - phone = Column(String(11), comment='主任手机号') - - majors = relationship('Major') # 不是字段, 可以通过 department ORM对象引用 major 表的类集合 - - teachers = relationship('Teacher') # 不是字段, 可以通过 department ORM对象引用 teacher 表的类集合 diff --git a/backend/models/elective.py b/backend/models/elective.py deleted file mode 100644 index 293e0c6..0000000 --- a/backend/models/elective.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 22:24 -# @Author : zxiaosi -# @desc : 选课表 -from sqlalchemy import Column, ForeignKey, Integer - -from models import Base - - -class Elective(Base): - """ 选课表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='编号') - - grade = Column(Integer, default=0, server_default='0', comment='成绩') - - studentId = Column(Integer, ForeignKey('student.id', ondelete='CASCADE'), nullable=False, comment='学号') - - courseId = Column(Integer, ForeignKey('course.id', ondelete='CASCADE'), nullable=False, comment='课程编号') diff --git a/backend/models/major.py b/backend/models/major.py deleted file mode 100644 index 13484f2..0000000 --- a/backend/models/major.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 22:23 -# @Author : zxiaosi -# @desc : 专业表 -from sqlalchemy import Column, Integer, String, ForeignKey -from sqlalchemy.orm import relationship - -from models import Base - - -class Major(Base): - """ 专业表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='编号') - - name = Column(String(20), unique=True, nullable=False, comment='名字') - - assistant = Column(String(10), nullable=False, comment='辅导员姓名') - - phone = Column(String(11), comment='辅导员手机号') - - departmentId = Column(Integer, ForeignKey('department.id', ondelete='CASCADE'), nullable=False, comment='院系编号') - - students = relationship('Student') # 不是字段, 可以通过 major ORM对象引用 student 表的类集合 diff --git a/backend/models/student.py b/backend/models/student.py deleted file mode 100644 index 0ad8b8d..0000000 --- a/backend/models/student.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 13:48 -# @Author : zxiaosi -# @desc : 学生表 -from datetime import date - -from sqlalchemy import Column, Integer, String, Date, text, ForeignKey -from sqlalchemy.orm import relationship - -from core import settings, get_password_hash -from models import Base -from utils import check_or_enum - - -class Student(Base): - """ 学生表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='学号') - - name = Column(String(10), unique=False, nullable=False, comment='姓名') - - sex = check_or_enum(name='sex', enumList=['0', '1'], comment='性别: 0->男, 1->女') - - birthday = Column(Date, default=date(2012, 1, 1), comment='生日') - - address = Column(String(20), server_default=text("'广东省广州市'"), comment='地址') - - image = Column(String(60), server_default=f'{settings.BASE_URL}/{settings.STATIC_DIR}/author.jpg', comment='头像') - - hashed_password = Column(String(60), server_default=get_password_hash('123456'), comment='密码') - - majorId = Column(Integer, ForeignKey('major.id', ondelete='CASCADE'), nullable=False, comment='专业编号') - - elective = relationship('Elective') # 不是字段, 可以通过 student ORM对象引用 elective 表的类集合 diff --git a/backend/models/taught.py b/backend/models/taught.py deleted file mode 100644 index 3ead50e..0000000 --- a/backend/models/taught.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 22:24 -# @Author : zxiaosi -# @desc : 讲授表 -from sqlalchemy import Column, ForeignKey, Integer - -from models import Base - - -class Taught(Base): - """ 讲授表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='编号') - - teacherId = Column(Integer, ForeignKey('teacher.id', ondelete='CASCADE'), comment='职工号') - - courseId = Column(Integer, ForeignKey('course.id', ondelete='CASCADE'), comment='课程编号') diff --git a/backend/models/teacher.py b/backend/models/teacher.py deleted file mode 100644 index de4e3e9..0000000 --- a/backend/models/teacher.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/19 22:24 -# @Author : zxiaosi -# @desc : 教师表 -from datetime import date -from sqlalchemy import Column, String, Integer, Date, ForeignKey -from sqlalchemy.orm import relationship - -from core import settings, get_password_hash -from models import Base -from utils import check_or_enum - - -class Teacher(Base): - """ 教师表 """ - - id = Column(Integer, primary_key=True, autoincrement=True, index=True, comment='职工号') - - name = Column(String(10), unique=True, nullable=False, comment='姓名') - - sex = check_or_enum(name='sex', enumList=['0', '1'], comment='性别: 0->男, 1->女') - - birthday = Column(Date, default=date(2012, 1, 1), comment='生日') - - education = check_or_enum(name='education', enumList=['1', '2', '3'], comment='学历: 1->学士, 2->硕士, 3->博士') - - title = check_or_enum(name='title', enumList=['1', '2', '3', '4'], comment='职称: 1->助教, 2->讲师, 3->副教授, 4->教授') - - hashed_password = Column(String(60), server_default=get_password_hash('123456'), comment='密码') - - address = Column(String(20), server_default='广东省广州市', comment='上次登录地点') - - image = Column(String(60), server_default=f'{settings.BASE_URL}/{settings.STATIC_DIR}/author.jpg', comment='头像') - - departmentId = Column(Integer, ForeignKey('department.id', ondelete='CASCADE'), nullable=False, doc='院系编号') - - taught = relationship('Taught') # 不是字段, 可以通过 teacher ORM对象引用 taught 表的类集合 diff --git a/backend/register/__init__.py b/backend/register/__init__.py deleted file mode 100644 index 42780d2..0000000 --- a/backend/register/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/8 14:53 -# @Author : zxiaosi -# @desc : 注册中心 -from .cors import register_cors -from .exception import register_exception -from .router import register_router -from .middleware import register_middleware -from .mount import register_mount diff --git a/backend/register/cors.py b/backend/register/cors.py deleted file mode 100644 index a9a6da9..0000000 --- a/backend/register/cors.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/9 16:48 -# @Author : zxiaosi -# @desc : 跨域请求 -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - -from core import settings - - -def register_cors(app: FastAPI): - """ 跨域请求 -- https://fastapi.tiangolo.com/zh/tutorial/cors/ """ - - app.add_middleware( - CORSMiddleware, - allow_origins=[str(origin) for origin in settings.CORS_ORIGINS], - allow_credentials=True, - allow_methods=("GET", "POST", "PUT", "DELETE"), - allow_headers=("*", "authentication"), - ) diff --git a/backend/register/exception.py b/backend/register/exception.py deleted file mode 100644 index 07ff876..0000000 --- a/backend/register/exception.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/8 15:48 -# @Author : zxiaosi -# @desc : 全局异常 -import traceback -from fastapi import FastAPI -from fastapi.exceptions import RequestValidationError -from pydantic import ValidationError -from sqlalchemy.exc import IntegrityError, ProgrammingError -from sqlalchemy.orm.exc import UnmappedInstanceError -from starlette.requests import Request - -from core.logger import logger -from utils.custom_exc import IpError, ErrorUser, UserNotExist, SetRedis, AccessTokenFail, IdNotExist, \ - PermissionNotEnough -from utils.resp_code import resp_400, resp_401, resp_403, resp_422, resp_500 - - -def register_exception(app: FastAPI): - """ 全局异常捕获 -- https://www.charmcode.cn/article/2020-07-19_fastapi_exception """ - - @app.exception_handler(IpError) - async def ip_error_handler(request: Request, exc: IpError): - """ ip错误(自定义异常) """ - logger.warning(f"{exc.err_desc}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_400(msg=exc.err_desc) - - @app.exception_handler(ErrorUser) - async def error_user_handler(request: Request, exc: ErrorUser): - """ 错误的用户名或密码(自定义异常) """ - logger.warning(f"{exc.err_desc}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_400(msg=exc.err_desc) - - @app.exception_handler(UserNotExist) - async def user_not_exist_handler(request: Request, exc: UserNotExist): - """ 用户不存在(自定义异常) """ - logger.warning(f"{exc.err_desc}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_400(msg=exc.err_desc) - - @app.exception_handler(IdNotExist) - async def id_not_exist_handler(request: Request, exc: IdNotExist): - """ 查询id不存在(自定义异常) """ - logger.warning(f"{exc.err_desc}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_400(msg=exc.err_desc) - - @app.exception_handler(SetRedis) - async def set_redis_handler(request: Request, exc: SetRedis): - """ Redis存储失败(自定义异常) """ - logger.warning(f"{exc.err_desc}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_400(msg=exc.err_desc) - - @app.exception_handler(AccessTokenFail) - async def access_token_fail_handler(request: Request, exc: AccessTokenFail): - """ 访问令牌失败(自定义异常) """ - logger.warning(f"{exc.err_desc}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_401(msg=exc.err_desc) - - @app.exception_handler(PermissionNotEnough) - async def permission_not_enough_handler(request: Request, exc: AccessTokenFail): - """ 权限不足,拒绝访问(自定义异常) """ - logger.warning(f"{exc.err_desc}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_403(msg=exc.err_desc) - - @app.exception_handler(IntegrityError) - async def integrity_error_handler(request: Request, exc: IntegrityError): - """ 添加/更新的数据与数据库中数据冲突 """ - text = f"添加/更新的数据与数据库中数据冲突!" - logger.warning(f"{text}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}\nerror:{exc.orig}") - return resp_400(msg=text) - - @app.exception_handler(ProgrammingError) - async def programming_error_handle(request: Request, exc: ProgrammingError): - """ 请求参数丢失 """ - logger.error(f"请求参数丢失\nURL:{request.method}-{request.url}\nHeaders:{request.headers}\nerror:{exc}") - return resp_400(msg='请求参数丢失!(实际请求参数错误)') - - @app.exception_handler(RequestValidationError) - async def request_validation_exception_handler(request: Request, exc: RequestValidationError): - """ 请求参数验证异常 """ - logger.error(f"请求参数格式错误\nURL:{request.method}-{request.url}\nHeaders:{request.headers}\nerror:{exc.errors()}") - return resp_422(msg=exc.errors()) - - @app.exception_handler(ValidationError) - async def inner_validation_exception_handler(request: Request, exc: ValidationError): - """ 内部参数验证异常 """ - logger.error(f"内部参数验证错误\nURL:{request.method}-{request.url}\nHeaders:{request.headers}\nerror:{exc.errors()}") - return resp_500(msg=exc.errors()) - - @app.exception_handler(UnmappedInstanceError) - async def un_mapped_instance_error_handler(request: Request, exc: UnmappedInstanceError): - """ 删除数据的id在数据库中不存在 """ - id = request.path_params.get("id") - text = f"不存在编号为 {id} 的数据, 删除失败!" - logger.warning(f"{text}\nURL:{request.method}-{request.url}\nHeaders:{request.headers}") - return resp_400(msg=text) - - @app.exception_handler(Exception) - async def all_exception_handler(request: Request, exc: Exception): - """ 捕获全局异常 """ - logger.error(f"全局异常\n{request.method}URL:{request.url}\nHeaders:{request.headers}\n{traceback.format_exc()}") - return resp_500(msg="服务器内部错误") diff --git a/backend/register/middleware.py b/backend/register/middleware.py deleted file mode 100644 index 0591d6c..0000000 --- a/backend/register/middleware.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/6 14:29 -# @Author : zxiaosi -# @desc : 中间件 -from fastapi import FastAPI, Request -from sqlalchemy.exc import OperationalError -from aioredis.exceptions import ConnectionError - -from core.logger import logger -from utils import resp_500 - - -# 权限验证 https://www.cnblogs.com/mazhiyong/p/13433214.html -# 得到真实ip https://stackoverflow.com/questions/60098005/fastapi-starlette-get-client-real-ip -# nginx 解决跨域请求 https://segmentfault.com/a/1190000019227927 - -def register_middleware(app: FastAPI): - """ 请求拦截与响应拦截 -- https://fastapi.tiangolo.com/tutorial/middleware/ """ - - @app.middleware("http") - async def intercept(request: Request, call_next): - logger.info(f"访问记录:IP:{request.client.host}-method:{request.method}-url:{request.url}") - try: - await request.app.state.redis.incr('request_num') # redis 请求数量 (自增 1) - return await call_next(request) # 返回请求(跳过token) - except ConnectionError as e: - logger.error(f'redis连接失败!-- {e}') - return resp_500(msg=f'redis连接失败!') - except OperationalError as e: - logger.error(f'数据库连接失败!-- {e}') - return resp_500(msg=f'数据库连接失败!') diff --git a/backend/register/mount.py b/backend/register/mount.py deleted file mode 100644 index 616dd8a..0000000 --- a/backend/register/mount.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/3/4 16:04 -# @Author : zxiaosi -# @desc : 挂载静态文件 -from fastapi import FastAPI -from fastapi.staticfiles import StaticFiles - -from core import settings - - -def register_mount(app: FastAPI): - """ 挂载静态文件 -- https://fastapi.tiangolo.com/zh/tutorial/static-files/ """ - - # 第一个参数为url路径参数, 第二参数为静态文件目录的路径, 第三个参数是FastAPI内部使用的名字 - app.mount(f"/{settings.STATIC_DIR}", StaticFiles(directory=settings.STATIC_DIR), name=settings.STATIC_DIR) diff --git a/backend/register/router.py b/backend/register/router.py deleted file mode 100644 index 66709a7..0000000 --- a/backend/register/router.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/8 23:06 -# @Author : zxiaosi -# @desc : 注册路由 -from fastapi import FastAPI, Security - -from core import settings -from apis import app_router -from apis.deps import get_current_user -from apis.common import redis_check, login, dashboard - - -def register_router(app: FastAPI): - """ 注册路由 """ - - app.include_router(redis_check.router, prefix=settings.API_PREFIX, tags=["Redis"]) # Redis(不需要权限) - - app.include_router(login.router, prefix=settings.API_PREFIX, tags=["Login"]) # Login(权限在每个接口上) - - app.include_router(dashboard.router, prefix=settings.API_PREFIX, tags=["Dashboard"], - dependencies=[Security(get_current_user, scopes=[])]) # Dashboard(不需要权限,但需要登录) - - # 权限(权限在每个接口上) - app.include_router(app_router, prefix=settings.API_PREFIX) diff --git a/backend/requirement.txt b/backend/requirement.txt new file mode 100644 index 0000000..106a53c --- /dev/null +++ b/backend/requirement.txt @@ -0,0 +1,18 @@ +anyio==3.6.1 +click==8.1.3 +colorama==0.4.5 +fastapi==0.78.0 +h11==0.13.0 +httptools==0.4.0 +idna==3.3 +loguru==0.6.0 +pydantic==1.9.1 +python-dotenv==0.20.0 +PyYAML==6.0 +sniffio==1.2.0 +starlette==0.19.1 +typing-extensions==4.2.0 +uvicorn==0.18.1 +watchfiles==0.15.0 +websockets==10.3 +win32-setctime==1.1.0 diff --git a/backend/requirements.txt b/backend/requirements.txt deleted file mode 100644 index 8b6beb2..0000000 --- a/backend/requirements.txt +++ /dev/null @@ -1,37 +0,0 @@ -aioredis==2.0.1 -aiosqlite==0.17.0 -anyio==3.5.0 -asgiref==3.5.0 -async-timeout==4.0.2 -asyncmy==0.2.5 -asyncpg==0.25.0 -bcrypt==3.2.2 -cffi==1.15.0 -click==8.1.2 -colorama==0.4.4 -ecdsa==0.17.0 -fastapi==0.75.2 -greenlet==1.1.2 -h11==0.13.0 -httptools==0.4.0 -idna==3.3 -loguru==0.6.0 -orjson==3.6.8 -passlib==1.7.4 -pyasn1==0.4.8 -pycparser==2.21 -pydantic==1.9.0 -python-dotenv==0.20.0 -python-jose==3.3.0 -python-multipart==0.0.5 -PyYAML==6.0 -rsa==4.8 -six==1.16.0 -sniffio==1.2.0 -SQLAlchemy==1.4.36 -starlette==0.17.1 -typing_extensions==4.2.0 -uvicorn==0.17.6 -watchgod==0.8.2 -websockets==10.3 -win32-setctime==1.1.0 diff --git a/backend/schemas/__init__.py b/backend/schemas/__init__.py index da3f104..44bf1b4 100644 --- a/backend/schemas/__init__.py +++ b/backend/schemas/__init__.py @@ -1,18 +1,5 @@ #!/usr/bin/env python3 # _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 9:40 # @Author : zxiaosi -# @desc : 数据模型(类似于TS中的interface) -from .common import GMT -from .department import DepartmentCreate, DepartmentUpdate, DepartmentOut -from .major import MajorCreate, MajorUpdate, MajorOut -from .teacher import TeacherCreate, TeacherUpdate, TeacherOut -from .student import StudentCreate, StudentUpdate, StudentOut -from .course import CourseCreate, CourseUpdate, CourseOut -from .taught import TaughtCreate, TaughtUpdate, TaughtOut -from .elective import ElectiveCreate, ElectiveUpdate, ElectiveOut -from .admin import AdminCreate, AdminUpdate, AdminOut -from .token import Token, TokenData -from .result import SchemasType, Result, ResultPlus -from .todo import TodoId, Todo -from .login import Login +# @Time : 2022/7/1 14:43 +# @desc : diff --git a/backend/schemas/admin.py b/backend/schemas/admin.py deleted file mode 100644 index 05effc0..0000000 --- a/backend/schemas/admin.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 10:01 -# @Author : zxiaosi -# @desc : 管理员表的模型 -from typing import Optional - -from pydantic import BaseModel, Field - -from schemas import GMT - - -class AdminIn(BaseModel): - """ 共享模型字段 """ - name: str = Field(..., max_length=10, example='姓名') - image: str = Field(..., example='头像') - address: str = Field(..., example='地址') - - -class AdminUpdate(AdminIn): - """ 更新数据的字段验证 """ - password: Optional[str] = Field(max_length=60, example='管理员密码') # 前端返回可不带该字段 - - -class AdminCreate(AdminUpdate): - """ 添加数据时的字段验证 """ - id: int = Field(..., example='自增编号') - - -class AdminOut(AdminIn, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='自增编号') - - class Config: - orm_mode = True # 是否使用orm模型(个人理解: 放行,不验证) diff --git a/backend/schemas/common.py b/backend/schemas/common.py deleted file mode 100644 index a35c86f..0000000 --- a/backend/schemas/common.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/2/22 20:06 -# @Author : zxiaosi -# @desc : 常用模型 -from datetime import datetime - -from pydantic import BaseModel, validator - - -class GMT(BaseModel): - """ 时间字段处理 """ - create_time: datetime - update_time: datetime - - @validator("create_time", "update_time") - def format_time(cls, value: datetime) -> str: - return value.strftime('%Y-%m-%d %H:%M:%S') # 格式化时间 diff --git a/backend/schemas/course.py b/backend/schemas/course.py deleted file mode 100644 index 4609833..0000000 --- a/backend/schemas/course.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 10:01 -# @Author : zxiaosi -# @desc : 课程表模型 -from pydantic import BaseModel, Field -from schemas import GMT - - -class CourseIn(BaseModel): - """ 共享模型字段 """ - name: str = Field(min_length=1, max_length=20, example='课程名字') - credit: float = Field(..., example='学分') - period: int = Field(..., example='课时') - - -class CourseCreate(CourseIn): - """ 添加数据时的字段验证 """ - id: str = Field(regex=r'^[1-9][0-9]{3}$', min_length=4, max_length=4, example='课程编号:1101') - - -class CourseUpdate(CourseIn): - """ 更新数据的字段验证 """ - pass - - -class CourseOut(CourseCreate, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='编号') - - class Config: - orm_mode = True # 是否使用orm模型(结果为字典类型) diff --git a/backend/schemas/department.py b/backend/schemas/department.py deleted file mode 100644 index 15bcff4..0000000 --- a/backend/schemas/department.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 9:59 -# @Author : zxiaosi -# @desc : 院系表模型 -from typing import Optional -from pydantic import BaseModel, Field - -from schemas import GMT - - -class DepartmentIn(BaseModel): - """ 共享模型字段 """ - name: str = Field(min_length=1, max_length=20, example='名字') - chairman: str = Field(min_length=1, max_length=10, example='主任名') - phone: Optional[str] = Field(regex=r'(^\s{0}$)|^(0\d{2,3}-\d{7,8})|(1[34578]\d{9})$', example='主任手机号') - - -class DepartmentCreate(DepartmentIn): - """ 添加数据时的字段验证 """ - id: str = Field(regex=r'^[1-9][0-9]{3}$', example='编号') - - -class DepartmentUpdate(DepartmentIn): - """ 更新数据的字段验证 """ - pass - - -class DepartmentOut(DepartmentIn, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='编号') - - class Config: - orm_mode = True # 是否使用orm模型(结果为字典类型) diff --git a/backend/schemas/elective.py b/backend/schemas/elective.py deleted file mode 100644 index f599146..0000000 --- a/backend/schemas/elective.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 10:01 -# @Author : zxiaosi -# @desc : 选课表模型 -from typing import Optional - -from pydantic import BaseModel, Field - -from schemas import GMT - - -class ElectiveIn(BaseModel): - """ 共享模型字段 """ - grade: Optional[str] = Field(regex=r'(^\s{0}$)|(^100)|(^([0]{0})([1-9]?)([0-9]))$', max_length=3, example='成绩') - studentId: str = Field(regex=r'^[1-9][0-9]{9}$', min_length=10, max_length=10, example='学号:1810020401') - courseId: str = Field(regex=r'^[1-9][0-9]{3}$', min_length=4, max_length=4, example='课程编号:1101') - - -class ElectiveCreate(ElectiveIn): - """ 添加数据时的字段验证 """ - pass - - -class ElectiveUpdate(ElectiveIn): - """ 更新数据的字段验证 """ - pass - - -class ElectiveOut(ElectiveIn, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='自增编号') - studentId: int = Field(..., example='学号') - courseId: int = Field(..., example='课程号') - - class Config: - orm_mode = True # 是否使用orm模型(结果为字典类型) diff --git a/backend/schemas/login.py b/backend/schemas/login.py deleted file mode 100644 index 42e192f..0000000 --- a/backend/schemas/login.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/2/25 11:23 -# @Author : zxiaosi -# @desc : 登录模型 -from pydantic import BaseModel - - -class Login(BaseModel): - """ 登录模型 """ - username: str - password: str diff --git a/backend/schemas/major.py b/backend/schemas/major.py deleted file mode 100644 index 691377d..0000000 --- a/backend/schemas/major.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 10:00 -# @Author : zxiaosi -# @desc : 专业表模型 -from typing import Optional -from pydantic import BaseModel, Field - -from schemas import GMT - - -class MajorIn(BaseModel): - """ 共享模型字段 """ - name: str = Field(max_length=20, example='名字') - assistant: str = Field(min_length=1, max_length=10, example='辅导员名') - phone: Optional[str] = Field(regex=r'(^\s{0}$)|^(0\d{2,3}-\d{7,8})|(1[34578]\d{9})$', example='辅导员手机号') - departmentId: str = Field(regex=r'^10[0-9]{2}$', min_length=4, max_length=4, example='院系编号') - - -class MajorCreate(MajorIn): - """ 添加数据时的字段验证 """ - id: str = Field(regex=r'^10[0-9]{4}$', min_length=6, max_length=6, example='编号') - - -class MajorUpdate(MajorIn): - """ 更新数据的字段验证 """ - pass - - -class MajorOut(MajorIn, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='编号') - departmentId: int = Field(..., example='院系编号') - - class Config: - orm_mode = True # 是否使用orm模型(结果为字典类型) diff --git a/backend/schemas/result.py b/backend/schemas/result.py deleted file mode 100644 index 1a6169a..0000000 --- a/backend/schemas/result.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/7 11:24 -# @Author : zxiaosi -# @desc : 返回数据验证 -from typing import TypeVar, Generic, Optional, Union, List, Dict, Any -from pydantic import BaseModel -from pydantic.generics import GenericModel - -SchemasType = TypeVar("SchemasType", bound=BaseModel) - - -class Result(GenericModel, Generic[SchemasType]): - """ 普通结果验证 """ - code: int - data: Union[SchemasType, str, list, dict, bool] - msg: Optional[str] - - -class ListModel(GenericModel, Generic[SchemasType]): - """ 自定义结果 --> data 数据 """ - list: List[Union[SchemasType, Dict[str, Any]]] - count: int - - -class ResultPlus(GenericModel, Generic[SchemasType]): - """ 列表结果验证 """ - code: int - data: ListModel[SchemasType] - msg: Optional[str] diff --git a/backend/schemas/student.py b/backend/schemas/student.py deleted file mode 100644 index a98990e..0000000 --- a/backend/schemas/student.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 10:00 -# @Author : zxiaosi -# @desc : 学生表模型 -from datetime import date -from typing import Optional, Literal -from pydantic import BaseModel, Field - -from schemas import GMT - - -class StudentIn(BaseModel): - """ 共享模型字段 """ - name: str = Field(min_length=1, max_length=10, example='姓名') - sex: Literal['0', '1'] = Field(..., example='性别:0->男, 1->女') - birthday: date = Field(..., example='生日: 1998-7-2') - image: str = Field(..., example='头像') - address: str = Field(..., example='地址') - majorId: str = Field(regex=r'^10\d{4}$', min_length=6, max_length=6, example='专业编号') - - -class StudentUpdate(StudentIn): - """ 更新数据的字段验证 """ - password: Optional[str] = Field(max_length=60, example='密码') # 前端返回可不带该字段 - - -class StudentCreate(StudentUpdate): - """ 添加数据时的字段验证 """ - id: str = Field(regex=r'^[1-9][0-9]{9}$', min_length=10, max_length=10, example='学号:1810020401') - - -class StudentOut(StudentIn, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='学号:1810020401') - majorId: int = Field(..., example='专业编号') - - class Config: - orm_mode = True # 是否使用orm模型(结果为字典类型) diff --git a/backend/schemas/taught.py b/backend/schemas/taught.py deleted file mode 100644 index 6e4160d..0000000 --- a/backend/schemas/taught.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 10:01 -# @Author : zxiaosi -# @desc : 讲授表模型 -from pydantic import BaseModel, Field - -from schemas import GMT - - -class TaughtIn(BaseModel): - """ 共享模型字段 """ - teacherId: str = Field(regex=r'^[1-9][0-9]{5}$', min_length=6, max_length=6, example='职工号:180404') - courseId: str = Field(regex=r'^[1-9][0-9]{3}$', min_length=4, max_length=4, example='课程编号:1101') - - -class TaughtCreate(TaughtIn): - """ 添加数据时的字段验证 """ - pass - - -class TaughtUpdate(TaughtIn): - """ 更新数据的字段验证 """ - pass - - -class TaughtOut(TaughtIn, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='自增编号') - teacherId: int = Field(..., example='职工号') - courseId: int = Field(..., example='课程号') - - class Config: - orm_mode = True # 是否使用orm模型(结果为字典类型) diff --git a/backend/schemas/teacher.py b/backend/schemas/teacher.py deleted file mode 100644 index 6d0f6c1..0000000 --- a/backend/schemas/teacher.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/9/22 10:01 -# @Author : zxiaosi -# @desc : 教师表模型 -from datetime import date -from typing import Optional, Literal -from pydantic import BaseModel, Field - -from schemas import GMT - - -class TeacherIn(BaseModel): - """ 共享模型字段 """ - name: str = Field(min_length=1, max_length=10, example='姓名') - sex: Literal['0', '1'] = Field(..., example='性别: 0->男, 1->女') - birthday: date = Field(..., example='生日: 1998-7-2') - education: Literal['1', '2', '3'] = Field(..., example='学历:1->学士, 2->硕士, 3->博士') - title: Literal['1', '2', '3', '4'] = Field(..., example='职称:1->助教, 2->讲师, 3->副教授, 4->教授') - image: str = Field(example='头像') - address: str = Field(example='地址') - departmentId: str = Field(regex=r'^10[0-9]{2}$', min_length=4, max_length=4, example='院系编号') - - -class TeacherUpdate(TeacherIn): - """ 更新数据的字段验证 """ - password: Optional[str] = Field(max_length=60, example='密码') # 前端返回可不带该字段 - - -class TeacherCreate(TeacherUpdate): - """ 添加数据时的字段验证 """ - id: str = Field(regex=r'^[1-9][0-9]{5}$', min_length=6, max_length=6, example='职工号:180404') - - -class TeacherOut(TeacherIn, GMT): - """ 查询数据的字段验证 """ - id: int = Field(..., example='职工号:180404') - departmentId: int = Field(..., example='院系编号') - - class Config: - orm_mode = True # 是否使用orm模型(结果为字典类型) diff --git a/backend/schemas/todo.py b/backend/schemas/todo.py deleted file mode 100644 index f6c8768..0000000 --- a/backend/schemas/todo.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/2/24 11:20 -# @Author : zxiaosi -# @desc : 待办模型 -from typing import Optional - -from pydantic import BaseModel - - -class TodoId(BaseModel): - id: Optional[int] - - -class Todo(BaseModel): - title: Optional[str] - status: Optional[bool] = False diff --git a/backend/schemas/token.py b/backend/schemas/token.py deleted file mode 100644 index 060952e..0000000 --- a/backend/schemas/token.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/12/24 14:48 -# @Author : zxiaosi -# @desc : token -from typing import Optional, List - -from pydantic import BaseModel - - -class Token(BaseModel): - """ token """ - access_token: str - token_type: str - - -class TokenData(BaseModel): - sub: Optional[str] = None - scopes: List[str] = [] diff --git a/backend/sql_app.db b/backend/sql_app.db deleted file mode 100644 index bb03a95a97f3e080f4dab8572f413ba76c7fc2eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94208 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 diff --git a/backend/static/author.jpg b/backend/static/author.jpg deleted file mode 100644 index b5901c1f7ad0d5e58f2a1953ae7a462dde7ff4df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45621 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 MiABU*%L$+V*)WFAX8-^I diff --git a/backend/test_main.http b/backend/test_main.http new file mode 100644 index 0000000..a2d81a9 --- /dev/null +++ b/backend/test_main.http @@ -0,0 +1,11 @@ +# Test your FastAPI endpoints + +GET http://127.0.0.1:8000/ +Accept: application/json + +### + +GET http://127.0.0.1:8000/hello/User +Accept: application/json + +### diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index ebc1f25..9803b20 100644 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -1,11 +1,5 @@ #!/usr/bin/env python3 # _*_ coding: utf-8 _*_ -# @Time : 2021/11/11 15:42 # @Author : zxiaosi -# @desc : 工具类 -from .create_dir import create_dir -from .check_enum import check_or_enum -from .ip_address import by_ip_get_address -from .obj_dict import obj_as_dict, list_obj_as_dict -from .resp_code import resp_200, resp_400, resp_401, resp_403, resp_404, resp_422, resp_500 -from .custom_exc import IdNotExist, SetRedis, UserNotExist, AccessTokenFail, ErrorUser, IpError, PermissionNotEnough +# @Time : 2022/7/1 14:41 +# @desc : diff --git a/backend/utils/check_enum.py b/backend/utils/check_enum.py deleted file mode 100644 index e81e858..0000000 --- a/backend/utils/check_enum.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/4/28 18:54 -# @Author : zxiaosi -# @desc : 创建枚举属性 -from sqlalchemy import Enum, CheckConstraint, String, Column - -from core import settings - - -# sex = Column(String(1), CheckConstraint("sex in ('1', '2', '3')")) # sqlite -# sex = Column(Enum('0', '1')) # mysql -# sex = Column(Enum('0', '1', name='sex_enum')) # postgresql - -def check_or_enum(name: str, enumList: [], comment: str = ''): - """ - - sqlite不支持枚举, 用check约束 - - mysql和postgresql用check没效果, 用枚举限制 - """ - if settings.DATABASE_URI.startswith('sqlite'): - return Column(String(1), CheckConstraint(f"{name} in {tuple(enumList)}"), nullable=False, comment=comment) - else: - return Column(Enum(*enumList, name=name), nullable=False, comment=comment) diff --git a/backend/utils/create_dir.py b/backend/utils/create_dir.py deleted file mode 100644 index 5405bb1..0000000 --- a/backend/utils/create_dir.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/3/4 15:48 -# @Author : zxiaosi -# @desc : 创建文件夹 -import os - - -# 请不要随意移动该文件,创建文件夹是根据当前文件位置来创建 -def create_dir(file_name: str) -> str: - """ 创建文件夹 """ - current_path = os.path.dirname(__file__) # 获取当前文件夹 - - base_path = os.path.abspath(os.path.join(current_path, "..")) # 获取当前文件夹的上一层文件 - - path = base_path + os.sep + file_name + os.sep # 拼接日志文件夹的路径 - - os.makedirs(path, exist_ok=True) # 如果文件夹不存在就创建 - - return path diff --git a/backend/utils/custom_exc.py b/backend/utils/custom_exc.py deleted file mode 100644 index c40553e..0000000 --- a/backend/utils/custom_exc.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/1/8 13:48 -# @Author : zxiaosi -# @desc : 自定义异常 -class IpError(Exception): - """ ip错误 """ - - def __init__(self, err_desc: str = "ip错误"): - self.err_desc = err_desc - - -class SetRedis(Exception): - """ Redis存储失败 """ - - def __init__(self, err_desc: str = "Redis存储失败"): - self.err_desc = err_desc - - -class IdNotExist(Exception): - """ 查询id不存在 """ - - def __init__(self, err_desc: str = "查询id不存在"): - self.err_desc = err_desc - - -class UserNotExist(Exception): - """ 用户不存在 """ - - def __init__(self, err_desc: str = "用户不存在"): - self.err_desc = err_desc - - -class AccessTokenFail(Exception): - """ 访问令牌失败 """ - - def __init__(self, err_desc: str = "访问令牌失败"): - self.err_desc = err_desc - - -class ErrorUser(Exception): - """ 错误的用户名或密码 """ - - def __init__(self, err_desc: str = "错误的用户名或密码"): - self.err_desc = err_desc - - -class PermissionNotEnough(Exception): - """ 权限不足,拒绝访问 """ - - def __init__(self, err_desc: str = "权限不足,拒绝访问"): - self.err_desc = err_desc diff --git a/backend/utils/ip_address.py b/backend/utils/ip_address.py deleted file mode 100644 index 4d944f8..0000000 --- a/backend/utils/ip_address.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/3/4 15:21 -# @Author : zxiaosi -# @desc : 根据ip获取地址 -import re -from urllib import request -from ipaddress import ip_address - -from utils.custom_exc import IpError - - -def verify_ip(ip): - """ 验证ip格式是否正确 """ - try: - return str(ip_address(ip)) - except Exception as e: - raise IpError(f'错误的IP格式! -- {e}') - - -def by_ip_get_address(ip) -> str: - """ 根据ip获取地址 """ - verify_ip(ip) - - req = request.Request(f"http://ip.ws.126.net/ipquery?ip={ip}") - response = request.urlopen(req).read().decode('gbk') # 获取响应 - - handle_address = re.findall(r'"([^"]*)"', response) - if handle_address: - return handle_address[0] + handle_address[1] - else: - return '广东省广州市' diff --git a/backend/utils/obj_dict.py b/backend/utils/obj_dict.py deleted file mode 100644 index 1a1b7b4..0000000 --- a/backend/utils/obj_dict.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/4/11 11:05 -# @Author : zxiaosi -# @desc : 对象转字典 -from sqlalchemy import inspect - - -def obj_as_dict(obj): - """ ORM对象转字典 """ - return {c.key: getattr(obj, c.key) for c in inspect(obj).mapper.column_attrs} if obj else None - - -def list_obj_as_dict(list_obj): - """ ORM列表对象转字典 """ - return [obj_as_dict(obj) for obj in list_obj] diff --git a/backend/utils/permission_assign.py b/backend/utils/permission_assign.py deleted file mode 100644 index 3215384..0000000 --- a/backend/utils/permission_assign.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2022/3/9 20:44 -# @Author : zxiaosi -# @desc : 权限分配 -from typing import List, Union - -import crud -from core import settings -from crud.admin import CRUDAdmin -from crud.teacher import CRUDTeacher -from crud.student import CRUDStudent -from utils import PermissionNotEnough - - -def by_scopes_get_crud(scopes: List[Union[str]]) -> Union[CRUDAdmin, CRUDTeacher, CRUDStudent]: - """ 根据scopes(权限)分配不同的权限 """ - if 'admin' in scopes: - return crud.admin - elif 'teacher' in scopes: - return crud.teacher - elif 'student' in scopes: - return crud.student - else: - raise PermissionNotEnough - - -def handle_oauth2_scopes(): - """ 配置 OAuth2PasswordBearer 的 scopes """ - join_dict = {} - for item in settings.PERMISSION_DATA: - join_dict.update(item) - return join_dict - - -def generate_permission_data(): - """ 生成 permission 表数据 """ - data = [] - for item in settings.PERMISSION_DATA: - (key, value), = item.items() # , 一定要存在 - data.append({'name': key, 'desc': value}) - return data diff --git a/backend/utils/resp_code.py b/backend/utils/resp_code.py deleted file mode 100644 index 4291419..0000000 --- a/backend/utils/resp_code.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -# _*_ coding: utf-8 _*_ -# @Time : 2021/11/13 13:41 -# @Author : zxiaosi -# @desc : 响应状态码 -from typing import Union, Any, Optional -from starlette import status -from starlette.responses import Response -from fastapi.responses import ORJSONResponse - -from core.logger import logger - - -def resp_200(*, data: Any = '', msg: str = "Success") -> dict: - logger.info(msg) - return {'code': 200, 'data': data, 'msg': msg} - - -def resp_400(code: int = 400, data: str = None, msg: str = "请求错误(400)") -> Response: - return ORJSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content={'code': code, 'msg': msg, 'data': data}) - - -def resp_401(*, data: str = None, msg: str = "未授权,请重新登录(401)") -> Response: - return ORJSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content={'code': 401, 'msg': msg, 'data': data}) - - -def resp_403(*, data: str = None, msg: str = "拒绝访问(403)") -> Response: - return ORJSONResponse(status_code=status.HTTP_403_FORBIDDEN, content={'code': 403, 'msg': msg, 'data': data}) - - -def resp_404(*, data: str = None, msg: str = "请求出错(404)") -> Response: - return ORJSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={'code': 404, 'msg': msg, 'data': data}) - - -def resp_422(*, data: str = None, msg: Union[list, dict, str] = "不可处理的实体") -> Response: - return ORJSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - content={'code': 422, 'msg': msg, 'data': data}) - - -def resp_500(*, data: str = None, msg: Union[list, dict, str] = "服务器错误(500)") -> Response: - return ORJSONResponse(headers={'Access-Control-Allow-Origin': '*'}, - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={'code': 500, 'msg': msg, 'data': data}) - - -def resp_502(*, data: str = None, msg: str = "网络错误(502)") -> Response: - return ORJSONResponse(status_code=status.HTTP_502_BAD_GATEWAY, content={'code': 502, 'msg': msg, 'data': data}) - - -# ------------------------------------------- 以下不常用 ------------------------------------------- - -def resp_406(*, data: str = None, msg: str = "请求的格式不可得(406)") -> Response: - return ORJSONResponse(status_code=status.HTTP_406_NOT_ACCEPTABLE, content={'code': 406, 'msg': msg, 'data': data}) - - -def resp_408(*, data: str = None, msg: str = "请求超时(408)") -> Response: - return ORJSONResponse(status_code=status.HTTP_408_REQUEST_TIMEOUT, content={'code': 408, 'msg': msg, 'data': data}) - - -def resp_410(*, data: str = None, msg: str = "请求的资源被永久删除,且不会再得到的(410)") -> Response: - return ORJSONResponse(status_code=status.HTTP_410_GONE, content={'code': 410, 'msg': msg, 'data': data}) - - -def resp_501(*, data: str = None, msg: str = "服务未实现(501)") -> Response: - return ORJSONResponse(status_code=status.HTTP_501_NOT_IMPLEMENTED, content={'code': 501, 'msg': msg, 'data': data}) - - -def resp_503(*, data: str = None, msg: str = "服务不可用(503)") -> Response: - return ORJSONResponse(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - content={'code': 503, 'msg': msg, 'data': data}) - - -def resp_504(*, data: str = None, msg: str = "网络超时(504)") -> Response: - return ORJSONResponse(status_code=status.HTTP_504_GATEWAY_TIMEOUT, content={'code': 504, 'msg': msg, 'data': data}) - - -def resp_505(*, data: str = None, msg: str = "HTTP版本不受支持(505)") -> Response: - return ORJSONResponse(status_code=status.HTTP_505_HTTP_VERSION_NOT_SUPPORTED, - content={'code': 505, 'msg': msg, 'data': data}) diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 38adffa..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -.DS_Store -dist -dist-ssr -coverage -*.local - -/cypress/videos/ -/cypress/screenshots/ - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json deleted file mode 100644 index 806eacd..0000000 --- a/frontend/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["johnsoncodehk.volar", "johnsoncodehk.vscode-typescript-vue-plugin"] -} diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index f311dc8..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,133 +0,0 @@ -## Vue3+ElementPlus+Vite - -## 版本 - -+ `V1.0` 初始化项目 -+ `V1.1` 调试了接口(测试页面)&&引入阿里的图标 -+ `V1.2` 测试数据的增删改查已完成 -+ `V1.3` 添加了院系表的增删改查,以及其相关字段的验证规则,添加了额外功能排序 -+ `V1.4` 首页数据更改,待办事项可以添加(临时数据) -+ `V1.5` 添加了专业表的增删改查 -+ `V1.6` 调试了专业表中下拉框 -+ `V1.6` 添加了教师表 -+ `V1.7` 添加了学生表、课程表、选课表 -+ `V1.8` 重构代码 -+ `V1.9` 封装了部分组件 -+ `V2.0` 重构表格代码 -+ `V2.1` 封装了部分方法,重构表格组件 -+ `V2.1` 修改了校验规则 -+ `v2.2` 修复打包出现的小问题 -+ `v2.3` 部署成功 -+ `v2.4` 添加加载效果 -+ `v2.5` 通过vuex优化取数据的方式 -+ `v2.6` 优化请求方式&&部分数据添加到redis中 -+ `v2.7` 首页数据添加到redis中 -+ `v2.8` 加入TS -+ `v2.9` 实现图片上传 -+ `v3.0` 分离文件(vue与ts分离) -+ `v3.1` 简单实现权限管理 -+ `v3.2` 整理代码 -+ `v3.3` 语言详情更换为Github公共API -+ `v3.4` 简单实现学生选课 -+ `v3.5` 简单实现教师讲授课程 - -## 安装 - -1. 进到 `frontend` 目录下 - -2. `npm install` 安装所需的包 - -3. 启动服务 - - + `npm run dev` 运行项目 - + 报错见下面 - - >服务接口:http://localhost:3000/ - > - >用户名:admin - > - >密码:123 - -## 项目截图 - -+ 登录界面(待定) - - ![](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/frontend-login.png) - -+ 后台界面(待定) - - ![](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/frontend-%E5%90%8E%E5%8F%B0.png) - -## 项目目录 - -```sh -|-- frontend - |-- dist # 输出文件 - |-- src # 核心文件 - |-- api # 接口 - |-- assets # 静态文件 - |-- commponents # 组件 - |-- request # axios请求 - |-- router # 路由 - |-- stores # 状态管理 - |-- types # 公用接口 - |-- utils # 工具类 - |-- views # 页面 - |-- App.vue # app - |-- main.ts # 主文件 - |-- index.html # 首页 - |-- env.d.ts # - |-- package-lock.json # 包版本锁 - |-- package.json # 安装的包 - |-- README.md # 文档说明 - |-- tsconfig.json # ts配置 - |-- tsconfig.vite-config.json # 兼用node与vite - |-- vite.config.js # vite配置 - -``` - - - -## 常见错误 - -### 1. `npm run dev` 报错 - -+ 内容如下 - - ```sh - > vue-manage-system@5.1.0 dev - > vite - - events.js:377 - throw er; // Unhandled 'error' event - ^ - - Error: spawn D:\Vscode\Vue-Manage-System\node_modules\esbuild\esbuild.exe ENOENT - at Process.ChildProcess._handle.onexit (internal/child_process.js:274:19) - at onErrorNT (internal/child_process.js:469:16) - at processTicksAndRejections (internal/process/task_queues.js:82:21) - Emitted 'error' event on ChildProcess instance at: - at Process.ChildProcess._handle.onexit (internal/child_process.js:280:12) - at onErrorNT (internal/child_process.js:469:16) - at processTicksAndRejections (internal/process/task_queues.js:82:21) { - errno: -4058, - code: 'ENOENT', - syscall: 'spawn D:\\Vscode\\Vue-Manage-System\\node_modules\\esbuild\\esbuild.exe', - path: 'D:\\Vscode\\Vue-Manage-System\\node_modules\\esbuild\\esbuild.exe', - spawnargs: [ '--service=0.12.9', '--ping' ] - } - ``` - -+ 错误原因:`vite` 安装失败 - -+ 解决措施:输入下面命令 `node .\node_modules\esbuild\install.js`,重新运行 `npm run dev` - -### 2. `npm run dev` 报错 - -+ 内容如下 - - ![](https://gitee.com/zxiaosi/image/raw/master/Project/Vue+FastAPI/%E6%8A%A5%E9%94%992.png) - -+ 错误原因:`端口号被占用` -+ 解决措施:关闭其他占用 `3000`端口号的应用,重新运行 `npm run dev` - diff --git a/frontend/env.d.ts b/frontend/env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/frontend/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index b86e8f1..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - 学生选课系统 - - - - -
- - - diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index ba8f6e2..0000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,3256 +0,0 @@ -{ - "name": "frontend", - "version": "3.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "frontend", - "version": "3.1.0", - "dependencies": { - "axios": "^0.27.2", - "echarts": "^5.3.2", - "element-plus": "^2.1.11", - "pinia": "^2.0.13", - "vue": "^3.2.33", - "vue-router": "^4.0.14" - }, - "devDependencies": { - "@types/node": "^16.11.27", - "@vitejs/plugin-vue": "^2.3.1", - "@vitejs/plugin-vue-jsx": "^1.3.10", - "@vue/tsconfig": "^0.1.3", - "typescript": "~4.6.3", - "vite": "^2.9.5", - "vue-tsc": "^0.34.7" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.17.10.tgz", - "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.17.10.tgz", - "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.10", - "@babel/helper-compilation-targets": "^7.17.10", - "@babel/helper-module-transforms": "^7.17.7", - "@babel/helpers": "^7.17.9", - "@babel/parser": "^7.17.10", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.10", - "@babel/types": "^7.17.10", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.17.10.tgz", - "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.10", - "@jridgewell/gen-mapping": "^0.1.0", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", - "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz", - "integrity": "sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-member-expression-to-functions": "^7.17.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", - "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.17.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", - "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.17.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", - "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.17.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.17.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", - "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.17.9.tgz", - "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.9", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.17.9.tgz", - "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.17.10.tgz", - "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz", - "integrity": "sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.16.8", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", - "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-typescript": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.17.9.tgz", - "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", - "deprecated": "[WARNING] Use 7.17.9 instead of 7.17.10, reason: https://github.com/babel/babel/issues/14525", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.9", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.9", - "@babel/types": "^7.17.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.17.10.tgz", - "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "3.4.1", - "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz", - "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@element-plus/icons-vue": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-1.1.4.tgz", - "integrity": "sha512-Iz/nHqdp1sFPmdzRwHkEQQA3lKvoObk8azgABZ81QUOpW9s/lUyQVUSh0tNtEPZXQlKwlSh7SPgoVxzrE0uuVQ==", - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "0.6.2", - "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-0.6.2.tgz", - "integrity": "sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg==" - }, - "node_modules/@floating-ui/dom": { - "version": "0.4.5", - "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-0.4.5.tgz", - "integrity": "sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==", - "dependencies": { - "@floating-ui/core": "^0.6.2" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.11", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.11.tgz", - "integrity": "sha512-RllI476aSMsxzeI9TtlSMoNTgHDxEmnl6GkkHwhr0vdL8W+0WuesyI8Vd3rBOfrwtPXbPxdT9ADJdiOKgzxPQA==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@popperjs/core": { - "name": "@sxzz/popperjs-es", - "version": "2.11.7", - "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", - "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" - }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", - "dev": true, - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/@types/lodash": { - "version": "4.14.182", - "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.182.tgz", - "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.6", - "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.6.tgz", - "integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "16.11.33", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.11.33.tgz", - "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==", - "dev": true - }, - "node_modules/@vitejs/plugin-vue": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz", - "integrity": "sha512-umyypfSHS4kQLdYAnJHhaASq7FRzNCdvcRoQ3uYGNk1/M4a+hXUd7ysN7BLhCrWH6uBokyCkFeUAaFDzSaaSrQ==", - "dev": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "vite": "^2.5.10", - "vue": "^3.2.25" - } - }, - "node_modules/@vitejs/plugin-vue-jsx": { - "version": "1.3.10", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-1.3.10.tgz", - "integrity": "sha512-Cf5zznh4yNMiEMBfTOztaDVDmK1XXfgxClzOSUVUc8WAmHzogrCUeM8B05ABzuGtg0D1amfng+mUmSIOFGP3Pw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.17.9", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-typescript": "^7.16.8", - "@rollup/pluginutils": "^4.2.0", - "@vue/babel-plugin-jsx": "^1.1.1", - "hash-sum": "^2.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@volar/code-gen": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/code-gen/-/code-gen-0.34.12.tgz", - "integrity": "sha512-5GAPsSjScnfMmMoh9qLW7CWQjjnT0fTUsPWnDMMjKIOqQF9J5mOyo7rprt1VzX63zwayqFfx7V8W3EVNhUCE3w==", - "dev": true, - "dependencies": { - "@volar/source-map": "0.34.12" - } - }, - "node_modules/@volar/source-map": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-0.34.12.tgz", - "integrity": "sha512-07imKws1cz9g3eo0VWXdioNfc1eCjqwK7GsxVuYSc7OCzKASt9PywUW+F39QGB9g2Kewof+PjCVIPeGqGRECTA==", - "dev": true - }, - "node_modules/@volar/vue-code-gen": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/vue-code-gen/-/vue-code-gen-0.34.12.tgz", - "integrity": "sha512-PFcft62eIvQvcB6H2Z88fouTu2JmYwimORziFGr3LlGriQUEVmyDtqddtb+E+j2wGChtLkh6hf1py94C5VpI/Q==", - "dev": true, - "dependencies": { - "@volar/code-gen": "0.34.12", - "@volar/source-map": "0.34.12", - "@vue/compiler-core": "^3.2.31", - "@vue/compiler-dom": "^3.2.31", - "@vue/shared": "^3.2.31" - } - }, - "node_modules/@volar/vue-typescript": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/vue-typescript/-/vue-typescript-0.34.12.tgz", - "integrity": "sha512-mY5cZ2OFOKt1HcCuoX1ViEsccltX3mdACk/FAjrSZTrilTdVHI1zkmQlrpCSnjmE1qowd8I6YoVt7THCaVrHdg==", - "dev": true, - "dependencies": { - "@volar/code-gen": "0.34.12", - "@volar/source-map": "0.34.12", - "@volar/vue-code-gen": "0.34.12", - "@vue/compiler-sfc": "^3.2.31", - "@vue/reactivity": "^3.2.31" - } - }, - "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.2.tgz", - "integrity": "sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==", - "dev": true - }, - "node_modules/@vue/babel-plugin-jsx": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz", - "integrity": "sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "@vue/babel-helper-vue-transform-on": "^1.0.2", - "camelcase": "^6.0.0", - "html-tags": "^3.1.0", - "svg-tags": "^1.0.0" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz", - "integrity": "sha512-AAmr52ji3Zhk7IKIuigX2osWWsb2nQE5xsdFYjdnmtQ4gymmqXbjLvkSE174+fF3A3kstYrTgGkqgOEbsdLDpw==", - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.33", - "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.33.tgz", - "integrity": "sha512-GhiG1C8X98Xz9QUX/RlA6/kgPBWJkjq0Rq6//5XTAGSYrTMBgcLpP9+CnlUg1TFxnnCVughAG+KZl28XJqw8uQ==", - "dependencies": { - "@vue/compiler-core": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.33.tgz", - "integrity": "sha512-H8D0WqagCr295pQjUYyO8P3IejM3vEzeCO1apzByAEaAR/WimhMYczHfZVvlCE/9yBaEu/eu9RdiWr0kF8b71Q==", - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.33", - "@vue/compiler-dom": "3.2.33", - "@vue/compiler-ssr": "3.2.33", - "@vue/reactivity-transform": "3.2.33", - "@vue/shared": "3.2.33", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.33.tgz", - "integrity": "sha512-XQh1Xdk3VquDpXsnoCd7JnMoWec9CfAzQDQsaMcSU79OrrO2PNR0ErlIjm/mGq3GmBfkQjzZACV+7GhfRB8xMQ==", - "dependencies": { - "@vue/compiler-dom": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.1.4", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz", - "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==" - }, - "node_modules/@vue/reactivity": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.33.tgz", - "integrity": "sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ==", - "dependencies": { - "@vue/shared": "3.2.33" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.33.tgz", - "integrity": "sha512-4UL5KOIvSQb254aqenW4q34qMXbfZcmEsV/yVidLUgvwYQQ/D21bGX3DlgPUGI3c4C+iOnNmDCkIxkILoX/Pyw==", - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.33", - "@vue/shared": "3.2.33", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz", - "integrity": "sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw==", - "dependencies": { - "@vue/reactivity": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz", - "integrity": "sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw==", - "dependencies": { - "@vue/runtime-core": "3.2.33", - "@vue/shared": "3.2.33", - "csstype": "^2.6.8" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.33.tgz", - "integrity": "sha512-4jpJHRD4ORv8PlbYi+/MfP8ec1okz6rybe36MdpkDrGIdEItHEUyaHSKvz+ptNEyQpALmmVfRteHkU9F8vxOew==", - "dependencies": { - "@vue/compiler-ssr": "3.2.33", - "@vue/shared": "3.2.33" - }, - "peerDependencies": { - "vue": "3.2.33" - } - }, - "node_modules/@vue/shared": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.33.tgz", - "integrity": "sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==" - }, - "node_modules/@vue/tsconfig": { - "version": "0.1.3", - "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz", - "integrity": "sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg==", - "dev": true, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@vueuse/core": { - "version": "8.4.2", - "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-8.4.2.tgz", - "integrity": "sha512-dUVU96lii1ZdWoNJXauQNt+4QrHz1DKbuW+y6pDR2N10q7rGZJMDU7pQeMcC2XeosX7kMODfaBuqsF03NozzLg==", - "dependencies": { - "@vueuse/metadata": "8.4.2", - "@vueuse/shared": "8.4.2", - "vue-demi": "*" - }, - "peerDependencies": { - "@vue/composition-api": "^1.1.0", - "vue": "^2.6.0 || ^3.2.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@vueuse/core/node_modules/@vueuse/shared": { - "version": "8.4.2", - "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-8.4.2.tgz", - "integrity": "sha512-hILXMEjL8YQhj1LHN/HZ49UThyfk8irTjhele2nW+L3N55ElFUBGB/f4w0rg8EW+/suhqv7kJJPTZzvHCqxlIw==", - "dependencies": { - "vue-demi": "*" - }, - "peerDependencies": { - "@vue/composition-api": "^1.1.0", - "vue": "^2.6.0 || ^3.2.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.12.5", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz", - "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "8.4.2", - "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-8.4.2.tgz", - "integrity": "sha512-2BIj++7P0/I5dfMsEe8q7Kw0HqVAjVcyNOd9+G22/ILUC9TVLTeYOuJ1kwa1Gpr+0LWKHc6GqHiLWNL33+exoQ==" - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/async-validator": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.1.1.tgz", - "integrity": "sha512-p4DO/JXwjs8klJyJL8Q2oM4ks5fUTze/h5k10oPPKMiLe1fj3G1QMzPHNmN1Py4ycOk7WlO2DcGXv1qiESJCZA==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmmirror.com/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.20.3", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.20.3.tgz", - "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001332", - "electron-to-chromium": "^1.4.118", - "escalade": "^3.1.1", - "node-releases": "^2.0.3", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001339", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001339.tgz", - "integrity": "sha512-Es8PiVqCe+uXdms0Gu5xP5PF2bxLR7OBp3wUzUnuO7OHzhOfCyg3hdiGWVPVxhiuniOzng+hTc1u3fEQ0TlkSQ==", - "dev": true - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/csstype": { - "version": "2.6.20", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz", - "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" - }, - "node_modules/dayjs": { - "version": "1.11.2", - "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.2.tgz", - "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/echarts": { - "version": "5.3.2", - "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.3.2.tgz", - "integrity": "sha512-LWCt7ohOKdJqyiBJ0OGBmE9szLdfA9sGcsMEi+GGoc6+Xo75C+BkcT/6NNGRHAWtnQl2fNow05AQjznpap28TQ==", - "dependencies": { - "tslib": "2.3.0", - "zrender": "5.3.1" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.137", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", - "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==", - "dev": true - }, - "node_modules/element-plus": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.0.tgz", - "integrity": "sha512-zxmAFEAa1T/n09rR+NozXcWl5CjaFtqoaxhFSafag0dgc90tgEHitDXfegdFAl4ahugdNTqu9aLzngx3VhDAtA==", - "dependencies": { - "@ctrl/tinycolor": "^3.4.1", - "@element-plus/icons-vue": "^1.1.4", - "@floating-ui/dom": "^0.4.5", - "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.6", - "@types/lodash": "^4.14.182", - "@types/lodash-es": "^4.17.6", - "@vueuse/core": "^8.2.6", - "async-validator": "^4.0.7", - "dayjs": "^1.11.1", - "escape-html": "^1.0.3", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "lodash-unified": "^1.0.2", - "memoize-one": "^6.0.0", - "normalize-wheel-es": "^1.1.2" - }, - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/esbuild": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.38.tgz", - "integrity": "sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "esbuild-android-64": "0.14.38", - "esbuild-android-arm64": "0.14.38", - "esbuild-darwin-64": "0.14.38", - "esbuild-darwin-arm64": "0.14.38", - "esbuild-freebsd-64": "0.14.38", - "esbuild-freebsd-arm64": "0.14.38", - "esbuild-linux-32": "0.14.38", - "esbuild-linux-64": "0.14.38", - "esbuild-linux-arm": "0.14.38", - "esbuild-linux-arm64": "0.14.38", - "esbuild-linux-mips64le": "0.14.38", - "esbuild-linux-ppc64le": "0.14.38", - "esbuild-linux-riscv64": "0.14.38", - "esbuild-linux-s390x": "0.14.38", - "esbuild-netbsd-64": "0.14.38", - "esbuild-openbsd-64": "0.14.38", - "esbuild-sunos-64": "0.14.38", - "esbuild-windows-32": "0.14.38", - "esbuild-windows-64": "0.14.38", - "esbuild-windows-arm64": "0.14.38" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz", - "integrity": "sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz", - "integrity": "sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz", - "integrity": "sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz", - "integrity": "sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz", - "integrity": "sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz", - "integrity": "sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz", - "integrity": "sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz", - "integrity": "sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz", - "integrity": "sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz", - "integrity": "sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz", - "integrity": "sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz", - "integrity": "sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz", - "integrity": "sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz", - "integrity": "sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz", - "integrity": "sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz", - "integrity": "sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz", - "integrity": "sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz", - "integrity": "sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz", - "integrity": "sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz", - "integrity": "sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "node_modules/follow-redirects": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.0.tgz", - "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-sum": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz", - "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", - "dev": true - }, - "node_modules/html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "node_modules/lodash-unified": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.2.tgz", - "integrity": "sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g==", - "peerDependencies": { - "@types/lodash-es": "*", - "lodash": "*", - "lodash-es": "*" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.4.tgz", - "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", - "dev": true - }, - "node_modules/normalize-wheel-es": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.1.2.tgz", - "integrity": "sha512-scX83plWJXYH1J4+BhAuIHadROzxX0UBF3+HuZNY2Ks8BciE7tSTQ+5JhTsvzjaO0/EJdm4JBGrfObKxFf3Png==" - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/pinia": { - "version": "2.0.14", - "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.14.tgz", - "integrity": "sha512-0nPuZR4TetT/WcLN+feMSjWJku3SQU7dBbXC6uw+R6FLQJCsg+/0pzXyD82T1FmAYe0lsx+jnEDQ1BLgkRKlxA==", - "dependencies": { - "@vue/devtools-api": "^6.1.4", - "vue-demi": "*" - }, - "peerDependencies": { - "@vue/composition-api": "^1.4.0", - "typescript": ">=4.4.4", - "vue": "^2.6.14 || ^3.2.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/pinia/node_modules/vue-demi": { - "version": "0.12.5", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz", - "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/postcss": { - "version": "8.4.13", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.13.tgz", - "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", - "dependencies": { - "nanoid": "^3.3.3", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - } - }, - "node_modules/rollup": { - "version": "2.72.1", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.72.1.tgz", - "integrity": "sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "node_modules/typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", - "devOptional": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/vite": { - "version": "2.9.8", - "resolved": "https://registry.npmmirror.com/vite/-/vite-2.9.8.tgz", - "integrity": "sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw==", - "dev": true, - "dependencies": { - "esbuild": "^0.14.27", - "postcss": "^8.4.13", - "resolve": "^1.22.0", - "rollup": "^2.59.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": ">=12.2.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "less": "*", - "sass": "*", - "stylus": "*" - }, - "peerDependenciesMeta": { - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.33.tgz", - "integrity": "sha512-si1ExAlDUrLSIg/V7D/GgA4twJwfsfgG+t9w10z38HhL/HA07132pUQ2KuwAo8qbCyMJ9e6OqrmWrOCr+jW7ZQ==", - "dependencies": { - "@vue/compiler-dom": "3.2.33", - "@vue/compiler-sfc": "3.2.33", - "@vue/runtime-dom": "3.2.33", - "@vue/server-renderer": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "node_modules/vue-router": { - "version": "4.0.15", - "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.0.15.tgz", - "integrity": "sha512-xa+pIN9ZqORdIW1MkN2+d9Ui2pCM1b/UMgwYUCZOiFYHAvz/slKKBDha8DLrh5aCG/RibtrpyhKjKOZ85tYyWg==", - "dependencies": { - "@vue/devtools-api": "^6.0.0" - }, - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/vue-tsc": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-0.34.12.tgz", - "integrity": "sha512-CmuqLXHEW5UvS8UpT2RYom5MzOWBD142PLXxDX0ARdZ/u1oLobA3od4XY2XZACQYCFCzjTvfD1H5wrWwiGwoUA==", - "dev": true, - "dependencies": { - "@volar/vue-typescript": "0.34.12" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/zrender": { - "version": "5.3.1", - "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.3.1.tgz", - "integrity": "sha512-7olqIjy0gWfznKr6vgfnGBk7y4UtdMvdwFmK92vVQsQeDPyzkHW1OlrLEKg6GHz1W5ePf0FeN1q2vkl/HFqhXw==", - "dependencies": { - "tslib": "2.3.0" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/compat-data": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.17.10.tgz", - "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", - "dev": true - }, - "@babel/core": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.17.10.tgz", - "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.10", - "@babel/helper-compilation-targets": "^7.17.10", - "@babel/helper-module-transforms": "^7.17.7", - "@babel/helpers": "^7.17.9", - "@babel/parser": "^7.17.10", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.10", - "@babel/types": "^7.17.10", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.17.10.tgz", - "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", - "dev": true, - "requires": { - "@babel/types": "^7.17.10", - "@jridgewell/gen-mapping": "^0.1.0", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", - "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.17.10", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz", - "integrity": "sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-member-expression-to-functions": "^7.17.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", - "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.17.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", - "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", - "dev": true, - "requires": { - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-module-transforms": { - "version": "7.17.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", - "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.17.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-simple-access": { - "version": "7.17.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", - "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", - "dev": true, - "requires": { - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true - }, - "@babel/helpers": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.17.9.tgz", - "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.9", - "@babel/types": "^7.17.0" - } - }, - "@babel/highlight": { - "version": "7.17.9", - "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.17.9.tgz", - "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.17.10.tgz", - "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==" - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz", - "integrity": "sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.16.8", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", - "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-typescript": "^7.16.7" - } - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.17.9.tgz", - "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.9", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.9", - "@babel/types": "^7.17.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.17.10", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.17.10.tgz", - "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "@ctrl/tinycolor": { - "version": "3.4.1", - "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz", - "integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==" - }, - "@element-plus/icons-vue": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-1.1.4.tgz", - "integrity": "sha512-Iz/nHqdp1sFPmdzRwHkEQQA3lKvoObk8azgABZ81QUOpW9s/lUyQVUSh0tNtEPZXQlKwlSh7SPgoVxzrE0uuVQ==", - "requires": {} - }, - "@floating-ui/core": { - "version": "0.6.2", - "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-0.6.2.tgz", - "integrity": "sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg==" - }, - "@floating-ui/dom": { - "version": "0.4.5", - "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-0.4.5.tgz", - "integrity": "sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==", - "requires": { - "@floating-ui/core": "^0.6.2" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.11", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.11.tgz", - "integrity": "sha512-RllI476aSMsxzeI9TtlSMoNTgHDxEmnl6GkkHwhr0vdL8W+0WuesyI8Vd3rBOfrwtPXbPxdT9ADJdiOKgzxPQA==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@popperjs/core": { - "version": "npm:@sxzz/popperjs-es@2.11.7", - "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", - "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" - }, - "@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", - "dev": true, - "requires": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - } - }, - "@types/lodash": { - "version": "4.14.182", - "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.182.tgz", - "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==" - }, - "@types/lodash-es": { - "version": "4.17.6", - "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.6.tgz", - "integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==", - "requires": { - "@types/lodash": "*" - } - }, - "@types/node": { - "version": "16.11.33", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.11.33.tgz", - "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==", - "dev": true - }, - "@vitejs/plugin-vue": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz", - "integrity": "sha512-umyypfSHS4kQLdYAnJHhaASq7FRzNCdvcRoQ3uYGNk1/M4a+hXUd7ysN7BLhCrWH6uBokyCkFeUAaFDzSaaSrQ==", - "dev": true, - "requires": {} - }, - "@vitejs/plugin-vue-jsx": { - "version": "1.3.10", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-1.3.10.tgz", - "integrity": "sha512-Cf5zznh4yNMiEMBfTOztaDVDmK1XXfgxClzOSUVUc8WAmHzogrCUeM8B05ABzuGtg0D1amfng+mUmSIOFGP3Pw==", - "dev": true, - "requires": { - "@babel/core": "^7.17.9", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-typescript": "^7.16.8", - "@rollup/pluginutils": "^4.2.0", - "@vue/babel-plugin-jsx": "^1.1.1", - "hash-sum": "^2.0.0" - } - }, - "@volar/code-gen": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/code-gen/-/code-gen-0.34.12.tgz", - "integrity": "sha512-5GAPsSjScnfMmMoh9qLW7CWQjjnT0fTUsPWnDMMjKIOqQF9J5mOyo7rprt1VzX63zwayqFfx7V8W3EVNhUCE3w==", - "dev": true, - "requires": { - "@volar/source-map": "0.34.12" - } - }, - "@volar/source-map": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-0.34.12.tgz", - "integrity": "sha512-07imKws1cz9g3eo0VWXdioNfc1eCjqwK7GsxVuYSc7OCzKASt9PywUW+F39QGB9g2Kewof+PjCVIPeGqGRECTA==", - "dev": true - }, - "@volar/vue-code-gen": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/vue-code-gen/-/vue-code-gen-0.34.12.tgz", - "integrity": "sha512-PFcft62eIvQvcB6H2Z88fouTu2JmYwimORziFGr3LlGriQUEVmyDtqddtb+E+j2wGChtLkh6hf1py94C5VpI/Q==", - "dev": true, - "requires": { - "@volar/code-gen": "0.34.12", - "@volar/source-map": "0.34.12", - "@vue/compiler-core": "^3.2.31", - "@vue/compiler-dom": "^3.2.31", - "@vue/shared": "^3.2.31" - } - }, - "@volar/vue-typescript": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/@volar/vue-typescript/-/vue-typescript-0.34.12.tgz", - "integrity": "sha512-mY5cZ2OFOKt1HcCuoX1ViEsccltX3mdACk/FAjrSZTrilTdVHI1zkmQlrpCSnjmE1qowd8I6YoVt7THCaVrHdg==", - "dev": true, - "requires": { - "@volar/code-gen": "0.34.12", - "@volar/source-map": "0.34.12", - "@volar/vue-code-gen": "0.34.12", - "@vue/compiler-sfc": "^3.2.31", - "@vue/reactivity": "^3.2.31" - } - }, - "@vue/babel-helper-vue-transform-on": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.2.tgz", - "integrity": "sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==", - "dev": true - }, - "@vue/babel-plugin-jsx": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz", - "integrity": "sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "@vue/babel-helper-vue-transform-on": "^1.0.2", - "camelcase": "^6.0.0", - "html-tags": "^3.1.0", - "svg-tags": "^1.0.0" - } - }, - "@vue/compiler-core": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz", - "integrity": "sha512-AAmr52ji3Zhk7IKIuigX2osWWsb2nQE5xsdFYjdnmtQ4gymmqXbjLvkSE174+fF3A3kstYrTgGkqgOEbsdLDpw==", - "requires": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.33", - "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - } - }, - "@vue/compiler-dom": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.33.tgz", - "integrity": "sha512-GhiG1C8X98Xz9QUX/RlA6/kgPBWJkjq0Rq6//5XTAGSYrTMBgcLpP9+CnlUg1TFxnnCVughAG+KZl28XJqw8uQ==", - "requires": { - "@vue/compiler-core": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "@vue/compiler-sfc": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.33.tgz", - "integrity": "sha512-H8D0WqagCr295pQjUYyO8P3IejM3vEzeCO1apzByAEaAR/WimhMYczHfZVvlCE/9yBaEu/eu9RdiWr0kF8b71Q==", - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.33", - "@vue/compiler-dom": "3.2.33", - "@vue/compiler-ssr": "3.2.33", - "@vue/reactivity-transform": "3.2.33", - "@vue/shared": "3.2.33", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "@vue/compiler-ssr": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.33.tgz", - "integrity": "sha512-XQh1Xdk3VquDpXsnoCd7JnMoWec9CfAzQDQsaMcSU79OrrO2PNR0ErlIjm/mGq3GmBfkQjzZACV+7GhfRB8xMQ==", - "requires": { - "@vue/compiler-dom": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "@vue/devtools-api": { - "version": "6.1.4", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz", - "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==" - }, - "@vue/reactivity": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.33.tgz", - "integrity": "sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ==", - "requires": { - "@vue/shared": "3.2.33" - } - }, - "@vue/reactivity-transform": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.33.tgz", - "integrity": "sha512-4UL5KOIvSQb254aqenW4q34qMXbfZcmEsV/yVidLUgvwYQQ/D21bGX3DlgPUGI3c4C+iOnNmDCkIxkILoX/Pyw==", - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.33", - "@vue/shared": "3.2.33", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "@vue/runtime-core": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz", - "integrity": "sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw==", - "requires": { - "@vue/reactivity": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "@vue/runtime-dom": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz", - "integrity": "sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw==", - "requires": { - "@vue/runtime-core": "3.2.33", - "@vue/shared": "3.2.33", - "csstype": "^2.6.8" - } - }, - "@vue/server-renderer": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.33.tgz", - "integrity": "sha512-4jpJHRD4ORv8PlbYi+/MfP8ec1okz6rybe36MdpkDrGIdEItHEUyaHSKvz+ptNEyQpALmmVfRteHkU9F8vxOew==", - "requires": { - "@vue/compiler-ssr": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "@vue/shared": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.33.tgz", - "integrity": "sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==" - }, - "@vue/tsconfig": { - "version": "0.1.3", - "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz", - "integrity": "sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg==", - "dev": true, - "requires": {} - }, - "@vueuse/core": { - "version": "8.4.2", - "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-8.4.2.tgz", - "integrity": "sha512-dUVU96lii1ZdWoNJXauQNt+4QrHz1DKbuW+y6pDR2N10q7rGZJMDU7pQeMcC2XeosX7kMODfaBuqsF03NozzLg==", - "requires": { - "@vueuse/metadata": "8.4.2", - "@vueuse/shared": "8.4.2", - "vue-demi": "*" - }, - "dependencies": { - "@vueuse/shared": { - "version": "8.4.2", - "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-8.4.2.tgz", - "integrity": "sha512-hILXMEjL8YQhj1LHN/HZ49UThyfk8irTjhele2nW+L3N55ElFUBGB/f4w0rg8EW+/suhqv7kJJPTZzvHCqxlIw==", - "requires": { - "vue-demi": "*" - } - }, - "vue-demi": { - "version": "0.12.5", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz", - "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", - "requires": {} - } - } - }, - "@vueuse/metadata": { - "version": "8.4.2", - "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-8.4.2.tgz", - "integrity": "sha512-2BIj++7P0/I5dfMsEe8q7Kw0HqVAjVcyNOd9+G22/ILUC9TVLTeYOuJ1kwa1Gpr+0LWKHc6GqHiLWNL33+exoQ==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "async-validator": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.1.1.tgz", - "integrity": "sha512-p4DO/JXwjs8klJyJL8Q2oM4ks5fUTze/h5k10oPPKMiLe1fj3G1QMzPHNmN1Py4ycOk7WlO2DcGXv1qiESJCZA==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmmirror.com/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "browserslist": { - "version": "4.20.3", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.20.3.tgz", - "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001332", - "electron-to-chromium": "^1.4.118", - "escalade": "^3.1.1", - "node-releases": "^2.0.3", - "picocolors": "^1.0.0" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001339", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001339.tgz", - "integrity": "sha512-Es8PiVqCe+uXdms0Gu5xP5PF2bxLR7OBp3wUzUnuO7OHzhOfCyg3hdiGWVPVxhiuniOzng+hTc1u3fEQ0TlkSQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "csstype": { - "version": "2.6.20", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz", - "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" - }, - "dayjs": { - "version": "1.11.2", - "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.2.tgz", - "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "echarts": { - "version": "5.3.2", - "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.3.2.tgz", - "integrity": "sha512-LWCt7ohOKdJqyiBJ0OGBmE9szLdfA9sGcsMEi+GGoc6+Xo75C+BkcT/6NNGRHAWtnQl2fNow05AQjznpap28TQ==", - "requires": { - "tslib": "2.3.0", - "zrender": "5.3.1" - } - }, - "electron-to-chromium": { - "version": "1.4.137", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", - "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==", - "dev": true - }, - "element-plus": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.0.tgz", - "integrity": "sha512-zxmAFEAa1T/n09rR+NozXcWl5CjaFtqoaxhFSafag0dgc90tgEHitDXfegdFAl4ahugdNTqu9aLzngx3VhDAtA==", - "requires": { - "@ctrl/tinycolor": "^3.4.1", - "@element-plus/icons-vue": "^1.1.4", - "@floating-ui/dom": "^0.4.5", - "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.6", - "@types/lodash": "^4.14.182", - "@types/lodash-es": "^4.17.6", - "@vueuse/core": "^8.2.6", - "async-validator": "^4.0.7", - "dayjs": "^1.11.1", - "escape-html": "^1.0.3", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "lodash-unified": "^1.0.2", - "memoize-one": "^6.0.0", - "normalize-wheel-es": "^1.1.2" - } - }, - "esbuild": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.38.tgz", - "integrity": "sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==", - "dev": true, - "requires": { - "esbuild-android-64": "0.14.38", - "esbuild-android-arm64": "0.14.38", - "esbuild-darwin-64": "0.14.38", - "esbuild-darwin-arm64": "0.14.38", - "esbuild-freebsd-64": "0.14.38", - "esbuild-freebsd-arm64": "0.14.38", - "esbuild-linux-32": "0.14.38", - "esbuild-linux-64": "0.14.38", - "esbuild-linux-arm": "0.14.38", - "esbuild-linux-arm64": "0.14.38", - "esbuild-linux-mips64le": "0.14.38", - "esbuild-linux-ppc64le": "0.14.38", - "esbuild-linux-riscv64": "0.14.38", - "esbuild-linux-s390x": "0.14.38", - "esbuild-netbsd-64": "0.14.38", - "esbuild-openbsd-64": "0.14.38", - "esbuild-sunos-64": "0.14.38", - "esbuild-windows-32": "0.14.38", - "esbuild-windows-64": "0.14.38", - "esbuild-windows-arm64": "0.14.38" - } - }, - "esbuild-android-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz", - "integrity": "sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz", - "integrity": "sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz", - "integrity": "sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz", - "integrity": "sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz", - "integrity": "sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz", - "integrity": "sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz", - "integrity": "sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz", - "integrity": "sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz", - "integrity": "sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz", - "integrity": "sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz", - "integrity": "sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz", - "integrity": "sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz", - "integrity": "sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz", - "integrity": "sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz", - "integrity": "sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz", - "integrity": "sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==", - "dev": true, - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz", - "integrity": "sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==", - "dev": true, - "optional": true - }, - "esbuild-windows-32": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz", - "integrity": "sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==", - "dev": true, - "optional": true - }, - "esbuild-windows-64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz", - "integrity": "sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==", - "dev": true, - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.14.38", - "resolved": "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz", - "integrity": "sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==", - "dev": true, - "optional": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "follow-redirects": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.0.tgz", - "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==" - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "hash-sum": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz", - "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", - "dev": true - }, - "html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", - "dev": true - }, - "is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "lodash-unified": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.2.tgz", - "integrity": "sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g==", - "requires": {} - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" - }, - "node-releases": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.4.tgz", - "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", - "dev": true - }, - "normalize-wheel-es": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.1.2.tgz", - "integrity": "sha512-scX83plWJXYH1J4+BhAuIHadROzxX0UBF3+HuZNY2Ks8BciE7tSTQ+5JhTsvzjaO0/EJdm4JBGrfObKxFf3Png==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pinia": { - "version": "2.0.14", - "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.14.tgz", - "integrity": "sha512-0nPuZR4TetT/WcLN+feMSjWJku3SQU7dBbXC6uw+R6FLQJCsg+/0pzXyD82T1FmAYe0lsx+jnEDQ1BLgkRKlxA==", - "requires": { - "@vue/devtools-api": "^6.1.4", - "vue-demi": "*" - }, - "dependencies": { - "vue-demi": { - "version": "0.12.5", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz", - "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==", - "requires": {} - } - } - }, - "postcss": { - "version": "8.4.13", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.13.tgz", - "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", - "requires": { - "nanoid": "^3.3.3", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "rollup": { - "version": "2.72.1", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.72.1.tgz", - "integrity": "sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - }, - "typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", - "devOptional": true - }, - "vite": { - "version": "2.9.8", - "resolved": "https://registry.npmmirror.com/vite/-/vite-2.9.8.tgz", - "integrity": "sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw==", - "dev": true, - "requires": { - "esbuild": "^0.14.27", - "fsevents": "~2.3.2", - "postcss": "^8.4.13", - "resolve": "^1.22.0", - "rollup": "^2.59.0" - } - }, - "vue": { - "version": "3.2.33", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.33.tgz", - "integrity": "sha512-si1ExAlDUrLSIg/V7D/GgA4twJwfsfgG+t9w10z38HhL/HA07132pUQ2KuwAo8qbCyMJ9e6OqrmWrOCr+jW7ZQ==", - "requires": { - "@vue/compiler-dom": "3.2.33", - "@vue/compiler-sfc": "3.2.33", - "@vue/runtime-dom": "3.2.33", - "@vue/server-renderer": "3.2.33", - "@vue/shared": "3.2.33" - } - }, - "vue-router": { - "version": "4.0.15", - "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.0.15.tgz", - "integrity": "sha512-xa+pIN9ZqORdIW1MkN2+d9Ui2pCM1b/UMgwYUCZOiFYHAvz/slKKBDha8DLrh5aCG/RibtrpyhKjKOZ85tYyWg==", - "requires": { - "@vue/devtools-api": "^6.0.0" - } - }, - "vue-tsc": { - "version": "0.34.12", - "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-0.34.12.tgz", - "integrity": "sha512-CmuqLXHEW5UvS8UpT2RYom5MzOWBD142PLXxDX0ARdZ/u1oLobA3od4XY2XZACQYCFCzjTvfD1H5wrWwiGwoUA==", - "dev": true, - "requires": { - "@volar/vue-typescript": "0.34.12" - } - }, - "zrender": { - "version": "5.3.1", - "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.3.1.tgz", - "integrity": "sha512-7olqIjy0gWfznKr6vgfnGBk7y4UtdMvdwFmK92vVQsQeDPyzkHW1OlrLEKg6GHz1W5ePf0FeN1q2vkl/HFqhXw==", - "requires": { - "tslib": "2.3.0" - } - } - } -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index ddceed3..0000000 --- a/frontend/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "frontend", - "version": "3.1.0", - "scripts": { - "dev": "vite", - "build": "vue-tsc --noEmit && vite build", - "preview": "vite preview --port 5050", - "typecheck": "vue-tsc --noEmit" - }, - "dependencies": { - "axios": "^0.27.2", - "echarts": "^5.3.2", - "element-plus": "^2.1.11", - "pinia": "^2.0.13", - "vue": "^3.2.33", - "vue-router": "^4.0.14" - }, - "devDependencies": { - "@types/node": "^16.11.27", - "@vitejs/plugin-vue": "^2.3.1", - "@vitejs/plugin-vue-jsx": "^1.3.10", - "@vue/tsconfig": "^0.1.3", - "typescript": "~4.6.3", - "vite": "^2.9.5", - "vue-tsc": "^0.34.7" - } -} diff --git a/frontend/src/App.vue b/frontend/src/App.vue deleted file mode 100644 index 26d8165..0000000 --- a/frontend/src/App.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts deleted file mode 100644 index c44c2e2..0000000 --- a/frontend/src/api/index.ts +++ /dev/null @@ -1,96 +0,0 @@ -import axios from "axios"; -import { get, post, put, del } from "@/request"; -import type { GetUserInfo, Todo, TableDataList, TableData, TableObject, DelDataList, CourseId, Details } from "./model"; - -/** - * 退出登录 - */ -export const logout = (): Promise => post("/logout"); - -/** - * 获取用户信息 - */ -export const getUserInfo = (data: GetUserInfo): Promise => get(`${data.roles}/index`); - -/** - * 查询首页数据(访问量 && 待办事项 && 请求数 && 待办事项) - */ -export const getDashboard = (): Promise => get("/dashboard"); - -/** - * 添加待办 - */ -export const addTodo = (data: Todo): Promise => post(`/todo/add`, { ...data }); - -/** - * 根据索引更新待办 - */ -export const updateTodo = (data: Todo): Promise => post(`/todo/update`, { ...data }); - -/** - * 获取表的数据 - */ -export const readDatas = (data: TableDataList): Promise => { - const { path, ...rest } = data; - return get(`${path}/`, { ...rest }); -}; - -/** - * 根据 id 查询信息 - */ -export const readData = (data: TableData): Promise => get(`${data.path}/${data.id}`); - -/** - * 添加表格信息 - */ -export const createData = (data: TableObject): Promise => { - let { path, ...rest } = data; - return post(`${path}/`, { ...rest }); -}; - -/** - * 更新表格信息 - */ -export const updateData = (data: TableObject): Promise => { - let { path, id, ...rest } = data; - return put(`${path}/${id}`, { ...rest }); -}; - -/** - * 根据 id 删除表格信息 - * @param {*} data id - */ -export const deleteData = (data: TableData): Promise => del(`${data.path}/${data.id}`); - -/** - * 同时删除多个表格信息 - * @param {*} data id列表 - */ -export const deleteDatas = (data: DelDataList): Promise => post(`${data.path}/del/`, data.idList); - -/** - * 得到讲授详情 - */ -export const getTughtDetail = (data: Details): Promise => get(`${data.path}/detail/`); - -/** - * 根据 课程id 添加选课信息 - */ -export const addByCourseId = (data: CourseId): Promise => post(`${data.path}/add/${data.courseId}`); - -/** - * 根据 课程id 删除选课信息 - */ -export const delByCourseId = (data: CourseId): Promise => post(`${data.path}/del/${data.courseId}`); - -/** - * 得到选课详情 - */ -export const getElectiveDetail = (data: Details): Promise => get(`${data.path}/detail/`); - -/** - * 获取语言详情 - */ -export const getLangList = async (): Promise => { - return await axios.get("https://api.github.com/repos/zxiaosi/Vue3-FastAPI/languages"); -}; diff --git a/frontend/src/api/login.ts b/frontend/src/api/login.ts deleted file mode 100644 index eee4b1b..0000000 --- a/frontend/src/api/login.ts +++ /dev/null @@ -1,30 +0,0 @@ -import http from "@/request/http"; -import { API_URL } from "@/assets/js/global"; -import type { Login } from "./model"; - -/** - * 登录(发送表单请求) - */ -export const login = async (data: Login): Promise => - http.request({ - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - url: `${API_URL}/login`, - method: "POST", - data: data, - transformRequest: [ - // 请求时, 将{username:111,password:111} 转成 username=111&password=111 - function (data) { - var ret = ""; - for (var it in data) { - // 判断是否是数组 - if (Array.isArray(data[it])) { - let tmp = data[it].join(" "); // 将 ['admin','teacher','student'] 转为 ['admin' 'teacher' 'student'] - ret += encodeURIComponent(it) + "=" + encodeURIComponent(tmp) + "&"; - } else { - ret += encodeURIComponent(it) + "=" + encodeURIComponent(data[it]) + "&"; - } - } - return ret.substring(0, ret.length - 1); - }, - ], - }); diff --git a/frontend/src/api/model.ts b/frontend/src/api/model.ts deleted file mode 100644 index 7f48b54..0000000 --- a/frontend/src/api/model.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { FormData, PathEnum } from "@/types/table"; - -/** - * 登录 - */ -export interface Login { - username: string; - password: string; - scope: any; -} - -/** - * 获取用户信息 - */ -export interface GetUserInfo { - roles: "admin" | "student" | "teacher"; -} - -/** - * 添加待办/根据索引更新待办 - */ -export interface Todo { - id?: number; // 待办索引(临时数据) - title?: string; // 待办文案 - status?: boolean; // 是否选中 -} - -/** - * 获取所有数据(表格数据) - */ -export interface TableDataList { - path: PathEnum; // 路径参数 - pageIndex?: number; // 页码 - pageSize?: number; // 每页个数 -} - -/** - * 根据id查询/删除(多条)信息 - */ -export interface TableData { - path: PathEnum; // 路径参数 - id: number | string; // id -} - -/** - * 添加/更新信息 - */ -export interface TableObject extends FormData { - path: PathEnum; // 路径参数 -} - -/** - * 同时删除多个信息 - */ -export interface DelDataList { - path: PathEnum; // 路径参数 - idList: number[] | string[]; // id列表 -} - -/** - * 根据课程id添加/删除选课信息 - */ -export interface CourseId { - path: PathEnum; // 路径参数 - courseId: number | string; // 课程id -} -/** - * 得到课程详情 - */ -export interface Details { - path: PathEnum; // 路径参数 -} diff --git a/frontend/src/assets/css/color-dark.css b/frontend/src/assets/css/color-dark.css deleted file mode 100644 index ae3f22a..0000000 --- a/frontend/src/assets/css/color-dark.css +++ /dev/null @@ -1,28 +0,0 @@ -.header { - background-color: #242f42; -} -.login-wrap { - background: #324157; -} -.plugins-tips { - background: #eef1f6; -} -.plugins-tips a { - color: #20a0ff; -} -.el-upload--text em { - color: #20a0ff; -} -.pure-button { - background: #20a0ff; -} -.tags-li.active { - border: 1px solid #409eff; - background-color: #409eff; -} -.message-title { - color: #20a0ff; -} -.collapse-btn:hover { - background: rgb(40, 52, 70); -} diff --git a/frontend/src/assets/css/icon.css b/frontend/src/assets/css/icon.css deleted file mode 100644 index 13713e4..0000000 --- a/frontend/src/assets/css/icon.css +++ /dev/null @@ -1,9 +0,0 @@ -/* 修改图标见 https://www.jianshu.com/p/59dd28f0b9c9 */ -[class^="el-icon-ali"], -[class*=" el-icon-ali"] { - font-family: "iconfont" !important; - font-size: 1rem; - font-style: normal; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/frontend/src/assets/css/main.css b/frontend/src/assets/css/main.css deleted file mode 100644 index e8d6736..0000000 --- a/frontend/src/assets/css/main.css +++ /dev/null @@ -1,116 +0,0 @@ -* { - margin: 0; - padding: 0; -} - -html, -body, -#app, -.wrapper { - width: 100%; - height: 100%; - overflow: hidden; -} - -body { - font-family: "PingFang SC", "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif; -} - -a { - text-decoration: none; -} - -.content-box { - position: absolute; - left: 220px; - right: 0; - top: 70px; - bottom: 0; - padding-bottom: 30px; - -webkit-transition: left 0.3s ease-in-out; - transition: left 0.3s ease-in-out; - background: #f0f0f0; -} - -.content { - width: auto; - height: 100%; - padding: 10px; - overflow-y: scroll; - box-sizing: border-box; -} - -.content-collapse { - left: 65px; -} - -.container { - padding: 30px; - background: #fff; - border: 1px solid #ddd; - border-radius: 5px; -} - -.crumbs { - margin: 10px 0; -} - -.el-table th { - background-color: #f5f7fa !important; -} - -.pagination { - margin: 20px 0; - text-align: right; -} - -.plugins-tips { - padding: 20px 10px; - margin-bottom: 20px; -} - -.el-button + .el-tooltip { - margin-left: 10px; -} - -.el-table tr:hover { - background: #f6faff; -} - -.mgb20 { - margin-bottom: 20px; -} - -.move-enter-active, -.move-leave-active { - transition: opacity 0.1s ease; -} - -.move-enter-from, -.move-leave-to { - opacity: 0; -} - -/*BaseForm*/ - -.form-box { - width: 600px; -} - -.form-box .line { - text-align: center; -} - -.el-time-panel__content::after, -.el-time-panel__content::before { - margin-top: -7px; -} - -.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) { - padding-bottom: 0; -} - -/* element-icon 图标 (对应 size:20) */ -/* .el-icon{ - --font-size: 20px !important; -} */ diff --git a/frontend/src/assets/img/img.jpg b/frontend/src/assets/img/img.jpg deleted file mode 100644 index b5901c1f7ad0d5e58f2a1953ae7a462dde7ff4df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45621 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
ds4Sa> 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 MiABU*%L$+V*)WFAX8-^I diff --git a/frontend/src/assets/js/global.ts b/frontend/src/assets/js/global.ts deleted file mode 100644 index c82130b..0000000 --- a/frontend/src/assets/js/global.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * 全局标题 - */ -export const TITLE: string = "学生选课系统"; - -/** - * axios-baseUrl - */ -// export const API_URL: string = `http://127.0.0.1:8000/api`; // 开发环境 -export const API_URL: string = `http://8.136.82.204:8000/api`; // 线上环境 - -/** - * axios-timeOut - */ -export const TIME_OUT: number = 15000; diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg deleted file mode 100644 index 0f544cc..0000000 --- a/frontend/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/BaseTable/index.ts b/frontend/src/components/BaseTable/index.ts deleted file mode 100644 index c4e96ab..0000000 --- a/frontend/src/components/BaseTable/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { FormData } from "@/types/table"; - -export interface State { - pageName: any; - searched: FormData[]; - isShowSearched: boolean; - selectedList: string[]; - relationData: any; - showDialog: boolean; - addOrUpdate: boolean; - isStudent: boolean; - isTeacher: boolean; -} diff --git a/frontend/src/components/BaseTable/index.vue b/frontend/src/components/BaseTable/index.vue deleted file mode 100644 index 3d3233d..0000000 --- a/frontend/src/components/BaseTable/index.vue +++ /dev/null @@ -1,390 +0,0 @@ - - - - - diff --git a/frontend/src/components/Breadcrumb/index.vue b/frontend/src/components/Breadcrumb/index.vue deleted file mode 100644 index cbceea6..0000000 --- a/frontend/src/components/Breadcrumb/index.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/frontend/src/components/LoadingBar/index.vue b/frontend/src/components/LoadingBar/index.vue deleted file mode 100644 index 36e5e2a..0000000 --- a/frontend/src/components/LoadingBar/index.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - - diff --git a/frontend/src/components/Pagination/index.vue b/frontend/src/components/Pagination/index.vue deleted file mode 100644 index 7c8e7c9..0000000 --- a/frontend/src/components/Pagination/index.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/frontend/src/layout/Header/index.vue b/frontend/src/layout/Header/index.vue deleted file mode 100644 index e092635..0000000 --- a/frontend/src/layout/Header/index.vue +++ /dev/null @@ -1,182 +0,0 @@ - - - - - diff --git a/frontend/src/layout/Home/index.vue b/frontend/src/layout/Home/index.vue deleted file mode 100644 index e62e070..0000000 --- a/frontend/src/layout/Home/index.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - - diff --git a/frontend/src/layout/Sidebar/index.vue b/frontend/src/layout/Sidebar/index.vue deleted file mode 100644 index 33dbd4c..0000000 --- a/frontend/src/layout/Sidebar/index.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/frontend/src/layout/Tags/index.vue b/frontend/src/layout/Tags/index.vue deleted file mode 100644 index 45aca8c..0000000 --- a/frontend/src/layout/Tags/index.vue +++ /dev/null @@ -1,193 +0,0 @@ - - - - - diff --git a/frontend/src/main.ts b/frontend/src/main.ts deleted file mode 100644 index 996ce26..0000000 --- a/frontend/src/main.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createApp } from "vue"; -import { createPinia } from "pinia"; // 状态管理 - -import App from "./App.vue"; -import router from "./router"; // 路由 - -// 全局ElementPlus -import ElementPlus from "element-plus"; -import zhCn from "element-plus/es/locale/lang/zh-cn"; // 中文 -import "element-plus/dist/index.css"; // 样式文件 -import "@/assets/css/icon.css"; // 阿里云图标 - -const app = createApp(App); - -app.use(createPinia()); -app.use(router); -app.use(ElementPlus, { locale: zhCn }); - -app.mount("#app"); diff --git a/frontend/src/request/auth.ts b/frontend/src/request/auth.ts deleted file mode 100644 index 85d291e..0000000 --- a/frontend/src/request/auth.ts +++ /dev/null @@ -1,28 +0,0 @@ -const TokenKey = "Authorization$://"; //授权码 -/* - * 获取getItem - * */ -export function getLocal(key?: string) { - return localStorage.getItem(key ? key : TokenKey) as any; -} - -/* - * 设置setItem - * */ -export function setLocal(key: string | undefined, params: any) { - return localStorage.setItem(key ? key : TokenKey, params); -} - -/* - * 移除removeItem - * */ -export function removeLocal(key?: string) { - return localStorage.removeItem(key ? key : TokenKey); -} - -/* - * 清空所有Item - * */ -export function clearLocal() { - return localStorage.clear(); -} diff --git a/frontend/src/request/http.ts b/frontend/src/request/http.ts deleted file mode 100644 index f9b3535..0000000 --- a/frontend/src/request/http.ts +++ /dev/null @@ -1,135 +0,0 @@ -import axios, { AxiosError, type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from "axios"; -import { ElLoading, ElMessage } from "element-plus"; -import type { LoadingInstance } from "element-plus/lib/components/loading/src/loading"; -import type { ResponseData } from "@/types"; -import { API_URL, TIME_OUT } from "@/assets/js/global"; -import { clearLocal, getLocal } from "./auth"; -import { showStatus } from "./statusCode"; - -// 请求 -class AppRequest { - instance: AxiosInstance; // axios实例 - loading?: LoadingInstance; // 加载动画 - - // 构造器 - constructor(config: AxiosRequestConfig) { - this.instance = axios.create(config); - } - - /** - * 自定义请求 - */ - request(config: AxiosRequestConfig): Promise> { - return new Promise((resolve, reject) => { - this.instance - .request(config) - .then((res) => resolve(res.data)) - .catch((err) => reject(err)); - }); - } - - /** - * 开始加载动画: https://www.cxybb.com/article/weixin_45685252/114917309 - */ - startLoading() { - this.closeLoading(); // 先清除动画, 防止连续请求多次加载 - - this.loading = ElLoading.service({ - target: ".el-table, .ms-content, .info", // 设置加载动画区域(样式class名) - lock: true, // 锁定屏幕的滚动 - text: "正在请求数据...", // 显示文案 - }); - - // 设定定时器,超时5S后自动关闭遮罩层,避免请求失败时,遮罩层一直存在的问题 - setTimeout(() => this.closeLoading(), 5000); // 关闭遮罩层 - } - - /** - * 关闭加载动画 - */ - closeLoading() { - this.loading?.close(); // 关闭遮罩层 - } -} - -// 创建对象 -const http = new AppRequest({ - baseURL: API_URL, // 请求地址 - timeout: TIME_OUT, // 超时时间 - headers: { "Content-Type": "application/json;charset=utf-8" }, // 请求头 - transformRequest: [ - (data) => { - // 请求参数序列化(对象转字符串) - return JSON.stringify(data); - }, - ], - validateStatus() { - // 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常 - return true; - }, - transformResponse: [ - (data) => { - // 响应数据反序列化(字符串转对象) - if (typeof data === "string" && data.startsWith("{")) { - data = JSON.parse(data); - } - return data; - }, - ], -}); - -// 请求拦截器 -http.instance.interceptors.request.use( - (config: AxiosRequestConfig) => { - http.startLoading(); // 加载动画 - - //获取token,并将其添加至请求头中 - let token = getLocal("Authorization"); - if (token) { - // @ts-ignore (防止下面报错) - config.headers.Authorization = "Bearer " + token; // 前面一定要加 Bearer - } - - return config; - }, - (error: AxiosError) => { - // @ts-ignore - error.data.msg = "请求超时或服务器异常,请检查网络或联系管理员!"; - return Promise.reject(error); - } -); - -// 响应拦截器 -http.instance.interceptors.response.use( - (response: AxiosResponse) => { - http.closeLoading(); // 关闭加载动画 - - let msg = ""; - if (response.status == 200 && typeof response.status == "number") { - // 请求成功 - return response; - } else if (response.status == 401) { - // 后端验证是否有token,没有则返回401 - window.location.href = "/login"; // 跳转登录 - clearLocal(); // 清除本地存储 - msg = "Token已过期,请重新登录!"; - } else if (response.status == 403) { - window.location.href = "/403"; // 没有权限 - msg = "没有权限!"; - } else { - if (response.data.msg != null) { - msg = response.data.msg; // 后端返回的msg - } else { - msg = showStatus(response.status); // 后端未返回msg,前端根据状态码自定义的msg - } - } - ElMessage.error({ message: msg, grouping: true }); - }, - (error: AxiosError) => { - let msg = showStatus(error.response?.status); - ElMessage.error(msg); - return Promise.reject(error); - } -); - -export default http; diff --git a/frontend/src/request/index.ts b/frontend/src/request/index.ts deleted file mode 100644 index b7c54c4..0000000 --- a/frontend/src/request/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import http from "./http"; -import type { RequestData, ResponseData } from "@/types"; - -export function get(url: string, data?: RequestData | string): Promise> { - return http.request({ url, params: data, method: "GET" }); -} - -export function post(url: string, data?: RequestData | string): Promise> { - return http.request({ url, data: data, method: "POST" }); -} - -export function put(url: string, data?: RequestData | string): Promise> { - return http.request({ url, data: data, method: "PUT" }); -} - -export function del(url: string, data?: RequestData | string): Promise> { - return http.request({ url, params: data, method: "DELETE" }); -} - -export default http; diff --git a/frontend/src/request/statusCode.ts b/frontend/src/request/statusCode.ts deleted file mode 100644 index 8e18adc..0000000 --- a/frontend/src/request/statusCode.ts +++ /dev/null @@ -1,41 +0,0 @@ -export const showStatus = (status?: number) => { - let message = ""; - switch (status) { - case 400: - message = "请求错误(400)"; - break; - case 401: - message = "未授权,请重新登录(401)"; - break; - case 403: - message = "拒绝访问(403)"; - break; - case 404: - message = "请求出错(404)"; - break; - case 408: - message = "请求超时(408)"; - break; - case 500: - message = "服务器错误(500)"; - break; - case 501: - message = "服务未实现(501)"; - break; - case 502: - message = "网络错误(502)"; - break; - case 503: - message = "服务不可用(503)"; - break; - case 504: - message = "网络超时(504)"; - break; - case 505: - message = "HTTP版本不受支持(505)"; - break; - default: - message = `连接出错(${status})!`; - } - return `${message},请检查网络或联系管理员!`; -}; diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts deleted file mode 100644 index 57427eb..0000000 --- a/frontend/src/router/index.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router"; -import Layout from "@/layout/Home/index.vue"; -import { getLocal } from "@/request/auth"; -import { TITLE } from "@/assets/js/global"; - -const routes: RouteRecordRaw[] = [ - { path: "/", redirect: "/dashboard" }, // 重定向 - { - path: "/login", - name: "Login", - meta: { title: "登录" }, - component: () => import("@/views/login/index.vue"), - }, - { - path: "/", - name: "Layout", - component: Layout, - children: [ - { - path: "/dashboard", - name: "Dashboard", - meta: { title: "系统首页", icon: "dashboard", roles: ["admin", "teacher", "student"] }, - component: () => import("@/views/dashboard/index.vue"), - }, - { - path: "/settings", - name: "Settings", - meta: { title: "系统管理", icon: "setting", roles: ["admin", "teacher", "student"] }, - component: () => import("@/views/settings/index.vue"), - children: [ - { - path: "/department", - name: "Department", - meta: { title: "院系管理", roles: ["admin"], icon: "dept" }, - component: () => import("@/views/settings/department/index.vue"), - }, - { - path: "/major", - name: "Major", - meta: { title: "专业管理", roles: ["admin"], icon: "major" }, - component: () => import("@/views/settings/major/index.vue"), - }, - { - path: "/teacher", - name: "Teacher", - meta: { title: "教师管理", roles: ["admin"], icon: "tutor" }, - component: () => import("@/views/settings/teacher/index.vue"), - }, - { - path: "/student", - name: "Student", - meta: { title: "学生管理", roles: ["admin"], icon: "stu" }, - component: () => import("@/views/settings/student/index.vue"), - }, - { - path: "/course", - name: "Course", - meta: { title: "课程管理", roles: ["admin", "teacher", "student"], icon: "intro" }, - component: () => import("@/views/settings/course/index.vue"), - }, - { - path: "/elective", - name: "Elective", - meta: { title: "选课管理", roles: ["admin"], icon: "sc" }, - component: () => import("@/views/settings/elective/index.vue"), - }, - { - path: "/taught", - name: "Taught", - meta: { title: "讲授管理", roles: ["admin"], icon: "taught" }, - component: () => import("@/views/settings/taught/index.vue"), - }, - { - path: "/myTaught", - name: "MyTaught", - meta: { title: "我的讲授", roles: ["teacher"], icon: "taught" }, - component: () => import("@/views/settings/myTaught/index.vue"), - }, - { - path: "/myElective", - name: "MyElective", - meta: { title: "我的课程", roles: ["student"], icon: "sc" }, - component: () => import("@/views/settings/myElective/index.vue"), - }, - ], - }, - { - path: "/messages", - name: "Messages", - meta: { title: "消息中心", icon: "msg", roles: ["admin", "teacher", "student"] }, - component: () => import("@/views/messages/index.vue"), - }, - { - path: "/user", - name: "User", - meta: { title: "个人中心", icon: "user", roles: ["admin", "teacher", "student"] }, - component: () => import("@/views/user/index.vue"), - }, - ], - }, - { - path: "/403", - name: "403", - meta: { title: "没有权限" }, - component: () => import("@/views/error/403/index.vue"), - }, - { - path: "/:pathMatch(.*)*", - name: "404", - meta: { title: "找不到页面" }, - component: () => import("@/views/error/404/index.vue"), - }, - // { - // path: '/:pathMatch(.*)', - // redirect: '/404' - // } -]; - -const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: routes, -}); - -// 全局路由守卫(前置) -router.beforeEach((to, from, next) => { - // VNode.component?.exposed?.startLoading(); - document.title = `${to.meta.title} | ${TITLE}`; // 页面名 - const userInfo = JSON.parse(getLocal("userInfo")); - const role = getLocal("role"); - if (!userInfo && to.path !== "/login") { - next("/login"); - } else if (to.meta.roles) { - let roles: any = to.meta.roles; - // 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已 - roles.indexOf(role) > -1 ? next() : next("/403"); - } else { - next(); - } -}); - -// 全局路由守卫(后置) -router.afterEach((to, from) => { - // VNode.component?.exposed?.stopLoading(); -}); - -export default router; diff --git a/frontend/src/stores/data.ts b/frontend/src/stores/data.ts deleted file mode 100644 index bc92576..0000000 --- a/frontend/src/stores/data.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineStore } from "pinia"; -import type { CourseForm, DeptForm, MajorForm, StudentForm, TeacherForm } from "@/types/table"; - -export const useDataStore = defineStore({ - id: "table", - state: () => ({ - departmentData: [] as DeptForm[], // 院系 - majorData: [] as MajorForm[], // 专业 - teacherData: [] as TeacherForm[], // 教师 - studentData: [] as StudentForm[], // 学生 - courseData: [] as CourseForm[], // 课程 - }), - getters: {}, - actions: { - /** - * 存储数据 - * @param prefix 表名 - * @param data 数据 - */ - handleData(prefix: string, data: string[]) { - // console.log(prefix, data); - this[`${prefix}Data`] = data; - }, - }, -}); diff --git a/frontend/src/stores/index.ts b/frontend/src/stores/index.ts deleted file mode 100644 index 21bf833..0000000 --- a/frontend/src/stores/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { defineStore } from "pinia"; -import type { UserInfo } from "@/types"; - -interface Tags { - name: string; - path: string; - title: string; -} - -export const useStore = defineStore({ - id: "index", - state: () => ({ - tagsList: [] as Tags[], // 标签列表 - collapse: false as boolean, // 侧边栏是否折叠 - userInfo: {} as UserInfo, // 用户信息 - messages: 4 as number, // 消息数量 - }), - getters: { - tagNameList(state) { - return state.tagsList.map((item: Tags) => { - return item.name; - }); - }, - }, - actions: { - /** - * 根据索引删除标签 - * @param index 索引值 - */ - delTagsItem(index: number) { - this.tagsList.splice(index, 1); - }, - - /** - * 添加路由对象 - * @param route 路由对象 - */ - setTagsItem(route: Tags) { - this.tagsList.push(route); - }, - - /** - * 关闭全部标签 - */ - clearTags() { - this.tagsList = []; - }, - - /** - * 关闭其他标签 - * @param data 标签数组 - */ - closeTagsOther(data: Tags[]) { - this.tagsList = data; - }, - }, -}); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts deleted file mode 100644 index f260d3a..0000000 --- a/frontend/src/types/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 请求参数(前端发送的数据格式) - */ -export interface RequestData { - [key: string]: any; -} - -/** - * 响应数据(后端返回字段) - */ -export interface ResponseData { - code: number; // 状态码 - msg: string; // 成功/报错信息 - data: T; // 数据 -} - -/** - * 权限参数类型(限定可选值) - */ -export enum Roles { - admin = "admin", // 管理员权限 - teacher = "teacher", // 教师权限 - student = "student", // 学生权限 -} - -/** - * 缓存用户信息的字段 - */ -export interface UserInfo { - id: number | string; // 用户id - name: string; // 名称 - image: string; // 头像 - address: string; // 地址 - update_time: string; // 最后更新时间 -} diff --git a/frontend/src/types/table.ts b/frontend/src/types/table.ts deleted file mode 100644 index 6cffa0b..0000000 --- a/frontend/src/types/table.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * 路径参数类型(限定可选值) - */ -export enum PathEnum { - dept = "department", - major = "major", - teacher = "teacher", - student = "student", - course = "course", - taught = "taught", - elective = "elective", -} - -/** - * 查询参数类型 - */ -export interface Query { - id: string; // 请求id - currentPage: number; // 页码 - pageSize: number; // 每页个数 -} - -/** - * form表单时间数据类型(创建时间和更新时间) - */ -export interface TimeForm { - create_time?: string; // 创建时间 - update_time?: string; // 更新时间 -} - -/** - * 院系表数据类型(请使用 ?: , 后面继承接口用到) - */ -export interface DeptForm extends TimeForm { - id?: number | string; // 编号 - name?: string; // 名称 - chairman?: string; // 主任名 - phone?: string; // 手机号 -} - -/** - * 专业表数据类型 - */ -export interface MajorForm extends TimeForm { - id?: number | string; // 编号 - name?: string; // 名称 - assistant?: string; // 辅导员名 - phone?: string; // 手机号 - departmentId?: number | string; // 院系编号 -} - -/** - * 教师表数据类型 - */ -export interface TeacherForm extends TimeForm { - id?: number | string; // 编号 - name?: string; // 名称 - sex?: "0" | "1"; // 性别 - birthday?: string; // 生日 - education?: "1" | "2" | "3"; // 学历 - title?: "1" | "2" | "3" | "4"; // 职称 - address?: string; // 地址 - image?: string; //头像 - password?: string; // 密码 - departmentId?: number | string; // 院系编号 -} - -/** - * 学生表数据类型 - */ -export interface StudentForm extends TimeForm { - id?: number | string; // 编号 - name?: string; // 名称 - sex?: "0" | "1"; // 性别 - birthday?: string; // 生日 - address?: string; // 地址 - image?: string; //头像 - password?: string; // 密码 - majorId?: number | string; // 专业编号 -} - -/** - * 课程表数据类型 - */ -export interface CourseForm extends TimeForm { - id?: number | string; // 编号 - name?: string; // 名称 - credit?: number | string; // 学分 - period?: number | string; // 学时 -} - -/** - * 讲授表数据类型 - */ -export interface TaughtForm extends TimeForm { - id?: number | string; // 编号 - grade?: number | string; // 成绩 - teacherId?: number | string; // 教师编号 - courseId?: number | string; // 课程编号 -} - -/** - * 选课表数据类型 - */ -export interface ElectiveForm extends TimeForm { - id?: number | string; // 编号 - grade?: number | string; // 成绩 - studentId?: number | string; // 学生编号 - courseId?: number | string; // 课程编号 -} - -/** - * form表单数据类型 - */ -export interface FormData extends DeptForm, MajorForm, TeacherForm, StudentForm, CourseForm, TaughtForm, ElectiveForm {} diff --git a/frontend/src/utils/clickRecover.ts b/frontend/src/utils/clickRecover.ts deleted file mode 100644 index c14d67f..0000000 --- a/frontend/src/utils/clickRecover.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 点击后鼠标移开恢复按钮默认样式 - */ -export const clickRecover = (event: MouseEvent) => { - let target: any = event.target; - // (如果按钮没有加icon图标的话,target.nodeName == "I"可以去掉) - if (target.nodeName == "I" || target.nodeName == "SPAN") { - target = target.parentNode; - } - target.blur(); -}; diff --git a/frontend/src/utils/handleArray.ts b/frontend/src/utils/handleArray.ts deleted file mode 100644 index 9339c9a..0000000 --- a/frontend/src/utils/handleArray.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { FormData } from "@/types/table"; - -/** - * 加工数组 - * @param data 数组 - * @param keyName 想要返回的 key 名 - * @returns value 列表 - */ -export const valueList = (data: any[], keyName: string): string[] => { - return data.map((item: any) => { - return item[keyName].toString(); - }); -}; - -/** - * 通过id得到name - * @param {*} id 外键 - * @param {*} data 外键对应的表数据 - * @returns 对应的name - */ -export const byIdGetName = (id: string, data: FormData[]) => { - if (!id) { - return null; - } - for (let i = 0, len = data.length; i < len; i++) { - let item = data[i]; - if (item.id == id) { - return item.name; - } - } - return null; -}; diff --git a/frontend/src/utils/handleInject.ts b/frontend/src/utils/handleInject.ts deleted file mode 100644 index 436857e..0000000 --- a/frontend/src/utils/handleInject.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { inject, type InjectionKey } from "vue"; - -/** - * 为inject标注类型 - * @param key key值 - * @param fallback 失败返回信息 - * @returns inject(key) - */ -function injectStrict(key: InjectionKey, fallback?: T) { - const resolved = inject(key, fallback); - if (!resolved) { - throw new Error(`Could not resolve ${key.toString}`); - } - return resolved; -} - -export default injectStrict; diff --git a/frontend/src/utils/handleTime.ts b/frontend/src/utils/handleTime.ts deleted file mode 100644 index 9a7a6df..0000000 --- a/frontend/src/utils/handleTime.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const dateFunction = (time: any) => { - var zoneDate = new Date(time).toJSON(); - var date = new Date(+new Date(zoneDate) + 8 * 3600 * 1000) - .toISOString() - .replace(/T/g, " ") - .replace(/\.[\d]{3}Z/, ""); - return date; -}; diff --git a/frontend/src/utils/spanMethod.ts b/frontend/src/utils/spanMethod.ts deleted file mode 100644 index dbc713a..0000000 --- a/frontend/src/utils/spanMethod.ts +++ /dev/null @@ -1,66 +0,0 @@ -// http://www.manongjc.com/detail/23-npfbicezsphbgyn.html -// https://blog.csdn.net/u012175183/article/details/123205887 - -/** - * 合并相同数据,导出合并行所需的方法(只适合el-table) - * @param {Array} dataArray el-table表数据源 - * @param {Array} mergeRowProp 合并行的列prop - * @param {Array} sameRuleRowProp 相同合并规则行的列prop - */ -export function getSpanMethod(dataArray, mergeRowProp, sameRuleRowProp) { - /** - * 要合并行的数据 - */ - const rowspanNumObject = {}; - - //初始化 rowspanNumObject - mergeRowProp.map((item) => { - rowspanNumObject[item] = new Array(dataArray.length).fill(1, 0, 1).fill(0, 1); - rowspanNumObject[`${item}-index`] = 0; - }); - - //计算相关的合并信息 - for (let i = 1; i < dataArray.length; i++) { - mergeRowProp.map((key) => { - const index = rowspanNumObject[`${key}-index`]; - if (dataArray[i][key] === dataArray[i - 1][key]) { - rowspanNumObject[key][index]++; - } else { - rowspanNumObject[`${key}-index`] = i; - rowspanNumObject[key][i] = 1; - } - }); - } - - /** - * 添加同规则合并行的数据 - */ - if (sameRuleRowProp !== undefined) { - let k = Object.keys(rowspanNumObject).filter((key) => { - if (!key.includes("index")) { - return key; - } - })[0]; - for (let prop of sameRuleRowProp) { - rowspanNumObject[prop] = rowspanNumObject[k]; - rowspanNumObject[`${prop}-index`] = rowspanNumObject[`${k}-index`]; - mergeRowProp.push(prop); - } - } - - /** - * 导出合并方法 - */ - const spanMethod = function ({ row, column, rowIndex, columnIndex }) { - if (mergeRowProp.includes(column["property"])) { - const rowspan = rowspanNumObject[column["property"]][rowIndex]; - if (rowspan > 0) { - return { rowspan: rowspan, colspan: 1 }; - } - return { rowspan: 0, colspan: 0 }; - } - return { rowspan: 1, colspan: 1 }; - }; - - return spanMethod; -} diff --git a/frontend/src/views/dashboard/index.ts b/frontend/src/views/dashboard/index.ts deleted file mode 100644 index af7f025..0000000 --- a/frontend/src/views/dashboard/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface Language { - title: string; - percentage: number; - color: string; -} - -export interface Todo { - title: string; - status: boolean; -} - -export interface State { - identity: string; - langDetails: Language[]; - todoList: Todo[]; - visitNum: number; - todoNum: number; - requestNum: number; - showDialog: boolean; - todoText: string; -} - -export enum RolesEnum { - "admin" = "管理员", - "teacher" = "教师", - "student" = "学生", -} diff --git a/frontend/src/views/dashboard/index.vue b/frontend/src/views/dashboard/index.vue deleted file mode 100644 index 474ff55..0000000 --- a/frontend/src/views/dashboard/index.vue +++ /dev/null @@ -1,430 +0,0 @@ - - - - - diff --git a/frontend/src/views/error/403/index.vue b/frontend/src/views/error/403/index.vue deleted file mode 100644 index 54bea1f..0000000 --- a/frontend/src/views/error/403/index.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - - - diff --git a/frontend/src/views/error/404/index.vue b/frontend/src/views/error/404/index.vue deleted file mode 100644 index b8c5021..0000000 --- a/frontend/src/views/error/404/index.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - - - diff --git a/frontend/src/views/login/index.ts b/frontend/src/views/login/index.ts deleted file mode 100644 index dc12d32..0000000 --- a/frontend/src/views/login/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface UserNamePwd { - username: string; - password: string; -} diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue deleted file mode 100644 index b6b31ba..0000000 --- a/frontend/src/views/login/index.vue +++ /dev/null @@ -1,158 +0,0 @@ - - - - - diff --git a/frontend/src/views/messages/index.ts b/frontend/src/views/messages/index.ts deleted file mode 100644 index 3c6c48c..0000000 --- a/frontend/src/views/messages/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Message { - date: string; - title: string; -} - -export interface State { - [key: string]: Message[]; -} diff --git a/frontend/src/views/messages/index.vue b/frontend/src/views/messages/index.vue deleted file mode 100644 index ee7f702..0000000 --- a/frontend/src/views/messages/index.vue +++ /dev/null @@ -1,166 +0,0 @@ - - - - - diff --git a/frontend/src/views/settings/course/index.ts b/frontend/src/views/settings/course/index.ts deleted file mode 100644 index 3eac166..0000000 --- a/frontend/src/views/settings/course/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { CourseForm } from "@/types/table"; - -export interface State { - courseData: CourseForm[]; - pageTotal: number; - isDisabled: boolean; -} diff --git a/frontend/src/views/settings/course/index.vue b/frontend/src/views/settings/course/index.vue deleted file mode 100644 index 1a98c29..0000000 --- a/frontend/src/views/settings/course/index.vue +++ /dev/null @@ -1,144 +0,0 @@ - - -