diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cfe399 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +__pycache__/ +.idea/ +venv/ +.mypy_cache/ +.vscode +.ruff_cache/ +.pytest_cache/ + + +db.sqlite3 +db.sqlite3-journal +db.sqlite3-shm +db.sqlite3-wal + +.DS_Store \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f16eff0 --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +# Build configuration +# ------------------- + +APP_NAME := `sed -n 's/^ *name.*=.*"\([^"]*\)".*/\1/p' pyproject.toml` +APP_VERSION := `sed -n 's/^ *version.*=.*"\([^"]*\)".*/\1/p' pyproject.toml` +GIT_REVISION = `git rev-parse HEAD` + +# Introspection targets +# --------------------- + +.PHONY: help +help: header targets + +.PHONY: header +header: + @echo "\033[34mEnvironment\033[0m" + @echo "\033[34m---------------------------------------------------------------\033[0m" + @printf "\033[33m%-23s\033[0m" "APP_NAME" + @printf "\033[35m%s\033[0m" $(APP_NAME) + @echo "" + @printf "\033[33m%-23s\033[0m" "APP_VERSION" + @printf "\033[35m%s\033[0m" $(APP_VERSION) + @echo "" + @printf "\033[33m%-23s\033[0m" "GIT_REVISION" + @printf "\033[35m%s\033[0m" $(GIT_REVISION) + @echo "\n" + +.PHONY: targets +targets: + @echo "\033[34mDevelopment Targets\033[0m" + @echo "\033[34m---------------------------------------------------------------\033[0m" + @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-22s\033[0m %s\n", $$1, $$2}' + +# Development targets +# ------------- + +.PHONY: install +install: ## Install dependencies + poetry install + +.PHONY: run +run: start + +.PHONY: start +start: ## Starts the server + $(eval include .env) + $(eval export $(sh sed 's/=.*//' .env)) + + poetry run python run.py + +# Check, lint and format targets +# ------------------------------ + +.PHONY: check +check: check-format lint + +.PHONY: check-format +check-format: ## Dry-run code formatter + poetry run black ./ --check + poetry run isort ./ --profile black --check + +.PHONY: lint +lint: ## Run ruff + poetry run ruff check ./app + +.PHONY: format +format: ## Run code formatter + poetry run black ./ + poetry run isort ./ --profile black + +.PHONY: check-lockfile +check-lockfile: ## Compares lock file with pyproject.toml + poetry lock --check + +.PHONY: test +test: ## Run the test suite + $(eval include .env) + $(eval export $(sh sed 's/=.*//' .env)) + + poetry run pytest -vv -s --cache-clear ./ \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..ad9acd9 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,39 @@ +from fastapi import FastAPI + +from app.core.exceptions import SettingNotFound +from app.core.init_app import ( + init_menus, + init_superuser, + make_middlewares, + register_db, + register_exceptions, + register_routers, +) + +try: + from app.settings.config import settings +except ImportError: + raise SettingNotFound("Can not import settings. Create settings file from template.config.py") + + +def create_app() -> FastAPI: + app = FastAPI( + title=settings.APP_TITLE, + description=settings.APP_DESCRIPTION, + version=settings.VERSION, + openapi_url="/openapi.json", + middleware=make_middlewares(), + ) + register_db(app) + register_exceptions(app) + register_routers(app, prefix="/api") + return app + + +app = create_app() + + +@app.on_event("startup") +async def startup_event(): + await init_superuser() + await init_menus() diff --git a/app/api/__init__.py b/app/api/__init__.py new file mode 100644 index 0000000..8ed6454 --- /dev/null +++ b/app/api/__init__.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter + +from .v1 import v1_router + +api_router = APIRouter() +api_router.include_router(v1_router, prefix="/v1") + + +__all__ = ["api_router"] diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py new file mode 100644 index 0000000..80b53a7 --- /dev/null +++ b/app/api/v1/__init__.py @@ -0,0 +1,17 @@ +from fastapi import APIRouter + +from app.core.dependency import DependPermisson + +from .apis import apis_router +from .base import base_router +from .menus import menus_router +from .roles import roles_router +from .users import users_router + +v1_router = APIRouter() + +v1_router.include_router(base_router, prefix="/base") +v1_router.include_router(users_router, prefix="/user", dependencies=[DependPermisson]) +v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermisson]) +v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermisson]) +v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermisson]) diff --git a/app/api/v1/apis/__init__.py b/app/api/v1/apis/__init__.py new file mode 100644 index 0000000..e5435dc --- /dev/null +++ b/app/api/v1/apis/__init__.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +from .apis import router + +apis_router = APIRouter() +apis_router.include_router(router, tags=["API模块"]) + +__all__ = ["apis_router"] diff --git a/app/api/v1/apis/apis.py b/app/api/v1/apis/apis.py new file mode 100644 index 0000000..fc5a936 --- /dev/null +++ b/app/api/v1/apis/apis.py @@ -0,0 +1,101 @@ +from fastapi import APIRouter, Query +from fastapi.routing import APIRoute +from tortoise.expressions import Q + +from app.controllers.api import api_controller +from app.log import logger +from app.models.admin import Api +from app.schemas import Success, SuccessExtra +from app.schemas.apis import * + +router = APIRouter() + + +@router.get("/list", summary="查看API列表") +async def list_api( + page: int = Query(1, description="页码"), + page_size: int = Query(10, description="每页数量"), + path: str = Query(None, description="API路径"), + summary: str = Query(None, description="API简介"), + tags: str = Query(None, description="API模块"), +): + q = Q() + if path: + q &= Q(path__contains=path) + if summary: + q &= Q(summary__contains=summary) + if tags: + q &= Q(tags__contains=tags) + total, api_objs = await api_controller.list(page=page, page_size=page_size, search=q, order=["id"]) + result = [] + for api in api_objs: + api_dict = await api.to_dict(m2m=False) + result.append(api_dict) + return SuccessExtra(data=result, total=total, page=page, page_size=page_size) + + +@router.get("/get", summary="查看Api") +async def get_api( + id: int = Query(..., description="Api"), +): + api_obj = await api_controller.get(id=id) + api_dict = await api_obj.to_dict() + return Success(code=200, data=api_dict) + + +@router.post("/create", summary="创建Api") +async def create_api( + api_in: ApiCreate, +): + new_api = await api_controller.create(obj_in=api_in) + return Success(msg="Created Successfully", data=new_api) + + +@router.post("/update", summary="更新Api") +async def update_api( + api_in: ApiUpdate, +): + await api_controller.update(id=api_in.id, obj_in=api_in.update_dict()) + return Success(msg="Update Successfully") + + +@router.delete("/delete", summary="删除Api") +async def delete_api( + api_id: int = Query(..., description="ApiID"), +): + await api_controller.remove(id=api_id) + return Success(msg="Deleted Success") + + +@router.post("/refresh", summary="刷新API列表") +async def refresh_api(): + from app import app + + # 删除废弃API数据 + all_api_list = [] + for route in app.routes: + if isinstance(route, APIRoute): + all_api_list.append((list(route.methods)[0], route.path_format)) + delete_api = [] + for api in await Api.all(): + if (api.method, api.path) not in all_api_list: + delete_api.append((api.method, api.path)) + for item in delete_api: + method, path = item + logger.debug(f"API Deleted {method} {path}") + await Api.filter(method=method, path=path).delete() + + for route in app.routes: + if isinstance(route, APIRoute): + method = list(route.methods)[0] + path = route.path_format + summary = route.summary + tags = list(route.tags)[0] + api_obj = await Api.filter(method=method, path=path).first() + if api_obj: + await api_obj.update_from_dict(dict(method=method, path=path, summary=summary, tags=tags)).save() + else: + logger.debug(f"API Created {method} {path}") + await Api.create(**dict(method=method, path=path, summary=summary, tags=tags)) + + return Success(msg="OK") diff --git a/app/api/v1/base/__init__.py b/app/api/v1/base/__init__.py new file mode 100644 index 0000000..bc9b157 --- /dev/null +++ b/app/api/v1/base/__init__.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +from .base import router + +base_router = APIRouter() +base_router.include_router(router, tags=["基础模块"]) + +__all__ = ["base_router"] diff --git a/app/api/v1/base/base.py b/app/api/v1/base/base.py new file mode 100644 index 0000000..eeefe52 --- /dev/null +++ b/app/api/v1/base/base.py @@ -0,0 +1,106 @@ +from datetime import datetime, timedelta + +from fastapi import APIRouter + +from app.controllers.user import UserController, user_controller +from app.core.ctx import CTX_USER_ID +from app.core.dependency import DependAuth +from app.models.admin import Api, Menu, Role, User +from app.schemas.base import BaseResponse, Fail, Success +from app.schemas.login import * +from app.schemas.users import UpdatePassword +from app.settings import settings +from app.utils.jwt import create_access_token +from app.utils.password import get_password_hash, verify_password + +router = APIRouter() + + +@router.post("/access_token", summary="获取token") +async def login_access_token(credentials: CredentialsSchema) -> BaseResponse: + user: User = await user_controller.authenticate(credentials) + await user_controller.update_last_login(user.id) + access_token_expires = timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) + expire = datetime.utcnow() + access_token_expires + + result = JWTOut( + access_token=create_access_token( + data=JWTPayload( + user_id=user.id, + username=user.username, + is_superuser=user.is_superuser, + exp=expire, + ) + ), + username=user.username, + ) + + return BaseResponse(code=200, data=result) + + +@router.get("/userinfo", summary="查看用户信息", dependencies=[DependAuth]) +async def get_userinfo(): + user_id = CTX_USER_ID.get() + user_obj = await user_controller.get(id=user_id) + to_dict = await user_obj.to_dict() + to_dict.pop("password") + to_dict["avatar"] = "https://avatars.githubusercontent.com/u/54677442?v=4" + + return BaseResponse(code=200, data=to_dict) + + +@router.get("/usermenu", summary="查看用户菜单", dependencies=[DependAuth]) +async def get_user_menu() -> BaseResponse: + user_id = CTX_USER_ID.get() + user_obj = await User.filter(id=user_id).first() + menus: list[Menu] = [] + if user_obj.is_superuser: + menus = await Menu.all() + else: + role_objs: list[Role] = await user_obj.roles + for role_obj in role_objs: + menu = await role_obj.menus + menus.extend(menu) + menus = list(set(menus)) + parent_menus: list[Menu] = [] + for menu in menus: + if menu.parent_id == 0: + parent_menus.append(menu) + res = [] + for parent_menu in parent_menus: + parent_menu_dict = await parent_menu.to_dict() + parent_menu_dict["children"] = [] + for menu in menus: + if menu.parent_id == parent_menu.id: + parent_menu_dict["children"].append(await menu.to_dict()) + res.append(parent_menu_dict) + return Success(data=res) + + +@router.get("/userapi", summary="查看用户API", dependencies=[DependAuth]) +async def get_user_api() -> BaseResponse: + user_id = CTX_USER_ID.get() + user_obj = await User.filter(id=user_id).first() + if user_obj.is_superuser: + api_objs: list[Api] = await Api.all() + apis = [api.method.lower() + api.path for api in api_objs] + return Success(data=apis) + role_objs: list[Role] = await user_obj.roles + apis = [] + for role_obj in role_objs: + api_objs: list[Api] = await role_obj.apis + apis.extend([api.method.lower() + api.path for api in api_objs]) + apis = list(set(apis)) + return Success(data=apis) + + +@router.post("/update_password", summary="更新用户密码", dependencies=[DependAuth]) +async def update_user_password(req_in: UpdatePassword) -> BaseResponse: + user_controller = UserController() + user = await user_controller.get(req_in.id) + verified = verify_password(req_in.old_password, user.password) + if not verified: + return Fail(msg="旧密码验证错误!") + user.password = get_password_hash(req_in.new_password) + await user.save() + return Success(msg="修改成功") diff --git a/app/api/v1/menus/__init__.py b/app/api/v1/menus/__init__.py new file mode 100644 index 0000000..3e27a24 --- /dev/null +++ b/app/api/v1/menus/__init__.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +from .menus import router + +menus_router = APIRouter() +menus_router.include_router(router, tags=["菜单模块"]) + +__all__ = ["menus_router"] diff --git a/app/api/v1/menus/menus.py b/app/api/v1/menus/menus.py new file mode 100644 index 0000000..9eeef21 --- /dev/null +++ b/app/api/v1/menus/menus.py @@ -0,0 +1,61 @@ +import logging + +from fastapi import APIRouter, Query + +from app.controllers.menu import menu_controller +from app.schemas.base import BaseResponse, Fail, Success +from app.schemas.menus import * + +logger = logging.getLogger(__name__) + +router = APIRouter() + + +@router.get("/list", summary="查看菜单列表") +async def list_menu( + page: int = Query(1, description="页码"), + page_size: int = Query(10, description="每页数量"), +) -> MenuOutList: + parent_menus = await menu_controller.model.filter(parent_id=0).order_by("order") + res_menu = [] + for menu in parent_menus: + child_menu = await menu_controller.model.filter(parent_id=menu.id).order_by("order") + menu_dict = await menu.to_dict() + menu_dict["children"] = child_menu + res_menu.append(menu_dict) + return SuccessExtra(data=res_menu, total=len(res_menu), page=page, page_size=page_size) + + +@router.get("/get", summary="查看菜单") +async def get_menu( + menu_id: int = Query(..., description="菜单id"), +) -> BaseResponse: + result = await menu_controller.get(id=menu_id) + return Success(data=result) + + +@router.post("/create", summary="创建菜单") +async def create_menu( + menu_in: MenuCreate, +) -> BaseResponse: + await menu_controller.create(obj_in=menu_in) + return Success(msg="Created Success") + + +@router.post("/update", summary="更新菜单") +async def update_menu( + menu_in: MenuUpdate, +) -> BaseResponse: + await menu_controller.update(id=menu_in.id, obj_in=menu_in.update_dict()) + return Success(msg="Updated Success") + + +@router.delete("/delete", summary="删除菜单") +async def delete_menu( + id: int = Query(..., description="菜单id"), +) -> BaseResponse: + child_menu_count = await menu_controller.model.filter(parent_id=id).count() + if child_menu_count > 0: + return Fail(msg="Cannot delete a menu with child menus") + await menu_controller.remove(id=id) + return Success(msg="Deleted Success") diff --git a/app/api/v1/roles/__init__.py b/app/api/v1/roles/__init__.py new file mode 100644 index 0000000..0b6957e --- /dev/null +++ b/app/api/v1/roles/__init__.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +from .roles import router + +roles_router = APIRouter() +roles_router.include_router(router, tags=["角色模块"]) + +__all__ = ["roles_router"] diff --git a/app/api/v1/roles/roles.py b/app/api/v1/roles/roles.py new file mode 100644 index 0000000..7357a96 --- /dev/null +++ b/app/api/v1/roles/roles.py @@ -0,0 +1,74 @@ +import logging + +from fastapi import APIRouter, Query +from fastapi.exceptions import HTTPException +from tortoise.expressions import Q + +from app.controllers import role_controller +from app.schemas.base import BaseResponse, Success, SuccessExtra +from app.schemas.roles import * + +logger = logging.getLogger(__name__) +router = APIRouter() + + +@router.get("/list", summary="查看角色列表") +async def list_role( + page: int = Query(1, description="页码"), + page_size: int = Query(10, description="每页数量"), + role_name: str = Query("", description="角色名称,用于查询"), +) -> RoleOutList: + q = Q() + if role_name: + q = Q(name__contains=role_name) + total, result = await role_controller.list(page=page, page_size=page_size, search=q) + return SuccessExtra(data=result, total=total, page=page, page_size=page_size) + + +@router.get("/get", summary="查看角色") +async def get_role( + role_id: int = Query(..., description="角色ID"), +) -> RoleOut: + role_obj = await role_controller.get(id=role_id) + return Success(data=role_obj) + + +@router.post("/create", summary="创建角色") +async def create_role( + role_in: RoleCreate, +) -> BaseResponse: + if await role_controller.is_exist(name=role_in.name): + raise HTTPException( + status_code=400, + detail="The role with this rolename already exists in the system.", + ) + await role_controller.create(obj_in=role_in) + return Success(msg="Created Successfully") + + +@router.post("/update", summary="更新角色") +async def update_role(role_in: RoleUpdate) -> BaseResponse: + await role_controller.update(id=role_in.id, obj_in=role_in.update_dict()) + return Success(msg="Updated Successfully") + + +@router.delete("/delete", summary="删除角色") +async def delete_role( + role_id: int = Query(..., description="角色ID"), +) -> BaseResponse: + await role_controller.remove(id=role_id) + return Success(msg="Deleted Success") + + +@router.get("/authorized", summary="查看角色权限") +async def get_role_authorized(id: int = Query(..., description="角色ID")) -> BaseResponse: + role_obj = await role_controller.get(id=id) + role_dict = await role_obj.to_dict() + return Success(data=role_dict) + + +@router.post("/authorized", summary="更新角色权限") +async def update_role_authorized(role_in: RoleUpdateMenusApis) -> BaseResponse: + role_obj = await role_controller.get(id=role_in.id) + await role_controller.update_roles(role=role_obj, menu_ids=role_in.menu_ids, api_infos=role_in.api_infos) + return Success(msg="Updated Successfully") diff --git a/app/api/v1/users/__init__.py b/app/api/v1/users/__init__.py new file mode 100644 index 0000000..c3b420e --- /dev/null +++ b/app/api/v1/users/__init__.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +from .users import router + +users_router = APIRouter() +users_router.include_router(router, tags=["用户模块"]) + +__all__ = ["users_router"] diff --git a/app/api/v1/users/users.py b/app/api/v1/users/users.py new file mode 100644 index 0000000..50807d1 --- /dev/null +++ b/app/api/v1/users/users.py @@ -0,0 +1,81 @@ +import logging + +from fastapi import APIRouter, Query +from fastapi.exceptions import HTTPException +from tortoise.expressions import Q + +from app.controllers.user import UserController +from app.core.dependency import DependPermisson +from app.schemas.base import BaseResponse, Success, SuccessExtra +from app.schemas.users import * + +logger = logging.getLogger(__name__) + +router = APIRouter() + + +@router.get("/list", summary="查看用户列表", dependencies=[DependPermisson]) +async def list_user( + page: int = Query(1, description="页码"), + page_size: int = Query(10, description="每页数量"), + username: str = Query("", description="用户名称,用于搜索"), + email: str = Query("", description="邮箱地址"), +) -> UserOutList: + user_controller = UserController() + q = Q() + if username: + q &= Q(username__contains=username) + if email: + q &= Q(email__contains=email) + total, user_objs = await user_controller.list(page=page, page_size=page_size, search=q) + result = [] + for user in user_objs: + user_dict = await user.to_dict() + result.append(user_dict) + return SuccessExtra(data=result, total=total, page=page, page_size=page_size) + + +@router.get("/get", summary="查看用户", dependencies=[DependPermisson]) +async def get_user( + user_id: int = Query(..., description="用户ID"), +) -> UserOut: + user_controller = UserController() + user_obj = await user_controller.get(id=user_id) + user_dict = await user_obj.to_dict() + return Success(code=200, data=user_dict) + + +@router.post("/create", summary="创建用户", dependencies=[DependPermisson]) +async def create_user( + user_in: UserCreate, +) -> BaseResponse: + user_controller = UserController() + user = await user_controller.get_by_email(user_in.email) + if user: + raise HTTPException( + status_code=400, + detail="The user with this email already exists in the system.", + ) + new_user = await user_controller.create(obj_in=user_in) + await user_controller.update_roles(new_user, user_in.roles) + return Success(msg="Created Successfully") + + +@router.post("/update", summary="更新用户", dependencies=[DependPermisson]) +async def update_user( + user_in: UserUpdate, +) -> BaseResponse: + print(user_in.dict()) + user_controller = UserController() + user = await user_controller.update(obj_in=user_in) + await user_controller.update_roles(user, user_in.roles) + return Success(msg="Updated Successfully") + + +@router.delete("/delete", summary="删除用户", dependencies=[DependPermisson]) +async def delete_user( + user_id: int = Query(..., description="用户ID"), +) -> BaseResponse: + user_controller = UserController() + await user_controller.remove(id=user_id) + return Success(msg="Deleted Successfully") diff --git a/app/controllers/__init__.py b/app/controllers/__init__.py new file mode 100644 index 0000000..f12bef9 --- /dev/null +++ b/app/controllers/__init__.py @@ -0,0 +1,2 @@ +from .role import role_controller as role_controller +from .user import user_controller as user_controller diff --git a/app/controllers/api.py b/app/controllers/api.py new file mode 100644 index 0000000..ad51a4f --- /dev/null +++ b/app/controllers/api.py @@ -0,0 +1,11 @@ +from app.core.crud import CRUDBase +from app.models.admin import Api +from app.schemas.apis import ApiCreate, ApiUpdate + + +class ApiController(CRUDBase[Api, ApiCreate, ApiUpdate]): + def __init__(self): + super().__init__(model=Api) + + +api_controller = ApiController() diff --git a/app/controllers/menu.py b/app/controllers/menu.py new file mode 100644 index 0000000..d42586a --- /dev/null +++ b/app/controllers/menu.py @@ -0,0 +1,16 @@ +from typing import Optional + +from app.core.crud import CRUDBase +from app.models.admin import Menu +from app.schemas.menus import MenuCreate, MenuUpdate + + +class MenuController(CRUDBase[Menu, MenuCreate, MenuUpdate]): + def __init__(self): + super().__init__(model=Menu) + + async def get_by_menu_path(self, path: str) -> Optional["Menu"]: + return await self.model.filter(path=path).first() + + +menu_controller = MenuController() diff --git a/app/controllers/role.py b/app/controllers/role.py new file mode 100644 index 0000000..cb6a528 --- /dev/null +++ b/app/controllers/role.py @@ -0,0 +1,27 @@ +from typing import List + +from app.core.crud import CRUDBase +from app.models.admin import Api, Menu, Role +from app.schemas.roles import RoleCreate, RoleUpdate + + +class RoleController(CRUDBase[Role, RoleCreate, RoleUpdate]): + def __init__(self): + super().__init__(model=Role) + + async def is_exist(self, name: str) -> bool: + return await self.model.filter(name=name).exists() + + async def update_roles(self, role: Role, menu_ids: List[int], api_infos: List[dict]) -> None: + await role.menus.clear() + for menu_id in menu_ids: + menu_obj = await Menu.filter(id=menu_id).first() + await role.menus.add(menu_obj) + + await role.apis.clear() + for item in api_infos: + api_obj = await Api.filter(path=item.get("path"), method=item.get("method")).first() + await role.apis.add(api_obj) + + +role_controller = RoleController() diff --git a/app/controllers/user.py b/app/controllers/user.py new file mode 100644 index 0000000..b7a8494 --- /dev/null +++ b/app/controllers/user.py @@ -0,0 +1,56 @@ +from datetime import datetime +from typing import List, Optional + +from fastapi.exceptions import HTTPException + +from app.core.crud import CRUDBase +from app.models.admin import User +from app.schemas.login import CredentialsSchema +from app.schemas.users import UserCreate, UserUpdate +from app.utils.password import get_password_hash, verify_password + +from .role import role_controller + + +class UserController(CRUDBase[User, UserCreate, UserUpdate]): + def __init__(self): + super().__init__(model=User) + + async def get_by_email(self, email: str) -> Optional[User]: + return await self.model.filter(email=email).first() + + async def get_by_username(self, username: str) -> Optional[User]: + return await self.model.filter(username=username).first() + + async def create(self, obj_in: UserCreate) -> User: + obj_in.password = get_password_hash(password=obj_in.password) + obj = await super().create(obj_in.create_dict()) + return obj + + async def update(self, obj_in: UserUpdate) -> User: + return await super().update(id=obj_in.id, obj_in=obj_in.update_dict()) + + async def update_last_login(self, id: int) -> None: + user = await self.model.get(id=id) + user.last_login = datetime.now() + await user.save() + + async def authenticate(self, credentials: CredentialsSchema) -> Optional["User"]: + user = await self.model.filter(username=credentials.username).first() + if not user: + raise HTTPException(status_code=400, detail="无效的用户名") + verified = verify_password(credentials.password, user.password) + if not verified: + raise HTTPException(status_code=400, detail="密码错误!") + if not user.is_active: + raise HTTPException(status_code=400, detail="用户已被禁用") + return user + + async def update_roles(self, user: User, roles: List[int]) -> None: + await user.roles.clear() + for role_id in roles: + role_obj = await role_controller.get(id=role_id) + await user.roles.add(role_obj) + + +user_controller = UserController() diff --git a/app/core/bgtask.py b/app/core/bgtask.py new file mode 100644 index 0000000..6d5b6fe --- /dev/null +++ b/app/core/bgtask.py @@ -0,0 +1,31 @@ +from starlette.background import BackgroundTasks + +from .ctx import CTX_BG_TASKS + + +class BgTasks: + """后台任务统一管理""" + + @classmethod + async def init_bg_tasks_obj(cls): + """实例化后台任务,并设置到上下文""" + bg_tasks = BackgroundTasks() + CTX_BG_TASKS.set(bg_tasks) + + @classmethod + async def get_bg_tasks_obj(cls): + """从上下文中获取后台任务实例""" + return CTX_BG_TASKS.get() + + @classmethod + async def add_task(cls, func, *args, **kwargs): + """添加后台任务""" + bg_tasks = await cls.get_bg_tasks_obj() + bg_tasks.add_task(func, *args, **kwargs) + + @classmethod + async def execute_tasks(cls): + """执行后台任务,一般是请求结果返回之后执行""" + bg_tasks = await cls.get_bg_tasks_obj() + if bg_tasks.tasks: + await bg_tasks() diff --git a/app/core/crud.py b/app/core/crud.py new file mode 100644 index 0000000..6743427 --- /dev/null +++ b/app/core/crud.py @@ -0,0 +1,45 @@ +from typing import Any, Dict, Generic, List, NewType, Tuple, Type, TypeVar, Union + +from pydantic import BaseModel +from tortoise.expressions import Q +from tortoise.models import Model + +Total = NewType("Total", int) +ModelType = TypeVar("ModelType", bound=Model) +CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) +UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) + + +class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): + def __init__(self, model: Type[ModelType]): + self.model = model + + async def get(self, id: int) -> ModelType: + return await self.model.get(id=id) + + async def list(self, page: int, page_size: int, search: Q = Q(), order: list = []) -> Tuple[Total, List[ModelType]]: + query = self.model.filter(search) + return await query.count(), await query.offset((page - 1) * page_size).limit(page_size).order_by(*order) + + async def create(self, obj_in: CreateSchemaType) -> ModelType: + if isinstance(obj_in, Dict): + obj_dict = obj_in + else: + obj_dict = obj_in.dict() + obj = self.model(**obj_dict) + await obj.save() + return obj + + async def update(self, id: int, obj_in: Union[UpdateSchemaType, Dict[str, Any]]) -> ModelType: + if isinstance(obj_in, Dict): + obj_dict = obj_in + else: + obj_dict = obj_in.dict(exclude_unset=True) + obj = await self.get(id=id) + obj = obj.update_from_dict(obj_dict) + await obj.save() + return obj + + async def remove(self, id: int) -> None: + obj = await self.get(id=id) + await obj.delete() diff --git a/app/core/ctx.py b/app/core/ctx.py new file mode 100644 index 0000000..91aab2f --- /dev/null +++ b/app/core/ctx.py @@ -0,0 +1,6 @@ +import contextvars + +from starlette.background import BackgroundTasks + +CTX_USER_ID: contextvars.ContextVar[int] = contextvars.ContextVar("user_id", default=0) +CTX_BG_TASKS: contextvars.ContextVar[BackgroundTasks] = contextvars.ContextVar("bg_task", default=None) diff --git a/app/core/dependency.py b/app/core/dependency.py new file mode 100644 index 0000000..2c515a4 --- /dev/null +++ b/app/core/dependency.py @@ -0,0 +1,53 @@ +from typing import Optional + +import jwt +from fastapi import Depends, Header, HTTPException, Request + +from app.core.ctx import CTX_USER_ID +from app.models import Role, User +from app.settings import settings + + +class AuthControl: + @classmethod + async def is_authed(cls, token: str = Header(..., description="token验证")) -> Optional["User"]: + try: + if token == "dev": + user = await User.filter().first() + user_id = user.id + else: + decode_data = jwt.decode(token, settings.SECRET_KEY, algorithms=settings.JWT_ALGORITHM) + user_id = decode_data.get("user_id") + user = await User.filter(id=user_id).first() + if not user: + raise HTTPException(status_code=401, detail="Authentication failed") + CTX_USER_ID.set(int(user_id)) + return user + except jwt.DecodeError: + raise HTTPException(status_code=401, detail="无效的Token") + except jwt.ExpiredSignatureError: + raise HTTPException(status_code=401, detail="登录已过期") + except Exception as e: + raise HTTPException(status_code=500, detail=f"{repr(e)}") + + +class PermissionControl: + @classmethod + async def has_permission(cls, request: Request, current_user: User = Depends(AuthControl.is_authed)) -> None: + if current_user.is_superuser: + return + method = request.method + path = request.url.path + roles: list[Role] = await current_user.roles + if not roles: + raise HTTPException(status_code=403, detail="The user is not bound to a role") + apis = [await role.apis for role in roles] + permission_apis = list(set((api.method, api.path) for api in sum(apis, []))) + # path = "/api/v1/auth/userinfo" + # method = "GET" + if (method, path) not in permission_apis: + raise HTTPException(status_code=403, detail=f"Permission denied method:{method} path:{path}") + + +DependAuth = Depends(AuthControl.is_authed) +DependPermisson = Depends(PermissionControl.has_permission) diff --git a/app/core/exceptions.py b/app/core/exceptions.py new file mode 100644 index 0000000..b1ba744 --- /dev/null +++ b/app/core/exceptions.py @@ -0,0 +1,43 @@ +from fastapi.exceptions import ( + HTTPException, + RequestValidationError, + ResponseValidationError, +) +from fastapi.requests import Request +from fastapi.responses import JSONResponse +from tortoise.exceptions import DoesNotExist, IntegrityError + + +class SettingNotFound(Exception): + pass + + +async def DoesNotExistHandle(req: Request, exc: DoesNotExist) -> JSONResponse: + content = dict( + code=404, + msg=f"Object has not found, exc: {exc}, query_params: {req.query_params}", + ) + return JSONResponse(content=content, status_code=404) + + +async def IntegrityHandle(_: Request, exc: IntegrityError) -> JSONResponse: + content = dict( + code=500, + msg=f"IntegrityError,{exc}", + ) + return JSONResponse(content=content, status_code=500) + + +async def HttpExcHandle(_: Request, exc: HTTPException) -> JSONResponse: + content = dict(code=exc.status_code, msg=exc.detail, data=None) + return JSONResponse(content=content, status_code=exc.status_code) + + +async def RequestValidationHandle(_: Request, exc: RequestValidationError) -> JSONResponse: + content = dict(code=422, msg=f"RequestValidationError, {exc}") + return JSONResponse(content=content, status_code=422) + + +async def ResponseValidationHandle(_: Request, exc: ResponseValidationError) -> JSONResponse: + content = dict(code=500, msg=f"ResponseValidationError, {exc}") + return JSONResponse(content=content, status_code=500) diff --git a/app/core/init_app.py b/app/core/init_app.py new file mode 100644 index 0000000..ef06eac --- /dev/null +++ b/app/core/init_app.py @@ -0,0 +1,138 @@ +from fastapi import FastAPI +from fastapi.middleware import Middleware +from fastapi.middleware.cors import CORSMiddleware +from tortoise.contrib.fastapi import register_tortoise + +from app.api import api_router +from app.controllers.user import UserCreate, user_controller +from app.core.exceptions import ( + DoesNotExist, + DoesNotExistHandle, + HTTPException, + HttpExcHandle, + IntegrityError, + IntegrityHandle, + RequestValidationError, + RequestValidationHandle, + ResponseValidationError, + ResponseValidationHandle, +) +from app.models.admin import Menu +from app.schemas.menus import MenuType +from app.settings.config import settings + +from .middlewares import BackGroundTaskMiddleware + + +def make_middlewares(): + middleware = [ + Middleware( + CORSMiddleware, + allow_origins=settings.CORS_ORIGINS, + allow_credentials=settings.CORS_ALLOW_CREDENTIALS, + allow_methods=settings.CORS_ALLOW_METHODS, + allow_headers=settings.CORS_ALLOW_HEADERS, + ), + Middleware(BackGroundTaskMiddleware), + ] + return middleware + + +def register_db(app: FastAPI, db_url=None): + register_tortoise( + app, + # db_url='sqlite://db.sqlite3', + # modules={'models':['app.models', "aerich.models"]}, + config=settings.TORTOISE_ORM, + generate_schemas=True, + ) + + +def register_exceptions(app: FastAPI): + app.add_exception_handler(DoesNotExist, DoesNotExistHandle) + app.add_exception_handler(HTTPException, HttpExcHandle) + app.add_exception_handler(IntegrityError, IntegrityHandle) + app.add_exception_handler(RequestValidationError, RequestValidationHandle) + app.add_exception_handler(ResponseValidationError, ResponseValidationHandle) + + +def register_routers(app: FastAPI, prefix: str = "/api"): + app.include_router(api_router, prefix=prefix) + + +async def init_superuser(): + user = await user_controller.model.exists() + if not user: + await user_controller.create( + UserCreate( + username="admin", + email="admin@admin.com", + password="123456", + is_active=True, + is_superuser=True, + ) + ) + + +async def init_menus(): + menus = await Menu.exists() + if not menus: + parent_menu = await Menu.create( + menu_type=MenuType.CATALOG, + name="系统管理", + path="/system", + order=1, + parent_id=0, + icon="carbon:gui-management", + is_hidden=False, + component="Layout", + keepalive=True, + redirect="/system/user", + ) + children_menu = [ + Menu( + menu_type=MenuType.MENU, + name="用户管理", + path="user", + order=1, + parent_id=parent_menu.id, + icon="material-symbols:person-outline-rounded", + is_hidden=False, + component="/system/user", + keepalive=True, + ), + Menu( + menu_type=MenuType.MENU, + name="角色管理", + path="role", + order=2, + parent_id=parent_menu.id, + icon="carbon:user-role", + is_hidden=False, + component="/system/role", + keepalive=True, + ), + Menu( + menu_type=MenuType.MENU, + name="菜单管理", + path="menu", + order=3, + parent_id=parent_menu.id, + icon="material-symbols:list-alt-outline", + is_hidden=False, + component="/system/menu", + keepalive=True, + ), + Menu( + menu_type=MenuType.MENU, + name="API管理", + path="api", + order=4, + parent_id=parent_menu.id, + icon="ant-design:api-outlined", + is_hidden=False, + component="/system/api", + keepalive=True, + ), + ] + await Menu.bulk_create(children_menu) diff --git a/app/core/middlewares.py b/app/core/middlewares.py new file mode 100644 index 0000000..a6456b5 --- /dev/null +++ b/app/core/middlewares.py @@ -0,0 +1,34 @@ +from starlette.requests import Request +from starlette.types import ASGIApp, Receive, Scope, Send + +from .bgtask import BgTasks + + +class SimpleBaseMiddleware: + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": + await self.app(scope, receive, send) + return + + request = Request(scope, receive=receive) + + response = await self.before_request(request) or self.app + await response(request.scope, request.receive, send) + await self.after_request(request) + + async def before_request(self, request: Request): + return self.app + + async def after_request(self, request: Request): + return None + + +class BackGroundTaskMiddleware(SimpleBaseMiddleware): + async def before_request(self, request): + await BgTasks.init_bg_tasks_obj() + + async def after_request(self, request): + await BgTasks.execute_tasks() diff --git a/app/log/__init__.py b/app/log/__init__.py new file mode 100644 index 0000000..968bf67 --- /dev/null +++ b/app/log/__init__.py @@ -0,0 +1 @@ +from .log import logger as logger diff --git a/app/log/log.py b/app/log/log.py new file mode 100644 index 0000000..89ef8e8 --- /dev/null +++ b/app/log/log.py @@ -0,0 +1,25 @@ +import sys + +from loguru import logger as loguru_logger + +from app.settings import settings + + +class Loggin: + def __init__(self) -> None: + debug = settings.DEBUG + if debug: + self.level = "DEBUG" + else: + self.level = "INFO" + + def setup_logger(self): + loguru_logger.remove() + loguru_logger.add(sink=sys.stdout, level=self.level) + + # logger.add("my_project.log", level=level, rotation="100 MB") # Output log messages to a file + return loguru_logger + + +loggin = Loggin() +logger = loggin.setup_logger() diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..6e607b7 --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1 @@ +from .admin import * diff --git a/app/models/admin.py b/app/models/admin.py new file mode 100644 index 0000000..d62dd26 --- /dev/null +++ b/app/models/admin.py @@ -0,0 +1,74 @@ +from tortoise import fields + +from app.schemas.menus import MenuType + +from .base import BaseModel, TimestampMixin +from .enums import MethodType + + +class User(BaseModel, TimestampMixin): + username = fields.CharField(max_length=20, unique=True, description="用户名称") + alias = fields.CharField(max_length=30, null=True, description="姓名") + email = fields.CharField(max_length=255, unique=True, description="邮箱") + phone = fields.CharField(max_length=20, null=True, description="电话") + password = fields.CharField(max_length=128, null=True, description="密码") + is_active = fields.BooleanField(default=True, description="是否激活") + is_superuser = fields.BooleanField(default=False, description="是否为超级管理员") + last_login = fields.DatetimeField(null=True, description="最后登录时间") + roles = fields.ManyToManyField("models.Role", related_name="user_roles") + + class Meta: + table = "user" + + class PydanticMeta: + # todo + # computed = ["full_name"] + ... + + +class Role(BaseModel, TimestampMixin): + name = fields.CharField(max_length=20, unique=True, description="角色名称") + desc = fields.CharField(max_length=500, null=True, blank=True, description="角色描述") + menus = fields.ManyToManyField("models.Menu", related_name="role_menus") + apis = fields.ManyToManyField("models.Api", related_name="role_apis") + + class Meta: + table = "role" + + +class Api(BaseModel, TimestampMixin): + path = fields.CharField(max_length=100, description="API路径") + method = fields.CharEnumField(MethodType, description="请求方法") + summary = fields.CharField(max_length=500, description="请求简介") + tags = fields.CharField(max_length=100, description="API标签") + + class Meta: + table = "api" + + +class Menu(BaseModel, TimestampMixin): + name = fields.CharField(max_length=20, description="菜单名称") + remark = fields.JSONField(null=True, description="保留字段", blank=True) + menu_type = fields.CharEnumField(MenuType, null=True, blank=True, description="菜单类型") + icon = fields.CharField(max_length=100, null=True, blank=True, description="菜单图标") + path = fields.CharField(max_length=100, description="菜单路径") + order = fields.IntField(default=0, description="排序") + parent_id = fields.IntField(default=0, max_length=10, description="父菜单ID") + is_hidden = fields.BooleanField(default=False, description="是否隐藏") + component = fields.CharField(max_length=100, description="组件") + keepalive = fields.BooleanField(default=True, description="存活") + redirect = fields.CharField(max_length=100, null=True, blank=True, description="重定向") + + class Meta: + table = "menu" + + +class Dept(BaseModel, TimestampMixin): + name = fields.CharField(max_length=20, unique=True, description="部门名称") + desc = fields.CharField(max_length=500, null=True, blank=True, description="菜单描述") + is_deleted = fields.BooleanField(default=False, description="软删除标记") + order = fields.IntField(default=0, description="排序") + parent_id = fields.IntField(default=0, max_length=10, description="父部门ID") + + class Meta: + table = "dept" diff --git a/app/models/base.py b/app/models/base.py new file mode 100644 index 0000000..8ed2045 --- /dev/null +++ b/app/models/base.py @@ -0,0 +1,26 @@ +from tortoise import fields, models + + +class BaseModel(models.Model): + id = fields.BigIntField(pk=True, index=True) + + async def to_dict(self, m2m=True): + d = {} + for field in self._meta.db_fields: + d[field] = getattr(self, field) + if m2m: + for field in self._meta.m2m_fields: + d[field] = await getattr(self, field).all().values() + return d + + class Meta: + abstract = True + + +class UUIDModel: + uuid = fields.UUIDField(unique=True, pk=False) + + +class TimestampMixin: + created_at = fields.DatetimeField(auto_now_add=True) + updated_at = fields.DatetimeField(auto_now=True) diff --git a/app/models/enums.py b/app/models/enums.py new file mode 100644 index 0000000..a74cdf4 --- /dev/null +++ b/app/models/enums.py @@ -0,0 +1,27 @@ +from enum import Enum + + +class EnumBase(Enum): + @classmethod + def get_member_values(cls): + return [item.value for item in cls._member_map_.values()] + + @classmethod + def get_member_names(cls): + return [name for name in cls._member_names_] + + +class IntEnum(int, EnumBase): + ... + + +class StrEnum(str, EnumBase): + ... + + +class MethodType(StrEnum): + GET = "GET" + POST = "POST" + PUT = "PUT" + DELETE = "DELETE" + PATCH = "PATCH" diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py new file mode 100644 index 0000000..9b5ed21 --- /dev/null +++ b/app/schemas/__init__.py @@ -0,0 +1 @@ +from .base import * diff --git a/app/schemas/apis.py b/app/schemas/apis.py new file mode 100644 index 0000000..2ee5586 --- /dev/null +++ b/app/schemas/apis.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel, Field + +from app.models.enums import MethodType + + +class BaseApi(BaseModel): + path: str = Field(..., description="API路径", example="/api/v1/user/list") + summary: str = Field(None, description="API简介", example="查看用户列表") + method: MethodType = Field(..., description="API方法", example="GET") + tags: str = Field(..., description="API标签", example="User") + + +class ApiCreate(BaseApi): + ... + + +class ApiUpdate(BaseApi): + id: int + + def update_dict(self): + return self.dict(exclude_unset=True, exclude={"id"}) diff --git a/app/schemas/base.py b/app/schemas/base.py new file mode 100644 index 0000000..25a8fb9 --- /dev/null +++ b/app/schemas/base.py @@ -0,0 +1,26 @@ +from typing import Generic, Optional, TypeVar + +from pydantic import BaseModel +from pydantic.generics import GenericModel + +DataT = TypeVar("DataT") + + +class BaseResponse(GenericModel, BaseModel, Generic[DataT]): + code: int + msg: str = "" + data: Optional[DataT] = None + + +class Success(BaseResponse): + code: int = 200 + + +class Fail(BaseResponse): + code: int = -1 + + +class SuccessExtra(Success): + total: int + page: int + page_size: int diff --git a/app/schemas/login.py b/app/schemas/login.py new file mode 100644 index 0000000..eaefafc --- /dev/null +++ b/app/schemas/login.py @@ -0,0 +1,21 @@ +from datetime import datetime +from typing import Optional + +from pydantic import BaseModel, EmailStr, Field + + +class CredentialsSchema(BaseModel): + username: str = Field(..., description="用户名称", example="admin") + password: str = Field(..., description="密码", example="123456") + + +class JWTOut(BaseModel): + access_token: str + username: str + + +class JWTPayload(BaseModel): + user_id: int + username: str + is_superuser: bool + exp: datetime diff --git a/app/schemas/menus.py b/app/schemas/menus.py new file mode 100644 index 0000000..a18c1a6 --- /dev/null +++ b/app/schemas/menus.py @@ -0,0 +1,70 @@ +from enum import Enum +from typing import List, Optional + +from pydantic import BaseModel, Field + +from .base import BaseResponse, SuccessExtra + + +class MenuType(str, Enum): + CATALOG = "catalog" + MENU = "menu" + + +class BaseMenu(BaseModel): + id: int + name: str + path: str + remark: Optional[dict] + menu_type: Optional[MenuType] + icon: Optional[str] + order: int + parent_id: int + is_hidden: bool + component: str + keepalive: bool + redirect: Optional[str] + children: Optional[list["BaseMenu"]] + + +class MenuCreate(BaseModel): + menu_type: MenuType = Field(default=MenuType.CATALOG.value) + name: str = Field(example="用户管理") + remark: Optional[dict] = Field(example={}) + icon: Optional[str] = "ph:user-list-bold" + path: str = Field(example="/system/user") + order: Optional[int] = Field(example=1) + parent_id: Optional[int] = Field(example=0, default=0) + is_hidden: Optional[bool] = False + component: str = Field(default="Layout", example="/system/user") + keepalive: Optional[bool] = True + redirect: Optional[str] = "" + + +class MenuUpdate(BaseModel): + id: int + menu_type: Optional[MenuType] = Field(example=MenuType.CATALOG.value) + name: Optional[str] = Field(example="用户管理") + remark: Optional[dict] = Field(example={}) + icon: Optional[str] = "ph:user-list-bold" + path: Optional[str] = Field(example="/system/user") + order: Optional[int] = Field(example=1) + parent_id: Optional[int] = Field(example=0) + is_hidden: Optional[bool] = False + component: str = Field(example="/system/user") + keepalive: Optional[bool] = False + redirect: Optional[str] = "" + + def update_dict(self): + return self.dict(exclude_unset=True, exclude={"id"}) + + +"""Response""" + + +class MenuOutList(SuccessExtra): + data: Optional[List[BaseMenu]] + + +class MenuOut(BaseResponse): + data: Optional[BaseMenu] diff --git a/app/schemas/roles.py b/app/schemas/roles.py new file mode 100644 index 0000000..9c6ed2a --- /dev/null +++ b/app/schemas/roles.py @@ -0,0 +1,48 @@ +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, Field + +from .base import BaseResponse, SuccessExtra + + +class BaseRole(BaseModel): + id: int + name: str + desc: Optional[str] + users: Optional[list] + menus: Optional[list] + apis: Optional[list] + created_at: Optional[datetime] + updated_at: Optional[datetime] + + +class RoleCreate(BaseModel): + name: str = Field(example="管理员") + desc: Optional[str] = Field(example="管理员角色") + + +class RoleUpdate(BaseModel): + id: int = Field(example=1) + name: Optional[str] = Field(example="管理员") + desc: Optional[str] = Field(example="管理员角色") + + def update_dict(self): + return self.dict(exclude_unset=True, exclude={"id"}) + + +class RoleUpdateMenusApis(BaseModel): + id: int + menu_ids: List[int] = [] + api_infos: List[dict] = [] + + +"""Response""" + + +class RoleOutList(SuccessExtra): + data: Optional[List[BaseRole]] + + +class RoleOut(BaseResponse): + data: Optional[BaseRole] diff --git a/app/schemas/users.py b/app/schemas/users.py new file mode 100644 index 0000000..3b91905 --- /dev/null +++ b/app/schemas/users.py @@ -0,0 +1,60 @@ +from datetime import datetime +from typing import List, Optional + +from pydantic import BaseModel, EmailStr, Field + +from app.schemas.base import BaseResponse, SuccessExtra + + +class BaseUser(BaseModel): + id: int + email: Optional[EmailStr] = None + username: Optional[str] = None + is_active: Optional[bool] = True + is_superuser: Optional[bool] = False + created_at: Optional[datetime] + updated_at: Optional[datetime] + last_login: Optional[datetime] + roles: Optional[list] = [] + + +class UserCreate(BaseModel): + email: EmailStr = Field(example="admin@qq.com") + username: str = Field(example="admin") + password: str = Field(example="123456") + is_active: Optional[bool] = True + is_superuser: Optional[bool] = False + roles: Optional[List[int]] = [] + + def create_dict(self): + return self.dict(exclude_unset=True, exclude={"roles"}) + + +class UserUpdate(BaseModel): + id: int + password: Optional[str] + email: Optional[EmailStr] + username: Optional[str] + is_active: Optional[bool] = True + is_superuser: Optional[bool] = False + roles: Optional[List[int]] = [] + + def update_dict(self): + return self.dict(exclude_unset=True, exclude={"roles", "id"}) + + +class UpdatePassword(BaseModel): + id: int = Field(description="用户ID") + old_password: str = Field(description="旧密码") + new_password: str = Field(description="新密码") + + +"""Response""" + + +class UserOutList(SuccessExtra): + data: Optional[List[BaseUser]] + + +class UserOut(BaseResponse): + data: Optional[BaseUser] diff --git a/app/settings/__init__.py b/app/settings/__init__.py new file mode 100644 index 0000000..ff19664 --- /dev/null +++ b/app/settings/__init__.py @@ -0,0 +1,3 @@ +from .config import settings as settings + +TORTOISE_ORM = settings.TORTOISE_ORM diff --git a/app/settings/config.py b/app/settings/config.py new file mode 100644 index 0000000..8552743 --- /dev/null +++ b/app/settings/config.py @@ -0,0 +1,56 @@ +import os +import typing + +from pydantic import BaseSettings + + +class Settings(BaseSettings): + VERSION: str = "0.1.0" + APP_TITLE: str = "Template Application" + PROJECT_NAME: str = "Template Application" + APP_DESCRIPTION: str = "Description" + + CORS_ORIGINS: typing.List = ["*"] + CORS_ALLOW_CREDENTIALS: bool = True + CORS_ALLOW_METHODS: typing.List = ["*"] + CORS_ALLOW_HEADERS: typing.List = ["*"] + + DEBUG = True + DB_URL: str = "sqlite://db.sqlite3" + DB_CONNECTIONS: dict = { + "default": { + "engine": "tortoise.backends.sqlite", + "db_url": DB_URL, + "credentials": { + "host": "", + "port": "", + "user": "", + "password": "", + "database": "", + }, + }, + } + + PROJECT_ROOT: str = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) + BASE_DIR: str = os.path.abspath(os.path.join(PROJECT_ROOT, os.pardir)) + LOGS_ROOT: str = os.path.join(BASE_DIR, "app/logs") + SECRET_KEY: str = "3488a63e1765035d386f05409663f55c83bfae3b3c61a932744b20ad14244dcf" # openssl rand -hex 32 + JWT_ALGORITHM: str = "HS256" + JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 # 7 day + TORTOISE_ORM = { + "connections": { + "sqlite": { + "engine": "tortoise.backends.sqlite", + "credentials": {"file_path": f"{BASE_DIR}/db.sqlite3"}, + } + }, + "apps": { + "models": { + "models": ["app.models"], + "default_connection": "sqlite", + }, + }, + } + + +settings = Settings() diff --git a/app/utils/jwt.py b/app/utils/jwt.py new file mode 100644 index 0000000..d8567d9 --- /dev/null +++ b/app/utils/jwt.py @@ -0,0 +1,10 @@ +import jwt + +from app.schemas.login import JWTPayload +from app.settings.config import settings + + +def create_access_token(*, data: JWTPayload): + payload = data.dict().copy() + encoded_jwt = jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM) + return encoded_jwt diff --git a/app/utils/password.py b/app/utils/password.py new file mode 100644 index 0000000..a76058b --- /dev/null +++ b/app/utils/password.py @@ -0,0 +1,16 @@ +from passlib import pwd +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + + +def generate_password() -> str: + return pwd.genword() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ca389f7 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,578 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiosqlite" +version = "0.17.0" +description = "asyncio bridge to the standard sqlite3 module" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, + {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, +] + +[package.dependencies] +typing_extensions = ">=3.7.2" + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "bcrypt" +version = "4.0.1" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.6" +files = [ + {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, + {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, + {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, + {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "black" +version = "23.7.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2023.5.7" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] + +[[package]] +name = "click" +version = "8.1.5" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"}, + {file = "click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dnspython" +version = "2.4.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "dnspython-2.4.0-py3-none-any.whl", hash = "sha256:46b4052a55b56beea3a3bdd7b30295c292bd6827dd442348bc116f2d35b17f0a"}, + {file = "dnspython-2.4.0.tar.gz", hash = "sha256:758e691dbb454d5ccf4e1b154a19e52847f79e21a42fef17b969144af29a4e6c"}, +] + +[package.dependencies] +httpcore = {version = ">=0.17.3", markers = "python_version >= \"3.8\""} +sniffio = ">=1.1,<2.0" + +[package.extras] +dnssec = ["cryptography (>=2.6,<42.0)"] +doh = ["h2 (>=4.1.0)", "httpx (>=0.24.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.23)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + +[[package]] +name = "email-validator" +version = "2.0.0.post2" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "email_validator-2.0.0.post2-py3-none-any.whl", hash = "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"}, + {file = "email_validator-2.0.0.post2.tar.gz", hash = "sha256:1ff6e86044200c56ae23595695c54e9614f4a9551e0e393614f764860b3d7900"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "fastapi" +version = "0.100.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"}, + {file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "0.17.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "iso8601" +version = "1.1.0" +description = "Simple module to parse ISO 8601 dates" +optional = false +python-versions = ">=3.6.2,<4.0" +files = [ + {file = "iso8601-1.1.0-py3-none-any.whl", hash = "sha256:8400e90141bf792bce2634df533dc57e3bee19ea120a87bebcd3da89a58ad73f"}, + {file = "iso8601-1.1.0.tar.gz", hash = "sha256:32811e7b81deee2063ea6d2e94f8819a86d1f3811e49d23623a41fa832bef03f"}, +] + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "loguru" +version = "0.7.0" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = ">=3.5" +files = [ + {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, + {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +optional = false +python-versions = "*" +files = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] +totp = ["cryptography"] + +[[package]] +name = "pathspec" +version = "0.11.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] + +[[package]] +name = "platformdirs" +version = "3.9.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pydantic" +version = "1.10.11" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, + {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, + {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, + {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, + {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, + {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, + {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, + {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, + {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, + {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyjwt" +version = "2.7.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.7.0-py3-none-any.whl", hash = "sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1"}, + {file = "PyJWT-2.7.0.tar.gz", hash = "sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pypika-tortoise" +version = "0.1.6" +description = "Forked from pypika and streamline just for tortoise-orm" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pypika-tortoise-0.1.6.tar.gz", hash = "sha256:d802868f479a708e3263724c7b5719a26ad79399b2a70cea065f4a4cadbebf36"}, + {file = "pypika_tortoise-0.1.6-py3-none-any.whl", hash = "sha256:2d68bbb7e377673743cff42aa1059f3a80228d411fbcae591e4465e173109fd8"}, +] + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "ruff" +version = "0.0.281" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.281-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:418fbddfd3dba4d7b11e4e016eacc40d321ff0b7d3637c7ba9ad3ee0474c9a35"}, + {file = "ruff-0.0.281-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:c086bf3968d5cb2b4f31a586fc73bc42cb688c32f4c992ff161d4ce19f551cf2"}, + {file = "ruff-0.0.281-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0162b149a94f6007768820bcdf4ccb7e90a21655aac829ace49f4682d0565fdb"}, + {file = "ruff-0.0.281-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3495175e6d85a01d3da409a079461a5a3c15b70237cc82550ad8c1f091002c8"}, + {file = "ruff-0.0.281-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae0b836c03a7010527bb56384a4e3718e0958e32bea64459879aacdcb65c4945"}, + {file = "ruff-0.0.281-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6d34cae6ef6c6b6fd6d4f09271fbf635db49e6b788da1b2e1dea11a29f1c2a11"}, + {file = "ruff-0.0.281-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd3c94260a148e955fb46f41d4bcecd857c75794e9f06ebfa7f9be65cfed9621"}, + {file = "ruff-0.0.281-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ccb875a4000bcba6cc61cb9d3cd5969d6b0921b5234f0ef99ad75f74e8935ef"}, + {file = "ruff-0.0.281-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f5b8ccaabad61e2d50494df820b7bafd94eac13f10d2d8b831994c1618801a9"}, + {file = "ruff-0.0.281-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cbf279fd9c2ca674896656df2d82831010afd336a6703a060fe08d6f2358e47b"}, + {file = "ruff-0.0.281-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:24d0defeb2c6a1b16a4230840d1138e08bc4ef2318496fa6ff7ddbf3a443626f"}, + {file = "ruff-0.0.281-py3-none-musllinux_1_2_i686.whl", hash = "sha256:54bab7128167057ee5987bbd9f925fbf105071068de9d8474ca7c38f684b8463"}, + {file = "ruff-0.0.281-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:29a22b7a6433ce0b4e601897e8a5dd58a75c75c01afee9b8922ebbdd1fe51e51"}, + {file = "ruff-0.0.281-py3-none-win32.whl", hash = "sha256:7b781f6a7ed35196e6565ed32f57d07b852b0dcd7158c6c7669c8b5d0f8cf97a"}, + {file = "ruff-0.0.281-py3-none-win_amd64.whl", hash = "sha256:70f921438bf09f04c0547cf64c137c87ef33cbec2b64be12b8caa87df261a016"}, + {file = "ruff-0.0.281-py3-none-win_arm64.whl", hash = "sha256:42a92a62fc841f7444821444553fd6e1e700bb55348f24e8ec39afdd4e3d0312"}, + {file = "ruff-0.0.281.tar.gz", hash = "sha256:bab2cdfa78754315cccc2b4d46ad6181aabb29e89747a3b135a4b85e11baa025"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "tortoise-orm" +version = "0.19.3" +description = "Easy async ORM for python, built with relations in mind" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "tortoise_orm-0.19.3-py3-none-any.whl", hash = "sha256:9e368820c70a0866ef9c521d43aa5503485bd7a20a561edc0933b7b0f7036fbc"}, + {file = "tortoise_orm-0.19.3.tar.gz", hash = "sha256:ca574bca5191f55608f9013314b1f5d1c6ffd4165a1fcc2f60f6c902f529b3b6"}, +] + +[package.dependencies] +aiosqlite = ">=0.16.0,<0.18.0" +iso8601 = ">=1.0.2,<2.0.0" +pypika-tortoise = ">=0.1.6,<0.2.0" +pytz = "*" + +[package.extras] +accel = ["ciso8601", "orjson", "uvloop"] +aiomysql = ["aiomysql"] +asyncmy = ["asyncmy (>=0.2.5,<0.3.0)"] +asyncodbc = ["asyncodbc (>=0.1.1,<0.2.0)"] +asyncpg = ["asyncpg"] +psycopg = ["psycopg[binary,pool] (==3.0.12)"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "uvicorn" +version = "0.23.1" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.23.1-py3-none-any.whl", hash = "sha256:1d55d46b83ee4ce82b4e82f621f2050adb3eb7b5481c13f9af1744951cae2f1f"}, + {file = "uvicorn-0.23.1.tar.gz", hash = "sha256:da9b0c8443b2d7ee9db00a345f1eee6db7317432c9d4400f5049cc8d358383be"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +optional = false +python-versions = ">=3.5" +files = [ + {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, + {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, +] + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "9da7bb387eefe738f40f5b1b879e621e924379162f7034f1b0af8e03c14f60f1" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6fa481d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[tool.poetry] +name = "vue-fastapi-admin" +version = "0.1.0" +description = "Vue Fastapi admin" +authors = ["王津校 "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +fastapi = "^0.100.0" +uvicorn = "^0.23.1" +tortoise-orm = "^0.19.3" +pydantic = "^1.10.5" +email-validator = "^2.0.0.post2" +passlib = "^1.7.4" +pyjwt = "^2.7.0" +bcrypt = "^4.0.1" +black = "^23.7.0" +isort = "^5.12.0" +ruff = "^0.0.281" +loguru = "^0.7.0" + +[tool.black] +line-length = 120 +target-version = ["py310", "py311"] + +[tool.ruff] +line-length = 120 +extend-select = [ + "I", # isort +# "B", # flake8-bugbear +# "C4", # flake8-comprehensions +# "PGH", # pygrep-hooks + "RUF", # ruff +# "W", # pycodestyle +# "YTT", # flake8-2020 +] +ignore = [ + "F403", + "F405", +] \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..f684449 --- /dev/null +++ b/run.py @@ -0,0 +1,4 @@ +import uvicorn + +if __name__ == "__main__": + uvicorn.run("app:app", host="0.0.0.0", port=9999, reload=True) diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..a35e37a --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/zsh + +uvicorn app:app --reload --host 0.0.0.0 --port 9999 \ No newline at end of file diff --git a/web/.env b/web/.env new file mode 100644 index 0000000..c6b4793 --- /dev/null +++ b/web/.env @@ -0,0 +1,3 @@ +VITE_TITLE = 'Vue FastAPI Admin' + +VITE_PORT = 3100 diff --git a/web/.env.development b/web/.env.development new file mode 100644 index 0000000..6ac7c30 --- /dev/null +++ b/web/.env.development @@ -0,0 +1,8 @@ +# 资源公共路径,需要以 /开头和结尾 +VITE_PUBLIC_PATH = '/' + +# 是否启用代理 +VITE_USE_PROXY = true + +# 代理类型 'dev' | 'test' | 'prod' +VITE_PROXY_TYPE = 'dev' \ No newline at end of file diff --git a/web/.env.production b/web/.env.production new file mode 100644 index 0000000..88cf20f --- /dev/null +++ b/web/.env.production @@ -0,0 +1,11 @@ +# 资源公共路径,需要以 /开头和结尾 +VITE_PUBLIC_PATH = '/' + +# base api +VITE_BASE_API = '/api' + +# 是否启用压缩 +VITE_USE_COMPRESS = true + +# 压缩类型 +VITE_COMPRESS_TYPE = gzip \ No newline at end of file diff --git a/web/.eslint-global-variables.json b/web/.eslint-global-variables.json new file mode 100644 index 0000000..4336802 --- /dev/null +++ b/web/.eslint-global-variables.json @@ -0,0 +1,62 @@ +{ + "globals": { + "$loadingBar": true, + "$message": true, + "defineOptions": true, + "$dialog": true, + "$notification": true, + "EffectScope": true, + "computed": true, + "createApp": true, + "customRef": true, + "defineAsyncComponent": true, + "defineComponent": true, + "effectScope": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "inject": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onUnmounted": true, + "onUpdated": true, + "provide": true, + "reactive": true, + "readonly": true, + "ref": true, + "resolveComponent": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "toRaw": true, + "toRef": true, + "toRefs": true, + "triggerRef": true, + "unref": true, + "useAttrs": true, + "useCssModule": true, + "useCssVars": true, + "useRoute": true, + "useRouter": true, + "useSlots": true, + "watch": true, + "watchEffect": true, + "watchPostEffect": true, + "watchSyncEffect": true + } +} diff --git a/web/.eslintignore b/web/.eslintignore new file mode 100644 index 0000000..3d0576e --- /dev/null +++ b/web/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +public +package.json \ No newline at end of file diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..7cc64f4 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,25 @@ +# 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 + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/web/.prettierignore b/web/.prettierignore new file mode 100644 index 0000000..4b6d469 --- /dev/null +++ b/web/.prettierignore @@ -0,0 +1,3 @@ +/node_modules/** +/dist/* +/public/* \ No newline at end of file diff --git a/web/.prettierrc.json b/web/.prettierrc.json new file mode 100644 index 0000000..f59a508 --- /dev/null +++ b/web/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "printWidth": 100, + "singleQuote": true, + "semi": false, + "endOfLine": "lf" +} diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..312b2af --- /dev/null +++ b/web/README.md @@ -0,0 +1,16 @@ +## 快速开始 +进入前端目录 +```sh +cd web +``` + +安装依赖(建议使用pnpm: https://pnpm.io/zh/installation) +```sh +npm i -g pnpm # 已安装可忽略 +pnpm i # 或者 npm i +``` + +启动 +```sh +pnpm dev +``` \ No newline at end of file diff --git a/web/build/config/define.js b/web/build/config/define.js new file mode 100644 index 0000000..4b10543 --- /dev/null +++ b/web/build/config/define.js @@ -0,0 +1,13 @@ +import dayjs from 'dayjs' + +/** + * * 此处定义的是全局常量,启动或打包后将添加到window中 + * https://vitejs.cn/config/#define + */ + +// 项目构建时间 +const _BUILD_TIME_ = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss')) + +export const viteDefine = { + _BUILD_TIME_, +} diff --git a/web/build/config/index.js b/web/build/config/index.js new file mode 100644 index 0000000..967ddda --- /dev/null +++ b/web/build/config/index.js @@ -0,0 +1,2 @@ +export * from './define' +export * from './proxy' diff --git a/web/build/config/proxy.js b/web/build/config/proxy.js new file mode 100644 index 0000000..f3d0c92 --- /dev/null +++ b/web/build/config/proxy.js @@ -0,0 +1,15 @@ +import { getProxyConfig } from '../../settings' + +export function createViteProxy(isUseProxy = true, proxyType) { + if (!isUseProxy) return undefined + + const proxyConfig = getProxyConfig(proxyType) + const proxy = { + [proxyConfig.prefix]: { + target: proxyConfig.target, + changeOrigin: true, + rewrite: (path) => path.replace(new RegExp(`^${proxyConfig.prefix}`), ''), + }, + } + return proxy +} diff --git a/web/build/plugin/html.js b/web/build/plugin/html.js new file mode 100644 index 0000000..7ed7f76 --- /dev/null +++ b/web/build/plugin/html.js @@ -0,0 +1,15 @@ +import { createHtmlPlugin } from 'vite-plugin-html' + +export function configHtmlPlugin(viteEnv, isBuild) { + const { VITE_TITLE } = viteEnv + + const htmlPlugin = createHtmlPlugin({ + minify: isBuild, + inject: { + data: { + title: VITE_TITLE, + }, + }, + }) + return htmlPlugin +} diff --git a/web/build/plugin/index.js b/web/build/plugin/index.js new file mode 100644 index 0000000..80f81fd --- /dev/null +++ b/web/build/plugin/index.js @@ -0,0 +1,35 @@ +import vue from '@vitejs/plugin-vue' + +/** + * * unocss插件,原子css + * https://github.com/antfu/unocss + */ +import Unocss from 'unocss/vite' + +// rollup打包分析插件 +import visualizer from 'rollup-plugin-visualizer' +// 压缩 +import viteCompression from 'vite-plugin-compression' + +import { configHtmlPlugin } from './html' +import unplugin from './unplugin' + +export function createVitePlugins(viteEnv, isBuild) { + const plugins = [vue(), ...unplugin, configHtmlPlugin(viteEnv, isBuild), Unocss()] + + if (viteEnv.VITE_USE_COMPRESS) { + plugins.push(viteCompression({ algorithm: viteEnv.VITE_COMPRESS_TYPE || 'gzip' })) + } + + if (isBuild) { + plugins.push( + visualizer({ + open: true, + gzipSize: true, + brotliSize: true, + }) + ) + } + + return plugins +} diff --git a/web/build/plugin/unplugin.js b/web/build/plugin/unplugin.js new file mode 100644 index 0000000..3277682 --- /dev/null +++ b/web/build/plugin/unplugin.js @@ -0,0 +1,46 @@ +import { resolve } from 'path' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' +import { FileSystemIconLoader } from 'unplugin-icons/loaders' +import IconsResolver from 'unplugin-icons/resolver' + +/** + * * unplugin-icons插件,自动引入iconify图标 + * usage: https://github.com/antfu/unplugin-icons + * 图标库: https://icones.js.org/ + */ +import Icons from 'unplugin-icons/vite' +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' + +import { getSrcPath } from '../utils' + +const customIconPath = resolve(getSrcPath(), 'assets/svg') + +export default [ + AutoImport({ + imports: ['vue', 'vue-router'], + dts: false, + }), + Icons({ + compiler: 'vue3', + customCollections: { + custom: FileSystemIconLoader(customIconPath), + }, + scale: 1, + defaultClass: 'inline-block', + }), + Components({ + resolvers: [ + NaiveUiResolver(), + IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' }), + ], + dts: false, + }), + createSvgIconsPlugin({ + iconDirs: [customIconPath], + symbolId: 'icon-custom-[dir]-[name]', + inject: 'body-last', + customDomId: '__CUSTOM_SVG_ICON__', + }), +] diff --git a/web/build/script/build-cname.js b/web/build/script/build-cname.js new file mode 100644 index 0000000..7658c98 --- /dev/null +++ b/web/build/script/build-cname.js @@ -0,0 +1,15 @@ +import { resolve } from 'path' +import chalk from 'chalk' +import { writeFileSync } from 'fs-extra' +import { OUTPUT_DIR } from '../constant' +import { getEnvConfig, getRootPath } from '../utils' + +export function runBuildCNAME() { + const { VITE_CNAME } = getEnvConfig() + if (!VITE_CNAME) return + try { + writeFileSync(resolve(getRootPath(), `${OUTPUT_DIR}/CNAME`), VITE_CNAME) + } catch (error) { + console.log(chalk.red('CNAME file failed to package:\n' + error)) + } +} diff --git a/web/build/script/index.js b/web/build/script/index.js new file mode 100644 index 0000000..e0efeb0 --- /dev/null +++ b/web/build/script/index.js @@ -0,0 +1,14 @@ +import chalk from 'chalk' +import { runBuildCNAME } from './build-cname' + +export const runBuild = async () => { + try { + runBuildCNAME() + console.log(`✨ ${chalk.cyan('build successfully!')}`) + } catch (error) { + console.log(chalk.red('vite build error:\n' + error)) + process.exit(1) + } +} + +runBuild() diff --git a/web/build/utils.js b/web/build/utils.js new file mode 100644 index 0000000..fe43a2b --- /dev/null +++ b/web/build/utils.js @@ -0,0 +1,70 @@ +import fs from 'fs' +import path from 'path' +import dotenv from 'dotenv' + +/** + * * 项目根路径 + * @descrition 结尾不带/ + */ +export function getRootPath() { + return path.resolve(process.cwd()) +} + +/** + * * 项目src路径 + * @param srcName src目录名称(默认: "src") + * @descrition 结尾不带斜杠 + */ +export function getSrcPath(srcName = 'src') { + return path.resolve(getRootPath(), srcName) +} + +export function convertEnv(envOptions) { + const result = {} + if (!envOptions) return result + + for (const envKey in envOptions) { + let envVal = envOptions[envKey] + if (['true', 'false'].includes(envVal)) envVal = envVal === 'true' + + if (['VITE_PORT'].includes(envKey)) envVal = +envVal + + result[envKey] = envVal + } + return result +} + +/** + * 获取当前环境下生效的配置文件名 + */ +function getConfFiles() { + const script = process.env.npm_lifecycle_script + const reg = new RegExp('--mode ([a-z_\\d]+)') + const result = reg.exec(script) + if (result) { + const mode = result[1] + return ['.env', '.env.local', `.env.${mode}`] + } + return ['.env', '.env.local', '.env.production'] +} + +export function getEnvConfig(match = 'VITE_', confFiles = getConfFiles()) { + let envConfig = {} + confFiles.forEach((item) => { + try { + if (fs.existsSync(path.resolve(process.cwd(), item))) { + const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item))) + envConfig = { ...envConfig, ...env } + } + } catch (e) { + console.error(`Error in parsing ${item}`, e) + } + }) + const reg = new RegExp(`^(${match})`) + Object.keys(envConfig).forEach((key) => { + if (!reg.test(key)) { + Reflect.deleteProperty(envConfig, key) + } + }) + return envConfig +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..2b11570 --- /dev/null +++ b/web/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + <%= title %> + + + +
+ +
+ +
+
+
+
+
+
+
+
+
<%= title %>
+
+ +
+ + + diff --git a/web/jsconfig.json b/web/jsconfig.json new file mode 100644 index 0000000..e014d4a --- /dev/null +++ b/web/jsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ESNext", + "baseUrl": "./", + "moduleResolution": "node", + "paths": { + "~/*": ["./*"], + "@/*": ["src/*"] + }, + "jsx": "preserve", + "allowJs": true + }, + "exclude": ["node_modules", "dist"] +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..b242cf3 --- /dev/null +++ b/web/package.json @@ -0,0 +1,55 @@ +{ + "name": "vue-fastapi-admin-web", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint --ext .js,.vue .", + "lint:fix": "eslint --fix --ext .js,.vue .", + "lint:staged": "lint-staged" + }, + "dependencies": { + "@iconify/json": "^2.2.101", + "@iconify/vue": "^4.1.1", + "@unocss/eslint-config": "^0.55.0", + "@vueuse/core": "^10.3.0", + "@zclzone/eslint-config": "^0.0.4", + "axios": "^1.4.0", + "dayjs": "^1.11.9", + "dotenv": "^16.3.1", + "eslint": "^8.46.0", + "lodash-es": "^4.17.21", + "naive-ui": "^2.34.4", + "pinia": "^2.1.6", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "^1.65.1", + "typescript": "^5.1.6", + "unocss": "^0.55.0", + "unplugin-auto-import": "^0.16.6", + "unplugin-icons": "^0.16.5", + "unplugin-vue-components": "^0.25.1", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-html": "^3.2.0", + "vite-plugin-svg-icons": "^2.0.1", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.2.3", + "vite": "^4.4.6" + }, + "lint-staged": { + "*.{js,vue}": [ + "eslint --ext .js,.vue ." + ] + }, + "eslintConfig": { + "extends": [ + "@zclzone", + "@unocss", + ".eslint-global-variables.json" + ] + } +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000..9a02d92 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,4077 @@ +lockfileVersion: 5.4 + +specifiers: + '@iconify/json': ^2.2.101 + '@iconify/vue': ^4.1.1 + '@unocss/eslint-config': ^0.55.0 + '@vitejs/plugin-vue': ^4.2.3 + '@vueuse/core': ^10.3.0 + '@zclzone/eslint-config': ^0.0.4 + axios: ^1.4.0 + dayjs: ^1.11.9 + dotenv: ^16.3.1 + eslint: ^8.46.0 + lodash-es: ^4.17.21 + naive-ui: ^2.34.4 + pinia: ^2.1.6 + rollup-plugin-visualizer: ^5.9.2 + sass: ^1.65.1 + typescript: ^5.1.6 + unocss: ^0.55.0 + unplugin-auto-import: ^0.16.6 + unplugin-icons: ^0.16.5 + unplugin-vue-components: ^0.25.1 + vite: ^4.4.6 + vite-plugin-compression: ^0.5.1 + vite-plugin-html: ^3.2.0 + vite-plugin-svg-icons: ^2.0.1 + vue: ^3.3.4 + vue-router: ^4.2.4 + +dependencies: + '@iconify/json': 2.2.101 + '@iconify/vue': 4.1.1_vue@3.3.4 + '@unocss/eslint-config': 0.55.0_7haavtekmro7ptbnqmctjaodju + '@vueuse/core': 10.3.0_vue@3.3.4 + '@zclzone/eslint-config': 0.0.4 + axios: 1.4.0 + dayjs: 1.11.9 + dotenv: 16.3.1 + eslint: 8.46.0 + lodash-es: 4.17.21 + naive-ui: 2.34.4_vue@3.3.4 + pinia: 2.1.6_typescript@5.1.6+vue@3.3.4 + rollup-plugin-visualizer: 5.9.2 + sass: 1.65.1 + typescript: 5.1.6 + unocss: 0.55.0_vite@4.4.9 + unplugin-auto-import: 0.16.6_@vueuse+core@10.3.0 + unplugin-icons: 0.16.5 + unplugin-vue-components: 0.25.1_vue@3.3.4 + vite-plugin-compression: 0.5.1_vite@4.4.9 + vite-plugin-html: 3.2.0_vite@4.4.9 + vite-plugin-svg-icons: 2.0.1_vite@4.4.9 + vue: 3.3.4 + vue-router: 4.2.4_vue@3.3.4 + +devDependencies: + '@vitejs/plugin-vue': 4.2.3_vite@4.4.9+vue@3.3.4 + vite: 4.4.9_sass@1.65.1 + +packages: + + /@aashutoshrathi/word-wrap/1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: false + + /@ampproject/remapping/2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.19 + dev: false + + /@antfu/install-pkg/0.1.1: + resolution: {integrity: sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==} + dependencies: + execa: 5.1.1 + find-up: 5.0.0 + dev: false + + /@antfu/utils/0.7.5: + resolution: {integrity: sha512-dlR6LdS+0SzOAPx/TPRhnoi7hE251OVeT2Snw0RguNbBSbjUHdWr0l3vcUUDg26rEysT89kCbtw1lVorBXLLCg==} + dev: false + + /@babel/helper-string-parser/7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-identifier/7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + engines: {node: '>=6.9.0'} + + /@babel/parser/7.22.10: + resolution: {integrity: sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.22.10 + + /@babel/runtime/7.22.10: + resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: false + + /@babel/types/7.22.10: + resolution: {integrity: sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + to-fast-properties: 2.0.0 + + /@css-render/plugin-bem/0.15.12_css-render@0.15.12: + resolution: {integrity: sha512-Lq2jSOZn+wYQtsyaFj6QRz2EzAnd3iW5fZeHO1WSXQdVYwvwGX0ZiH3X2JQgtgYLT1yeGtrwrqJdNdMEUD2xTw==} + peerDependencies: + css-render: ~0.15.12 + dependencies: + css-render: 0.15.12 + dev: false + + /@css-render/vue3-ssr/0.15.12_vue@3.3.4: + resolution: {integrity: sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==} + peerDependencies: + vue: ^3.0.11 + dependencies: + vue: 3.3.4 + dev: false + + /@emotion/hash/0.8.0: + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + dev: false + + /@esbuild/android-arm/0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-arm64/0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-x64/0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/darwin-arm64/0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/darwin-x64/0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/freebsd-arm64/0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/freebsd-x64/0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/linux-arm/0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-arm64/0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ia32/0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-loong64/0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-mips64el/0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ppc64/0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-riscv64/0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-s390x/0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-x64/0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/netbsd-x64/0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /@esbuild/openbsd-x64/0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + + /@esbuild/sunos-x64/0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + + /@esbuild/win32-arm64/0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-ia32/0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-x64/0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@eslint-community/eslint-utils/4.4.0_eslint@8.46.0: + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.46.0 + eslint-visitor-keys: 3.4.2 + dev: false + + /@eslint-community/regexpp/4.6.2: + resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: false + + /@eslint/eslintrc/2.1.1: + resolution: {integrity: sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@eslint/js/8.46.0: + resolution: {integrity: sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: false + + /@humanwhocodes/config-array/0.11.10: + resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: false + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: false + + /@iconify/json/2.2.101: + resolution: {integrity: sha512-4otJXRpPYZPVY1IhDZSun8FI5Tk5BBlFYlq5Kl44e/DAG3MGS08fzGwqFUEcG5cbAWMhrQUIGHOhy3kkXxmcJw==} + dependencies: + '@iconify/types': 2.0.0 + pathe: 1.1.1 + dev: false + + /@iconify/types/2.0.0: + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + dev: false + + /@iconify/utils/2.1.7: + resolution: {integrity: sha512-P8S3z/L1LcV4Qem9AoCfVAaTFGySEMzFEY4CHZLkfRj0Fv9LiR+AwjDgrDrzyI93U2L2mg9JHsbTJ52mF8suNw==} + dependencies: + '@antfu/install-pkg': 0.1.1 + '@antfu/utils': 0.7.5 + '@iconify/types': 2.0.0 + debug: 4.3.4 + kolorist: 1.8.0 + local-pkg: 0.4.3 + transitivePeerDependencies: + - supports-color + dev: false + + /@iconify/vue/4.1.1_vue@3.3.4: + resolution: {integrity: sha512-RL85Bm/DAe8y6rT6pux7D2FJSiUEM/TPfyK7GrbAOfTSwrhvwJW+S5yijdGcmtXouA8MtuH9C7l4hiSE4mLMjg==} + peerDependencies: + vue: '>=3' + dependencies: + '@iconify/types': 2.0.0 + vue: 3.3.4 + dev: false + + /@jridgewell/gen-mapping/0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 + dev: false + + /@jridgewell/resolve-uri/3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/set-array/1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/source-map/0.3.5: + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.19 + dev: false + + /@jridgewell/sourcemap-codec/1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + /@jridgewell/trace-mapping/0.3.19: + resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: false + + /@juggle/resize-observer/3.4.0: + resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + dev: false + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: false + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: false + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: false + + /@pkgr/utils/2.4.2: + resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + fast-glob: 3.3.1 + is-glob: 4.0.3 + open: 9.1.0 + picocolors: 1.0.0 + tslib: 2.6.1 + dev: false + + /@polka/url/1.0.0-next.21: + resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} + dev: false + + /@rollup/pluginutils/4.2.1: + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: false + + /@rollup/pluginutils/5.0.2: + resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.1 + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: false + + /@trysound/sax/0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + dev: false + + /@types/estree/1.0.1: + resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + dev: false + + /@types/json-schema/7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + dev: false + + /@types/katex/0.14.0: + resolution: {integrity: sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==} + dev: false + + /@types/lodash-es/4.17.8: + resolution: {integrity: sha512-euY3XQcZmIzSy7YH5+Unb3b2X12Wtk54YWINBvvGQ5SmMvwb11JQskGsfkH/5HXK77Kr8GF0wkVDIxzAisWtog==} + dependencies: + '@types/lodash': 4.14.197 + dev: false + + /@types/lodash/4.14.197: + resolution: {integrity: sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==} + dev: false + + /@types/node/20.4.9: + resolution: {integrity: sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ==} + dev: false + + /@types/semver/7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + dev: false + + /@types/svgo/2.6.4: + resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} + dependencies: + '@types/node': 20.4.9 + dev: false + + /@types/web-bluetooth/0.0.17: + resolution: {integrity: sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==} + dev: false + + /@typescript-eslint/scope-manager/6.3.0: + resolution: {integrity: sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.3.0 + '@typescript-eslint/visitor-keys': 6.3.0 + dev: false + + /@typescript-eslint/types/6.3.0: + resolution: {integrity: sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: false + + /@typescript-eslint/typescript-estree/6.3.0_typescript@5.1.6: + resolution: {integrity: sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.3.0 + '@typescript-eslint/visitor-keys': 6.3.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.1_typescript@5.1.6 + typescript: 5.1.6 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/utils/6.3.0_7haavtekmro7ptbnqmctjaodju: + resolution: {integrity: sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0_eslint@8.46.0 + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 6.3.0 + '@typescript-eslint/types': 6.3.0 + '@typescript-eslint/typescript-estree': 6.3.0_typescript@5.1.6 + eslint: 8.46.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /@typescript-eslint/visitor-keys/6.3.0: + resolution: {integrity: sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.3.0 + eslint-visitor-keys: 3.4.2 + dev: false + + /@unocss/astro/0.55.0_vite@4.4.9: + resolution: {integrity: sha512-Qqk8zONPBBigEcUOGhEwBPIQmWnQGpjpQrSdpjs86BphKbQcqWHES1fQA83Fk2tpZ08zo0zAPDJ8VhfR+c+yqg==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + '@unocss/core': 0.55.0 + '@unocss/reset': 0.55.0 + '@unocss/vite': 0.55.0_vite@4.4.9 + vite: 4.4.9_sass@1.65.1 + transitivePeerDependencies: + - rollup + dev: false + + /@unocss/cli/0.55.0: + resolution: {integrity: sha512-K8PR4UydtTfT8rMynDcNQKk1WWI97312kZYjBLHUlrJkNbSgcmpU3wfREIqvCSgPg61ttZAgE5uI6omf8FudtA==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@ampproject/remapping': 2.2.1 + '@rollup/pluginutils': 5.0.2 + '@unocss/config': 0.55.0 + '@unocss/core': 0.55.0 + '@unocss/preset-uno': 0.55.0 + cac: 6.7.14 + chokidar: 3.5.3 + colorette: 2.0.20 + consola: 3.2.3 + fast-glob: 3.3.1 + magic-string: 0.30.2 + pathe: 1.1.1 + perfect-debounce: 1.0.0 + transitivePeerDependencies: + - rollup + dev: false + + /@unocss/config/0.55.0: + resolution: {integrity: sha512-N1o49aqqMP8UTmFZKsqN+CZFxoiUbatTYdPixCGErI5H6jA0VByVU7RI3Dr+Lk3PTOxbmZUunaDaWZP3iT4X5w==} + engines: {node: '>=14'} + dependencies: + '@unocss/core': 0.55.0 + unconfig: 0.3.10 + dev: false + + /@unocss/core/0.55.0: + resolution: {integrity: sha512-TcTugpuhsv6OwMsP3iFIG8FVc9N5JzkojIGNAKF8I2WBftZ//3QcpEHiHc1mH3MlPYfJgUvCcT6/Gad55qmHzg==} + dev: false + + /@unocss/eslint-config/0.55.0_7haavtekmro7ptbnqmctjaodju: + resolution: {integrity: sha512-18kDqTFQuATIOh+rja920q/S4I0aLLVG1ccZ1i8cJmpNF+XGuevu7UtPjxlcNq1F7pzmTlq/79jYrjBDZBGL/g==} + engines: {node: '>=14'} + dependencies: + '@unocss/eslint-plugin': 0.55.0_7haavtekmro7ptbnqmctjaodju + transitivePeerDependencies: + - eslint + - supports-color + - typescript + dev: false + + /@unocss/eslint-plugin/0.55.0_7haavtekmro7ptbnqmctjaodju: + resolution: {integrity: sha512-w6VjKtjTtiuG7jIZXUZzSCHgarV2bp5dMH3ALOhhvNZYfqLbcrJPSPk0RisVZ+uF87QCK4WSlKdXmO4UBd0ENg==} + engines: {node: '>=14'} + dependencies: + '@typescript-eslint/utils': 6.3.0_7haavtekmro7ptbnqmctjaodju + '@unocss/config': 0.55.0 + '@unocss/core': 0.55.0 + magic-string: 0.30.2 + synckit: 0.8.5 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + dev: false + + /@unocss/extractor-arbitrary-variants/0.55.0: + resolution: {integrity: sha512-FCel+gJ3N8C/361yQ3gYTmbCjX3DXQ+LdxBiAawapbtTA4eXw55/f7cpiiWcHoouCRrWIEMOQN5DskAJvmMaTw==} + dependencies: + '@unocss/core': 0.55.0 + dev: false + + /@unocss/inspector/0.55.0: + resolution: {integrity: sha512-wIkypLsBUA76A9cpyf/VtbVU7TLJO9HETEVMZytxEyVpq4KVNJBwNLUGWQ07IOc0oRTX+HJKiQK9/bLnIYMCHA==} + dependencies: + gzip-size: 6.0.0 + sirv: 2.0.3 + dev: false + + /@unocss/postcss/0.55.0: + resolution: {integrity: sha512-qytqO8riNLpy1m6qVfISVHw3dwNRHgpxcUaufSN7P8lgsbOimwh2nRE35f/HoKS1VV+5JVsVaHmUFQVxwiW6cw==} + engines: {node: '>=14'} + dependencies: + '@unocss/config': 0.55.0 + '@unocss/core': 0.55.0 + css-tree: 2.3.1 + fast-glob: 3.3.1 + magic-string: 0.30.2 + postcss: 8.4.27 + dev: false + + /@unocss/preset-attributify/0.55.0: + resolution: {integrity: sha512-AbqoamJLsFqJih1MMyxEslLScWSpOdlTbF9R+gSnrWwkGZDuZszcyTDbXrhCPWPUkihR7NY9XQSKxUkTb6MJJg==} + dependencies: + '@unocss/core': 0.55.0 + dev: false + + /@unocss/preset-icons/0.55.0: + resolution: {integrity: sha512-BeseXUz2WFRztLtfblGhpFBJkgKi8k7tKPyEx/QX2I/xhQNsXqfWqeiCEVLxrEI3HxXOZPV1G4idCCbBiZQ3ww==} + dependencies: + '@iconify/utils': 2.1.7 + '@unocss/core': 0.55.0 + ofetch: 1.1.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@unocss/preset-mini/0.55.0: + resolution: {integrity: sha512-zAMLmpCBXE36CDCrMtvxNp7lN9mk5QpArPpLekR3lPZ7NTAYSxHkieCJ0TryeOYWlt1sBdrOFE8X0cQCyG96Zg==} + dependencies: + '@unocss/core': 0.55.0 + '@unocss/extractor-arbitrary-variants': 0.55.0 + dev: false + + /@unocss/preset-tagify/0.55.0: + resolution: {integrity: sha512-crvJAZpG2ge9Lq51vpWANiB3BKv8Vs8sjplwRfUVRCYMiN7ZNzq9bNzUwTXhJXmRJ4LVjTSFciKPQR7fCjUScA==} + dependencies: + '@unocss/core': 0.55.0 + dev: false + + /@unocss/preset-typography/0.55.0: + resolution: {integrity: sha512-M4fJUEzkBqjxEUIDbwoWb00hjPbpwZKDOcB81S0F+xE3SVu1pQj8KgymhxaJvz+FxGZRajnJJ9esaGPIrzG2gQ==} + dependencies: + '@unocss/core': 0.55.0 + '@unocss/preset-mini': 0.55.0 + dev: false + + /@unocss/preset-uno/0.55.0: + resolution: {integrity: sha512-iYGdE/MQLAvpQkyQ8f3aolC9NK9NtrG87LfQmiKu/RpzjghDlTY8VTuWIDcdIk80zTtOxRtitLqGEsoDl8WnuA==} + dependencies: + '@unocss/core': 0.55.0 + '@unocss/preset-mini': 0.55.0 + '@unocss/preset-wind': 0.55.0 + dev: false + + /@unocss/preset-web-fonts/0.55.0: + resolution: {integrity: sha512-nFA5q0WinD/z7Iqv3uJQ8sTK7mQf18qbcFKmgWZ+QZXdI/wACOfExd6futsXj5EdACJwsEixYJe4DURTsD5XtA==} + dependencies: + '@unocss/core': 0.55.0 + ofetch: 1.1.1 + dev: false + + /@unocss/preset-wind/0.55.0: + resolution: {integrity: sha512-H9vNXdEJVQx1UO367V3RInAUj4qrsPCXm914RNC4D7qojOuPQlipW0YD5WFklcXeI/k0f1B30VjDYGZhV0jykg==} + dependencies: + '@unocss/core': 0.55.0 + '@unocss/preset-mini': 0.55.0 + dev: false + + /@unocss/reset/0.55.0: + resolution: {integrity: sha512-TzpcOCIr16IbFxQ4vrSfEV+A8k0N4mJkhl7J3SZfAxBpNDBKAWDB6VBW9iEQY5aBYDLN3wRx1LskgEoubqBCPQ==} + dev: false + + /@unocss/scope/0.55.0: + resolution: {integrity: sha512-44xgHoklh2BWWuOkA0ZL1qgr4t/DGnynj3UI8K8YP+PClFFMZ/T+kfhsLBDOrS2a4ytzgh17cTGhjAc3cTwiEA==} + dev: false + + /@unocss/transformer-attributify-jsx-babel/0.55.0: + resolution: {integrity: sha512-gsPuD56gNw47AFgOmdpqT9+gdisLXKnPccF4ozZoqGOv3Hy2MPOc+Dkwk7qkDzzSdC39G5Aq09z8X9R2vU64XQ==} + dependencies: + '@unocss/core': 0.55.0 + dev: false + + /@unocss/transformer-attributify-jsx/0.55.0: + resolution: {integrity: sha512-17/4I2Uqj44JJ4iv3e/mBH1DsWvyVbbbA9JivS/iBPferdFTPtt4Buddhm7bkx1tE86KYZcokVZ8N5RA2zu2UQ==} + dependencies: + '@unocss/core': 0.55.0 + dev: false + + /@unocss/transformer-compile-class/0.55.0: + resolution: {integrity: sha512-aH2SWXqOGJAEuNS1S/fIfs0gFwnakxgG83PCS40uNbEiLv/iG0HuALaQbVvyWHo3O7xLoMa7os9p72Q2amaVQw==} + dependencies: + '@unocss/core': 0.55.0 + dev: false + + /@unocss/transformer-directives/0.55.0: + resolution: {integrity: sha512-bWfAOqQxzy5vIul/jgXN2b0APAk9tWKeTN9Rh4FWvz+dI0P7cBc3rHVEC5qM3i9xwYObtjQcNZjEfJpyeapnzg==} + dependencies: + '@unocss/core': 0.55.0 + css-tree: 2.3.1 + dev: false + + /@unocss/transformer-variant-group/0.55.0: + resolution: {integrity: sha512-0VSvQpEmN8/Y+CfVMhL1+u1+FjmDFtviqKz8aaLFBapC/hnxbkAQTZRVv7mFNvBhBVUHXZOz7LASR4q9RtIeVA==} + dependencies: + '@unocss/core': 0.55.0 + dev: false + + /@unocss/vite/0.55.0_vite@4.4.9: + resolution: {integrity: sha512-qUOqJzSnztCQFXOCNOCqpwwziVMmygXmdbuaqNAmkAg2EzoMSacQKzmLIj49UU0l+iykf2mDh8DmQxpnEU2JSQ==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 + dependencies: + '@ampproject/remapping': 2.2.1 + '@rollup/pluginutils': 5.0.2 + '@unocss/config': 0.55.0 + '@unocss/core': 0.55.0 + '@unocss/inspector': 0.55.0 + '@unocss/scope': 0.55.0 + '@unocss/transformer-directives': 0.55.0 + chokidar: 3.5.3 + fast-glob: 3.3.1 + magic-string: 0.30.2 + vite: 4.4.9_sass@1.65.1 + transitivePeerDependencies: + - rollup + dev: false + + /@vitejs/plugin-vue/4.2.3_vite@4.4.9+vue@3.3.4: + resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 + vue: ^3.2.25 + dependencies: + vite: 4.4.9_sass@1.65.1 + vue: 3.3.4 + dev: true + + /@vue/compiler-core/3.3.4: + resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} + dependencies: + '@babel/parser': 7.22.10 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + + /@vue/compiler-dom/3.3.4: + resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==} + dependencies: + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 + + /@vue/compiler-sfc/3.3.4: + resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} + dependencies: + '@babel/parser': 7.22.10 + '@vue/compiler-core': 3.3.4 + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-ssr': 3.3.4 + '@vue/reactivity-transform': 3.3.4 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + magic-string: 0.30.2 + postcss: 8.4.27 + source-map-js: 1.0.2 + + /@vue/compiler-ssr/3.3.4: + resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==} + dependencies: + '@vue/compiler-dom': 3.3.4 + '@vue/shared': 3.3.4 + + /@vue/devtools-api/6.5.0: + resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} + dev: false + + /@vue/reactivity-transform/3.3.4: + resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} + dependencies: + '@babel/parser': 7.22.10 + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + magic-string: 0.30.2 + + /@vue/reactivity/3.3.4: + resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==} + dependencies: + '@vue/shared': 3.3.4 + + /@vue/runtime-core/3.3.4: + resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==} + dependencies: + '@vue/reactivity': 3.3.4 + '@vue/shared': 3.3.4 + + /@vue/runtime-dom/3.3.4: + resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==} + dependencies: + '@vue/runtime-core': 3.3.4 + '@vue/shared': 3.3.4 + csstype: 3.1.2 + + /@vue/server-renderer/3.3.4_vue@3.3.4: + resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==} + peerDependencies: + vue: 3.3.4 + dependencies: + '@vue/compiler-ssr': 3.3.4 + '@vue/shared': 3.3.4 + vue: 3.3.4 + + /@vue/shared/3.3.4: + resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} + + /@vueuse/core/10.3.0_vue@3.3.4: + resolution: {integrity: sha512-BEM5yxcFKb5btFjTSAFjTu5jmwoW66fyV9uJIP4wUXXU8aR5Hl44gndaaXp7dC5HSObmgbnR2RN+Un1p68Mf5Q==} + dependencies: + '@types/web-bluetooth': 0.0.17 + '@vueuse/metadata': 10.3.0 + '@vueuse/shared': 10.3.0_vue@3.3.4 + vue-demi: 0.14.5_vue@3.3.4 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /@vueuse/metadata/10.3.0: + resolution: {integrity: sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==} + dev: false + + /@vueuse/shared/10.3.0_vue@3.3.4: + resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} + dependencies: + vue-demi: 0.14.5_vue@3.3.4 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /@zclzone/eslint-config/0.0.4: + resolution: {integrity: sha512-dDDHsLc0qEt/tczC1nRU5d+2LCOPwwKohw5Wlq4A1mTFgTQaFoSDmP/j9XnAbjCYfxbGUeEat0221WwwVbPhuw==} + dependencies: + eslint: 8.46.0 + eslint-config-prettier: 8.10.0_eslint@8.46.0 + eslint-plugin-prettier: 4.2.1_i2tnxmyaxd2bqdmeheimspsciq + eslint-plugin-vue: 9.17.0_eslint@8.46.0 + prettier: 2.8.8 + transitivePeerDependencies: + - supports-color + dev: false + + /acorn-jsx/5.3.2_acorn@8.10.0: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.10.0 + dev: false + + /acorn/8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: false + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: false + + /ansi-regex/2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + dev: false + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: false + + /ansi-styles/2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + dev: false + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: false + + /anymatch/3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: false + + /arr-diff/4.0.0: + resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} + engines: {node: '>=0.10.0'} + dev: false + + /arr-flatten/1.1.0: + resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} + engines: {node: '>=0.10.0'} + dev: false + + /arr-union/3.1.0: + resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} + engines: {node: '>=0.10.0'} + dev: false + + /array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: false + + /array-unique/0.3.2: + resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} + engines: {node: '>=0.10.0'} + dev: false + + /assign-symbols/1.0.0: + resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} + engines: {node: '>=0.10.0'} + dev: false + + /async-validator/4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + dev: false + + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: false + + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /atob/2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + dev: false + + /axios/1.4.0: + resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: false + + /base/0.11.2: + resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} + engines: {node: '>=0.10.0'} + dependencies: + cache-base: 1.0.1 + class-utils: 0.3.6 + component-emitter: 1.3.0 + define-property: 1.0.0 + isobject: 3.0.1 + mixin-deep: 1.3.2 + pascalcase: 0.1.1 + dev: false + + /big-integer/1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + dev: false + + /big.js/5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + dev: false + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + + /bluebird/3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + dev: false + + /boolbase/1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: false + + /bplist-parser/0.2.0: + resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} + engines: {node: '>= 5.10.0'} + dependencies: + big-integer: 1.6.51 + dev: false + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: false + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + + /braces/2.3.2: + resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} + engines: {node: '>=0.10.0'} + dependencies: + arr-flatten: 1.1.0 + array-unique: 0.3.2 + extend-shallow: 2.0.1 + fill-range: 4.0.0 + isobject: 3.0.1 + repeat-element: 1.1.4 + snapdragon: 0.8.2 + snapdragon-node: 2.1.1 + split-string: 3.1.0 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: false + + /bundle-name/3.0.0: + resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} + engines: {node: '>=12'} + dependencies: + run-applescript: 5.0.0 + dev: false + + /cac/6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: false + + /cache-base/1.0.1: + resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} + engines: {node: '>=0.10.0'} + dependencies: + collection-visit: 1.0.0 + component-emitter: 1.3.0 + get-value: 2.0.6 + has-value: 1.0.0 + isobject: 3.0.1 + set-value: 2.0.1 + to-object-path: 0.3.0 + union-value: 1.0.1 + unset-value: 1.0.0 + dev: false + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: false + + /camel-case/4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + dependencies: + pascal-case: 3.1.2 + tslib: 2.6.1 + dev: false + + /chalk/1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + dev: false + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + + /class-utils/0.3.6: + resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} + engines: {node: '>=0.10.0'} + dependencies: + arr-union: 3.1.0 + define-property: 0.2.5 + isobject: 3.0.1 + static-extend: 0.1.2 + dev: false + + /clean-css/5.3.2: + resolution: {integrity: sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==} + engines: {node: '>= 10.0'} + dependencies: + source-map: 0.6.1 + dev: false + + /cliui/8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: false + + /clone/2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + dev: false + + /collection-visit/1.0.0: + resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} + engines: {node: '>=0.10.0'} + dependencies: + map-visit: 1.0.0 + object-visit: 1.0.1 + dev: false + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /colorette/2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: false + + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /commander/2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: false + + /commander/7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: false + + /commander/8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: false + + /component-emitter/1.3.0: + resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} + dev: false + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: false + + /connect-history-api-fallback/1.6.0: + resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==} + engines: {node: '>=0.8'} + dev: false + + /consola/2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + dev: false + + /consola/3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: false + + /copy-descriptor/0.1.1: + resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} + engines: {node: '>=0.10.0'} + dev: false + + /cors/2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: false + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: false + + /css-render/0.15.12: + resolution: {integrity: sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==} + dependencies: + '@emotion/hash': 0.8.0 + csstype: 3.0.11 + dev: false + + /css-select/4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + dev: false + + /css-tree/1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + dev: false + + /css-tree/2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: false + + /css-what/6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: false + + /cssesc/3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /csso/4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + dependencies: + css-tree: 1.1.3 + dev: false + + /csstype/3.0.11: + resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==} + dev: false + + /csstype/3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + + /date-fns-tz/1.3.8_date-fns@2.30.0: + resolution: {integrity: sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==} + peerDependencies: + date-fns: '>=2.0.0' + dependencies: + date-fns: 2.30.0 + dev: false + + /date-fns/2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.22.10 + dev: false + + /dayjs/1.11.9: + resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==} + dev: false + + /debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false + + /decode-uri-component/0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + dev: false + + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: false + + /default-browser-id/3.0.0: + resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} + engines: {node: '>=12'} + dependencies: + bplist-parser: 0.2.0 + untildify: 4.0.0 + dev: false + + /default-browser/4.0.0: + resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} + engines: {node: '>=14.16'} + dependencies: + bundle-name: 3.0.0 + default-browser-id: 3.0.0 + execa: 7.2.0 + titleize: 3.0.0 + dev: false + + /define-lazy-prop/2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: false + + /define-lazy-prop/3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + dev: false + + /define-property/0.2.5: + resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} + engines: {node: '>=0.10.0'} + dependencies: + is-descriptor: 0.1.6 + dev: false + + /define-property/1.0.0: + resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} + engines: {node: '>=0.10.0'} + dependencies: + is-descriptor: 1.0.2 + dev: false + + /define-property/2.0.2: + resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-descriptor: 1.0.2 + isobject: 3.0.1 + dev: false + + /defu/6.1.2: + resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} + dev: false + + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /destr/2.0.1: + resolution: {integrity: sha512-M1Ob1zPSIvlARiJUkKqvAZ3VAqQY6Jcuth/pBKQ2b1dX/Qx0OnJ8Vux6J2H5PTMQeRzWrrbTu70VxBfv/OPDJA==} + dev: false + + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: false + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: false + + /dom-serializer/0.2.2: + resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} + dependencies: + domelementtype: 2.3.0 + entities: 2.2.0 + dev: false + + /dom-serializer/1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + dev: false + + /domelementtype/1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} + dev: false + + /domelementtype/2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler/2.4.2: + resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} + dependencies: + domelementtype: 1.3.1 + dev: false + + /domhandler/4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils/1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + dependencies: + dom-serializer: 0.2.2 + domelementtype: 1.3.1 + dev: false + + /domutils/2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + dev: false + + /dot-case/3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dependencies: + no-case: 3.0.4 + tslib: 2.6.1 + dev: false + + /dotenv-expand/8.0.3: + resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==} + engines: {node: '>=12'} + dev: false + + /dotenv/16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: false + + /duplexer/0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: false + + /ejs/3.1.9: + resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + jake: 10.8.7 + dev: false + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: false + + /emojis-list/3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + dev: false + + /entities/1.1.2: + resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} + dev: false + + /entities/2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + dev: false + + /esbuild/0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: false + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: false + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: false + + /escape-string-regexp/5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: false + + /eslint-config-prettier/8.10.0_eslint@8.46.0: + resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.46.0 + dev: false + + /eslint-plugin-prettier/4.2.1_i2tnxmyaxd2bqdmeheimspsciq: + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.46.0 + eslint-config-prettier: 8.10.0_eslint@8.46.0 + prettier: 2.8.8 + prettier-linter-helpers: 1.0.0 + dev: false + + /eslint-plugin-vue/9.17.0_eslint@8.46.0: + resolution: {integrity: sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0_eslint@8.46.0 + eslint: 8.46.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.0.13 + semver: 7.5.4 + vue-eslint-parser: 9.3.1_eslint@8.46.0 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /eslint-scope/7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: false + + /eslint-visitor-keys/3.4.2: + resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: false + + /eslint/8.46.0: + resolution: {integrity: sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0_eslint@8.46.0 + '@eslint-community/regexpp': 4.6.2 + '@eslint/eslintrc': 2.1.1 + '@eslint/js': 8.46.0 + '@humanwhocodes/config-array': 0.11.10 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.2 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: false + + /espree/9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.10.0 + acorn-jsx: 5.3.2_acorn@8.10.0 + eslint-visitor-keys: 3.4.2 + dev: false + + /esquery/1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: false + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: false + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: false + + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: false + + /etag/1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /evtd/0.2.4: + resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==} + dev: false + + /execa/5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: false + + /execa/7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 4.3.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: false + + /expand-brackets/2.1.4: + resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} + engines: {node: '>=0.10.0'} + dependencies: + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + posix-character-classes: 0.1.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /extend-shallow/2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + dependencies: + is-extendable: 0.1.1 + dev: false + + /extend-shallow/3.0.2: + resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} + engines: {node: '>=0.10.0'} + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + dev: false + + /extglob/2.0.4: + resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} + engines: {node: '>=0.10.0'} + dependencies: + array-unique: 0.3.2 + define-property: 1.0.0 + expand-brackets: 2.1.4 + extend-shallow: 2.0.1 + fragment-cache: 0.2.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: false + + /fast-diff/1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: false + + /fast-glob/3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: false + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: false + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: false + + /fastq/1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: false + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: false + + /filelist/1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + dependencies: + minimatch: 5.1.6 + dev: false + + /fill-range/4.0.0: + resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 2.0.1 + is-number: 3.0.0 + repeat-string: 1.6.1 + to-regex-range: 2.1.1 + dev: false + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: false + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: false + + /flatted/3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: false + + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /for-in/1.0.2: + resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} + engines: {node: '>=0.10.0'} + dev: false + + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /fragment-cache/0.2.1: + resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} + engines: {node: '>=0.10.0'} + dependencies: + map-cache: 0.2.2 + dev: false + + /fs-extra/10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: false + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: false + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: false + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: false + + /get-stream/6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: false + + /get-value/2.0.6: + resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} + engines: {node: '>=0.10.0'} + dev: false + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: false + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: false + + /globals/13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: false + + /globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: false + + /graceful-fs/4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: false + + /graphemer/1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: false + + /gzip-size/6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + dependencies: + duplexer: 0.1.2 + dev: false + + /has-ansi/2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + dependencies: + ansi-regex: 2.1.1 + dev: false + + /has-flag/1.0.0: + resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} + engines: {node: '>=0.10.0'} + dev: false + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: false + + /has-value/0.3.1: + resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} + engines: {node: '>=0.10.0'} + dependencies: + get-value: 2.0.6 + has-values: 0.1.4 + isobject: 2.1.0 + dev: false + + /has-value/1.0.0: + resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} + engines: {node: '>=0.10.0'} + dependencies: + get-value: 2.0.6 + has-values: 1.0.0 + isobject: 3.0.1 + dev: false + + /has-values/0.1.4: + resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} + engines: {node: '>=0.10.0'} + dev: false + + /has-values/1.0.0: + resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-number: 3.0.0 + kind-of: 4.0.0 + dev: false + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: false + + /he/1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: false + + /highlight.js/11.8.0: + resolution: {integrity: sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==} + engines: {node: '>=12.0.0'} + dev: false + + /html-minifier-terser/6.1.0: + resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} + engines: {node: '>=12'} + hasBin: true + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.2 + commander: 8.3.0 + he: 1.2.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.19.2 + dev: false + + /htmlparser2/3.10.1: + resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + dependencies: + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + + /human-signals/2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: false + + /human-signals/4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} + engines: {node: '>=14.18.0'} + dev: false + + /ignore/5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: false + + /image-size/0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dev: false + + /immutable/4.3.2: + resolution: {integrity: sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==} + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: false + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: false + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: false + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /is-accessor-descriptor/0.1.6: + resolution: {integrity: sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + + /is-accessor-descriptor/1.0.0: + resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 6.0.3 + dev: false + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + + /is-buffer/1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + dev: false + + /is-core-module/2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + dev: false + + /is-data-descriptor/0.1.4: + resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + + /is-data-descriptor/1.0.0: + resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 6.0.3 + dev: false + + /is-descriptor/0.1.6: + resolution: {integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==} + engines: {node: '>=0.10.0'} + dependencies: + is-accessor-descriptor: 0.1.6 + is-data-descriptor: 0.1.4 + kind-of: 5.1.0 + dev: false + + /is-descriptor/1.0.2: + resolution: {integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==} + engines: {node: '>=0.10.0'} + dependencies: + is-accessor-descriptor: 1.0.0 + is-data-descriptor: 1.0.0 + kind-of: 6.0.3 + dev: false + + /is-docker/2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: false + + /is-docker/3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + dev: false + + /is-extendable/0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + dev: false + + /is-extendable/1.0.1: + resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} + engines: {node: '>=0.10.0'} + dependencies: + is-plain-object: 2.0.4 + dev: false + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: false + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + + /is-inside-container/1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + dependencies: + is-docker: 3.0.0 + dev: false + + /is-number/3.0.0: + resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: false + + /is-plain-obj/1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: false + + /is-plain-object/2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + dependencies: + isobject: 3.0.1 + dev: false + + /is-stream/2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: false + + /is-stream/3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + + /is-windows/1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: false + + /is-wsl/2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: false + + /isarray/1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: false + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: false + + /isobject/2.1.0: + resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} + engines: {node: '>=0.10.0'} + dependencies: + isarray: 1.0.0 + dev: false + + /isobject/3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + dev: false + + /jake/10.8.7: + resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} + engines: {node: '>=10'} + hasBin: true + dependencies: + async: 3.2.4 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + dev: false + + /jiti/1.19.1: + resolution: {integrity: sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==} + hasBin: true + dev: false + + /js-base64/2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + dev: false + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: false + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: false + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: false + + /json5/1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: false + + /jsonc-parser/3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: false + + /jsonfile/6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + dev: false + + /kind-of/3.2.2: + resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-buffer: 1.1.6 + dev: false + + /kind-of/4.0.0: + resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} + engines: {node: '>=0.10.0'} + dependencies: + is-buffer: 1.1.6 + dev: false + + /kind-of/5.1.0: + resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} + engines: {node: '>=0.10.0'} + dev: false + + /kind-of/6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: false + + /kolorist/1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + dev: false + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: false + + /loader-utils/1.4.2: + resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==} + engines: {node: '>=4.0.0'} + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 1.0.2 + dev: false + + /local-pkg/0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + dev: false + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: false + + /lodash-es/4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: false + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: false + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /lower-case/2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.6.1 + dev: false + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: false + + /magic-string/0.30.2: + resolution: {integrity: sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + + /map-cache/0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + dev: false + + /map-visit/1.0.0: + resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} + engines: {node: '>=0.10.0'} + dependencies: + object-visit: 1.0.1 + dev: false + + /mdn-data/2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + dev: false + + /mdn-data/2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: false + + /merge-options/1.0.1: + resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==} + engines: {node: '>=4'} + dependencies: + is-plain-obj: 1.1.0 + dev: false + + /merge-stream/2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: false + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: false + + /micromatch/3.1.0: + resolution: {integrity: sha512-3StSelAE+hnRvMs8IdVW7Uhk8CVed5tp+kLLGlBP6WiRAXS21GPGu/Nat4WNPXj2Eoc24B02SaeoyozPMfj0/g==} + engines: {node: '>=0.10.0'} + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + braces: 2.3.2 + define-property: 1.0.0 + extend-shallow: 2.0.1 + extglob: 2.0.4 + fragment-cache: 0.2.1 + kind-of: 5.1.0 + nanomatch: 1.2.13 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: false + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mimic-fn/2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: false + + /mimic-fn/4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: false + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: false + + /minimatch/5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimatch/9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimist/1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + + /mixin-deep/1.3.2: + resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} + engines: {node: '>=0.10.0'} + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + dev: false + + /mlly/1.4.0: + resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} + dependencies: + acorn: 8.10.0 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.2.0 + dev: false + + /mrmime/1.0.1: + resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} + engines: {node: '>=10'} + dev: false + + /ms/2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false + + /naive-ui/2.34.4_vue@3.3.4: + resolution: {integrity: sha512-aPG8PDfhSzIzn/jSC9y3Jb3Pe2wHJ7F0cFV1EWlbImSrZECeUmoc+fIcOSWbizoztkKfaUAeKwYdMl09MKkj1g==} + peerDependencies: + vue: ^3.0.0 + dependencies: + '@css-render/plugin-bem': 0.15.12_css-render@0.15.12 + '@css-render/vue3-ssr': 0.15.12_vue@3.3.4 + '@types/katex': 0.14.0 + '@types/lodash': 4.14.197 + '@types/lodash-es': 4.17.8 + async-validator: 4.2.5 + css-render: 0.15.12 + date-fns: 2.30.0 + date-fns-tz: 1.3.8_date-fns@2.30.0 + evtd: 0.2.4 + highlight.js: 11.8.0 + lodash: 4.17.21 + lodash-es: 4.17.21 + seemly: 0.3.6 + treemate: 0.3.11 + vdirs: 0.1.8_vue@3.3.4 + vooks: 0.2.12_vue@3.3.4 + vue: 3.3.4 + vueuc: 0.4.51_vue@3.3.4 + dev: false + + /nanoid/3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /nanomatch/1.2.13: + resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} + engines: {node: '>=0.10.0'} + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + fragment-cache: 0.2.1 + is-windows: 1.0.2 + kind-of: 6.0.3 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: false + + /no-case/3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.6.1 + dev: false + + /node-fetch-native/1.2.0: + resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==} + dev: false + + /node-html-parser/5.4.2: + resolution: {integrity: sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==} + dependencies: + css-select: 4.3.0 + he: 1.2.0 + dev: false + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + /npm-run-path/4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: false + + /npm-run-path/5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: false + + /nth-check/2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: false + + /object-assign/4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + + /object-copy/0.1.0: + resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} + engines: {node: '>=0.10.0'} + dependencies: + copy-descriptor: 0.1.1 + define-property: 0.2.5 + kind-of: 3.2.2 + dev: false + + /object-visit/1.0.1: + resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} + engines: {node: '>=0.10.0'} + dependencies: + isobject: 3.0.1 + dev: false + + /object.pick/1.3.0: + resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} + engines: {node: '>=0.10.0'} + dependencies: + isobject: 3.0.1 + dev: false + + /ofetch/1.1.1: + resolution: {integrity: sha512-SSMoktrp9SNLi20BWfB/BnnKcL0RDigXThD/mZBeQxkIRv1xrd9183MtLdsqRYLYSqW0eTr5t8w8MqjNhvoOQQ==} + dependencies: + destr: 2.0.1 + node-fetch-native: 1.2.0 + ufo: 1.2.0 + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: false + + /onetime/5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: false + + /onetime/6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: false + + /open/8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: false + + /open/9.1.0: + resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} + engines: {node: '>=14.16'} + dependencies: + default-browser: 4.0.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 2.2.0 + dev: false + + /optionator/0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: false + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: false + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: false + + /param-case/3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + dependencies: + dot-case: 3.0.4 + tslib: 2.6.1 + dev: false + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: false + + /pascal-case/3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + dependencies: + no-case: 3.0.4 + tslib: 2.6.1 + dev: false + + /pascalcase/0.1.1: + resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} + engines: {node: '>=0.10.0'} + dev: false + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: false + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: false + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: false + + /path-key/4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: false + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: false + + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: false + + /pathe/0.2.0: + resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} + dev: false + + /pathe/1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: false + + /perfect-debounce/1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + dev: false + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /pinia/2.1.6_typescript@5.1.6+vue@3.3.4: + resolution: {integrity: sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==} + peerDependencies: + '@vue/composition-api': ^1.4.0 + typescript: '>=4.4.4' + vue: ^2.6.14 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + dependencies: + '@vue/devtools-api': 6.5.0 + typescript: 5.1.6 + vue: 3.3.4 + vue-demi: 0.14.5_vue@3.3.4 + dev: false + + /pkg-types/1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.0 + pathe: 1.1.1 + dev: false + + /posix-character-classes/0.1.1: + resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} + engines: {node: '>=0.10.0'} + dev: false + + /postcss-prefix-selector/1.16.0_postcss@5.2.18: + resolution: {integrity: sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q==} + peerDependencies: + postcss: '>4 <9' + dependencies: + postcss: 5.2.18 + dev: false + + /postcss-selector-parser/6.0.13: + resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: false + + /postcss/5.2.18: + resolution: {integrity: sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==} + engines: {node: '>=0.12'} + dependencies: + chalk: 1.1.3 + js-base64: 2.6.4 + source-map: 0.5.7 + supports-color: 3.2.3 + dev: false + + /postcss/8.4.27: + resolution: {integrity: sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /posthtml-parser/0.2.1: + resolution: {integrity: sha512-nPC53YMqJnc/+1x4fRYFfm81KV2V+G9NZY+hTohpYg64Ay7NemWWcV4UWuy/SgMupqQ3kJ88M/iRfZmSnxT+pw==} + dependencies: + htmlparser2: 3.10.1 + isobject: 2.1.0 + dev: false + + /posthtml-rename-id/1.0.12: + resolution: {integrity: sha512-UKXf9OF/no8WZo9edRzvuMenb6AD5hDLzIepJW+a4oJT+T/Lx7vfMYWT4aWlGNQh0WMhnUx1ipN9OkZ9q+ddEw==} + dependencies: + escape-string-regexp: 1.0.5 + dev: false + + /posthtml-render/1.4.0: + resolution: {integrity: sha512-W1779iVHGfq0Fvh2PROhCe2QhB8mEErgqzo1wpIt36tCgChafP+hbXIhLDOM8ePJrZcFs0vkNEtdibEWVqChqw==} + engines: {node: '>=10'} + dev: false + + /posthtml-svg-mode/1.0.3: + resolution: {integrity: sha512-hEqw9NHZ9YgJ2/0G7CECOeuLQKZi8HjWLkBaSVtOWjygQ9ZD8P7tqeowYs7WrFdKsWEKG7o+IlsPY8jrr0CJpQ==} + dependencies: + merge-options: 1.0.1 + posthtml: 0.9.2 + posthtml-parser: 0.2.1 + posthtml-render: 1.4.0 + dev: false + + /posthtml/0.9.2: + resolution: {integrity: sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==} + engines: {node: '>=0.10.0'} + dependencies: + posthtml-parser: 0.2.1 + posthtml-render: 1.4.0 + dev: false + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: false + + /prettier-linter-helpers/1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.3.0 + dev: false + + /prettier/2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: false + + /proxy-from-env/1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + + /punycode/2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + dev: false + + /query-string/4.3.4: + resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==} + engines: {node: '>=0.10.0'} + dependencies: + object-assign: 4.1.1 + strict-uri-encode: 1.1.0 + dev: false + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: false + + /readable-stream/3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + + /regenerator-runtime/0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false + + /regex-not/1.0.2: + resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 3.0.2 + safe-regex: 1.1.0 + dev: false + + /relateurl/0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + dev: false + + /repeat-element/1.1.4: + resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} + engines: {node: '>=0.10.0'} + dev: false + + /repeat-string/1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: false + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: false + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: false + + /resolve-url/0.2.1: + resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} + deprecated: https://github.com/lydell/resolve-url#deprecated + dev: false + + /resolve/1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: false + + /ret/0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + dev: false + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: false + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: false + + /rollup-plugin-visualizer/5.9.2: + resolution: {integrity: sha512-waHktD5mlWrYFrhOLbti4YgQCn1uR24nYsNuXxg7LkPH8KdTXVWR9DNY1WU0QqokyMixVXJS4J04HNrVTMP01A==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + rollup: 2.x || 3.x + peerDependenciesMeta: + rollup: + optional: true + dependencies: + open: 8.4.2 + picomatch: 2.3.1 + source-map: 0.7.4 + yargs: 17.7.2 + dev: false + + /rollup/3.28.0: + resolution: {integrity: sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + + /run-applescript/5.0.0: + resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} + engines: {node: '>=12'} + dependencies: + execa: 5.1.1 + dev: false + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: false + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safe-regex/1.1.0: + resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} + dependencies: + ret: 0.1.15 + dev: false + + /sass/1.65.1: + resolution: {integrity: sha512-9DINwtHmA41SEd36eVPQ9BJKpn7eKDQmUHmpI0y5Zv2Rcorrh0zS+cFrt050hdNbmmCNKTW3hV5mWfuegNRsEA==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + immutable: 4.3.2 + source-map-js: 1.0.2 + + /scule/1.0.0: + resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==} + dev: false + + /seemly/0.3.6: + resolution: {integrity: sha512-lEV5VB8BUKTo/AfktXJcy+JeXns26ylbMkIUco8CYREsQijuz4mrXres2Q+vMLdwkuLxJdIPQ8IlCIxLYm71Yw==} + dev: false + + /semver/7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + + /set-value/2.0.1: + resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + dev: false + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: false + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: false + + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: false + + /sirv/2.0.3: + resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} + engines: {node: '>= 10'} + dependencies: + '@polka/url': 1.0.0-next.21 + mrmime: 1.0.1 + totalist: 3.0.1 + dev: false + + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: false + + /snapdragon-node/2.1.1: + resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} + engines: {node: '>=0.10.0'} + dependencies: + define-property: 1.0.0 + isobject: 3.0.1 + snapdragon-util: 3.0.1 + dev: false + + /snapdragon-util/3.0.1: + resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + + /snapdragon/0.8.2: + resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} + engines: {node: '>=0.10.0'} + dependencies: + base: 0.11.2 + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + map-cache: 0.2.2 + source-map: 0.5.7 + source-map-resolve: 0.5.3 + use: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: false + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /source-map-resolve/0.5.3: + resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + resolve-url: 0.2.1 + source-map-url: 0.4.1 + urix: 0.1.0 + dev: false + + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: false + + /source-map-url/0.4.1: + resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} + deprecated: See https://github.com/lydell/source-map-url#deprecated + dev: false + + /source-map/0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + dev: false + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + + /source-map/0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: false + + /split-string/3.1.0: + resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 3.0.2 + dev: false + + /stable/0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + dev: false + + /static-extend/0.1.2: + resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} + engines: {node: '>=0.10.0'} + dependencies: + define-property: 0.2.5 + object-copy: 0.1.0 + dev: false + + /strict-uri-encode/1.1.0: + resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} + engines: {node: '>=0.10.0'} + dev: false + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: false + + /string_decoder/1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /strip-ansi/3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + dependencies: + ansi-regex: 2.1.1 + dev: false + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: false + + /strip-final-newline/2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: false + + /strip-final-newline/3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: false + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: false + + /strip-literal/1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.10.0 + dev: false + + /supports-color/2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + dev: false + + /supports-color/3.2.3: + resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} + engines: {node: '>=0.8.0'} + dependencies: + has-flag: 1.0.0 + dev: false + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: false + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: false + + /svg-baker/1.7.0: + resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==} + dependencies: + bluebird: 3.7.2 + clone: 2.1.2 + he: 1.2.0 + image-size: 0.5.5 + loader-utils: 1.4.2 + merge-options: 1.0.1 + micromatch: 3.1.0 + postcss: 5.2.18 + postcss-prefix-selector: 1.16.0_postcss@5.2.18 + posthtml-rename-id: 1.0.12 + posthtml-svg-mode: 1.0.3 + query-string: 4.3.4 + traverse: 0.6.7 + transitivePeerDependencies: + - supports-color + dev: false + + /svgo/2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.0.0 + stable: 0.1.8 + dev: false + + /synckit/0.8.5: + resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/utils': 2.4.2 + tslib: 2.6.1 + dev: false + + /terser/5.19.2: + resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.5 + acorn: 8.10.0 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: false + + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: false + + /titleize/3.0.0: + resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} + engines: {node: '>=12'} + dev: false + + /to-fast-properties/2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + /to-object-path/0.3.0: + resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + + /to-regex-range/2.1.1: + resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} + engines: {node: '>=0.10.0'} + dependencies: + is-number: 3.0.0 + repeat-string: 1.6.1 + dev: false + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /to-regex/3.0.2: + resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} + engines: {node: '>=0.10.0'} + dependencies: + define-property: 2.0.2 + extend-shallow: 3.0.2 + regex-not: 1.0.2 + safe-regex: 1.1.0 + dev: false + + /totalist/3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + dev: false + + /traverse/0.6.7: + resolution: {integrity: sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==} + dev: false + + /treemate/0.3.11: + resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==} + dev: false + + /ts-api-utils/1.0.1_typescript@5.1.6: + resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.1.6 + dev: false + + /tslib/2.6.1: + resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} + dev: false + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: false + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: false + + /typescript/5.1.6: + resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + engines: {node: '>=14.17'} + hasBin: true + dev: false + + /ufo/1.2.0: + resolution: {integrity: sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==} + dev: false + + /unconfig/0.3.10: + resolution: {integrity: sha512-tj317lhIq2iZF/NXrJnU1t2UaGUKKz1eL1sK2t63Oq66V9BxqvZV12m55fp/fpQJ+DDmVlLgo7cnLVOZkhlO/A==} + dependencies: + '@antfu/utils': 0.7.5 + defu: 6.1.2 + jiti: 1.19.1 + mlly: 1.4.0 + dev: false + + /unimport/3.1.3: + resolution: {integrity: sha512-up4TE2yA+nMyyErGTjbYGVw95MriGa2hVRXQ3/JRp7984cwwqULcnBjHaovVpsO8tZc2j0fvgGu9yiBKOyxvYw==} + dependencies: + '@rollup/pluginutils': 5.0.2 + escape-string-regexp: 5.0.0 + fast-glob: 3.3.1 + local-pkg: 0.4.3 + magic-string: 0.30.2 + mlly: 1.4.0 + pathe: 1.1.1 + pkg-types: 1.0.3 + scule: 1.0.0 + strip-literal: 1.3.0 + unplugin: 1.4.0 + transitivePeerDependencies: + - rollup + dev: false + + /union-value/1.0.1: + resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + engines: {node: '>=0.10.0'} + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + dev: false + + /universalify/2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: false + + /unocss/0.55.0_vite@4.4.9: + resolution: {integrity: sha512-mjtN/2Dr495swOA/u/UaA0keCtv8/vFc114pd0D4XzpbK2/nKNB9Got/lmhJp8fxblV+oNtLkD0PaHtpAvSpsw==} + engines: {node: '>=14'} + peerDependencies: + '@unocss/webpack': 0.55.0 + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 + peerDependenciesMeta: + '@unocss/webpack': + optional: true + vite: + optional: true + dependencies: + '@unocss/astro': 0.55.0_vite@4.4.9 + '@unocss/cli': 0.55.0 + '@unocss/core': 0.55.0 + '@unocss/extractor-arbitrary-variants': 0.55.0 + '@unocss/postcss': 0.55.0 + '@unocss/preset-attributify': 0.55.0 + '@unocss/preset-icons': 0.55.0 + '@unocss/preset-mini': 0.55.0 + '@unocss/preset-tagify': 0.55.0 + '@unocss/preset-typography': 0.55.0 + '@unocss/preset-uno': 0.55.0 + '@unocss/preset-web-fonts': 0.55.0 + '@unocss/preset-wind': 0.55.0 + '@unocss/reset': 0.55.0 + '@unocss/transformer-attributify-jsx': 0.55.0 + '@unocss/transformer-attributify-jsx-babel': 0.55.0 + '@unocss/transformer-compile-class': 0.55.0 + '@unocss/transformer-directives': 0.55.0 + '@unocss/transformer-variant-group': 0.55.0 + '@unocss/vite': 0.55.0_vite@4.4.9 + vite: 4.4.9_sass@1.65.1 + transitivePeerDependencies: + - rollup + - supports-color + dev: false + + /unplugin-auto-import/0.16.6_@vueuse+core@10.3.0: + resolution: {integrity: sha512-M+YIITkx3C/Hg38hp8HmswP5mShUUyJOzpifv7RTlAbeFlO2Tyw0pwrogSSxnipHDPTtI8VHFBpkYkNKzYSuyA==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': ^3.2.2 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + dependencies: + '@antfu/utils': 0.7.5 + '@rollup/pluginutils': 5.0.2 + '@vueuse/core': 10.3.0_vue@3.3.4 + fast-glob: 3.3.1 + local-pkg: 0.4.3 + magic-string: 0.30.2 + minimatch: 9.0.3 + unimport: 3.1.3 + unplugin: 1.4.0 + transitivePeerDependencies: + - rollup + dev: false + + /unplugin-icons/0.16.5: + resolution: {integrity: sha512-laCCqMWfng1XZgB9yowGfjBdDhtmz8t8zVnhzRNEMhBNdy26QrVewVmdXk/zsiAQYnEWvIxTjvW1nQXrxdd2+w==} + peerDependencies: + '@svgr/core': '>=7.0.0' + '@svgx/core': ^1.0.1 + '@vue/compiler-sfc': ^3.0.2 || ^2.7.0 + vue-template-compiler: ^2.6.12 + vue-template-es2015-compiler: ^1.9.0 + peerDependenciesMeta: + '@svgr/core': + optional: true + '@svgx/core': + optional: true + '@vue/compiler-sfc': + optional: true + vue-template-compiler: + optional: true + vue-template-es2015-compiler: + optional: true + dependencies: + '@antfu/install-pkg': 0.1.1 + '@antfu/utils': 0.7.5 + '@iconify/utils': 2.1.7 + debug: 4.3.4 + kolorist: 1.8.0 + local-pkg: 0.4.3 + unplugin: 1.4.0 + transitivePeerDependencies: + - supports-color + dev: false + + /unplugin-vue-components/0.25.1_vue@3.3.4: + resolution: {integrity: sha512-kzS2ZHVMaGU2XEO2keYQcMjNZkanDSGDdY96uQT9EPe+wqSZwwgbFfKVJ5ti0+8rGAcKHColwKUvctBhq2LJ3A==} + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + dependencies: + '@antfu/utils': 0.7.5 + '@rollup/pluginutils': 5.0.2 + chokidar: 3.5.3 + debug: 4.3.4 + fast-glob: 3.3.1 + local-pkg: 0.4.3 + magic-string: 0.30.2 + minimatch: 9.0.3 + resolve: 1.22.4 + unplugin: 1.4.0 + vue: 3.3.4 + transitivePeerDependencies: + - rollup + - supports-color + dev: false + + /unplugin/1.4.0: + resolution: {integrity: sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==} + dependencies: + acorn: 8.10.0 + chokidar: 3.5.3 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.5.0 + dev: false + + /unset-value/1.0.0: + resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} + engines: {node: '>=0.10.0'} + dependencies: + has-value: 0.3.1 + isobject: 3.0.1 + dev: false + + /untildify/4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + dev: false + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + dev: false + + /urix/0.1.0: + resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} + deprecated: Please see https://github.com/lydell/urix#deprecated + dev: false + + /use/3.1.1: + resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} + engines: {node: '>=0.10.0'} + dev: false + + /util-deprecate/1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /vary/1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /vdirs/0.1.8_vue@3.3.4: + resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==} + peerDependencies: + vue: ^3.0.11 + dependencies: + evtd: 0.2.4 + vue: 3.3.4 + dev: false + + /vite-plugin-compression/0.5.1_vite@4.4.9: + resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} + peerDependencies: + vite: '>=2.0.0' + dependencies: + chalk: 4.1.2 + debug: 4.3.4 + fs-extra: 10.1.0 + vite: 4.4.9_sass@1.65.1 + transitivePeerDependencies: + - supports-color + dev: false + + /vite-plugin-html/3.2.0_vite@4.4.9: + resolution: {integrity: sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==} + peerDependencies: + vite: '>=2.0.0' + dependencies: + '@rollup/pluginutils': 4.2.1 + colorette: 2.0.20 + connect-history-api-fallback: 1.6.0 + consola: 2.15.3 + dotenv: 16.3.1 + dotenv-expand: 8.0.3 + ejs: 3.1.9 + fast-glob: 3.3.1 + fs-extra: 10.1.0 + html-minifier-terser: 6.1.0 + node-html-parser: 5.4.2 + pathe: 0.2.0 + vite: 4.4.9_sass@1.65.1 + dev: false + + /vite-plugin-svg-icons/2.0.1_vite@4.4.9: + resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==} + peerDependencies: + vite: '>=2.0.0' + dependencies: + '@types/svgo': 2.6.4 + cors: 2.8.5 + debug: 4.3.4 + etag: 1.8.1 + fs-extra: 10.1.0 + pathe: 0.2.0 + svg-baker: 1.7.0 + svgo: 2.8.0 + vite: 4.4.9_sass@1.65.1 + transitivePeerDependencies: + - supports-color + dev: false + + /vite/4.4.9_sass@1.65.1: + resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.18.20 + postcss: 8.4.27 + rollup: 3.28.0 + sass: 1.65.1 + optionalDependencies: + fsevents: 2.3.2 + + /vooks/0.2.12_vue@3.3.4: + resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==} + peerDependencies: + vue: ^3.0.0 + dependencies: + evtd: 0.2.4 + vue: 3.3.4 + dev: false + + /vue-demi/0.14.5_vue@3.3.4: + resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.3.4 + dev: false + + /vue-eslint-parser/9.3.1_eslint@8.46.0: + resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: 4.3.4 + eslint: 8.46.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.2 + espree: 9.6.1 + esquery: 1.5.0 + lodash: 4.17.21 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: false + + /vue-router/4.2.4_vue@3.3.4: + resolution: {integrity: sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==} + peerDependencies: + vue: ^3.2.0 + dependencies: + '@vue/devtools-api': 6.5.0 + vue: 3.3.4 + dev: false + + /vue/3.3.4: + resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==} + dependencies: + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-sfc': 3.3.4 + '@vue/runtime-dom': 3.3.4 + '@vue/server-renderer': 3.3.4_vue@3.3.4 + '@vue/shared': 3.3.4 + + /vueuc/0.4.51_vue@3.3.4: + resolution: {integrity: sha512-pLiMChM4f+W8czlIClGvGBYo656lc2Y0/mXFSCydcSmnCR1izlKPGMgiYBGjbY9FDkFG8a2HEVz7t0DNzBWbDw==} + peerDependencies: + vue: ^3.0.11 + dependencies: + '@css-render/vue3-ssr': 0.15.12_vue@3.3.4 + '@juggle/resize-observer': 3.4.0 + css-render: 0.15.12 + evtd: 0.2.4 + seemly: 0.3.6 + vdirs: 0.1.8_vue@3.3.4 + vooks: 0.2.12_vue@3.3.4 + vue: 3.3.4 + dev: false + + /webpack-sources/3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + dev: false + + /webpack-virtual-modules/0.5.0: + resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} + dev: false + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: false + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: false + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: false + + /xml-name-validator/4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + dev: false + + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: false + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: false + + /yargs-parser/21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: false + + /yargs/17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: false + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: false diff --git a/web/public/favicon.svg b/web/public/favicon.svg new file mode 100644 index 0000000..6b8705f --- /dev/null +++ b/web/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/web/public/resource/loading.css b/web/public/resource/loading.css new file mode 100644 index 0000000..443a4f1 --- /dev/null +++ b/web/public/resource/loading.css @@ -0,0 +1,91 @@ +.loading-container { + position: fixed; + left: 0; + top: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + +.loading-svg { + width: 128px; + height: 128px; + color: var(--primary-color); +} + +.loading-spin__container { + width: 56px; + height: 56px; + margin: 36px 0; +} + +.loading-spin { + position: relative; + height: 100%; + animation: loadingSpin 1s linear infinite; +} + +.left-0 { + left: 0; +} +.right-0 { + right: 0; +} +.top-0 { + top: 0; +} +.bottom-0 { + bottom: 0; +} + +.loading-spin-item { + position: absolute; + height: 16px; + width: 16px; + background-color: var(--primary-color); + border-radius: 8px; + -webkit-animation: loadingPulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + animation: loadingPulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes loadingSpin { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes loadingPulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: .5; + } +} + +.loading-delay-500 { + -webkit-animation-delay: 500ms; + animation-delay: 500ms; +} +.loading-delay-1000 { + -webkit-animation-delay: 1000ms; + animation-delay: 1000ms; +} +.loading-delay-1500 { + -webkit-animation-delay: 1500ms; + animation-delay: 1500ms; +} + +.loading-title { + font-size: 28px; + font-weight: 500; + color: #6a6a6a; +} diff --git a/web/public/resource/loading.js b/web/public/resource/loading.js new file mode 100644 index 0000000..f073a52 --- /dev/null +++ b/web/public/resource/loading.js @@ -0,0 +1,25 @@ +/** + * 初始化加载效果的svg格式logo + * @param {string} id - 元素id + */ + function initSvgLogo(id) { + const svgStr = `` + const appEl = document.querySelector(id) + const div = document.createElement('div') + div.innerHTML = svgStr + if (appEl) { + appEl.appendChild(div) + } +} + +function addThemeColorCssVars() { + const key = '__THEME_COLOR__' + const defaultColor = '#316c72' + const themeColor = window.localStorage.getItem(key) || defaultColor + const cssVars = `--primary-color: ${themeColor}` + document.documentElement.style.cssText = cssVars +} + +addThemeColorCssVars() + +initSvgLogo('#loadingLogo') diff --git a/web/settings/index.js b/web/settings/index.js new file mode 100644 index 0000000..1957e86 --- /dev/null +++ b/web/settings/index.js @@ -0,0 +1,2 @@ +export * from './theme.json' +export * from './proxy-config' diff --git a/web/settings/proxy-config.js b/web/settings/proxy-config.js new file mode 100644 index 0000000..eda83ad --- /dev/null +++ b/web/settings/proxy-config.js @@ -0,0 +1,18 @@ +const proxyConfigMappings = { + dev: { + prefix: '/url-patten', + target: 'http://127.0.0.1:9999/api/v1', + }, + test: { + prefix: '/url-patten', + target: 'http://127.0.0.1:9999', + }, + prod: { + prefix: '/url-patten', + target: 'http://127.0.0.1:9999', + }, +} + +export function getProxyConfig(envType = 'dev') { + return proxyConfigMappings[envType] +} diff --git a/web/settings/theme.json b/web/settings/theme.json new file mode 100644 index 0000000..24785e3 --- /dev/null +++ b/web/settings/theme.json @@ -0,0 +1,37 @@ +{ + "header": { + "height": 60 + }, + "tags": { + "visible": true, + "height": 50 + }, + "naiveThemeOverrides": { + "common": { + "primaryColor": "#F4511E", + "primaryColorHover": "#F4511E", + "primaryColorPressed": "#2B4C59FF", + "primaryColorSuppl": "#F4511E", + + "infoColor": "#2080F0FF", + "infoColorHover": "#4098FCFF", + "infoColorPressed": "#1060C9FF", + "infoColorSuppl": "#4098FCFF", + + "successColor": "#18A058FF", + "successColorHover": "#F4511E", + "successColorPressed": "#0C7A43FF", + "successColorSuppl": "#F4511E", + + "warningColor": "#F0A020FF", + "warningColorHover": "#FCB040FF", + "warningColorPressed": "#C97C10FF", + "warningColorSuppl": "#FCB040FF", + + "errorColor": "#D03050FF", + "errorColorHover": "#DE576DFF", + "errorColorPressed": "#AB1F3FFF", + "errorColorSuppl": "#DE576DFF" + } + } +} diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 0000000..0ac3562 --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/web/src/api/index.js b/web/src/api/index.js new file mode 100644 index 0000000..ad57dfc --- /dev/null +++ b/web/src/api/index.js @@ -0,0 +1,34 @@ +import { request } from '@/utils' + +export default { + login: (data) => request.post('/base/access_token', data, { noNeedToken: true }), + getUserInfo: () => request.get('/base/userinfo'), + getUserMenu: () => request.get('/base/usermenu'), + getUserApi: () => request.get('/base/userapi'), + // profile + updatePassword: (data = {}) => request.post('/base/update_password', data), + // users + getUserList: (params = {}) => request.get('/user/list', { params }), + getUserById: (params = {}) => request.get('/user/get', { params }), + createUser: (data = {}) => request.post('/user/create', data), + updateUser: (data = {}) => request.post('/user/update', data), + deleteUser: (params = {}) => request.delete(`/user/delete`, { params }), + // role + getRoleList: (params = {}) => request.get('/role/list', { params }), + createRole: (data = {}) => request.post('/role/create', data), + updateRole: (data = {}) => request.post('/role/update', data), + deleteRole: (params = {}) => request.delete('/role/delete', { params }), + updateRoleAuthorized: (data = {}) => request.post('/role/authorized', data), + getRoleAuthorized: (params = {}) => request.get('/role/authorized', { params }), + // menus + getMenus: (params = {}) => request.get('/menu/list', { params }), + createMenu: (data = {}) => request.post('/menu/create', data), + updateMenu: (data = {}) => request.post('/menu/update', data), + deleteMenu: (params = {}) => request.delete('/menu/delete', { params }), + // apis + getApis: (params = {}) => request.get('/api/list', { params }), + createApi: (data = {}) => request.post('/api/create', data), + updateApi: (data = {}) => request.post('/api/update', data), + deleteApi: (params = {}) => request.delete('/api/delete', { params }), + refreshApi: (data = {}) => request.post('/api/refresh', data), +} diff --git a/web/src/assets/images/login_bg.webp b/web/src/assets/images/login_bg.webp new file mode 100644 index 0000000..2a9df19 Binary files /dev/null and b/web/src/assets/images/login_bg.webp differ diff --git a/web/src/assets/js/icons.js b/web/src/assets/js/icons.js new file mode 100644 index 0000000..9aece86 --- /dev/null +++ b/web/src/assets/js/icons.js @@ -0,0 +1,230 @@ +export default [ + 'mdi-air-humidifier-off', + 'mdi-chili-off', + 'mdi-cigar-off', + 'mdi-clock-time-eight', + 'mdi-clock-time-eight-outline', + 'mdi-clock-time-eleven', + 'mdi-clock-time-eleven-outline', + 'mdi-clock-time-five', + 'mdi-clock-time-five-outline', + 'mdi-clock-time-four', + 'mdi-clock-time-four-outline', + 'mdi-clock-time-nine', + 'mdi-clock-time-nine-outline', + 'mdi-clock-time-one', + 'mdi-clock-time-one-outline', + 'mdi-clock-time-seven', + 'mdi-clock-time-seven-outline', + 'mdi-clock-time-six', + 'mdi-clock-time-six-outline', + 'mdi-clock-time-ten', + 'mdi-clock-time-ten-outline', + 'mdi-clock-time-three', + 'mdi-clock-time-three-outline', + 'mdi-clock-time-twelve', + 'mdi-clock-time-twelve-outline', + 'mdi-clock-time-two', + 'mdi-clock-time-two-outline', + 'mdi-cog-refresh', + 'mdi-cog-refresh-outline', + 'mdi-cog-sync', + 'mdi-cog-sync-outline', + 'mdi-content-save-cog', + 'mdi-content-save-cog-outline', + 'mdi-cosine-wave', + 'mdi-cube-off', + 'mdi-cube-off-outline', + 'mdi-dome-light', + 'mdi-download-box', + 'mdi-download-box-outline', + 'mdi-download-circle', + 'mdi-download-circle-outline', + 'mdi-fan-alert', + 'mdi-fan-chevron-down', + 'mdi-fan-chevron-up', + 'mdi-fan-minus', + 'mdi-fan-plus', + 'mdi-fan-remove', + 'mdi-fan-speed-1', + 'mdi-fan-speed-2', + 'mdi-fan-speed-3', + 'mdi-food-drumstick', + 'mdi-food-drumstick-off', + 'mdi-food-drumstick-off-outline', + 'mdi-food-drumstick-outline', + 'mdi-food-steak', + 'mdi-food-steak-off', + 'mdi-fuse-alert', + 'mdi-fuse-off', + 'mdi-heart-minus', + 'mdi-heart-minus-outline', + 'mdi-heart-off-outline', + 'mdi-heart-plus', + 'mdi-heart-plus-outline', + 'mdi-heart-remove', + 'mdi-heart-remove-outline', + 'mdi-hours-24', + 'mdi-incognito-circle', + 'mdi-incognito-circle-off', + 'mdi-lingerie', + 'mdi-microwave-off', + 'mdi-minus-circle-off', + 'mdi-minus-circle-off-outline', + 'mdi-motion-sensor-off', + 'mdi-pail-minus', + 'mdi-pail-minus-outline', + 'mdi-pail-off', + 'mdi-pail-off-outline', + 'mdi-pail-outline', + 'mdi-pail-plus', + 'mdi-pail-plus-outline', + 'mdi-pail-remove', + 'mdi-pail-remove-outline', + 'mdi-pine-tree-fire', + 'mdi-power-plug-off-outline', + 'mdi-power-plug-outline', + 'mdi-printer-eye', + 'mdi-printer-search', + 'mdi-puzzle-check', + 'mdi-puzzle-check-outline', + 'mdi-rug', + 'mdi-sawtooth-wave', + 'mdi-set-square', + 'mdi-smoking-pipe-off', + 'mdi-spoon-sugar', + 'mdi-square-wave', + 'mdi-table-split-cell', + 'mdi-ticket-percent-outline', + 'mdi-triangle-wave', + 'mdi-waveform', + 'mdi-wizard-hat', + 'mdi-ab-testing', + 'mdi-abjad-arabic', + 'mdi-abjad-hebrew', + 'mdi-abugida-devanagari', + 'mdi-abugida-thai', + 'mdi-access-point', + 'mdi-access-point-network', + 'mdi-access-point-network-off', + 'mdi-account', + 'mdi-account-alert', + 'mdi-account-alert-outline', + 'mdi-account-arrow-left', + 'mdi-account-arrow-left-outline', + 'mdi-account-arrow-right', + 'mdi-account-arrow-right-outline', + 'mdi-account-box', + 'mdi-account-box-multiple', + 'mdi-account-box-multiple-outline', + 'mdi-account-box-outline', + 'mdi-account-cancel', + 'mdi-account-cancel-outline', + 'mdi-account-cash', + 'mdi-account-cash-outline', + 'mdi-account-check', + 'mdi-account-check-outline', + 'mdi-account-child', + 'mdi-account-child-circle', + 'mdi-account-child-outline', + 'mdi-account-circle', + 'mdi-account-circle-outline', + 'mdi-account-clock', + 'mdi-account-clock-outline', + 'mdi-account-cog', + 'mdi-account-cog-outline', + 'mdi-account-convert', + 'mdi-account-convert-outline', + 'mdi-account-cowboy-hat', + 'mdi-account-details', + 'mdi-account-details-outline', + 'mdi-account-edit', + 'mdi-account-edit-outline', + 'mdi-account-group', + 'mdi-account-group-outline', + 'mdi-account-hard-hat', + 'mdi-account-heart', + 'mdi-account-heart-outline', + 'mdi-account-key', + 'mdi-account-key-outline', + 'mdi-account-lock', + 'mdi-account-lock-outline', + 'mdi-account-minus', + 'mdi-account-minus-outline', + 'mdi-account-multiple', + 'mdi-account-multiple-check', + 'mdi-account-multiple-check-outline', + 'mdi-account-multiple-minus', + 'mdi-account-multiple-minus-outline', + 'mdi-account-multiple-outline', + 'mdi-account-multiple-plus', + 'mdi-account-multiple-plus-outline', + 'mdi-account-multiple-remove', + 'mdi-account-multiple-remove-outline', + 'mdi-account-music', + 'mdi-account-music-outline', + 'mdi-account-network', + 'mdi-account-network-outline', + 'mdi-account-off', + 'mdi-account-off-outline', + 'mdi-account-outline', + 'mdi-account-plus', + 'mdi-account-plus-outline', + 'mdi-account-question', + 'mdi-account-question-outline', + 'mdi-account-remove', + 'mdi-account-remove-outline', + 'mdi-account-search', + 'mdi-account-search-outline', + 'mdi-account-settings', + 'mdi-account-settings-outline', + 'mdi-account-star', + 'mdi-account-star-outline', + 'mdi-account-supervisor', + 'mdi-account-supervisor-circle', + 'mdi-account-supervisor-outline', + 'mdi-account-switch', + 'mdi-account-switch-outline', + 'mdi-account-tie', + 'mdi-account-tie-outline', + 'mdi-account-tie-voice', + 'mdi-account-tie-voice-off', + 'mdi-account-tie-voice-off-outline', + 'mdi-account-tie-voice-outline', + 'mdi-account-voice', + 'mdi-adjust', + 'mdi-adobe', + 'mdi-adobe-acrobat', + 'mdi-air-conditioner', + 'mdi-air-filter', + 'mdi-air-horn', + 'mdi-air-humidifier', + 'mdi-air-purifier', + 'mdi-airbag', + 'mdi-airballoon', + 'mdi-airballoon-outline', + 'mdi-airplane', + 'mdi-airplane-landing', + 'mdi-airplane-off', + 'mdi-airplane-takeoff', + 'mdi-airport', + 'mdi-alarm', + 'mdi-alarm-bell', + 'mdi-alarm-check', + 'mdi-alarm-light', + 'mdi-alarm-light-outline', + 'mdi-alarm-multiple', + 'mdi-alarm-note', + 'mdi-alarm-note-off', + 'mdi-alarm-off', + 'mdi-alarm-plus', + 'mdi-alarm-snooze', + 'mdi-album', + 'mdi-alert', + 'mdi-alert-box', + 'mdi-alert-box-outline', + 'mdi-alert-circle', + 'mdi-alert-circle-check', + 'mdi-alert-circle-check-outline', + 'mdi-alert-circle-outline', +] diff --git a/web/src/assets/svg/forbidden.svg b/web/src/assets/svg/forbidden.svg new file mode 100644 index 0000000..b6b1372 --- /dev/null +++ b/web/src/assets/svg/forbidden.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/front-page.svg b/web/src/assets/svg/front-page.svg new file mode 100644 index 0000000..ea7f5bc --- /dev/null +++ b/web/src/assets/svg/front-page.svg @@ -0,0 +1 @@ + diff --git a/web/src/assets/svg/logo.svg b/web/src/assets/svg/logo.svg new file mode 100644 index 0000000..1833189 --- /dev/null +++ b/web/src/assets/svg/logo.svg @@ -0,0 +1 @@ + diff --git a/web/src/assets/svg/network-error.svg b/web/src/assets/svg/network-error.svg new file mode 100644 index 0000000..c6b8df6 --- /dev/null +++ b/web/src/assets/svg/network-error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/no-data.svg b/web/src/assets/svg/no-data.svg new file mode 100644 index 0000000..31edb04 --- /dev/null +++ b/web/src/assets/svg/no-data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/not-found.svg b/web/src/assets/svg/not-found.svg new file mode 100644 index 0000000..f32b56c --- /dev/null +++ b/web/src/assets/svg/not-found.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/server-error.svg b/web/src/assets/svg/server-error.svg new file mode 100644 index 0000000..6f32b52 --- /dev/null +++ b/web/src/assets/svg/server-error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/service-unavailable.svg b/web/src/assets/svg/service-unavailable.svg new file mode 100644 index 0000000..055438f --- /dev/null +++ b/web/src/assets/svg/service-unavailable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/unauthorized.svg b/web/src/assets/svg/unauthorized.svg new file mode 100644 index 0000000..e918adc --- /dev/null +++ b/web/src/assets/svg/unauthorized.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/components/common/AppFooter.vue b/web/src/components/common/AppFooter.vue new file mode 100644 index 0000000..ce76521 --- /dev/null +++ b/web/src/components/common/AppFooter.vue @@ -0,0 +1,4 @@ + diff --git a/web/src/components/common/AppProvider.vue b/web/src/components/common/AppProvider.vue new file mode 100644 index 0000000..767ed63 --- /dev/null +++ b/web/src/components/common/AppProvider.vue @@ -0,0 +1,67 @@ + + + diff --git a/web/src/components/common/LoadingEmptyWrapper.vue b/web/src/components/common/LoadingEmptyWrapper.vue new file mode 100644 index 0000000..c4e3762 --- /dev/null +++ b/web/src/components/common/LoadingEmptyWrapper.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/web/src/components/common/ScrollX.vue b/web/src/components/common/ScrollX.vue new file mode 100644 index 0000000..7b56083 --- /dev/null +++ b/web/src/components/common/ScrollX.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/web/src/components/icon/CustomIcon.vue b/web/src/components/icon/CustomIcon.vue new file mode 100644 index 0000000..bf8d62f --- /dev/null +++ b/web/src/components/icon/CustomIcon.vue @@ -0,0 +1,22 @@ + + + diff --git a/web/src/components/icon/IconPicker.vue b/web/src/components/icon/IconPicker.vue new file mode 100644 index 0000000..c4e0b00 --- /dev/null +++ b/web/src/components/icon/IconPicker.vue @@ -0,0 +1,71 @@ + + + diff --git a/web/src/components/icon/SvgIcon.vue b/web/src/components/icon/SvgIcon.vue new file mode 100644 index 0000000..7adfe73 --- /dev/null +++ b/web/src/components/icon/SvgIcon.vue @@ -0,0 +1,24 @@ + + + diff --git a/web/src/components/icon/TheIcon.vue b/web/src/components/icon/TheIcon.vue new file mode 100644 index 0000000..8dda4c3 --- /dev/null +++ b/web/src/components/icon/TheIcon.vue @@ -0,0 +1,22 @@ + + + diff --git a/web/src/components/page/AppPage.vue b/web/src/components/page/AppPage.vue new file mode 100644 index 0000000..9e30448 --- /dev/null +++ b/web/src/components/page/AppPage.vue @@ -0,0 +1,18 @@ + + + diff --git a/web/src/components/page/CommonPage.vue b/web/src/components/page/CommonPage.vue new file mode 100644 index 0000000..eeef465 --- /dev/null +++ b/web/src/components/page/CommonPage.vue @@ -0,0 +1,33 @@ + + + diff --git a/web/src/components/query-bar/QueryBar.vue b/web/src/components/query-bar/QueryBar.vue new file mode 100644 index 0000000..5a70132 --- /dev/null +++ b/web/src/components/query-bar/QueryBar.vue @@ -0,0 +1,27 @@ + + + diff --git a/web/src/components/query-bar/QueryBarItem.vue b/web/src/components/query-bar/QueryBarItem.vue new file mode 100644 index 0000000..7434ed5 --- /dev/null +++ b/web/src/components/query-bar/QueryBarItem.vue @@ -0,0 +1,34 @@ + + + diff --git a/web/src/components/table/CrudModal.vue b/web/src/components/table/CrudModal.vue new file mode 100644 index 0000000..c9dc9d7 --- /dev/null +++ b/web/src/components/table/CrudModal.vue @@ -0,0 +1,56 @@ + + + diff --git a/web/src/components/table/CrudTable.vue b/web/src/components/table/CrudTable.vue new file mode 100644 index 0000000..f4e73b7 --- /dev/null +++ b/web/src/components/table/CrudTable.vue @@ -0,0 +1,132 @@ + + + diff --git a/web/src/composables/index.js b/web/src/composables/index.js new file mode 100644 index 0000000..e410b83 --- /dev/null +++ b/web/src/composables/index.js @@ -0,0 +1 @@ +export { default as useCRUD } from './useCRUD' diff --git a/web/src/composables/useCRUD.js b/web/src/composables/useCRUD.js new file mode 100644 index 0000000..f0b88a2 --- /dev/null +++ b/web/src/composables/useCRUD.js @@ -0,0 +1,106 @@ +import { isNullOrWhitespace } from '@/utils' + +const ACTIONS = { + view: '查看', + edit: '编辑', + add: '新增', +} + +export default function ({ name, initForm = {}, doCreate, doDelete, doUpdate, refresh }) { + const modalVisible = ref(false) + const modalAction = ref('') + const modalTitle = computed(() => ACTIONS[modalAction.value] + name) + const modalLoading = ref(false) + const modalFormRef = ref(null) + const modalForm = ref({ ...initForm }) + + /** 新增 */ + function handleAdd() { + modalAction.value = 'add' + modalVisible.value = true + modalForm.value = { ...initForm } + } + + /** 修改 */ + function handleEdit(row) { + modalAction.value = 'edit' + modalVisible.value = true + modalForm.value = { ...row } + } + + /** 查看 */ + function handleView(row) { + modalAction.value = 'view' + modalVisible.value = true + modalForm.value = { ...row } + } + + /** 保存 */ + function handleSave(...callbacks) { + console.log('handlesave') + if (!['edit', 'add'].includes(modalAction.value)) { + modalVisible.value = false + return + } + modalFormRef.value?.validate(async (err) => { + console.log('validate error', err) + if (err) return + const actions = { + add: { + api: () => doCreate(modalForm.value), + cb: () => { + callbacks.forEach((callback) => callback && callback()) + }, + msg: () => $message.success('新增成功'), + }, + edit: { + api: () => doUpdate(modalForm.value), + cb: () => { + callbacks.forEach((callback) => callback && callback()) + }, + msg: () => $message.success('编辑成功'), + }, + } + const action = actions[modalAction.value] + + try { + modalLoading.value = true + const data = await action.api() + action.cb() + action.msg() + modalLoading.value = modalVisible.value = false + data && refresh(data) + } catch (error) { + modalLoading.value = false + } + }) + } + + /** 删除 */ + async function handleDelete(params = {}) { + if (isNullOrWhitespace(params)) return + try { + modalLoading.value = true + const data = await doDelete(params) + $message.success('删除成功') + modalLoading.value = false + refresh(data) + } catch (error) { + modalLoading.value = false + } + } + + return { + modalVisible, + modalAction, + modalTitle, + modalLoading, + handleAdd, + handleDelete, + handleEdit, + handleView, + handleSave, + modalForm, + modalFormRef, + } +} diff --git a/web/src/directives/index.js b/web/src/directives/index.js new file mode 100644 index 0000000..a00c5f3 --- /dev/null +++ b/web/src/directives/index.js @@ -0,0 +1,6 @@ +import setupPermissionDirective from './permission' + +/** setup custom vue directives. - [安装自定义的vue指令] */ +export function setupDirectives(app) { + setupPermissionDirective(app) +} diff --git a/web/src/directives/permission.js b/web/src/directives/permission.js new file mode 100644 index 0000000..f870c72 --- /dev/null +++ b/web/src/directives/permission.js @@ -0,0 +1,34 @@ +import { useUserStore, usePermissionStore } from '@/store' + +function hasPermission(permission) { + const userStore = useUserStore() + const userPermissionStore = usePermissionStore() + + const accessApis = userPermissionStore.apis + if (userStore.isSuperUser) { + return true + } + return accessApis.includes(permission) +} + +export default function setupPermissionDirective(app) { + function updateElVisible(el, permission) { + if (!permission) { + throw new Error(`need roles: like v-permission="get/api/v1/user/list"`) + } + if (!hasPermission(permission)) { + el.parentElement?.removeChild(el) + } + } + + const permissionDirective = { + mounted(el, binding) { + updateElVisible(el, binding.value) + }, + beforeUpdate(el, binding) { + updateElVisible(el, binding.value) + }, + } + + app.directive('permission', permissionDirective) +} diff --git a/web/src/layout/components/AppMain.vue b/web/src/layout/components/AppMain.vue new file mode 100644 index 0000000..8baa0f6 --- /dev/null +++ b/web/src/layout/components/AppMain.vue @@ -0,0 +1,23 @@ + + + diff --git a/web/src/layout/components/header/components/BreadCrumb.vue b/web/src/layout/components/header/components/BreadCrumb.vue new file mode 100644 index 0000000..07b374c --- /dev/null +++ b/web/src/layout/components/header/components/BreadCrumb.vue @@ -0,0 +1,30 @@ + + + diff --git a/web/src/layout/components/header/components/FullScreen.vue b/web/src/layout/components/header/components/FullScreen.vue new file mode 100644 index 0000000..66e873d --- /dev/null +++ b/web/src/layout/components/header/components/FullScreen.vue @@ -0,0 +1,12 @@ + + + diff --git a/web/src/layout/components/header/components/GithubSite.vue b/web/src/layout/components/header/components/GithubSite.vue new file mode 100644 index 0000000..3aed953 --- /dev/null +++ b/web/src/layout/components/header/components/GithubSite.vue @@ -0,0 +1,11 @@ + + + diff --git a/web/src/layout/components/header/components/MenuCollapse.vue b/web/src/layout/components/header/components/MenuCollapse.vue new file mode 100644 index 0000000..8c721fb --- /dev/null +++ b/web/src/layout/components/header/components/MenuCollapse.vue @@ -0,0 +1,12 @@ + + + diff --git a/web/src/layout/components/header/components/ThemeMode.vue b/web/src/layout/components/header/components/ThemeMode.vue new file mode 100644 index 0000000..01ce41e --- /dev/null +++ b/web/src/layout/components/header/components/ThemeMode.vue @@ -0,0 +1,18 @@ + + + diff --git a/web/src/layout/components/header/components/UserAvatar.vue b/web/src/layout/components/header/components/UserAvatar.vue new file mode 100644 index 0000000..50e4abf --- /dev/null +++ b/web/src/layout/components/header/components/UserAvatar.vue @@ -0,0 +1,48 @@ + + + diff --git a/web/src/layout/components/header/index.vue b/web/src/layout/components/header/index.vue new file mode 100644 index 0000000..0c0cec8 --- /dev/null +++ b/web/src/layout/components/header/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/web/src/layout/components/sidebar/components/SideLogo.vue b/web/src/layout/components/sidebar/components/SideLogo.vue new file mode 100644 index 0000000..351ff6b --- /dev/null +++ b/web/src/layout/components/sidebar/components/SideLogo.vue @@ -0,0 +1,24 @@ + + + diff --git a/web/src/layout/components/sidebar/components/SideMenu.vue b/web/src/layout/components/sidebar/components/SideMenu.vue new file mode 100644 index 0000000..9c559c9 --- /dev/null +++ b/web/src/layout/components/sidebar/components/SideMenu.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/web/src/layout/components/sidebar/index.vue b/web/src/layout/components/sidebar/index.vue new file mode 100644 index 0000000..ffd659d --- /dev/null +++ b/web/src/layout/components/sidebar/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/web/src/layout/components/tags/ContextMenu.vue b/web/src/layout/components/tags/ContextMenu.vue new file mode 100644 index 0000000..7b69ff8 --- /dev/null +++ b/web/src/layout/components/tags/ContextMenu.vue @@ -0,0 +1,123 @@ + + + diff --git a/web/src/layout/components/tags/index.vue b/web/src/layout/components/tags/index.vue new file mode 100644 index 0000000..d6783ec --- /dev/null +++ b/web/src/layout/components/tags/index.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/web/src/layout/index.vue b/web/src/layout/index.vue new file mode 100644 index 0000000..7f44efb --- /dev/null +++ b/web/src/layout/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/web/src/main.js b/web/src/main.js new file mode 100644 index 0000000..5ee986c --- /dev/null +++ b/web/src/main.js @@ -0,0 +1,25 @@ +/** 重置样式 */ +import '@/styles/reset.css' +import 'uno.css' +import '@/styles/global.scss' +import 'virtual:svg-icons-register' + +import { createApp } from 'vue' +import { setupRouter } from '@/router' +import { setupStore } from '@/store' +import App from './App.vue' +import { setupDirectives } from './directives' +import { useResize } from '@/utils' + +async function setupApp() { + const app = createApp(App) + + setupStore(app) + + await setupRouter(app) + setupDirectives(app) + app.use(useResize) + app.mount('#app') +} + +setupApp() diff --git a/web/src/router/guard/auth-guard.js b/web/src/router/guard/auth-guard.js new file mode 100644 index 0000000..230e75a --- /dev/null +++ b/web/src/router/guard/auth-guard.js @@ -0,0 +1,18 @@ +import { getToken, isNullOrWhitespace } from '@/utils' + +const WHITE_LIST = ['/login', '/404'] +export function createAuthGuard(router) { + router.beforeEach(async (to) => { + const token = getToken() + + /** 没有token的情况 */ + if (isNullOrWhitespace(token)) { + if (WHITE_LIST.includes(to.path)) return true + return { path: 'login', query: { ...to.query, redirect: to.path } } + } + + /** 有token的情况 */ + if (to.path === '/login') return { path: '/' } + return true + }) +} diff --git a/web/src/router/guard/index.js b/web/src/router/guard/index.js new file mode 100644 index 0000000..2b1f367 --- /dev/null +++ b/web/src/router/guard/index.js @@ -0,0 +1,9 @@ +import { createPageLoadingGuard } from './page-loading-guard' +import { createPageTitleGuard } from './page-title-guard' +import { createAuthGuard } from './auth-guard' + +export function setupRouterGuard(router) { + createPageLoadingGuard(router) + createAuthGuard(router) + createPageTitleGuard(router) +} diff --git a/web/src/router/guard/page-loading-guard.js b/web/src/router/guard/page-loading-guard.js new file mode 100644 index 0000000..bd0f038 --- /dev/null +++ b/web/src/router/guard/page-loading-guard.js @@ -0,0 +1,15 @@ +export function createPageLoadingGuard(router) { + router.beforeEach(() => { + window.$loadingBar?.start() + }) + + router.afterEach(() => { + setTimeout(() => { + window.$loadingBar?.finish() + }, 200) + }) + + router.onError(() => { + window.$loadingBar?.error() + }) +} diff --git a/web/src/router/guard/page-title-guard.js b/web/src/router/guard/page-title-guard.js new file mode 100644 index 0000000..2a95278 --- /dev/null +++ b/web/src/router/guard/page-title-guard.js @@ -0,0 +1,12 @@ +const baseTitle = import.meta.env.VITE_TITLE + +export function createPageTitleGuard(router) { + router.afterEach((to) => { + const pageTitle = to.meta?.title + if (pageTitle) { + document.title = `${pageTitle} | ${baseTitle}` + } else { + document.title = baseTitle + } + }) +} diff --git a/web/src/router/index.js b/web/src/router/index.js new file mode 100644 index 0000000..54b877a --- /dev/null +++ b/web/src/router/index.js @@ -0,0 +1,68 @@ +import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router' +import { setupRouterGuard } from './guard' +import { basicRoutes, EMPTY_ROUTE, NOT_FOUND_ROUTE } from './routes' +import { getToken, isNullOrWhitespace } from '@/utils' +import { useUserStore, usePermissionStore } from '@/store' + +const isHash = import.meta.env.VITE_USE_HASH === 'true' +export const router = createRouter({ + history: isHash ? createWebHashHistory('/') : createWebHistory('/'), + routes: basicRoutes, + scrollBehavior: () => ({ left: 0, top: 0 }), +}) + +export async function setupRouter(app) { + await addDynamicRoutes() + setupRouterGuard(router) + app.use(router) +} + +export async function resetRouter() { + const basicRouteNames = getRouteNames(basicRoutes) + router.getRoutes().forEach((route) => { + const name = route.name + if (!basicRouteNames.includes(name)) { + router.removeRoute(name) + } + }) +} + +export async function addDynamicRoutes() { + const token = getToken() + + // 没有token情况 + if (isNullOrWhitespace(token)) { + router.addRoute(EMPTY_ROUTE) + return + } + + // 有token的情况 + try { + const userStore = useUserStore() + const permissionStore = usePermissionStore() + !userStore.userId && (await userStore.getUserInfo()) + const accessRoutes = await permissionStore.generateRoutes() + await permissionStore.getAccessApis() + accessRoutes.forEach((route) => { + !router.hasRoute(route.name) && router.addRoute(route) + }) + router.hasRoute(EMPTY_ROUTE.name) && router.removeRoute(EMPTY_ROUTE.name) + router.addRoute(NOT_FOUND_ROUTE) + console.log('routes', router.getRoutes()) + } catch (error) { + const userStore = useUserStore() + await userStore.logout() + } +} + +export function getRouteNames(routes) { + return routes.map((route) => getRouteName(route)).flat(1) +} + +function getRouteName(route) { + const names = [route.name] + if (route.children && route.children.length) { + names.push(...route.children.map((item) => getRouteName(item)).flat(1)) + } + return names +} diff --git a/web/src/router/routes/index.js b/web/src/router/routes/index.js new file mode 100644 index 0000000..10af7a0 --- /dev/null +++ b/web/src/router/routes/index.js @@ -0,0 +1,87 @@ +const Layout = () => import('@/layout/index.vue') + +export const basicRoutes = [ + { + name: '工作台', + path: '/', + component: Layout, + redirect: '/workbench', // 默认跳转到首页 + children: [ + { + path: 'workbench', + component: () => import('@/views/workbench/index.vue'), + name: '工作台', + meta: { + title: '工作台', + icon: 'icon-park-outline:workbench', + affix: true, + }, + }, + ], + meta: { order: 0 }, + }, + { + name: '个人中心', + path: '/profile', + component: Layout, + isHidden: true, + children: [ + { + path: '/profile', + component: () => import('@/views/profile/index.vue'), + name: '个人中心', + meta: { + title: '个人中心', + icon: 'user', + affix: true, + }, + }, + ], + meta: { order: 99 }, + }, + { + name: '403', + path: '/403', + component: () => import('@/views/error-page/403.vue'), + isHidden: true, + }, + { + name: '404', + path: '/404', + component: () => import('@/views/error-page/404.vue'), + isHidden: true, + }, + { + name: 'Login', + path: '/login', + component: () => import('@/views/login/index.vue'), + isHidden: true, + meta: { + title: '登录页', + }, + }, +] + +export const NOT_FOUND_ROUTE = { + name: 'NotFound', + path: '/:pathMatch(.*)*', + redirect: '/404', + isHidden: true, +} + +export const EMPTY_ROUTE = { + name: 'Empty', + path: '/:pathMatch(.*)*', + component: null, +} + +const modules = import.meta.glob('@/views/**/route.js', { eager: true }) +const asyncRoutes = [] +Object.keys(modules).forEach((key) => { + asyncRoutes.push(modules[key].default) +}) + +// 加载 views 下每个模块的 index.vue 文件 +const vueModules = import.meta.glob('@/views/**/index.vue') + +export { asyncRoutes, vueModules } diff --git a/web/src/store/index.js b/web/src/store/index.js new file mode 100644 index 0000000..1dec2e9 --- /dev/null +++ b/web/src/store/index.js @@ -0,0 +1,7 @@ +import { createPinia } from 'pinia' + +export function setupStore(app) { + app.use(createPinia()) +} + +export * from './modules' diff --git a/web/src/store/modules/app/index.js b/web/src/store/modules/app/index.js new file mode 100644 index 0000000..27353d2 --- /dev/null +++ b/web/src/store/modules/app/index.js @@ -0,0 +1,47 @@ +import { defineStore } from 'pinia' +import { useDark } from '@vueuse/core' + +const isDark = useDark() +export const useAppStore = defineStore('app', { + state() { + return { + reloadFlag: true, + collapsed: false, + /** keepAlive路由的key,重新赋值可重置keepAlive */ + aliveKeys: {}, + isDark, + } + }, + actions: { + async reloadPage() { + $loadingBar.start() + this.reloadFlag = false + await nextTick() + this.reloadFlag = true + + setTimeout(() => { + document.documentElement.scrollTo({ left: 0, top: 0 }) + $loadingBar.finish() + }, 100) + }, + switchCollapsed() { + this.collapsed = !this.collapsed + }, + setCollapsed(collapsed) { + this.collapsed = collapsed + }, + setAliveKeys(key, val) { + this.aliveKeys[key] = val + }, + /** 设置暗黑模式 */ + setDark(isDark) { + this.isDark = isDark + }, + /** 切换/关闭 暗黑模式 */ + toggleDark() { + this.isDark = !this.isDark + }, + }, +}) + + diff --git a/web/src/store/modules/index.js b/web/src/store/modules/index.js new file mode 100644 index 0000000..edeeee1 --- /dev/null +++ b/web/src/store/modules/index.js @@ -0,0 +1,4 @@ +export * from './app' +export * from './permission' +export * from './tags' +export * from './user' diff --git a/web/src/store/modules/permission/index.js b/web/src/store/modules/permission/index.js new file mode 100644 index 0000000..9eaee50 --- /dev/null +++ b/web/src/store/modules/permission/index.js @@ -0,0 +1,70 @@ +import { defineStore } from 'pinia' +import { basicRoutes, vueModules } from '@/router/routes' +import Layout from '@/layout/index.vue' +import api from '@/api' + +// * 后端路由相关函数 +// 根据后端传来数据构建出前端路由 +function buildRoutes(routes = []) { + return routes.map((e) => ({ + name: e.name, + path: e.component !== 'Layout' ? '/' : e.path, // 处理目录是一级菜单的情况 + component: shallowRef(Layout), // ? 不使用 shallowRef 控制台会有 warning + isHidden: e.is_hidden, + redirect: e.redirect, + meta: { + title: e.name, + icon: e.icon, + order: e.order, + keepAlive: e.keepalive, + }, + children: e.children.map((e_child) => ({ + name: e_child.name, + path: e_child.path, // 父路径 + 当前菜单路径 + // ! 读取动态加载的路由模块 + component: vueModules[`/src/views${e_child.component}/index.vue`], + isHidden: e_child.is_hidden, + meta: { + title: e_child.name, + icon: e_child.icon, + order: e_child.order, + keepAlive: e_child.keepalive, + }, + })), + })) +} + +export const usePermissionStore = defineStore('permission', { + state() { + return { + accessRoutes: [], + accessApis: [], + } + }, + getters: { + routes() { + return basicRoutes.concat(this.accessRoutes) + }, + menus() { + return this.routes.filter((route) => route.name && !route.isHidden) + }, + apis() { + return this.accessApis + }, + }, + actions: { + async generateRoutes() { + const res = await api.getUserMenu() // 调用接口获取后端传来的菜单路由 + this.accessRoutes = buildRoutes(res.data) // 处理成前端路由格式 + return this.accessRoutes + }, + async getAccessApis() { + const res = await api.getUserApi() + this.accessApis = res.data + return this.accessApis + }, + resetPermission() { + this.$reset() + }, + }, +}) diff --git a/web/src/store/modules/tags/helpers.js b/web/src/store/modules/tags/helpers.js new file mode 100644 index 0000000..d7c94c9 --- /dev/null +++ b/web/src/store/modules/tags/helpers.js @@ -0,0 +1,6 @@ +import { sStorage } from '@/utils' + +export const activeTag = sStorage.get('activeTag') +export const tags = sStorage.get('tags') + +export const WITHOUT_TAG_PATHS = ['/404', '/login'] diff --git a/web/src/store/modules/tags/index.js b/web/src/store/modules/tags/index.js new file mode 100644 index 0000000..60c2264 --- /dev/null +++ b/web/src/store/modules/tags/index.js @@ -0,0 +1,70 @@ +import { defineStore } from 'pinia' +import { activeTag, tags, WITHOUT_TAG_PATHS } from './helpers' +import { router } from '@/router' +import { sStorage } from '@/utils' + +export const useTagsStore = defineStore('tag', { + state() { + return { + tags: tags || [], + activeTag: activeTag || '', + } + }, + getters: { + activeIndex() { + return this.tags.findIndex((item) => item.path === this.activeTag) + }, + }, + actions: { + setActiveTag(path) { + this.activeTag = path + sStorage.set('activeTag', path) + }, + setTags(tags) { + this.tags = tags + sStorage.set('tags', tags) + }, + addTag(tag = {}) { + this.setActiveTag(tag.path) + if (WITHOUT_TAG_PATHS.includes(tag.path) || this.tags.some((item) => item.path === tag.path)) + return + this.setTags([...this.tags, tag]) + }, + removeTag(path) { + if (path === this.activeTag) { + if (this.activeIndex > 0) { + router.push(this.tags[this.activeIndex - 1].path) + } else { + router.push(this.tags[this.activeIndex + 1].path) + } + } + this.setTags(this.tags.filter((tag) => tag.path !== path)) + }, + removeOther(curPath = this.activeTag) { + this.setTags(this.tags.filter((tag) => tag.path === curPath)) + if (curPath !== this.activeTag) { + router.push(this.tags[this.tags.length - 1].path) + } + }, + removeLeft(curPath) { + const curIndex = this.tags.findIndex((item) => item.path === curPath) + const filterTags = this.tags.filter((item, index) => index >= curIndex) + this.setTags(filterTags) + if (!filterTags.find((item) => item.path === this.activeTag)) { + router.push(filterTags[filterTags.length - 1].path) + } + }, + removeRight(curPath) { + const curIndex = this.tags.findIndex((item) => item.path === curPath) + const filterTags = this.tags.filter((item, index) => index <= curIndex) + this.setTags(filterTags) + if (!filterTags.find((item) => item.path === this.activeTag)) { + router.push(filterTags[filterTags.length - 1].path) + } + }, + resetTags() { + this.setTags([]) + this.setActiveTag('') + }, + }, +}) diff --git a/web/src/store/modules/user/index.js b/web/src/store/modules/user/index.js new file mode 100644 index 0000000..4b10489 --- /dev/null +++ b/web/src/store/modules/user/index.js @@ -0,0 +1,61 @@ +import { defineStore } from 'pinia' +import { resetRouter } from '@/router' +import { useTagsStore, usePermissionStore } from '@/store' +import { removeToken, toLogin } from '@/utils' +import api from '@/api' + +export const useUserStore = defineStore('user', { + state() { + return { + userInfo: {}, + } + }, + getters: { + userId() { + return this.userInfo?.id + }, + name() { + return this.userInfo?.username + }, + email() { + return this.userInfo?.email + }, + avatar() { + return this.userInfo?.avatar + }, + role() { + return this.userInfo?.roles || [] + }, + isSuperUser() { + return this.userInfo?.is_superuser + }, + isActive() { + return this.userInfo?.is_active + }, + }, + actions: { + async getUserInfo() { + try { + const res = await api.getUserInfo() + const { id, username, email, avatar, roles, is_superuser, is_active } = res.data + this.userInfo = { id, username, email, avatar, roles, is_superuser, is_active } + return Promise.resolve(res.data) + } catch (error) { + return Promise.reject(error) + } + }, + async logout() { + const { resetTags } = useTagsStore() + const { resetPermission } = usePermissionStore() + removeToken() + resetTags() + resetPermission() + resetRouter() + this.$reset() + toLogin() + }, + setUserInfo(userInfo = {}) { + this.userInfo = { ...this.userInfo, ...userInfo } + }, + }, +}) diff --git a/web/src/styles/global.scss b/web/src/styles/global.scss new file mode 100644 index 0000000..4cbdda3 --- /dev/null +++ b/web/src/styles/global.scss @@ -0,0 +1,74 @@ +html, +body { + width: 100%; + height: 100%; + overflow: hidden; +} + +html { + font-size: 4px; // * 1rem = 4px 方便unocss计算:在unocss中 1字体单位 = 0.25rem,相当于 1等份 = 1px +} + +body { + font-size: 16px; +} + +#app { + width: 100%; + height: 100%; +} + +/* transition fade-slide */ +.fade-slide-leave-active, +.fade-slide-enter-active { + transition: all 0.3s; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translateX(-30px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* 自定义滚动条样式 */ +.cus-scroll { + overflow: auto; + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } +} +.cus-scroll-x { + overflow-x: auto; + &::-webkit-scrollbar { + width: 0; + height: 8px; + } +} +.cus-scroll-y { + overflow-y: auto; + &::-webkit-scrollbar { + width: 8px; + height: 0; + } +} +.cus-scroll, +.cus-scroll-x, +.cus-scroll-y { + &::-webkit-scrollbar-thumb { + background-color: transparent; + border-radius: 4px; + } + &:hover { + &::-webkit-scrollbar-thumb { + background: #bfbfbf; + } + &::-webkit-scrollbar-thumb:hover { + background: var(--primary-color); + } + } +} diff --git a/web/src/styles/reset.css b/web/src/styles/reset.css new file mode 100644 index 0000000..5af4258 --- /dev/null +++ b/web/src/styles/reset.css @@ -0,0 +1,35 @@ +html { + box-sizing: border-box; +} + +*, +::before, +::after { + margin: 0; + padding: 0; + box-sizing: inherit; +} + +a { + text-decoration: none; + color: inherit; +} + +a:hover, +a:link, +a:visited, +a:active { + text-decoration: none; +} + +ol, +ul { + list-style: none; +} + +input, +textarea { + outline: none; + border: none; + resize: none; +} diff --git a/web/src/utils/auth/auth.js b/web/src/utils/auth/auth.js new file mode 100644 index 0000000..0106aa1 --- /dev/null +++ b/web/src/utils/auth/auth.js @@ -0,0 +1,17 @@ +import { router } from '@/router' + +export function toLogin() { + const currentRoute = unref(router.currentRoute) + const needRedirect = + !currentRoute.meta.requireAuth && !['/404', '/login'].includes(router.currentRoute.value.path) + router.replace({ + path: '/login', + query: needRedirect ? { ...currentRoute.query, redirect: currentRoute.path } : {}, + }) +} + +export function toFourZeroFour() { + router.replace({ + path: '/404', + }) +} diff --git a/web/src/utils/auth/index.js b/web/src/utils/auth/index.js new file mode 100644 index 0000000..8c6545d --- /dev/null +++ b/web/src/utils/auth/index.js @@ -0,0 +1,2 @@ +export * from './auth' +export * from './token' diff --git a/web/src/utils/auth/token.js b/web/src/utils/auth/token.js new file mode 100644 index 0000000..e8d03f4 --- /dev/null +++ b/web/src/utils/auth/token.js @@ -0,0 +1,31 @@ +import { lStorage } from '@/utils' + +const TOKEN_CODE = 'access_token' + +export function getToken() { + return lStorage.get(TOKEN_CODE) +} + +export function setToken(token) { + lStorage.set(TOKEN_CODE, token) +} + +export function removeToken() { + lStorage.remove(TOKEN_CODE) +} + +// export async function refreshAccessToken() { +// const tokenItem = lStorage.getItem(TOKEN_CODE) +// if (!tokenItem) { +// return +// } +// const { time } = tokenItem +// // token生成或者刷新后30分钟内不执行刷新 +// if (new Date().getTime() - time <= 1000 * 60 * 30) return +// try { +// const res = await api.refreshToken() +// setToken(res.data.token) +// } catch (error) { +// console.error(error) +// } +// } diff --git a/web/src/utils/common/common.js b/web/src/utils/common/common.js new file mode 100644 index 0000000..0978703 --- /dev/null +++ b/web/src/utils/common/common.js @@ -0,0 +1,76 @@ +import dayjs from 'dayjs' + +/** + * @desc 格式化时间 + * @param {(Object|string|number)} time + * @param {string} format + * @returns {string | null} + */ +export function formatDateTime(time = undefined, format = 'YYYY-MM-DD HH:mm:ss') { + return dayjs(time).format(format) +} + +export function formatDate(date = undefined, format = 'YYYY-MM-DD') { + return formatDateTime(date, format) +} + +/** + * @desc 函数节流 + * @param {Function} fn + * @param {Number} wait + * @returns {Function} + */ +export function throttle(fn, wait) { + var context, args + var previous = 0 + + return function () { + var now = +new Date() + context = this + args = arguments + if (now - previous > wait) { + fn.apply(context, args) + previous = now + } + } +} + +/** + * @desc 函数防抖 + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(method, wait, immediate) { + let timeout + return function (...args) { + let context = this + if (timeout) { + clearTimeout(timeout) + } + // 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为null + if (immediate) { + /** + * 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null + * 这样确保立即执行后wait毫秒内不会被再次触发 + */ + let callNow = !timeout + timeout = setTimeout(() => { + timeout = null + }, wait) + if (callNow) { + method.apply(context, args) + } + } else { + // 如果immediate为false,则函数wait毫秒后执行 + timeout = setTimeout(() => { + /** + * args是一个类数组对象,所以使用fn.apply + * 也可写作method.call(context, ...args) + */ + method.apply(context, args) + }, wait) + } + } +} diff --git a/web/src/utils/common/icon.js b/web/src/utils/common/icon.js new file mode 100644 index 0000000..b49cb9d --- /dev/null +++ b/web/src/utils/common/icon.js @@ -0,0 +1,12 @@ +import { h } from 'vue' +import { Icon } from '@iconify/vue' +import { NIcon } from 'naive-ui' +import SvgIcon from '@/components/icon/SvgIcon.vue' + +export function renderIcon(icon, props = { size: 12 }) { + return () => h(NIcon, props, { default: () => h(Icon, { icon }) }) +} + +export function renderCustomIcon(icon, props = { size: 12 }) { + return () => h(NIcon, props, { default: () => h(SvgIcon, { icon }) }) +} diff --git a/web/src/utils/common/index.js b/web/src/utils/common/index.js new file mode 100644 index 0000000..391f05e --- /dev/null +++ b/web/src/utils/common/index.js @@ -0,0 +1,5 @@ +export * from './common' +export * from './is' +export * from './icon' +export * from './naiveTools' +export * from './useResize' diff --git a/web/src/utils/common/is.js b/web/src/utils/common/is.js new file mode 100644 index 0000000..0981213 --- /dev/null +++ b/web/src/utils/common/is.js @@ -0,0 +1,119 @@ +const toString = Object.prototype.toString + +export function is(val, type) { + return toString.call(val) === `[object ${type}]` +} + +export function isDef(val) { + return typeof val !== 'undefined' +} + +export function isUndef(val) { + return typeof val === 'undefined' +} + +export function isNull(val) { + return val === null +} + +export function isWhitespace(val) { + return val === '' +} + +export function isObject(val) { + return !isNull(val) && is(val, 'Object') +} + +export function isArray(val) { + return val && Array.isArray(val) +} + +export function isString(val) { + return is(val, 'String') +} + +export function isNumber(val) { + return is(val, 'Number') +} + +export function isBoolean(val) { + return is(val, 'Boolean') +} + +export function isDate(val) { + return is(val, 'Date') +} + +export function isRegExp(val) { + return is(val, 'RegExp') +} + +export function isFunction(val) { + return typeof val === 'function' +} + +export function isPromise(val) { + return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch) +} + +export function isElement(val) { + return isObject(val) && !!val.tagName +} + +export function isWindow(val) { + return typeof window !== 'undefined' && isDef(window) && is(val, 'Window') +} + +export function isNullOrUndef(val) { + return isNull(val) || isUndef(val) +} + +export function isNullOrWhitespace(val) { + return isNullOrUndef(val) || isWhitespace(val) +} + +/** 空数组 | 空字符串 | 空对象 | 空Map | 空Set */ +export function isEmpty(val) { + if (isArray(val) || isString(val)) { + return val.length === 0 + } + + if (val instanceof Map || val instanceof Set) { + return val.size === 0 + } + + if (isObject(val)) { + return Object.keys(val).length === 0 + } + + return false +} + +/** + * * 类似mysql的IFNULL函数 + * * 第一个参数为null/undefined/'' 则返回第二个参数作为备用值,否则返回第一个参数 + * @param {Number|Boolean|String} val + * @param {Number|Boolean|String} def + * @returns + */ +export function ifNull(val, def = '') { + return isNullOrWhitespace(val) ? def : val +} + +export function isUrl(path) { + const reg = + /(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/ + return reg.test(path) +} + +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +export const isServer = typeof window === 'undefined' + +export const isClient = !isServer diff --git a/web/src/utils/common/naiveTools.js b/web/src/utils/common/naiveTools.js new file mode 100644 index 0000000..320acd3 --- /dev/null +++ b/web/src/utils/common/naiveTools.js @@ -0,0 +1,79 @@ +import { isNullOrUndef } from '@/utils' + +export function setupMessage(NMessage) { + let loadingMessage = null + class Message { + /** + * 规则: + * * loading message只显示一个,新的message会替换正在显示的loading message + * * loading message不会自动清除,除非被替换成非loading message,非loading message默认2秒后自动清除 + */ + + removeMessage(message = loadingMessage, duration = 2000) { + setTimeout(() => { + if (message) { + message.destroy() + message = null + } + }, duration) + } + + showMessage(type, content, option = {}) { + if (loadingMessage && loadingMessage.type === 'loading') { + // 如果存在则替换正在显示的loading message + loadingMessage.type = type + loadingMessage.content = content + + if (type !== 'loading') { + // 非loading message需设置自动清除 + this.removeMessage(loadingMessage, option.duration) + } + } else { + // 不存在正在显示的loading则新建一个message,如果新建的message是loading message则将message赋值存储下来 + let message = NMessage[type](content, option) + if (type === 'loading') { + loadingMessage = message + } + } + } + + loading(content) { + this.showMessage('loading', content, { duration: 0 }) + } + + success(content, option = {}) { + this.showMessage('success', content, option) + } + + error(content, option = {}) { + this.showMessage('error', content, option) + } + + info(content, option = {}) { + this.showMessage('info', content, option) + } + + warning(content, option = {}) { + this.showMessage('warning', content, option) + } + } + + return new Message() +} + +export function setupDialog(NDialog) { + NDialog.confirm = function (option = {}) { + const showIcon = !isNullOrUndef(option.title) + return NDialog[option.type || 'warning']({ + showIcon, + positiveText: '确定', + negativeText: '取消', + onPositiveClick: option.confirm, + onNegativeClick: option.cancel, + onMaskClick: option.cancel, + ...option, + }) + } + + return NDialog +} diff --git a/web/src/utils/common/useResize.js b/web/src/utils/common/useResize.js new file mode 100644 index 0000000..2d32280 --- /dev/null +++ b/web/src/utils/common/useResize.js @@ -0,0 +1,24 @@ +export function useResize(el, cb) { + const observer = new ResizeObserver((entries) => { + cb(entries[0].contentRect); + }); + observer.observe(el); + return observer; +} + +const install = (app) => { + let observer; + + app.directive('resize', { + mounted(el, binding) { + observer = useResize(el, binding.value); + }, + beforeUnmount() { + observer?.disconnect(); + }, + }); +}; + +useResize.install = install; + + \ No newline at end of file diff --git a/web/src/utils/http/helpers.js b/web/src/utils/http/helpers.js new file mode 100644 index 0000000..2e45398 --- /dev/null +++ b/web/src/utils/http/helpers.js @@ -0,0 +1,32 @@ +import { useUserStore } from '@/store' + +export function addBaseParams(params) { + if (!params.userId) { + params.userId = useUserStore().userId + } +} + +export function resolveResError(code, message) { + switch (code) { + case 400: + message = message ?? '请求参数错误' + break + case 401: + message = message ?? '登录已过期' + useUserStore().logout() + break + case 403: + message = message ?? '没有权限' + break + case 404: + message = message ?? '资源或接口不存在' + break + case 500: + message = message ?? '服务器异常' + break + default: + message = message ?? `【${code}】: 未知异常!` + break + } + return message +} diff --git a/web/src/utils/http/index.js b/web/src/utils/http/index.js new file mode 100644 index 0000000..f30dd1f --- /dev/null +++ b/web/src/utils/http/index.js @@ -0,0 +1,23 @@ +import axios from 'axios' +import { resReject, resResolve, reqReject, reqResolve } from './interceptors' +import { getProxyConfig } from '../../../settings' + +const proxyConfig = getProxyConfig(import.meta.env.VITE_PROXY_TYPE) +const isUseProxy = false + +export function createAxios(options = {}) { + const defaultOptions = { + timeout: 12000, + } + const service = axios.create({ + ...defaultOptions, + ...options, + }) + service.interceptors.request.use(reqResolve, reqReject) + service.interceptors.response.use(resResolve, resReject) + return service +} + +export const request = createAxios({ + baseURL: isUseProxy ? proxyConfig.prefix : proxyConfig.target, +}) diff --git a/web/src/utils/http/interceptors.js b/web/src/utils/http/interceptors.js new file mode 100644 index 0000000..d6efd67 --- /dev/null +++ b/web/src/utils/http/interceptors.js @@ -0,0 +1,81 @@ +import { getToken } from '@/utils' +import { resolveResError } from './helpers' + +export function reqResolve(config) { + // 处理不需要token的请求 + if (config.noNeedToken) { + return config + } + + const token = getToken() + if (token) { + config.headers.token = config.headers.token || token + } + + return config +} + +export function reqReject(error) { + return Promise.reject(error) +} + +export function resResolve(response) { + const { data, status, statusText } = response + if (data?.code !== 200) { + const code = data?.code ?? status + /** 根据code处理对应的操作,并返回处理后的message */ + const message = resolveResError(code, data?.msg ?? statusText) + window.$message?.error(message, { keepAliveOnHover: true }) + return Promise.reject({ code, message, error: data || response }) + } + return Promise.resolve(data) +} + +let isDialogShow = false //解决多个请求弹出多个dialog + +export async function resReject(error) { + if (!error || !error.response) { + const code = error?.code + /** 根据code处理对应的操作,并返回处理后的message */ + const message = resolveResError(code, error.message) + window.$message?.error(message) + return Promise.reject({ code, message, error }) + } + const { data, status, config } = error.response + console.log('respon', data, config) + console.log('status', status) + console.log('config', config.noNeedTip) + if (data?.code === 401) { + if (isDialogShow) return + try { + isDialogShow = true + await new Promise((resolve, reject) => { + $dialog.confirm({ + title: '系统提示', + type: 'warning', + content: '账号登录已过期,您可以继续留在该页面,或者重新登录', + positiveText: '重新登录', + negativeText: '取消', + confirm() { + isDialogShow = false + location.reload() + resolve() // 解决 Promise 以继续执行 + }, + cancel() { + isDialogShow = false + reject(new Error('对话框已取消')) // 拒绝 Promise 以停止执行 + }, + }) + }) + } catch (error) { + isDialogShow = false + console.log('resReject error', error) + return + } + } + // 后端返回的response数据 + const code = data?.code ?? status + const message = resolveResError(code, data?.msg ?? error.message) + window.$message?.error(message, { keepAliveOnHover: true }) + return Promise.reject({ code, message, error: error.response?.data || error.response }) +} diff --git a/web/src/utils/index.js b/web/src/utils/index.js new file mode 100644 index 0000000..8ca6753 --- /dev/null +++ b/web/src/utils/index.js @@ -0,0 +1,4 @@ +export * from './common' +export * from './storage' +export * from './http' +export * from './auth' diff --git a/web/src/utils/storage/index.js b/web/src/utils/storage/index.js new file mode 100644 index 0000000..a36f1a0 --- /dev/null +++ b/web/src/utils/storage/index.js @@ -0,0 +1,21 @@ +import { createStorage } from './storage' + +const prefixKey = '' + +export const createLocalStorage = function (option = {}) { + return createStorage({ + prefixKey: option.prefixKey || '', + storage: localStorage, + }) +} + +export const createSessionStorage = function (option = {}) { + return createStorage({ + prefixKey: option.prefixKey || '', + storage: sessionStorage, + }) +} + +export const lStorage = createLocalStorage({ prefixKey }) + +export const sStorage = createSessionStorage({ prefixKey }) diff --git a/web/src/utils/storage/storage.js b/web/src/utils/storage/storage.js new file mode 100644 index 0000000..c59f971 --- /dev/null +++ b/web/src/utils/storage/storage.js @@ -0,0 +1,55 @@ +import { isNullOrUndef } from '@/utils' + +class Storage { + constructor(option) { + this.storage = option.storage + this.prefixKey = option.prefixKey + } + + getKey(key) { + return `${this.prefixKey}${key}`.toUpperCase() + } + + set(key, value, expire) { + const stringData = JSON.stringify({ + value, + time: Date.now(), + expire: !isNullOrUndef(expire) ? new Date().getTime() + expire * 1000 : null, + }) + this.storage.setItem(this.getKey(key), stringData) + } + + get(key) { + const { value } = this.getItem(key, {}) + return value + } + + getItem(key, def = null) { + const val = this.storage.getItem(this.getKey(key)) + if (!val) return def + try { + const data = JSON.parse(val) + const { value, time, expire } = data + if (isNullOrUndef(expire) || expire > new Date().getTime()) { + return { value, time } + } + this.remove(key) + return def + } catch (error) { + this.remove(key) + return def + } + } + + remove(key) { + this.storage.removeItem(this.getKey(key)) + } + + clear() { + this.storage.clear() + } +} + +export function createStorage({ prefixKey = '', storage = sessionStorage }) { + return new Storage({ prefixKey, storage }) +} diff --git a/web/src/views/error-page/403.vue b/web/src/views/error-page/403.vue new file mode 100644 index 0000000..531aa07 --- /dev/null +++ b/web/src/views/error-page/403.vue @@ -0,0 +1,16 @@ + + + diff --git a/web/src/views/error-page/404.vue b/web/src/views/error-page/404.vue new file mode 100644 index 0000000..631cb02 --- /dev/null +++ b/web/src/views/error-page/404.vue @@ -0,0 +1,16 @@ + + + diff --git a/web/src/views/error-page/route.js b/web/src/views/error-page/route.js new file mode 100644 index 0000000..8ea7655 --- /dev/null +++ b/web/src/views/error-page/route.js @@ -0,0 +1,24 @@ +const Layout = () => import('@/layout/index.vue') + +export default { + name: 'ErrorPage', + path: '/error-page', + component: Layout, + redirect: '/error-page/404', + meta: { + title: '错误页', + icon: 'mdi:alert-circle-outline', + order: 99, + }, + children: [ + { + name: 'ERROR-404', + path: '404', + component: () => import('./404.vue'), + meta: { + title: '404', + icon: 'tabler:error-404', + }, + }, + ], +} diff --git a/web/src/views/login/index.vue b/web/src/views/login/index.vue new file mode 100644 index 0000000..518ca31 --- /dev/null +++ b/web/src/views/login/index.vue @@ -0,0 +1,114 @@ + + + diff --git a/web/src/views/profile/index.vue b/web/src/views/profile/index.vue new file mode 100644 index 0000000..2bd51e8 --- /dev/null +++ b/web/src/views/profile/index.vue @@ -0,0 +1,189 @@ + + + diff --git a/web/src/views/system/api/index.vue b/web/src/views/system/api/index.vue new file mode 100644 index 0000000..f702248 --- /dev/null +++ b/web/src/views/system/api/index.vue @@ -0,0 +1,265 @@ + + + diff --git a/web/src/views/system/menu/index.vue b/web/src/views/system/menu/index.vue new file mode 100644 index 0000000..2d31c95 --- /dev/null +++ b/web/src/views/system/menu/index.vue @@ -0,0 +1,333 @@ + + + diff --git a/web/src/views/system/role/index.vue b/web/src/views/system/role/index.vue new file mode 100644 index 0000000..5af3274 --- /dev/null +++ b/web/src/views/system/role/index.vue @@ -0,0 +1,359 @@ + + + diff --git a/web/src/views/system/route.js b/web/src/views/system/route.js new file mode 100644 index 0000000..f58d3d2 --- /dev/null +++ b/web/src/views/system/route.js @@ -0,0 +1,37 @@ +// const Layout = () => import('@/layout/index.vue') + +// export default { +// name: 'System', +// path: '/system', +// component: Layout, +// redirect: '/system/user', +// meta: { +// title: '系统管理', +// icon: 'ph:user-list-bold', +// order: 5, +// // role: ['admin'], +// // requireAuth: true, +// }, +// children: [ +// { +// name: 'User', +// path: 'user', +// component: () => import('./user/index.vue'), +// meta: { +// title: '用户列表', +// icon: 'mdi:account', +// keepAlive: true, +// }, +// }, +// { +// name: 'Menu', +// path: 'menu', +// component: () => import('./menu/index.vue'), +// meta: { +// title: '菜单列表', +// icon: 'ic:twotone-menu-book', +// keepAlive: true, +// }, +// }, +// ], +// } diff --git a/web/src/views/system/user/index.vue b/web/src/views/system/user/index.vue new file mode 100644 index 0000000..6a58bb5 --- /dev/null +++ b/web/src/views/system/user/index.vue @@ -0,0 +1,408 @@ + + + diff --git a/web/src/views/workbench/index.vue b/web/src/views/workbench/index.vue new file mode 100644 index 0000000..3ed24b0 --- /dev/null +++ b/web/src/views/workbench/index.vue @@ -0,0 +1,67 @@ + + + diff --git a/web/src/views/workbench/route.js b/web/src/views/workbench/route.js new file mode 100644 index 0000000..2b4771a --- /dev/null +++ b/web/src/views/workbench/route.js @@ -0,0 +1,20 @@ +const Layout = () => import('@/layout/index.vue') + +export default { + name: 'Dashboard', + path: '/', + component: Layout, + redirect: '/workbench', + children: [ + { + name: 'Workbench', + path: 'workbench', + component: () => import('./index.vue'), + meta: { + title: '工作台', + icon: 'mdi:home', + order: 0, + }, + }, + ], +} diff --git a/web/stats.html b/web/stats.html new file mode 100644 index 0000000..bae5aca --- /dev/null +++ b/web/stats.html @@ -0,0 +1,4838 @@ + + + + + + + + Rollup Visualizer + + + +
+ + + + + diff --git a/web/unocss.config.js b/web/unocss.config.js new file mode 100644 index 0000000..8d0b7b4 --- /dev/null +++ b/web/unocss.config.js @@ -0,0 +1,60 @@ +import { defineConfig, presetAttributify, presetUno } from 'unocss' + +export default defineConfig({ + exclude: [ + 'node_modules', + '.git', + '.github', + '.husky', + '.vscode', + 'build', + 'dist', + 'mock', + 'public', + './stats.html', + ], + presets: [presetUno(), presetAttributify()], + shortcuts: [ + ['wh-full', 'w-full h-full'], + ['f-c-c', 'flex justify-center items-center'], + ['flex-col', 'flex flex-col'], + ['absolute-lt', 'absolute left-0 top-0'], + ['absolute-lb', 'absolute left-0 bottom-0'], + ['absolute-rt', 'absolute right-0 top-0'], + ['absolute-rb', 'absolute right-0 bottom-0'], + ['absolute-center', 'absolute-lt f-c-c wh-full'], + ['text-ellipsis', 'truncate'], + ], + rules: [ + [/^bc-(.+)$/, ([, color]) => ({ 'border-color': `#${color}` })], + [ + 'card-shadow', + { 'box-shadow': '0 1px 2px -2px #00000029, 0 3px 6px #0000001f, 0 5px 12px 4px #00000017' }, + ], + ], + theme: { + colors: { + primary: 'var(--primary-color)', + primary_hover: 'var(--primary-color-hover)', + primary_pressed: 'var(--primary-color-pressed)', + primary_active: 'var(--primary-color-active)', + info: 'var(--info-color)', + info_hover: 'var(--info-color-hover)', + info_pressed: 'var(--info-color-pressed)', + info_active: 'var(--info-color-active)', + success: 'var(--success-color)', + success_hover: 'var(--success-color-hover)', + success_pressed: 'var(--success-color-pressed)', + success_active: 'var(--success-color-active)', + warning: 'var(--warning-color)', + warning_hover: 'var(--warning-color-hover)', + warning_pressed: 'var(--warning-color-pressed)', + warning_active: 'var(--warning-color-active)', + error: 'var(--error-color)', + error_hover: 'var(--error-color-hover)', + error_pressed: 'var(--error-color-pressed)', + error_active: 'var(--error-color-active)', + dark: '#18181c', + }, + }, +}) diff --git a/web/vite.config.js b/web/vite.config.js new file mode 100644 index 0000000..0ba8ec1 --- /dev/null +++ b/web/vite.config.js @@ -0,0 +1,38 @@ +import { defineConfig, loadEnv } from 'vite' + +import { convertEnv, getSrcPath, getRootPath } from './build/utils' +import { createViteProxy, viteDefine } from './build/config' +import { createVitePlugins } from './build/plugin' + +export default defineConfig(({ command, mode }) => { + const srcPath = getSrcPath() + const rootPath = getRootPath() + const isBuild = command === 'build' + + const env = loadEnv(mode, process.cwd()) + const viteEnv = convertEnv(env) + const { VITE_PORT, VITE_PUBLIC_PATH, VITE_USE_PROXY, VITE_PROXY_TYPE } = viteEnv + return { + base: VITE_PUBLIC_PATH || '/', + resolve: { + alias: { + '~': rootPath, + '@': srcPath, + }, + }, + define: viteDefine, + plugins: createVitePlugins(viteEnv, isBuild), + server: { + host: '0.0.0.0', + port: VITE_PORT, + open: true, + proxy: createViteProxy(VITE_USE_PROXY, VITE_PROXY_TYPE), + }, + build: { + target: 'es2015', + outDir: 'dist', + reportCompressedSize: false, // 启用/禁用 gzip 压缩大小报告 + chunkSizeWarningLimit: 1024, // chunk 大小警告的限制(单位kb) + }, + } +})