31 Commits
Author SHA1 Message Date
zhengxinonly 65e41ac9f1 style: 修改 fetch 请求为 ajax,统一请求风格 2024-04-21 13:01:48 +08:00
zhengxinonly b5057af31e fix(templates): 修复新增内容时还存留缓存数据 2024-01-31 23:51:10 +08:00
zhengxinonly e5a60da215 style: 修复错别字 2024-01-31 23:35:18 +08:00
zhengxinonly c6598df116 style: 修改返回信息格式 2024-01-31 22:52:41 +08:00
zhengxinonly 801eed010f refactor(all): 完成项目的前后端逻辑分离,静态页面可以单独部署 2024-01-28 17:43:25 +08:00
zhengxinonly 2b296c1c64 fix(templates): 修复引用样式错误 2023-12-23 00:51:31 +08:00
zhengxinonly b5625ecf1f refactor(templates): 移除 jinja2 includes 使用 2023-12-22 23:01:45 +08:00
zhengxinonly 7203bcfecc fix(passport): 修复初始化数据错误密码也可以登录问题 2023-12-22 22:46:54 +08:00
zhengxinonly 2f4f1faf82 fix: 完善新增权限操作 2023-12-05 16:50:21 +08:00
zhengxinonly 2ffddda02b style: pear admin layui 更新到 4.0 正式版 2023-11-26 00:51:02 +08:00
zhengxinonly 3086c1a1fa feature: 动态菜单生成 2023-11-07 20:53:20 +08:00
zhengxinonly eee1bda795 docs: 添加使用说明 2023-10-31 21:53:30 +08:00
zhengxinonly 7c07b6c94d feature: 用户角色授权 2023-10-31 21:45:10 +08:00
zhengxinonly fcf2ad232e feature: update flask to 3.0.0 2023-10-31 20:52:32 +08:00
zhengxinonly 537e21728b feature: 完成角色授权 2023-10-31 20:23:09 +08:00
zhengxinonly 6a8fb955f5 feature: 错误页面返回 2023-10-29 17:04:05 +08:00
zhengxinonly 261ac32825 feature: 用户管理 2023-10-29 15:19:14 +08:00
zhengxinonly 6daad895b4 feature: 部门增删改查 2023-10-27 20:51:10 +08:00
zhengxinonly 52e0737311 feature: 角色增删改 2023-10-14 20:20:46 +08:00
zhengxinonly 075b624522 feature: 权限列表-3 2023-10-14 19:17:01 +08:00
zhengxinonly 607cbc94ba feature: 权限列表-2 2023-10-10 22:55:15 +08:00
zhengxinonly f95ae54fd5 feature: 权限列表-01 2023-10-03 01:04:15 +08:00
zhengxinonly 20b611ac29 feature: 动态生成菜单栏 2023-10-02 00:57:20 +08:00
zhengxinonly 69a4955ce2 feature: 登录&权限拦截 2023-10-01 23:42:59 +08:00
zhengxinonly 8765b2c344 feature: 初始化 jwt 插件 2023-09-30 16:19:07 +08:00
zhengxinonly bdd766d2b2 feature: 初始化接口 api 2023-09-30 16:12:43 +08:00
zhengxinonly ba5e2cf67b feature: 添加静态文件 2023-09-29 14:57:03 +08:00
zhengxinonly 2c3c79b21f test: 添加测试数据 2023-09-29 01:48:46 +08:00
zhengxinonly 9bf89e5919 feature: 新增数据模型 2023-09-27 17:49:13 +08:00
zhengxinonly 4db050c93d feature: 配置文件&配置插件 2023-09-27 17:10:45 +08:00
zhengxinonly 49822daada feature: 项目初始化 2023-09-26 22:47:21 +08:00
153 changed files with 158721 additions and 1550 deletions
+69
View File
@@ -0,0 +1,69 @@
[tool.commitizen]
name = "cz_customize"
tag_format = "$version"
version_scheme = "pep440"
version_provider = "poetry"
update_changelog_on_bump = true
major_version_zero = true
[tool.commitizen.customize]
message_template = "{{change_type}}{% if scope %}({{scope}}){% endif %}:{% if subject %} {{subject}}{% endif %} {% if footer %} {{footer}}{% endif %}"
example = "feature: this feature enable customize through config file"
schema = "<type>: <body>"
schema_pattern = "(feature|fix|docs|style|refactor|perf|test|revert|build).*?:(\\s.*)"
bump_pattern = "^(break|new|fix|hotfix)"
bump_map = { "break" = "MAJOR", "new" = "MINOR", "fix" = "PATCH", "hotfix" = "PATCH" }
change_type_order = ["BREAKING CHANGE", "feat", "fix", "refactor", "perf"]
info_path = "cz_customize_info.txt"
info = """
This is customized info
"""
commit_parser = "^(?P<change_type>feature|fix|docs|style|refactor|perf|test|revert|build).*?:\\s(?P<message>.*)?"
changelog_pattern = "^(feature|fix|docs|style|refactor|perf|test|revert|build)?(!)?"
change_type_map = { "feature" = "Feat", "bug fix" = "Fix" }
# 自定义提交指令
[[tool.commitizen.customize.questions]]
type = "list"
name = "change_type"
choices = [
{ value = "feature", name = "feature: 新功能" },
{ value = "fix", name = "fix: 修复 bug" },
{ value = "docs", name = "docs: 文档的修改" },
{ value = "style", name = "style: 格式化变动,不影响代码逻辑" },
{ value = "refactor", name = "refactor: 重构,即不是新增功能,也不是修改 bug 的代码变动" },
{ value = "perf", name = "perf: 性能优化" },
{ value = "test", name = "test: 增加测试" },
{ value = "revert", name = "revert: 回退" },
{ value = "build", name = "build: 打包" },
]
# choices = ["feature", "fix"] # short version
message = "请选择提交类型:"
# 修改内容的范围
[[tool.commitizen.customize.questions]]
type = "input"
name = "scope"
message = "请输入修改内容的范围(可选):"
# 自己的提交内容
[[tool.commitizen.customize.questions]]
type = "input"
name = "subject"
message = "请输入简要描述(必填):"
# 自己的提交内容
[[tool.commitizen.customize.questions]]
type = "input"
name = "body"
message = "请输入详细描述(可选):"
[[tool.commitizen.customize.questions]]
type = "input"
name = "footer"
message = "请输入需要关闭的 issue(可选):"
+4 -14
View File
@@ -1,14 +1,4 @@
# flask配置
FLASK_APP="entrance:create_app()"
FLASK_ENV=development
FLASK_DEBUG=1
FLASK_RUN_HOST = 127.0.0.1
FLASK_RUN_PORT = 5000
# MySql配置信息
MYSQL_HOST=127.0.0.1
# MYSQL_HOST=dbserver
MYSQL_PORT=3306
MYSQL_DATABASE=next
MYSQL_USERNAME=root
MYSQL_PASSWORD=root
FLASK_DEBUG=True # 开启调试模式
FLASK_RUN_PORT=5000 # 设置运行的端口
FLASK_RUN_HOST=0.0.0.0 # 设置监听的 ip
FLASK_APP="pear_admin:create_app()" # 运行程序
+3 -116
View File
@@ -1,118 +1,5 @@
# Byte-compiled / optimized / DLL files
__pycache__/
.idea/
.venv/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
#ide
.idea/
migrations/
+28
View File
@@ -0,0 +1,28 @@
repos:
- repo: https://github.com/python/black
rev: 23.9.1
hooks:
- id: black
language_version: python3.10
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: [ "--profile", "black", "--filter-files" ]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v3.0.3"
hooks:
- id: prettier
exclude: >
(?x)^(
.*.yaml|
package-lock.json|
yarn.lock|
^.+\.min\.(js|css)$
)$
stages: [ commit ]
- repo: https://github.com/commitizen-tools/commitizen
rev: 3.5.3
hooks:
- id: commitizen
stages: [ commit-msg ]
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 mkg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+41 -42
View File
@@ -1,56 +1,55 @@
<div align="center">
<br/>
<br/>
<h1 align="center">
Pear Admin Flask
</h1>
<h4 align="center">
开 箱 即 用 的 Flask 快 速 开 发 平 台
</h4>
## 本地启动
[预 览](http://flask.pearadmin.com) | [官 网](http://www.pearadmin.com/) | [群聊](https://jq.qq.com/?_wv=1027&k=5OdSmve) | [社区](http://forum.pearadmin.com/)
环境要求:python 3.10 以上,安装了 poetry
<p align="center">
<a href="#">
<img src="https://img.shields.io/badge/pear%20admin%20flask-1.0.0-green" alt="Pear Admin Layui Version">
</a>
<a href="#">
<img src="https://img.shields.io/badge/Python-3.6+-green.svg" alt="Python Version">
</a>
<a href="#">
<img src="https://img.shields.io/badge/Mysql-5.3.2+-green.svg" alt="Mysql Version">
</a>
</p>
</div>
<div align="center">
<img width="92%" style="border-radius:10px;margin-top:20px;margin-bottom:20px;box-shadow: 2px 0 6px gray;" src="https://images.gitee.com/uploads/images/2020/1019/104805_042b888c_4835367.png" />
</div>
#### 项目简介
>Pear Admin Flask 新版本开发中
#### 安装
初始化环境
```shell
pip install -r require/common.txt
poetry install
```
#### 目录结构
初始化数据库
pear-admin-flask
```shell
flask init
```
common -> 公共
启动
entrance -> 核心
```shell
flask run
```
logs -> 日志
## 贡献指南
modules -> 蓝图,视图,模型,服务
如果想参与项目的贡献,提交代码之前需要启用 pre-commit、commitizen 对代码进行校验,运行以下指令即可。
require -> 安装需要的文件
初始化 pre-commit
.flaskenv -> 配置文件
```shell
pre-commit install
```
app.py -> 启动模块(使用flask run命令的话可以删除)
检查代码是否符合规范
```shell
git add .
```
```shell
pre-commit run --all-files
```
初始化 commitizen
```shell
pre-commit install -t commit-msg
```
使用
```shell
cz commit
```
代替 `git commit` 进行提交
-6
View File
@@ -1,6 +0,0 @@
from entrance import create_app
app = create_app()
if __name__ == '__main__':
app.run()
View File
-19
View File
@@ -1,19 +0,0 @@
from flask import current_app
from cachelib.file import FileSystemCache
from cachelib.memcached import MemcachedCache
from cachelib.redis import RedisCache
from cachelib.simple import SimpleCache
from cachelib.uwsgi import UWSGICache
cacher: dict = {
'filesystem': FileSystemCache,
'memcached': MemcachedCache,
'redis': RedisCache,
'simple': SimpleCache,
'uwsgi': UWSGICache
}
cache = RedisCache()
def get_cache():
return cache.get(current_app.config.get("CACHE_TYPE") or "filesystem")()
-18
View File
@@ -1,18 +0,0 @@
from celery import Celery
from entrance.config import celery as config
from entrance.config.celery import broker_url
celery = Celery(__name__, broker=broker_url)
def register_celery(app):
celery.config_from_object(config)
class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
with app.app_context():
return self.run(*args, **kwargs)
celery.Task = ContextTask
-7
View File
@@ -1,7 +0,0 @@
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
-55
View File
@@ -1,55 +0,0 @@
from logging.config import dictConfig
def config_logger():
dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout",
},
"info_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "simple",
"filename": "logs/info.log",
"maxBytes": 10485760,
"backupCount": 50,
"encoding": "utf8",
},
"error_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "ERROR",
"formatter": "simple",
"filename": "logs/errors.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8",
},
"debug_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "simple",
"filename": "logs/debug.log",
"maxBytes": 10485760,
"backupCount": 50,
"encoding": "utf8",
},
},
"loggers": {
"my_module": {"level": "ERROR", "handlers": ["console"], "propagate": "no"}
},
"root": {
"level": "DEBUG",
"handlers": ["error_file_handler", "debug_file_handler"],
},
}
)
-3
View File
@@ -1,3 +0,0 @@
from flask_mail import Mail
mail = Mail()
-7
View File
@@ -1,7 +0,0 @@
from flask_migrate import Migrate
from common.extend.orm import db
from entrance.models import model_lists
migrate = Migrate(db=db, directory='entrance/migrations')
-32
View File
@@ -1,32 +0,0 @@
import datetime
from typing import Tuple
from flask import request
from flask_sqlalchemy import SQLAlchemy, BaseQuery
class Query(BaseQuery):
def all_json(self) -> list:
return [dict(i) for i in self.all()]
def soft_delete(self):
return self.update({"delete_at": datetime.datetime.now()})
def logic_all(self):
return self.filter_by(delete_at=None).all()
def layui_paginate(self):
return self.paginate(page=request.args.get('page', type=int),
per_page=request.args.get('limit', type=int),
error_out=False)
def layui_paginate_json(self) -> Tuple[list, int]:
p = self.paginate(page=request.args.get('page', type=int),
per_page=request.args.get('limit', type=int),
error_out=False)
return [dict(i) for i in p.items], p.total
db = SQLAlchemy(query_class=Query)
-89
View File
@@ -1,89 +0,0 @@
import datetime
from entrance.extensions import db
from sqlalchemy.orm import relationship
from sqlalchemy import ForeignKey
from sqlalchemy.orm import backref
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
class BaseModel(db.Model):
__abstract__ = True
# insert and update
def save(self):
db.session.add(self)
db.session.commit()
# delete
def delete(self):
db.session.delete(self)
db.session.commit()
class CommonModel(BaseModel):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='用户ID')
create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
update_time = db.Column(db.DateTime, onupdate=datetime.datetime.now, comment='更新时间')
class CurdModel(BaseModel):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='用户ID')
create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
update_time = db.Column(db.DateTime, onupdate=datetime.datetime.now, comment='更新时间')
delete_at = db.Column(db.DateTime, comment='删除时间')
Column = db.Column
Table = db.Table
relationship = relationship
ForeignKey = ForeignKey
backref = backref
TypeDecorator = db.TypeDecorator
INT = db.INT
CHAR = db.CHAR
VARCHAR = db.VARCHAR
NCHAR = db.NCHAR
NVARCHAR = db.NVARCHAR
TEXT = db.TEXT
Text = db.Text
FLOAT = db.FLOAT
NUMERIC = db.NUMERIC
REAL = db.REAL
DECIMAL = db.DECIMAL
TIMESTAMP = db.TIMESTAMP
DATETIME = db.DATETIME
CLOB = db.CLOB
BLOB = db.BLOB
BINARY = db.BINARY
VARBINARY = db.VARBINARY
BOOLEAN = db.BOOLEAN
BIGINT = db.BIGINT
SMALLINT = db.SMALLINT
INTEGER = db.INTEGER
DATE = db.DATE
TIME = db.TIME
TupleType = db.TupleType
String = db.String
Integer = db.Integer
SmallInteger = db.SmallInteger
BigInteger = db.BigInteger
Numeric = db.Numeric
Float = db.Float
DateTime = db.DateTime
Date = db.Date
Time = db.Time
LargeBinary = db.LargeBinary
Boolean = db.Boolean
Unicode = db.Unicode
UnicodeText = db.UnicodeText
PickleType = db.PickleType
Interval = db.Interval
Enum = db.Enum
ARRAY = db.ARRAY
JSON = db.JSON
SQLAlchemyAutoSchema = SQLAlchemyAutoSchema
-121
View File
@@ -1,121 +0,0 @@
import random, math
from PIL import Image, ImageDraw, ImageFont, ImageFilter
class vieCode:
__fontSize = 20 # 字体大小
__width = 120 # 画布宽度
__heigth = 45 # 画布高度
__length = 4 # 验证码长度
__draw = None # 画布
__img = None # 图片资源
__code = None # 验证码字符
__str = None # 自定义验证码字符集
__inCurve = True # 是否画干扰线
__inNoise = True # 是否画干扰点
__type = 2 # 验证码类型 1、纯字母 2、数字字母混合
__fontPatn = 'applications/common/utils/fonts/2.ttf' # 字体
def GetCodeImage(self, size=80, length=4):
'''获取验证码图片
@param int size 验证码大小
@param int length 验证码长度
'''
# 准备基础数据
self.__length = length
self.__fontSize = size
self.__width = self.__fontSize * self.__length
self.__heigth = int(self.__fontSize * 1.5)
# 生成验证码图片
self.__createCode()
self.__createImage()
self.__createNoise()
self.__printString()
self.__cerateFilter()
return self.__img, self.__code
def __cerateFilter(self):
'''模糊处理'''
self.__img = self.__img.filter(ImageFilter.BLUR)
filter = ImageFilter.ModeFilter(8)
self.__img = self.__img.filter(filter)
def __createCode(self):
'''创建验证码字符'''
# 是否自定义字符集合
if not self.__str:
# 源文本
number = "3456789"
srcLetter = "qwertyuipasdfghjkzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
srcUpper = srcLetter.upper()
if self.__type == 1:
self.__str = number
else:
self.__str = srcLetter + srcUpper + number
# 构造验证码
self.__code = random.sample(self.__str, self.__length)
def __createImage(self):
'''创建画布'''
bgColor = (random.randint(200, 255), random.randint(200, 255), random.randint(200, 255))
self.__img = Image.new('RGB', (self.__width, self.__heigth), bgColor)
self.__draw = ImageDraw.Draw(self.__img)
def __createNoise(self):
'''画干扰点'''
if not self.__inNoise:
return
font = ImageFont.truetype(self.__fontPatn, int(self.__fontSize / 1.5))
for i in range(5):
# 杂点颜色
noiseColor = (random.randint(150, 200), random.randint(150, 200), random.randint(150, 200))
putStr = random.sample(self.__str, 2)
for j in range(2):
# 绘杂点
size = (random.randint(-10, self.__width), random.randint(-10, self.__heigth))
self.__draw.text(size, putStr[j], font=font, fill=noiseColor)
pass
def __createCurve(self):
'''画干扰线'''
if not self.__inCurve:
return
x = y = 0
# 计算曲线系数
a = random.uniform(1, self.__heigth / 2)
b = random.uniform(-self.__width / 4, self.__heigth / 4)
f = random.uniform(-self.__heigth / 4, self.__heigth / 4)
t = random.uniform(self.__heigth, self.__width * 2)
xend = random.randint(self.__width / 2, self.__width * 2)
w = (2 * math.pi) / t
# 画曲线
color = (random.randint(30, 150), random.randint(30, 150), random.randint(30, 150))
for x in range(xend):
if w != 0:
for k in range(int(self.__heigth / 10)):
y = a * math.sin(w * x + f) + b + self.__heigth / 2
i = int(self.__fontSize / 5)
while i > 0:
px = x + i
py = y + i + k
self.__draw.point((px, py), color)
i -= i
def __printString(self):
'''打印验证码字符串'''
font = ImageFont.truetype(self.__fontPatn, self.__fontSize)
x = 0
# 打印字符到画板
for i in range(self.__length):
# 设置字体随机颜色
color = (random.randint(30, 150), random.randint(30, 150), random.randint(30, 150))
# 计算座标
x = random.uniform(self.__fontSize * i * 0.95, self.__fontSize * i * 1.1)
y = self.__fontSize * random.uniform(0.3, 0.5)
# 打印字符
self.__draw.text((x, y), self.__code[i], font=font, fill=color)
Binary file not shown.
-13
View File
@@ -1,13 +0,0 @@
from flask_mail import Message
# send_mail(subject='title', recipients=['123@qq.com'], content='body')
from common.extend.mail import mail
def send_mail(subject, recipients, content):
try:
message = Message(subject=subject, recipients=recipients, body=content)
mail.send(message)
except Exception as e:
print('邮箱发送出错了')
raise
-14
View File
@@ -1,14 +0,0 @@
from file_system_uploader import FilesystemUploader
from qiniu_uploader import QiniuUploader
from upyun_uploader import UpyunUploader
from flask import current_app
uploader: dict = {
"filesystem": FilesystemUploader,
"qiniu": QiniuUploader,
'upyun': UpyunUploader
}
def get_uploader():
return uploader.get(current_app.config.get("UPLOAD_PROVIDER") or "filesystem")()
-19
View File
@@ -1,19 +0,0 @@
class BaseUploader(object):
def __init__(self):
raise NotImplementedError
def store(self, fileobj, filename):
raise NotImplementedError
def upload(self, file_obj, filename):
raise NotImplementedError
def download(self, filename):
raise NotImplementedError
def delete(self, filename):
raise NotImplementedError
def sync(self):
raise NotImplementedError
@@ -1,62 +0,0 @@
import os
import posixpath
from pathlib import PurePath
from shutil import copyfileobj, rmtree
from PIL.features import codecs
from flask import current_app, send_file
from werkzeug.utils import safe_join, secure_filename
from common.utils.uploads.base_uploader import BaseUploader
def hexencode(s):
if isinstance(s, (str,)):
s = s.encode("utf-8")
encoded = codecs.encode(s, "hex")
try:
encoded = encoded.decode("utf-8")
except UnicodeDecodeError:
pass
return encoded
class FilesystemUploader(BaseUploader):
def __init__(self, base_path=None):
super(BaseUploader, self).__init__()
self.base_path = base_path or current_app.config.get("UPLOAD_FOLDER")
def store(self, fileobj, filename):
location = os.path.join(self.base_path, filename)
directory = os.path.dirname(location)
if not os.path.exists(directory):
os.makedirs(directory)
with open(location, "wb") as dst:
copyfileobj(fileobj, dst, 16384)
return filename
def upload(self, file_obj, filename):
if len(filename) == 0:
raise Exception("Empty filenames cannot be used")
filename = secure_filename(filename)
md5hash = hexencode(os.urandom(16))
file_path = posixpath.join(md5hash, filename)
return self.store(file_obj, file_path)
def download(self, filename):
return send_file(safe_join(self.base_path, filename), as_attachment=True)
def delete(self, filename):
if os.path.exists(os.path.join(self.base_path, filename)):
file_path = PurePath(filename).parts[0]
rmtree(os.path.join(self.base_path, file_path))
return True
return False
def sync(self):
pass
-38
View File
@@ -1,38 +0,0 @@
from qiniu import Auth, put_data
from base_uploader import BaseUploader
access_key = '自己账号的access key'
secret_key = '自己账号的secret key'
bucket_name = '空间名'
class QiniuUploader(BaseUploader):
def __init__(self):
raise NotImplementedError
def store(self, fileobj, filename):
raise NotImplementedError
def upload(self, file_obj, filename):
token = Auth(access_key=access_key, secret_key=secret_key).upload_token(bucket=bucket_name)
ret, res = put_data(token, None, data=file_obj)
ret.get('key')
print(ret)
print(res)
if res.status_code != 200:
raise Exception("upload failed")
return ret, res
def download(self, filename):
raise NotImplementedError
def delete(self, filename):
raise NotImplementedError
def sync(self):
raise NotImplementedError
-22
View File
@@ -1,22 +0,0 @@
from base_uploader import BaseUploader
import upyun
class UpyunUploader(BaseUploader):
def __init__(self):
raise NotImplementedError
def store(self, fileobj, filename):
raise NotImplementedError
def upload(self, file_obj, filename):
raise NotImplementedError
def download(self, filename):
raise NotImplementedError
def delete(self, filename):
raise NotImplementedError
def sync(self):
raise NotImplementedError
-67
View File
@@ -1,67 +0,0 @@
from flask import Blueprint as FlaskBlueprint
from flask.views import MethodViewType, MethodView
class Blueprint(FlaskBlueprint):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._endpoints = []
def add_url_rule(
self,
rule,
endpoint=None,
view_func=None,
provide_automatic_options=None,
*,
parameters=None,
tags=None,
**options,
):
if view_func is None:
raise TypeError("view_func must be provided")
if endpoint is None:
endpoint = view_func.__name__
# Ensure endpoint name is unique
# - to avoid a name clash when registering a MethodView
# - to use it as a key internally in endpoint -> doc mapping
if endpoint in self._endpoints:
endpoint = f"{endpoint}_{len(self._endpoints)}"
self._endpoints.append(endpoint)
if isinstance(view_func, MethodViewType):
func = view_func.as_view(endpoint)
else:
func = view_func
# Add URL rule in Flask and store endpoint documentation
super().add_url_rule(rule, endpoint, func, **options)
def route(self, rule, *, parameters=None, tags=None, **options):
"""Decorator to register view function in application and documentation
Calls :meth:`add_url_rule <Blueprint.add_url_rule>`.
"""
def decorator(func):
endpoint = options.pop("endpoint", None)
self.add_url_rule(
rule, endpoint, func, parameters=parameters, tags=tags, **options
)
return func
return decorator
def register_child_bp(self, bp_lists):
for bp in bp_lists:
self.register_blueprint(bp)
class View(MethodView):
pass
+36
View File
@@ -0,0 +1,36 @@
import os
from datetime import timedelta
class BaseConfig:
SECRET_KEY = os.getenv("SECRET_KEY", "pear-admin-flask")
SQLALCHEMY_DATABASE_URI = ""
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
JWT_TOKEN_LOCATION = ["headers"]
JWT_ACCESS_TOKEN_EXPIRES = timedelta(days=7)
class DevelopmentConfig(BaseConfig):
"""开发配置"""
SQLALCHEMY_DATABASE_URI = "sqlite:///pear_admin.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
class TestingConfig(BaseConfig):
"""测试配置"""
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" # 内存数据库
class ProductionConfig(BaseConfig):
"""生成环境配置"""
SQLALCHEMY_DATABASE_URI = "mysql://root:root@127.0.0.1:3306/pear_admin"
SQLALCHEMY_TRACK_MODIFICATIONS = False
config = {"dev": DevelopmentConfig, "test": TestingConfig, "prod": ProductionConfig}
-27
View File
@@ -1,27 +0,0 @@
from flask import Flask
import os
from common.extend.logger import config_logger
from entrance.bps import sys, register_parent_bp
from entrance.extensions import init_extensions
def create_app():
config_logger()
app = Flask(__name__)
app.config.from_pyfile('config/config.py')
register_parent_bp(app)
init_extensions(app)
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
print(
"""
___ _ _ _ ___ _ _
| _ \___ __ _ _ _ /_\ __| |_ __ (_)_ _ | __| |__ _ __| |__
| _/ -_) _` | '_| / _ \/ _` | ' \| | ' \ | _|| / _` (_-< / /
|_| \___\__,_|_| /_/ \_\__,_|_|_|_|_|_||_| |_| |_\__,_/__/_\_\\
"""
)
return app
-19
View File
@@ -1,19 +0,0 @@
from flask import Flask
from typing import List
from common.view import Blueprint
from modules.sys.view.user import user
sys = Blueprint('sys', __name__, url_prefix='/sys')
sys_lists: List[Blueprint] = [
user
]
sys.register_child_bp(sys_lists)
parent_lists: List[Blueprint] = [
sys
]
def register_parent_bp(app: Flask):
for bp in parent_lists:
app.register_blueprint(bp)
-8
View File
@@ -1,8 +0,0 @@
broker_url = 'redis://127.0.0.1:6379/1'
result_backend = 'rpc://'
task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']
timezone = 'Asia/Shanghai'
enable_utc = True
-15
View File
@@ -1,15 +0,0 @@
import os
# mysql 配置
MYSQL_USERNAME: str = os.getenv('MYSQL_USERNAME') or "root"
MYSQL_PASSWORD: str = os.getenv('MYSQL_PASSWORD') or "123456"
MYSQL_HOST: str = os.getenv('MYSQL_HOST') or "127.0.0.1"
MYSQL_PORT: int = int(os.getenv('MYSQL_PORT') or 3306)
MYSQL_DATABASE: str = os.getenv('MYSQL_DATABASE') or "PearAdminFlask"
# mysql 数据库的配置信息
SQLALCHEMY_DATABASE_URI: str = f"mysql+pymysql://{MYSQL_USERNAME}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}"
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLALCHEMY_ECHO = False
SQLALCHEMY_POOL_RECYCLE = 8
-13
View File
@@ -1,13 +0,0 @@
from flask import Flask
from common.extend.celeryer import register_celery
from common.extend.limit import limiter
from common.extend.orm import db
from common.extend.migrate import migrate
def init_extensions(app: Flask):
db.init_app(app)
migrate.init_app(app)
register_celery(app)
limiter.init_app(app)
-1
View File
@@ -1 +0,0 @@
Single-database configuration for Flask.
-50
View File
@@ -1,50 +0,0 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
-91
View File
@@ -1,91 +0,0 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.get_engine().url).replace(
'%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = current_app.extensions['migrate'].db.get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
-24
View File
@@ -1,24 +0,0 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
@@ -1,32 +0,0 @@
"""empty message
Revision ID: 74133ba99af7
Revises:
Create Date: 2022-01-22 18:50:29.097473
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '74133ba99af7'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('admin_user',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='用户ID'),
sa.Column('username', sa.String(length=20), nullable=True, comment='用户名'),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('admin_user')
# ### end Alembic commands ###
@@ -1,168 +0,0 @@
"""empty message
Revision ID: 8293d5eb9040
Revises: d51040631f21
Create Date: 2022-01-23 15:56:22.001914
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '8293d5eb9040'
down_revision = 'd51040631f21'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('sys_admin_log',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('method', sa.String(length=10), nullable=True),
sa.Column('uid', sa.Integer(), nullable=True),
sa.Column('url', sa.String(length=255), nullable=True),
sa.Column('desc', sa.Text(), nullable=True),
sa.Column('ip', sa.String(length=255), nullable=True),
sa.Column('success', sa.Integer(), nullable=True),
sa.Column('user_agent', sa.Text(), nullable=True),
sa.Column('create_time', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_dept',
sa.Column('id', sa.Integer(), nullable=False, comment='部门ID'),
sa.Column('parent_id', sa.Integer(), nullable=True, comment='父级编号'),
sa.Column('dept_name', sa.String(length=50), nullable=True, comment='部门名称'),
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
sa.Column('leader', sa.String(length=50), nullable=True, comment='负责人'),
sa.Column('phone', sa.String(length=20), nullable=True, comment='联系方式'),
sa.Column('email', sa.String(length=50), nullable=True, comment='邮箱'),
sa.Column('status', sa.Integer(), nullable=True, comment='状态(1开启,0关闭)'),
sa.Column('remark', sa.Text(), nullable=True, comment='备注'),
sa.Column('address', sa.String(length=255), nullable=True, comment='详细地址'),
sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('update_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_dict_data',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('data_label', sa.String(length=255), nullable=True, comment='字典类型名称'),
sa.Column('data_value', sa.String(length=255), nullable=True, comment='字典类型标识'),
sa.Column('type_code', sa.String(length=255), nullable=True, comment='字典类型描述'),
sa.Column('is_default', sa.Integer(), nullable=True, comment='是否默认'),
sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'),
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_dict_type',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('type_name', sa.String(length=255), nullable=True, comment='字典类型名称'),
sa.Column('type_code', sa.String(length=255), nullable=True, comment='字典类型标识'),
sa.Column('description', sa.String(length=255), nullable=True, comment='字典类型描述'),
sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'),
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_photo',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('href', sa.String(length=255), nullable=True),
sa.Column('mime', sa.CHAR(length=50), nullable=False),
sa.Column('size', sa.CHAR(length=30), nullable=False),
sa.Column('create_time', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_power',
sa.Column('id', sa.Integer(), nullable=False, comment='权限编号'),
sa.Column('name', sa.String(length=255), nullable=True, comment='权限名称'),
sa.Column('type', sa.String(length=1), nullable=True, comment='权限类型'),
sa.Column('code', sa.String(length=30), nullable=True, comment='权限标识'),
sa.Column('url', sa.String(length=255), nullable=True, comment='权限路径'),
sa.Column('open_type', sa.String(length=10), nullable=True, comment='打开方式'),
sa.Column('parent_id', sa.Integer(), nullable=True, comment='父类编号'),
sa.Column('icon', sa.String(length=128), nullable=True, comment='图标'),
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_role',
sa.Column('id', sa.Integer(), nullable=False, comment='角色ID'),
sa.Column('name', sa.String(length=255), nullable=True, comment='角色名称'),
sa.Column('code', sa.String(length=255), nullable=True, comment='角色标识'),
sa.Column('enable', sa.Integer(), nullable=True, comment='是否启用'),
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
sa.Column('details', sa.String(length=255), nullable=True, comment='详情'),
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_user',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='用户ID'),
sa.Column('username', sa.String(length=20), nullable=True, comment='用户名'),
sa.Column('realname', sa.String(length=20), nullable=True, comment='真实名字'),
sa.Column('avatar', sa.String(length=255), nullable=True, comment='头像'),
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
sa.Column('password_hash', sa.String(length=128), nullable=True, comment='哈希密码'),
sa.Column('enable', sa.Integer(), nullable=True, comment='启用'),
sa.Column('dept_id', sa.Integer(), nullable=True, comment='部门id'),
sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('update_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_role_power',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'),
sa.Column('power_id', sa.Integer(), nullable=True, comment='用户编号'),
sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'),
sa.ForeignKeyConstraint(['power_id'], ['sys_power.id'], ),
sa.ForeignKeyConstraint(['role_id'], ['sys_role.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sys_user_role',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'),
sa.Column('user_id', sa.Integer(), nullable=True, comment='用户编号'),
sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'),
sa.ForeignKeyConstraint(['role_id'], ['sys_role.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['sys_user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.drop_table('admin_test')
op.drop_table('admin_user')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('admin_user',
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False, comment='用户ID'),
sa.Column('username', mysql.VARCHAR(collation='utf8mb4_general_ci', length=20), nullable=True, comment='用户名'),
sa.PrimaryKeyConstraint('id'),
mysql_collate='utf8mb4_general_ci',
mysql_default_charset='utf8mb4',
mysql_engine='MyISAM'
)
op.create_table('admin_test',
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False, comment='ID'),
sa.Column('name', mysql.VARCHAR(collation='utf8mb4_general_ci', length=20), nullable=True, comment=''),
sa.PrimaryKeyConstraint('id'),
mysql_collate='utf8mb4_general_ci',
mysql_default_charset='utf8mb4',
mysql_engine='MyISAM'
)
op.drop_table('sys_user_role')
op.drop_table('sys_role_power')
op.drop_table('sys_user')
op.drop_table('sys_role')
op.drop_table('sys_power')
op.drop_table('sys_photo')
op.drop_table('sys_dict_type')
op.drop_table('sys_dict_data')
op.drop_table('sys_dept')
op.drop_table('sys_admin_log')
# ### end Alembic commands ###
@@ -1,32 +0,0 @@
"""empty message
Revision ID: d51040631f21
Revises: 74133ba99af7
Create Date: 2022-01-22 23:30:04.372043
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd51040631f21'
down_revision = '74133ba99af7'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('admin_test',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='ID'),
sa.Column('name', sa.String(length=20), nullable=True, comment=''),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('admin_test')
# ### end Alembic commands ###
-25
View File
@@ -1,25 +0,0 @@
from modules.sys.models.user_role import user_role
from modules.sys.models.role_power import role_power
from modules.sys.models.dict import DictType, DictData
from modules.sys.models.admin_log import AdminLog
from modules.sys.models.photo import Photo
from modules.sys.models.power import Power
from modules.sys.models.role import Role
from modules.sys.models.dept import Dept
from modules.sys.models.user import User
model_lists: list = [
role_power,
user_role,
Dept,
DictType, DictData,
AdminLog,
Photo,
Power,
Role,
User,
]
-23
View File
@@ -1,23 +0,0 @@
import datetime
from common.model import BaseModel, Column, Integer, String, Text, DateTime, SQLAlchemyAutoSchema
class AdminLog(BaseModel):
__tablename__ = 'sys_admin_log'
id = Column(Integer, primary_key=True)
method = Column(String(10))
uid = Column(Integer)
url = Column(String(255))
desc = Column(Text)
ip = Column(String(255))
success = Column(Integer)
user_agent = Column(Text)
create_time = Column(DateTime, default=datetime.datetime.now)
class AdminLogSchema(SQLAlchemyAutoSchema):
class Meta:
model = AdminLog
include_fk = True
load_instance = True
-26
View File
@@ -1,26 +0,0 @@
import datetime
from common.model import BaseModel, Column, Integer, String, Text, DateTime, SQLAlchemyAutoSchema
class Dept(BaseModel):
__tablename__ = 'sys_dept'
id = Column(Integer, primary_key=True, comment="部门ID")
parent_id = Column(Integer, comment="父级编号")
dept_name = Column(String(50), comment="部门名称")
sort = Column(Integer, comment="排序")
leader = Column(String(50), comment="负责人")
phone = Column(String(20), comment="联系方式")
email = Column(String(50), comment="邮箱")
status = Column(Integer, comment='状态(1开启,0关闭)')
remark = Column(Text, comment="备注")
address = Column(String(255), comment="详细地址")
create_at = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
update_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='创建时间')
class DeptSchema(SQLAlchemyAutoSchema):
class Meta:
model = Dept
include_fk = True
load_instance = True
-41
View File
@@ -1,41 +0,0 @@
import datetime
from common.model import BaseModel, Column, Integer, String, DateTime, SQLAlchemyAutoSchema
class DictType(BaseModel):
__tablename__ = 'sys_dict_type'
id = Column(Integer, primary_key=True)
type_name = Column(String(255), comment='字典类型名称')
type_code = Column(String(255), comment='字典类型标识')
description = Column(String(255), comment='字典类型描述')
enable = Column(Integer, comment='是否开启')
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
class DictTypeSchema(SQLAlchemyAutoSchema):
class Meta:
model = DictType
include_fk = True
load_instance = True
class DictData(BaseModel):
__tablename__ = 'sys_dict_data'
id = Column(Integer, primary_key=True)
data_label = Column(String(255), comment='字典类型名称')
data_value = Column(String(255), comment='字典类型标识')
type_code = Column(String(255), comment='字典类型描述')
is_default = Column(Integer, comment='是否默认')
enable = Column(Integer, comment='是否开启')
remark = Column(String(255), comment='备注')
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
class DictDataSchema(SQLAlchemyAutoSchema):
class Meta:
model = DictData
include_fk = True
load_instance = True
-20
View File
@@ -1,20 +0,0 @@
import datetime
from common.model import BaseModel, Integer, Column, String, CHAR, DateTime, SQLAlchemyAutoSchema
class File(BaseModel):
__tablename__ = 'sys_file'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
href = Column(String(255))
mime = Column(CHAR(50), nullable=False)
size = Column(CHAR(30), nullable=False)
create_time = Column(DateTime, default=datetime.datetime.now)
class AdminLogSchema(SQLAlchemyAutoSchema):
class Meta:
model = False
include_fk = True
load_instance = True
-20
View File
@@ -1,20 +0,0 @@
import datetime
from common.model import BaseModel, Integer, Column, String, CHAR, DateTime, SQLAlchemyAutoSchema
class Photo(BaseModel):
__tablename__ = 'sys_photo'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
href = Column(String(255))
mime = Column(CHAR(50), nullable=False)
size = Column(CHAR(30), nullable=False)
create_time = Column(DateTime, default=datetime.datetime.now)
class AdminLogSchema(SQLAlchemyAutoSchema):
class Meta:
model = Photo
include_fk = True
load_instance = True
-26
View File
@@ -1,26 +0,0 @@
import datetime
from common.model import BaseModel, Column, Integer, String, DateTime, SQLAlchemyAutoSchema
class Power(BaseModel):
__tablename__ = 'sys_power'
id = Column(Integer, primary_key=True, comment='权限编号')
name = Column(String(255), comment='权限名称')
type = Column(String(1), comment='权限类型')
code = Column(String(30), comment='权限标识')
url = Column(String(255), comment='权限路径')
open_type = Column(String(10), comment='打开方式')
parent_id = Column(Integer, comment='父类编号')
icon = Column(String(128), comment='图标')
sort = Column(Integer, comment='排序')
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
enable = Column(Integer, comment='是否开启')
class PowerSchema(SQLAlchemyAutoSchema):
class Meta:
model = Power
include_fk = True
load_instance = True
-23
View File
@@ -1,23 +0,0 @@
import datetime
from common.model import BaseModel, Column, String, Integer, DateTime, relationship, SQLAlchemyAutoSchema, backref
class Role(BaseModel):
__tablename__ = 'sys_role'
id = Column(Integer, primary_key=True, comment='角色ID')
name = Column(String(255), comment='角色名称')
code = Column(String(255), comment='角色标识')
enable = Column(Integer, comment='是否启用')
remark = Column(String(255), comment='备注')
details = Column(String(255), comment='详情')
sort = Column(Integer, comment='排序')
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
power = relationship('Power', secondary="sys_role_power", backref=backref('role'))
class RoleSchema(SQLAlchemyAutoSchema):
class Meta:
model = Role
include_fk = True
load_instance = True
-8
View File
@@ -1,8 +0,0 @@
from common.model import Column, Table, Integer, ForeignKey
role_power = Table(
"sys_role_power",
Column("id", Integer, primary_key=True, autoincrement=True, comment='标识'),
Column("power_id", Integer, ForeignKey("sys_power.id"), comment='用户编号'),
Column("role_id", Integer, ForeignKey("sys_role.id"), comment='角色编号'),
)
-35
View File
@@ -1,35 +0,0 @@
import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from common.model import BaseModel, Column, Integer, String, DateTime, relationship, SQLAlchemyAutoSchema, backref
class User(BaseModel, UserMixin):
__tablename__ = 'sys_user'
id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID')
username = Column(String(20), comment='用户名')
realname = Column(String(20), comment='真实名字')
avatar = Column(String(255), comment='头像', default="/static/admin/admin/images/avatar.jpg")
remark = Column(String(255), comment='备注')
password_hash = Column(String(128), comment='哈希密码')
enable = Column(Integer, default=0, comment='启用')
dept_id = Column(Integer, comment='部门id')
create_at = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
update_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='创建时间')
role = relationship('Role', secondary="sys_user_role", backref=backref('user'), lazy='dynamic')
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def validate_password(self, password):
return check_password_hash(self.password_hash, password)
# 如果要全换全部字段,就不要声明fields或exclude字段即可
class UserRoleSchema(SQLAlchemyAutoSchema):
class Meta:
model = User
include_fk = True
load_instance = True
exclude = ['password_hash']
-8
View File
@@ -1,8 +0,0 @@
from common.model import Table, Column, Integer, ForeignKey
user_role = Table(
"sys_user_role",
Column("id", Integer, primary_key=True, autoincrement=True, comment='标识'),
Column("user_id", Integer, ForeignKey("sys_user.id"), comment='用户编号'),
Column("role_id", Integer, ForeignKey("sys_role.id"), comment='角色编号'),
)
-13
View File
@@ -1,13 +0,0 @@
from common.view import Blueprint, View
user = Blueprint('sys', __name__, url_prefix='/user')
@user.route('/')
class UserView(View):
def get(self):
pass
# data, total = db.session.query(User.id, User.username).layui_paginate_json()
# print(data)
# return jsonify(data)
+32
View File
@@ -0,0 +1,32 @@
from flask import Flask, render_template
from configs import config
from pear_admin.apis import register_apis
from pear_admin.extensions import register_extensions
from pear_admin.orms import UserORM
from pear_admin.views import register_views
def create_app(config_name="dev"):
app = Flask("pear-admin-flask")
app.config.from_object(config[config_name])
register_extensions(app)
register_apis(app)
register_views(app)
# @app.errorhandler(403)
# def handle_404(e):
# return render_template("error/403.html")
#
# @app.errorhandler(404)
# def handle_403(e):
# return render_template("error/404.html")
#
# @app.errorhandler(500)
# def handle_500(e):
# return render_template("error/500.html")
return app
+19
View File
@@ -0,0 +1,19 @@
from flask import Blueprint, Flask
from .department import department_api
from .passport import passport_api
from .rights import rights_api
from .role import role_api
from .user import user_api
def register_apis(app: Flask):
apis = Blueprint("api", __name__, url_prefix="/api/v1")
apis.register_blueprint(passport_api)
apis.register_blueprint(rights_api)
apis.register_blueprint(role_api)
apis.register_blueprint(department_api)
apis.register_blueprint(user_api)
app.register_blueprint(apis)
+68
View File
@@ -0,0 +1,68 @@
from flask import Blueprint, request
from flask_sqlalchemy.pagination import Pagination
from pear_admin.extensions import db
from pear_admin.orms import DepartmentORM
department_api = Blueprint("department", __name__)
@department_api.get("/department")
def department_list():
page = request.args.get("page", default=1, type=int)
per_page = request.args.get("limit", default=10, type=int)
_type = request.args.get("type")
ret = []
if _type == "tree":
dept_obj: DepartmentORM = DepartmentORM.query.get(1)
for child in dept_obj.children:
child_data = child.json()
child_data["children"] = []
if child.children:
child_data["isParent"] = True
for son in child.children:
son_data = son.json()
# son_data["isParent"] = True
child_data["children"].append(son_data)
ret.append(child_data)
return {"code": 0, "message": "请求权限数据成功", "data": ret}
q = db.select(DepartmentORM)
pages: Pagination = db.paginate(q, page=page, per_page=per_page)
return {
"code": 0,
"msg": "获取部门数据成功",
"data": [item.json() for item in pages.items],
"count": pages.total,
}
@department_api.post("/department")
def create_department():
data = request.get_json()
if data["id"]:
del data["id"]
department = DepartmentORM(**data)
department.save()
return {"code": 0, "msg": "新增部门成功"}
@department_api.put("/department/<int:rid>")
def change_department(rid):
data = request.get_json()
del data["id"]
department_obj = DepartmentORM.query.get(rid)
for key, value in data.items():
setattr(department_obj, key, value)
department_obj.save()
return {"code": 0, "msg": "修改部门成功"}
@department_api.delete("/department/<int:rid>")
def del_department(rid):
department_obj = DepartmentORM.query.get(rid)
department_obj.delete()
return {"code": 0, "msg": "删除删除成功"}
+79
View File
@@ -0,0 +1,79 @@
from collections import OrderedDict
from copy import deepcopy
from flask import Blueprint, make_response, request
from flask_jwt_extended import (
create_access_token,
create_refresh_token,
get_current_user,
jwt_required,
)
from pear_admin.extensions import db
from pear_admin.orms import RightsORM, RoleORM, UserORM
passport_api = Blueprint("passport", __name__)
@passport_api.post("/login")
def login_in():
data = request.get_json()
user: UserORM = db.session.execute(
db.select(UserORM).where(UserORM.username == data["username"])
).scalar()
if not user:
return {"message": "用户不存在", "code": -1}, 401
if not user.check_password(data["password"]):
return {"message": "用户密码错误", "code": -1}, 401
access_token = create_access_token(user)
refresh_token = create_refresh_token(user)
response = make_response(
{
"code": 0,
"msg": "登录成功",
"access_token": access_token,
"refresh_token": refresh_token,
}
)
return response
@passport_api.route("/logout", methods=["GET", "POST"])
@jwt_required()
def logout():
return {"msg": "退出登录成功", "code": 0}
@passport_api.get("/menu")
@jwt_required()
def menus_api():
rights_orm_list = set()
current_user: UserORM = get_current_user()
for role in current_user.role_list:
for rights_orm in role.rights_list:
if rights_orm.type != "auth":
rights_orm_list.add(rights_orm)
rights_list = [rights_orm.menu_json() for rights_orm in rights_orm_list]
rights_list.sort(key=lambda x: (x["pid"], x["id"]), reverse=True)
menu_dict_list = OrderedDict()
for menu_dict in rights_list:
if menu_dict["id"] in menu_dict_list.keys(): # 如果当前节点已经存在与字典中
# 当前节点添加子节点
menu_dict["children"] = deepcopy(menu_dict_list[menu_dict["id"]])
menu_dict["children"].sort(key=lambda item: item["sort"])
# 删除子节点
del menu_dict_list[menu_dict["id"]]
if menu_dict["pid"] not in menu_dict_list:
menu_dict_list[menu_dict["pid"]] = [menu_dict]
else:
menu_dict_list[menu_dict["pid"]].append(menu_dict)
return sorted(menu_dict_list.get(0), key=lambda item: item["sort"])
+101
View File
@@ -0,0 +1,101 @@
from copy import deepcopy
from flask import Blueprint, request
from flask_jwt_extended import jwt_required
from pear_admin.extensions import db
from pear_admin.orms import RightsORM
rights_api = Blueprint("rights", __name__)
@rights_api.get("/rights")
@jwt_required()
def rights_list():
t = request.args.get("type", default="")
if t == "tree": # tree 组件数据,用于权限赋值
# 1. 获取所有的权限
rights_all = db.session.execute(db.select(RightsORM)).scalars()
rights_list = [
{"id": r.id, "pid": r.pid, "title": r.name, "sort": r.sort}
for r in rights_all
]
# 2. 获取已有权限 id 集合
# 3. 列表转属性组件
rights_list.sort(key=lambda item: (item["pid"], item["id"]), reverse=True)
tree_dict = {}
for rights_dict in rights_list: # 遍历子节点
# 2. 如果当前阶段已经存在于树状表格字典,则是父节点
if rights_dict["id"] in tree_dict.keys():
# 将之前的节点添加到父节点之下
rights_dict["children"] = deepcopy(tree_dict[rights_dict["id"]])
rights_dict["children"].sort(key=lambda item: item["sort"])
del tree_dict[rights_dict["id"]]
# 1. 如果父节点未出现在树状字典里面,就新增子节点列表,否则就追加
if rights_dict["pid"] not in tree_dict.keys():
tree_dict[rights_dict["pid"]] = [rights_dict]
else:
tree_dict[rights_dict["pid"]].append(rights_dict)
return {"code": 0, "data": tree_dict.get(0)}
if t == "treetable": # tree table 数据
page = request.args.get("page", type=int, default=1)
per_page = request.args.get("per_page", type=int, default=10)
q = db.select(RightsORM).where(RightsORM.type == "menu")
pages = db.paginate(q, page=page, per_page=per_page, error_out=False)
ret = []
# 构建树状表格的数据
for page in pages.items:
data = page.json()
data["children"] = []
for child in page.children:
child_data = child.json()
if child.children:
child_data["children"] = [
sub_child.json() for sub_child in child.children
]
child_data["isParent"] = True
data["children"].append(child_data)
data["isParent"] = True
ret.append(data)
return {"code": 0, "msg": "请求权限数据成功", "count": pages.total, "data": ret}
return {"code": 0, "msg": "请求权限数据成功,"}
@rights_api.post("/rights")
@jwt_required()
def create_rights():
data = request.get_json()
if not data["pid"]:
data["pid"] = 0
if not data["sort"]:
data["sort"] = int(data["sort"])
rights_obj = RightsORM(**data)
rights_obj.save()
return {"code": 0, "msg": "新增权限数据成功"}
@rights_api.put("/rights/<int:rid>")
@jwt_required()
def change_rights(rid):
data = request.get_json()
del data["id"]
rights_obj = RightsORM.query.get(rid)
for key, value in data.items():
setattr(rights_obj, key, value)
rights_obj.save()
return {"code": 0, "msg": "修改权限数据成功"}
@rights_api.delete("/rights/<int:rid>")
@jwt_required()
def delete_rights(rid):
rights_obj = RightsORM.query.get(rid)
rights_obj.delete()
return {"code": 0, "msg": "删除权限数据成功"}
+82
View File
@@ -0,0 +1,82 @@
from flask import Blueprint, request
from flask_sqlalchemy.pagination import Pagination
from pear_admin.extensions import db
from pear_admin.orms import RightsORM, RoleORM
role_api = Blueprint("role", __name__)
@role_api.get("/role")
def role_list():
page = request.args.get("page", default=1, type=int)
per_page = request.args.get("limit", default=10, type=int)
q = db.select(RoleORM)
pages: Pagination = db.paginate(q, page=page, per_page=per_page)
return {
"code": 0,
"msg": "获取角色数据成功",
"data": [item.json() for item in pages.items],
"count": pages.total,
}
@role_api.post("/role")
def create_role():
data = request.get_json()
if data["id"]:
del data["id"]
role = RoleORM(**data)
role.save()
return {"code": 0, "msg": "新增角色成功"}
@role_api.put("/role/<int:rid>")
def change_role(rid):
data = request.get_json()
del data["id"]
role_obj = RoleORM.query.get(rid)
for key, value in data.items():
setattr(role_obj, key, value)
role_obj.save()
return {"code": 0, "msg": "修改角色权限成功"}
@role_api.delete("/role/<int:rid>")
def del_role(rid):
role_obj = RoleORM.query.get(rid)
role_obj.delete()
return {"code": 0, "msg": "删除角色成功"}
@role_api.get("/role/role_rights/<int:rid>")
def role_rights(rid):
role: RoleORM = db.session.execute(
db.select(RoleORM).where(RoleORM.id == rid)
).scalar()
own_rights_list = [r.id for r in role.rights_list]
return {
"code": 0,
"msg": "返回角色权限数据成功",
"data": own_rights_list,
}
@role_api.put("/role/role_rights/<int:rid>")
def change_role_rights(rid):
rights_ids = request.json.get("rights_ids", "")
rights_list = rights_ids.split(",")
role: RoleORM = db.session.execute(
db.select(RoleORM).where(RoleORM.id == rid)
).scalar()
rights_obj_list = db.session.execute(
db.select(RightsORM).where(RightsORM.id.in_(rights_list))
).all()
role.rights_list = [r[0] for r in rights_obj_list]
role.save()
return {"code": 0, "msg": "授权成功"}
+102
View File
@@ -0,0 +1,102 @@
from datetime import datetime
from flask import Blueprint, request
from flask_jwt_extended import current_user, jwt_required
from flask_sqlalchemy.pagination import Pagination
from pear_admin.extensions import db
from pear_admin.orms import RoleORM, UserORM
user_api = Blueprint("user", __name__)
@user_api.get("/user")
def user_list():
page = request.args.get("page", default=1, type=int)
per_page = request.args.get("limit", default=10, type=int)
q = db.select(UserORM)
pages: Pagination = db.paginate(q, page=page, per_page=per_page)
return {
"code": 0,
"msg": "获取用户数据成功",
"data": [item.json() for item in pages.items],
"count": pages.total,
}
@user_api.post("/user")
def create_user():
data = request.get_json()
if data["id"]:
del data["id"]
role = UserORM(**data)
create_at = data["create_at"]
if create_at:
role.create_at = datetime.strptime(create_at, "%Y-%m-%d %H:%M:%S")
role.password = "123456"
role.save()
return {"code": 0, "msg": "新增用户成功"}
@user_api.put("/user/<int:uid>")
def change_user(uid):
data = request.get_json()
del data["id"]
user_obj = UserORM.query.get(uid)
for key, value in data.items():
if key == "create_at":
value = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
setattr(user_obj, key, value)
user_obj.save()
return {"code": 0, "msg": "修改用户信息成功"}
@user_api.delete("/user/<int:rid>")
def del_user(rid):
user_obj = UserORM.query.get(rid)
user_obj.delete()
return {"code": 0, "msg": "删除用户成功"}
@user_api.get("/user/user_role/<int:uid>")
def get_user_role(uid):
user: UserORM = db.session.execute(
db.select(UserORM).where(UserORM.id == uid)
).scalar()
wn_role_list = [r.id for r in user.role_list]
return {
"code": 0,
"msg": "返回角色权限数据成功",
"data": wn_role_list,
}
@user_api.put("/user/user_role/<int:rid>")
def change_user_role(rid):
role_ids = request.json.get("rights_ids", "")
role_list = role_ids.split(",")
user: UserORM = db.session.execute(
db.select(UserORM).where(UserORM.id == rid)
).scalar()
role_obj_list = db.session.execute(
db.select(RoleORM).where(RoleORM.id.in_(role_list))
).all()
user.role_list = [r[0] for r in role_obj_list]
user.save()
return {"code": 0, "msg": "授权成功"}
@user_api.get("/user/profile")
@jwt_required()
def user_profile():
return {
"code": 0,
"msg": "获取个人数据成功",
"data": current_user.json(),
}
+13
View File
@@ -0,0 +1,13 @@
from flask import Flask
from .init_db import db, migrate
from .init_jwt import jwt
from .init_script import register_script
def register_extensions(app: Flask):
db.init_app(app)
migrate.init_app(app, db)
jwt.init_app(app)
register_script(app)
+5
View File
@@ -0,0 +1,5 @@
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
migrate = Migrate()
+27
View File
@@ -0,0 +1,27 @@
from flask import redirect
from flask_jwt_extended import JWTManager
from pear_admin.orms.user import UserORM
jwt = JWTManager()
@jwt.user_identity_loader
def user_identity_lookup(user):
return user.id
@jwt.user_lookup_loader
def user_lookup_callback(_jwt_header, jwt_data):
identity = jwt_data["sub"]
return UserORM.query.filter(UserORM.id == identity).one_or_none()
@jwt.expired_token_loader
def expired_token_callback():
return {"msg": "token 已过期,请重新登录", "code": -1}, 403
@jwt.unauthorized_loader
def missing_token_callback(error):
return {"msg": "操作未授权,请重新登录", "code": -1}, 403
+64
View File
@@ -0,0 +1,64 @@
import csv
import os
from flask import Flask, current_app
from pear_admin.extensions import db
from pear_admin.orms import DepartmentORM, RightsORM, RoleORM, UserORM
def dict_to_orm(d, o):
for k, v in d.items():
if k == "password":
o.password = v
else:
setattr(o, k, v or None)
def csv_to_databases(path, orm):
with open(path, encoding="utf-8") as file:
for d in csv.DictReader(file):
o = orm()
dict_to_orm(d, o)
db.session.add(o)
db.session.flush()
db.session.commit()
def register_script(app: Flask):
@app.cli.command()
def init():
db.drop_all()
db.create_all()
root = current_app.config.get("ROOT_PATH")
rights_data_path = os.path.join(root, "static", "data", "ums_rights.csv")
csv_to_databases(rights_data_path, RightsORM)
role_data_path = os.path.join(root, "static", "data", "ums_role.csv")
csv_to_databases(role_data_path, RoleORM)
with open(role_data_path, encoding="utf-8") as file:
for d in csv.DictReader(file):
role: RoleORM = RoleORM.query.get(d["id"])
id_list = [int(_id) for _id in d["rights_ids"].split(":")]
role.rights_list = RightsORM.query.filter(
RightsORM.id.in_(id_list)
).all()
db.session.commit()
department_data_path = os.path.join(
root, "static", "data", "ums_department.csv"
)
csv_to_databases(department_data_path, DepartmentORM)
user_data_path = os.path.join(root, "static", "data", "ums_user.csv")
csv_to_databases(user_data_path, UserORM)
with open(user_data_path, encoding="utf-8") as file:
for d in csv.DictReader(file):
user: UserORM = UserORM.query.get(d["id"])
id_list = [int(_id) for _id in d["role_ids"].split(":")]
user.role_list = RoleORM.query.filter(RoleORM.id.in_(id_list)).all()
db.session.commit()
+20
View File
@@ -0,0 +1,20 @@
from pear_admin.extensions import db
from .department import DepartmentORM
from .rights import RightsORM
from .role import RoleORM
from .user import UserORM
user_role = db.Table(
"ums_user_role", # 用户-角色中间表名称
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment="标识"),
db.Column("user_id", db.Integer, db.ForeignKey("ums_user.id"), comment="用户编号"),
db.Column("role_id", db.Integer, db.ForeignKey("ums_role.id"), comment="角色编号"),
)
role_rights = db.Table(
"ums_role_rights", # 用户-权限中间表名称
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment="标识"),
db.Column("rights_id", db.Integer, db.ForeignKey("ums_rights.id"), comment="用户编号"),
db.Column("role_id", db.Integer, db.ForeignKey("ums_role.id"), comment="角色编号"),
)
+13
View File
@@ -0,0 +1,13 @@
from pear_admin.extensions import db
class BaseORM(db.Model):
__abstract__ = True
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
+28
View File
@@ -0,0 +1,28 @@
from pear_admin.extensions import db
from ._base import BaseORM
class DepartmentORM(BaseORM):
__tablename__ = "ums_department"
id = db.Column(db.Integer, primary_key=True, comment="部门ID")
name = db.Column(db.String(50), comment="部门名称")
leader = db.Column(db.String(50), comment="负责人")
enable = db.Column(db.Boolean, comment="状态(1开启,0关闭)", default=True)
users = db.relationship("UserORM", backref="department")
pid = db.Column(db.Integer, db.ForeignKey("ums_department.id"), default=1)
parent = db.relationship(
"DepartmentORM", back_populates="children", remote_side=[id]
) # 自关联
children = db.relationship("DepartmentORM", back_populates="parent")
def json(self):
return {
"id": self.id,
"pid": self.pid,
"name": self.name,
"leader": self.leader,
"enable": self.enable,
}
+59
View File
@@ -0,0 +1,59 @@
from sqlalchemy.orm import backref
from pear_admin.extensions import db
from ._base import BaseORM
class RightsORM(BaseORM):
__tablename__ = "ums_rights"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), nullable=False, comment="权限名称")
code = db.Column(db.String(30), comment="权限标识")
type = db.Column(db.String(30), comment="权限类型")
url = db.Column(db.String(30), comment="路径地址")
icon_sign = db.Column(db.String(128), comment="图标")
status = db.Column(db.Boolean, default=True, comment="是否开启")
sort = db.Column(db.Integer, default=0)
open_type = db.Column(db.String(128), comment="打开方式")
pid = db.Column(
db.Integer,
db.ForeignKey("ums_rights.id"),
default=0,
comment="父类编号",
)
parent = db.relationship(
"RightsORM", back_populates="children", remote_side=[id]
) # 自关联
children = db.relationship("RightsORM", back_populates="parent")
def json(self):
return {
"id": self.id,
"name": self.name,
"code": self.code,
"type": self.type,
"url": self.url,
"icon_sign": self.icon_sign,
"status": self.status,
"sort": self.sort,
"open_type": self.open_type,
"pid": self.pid,
}
def menu_json(self):
type_map_dict = {"menu": 0, "path": 1}
return {
"id": self.id,
"pid": self.pid,
"title": self.name,
"type": type_map_dict[self.type],
"href": self.url,
"icon": self.icon_sign,
"sort": self.sort,
"openType": self.open_type,
}
+30
View File
@@ -0,0 +1,30 @@
from pear_admin.extensions import db
from ._base import BaseORM
class RoleORM(BaseORM):
__tablename__ = "ums_role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), nullable=False, comment="角色名称")
code = db.Column(db.String(20), nullable=False, comment="角色标识符")
desc = db.Column(db.Text)
rights_ids = db.Column(
db.String(512),
comment="权限ids,1,2,5。冗余字段,用户缓存用户权限",
)
rights_list = db.relationship(
"RightsORM", secondary="ums_role_rights", backref=db.backref("role")
)
def json(self):
return {
"id": self.id,
"name": self.name,
"code": self.code,
"desc": self.desc,
"rights_ids": self.rights_ids,
}
+56
View File
@@ -0,0 +1,56 @@
from datetime import datetime
from werkzeug.security import check_password_hash, generate_password_hash
from pear_admin.extensions import db
from ._base import BaseORM
class UserORM(BaseORM):
__tablename__ = "ums_user"
id = db.Column(db.Integer, primary_key=True, comment="自增id")
nickname = db.Column(db.String(128), nullable=False, comment="昵称")
username = db.Column(db.String(128), nullable=False, comment="登录名")
password_hash = db.Column(db.String(102), nullable=False, comment="登录密码")
mobile = db.Column(db.String(32), nullable=False, comment="手机")
email = db.Column(db.String(64), nullable=False, comment="邮箱")
avatar = db.Column(db.Text, comment="头像地址")
create_at = db.Column(
db.DateTime,
nullable=False,
comment="创建时间",
default=datetime.now,
)
department_id = db.Column(
db.Integer, db.ForeignKey("ums_department.id"), default=1, comment="部门id"
)
role_list = db.relationship(
"RoleORM",
secondary="ums_user_role",
backref=db.backref("user"),
lazy="dynamic",
)
def json(self):
return {
"id": self.id,
"username": self.username,
"nickname": self.nickname,
"mobile": self.mobile,
"email": self.email,
"create_at": self.create_at.strftime("%Y-%m-%d %H:%M:%S"),
}
@property
def password(self):
return self.password_hash
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password=password)
+9
View File
@@ -0,0 +1,9 @@
from flask import Flask
from .index import index_bp
from .system import system_bp
def register_views(app: Flask):
app.register_blueprint(index_bp)
app.register_blueprint(system_bp)
+38
View File
@@ -0,0 +1,38 @@
from flask import Blueprint, render_template, send_file, send_from_directory
index_bp = Blueprint("index", __name__)
@index_bp.route("/")
def index():
return render_template("view/index.html")
@index_bp.route("/view/login.html")
def login():
return render_template("view/login.html")
@index_bp.route("/view/register.html")
def register():
return render_template("view/register.html")
@index_bp.route("/view/console/index.html")
def console1():
return render_template("view/console/index.html")
@index_bp.route("/view/analysis/index.html")
def analysis():
return render_template("view/analysis/index.html")
@index_bp.route("/view/system/person.html")
def person():
return render_template("view/system/person.html")
@index_bp.route("/favicon.ico")
def fav_icon():
return send_from_directory(directory="static", path="favicon.ico")
+23
View File
@@ -0,0 +1,23 @@
from flask import Blueprint, render_template
system_bp = Blueprint("system", __name__)
@system_bp.get("/system/<path1>/<path2>")
def system_view(path1, path2):
return render_template(f"system/{path1}/{path2}")
@system_bp.get("/views/role.html")
def role_view():
return render_template("system/role/index.html")
@system_bp.get("/views/department.html")
def department_view():
return render_template("system/department/index.html")
@system_bp.get("/views/user.html")
def user_view():
return render_template("system/user/index.html")
Generated
+935
View File
@@ -0,0 +1,935 @@
# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
[[package]]
name = "alembic"
version = "1.12.1"
description = "A database migration tool for SQLAlchemy."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "alembic-1.12.1-py3-none-any.whl", hash = "sha256:47d52e3dfb03666ed945becb723d6482e52190917fdb47071440cfdba05d92cb"},
{file = "alembic-1.12.1.tar.gz", hash = "sha256:bca5877e9678b454706347bc10b97cb7d67f300320fa5c3a94423e8266e2823f"},
]
[package.dependencies]
Mako = "*"
SQLAlchemy = ">=1.3.0"
typing-extensions = ">=4"
[package.extras]
tz = ["python-dateutil"]
[[package]]
name = "argcomplete"
version = "3.1.2"
description = "Bash tab completion for argparse"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "argcomplete-3.1.2-py3-none-any.whl", hash = "sha256:d97c036d12a752d1079f190bc1521c545b941fda89ad85d15afa909b4d1b9a99"},
{file = "argcomplete-3.1.2.tar.gz", hash = "sha256:d5d1e5efd41435260b8f85673b74ea2e883affcbec9f4230c582689e8e78251b"},
]
[package.extras]
test = ["coverage", "mypy", "pexpect", "ruff", "wheel"]
[[package]]
name = "blinker"
version = "1.6.3"
description = "Fast, simple object-to-object and broadcast signaling"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"},
{file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"},
]
[[package]]
name = "cfgv"
version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
]
[[package]]
name = "charset-normalizer"
version = "3.3.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "dev"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"},
{file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"},
{file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"},
{file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"},
{file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"},
{file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"},
{file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"},
{file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"},
{file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"},
{file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"},
{file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"},
{file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"},
{file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"},
{file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"},
{file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"},
{file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"},
{file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"},
{file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"},
{file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"},
{file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"},
{file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"},
{file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"},
{file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"},
{file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"},
{file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"},
{file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"},
{file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"},
{file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"},
{file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"},
{file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"},
{file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"},
{file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"},
{file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"},
{file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"},
{file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"},
{file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"},
{file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"},
{file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"},
{file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"},
{file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"},
{file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"},
{file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"},
{file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"},
{file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"},
{file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"},
{file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"},
{file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"},
{file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"},
{file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"},
{file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"},
{file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"},
{file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"},
{file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"},
{file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"},
{file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"},
{file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"},
{file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"},
{file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"},
{file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"},
{file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"},
{file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"},
{file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"},
{file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"},
{file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"},
{file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"},
{file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"},
{file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"},
{file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"},
{file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"},
{file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"},
{file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"},
{file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"},
{file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"},
{file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"},
{file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"},
{file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"},
{file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"},
{file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"},
]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
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 = "commitizen"
version = "3.12.0"
description = "Python commitizen client tool"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "commitizen-3.12.0-py3-none-any.whl", hash = "sha256:082f4733409bc4f01f987467295f8393ceb16b42cc648cf2f5a7a754c6d594db"},
{file = "commitizen-3.12.0.tar.gz", hash = "sha256:7c313f1f85f45c9acf1a70f1637deab5c388150ae8660a0037ac260e77bb1492"},
]
[package.dependencies]
argcomplete = ">=1.12.1,<3.2"
charset-normalizer = ">=2.1.0,<4"
colorama = ">=0.4.1,<0.5.0"
decli = ">=0.6.0,<0.7.0"
importlib_metadata = ">=4.13,<7"
jinja2 = ">=2.10.3"
packaging = ">=19"
pyyaml = ">=3.08"
questionary = ">=2.0,<3.0"
termcolor = ">=1.1,<3"
tomlkit = ">=0.5.3,<1.0.0"
[[package]]
name = "decli"
version = "0.6.1"
description = "Minimal, easy-to-use, declarative cli tool"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "decli-0.6.1-py3-none-any.whl", hash = "sha256:7815ac58617764e1a200d7cadac6315fcaacc24d727d182f9878dd6378ccf869"},
{file = "decli-0.6.1.tar.gz", hash = "sha256:ed88ccb947701e8e5509b7945fda56e150e2ac74a69f25d47ac85ef30ab0c0f0"},
]
[[package]]
name = "distlib"
version = "0.3.7"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
files = [
{file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"},
{file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"},
]
[[package]]
name = "filelock"
version = "3.13.1"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
{file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]]
name = "flask"
version = "3.0.0"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638"},
{file = "flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58"},
]
[package.dependencies]
blinker = ">=1.6.2"
click = ">=8.1.3"
itsdangerous = ">=2.1.2"
Jinja2 = ">=3.1.2"
Werkzeug = ">=3.0.0"
[package.extras]
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "flask-jwt-extended"
version = "4.5.3"
description = "Extended JWT integration with Flask"
category = "main"
optional = false
python-versions = ">=3.7,<4"
files = [
{file = "Flask-JWT-Extended-4.5.3.tar.gz", hash = "sha256:061ef3d25ed5743babe4964ab38f36d870e6d2fd8a126bab5d77ddef8a01932b"},
{file = "Flask_JWT_Extended-4.5.3-py2.py3-none-any.whl", hash = "sha256:eaec42af107dcb919785a4b3766c09ffba9f286b92a8d58603933f28fd4db6a3"},
]
[package.dependencies]
Flask = ">=2.0,<4.0"
PyJWT = ">=2.0,<3.0"
Werkzeug = ">=0.14"
[package.extras]
asymmetric-crypto = ["cryptography (>=3.3.1)"]
[[package]]
name = "flask-migrate"
version = "4.0.5"
description = "SQLAlchemy database migrations for Flask applications using Alembic."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "Flask-Migrate-4.0.5.tar.gz", hash = "sha256:d3f437a8b5f3849d1bb1b60e1b818efc564c66e3fefe90b62e5db08db295e1b1"},
{file = "Flask_Migrate-4.0.5-py3-none-any.whl", hash = "sha256:613a2df703998e78716cace68cd83972960834424457f5b67f56e74fff950aef"},
]
[package.dependencies]
alembic = ">=1.9.0"
Flask = ">=0.9"
Flask-SQLAlchemy = ">=1.0"
[[package]]
name = "flask-sqlalchemy"
version = "3.1.1"
description = "Add SQLAlchemy support to your Flask application."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"},
{file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"},
]
[package.dependencies]
flask = ">=2.2.5"
sqlalchemy = ">=2.0.16"
[[package]]
name = "greenlet"
version = "3.0.1"
description = "Lightweight in-process concurrent programming"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"},
{file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"},
{file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"},
{file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"},
{file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"},
{file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"},
{file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"},
{file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"},
{file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"},
{file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"},
{file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"},
{file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"},
{file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"},
{file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"},
{file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"},
{file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"},
{file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"},
{file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"},
{file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"},
{file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"},
{file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"},
{file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"},
{file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"},
{file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"},
{file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"},
{file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"},
{file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"},
{file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"},
{file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"},
{file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"},
{file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"},
{file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"},
{file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"},
]
[package.extras]
docs = ["Sphinx"]
test = ["objgraph", "psutil"]
[[package]]
name = "identify"
version = "2.5.31"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"},
{file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"},
]
[package.extras]
license = ["ukkonen"]
[[package]]
name = "importlib-metadata"
version = "6.8.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"},
{file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
[[package]]
name = "itsdangerous"
version = "2.1.2"
description = "Safely pass data to untrusted environments and back."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
]
[[package]]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "mako"
version = "1.2.4"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"},
{file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"},
]
[package.dependencies]
MarkupSafe = ">=0.9.2"
[package.extras]
babel = ["Babel"]
lingua = ["lingua"]
testing = ["pytest"]
[[package]]
name = "markupsafe"
version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
{file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
{file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
{file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
{file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
{file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
{file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
]
[[package]]
name = "nodeenv"
version = "1.8.0"
description = "Node.js virtual environment builder"
category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [
{file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
{file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
]
[package.dependencies]
setuptools = "*"
[[package]]
name = "packaging"
version = "23.2"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
]
[[package]]
name = "platformdirs"
version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
{file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
]
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pre-commit"
version = "3.5.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"},
{file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"},
]
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
virtualenv = ">=20.10.0"
[[package]]
name = "prompt-toolkit"
version = "3.0.36"
description = "Library for building powerful interactive command lines in Python"
category = "dev"
optional = false
python-versions = ">=3.6.2"
files = [
{file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"},
{file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"},
]
[package.dependencies]
wcwidth = "*"
[[package]]
name = "pyjwt"
version = "2.8.0"
description = "JSON Web Token implementation in Python"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"},
{file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"},
]
[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 = "python-dotenv"
version = "1.0.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
{file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
]
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "pyyaml"
version = "6.0.1"
description = "YAML parser and emitter for Python"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
{file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
{file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
{file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
{file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
{file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
{file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
]
[[package]]
name = "questionary"
version = "2.0.1"
description = "Python library to build pretty command line user prompts ⭐️"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "questionary-2.0.1-py3-none-any.whl", hash = "sha256:8ab9a01d0b91b68444dff7f6652c1e754105533f083cbe27597c8110ecc230a2"},
{file = "questionary-2.0.1.tar.gz", hash = "sha256:bcce898bf3dbb446ff62830c86c5c6fb9a22a54146f0f5597d3da43b10d8fc8b"},
]
[package.dependencies]
prompt_toolkit = ">=2.0,<=3.0.36"
[[package]]
name = "setuptools"
version = "68.2.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
{file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "sqlalchemy"
version = "2.0.22"
description = "Database Abstraction Library"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"},
{file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"},
{file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"},
{file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"},
{file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"},
{file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"},
{file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"},
{file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"},
{file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"},
]
[package.dependencies]
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
typing-extensions = ">=4.2.0"
[package.extras]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)"]
mysql-connector = ["mysql-connector-python"]
oracle = ["cx-oracle (>=7)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
pymysql = ["pymysql"]
sqlcipher = ["sqlcipher3-binary"]
[[package]]
name = "termcolor"
version = "2.3.0"
description = "ANSI color formatting for output in terminal"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"},
{file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"},
]
[package.extras]
tests = ["pytest", "pytest-cov"]
[[package]]
name = "tomlkit"
version = "0.12.1"
description = "Style preserving TOML library"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"},
{file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"},
]
[[package]]
name = "typing-extensions"
version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
{file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
]
[[package]]
name = "virtualenv"
version = "20.24.6"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"},
{file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"},
]
[package.dependencies]
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<4"
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]]
name = "wcwidth"
version = "0.2.9"
description = "Measures the displayed width of unicode strings in a terminal"
category = "dev"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"},
{file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"},
]
[[package]]
name = "werkzeug"
version = "3.0.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=3.8"
files = [
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
]
[package.dependencies]
MarkupSafe = ">=2.1.1"
[package.extras]
watchdog = ["watchdog (>=2.3)"]
[[package]]
name = "zipp"
version = "3.17.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
{file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "e99a77db2a32e04474571eea6c88dad45078d1a0b2d146af67e1f360ecd4760f"
+24
View File
@@ -0,0 +1,24 @@
[tool.poetry]
name = "pear-admin-flask"
version = "0.1.0"
description = "flask + pear admin layui"
authors = ["zhengxinonly <pyxxponly@gmail.com>"]
license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
flask = "^3.0.0"
flask-sqlalchemy = "^3.1.1"
flask-jwt-extended = "^4.5.2"
flask-migrate = "^4.0.5"
[tool.poetry.group.dev.dependencies]
python-dotenv = "^1.0.0"
pre-commit = "^3.4.0"
commitizen = "^3.10.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
-2
View File
@@ -1,2 +0,0 @@
-r min.txt
-r extra.txt
-4
View File
@@ -1,4 +0,0 @@
Flask-Mail==0.9.1
celery==5.2.3
qiniu==7.5.0
upyun==2.5.5
-11
View File
@@ -1,11 +0,0 @@
-i https://pypi.tuna.tsinghua.edu.cn/simple
Flask==2.0.2
flask-restx==0.5.1
pymysql==1.0.2
Flask-Migrate==3.1.0
flask-marshmallow==0.14.0
marshmallow-sqlalchemy==0.27.0
Flask-Login==0.5.0
Pillow==9.0.0
flask-limiter-2.1
cachelib-0.6.0
+643
View File
@@ -0,0 +1,643 @@
html,
body,
.layui-layout {
height: 100%;
}
.pear-admin .layui-body,
.pear-admin .layui-logo,
.pear-admin .layui-side,
.pear-admin .layui-header,
.pear-admin .layui-header .layui-layout-left {
transition: all 0.3s;
}
.pear-admin.banner-layout .layui-side {
top: 60px !important;
}
.pear-admin.banner-layout .layui-side .layui-logo {
display: none;
}
.pear-admin.banner-layout .layui-header .layui-logo {
display: inline-block;
}
.pear-admin.banner-layout .layui-side .layui-side-scroll {
height: 100% !important;
}
.pear-admin.banner-layout .layui-side .layui-side-scroll {
height: 100% !important;
}
.pear-admin .layui-header.dark-theme .layui-layout-control .layui-this * {
background-color: rgba(0, 0, 0, 0.1) !important;
}
.pear-admin .layui-header .layui-logo {
display: none;
}
.pear-admin .layui-logo .title {
font-size: 20px;
}
.pear-admin .layui-layout-right .layui-nav-child {
border: 1px solid whitesmoke;
border-radius: 4px;
width: auto;
left: auto;
right: -23px;
}
.pear-admin .layui-header {
left: 230px;
width: calc(100% - 230px);
background-color: white;
}
.pear-admin .layui-layout-control {
left: 140px;
position: absolute;
}
.pear-admin .layui-layout-control .layui-nav {
padding: 0px;
}
.pear-admin .layui-logo {
height: 60px;
line-height: 60px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
box-sizing: border-box;
position: relative;
background-color: #28333e;
width: 230px;
}
.pear-admin .layui-logo img {
width: 34px;
height: 34px;
}
.pear-admin .layui-logo .title {
font-size: 21px;
font-weight: 550;
color: var(--global-primary-color);
position: relative;
top: 5px;
margin-left: 5px;
}
.pear-admin .layui-logo .logo {
display: none;
}
.pear-admin .layui-side {
top: 0px;
width: 230px;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.2);
z-index: 9999;
}
.pear-admin .layui-side-scroll::-webkit-scrollbar {
width: 0px;
height: 0px;
}
.pear-admin .layui-side-scroll {
height: calc(100% - 60px) !important;
background-color: #28333e;
width: 247px;
}
.pear-admin .layui-header .layui-nav .layui-nav-item > a {
color: black;
font-size: 15px;
}
.pear-admin .layui-body {
bottom: 0px;
padding-bottom: 0px;
background-color: whitesmoke;
height: calc(100% - 60px);
overflow-y: auto;
left: 230px;
}
.pear-admin .layui-body > div {
height: 100%;
}
.pear-admin .layui-layout-left {
left: 0px;
}
.pear-admin .layui-footer {
position: absolute;
display: flex;
justify-content: space-between;
left: 230px;
background: #fff;
border-top: 1px solid #f2f2f2;
box-shadow: none;
-webkit-transition: left 0.3s;
transition: left 0.3s;
overflow: hidden;
color: #3c3c3cb3;
font-weight: 300;
font-size: 13.6px;
}
.pear-admin .layui-footer.close {
display: none;
}
/** 通栏布局 */
.pear-admin.banner-layout .layui-header {
left: 0px;
z-index: 99999;
width: 100%;
}
.pear-admin.banner-layout .layui-header.light-theme {
border-bottom: 1px solid whitesmoke;
}
.pear-admin.banner-layout .layui-header.auto-theme,
.pear-admin.banner-layout .layui-header.dark-theme {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.pear-admin.banner-layout .layui-header .layui-layout-left {
left: 230px;
}
.pear-admin.banner-layout .layui-header .layui-logo .title {
top: 2px;
}
.pear-admin.banner-layout .layui-header .layui-layout-control {
display: inline-block;
left: 370px;
}
/** 头部主题 */
.pear-admin .auto-theme {
background-color: var(--global-primary-color);
color: white;
}
.pear-admin .auto-theme .layui-logo {
background-color: var(--global-primary-color);
border: none;
}
.pear-admin .auto-theme .layui-logo .title {
color: white;
}
.pear-admin .auto-theme .layui-nav * {
color: white !important;
}
.pear-admin .auto-theme .layui-nav.pear-nav-control .layui-this * {
color: black !important;
}
.pear-admin .auto-theme .layui-nav .layui-nav-child a {
color: #5f5f5f !important;
color: rgba(0, 0, 0, 0.8) !important;
}
/** 收缩布局 */
.pear-mini .layui-side .layui-logo .title {
display: none;
}
.pear-mini .layui-side .layui-logo .logo {
display: inline-block;
}
.pear-mini .layui-side {
width: 60px;
}
.pear-mini .layui-header {
left: 60px;
width: calc(100% - 60px);
}
.pear-mini .layui-body {
left: 60px;
}
.pear-mini .layui-side .layui-logo {
width: 60px;
}
.pear-mini .layui-footer {
left: 60px;
}
.pear-mini .layui-nav-tree .layui-nav-item span {
display: none;
}
.pear-mini .layui-side-scroll {
height: calc(100% - 60px);
}
.pear-admin .layui-header .layui-nav .layui-nav-bar {
top: 0px !important;
background-color: var(--global-primary-color);
height: 2px !important;
}
.pear-admin .layui-header .layui-nav .layui-this:after {
display: none;
}
.pear-admin .layui-header .layui-nav-more {
display: none;
}
.pear-collapsed-pe {
right: 30px;
bottom: 30px;
z-index: 400000;
position: absolute;
background-color: var(--global-primary-color) !important;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.2);
text-align: center;
border-radius: 4px;
line-height: 50px;
display: none;
height: 50px;
width: 50px;
}
.pear-collapsed-pe a {
color: white !important;
}
@media screen and (min-width: 768px) {
.layui-hide-sm {
display: inline-block !important;
}
}
@media screen and (min-width: 769px) {
.layui-hide-sm {
display: none !important;
}
}
/** 新增兼容 */
@media screen and (max-width: 768px) {
.collapse {
display: none !important;
}
.pear-collapsed-pe {
display: inline-block !important;
}
.layui-layout-control {
left: 45px !important;
}
.layui-layout-left {
padding-left: 10px;
padding-right: 10px;
}
.pear-mini .layui-side-scroll {
height: calc(100% - 62px);
}
.pear-mini .layui-side {
width: 0px;
}
.pear-mini .layui-header {
left: 0px;
width: 100%;
}
.pear-mini .layui-body {
left: 0px;
}
.pear-mini .layui-footer {
left: 0px;
}
.pear-mini .layui-logo {
width: 0px;
}
.pear-admin .layui-body {
left: 0px;
}
.pear-admin .layui-header {
left: 0px;
width: 100%;
}
.pear-admin .pear-cover {
width: 100%;
height: 100%;
background-color: #1e1e1e;
display: block;
position: absolute;
z-index: 1000;
opacity: 0;
margin-top: -60px;
}
.pear-mini .pear-cover {
display: none;
}
}
@-webkit-keyframes am-horizontal-roll_show {
0% {
opacity: 1;
-webkit-transform: translateX(2000px);
transform: translateX(2000px);
}
100% {
opacity: 1;
-webkit-transform: translateX(0);
transform: translateX(0);
}
}
@keyframes am-horizontal-roll_show {
0% {
opacity: 1;
-webkit-transform: translateX(800px);
-ms-transform: translateX(800px);
transform: translateX(800px);
}
100% {
opacity: 1;
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
}
.layer-anim-right {
-webkit-animation: am-horizontal-roll_show 0.5s ease-out;
animation: am-horizontal-roll_show 0.5s ease-out;
}
/** 侧边主题 (亮) */
.light-theme.layui-side {
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05) !important;
}
.light-theme.layui-side .layui-logo {
background-color: white !important;
color: black !important;
border-bottom: 1px whitesmoke solid;
}
.light-theme.layui-side .layui-side-scroll {
background-color: white !important;
color: black !important;
}
.dark-theme.layui-header {
border-bottom: none;
background-color: #28333e;
color: whitesmoke;
}
.dark-theme.layui-header li > a {
color: whitesmoke !important;
}
.dark-theme.layui-header .layui-logo {
box-shadow: none;
border: none;
}
/** 顶部主题 (白) */
.light-theme.layui-header .layui-logo {
background-color: white;
border: none;
box-shadow: none;
}
/** 主题面板 */
.pearone-color .set-text {
height: 42px;
line-height: 42px;
}
.pearone-color .color-title {
padding: 15px 0 0px 20px;
margin-bottom: 4px;
}
.pearone-color .color-content {
padding: 15px 10px 0 20px;
}
.pearone-color .color-content ul {
list-style: none;
padding: 0px;
}
.pearone-color .color-content ul li {
position: relative;
display: inline-block;
vertical-align: top;
width: 70px;
height: 50px;
margin: 0 20px 20px 0;
padding: 2px 2px 2px 2px;
background-color: #f2f2f2;
cursor: pointer;
font-size: 12px;
color: #666;
}
.pearone-color .color-content li.layui-this:after,
.pearone-color .color-content li:hover:after {
width: 100%;
height: 100%;
padding: 4px;
top: -6px;
left: -6px;
border: var(--global-primary-color) 2px solid;
opacity: 1;
border-radius: 4px;
}
.pearone-color .color-content li:after {
content: "";
position: absolute;
z-index: 20;
top: 50%;
left: 50%;
width: 1px;
height: 0;
border: 2px solid #f2f2f2;
transition: all 0.3s;
-webkit-transition: all 0.3s;
opacity: 0;
}
.select-color {
margin-bottom: 30px;
}
.select-color .select-color-title {
padding: 15px 0 0px 20px;
margin-bottom: 4px;
}
.select-color .select-color-content {
padding: 20px 0 0px 0px;
margin-bottom: 4px;
}
.select-color .select-color-content .select-color-item {
width: 24px;
height: 24px;
color: white;
margin-left: 24px;
border-radius: 3px;
background-color: gray;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15);
text-align: center;
line-height: 24px;
font-size: 14px;
float: left;
}
.message .layui-tab-title li:not(:last-child) {
border-right: 1px solid #eee;
}
/** 首屏加载 */
.loader-wrapper {
position: fixed;
width: 100%;
height: 100%;
background-color: whitesmoke;
z-index: 9999999;
}
.loader {
width: 50px;
height: 50px;
margin: 30px auto 40px;
margin-top: 20%;
position: relative;
z-index: 999999;
background-color: whitesmoke;
}
.loader:before {
content: "";
width: 50px;
height: 7px;
border-radius: 50%;
background: #000;
opacity: 0.1;
position: absolute;
top: 59px;
left: 0;
animation: shadow 0.5s linear infinite;
}
.loader:after {
content: "";
width: 50px;
height: 50px;
border-radius: 3px;
background-color: var(--global-primary-color);
position: absolute;
top: 0;
left: 0;
animation: loading 0.5s linear infinite;
}
@-webkit-keyframes loading {
17% {
border-bottom-right-radius: 3px;
}
25% {
transform: translateY(9px) rotate(22.5deg);
}
50% {
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
border-bottom-right-radius: 40px;
}
75% {
transform: translateY(9px) rotate(67.5deg);
}
100% {
transform: translateY(0) rotate(90deg);
}
}
@keyframes loading {
17% {
border-bottom-right-radius: 3px;
}
25% {
transform: translateY(9px) rotate(22.5deg);
}
50% {
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
border-bottom-right-radius: 40px;
}
75% {
transform: translateY(9px) rotate(67.5deg);
}
100% {
transform: translateY(0) rotate(90deg);
}
}
@-webkit-keyframes shadow {
0%,
100% {
transform: scale(1, 1);
}
50% {
transform: scale(1.2, 1);
}
}
@keyframes shadow {
0%,
100% {
transform: scale(1, 1);
}
50% {
transform: scale(1.2, 1);
}
}
+356
View File
@@ -0,0 +1,356 @@
/** loader */
.pear-admin-dark .loader-wrapper,
.pear-admin-dark .loader-wrapper .loader {
background-color: #141414;
}
/** layout */
.pear-admin-dark .layui-layout {
background-color: #141414;
}
/** header */
.pear-admin-dark .layui-header,
.pear-admin-dark .layui-header .layui-logo {
background-color: #141414 !important;
box-shadow: none !important;
border: none !important;
}
.pear-admin-dark .layui-header.auto-theme,
.pear-admin-dark .layui-header.auto-theme .layui-logo {
background-color: var(--global-primary-color) !important;
}
.pear-admin-dark .layui-header.auto-theme .layui-logo .title {
color: #ffffff !important;
}
.pear-admin-dark .layui-header {
border: 1px solid rgba(0, 0, 0, 0.4) !important;
}
.pear-admin-dark .layui-header .layui-nav * {
color: #ffffff !important;
}
.pear-admin-dark .layui-header .layui-nav .layui-nav-child {
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.6) !important;
background-color: #141414;
border-color: #141414;
}
.pear-admin-dark .layui-header .layui-nav .layui-nav-child dd > a:hover {
background-color: #141414 !important;
}
.pear-admin-dark .layui-header .pear-nav-control .layui-this a {
background-color: #0c0c0c !important;
}
.pear-admin-dark .auto-theme .layui-logo .title {
color: var(--global-primary-color) !important;
}
/** side */
.pear-admin-dark .layui-side {
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.6) !important;
}
.pear-admin-dark .layui-logo {
border-color: rgba(0, 0, 0, 0.3) !important;
}
.pear-admin-dark .layui-side .layui-logo,
.pear-admin-dark .layui-side .layui-side-scroll,
.pear-admin-dark .layui-side .layui-side-scroll .layui-nav-tree {
background-color: #141414 !important;
}
.pear-admin-dark
.layui-side
.layui-side-scroll
.layui-nav-tree
.layui-nav-child {
background-color: rgba(0, 0, 0, 0.3) !important;
}
.pear-admin-dark .layui-side .layui-side-scroll .layui-nav .layui-nav-item a,
.pear-admin-dark
.layui-side
.layui-side-scroll
.layui-nav
.layui-nav-item
a
> .layui-nav-more {
color: rgba(255, 255, 255, 0.7) !important;
}
.pear-admin-dark
.layui-side
.layui-side-scroll
.layui-nav
.layui-nav-child
dd.layui-this
a,
.pear-admin-dark
.layui-side
.layui-side-scroll
.layui-nav
.layui-nav-itemed
> a,
.pear-admin-dark
.layui-side
.layui-side-scroll
.layui-nav
.layui-nav-itemed
> a
> .layui-nav-more,
.pear-admin-dark
.layui-side
.layui-side-scroll
.layui-nav
.layui-nav-item
> a:hover {
color: #ffffff !important;
}
/** body */
.pear-admin-dark .layui-body,
.pear-admin-dark .layui-body .pear-tab-page-loading,
.pear-admin-dark .layui-body .pear-page-loading {
background-color: #0a0a0a !important;
}
.pear-admin-dark .layui-body .layui-tab .layui-tab-title,
.pear-admin-dark .layui-body .layui-tab .layui-tab-title li,
.pear-admin-dark .layui-body .layui-tab .layui-tab-control li {
background-color: #141414 !important;
border-color: rgba(0, 0, 0, 0.3) !important;
color: #ffffff;
}
.pear-admin-dark .layui-body .layui-tab .layui-tab-title li > span:first-child {
background-color: #434343;
}
.pear-admin-dark .layui-body .layui-tab .layui-nav-child.layui-anim {
border-color: #141414;
background-color: #141414 !important;
}
.pear-admin-dark .layui-body .layui-tab .layui-nav-child.layui-anim a {
color: #ffffff;
}
.pear-admin-dark .layui-body .layui-tab .layui-nav-child.layui-anim a:hover {
background-color: #0a0a0a;
}
.pear-admin-dark .layui-body .layui-tab .layui-tab-close:hover {
border-radius: 50%;
background-color: #0a0a0a !important;
}
.pear-admin-dark .pear-tab-page-menu ul li {
color: #ffffff !important;
}
.pear-admin-dark .layui-footer {
background-color: #141414;
border-top: 1px solid #141414;
}
/** theme */
.pear-admin-dark .set-text,
.pear-admin-dark .select-color-title,
.pear-admin-dark .color-title {
color: #ffffff;
}
/** search */
.pear-admin-dark .menu-search-no-data {
color: #ffffff;
}
.pear-admin-dark .menu-search-tips * {
color: #ffffff;
}
.pear-admin-dark .menu-search-tips kbd {
border-color: rgba(0, 0, 0, 0.3) !important;
}
/** message center */
.pear-admin-dark .pear-message-center .layui-tab-title,
.pear-admin-dark .pear-message-center .message-item {
border-color: rgba(0, 0, 0, 0.3) !important;
color: #ffffff;
}
/** button */
.pear-admin-dark .layui-btn {
color: #ffffff;
border-color: #4c4d4f;
}
/** layer */
.pear-admin-dark .layui-layer {
background-color: #141414;
}
.pear-admin-dark .layui-layer-msg {
border-color: #141414;
}
.pear-admin-dark .layui-layer-msg .layui-layer-content {
color: #e5eaf3;
}
.pear-admin-dark .layui-layer .layui-layer-setwin > span,
.pear-admin-dark .layui-layer .layui-layer-title {
color: #ffffff;
}
/** card */
.pear-admin-dark .layui-card {
background-color: #1d1e1f !important;
}
.pear-admin-dark .layui-card .layui-card-header {
border-bottom-color: #414243;
color: #ffffff;
}
.pear-admin-dark .layui-card .layui-card-body {
color: #ffffff;
}
/** input */
.pear-admin-dark .layui-input {
background-color: transparent;
color: #ffffff;
border-color: rgba(0, 0, 0, 0.3) !important;
}
/** switch */
.pear-admin-dark .layui-form-switch {
border-color: #484849;
background-color: rgba(255, 255, 255, 0.08);
}
/** table */
.pear-admin-dark .layui-table {
background-color: transparent;
}
.pear-admin-dark .layui-table tr:hover {
background-color: #141414 !important;
}
.pear-admin-dark .layui-table td,
.pear-admin-dark .layui-table th,
.pear-admin-dark .layui-table-view,
.pear-admin-dark .layui-table-page,
.pear-admin-dark .layui-table-tool,
.pear-admin-dark .layui-table-header {
border-color: rgba(0, 0, 0, 0.4) !important;
}
.pear-admin-dark .layui-laypage select,
.pear-admin-dark .layui-laypage button {
border-color: rgba(0, 0, 0, 0.4) !important;
color: #ffffff !important;
background-color: transparent;
}
.pear-admin-dark .layui-table-tool-self > div {
border-color: rgba(0, 0, 0, 0.4) !important;
color: #ffffff !important;
background-color: transparent;
}
/** panel */
.pear-admin-dark .layui-panel {
background-color: #1d1e1f !important;
border-color: #1d1e1f !important;
}
/** menu */
.pear-admin-dark .layui-menu {
background-color: #1d1e1f !important;
}
.pear-admin-dark .layui-menu .layui-menu-body-title,
.pear-admin-dark .layui-menu .layui-menu-body-title:hover {
color: #ffffff;
background-color: #1d1e1f !important;
}
/** timeline */
.pear-admin-dark .layui-timeline-axis {
background-color: rgb(29, 30, 31) !important;
}
.pear-admin-dark .layui-timeline-item:before {
background-color: #414243 !important;
}
/** toast */
.pear-admin-dark .iziToast {
background-color: #1f1f1f !important;
}
/** console */
.pear-admin-dark .deputy,
.pear-admin-dark .shortcut-menu {
background-color: #141414 !important;
}
.pear-admin-dark .deputy:hover,
.pear-admin-dark .shortcut-menu:hover {
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.6) !important;
}
.pear-admin-dark .message-board li {
border-bottom: 1px solid rgba(0, 0, 0, 0.4) !important;
}
/** analysis */
.pear-admin-dark .top-panel-number {
color: #ffffff !important;
border-color: #414243;
}
.pear-admin-dark .dynamic-status dd {
border-color: #414243;
}
/** success failure */
.pear-admin-dark .pear-result .content {
background-color: rgba(153, 153, 153, 0.12);
color: #e5eaf3;
}
.pear-admin-dark .pear-result .title {
color: #ffffff;
}
.pear-admin-dark .pear-result .description {
color: #8d9095;
}
/** 403 404 500*/
.pear-admin-dark .pear-exception .title p {
color: #e5eaf3 !important;
}
/** scroll */
.pear-admin-dark *::-webkit-scrollbar-thumb {
background: #141414;
border-radius: 4px;
}
.pear-admin-dark *::-webkit-scrollbar-thumb:hover {
background: #0a0a0a;
}
+65
View File
@@ -0,0 +1,65 @@
.top-panel {
border-radius: 4px;
text-align: center;
}
.top-panel > .layui-card-body {
height: 60px;
}
.top-panel-number {
line-height: 60px;
font-size: 29px;
border-right: 1px solid #eceff9;
}
.top-panel-tips {
padding-left: 8px;
padding-top: 16px;
line-height: 30px;
font-size: 12px;
}
.top-panel-tips i {
font-size: 33px;
}
.top-panel-tips svg {
margin-top: -12px;
width: 50px;
height: 50px;
}
.dynamic-status {
padding: 0 10px 10px;
}
.dynamic-status dd {
padding: 15px 0;
border-bottom: 1px solid #eee;
display: -webkit-flex;
display: flex;
}
.dynamic-status dd div.dynamic-status-img {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 15px;
}
.dynamic-status dd div.dynamic-status-img a {
width: 100%;
height: 100%;
display: inline-block;
text-align: center;
line-height: 32px;
}
.dynamic-status dd div span {
color: #bbb;
}
.dynamic-status dd div a {
color: #01aaed;
}
+80
View File
@@ -0,0 +1,80 @@
.shortcut-menu {
width: 100%;
height: 66px;
background-color: #f8f8f8;
display: inline-block;
border-radius: 5px;
text-align: center;
margin-bottom: 3px;
}
.shortcut-menu i {
font-size: 30px;
height: 66px;
line-height: 66px;
}
.shortcut-menu:hover {
box-shadow: 2px 0 8px 0 lightgray !important;
}
.shortcut-menu-label {
width: 100%;
display: inline-block;
text-align: center;
}
.deputy {
width: 100%;
height: 90px;
background-color: #f8f8f8;
display: inline-block;
border-radius: 5px;
text-align: center;
margin-bottom: 3px;
}
.deputy:hover {
box-shadow: 2px 0 8px 0 lightgray !important;
}
.deputy .deputy-label {
color: gray;
margin-top: 14px;
font-size: 14px;
}
.deputy .deputy-count {
margin-top: 12px;
color: var(--global-primary-color);
font-size: 30px;
}
.message-board {
padding: 0 10px 10px;
}
.message-board li {
position: relative;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.message-board li p {
padding-bottom: 10px;
padding-top: 3px;
}
.message-board li > span {
color: #999;
height: 24px;
line-height: 24px;
}
.message-board .message-board-reply {
position: absolute;
right: 20px;
bottom: 12px;
height: 24px;
line-height: 24px;
}
+28
View File
@@ -0,0 +1,28 @@
.pear-exception {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
box-sizing: border-box;
padding: 70px 40px;
}
.pear-exception .title {
margin-top: 20px;
}
.pear-exception .title p {
color: rgb(0, 0, 0);
font-size: 20px;
}
.pear-exception .description {
margin-top: 10px;
color: #8d9095;
font-size: 14px;
}
.pear-exception .extra {
margin: 30px;
}
+180
View File
@@ -0,0 +1,180 @@
html,
body,
.layui-row {
height: 100%;
}
.login-page {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
.login-bg {
height: 100%;
box-sizing: border-box;
background-color: rgb(250, 250, 250);
display: flex;
align-items: center;
justify-content: center;
}
.login-bg-img {
width: 90%;
display: inline-block;
margin: 0 auto;
}
.login-form {
height: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
}
.form-center {
background: #fff;
box-sizing: border-box;
flex-flow: row wrap;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.form-center-box {
width: 360px;
}
.top-log-title {
align-items: center;
justify-content: center;
display: flex;
margin-bottom: 30px;
}
.top-log {
width: 50px;
border-radius: 12px;
margin-right: 20px;
height: 50px;
}
.top-log-title span {
font-size: 32px;
font-weight: 700;
color: var(--global-primary-color);
}
.top-desc {
font-size: 14px;
color: #808695;
}
.tab-log-method {
width: 100%;
display: flex;
padding: 20px 0px;
}
.tab-log-method-item {
flex: 1;
box-sizing: border-box;
padding: 5px 50px;
text-align: right;
color: #1f2225;
font-size: 16px;
}
.tab-log-method-item:nth-child(2) {
text-align: left;
}
.tab-log-method-item > span {
display: inline-block;
width: 40px;
text-align: center;
height: 30px;
border-bottom: 2px solid transparent;
}
.tab-log-method-item > span:hover {
cursor: pointer;
color: #16baaa;
border-bottom: 2px solid #16baaa;
}
.tab-log-verification {
width: 100%;
display: flex;
}
.verification-text {
flex: 2;
box-sizing: border-box;
margin-right: 20px;
}
.verification-img {
flex: 1;
box-sizing: border-box;
border: 1px solid #eeeeee;
border-radius: 4px;
height: 40px;
overflow: hidden;
text-align: center;
}
.remember-passsword {
margin: 20px 0;
width: 100%;
display: flex;
box-sizing: border-box;
}
.remember-cehcked {
flex: 1;
text-align: left;
}
.greenText {
color: #16baaa;
cursor: pointer;
}
.login-btn {
width: 100%;
box-sizing: border-box;
}
.login-btn > .layui-btn {
width: 100%;
}
.other-login {
width: 100%;
box-sizing: border-box;
margin: 20px 0 0;
text-align: left;
display: flex;
}
.other-login-methods {
display: inline-block;
flex: 1;
}
.layui-input {
border-radius: 4px;
line-height: 40px;
height: 40px;
}
.layui-btn {
border-radius: 4px;
background-color: var(--global-primary-color);
}
+77
View File
@@ -0,0 +1,77 @@
.text-center {
text-align: center;
}
.user-info-head {
width: 110px;
height: 110px;
line-height: 110px;
position: relative;
display: inline-block;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
margin: 0 auto;
}
.layui-line-dash {
border-bottom: 1px dashed #ccc;
margin: 15px 0;
}
.blog-title {
padding-left: 13.5px;
}
.blog-content {
padding-left: 13px;
font-size: 13px;
color: dimgray;
}
.layui-tab-title {
border-bottom: none;
}
.fl-item {
height: 30px;
font-size: 13.5;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50px;
background-color: gray;
display: inline-block;
margin-right: 10px;
}
.message-board {
padding: 0 10px 10px;
}
.message-board li {
position: relative;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.message-board li p {
padding-bottom: 10px;
padding-top: 3px;
}
.message-board li > span {
color: #999;
height: 24px;
line-height: 24px;
}
.message-board .message-board-reply {
position: absolute;
right: 20px;
bottom: 12px;
height: 24px;
line-height: 24px;
}
+36
View File
@@ -0,0 +1,36 @@
.pear-result {
text-align: center;
}
.pear-result .success svg {
color: #32c682;
text-align: center;
margin-top: 40px;
}
.pear-result .error svg {
color: #f56c6c;
text-align: center;
margin-top: 40px;
}
.pear-result .title {
margin-top: 25px;
}
.pear-result .description {
margin-top: 25px;
width: 60%;
margin-left: 20%;
color: rgba(0, 0, 0, 0.45);
}
.pear-result .content {
margin-top: 20px;
width: 80%;
border-radius: 4px;
background-color: whitesmoke;
padding: 20px 32px;
margin-left: 10%;
margin-bottom: 30px;
text-align: left;
}
.pear-result .extra {
padding-top: 10px;
margin-top: 25px;
}
+40
View File
@@ -0,0 +1,40 @@
.layui-dropdown {
border-radius: var(--global-border-radius);
}
.layui-input {
border-radius: var(--global-border-radius);
}
.layui-form-onswitch {
background-color: var(--global-primary-color) !important;
}
.layui-btn {
background-color: var(--global-primary-color);
}
.layui-btn.layui-btn-normal {
background-color: #1e9fff !important;
}
.layui-btn.layui-btn-danger {
background-color: #ff5722 !important;
}
.layui-btn.layui-btn-warm {
background-color: #ffb800 !important;
}
.layui-btn.layui-btn-primary {
background-color: transparent !important;
color: #5f5f5f !important;
}
.layui-card {
border-radius: var(--global-border-radius);
}
.layui-timeline-axis {
color: var(--global-primary-color);
}
+5
View File
@@ -0,0 +1,5 @@
:root {
--global-primary-color: #16baaa;
--global-border-radius: 4px;
}
+305
View File
@@ -0,0 +1,305 @@
[
{
"id": 1,
"title": "工作空间",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [
{
"id": "10",
"title": "分析页",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_component",
"href": "view/analysis/index.html"
},
{
"id": "11",
"title": "工作台",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_component",
"href": "view/console/index.html"
}
]
},
{
"id": 9,
"title": "列表页面",
"icon": "layui-icon layui-icon-auz",
"type": 0,
"children": [
{
"id": 91,
"title": "查询表格",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/listing/table.html"
}
]
},
{
"id": 2,
"title": "扩展组件",
"icon": "layui-icon layui-icon-auz",
"type": 0,
"children": [
{
"id": 23,
"title": "高级栅格",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/component/grid.html"
},
{
"id": 24,
"title": "消息提示",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/component/toast.html"
},
{
"id": 25,
"title": "路由过渡",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/component/nprogress.html"
}
]
},
{
"id": "result",
"title": "结果页面",
"icon": "layui-icon layui-icon-auz",
"type": 0,
"href": "",
"children": [
{
"id": "success",
"title": "成功页面",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/result/success.html"
},
{
"id": "failure",
"title": "失败页面",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_component",
"href": "view/result/error.html"
}
]
},
{
"id": "exception",
"title": "异常页面",
"icon": "layui-icon layui-icon-auz",
"type": 0,
"href": "",
"children": [
{
"id": "403",
"title": "403",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/exception/403.html"
},
{
"id": "404",
"title": "404",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_component",
"href": "view/exception/404.html"
},
{
"id": "500",
"title": "500",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_component",
"href": "view/exception/500.html"
}
]
},
{
"id": "open",
"title": "菜单模式",
"icon": "layui-icon layui-icon-auz",
"type": 0,
"children": [
{
"id": "a",
"title": "普通路由",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/result/success.html"
},
{
"id": "b",
"title": "嵌套网页",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "http://www.layui-vue.com"
},
{
"id": "c",
"title": "新建标签",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_blank",
"href": "http://www.layui-vue.com"
},
{
"id": "d",
"title": "弹窗网页",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_layer",
"href": "http://www.layui-vue.com"
}
]
},
{
"id": "deep",
"title": "深度测试",
"icon": "layui-icon layui-icon-auz",
"type": 0,
"href": "",
"children": [
{
"id": "deep1-1",
"title": "二级菜单",
"icon": "layui-icon layui-icon-face-smile",
"type": 0,
"openType": "_component",
"href": "view/result/success.html",
"children": [
{
"id": "deep1-1-1",
"title": "三级菜单",
"icon": "layui-icon layui-icon-face-smile",
"type": 0,
"openType": "_component",
"href": "view/result/success.html",
"children": [
{
"id": "deep1-1-1-1",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/result/success.html"
},
{
"id": "deep1-1-1-2",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_blank",
"href": "http://www.layui-vue.com"
}
]
},
{
"id": "deep1-1-2",
"title": "三级菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 0,
"openType": "_blank",
"href": "http://www.layui-vue.com",
"children": [
{
"id": "deep1-1-2-1",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/result/success.html"
},
{
"id": "deep1-1-2-2",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_blank",
"href": "http://www.layui-vue.com"
}
]
}
]
},
{
"id": "deep1-2",
"title": "二级菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 0,
"openType": "_blank",
"href": "http://www.layui-vue.com",
"children": [
{
"id": "deep1-2-1",
"title": "三级菜单",
"icon": "layui-icon layui-icon-face-smile",
"type": 0,
"openType": "_component",
"href": "view/result/success.html",
"children": [
{
"id": "deep1-2-1-1",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/result/success.html"
},
{
"id": "deep1-2-1-2",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_blank",
"href": "http://www.layui-vue.com"
}
]
},
{
"id": "deep1-2-2",
"title": "三级菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 0,
"openType": "_blank",
"href": "http://www.layui-vue.com",
"children": [
{
"id": "deep1-2-2-1",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_component",
"href": "view/result/success.html"
},
{
"id": "deep1-2-2-2",
"title": "四级菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_blank",
"href": "http://www.layui-vue.com"
}
]
}
]
}
]
}
]
+102
View File
@@ -0,0 +1,102 @@
{
"code": 200,
"data": [
{
"id": 1,
"title": "通知",
"children": [
{
"id": 11,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
"title": "你收到了 14 份新周报",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 12,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
"title": "曲妮妮 已通过第三轮面试",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 11,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
"title": "可以区分多种通知类型",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 12,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
"title": "左侧图标用于区分不同的类型",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 11,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
"title": "内容不要超过两行字",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
}
]
},
{
"id": 2,
"title": "消息",
"children": [
{
"id": 11,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
"title": "你收到了 14 份新周报",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 12,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
"title": "曲妮妮 已通过第三轮面试",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 11,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
"title": "可以区分多种通知类型",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 12,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
"title": "左侧图标用于区分不同的类型",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
},
{
"id": 11,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
"title": "内容不要超过两行字",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "刚刚"
}
]
},
{
"id": 3,
"title": "代办",
"children": []
}
]
}
+303
View File
@@ -0,0 +1,303 @@
{
"code": 0,
"msg": "",
"count": 1000,
"totalRow": {
"era": {
"tang": "2",
"song": "2",
"xian": "20"
}
},
"data": [
{
"id": "10001",
"username": "就眠儀式",
"email": "test1@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10002",
"username": "杜甫",
"email": "test2@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "108",
"joinTime": "2016-10-14",
"LAY_CHECKED": true
},
{
"id": "10003",
"username": "就眠儀式",
"email": "test3@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10004",
"username": "就眠儀式",
"email": "test4@email.com",
"sex": "女",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10005",
"username": "就眠儀式",
"email": "test5@email.com",
"sex": "女",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10006",
"username": "就眠儀式",
"email": "test6@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10007",
"username": "就眠儀式",
"email": "test7@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10008",
"username": "就眠儀式",
"email": "test8@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10009",
"username": "就眠儀式",
"email": "test9@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10010",
"username": "就眠儀式",
"email": "test10@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10011",
"username": "就眠儀式",
"email": "test11@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10012",
"username": "就眠儀式",
"email": "test12@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10013",
"username": "就眠儀式",
"email": "test13@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10014",
"username": "就眠儀式",
"email": "test14@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10015",
"username": "就眠儀式",
"email": "test15@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10016",
"username": "就眠儀式",
"email": "test16@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10017",
"username": "就眠儀式",
"email": "test17@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10018",
"username": "就眠儀式",
"email": "test18@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10019",
"username": "就眠儀式",
"email": "test19@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10020",
"username": "就眠儀式",
"email": "test20@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10021",
"username": "就眠儀式",
"email": "test21@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10022",
"username": "就眠儀式",
"email": "test22@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10023",
"username": "就眠儀式",
"email": "test23@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
},
{
"id": "10024",
"username": "就眠儀式",
"email": "test24@email.com",
"sex": "男",
"city": "浙江杭州",
"sign": "花开堪折直须折,莫待无花空折枝.",
"experience": "100",
"ip": "192.168.0.8",
"checkin": "106",
"joinTime": "2016-10-14"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

+68
View File
@@ -0,0 +1,68 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs/>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"/>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"/>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"/>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"/>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"/>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"/>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "/>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"/>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"/>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"/>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"/>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"/>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"/>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "/>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"/>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"/>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"/>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"/>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"/>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"/>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"/>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"/>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"/>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"/>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"/>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"/>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "/>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"/>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"/>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"/>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"/>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"/>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"/>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"/>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"/>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"/>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"/>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"/>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More