123 Commits
Author SHA1 Message Date
zhengxinonlyandGitee 55ea1e7252 !82 update README.md.
Merge pull request !82 from alan968/N/A
2023-04-06 04:51:15 +00:00
alan968andGitee b80b869dd8 update README.md.
更新README.md文档中在拉取代码时的步骤。

Signed-off-by: alan968 <2669660794@qq.com>
2023-03-23 12:21:18 +00:00
zhengxinonlyandGitee 7fea9743c9 !50 修复文件管理上传错误 2022-09-30 11:57:45 +00:00
mengfu188 c09812f85d 修复文件管理上传错误 2022-09-29 13:39:37 +08:00
zhengxinonlyandGitee 794a8a57ad !48 修复创建权限时的bug
修复创建权限时的bug
2022-09-05 15:45:16 +00:00
mengfu188 cab60da886 修复创建权限时的bug 2022-09-04 15:55:00 +08:00
zhengxinonly 7501ae1dc2 fix:提交冲突 2022-05-24 18:59:51 +08:00
zhengxinonly 9fc2eb0ae3 style(jinja2):修改模板的复用方式 2022-05-24 18:53:37 +08:00
zhengxinonly 12d4dfe410 break:移除flask-restful 2022-05-21 02:10:26 +08:00
zhengxinonly d54eb4dc3e wip:(重构接口实现方式5) 2022-05-20 23:56:11 +08:00
zhengxinonly 246ce3b3b7 20220512 2022-05-12 04:17:58 +08:00
zhengxinonly 36250fad43 wip:(重构接口实现方式4) 2022-05-11 23:31:58 +08:00
zhengxinonly 0d25b479e5 wip:(重构接口实现方式3) 2022-05-10 23:43:33 +08:00
zhengxinonly 7e6aecc99b wip:(重构接口实现方式2) 2022-05-10 01:13:37 +08:00
zhengxinonly 82de7b0273 wip:(重构接口实现方式) 2022-05-09 03:30:41 +08:00
zhengxinonly 8760f21b03 添加服务器配置文件 2022-03-30 20:54:30 +08:00
zhengxinonly 1e34ef339a fix(修复模型名) 2022-03-11 21:26:36 +08:00
zhengxinonlyandGitee 5333a68416 !36 修复多写了一个rights导致的权限创建和修改问题
Merge pull request !36 from Rayi/N/A
2022-03-05 10:59:34 +00:00
RayiandGitee decde3a69a 修复多写了一个rights导致的权限创建和修改问题 2022-03-05 08:39:42 +00:00
zhengxinonly 9dcc05deda fix(power fields) 2022-03-04 15:25:02 +08:00
zhengxinonly c0d963628b refactor(移除生产部署文件) 2022-01-23 01:09:35 +08:00
zhengxinonly 6978002393 refactor(重构程序&目录结构) 2022-01-23 00:43:39 +08:00
zhengxinonly 3453b01938 refactor(javascript):移除esm,es6语法 2021-12-08 15:43:10 +08:00
zhengxinonly 820ab9f5c6 refactor(api):移除marshal序列化 2021-12-07 20:24:07 +08:00
不胜舟andGitee 9490ccb7c0 !29 修改模板复用方式
Merge pull request !29 from zhengxinonly/mini
2021-11-21 14:33:49 +00:00
zhengxinonly 056c4b7023 撤销删除模板组件 2021-11-19 22:56:30 +08:00
zhengxinonly d4d8d8b294 refactor(jinja2):修改模板使用继承 2021-11-19 18:38:55 +08:00
zhengxinonly 8bc5d6e0c2 fix:修复权限数据序列化 2021-09-17 04:00:11 +08:00
zhengxinonly 3fa8ae0a8a refactor(api-logging):暂时删除权限校验与日志记录 2021-09-16 02:46:48 +08:00
zhengxinonly 316ebc2441 ''wip(semantic):调整序列化返回结果 2021-09-15 00:45:35 +08:00
zhengxinonly 550f99e468 'wip(semantic):调整模板api接口' 2021-09-12 18:16:02 +08:00
zhengxinonly b9c449ad57 'wip(logging):调整日志记录方式' 2021-09-10 01:17:26 +08:00
zhengxinonly db5b4e89ed wip(semantic):调整模板文件内容 2021-09-09 02:52:15 +08:00
zhengxinonly 733286b090 wip(semantic):调整模板文件内容 2021-09-09 02:49:55 +08:00
zhengxinonly 7505e4552b 'wip(semantic):调整模板文件' 2021-09-08 01:05:54 +08:00
zhengxinonly e918727984 wip(semantic):数据æapi接口改为集中式 2021-09-07 03:43:01 +08:00
zhengxinonly 00053da117 wip(semantic):数据模型语义化 2021-09-06 02:17:36 +08:00
zhengxinonly 27f5d78c35 refactor(dept):changge to iframe 2021-08-16 00:32:32 +08:00
zhengxinonly 97e0160aaf refactor(jinja2):remove-jinja2-urlfor 2021-08-11 03:47:53 +08:00
zhengxinonly 04794e583f fix(menu):修复菜单排序 2021-07-31 01:15:34 +08:00
zhengxinonly 0640255519 fix(issues):修改bug 2021-07-28 00:08:47 +08:00
zhengxinonly 460984f209 fix(rights):修复权限表状态禁用切换 2021-07-27 16:07:37 +08:00
zhengxinonly 635dcc1dac refactor(api):调整模板 2021-07-27 03:34:00 +08:00
zhengxinonly b26237e24b refactor(rest-api):抽取请求地址 2021-07-27 00:12:06 +08:00
zhengxinonly a35d443fb6 refactor(rest-api):角色与用户数据 2021-07-26 19:55:59 +08:00
zhengxinonly ab4bb1ca40 refactor(rest-api):调整部门数据 2021-07-26 14:36:49 +08:00
zhengxinonly 2b7304e615 refactor(rest-api):调整文件接口数据 2021-07-26 04:21:13 +08:00
zhengxinonly 64748e513d refactor(rest-api):调整部门接口数据 2021-07-26 03:42:53 +08:00
mkg 5b07fed518 解决layer关闭 2021-07-24 16:50:32 +08:00
zhengxinonly eb85e30b79 refactor(api):调整后端逻辑-下 2021-07-24 16:27:17 +08:00
zhengxinonly b8e0173a9c refactor(api):调整后端逻辑-上 2021-07-24 03:06:19 +08:00
zhengxinonly 0ca75c81d4 refactor(api):调整前端逻辑 2021-07-23 02:00:10 +08:00
zhengxinonly aa86bbe372 refactor(api):分离部分模板页面 2021-07-23 01:22:28 +08:00
zhengxinonly 86f07817b1 refactor(migrate):重构数据迁移脚本 2021-07-22 15:55:54 +08:00
mkg 3f74011c98 photo.html页面add关闭当前layer 2021-07-22 14:31:41 +08:00
zhengxinonly ce8adda79d refactor(api):调整模板目录 2021-07-22 00:57:22 +08:00
zhengxinonly 5470837707 refactor(api):前后端半分离 2021-07-21 02:19:52 +08:00
zhengxinonly c26f15ae69 feat(supervisor):新增守护进程 2021-07-10 13:54:02 +08:00
zhengxinonly 4d6d998ae2 wip(production):生产环境部署 2021-07-08 23:26:40 +08:00
zhengxinonly d628432621 fix(flask-uploads):修复flask-uploads与Werkzeug2.0.1兼容问题 2021-07-08 22:13:27 +08:00
mkg 2bcaa8e071 升级layui 2021-07-07 13:58:32 +08:00
zhengxinonly ebf76055a4 fix(dept):部门数据更新bug 2021-07-06 02:15:48 +08:00
mkgandGitee b2b08769b0 update README.md. 2021-07-04 09:19:15 +00:00
mkgandGitee aae15fd07a !8 v2新分支
Merge pull request !8 from zhengxinonly/mini
2021-07-03 00:18:38 +00:00
zhengxinonly 945f746d97 修改说明文档 2021-07-03 01:43:56 +08:00
zhengxinonly dce7908041 调整目录结构 2021-07-02 04:39:57 +08:00
zhengxinonly 0ba2b3aa56 修改说明文档 2021-06-29 04:04:42 +08:00
zhengxinonly 17792ccacf flask-restful依赖替换完成 2021-06-28 01:56:05 +08:00
zhengxinonly e3dd5458ad 使用flask-restful实现数据校验与序列化3 2021-06-27 19:27:08 +08:00
zhengxinonly e9bd8a17a0 使用flask-restful实现数据校验与序列化2 2021-06-27 15:33:13 +08:00
zhengxinonly b8870d4c33 使用flask-restful实现数据校验与序列化 2021-06-26 03:17:29 +08:00
zhengxinonly 8fba71c464 调整魔目录结构 2021-06-24 02:42:34 +08:00
zhengxinonly 0cb74c5eb7 修改目录结构2 2021-06-23 23:56:04 +08:00
zhengxinonly 6da6768f81 修改目录结构 2021-06-22 23:42:25 +08:00
zhengxinonly 05dff28523 继续è°workflowï:调整目录结构 2021-06-22 03:18:44 +08:00
zhengxinonly d7c7237cd4 将数据操作全部改为迁移脚本 2021-06-14 02:10:29 +08:00
zhengxinonly 9e8a5357be 将权限与部门的逻辑分包 2021-06-13 15:07:40 +08:00
zhengxinonly 5faf88283f pycharm下格式化模板文件,统一风格 2021-06-13 12:45:11 +08:00
zhengxinonly d9a5bcb0de 调整目录结构 2021-06-12 21:20:13 +08:00
mkg 48633729fb 更新依赖,使用flask新路由定义方法 2021-06-12 12:59:56 +08:00
mkgandGitee f274e060f4 !3 删除弃用代码,解决user.css丢失
Merge pull request !3 from mkg/master
2021-06-12 12:25:26 +08:00
mkg 9784f7816a 删除弃用代码,解决user.css丢失 2021-06-12 12:22:31 +08:00
mkg f93a36c094 加入marshmallow数据验证 2021-06-07 22:57:21 +08:00
mkg 375d3a10b2 更新依赖pear-admin-layui到3.8.3,增加前端渲染配置接口 2021-06-05 13:10:29 +08:00
mkg a1fbf5a0ae 增加xss过滤 2021-06-03 14:38:48 +08:00
mkg 10035cc158 修改部分命名 2021-06-03 11:33:40 +08:00
mkg 3b1150ad1f 优化curd函数在view的引入方式,优化权限与日志的装饰器 2021-06-03 11:26:24 +08:00
mkg 73222e854c 增加日志页面的权限 2021-06-03 11:24:40 +08:00
mkg 3db99422b7 误删除监控页样式 2021-06-03 10:40:26 +08:00
mkg d876c8309f 模板引入优化 2021-06-03 10:37:12 +08:00
mkg e0738aa5b2 完成部门管理,增加.flaskenv配置,抽基本js和css作为引入模板 2021-06-01 17:54:13 +08:00
mkg f051693690 完成主机监控动态图表 2021-05-29 13:00:55 +08:00
mkg c6ffbbc062 删除无用代码 2021-05-13 12:40:37 +08:00
mkg 2836fc1139 修补数据库安装脚本未指定端口问题 2021-05-13 11:35:45 +08:00
mkg e111f4a763 统一table,成功,失败返回响应,封装模型序列化函数 2021-04-29 19:01:18 +08:00
mkg 3d5e4b9e9b 序列化类移到models里,简化代码 2021-04-29 13:09:10 +08:00
mkg 8f4054e576 分解models 2021-04-27 11:58:21 +08:00
mkg 3ea4edd117 修改菜单类型(框架和签页) 2021-04-23 15:48:58 +08:00
mkg 2f0df0a700 增加数据字典 2021-04-16 14:08:13 +08:00
就眠仪式andGitee a709095dc2 !1 Flask_Uploads报错替代方案
Merge pull request !1 from bingyun/master
2021-04-02 17:01:54 +08:00
bingyunandGitee 4a8ac0ac83 update requirement.txt.
使用Flask_Reuploaded==0.5.0代替-e git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc93e684fa0a3a4350a38e41ae00483a796#egg=Flask_Uploads
修复Flask_Uploads报错的问题
2021-04-02 14:56:36 +08:00
mkg b9301046da 更新layui,新增个人信息修改 2021-04-01 23:51:41 +08:00
mkg 3e98b4e656 更新项目简介 2021-03-29 00:09:19 +08:00
mkg dd97ccf8c4 Merge branch 'master' of https://gitee.com/pear-admin/pear-admin-flask into master 2021-03-27 20:47:19 +08:00
mkg 43d2ffa132 解决initDb文件导包路径错误问题 2021-03-27 20:47:06 +08:00
就眠仪式andGitee f4420c211e update README.md. 2021-03-27 11:49:25 +08:00
mkg 6757c5d595 更新README,删除无用的文件,删除无用的代码 2021-03-26 21:22:42 +08:00
mkg 2455351347 用户,角色,权限增加是否启用,修补菜单排序 2021-03-25 20:00:00 +08:00
mkg 0672a13c0a 修改权限编辑中url错误显示为打开方式 2021-03-23 11:37:01 +08:00
mkg cc6a96fc94 解决上传和用户管理时间格式化问题 2021-03-23 09:02:47 +08:00
mkg d97b600f88 Merge branch 'master' of https://gitee.com/pear-admin/pear-admin-flask into master 2021-03-23 08:48:46 +08:00
mkg 94b05256b3 新增日志访问状态,修改日志时间bug,修改权限编辑返回拼写错误 2021-03-23 08:48:24 +08:00
就眠仪式 c15d5aff66 预览文件 2021-03-23 00:46:26 +08:00
就眠仪式 229cb88179 edit readme 2021-03-23 00:39:23 +08:00
就眠仪式 03df5e8014 更新 readme 2021-03-23 00:29:10 +08:00
mkgandGitee a521fcb7dc update README.md. 2021-03-22 20:46:56 +08:00
mkg 51f1201bca Merge branch 'master' of https://gitee.com/pear-admin/pear-admin-flask into master 2021-03-22 20:42:24 +08:00
mkg 4c8917fe05 rbac基本完成 2021-03-22 20:42:02 +08:00
就眠仪式andGitee 115d0c0c5a update README.md. 2021-03-21 23:01:30 +08:00
mkg 328458eda9 完成角色删除和权限编辑 2021-03-20 22:41:00 +08:00
mkg 3dcdf8e1a3 过滤查询,完善基础功能,解决unicode编码问题,增加磁盘监控对于docker容器的判断 2021-03-19 22:20:45 +08:00
mkg d2a2b35317 基本构建 2021-03-18 22:59:02 +08:00
mkgandGitee 3cf62d4305 Initial commit 2021-02-15 23:03:20 +08:00
651 changed files with 287569 additions and 157146 deletions
-69
View File
@@ -1,69 +0,0 @@
[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(可选):"
+3 -4
View File
@@ -1,4 +1,3 @@
FLASK_DEBUG=True # 开启调试模式
FLASK_RUN_PORT=5000 # 设置运行的端口
FLASK_RUN_HOST=0.0.0.0 # 设置监听的 ip
FLASK_APP="pear_admin:create_app()" # 运行程序
FLASK_ENV=development
FLASK_RUN_HOST=0.0.0.0
FLASK_APP='applications:create_app'
+121 -2
View File
@@ -1,5 +1,124 @@
.idea/
.venv/
# Byte-compiled / optimized / DLL files
__pycache__/
*.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/
# 数据库文件
*.db
#上传到本地的图片文件
*upload/
migrations/
-28
View File
@@ -1,28 +0,0 @@
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
@@ -0,0 +1,21 @@
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.
+127 -42
View File
@@ -1,55 +1,140 @@
## 本地启动
<div align="center">
<br/>
<br/>
<img src="https://gitee.com/pear-admin/Pear-Admin-Layui/raw/master/admin/images/logo.png" width="90px" style="margin-top:30px;"/>
<h1 align="center">
Pear Admin Flask
</h1>
<h4 align="center">
开 箱 即 用 的 Flask 快 速 开 发 平 台
</h4>
环境要求:python 3.10 以上,安装了 poetry
[预 览](http://flask.pearadmin.com) | [官 网](http://www.pearadmin.com/) | [群聊](https://jq.qq.com/?_wv=1027&k=5OdSmve) | [社区](http://forum.pearadmin.com/)
初始化环境
```shell
poetry install
<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 基于 Flask 生态的后台管理系统,该项目旨在为 python 开发者提供一个快速开发前后端半分离的后台管理系统的模板
在使用本项目之前最好掌握以下知识点
+ [html、css、JavaScript](https://developer.mozilla.org/zh-CN/docs/Learn/HTML)
+ [jQuery](https://www.w3school.com.cn/jquery/index.asp)
+ [layui](https://www.layui.com/)
+ [flask](https://dormousehole.readthedocs.io/en/latest/)
+ [flask-login](https://flask-login.readthedocs.io/en/latest/)
+ [flask-sqlalchemy](http://www.pythondoc.com/flask-sqlalchemy/quickstart.html)
+ [flask-restful](https://flask-restful.readthedocs.io/en/latest/)
## 预览
Pear Admin Flask 有以下几个版本:
[master分支版本 ](https://gitee.com/pear-admin/pear-admin-flask/tree/master/)
>flask 2.x + flask-sqlalchemy + 权限验证 + Flask-APScheduler 定时任务 + marshmallow 序列化与数据验证
[Mini 分支版本 ](https://gitee.com/pear-admin/pear-admin-flask/tree/mini/)
>flask 2.x + flask-sqlalchemy + Flask-restful + 基于角色的权限管理
| | |
|---------------------|---------------------|
| ![](docs/assets/1.jpg) | ![](docs/assets/2.jpg) |
| ![](docs/assets/3.jpg)| ![](docs/assets/4.jpg) |
| ![](docs/assets/5.jpg) | ![](docs/assets/6.jpg) |
## 内置功能
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
- [x] 权限管理:配置系统菜单,操作权限,按钮权限标识等。
- [x] 角色管理:角色菜单权限分配。
- [x] 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
- [x] 登录日志:系统登录日志记录查询包含登录异常。
- [x] 文件上传: 图片上传示例
## 文档
正在编写
## 安装使用
+ 下载源码
```bash
git clone https://gitee.com/pear-admin/pear-admin-flask
# 进入 pear-admin-flask 代码根目录
cd pear-admin-flask
# 切换分支
git checkout mini
```
初始化数据库
+ 安装依赖
```bash
# 创建虚拟环境
python -m venv venv
```shell
flask init
# 然后使虚拟环境生效(windows)
venv\Scripts\activate
# source venv/bin/activate # Linux激活虚拟环境)
# 安装依赖
pip install -r requirement.txt
```
启动
+ 数据迁移
```shell
默认的使用 `sqlite3` 作为测试环境的数据库进行演示,不需要按照mysql即可查看演示。如果需要二次开发,建议改成 `mysql`
如果需要在开发环境使用 mysql 作为数据库,请查看 `applications/configs/config.py` 文件里面的相关配置文件, 注释掉 sqlite 的配置即可
如果需要修改数据的配置信息,请在 `.flaskenv` 里面调整即可
```bash
flask db init
flask db migrate -m '数据初始化'
flask db upgrade
flask init-db
```
+ 项目启动
```bash
flask run
```
## 贡献指南
## Git 贡献提交规范
如果想参与项目的贡献,提交代码之前需要启用 pre-commit、commitizen 对代码进行校验,运行以下指令即可。
初始化 pre-commit
```shell
pre-commit install
```
检查代码是否符合规范
```shell
git add .
```
```shell
pre-commit run --all-files
```
初始化 commitizen
```shell
pre-commit install -t commit-msg
```
使用
```shell
cz commit
```
代替 `git commit` 进行提交
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
+60
View File
@@ -0,0 +1,60 @@
import os
from flask import Flask, Blueprint
import config
from applications.view import init_view
from common import register_api
from common.flask_uploads import configure_uploads
from common.utils.upload import photos
from extensions import init_plugs
api_bp: Blueprint = Blueprint('api', __name__, url_prefix='/api/v1')
from .rights import register_rights_api
from .system import register_sys_api
from .users import register_users_api
register_rights_api(api_bp)
register_users_api(api_bp)
register_sys_api(api_bp)
def init_api(app: Flask) -> None:
app.register_blueprint(api_bp)
def create_app() -> Flask:
app = Flask('pear-admin-flask')
# 引入数据库配置
app.config.from_object(config)
# 注册各种插件
init_plugs(app)
# 注册路由
init_view(app)
# 注册接口(restful api
init_api(app)
# 文件上传
configure_uploads(app, photos)
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
logo()
return app
def logo():
print('''
_____ _ _ ______ _ _
| __ \ /\ | | (_) | ____| | | |
| |__) |__ __ _ _ __ / \ __| |_ __ ___ _ _ __ | |__ | | __ _ ___| | __
| ___/ _ \/ _` | '__| / /\ \ / _` | '_ ` _ \| | '_ \ | __| | |/ _` / __| |/ /
| | | __/ (_| | | / ____ \ (_| | | | | | | | | | | | | | | (_| \__ \ <
|_| \___|\__,_|_| /_/ \_\__,_|_| |_| |_|_|_| |_| |_| |_|\__,_|___/_|\_\\
''')
+42
View File
@@ -0,0 +1,42 @@
from common import register_api
from .department import DepartmentsApi, DeptEnableAPI
from .rights import (
RightsApi, PowerApi, right_power_enable_resource,
admin_configs_resource, admin_menu_resource
)
from .roles import RoleRoleApi, RolePowerApi, role_deletes, role_enable_resource
def register_rights_api(api_bp):
register_api(DepartmentsApi, 'rights_dept_api', '/dept/department/', pk='_id', app=api_bp)
api_bp.add_url_rule('/dept/department/<int:_id>/status',
view_func=DeptEnableAPI.as_view('dept_status_api'),
methods=['PUT', ])
api_bp.add_url_rule('/rights/rights',
view_func=RightsApi.as_view('dept_rights_api'),
methods=['GET', 'DELETE'])
register_api(PowerApi, 'rights_power_api', '/rights/power/', pk='_id', app=api_bp)
api_bp.add_url_rule('/rights/configs',
endpoint='rights_config_api',
view_func=admin_configs_resource,
methods=['GET'])
api_bp.add_url_rule('/rights/menu',
endpoint='rights_menu_api',
view_func=admin_menu_resource,
methods=['GET'])
api_bp.add_url_rule('/rights/power/<int:_id>/<action>',
endpoint='rights_power_action_api',
view_func=right_power_enable_resource,
methods=['PUT'])
api_bp.add_url_rule('/roles/role',
endpoint='rights_roles_role_api',
view_func=role_deletes,
methods=['DELETE'])
register_api(RoleRoleApi, 'rights_role_api', '/roles/role/', pk='_id', app=api_bp)
register_api(RolePowerApi, 'rights_role_power_api', '/roles/role_power/', pk='_id', app=api_bp)
api_bp.add_url_rule('/roles/role/<int:_id>/status',
endpoint='rights_roles_enable_api',
view_func=role_enable_resource,
methods=['PUT'])
+114
View File
@@ -0,0 +1,114 @@
import typing as t
from flask import jsonify
from flask.views import MethodView
from flask_pydantic import validate
from pydantic import BaseModel, Field
from common.utils.http import success_api, fail_api
from extensions import db
from models import DepartmentModel, UserModel
class DeptModel(BaseModel):
address: t.Optional[str]
dept_name: t.Optional[str] = Field(alias='deptName')
email: t.Optional[str]
leader: t.Optional[str]
parent_id: t.Optional[str] = Field(alias='parentId')
phone: t.Optional[str]
sort: t.Optional[int]
status: t.Optional[int]
class DepartmentsApi(MethodView):
def get(self, _id):
if _id:
dept = DepartmentModel.query.filter_by(id=_id).first()
dept_data = {
'id': dept.id,
'dept_name': dept.dept_name,
'leader': dept.leader,
'email': dept.email,
'phone': dept.phone,
'status': dept.status,
'sort': dept.sort,
'address': dept.address,
}
return dict(success=True, message='ok', dept=dept_data)
dept_data = DepartmentModel.query.order_by(DepartmentModel.sort).all()
# TODO dtree 需要返回状态信息
res = {
"status": {"code": 200, "message": "默认"},
"data": [
{
'deptId': item.id,
'parentId': item.parent_id,
'deptName': item.dept_name,
'sort': item.sort,
'leader': item.leader,
'phone': item.phone,
'email': item.email,
'status': item.status,
'comment': item.comment,
'address': item.address,
'create_at': item.create_at.strftime('%Y-%m-%d %H:%M:%S')
} for item in dept_data
]
}
return jsonify(res)
@validate()
def post(self, body: DeptModel):
dept = DepartmentModel(
parent_id=body.parent_id,
dept_name=body.dept_name,
sort=body.sort,
leader=body.leader,
phone=body.phone,
email=body.email,
status=body.status,
address=body.address
)
db.session.add(dept)
db.session.commit()
return success_api(message="成功")
@validate()
def put(self, _id, body: DeptModel):
data = {
"dept_name": body.dept_name,
"sort": body.sort,
"leader": body.leader,
"phone": body.phone,
"email": body.email,
"status": body.status,
"address": body.address
}
body = DepartmentModel.query.filter_by(id=_id).update(data)
if not body:
return fail_api(message="更新失败")
db.session.commit()
return success_api(message="更新成功")
def delete(self, _id):
ret = DepartmentModel.query.filter_by(id=_id).delete()
UserModel.query.filter_by(dept_id=_id).update({"dept_id": None})
db.session.commit()
if ret:
return success_api(message="删除成功")
return fail_api(message="删除失败")
class DeptEnableAPI(MethodView):
def put(self, _id):
d = DepartmentModel.query.get(_id)
if d:
d.status = not d.status
db.session.commit()
message = '修改成功'
return success_api(message=message)
return fail_api(message="出错啦")
+278
View File
@@ -0,0 +1,278 @@
import copy
from collections import OrderedDict
from typing import Optional
from flask import request, jsonify, current_app
from flask.views import MethodView
from flask_login import current_user
from flask_pydantic import validate
from pydantic import BaseModel, Field
from common.utils.http import success_api, fail_api
from extensions import db
from models import RightModel, RoleModel
def get_render_config():
# 网站配置
config = {
'logo': {
# 网站名称
"title": current_app.config.get("SYSTEM_NAME"),
# 网站图标
"image": "/static/admin/admin/images/logo.png"
# 菜单配置
},
'menu': {
# 菜单数据来源
"data": "/api/v1/rights/menu",
"collaspe": True,
# 是否同时只打开一个菜单目录
"accordion": True,
"method": "GET",
# 是否开启多系统菜单模式
"control": False,
# 默认选中的菜单项
"select": "0",
# 是否开启异步菜单,false 时 data 属性设置为菜单数据,false 时为 json 文件或后端接口
"async": True
},
'tab': {
# 是否开启多选项卡
"muiltTab": True,
# 切换选项卡时,是否刷新页面状态
"keepState": True,
# 是否开启 Tab 记忆
"session": True,
# 最大可打开的选项卡数量
"tabMax": 30,
"index": {
# 标识 ID , 建议与菜单项中的 ID 一致
"id": "10",
# 页面地址
"href": "/admin/welcome",
# 标题
"title": "首页"
}
},
'theme': {
# 默认主题色,对应 colors 配置中的 ID 标识
"defaultColor": "2",
# 默认的菜单主题 dark-theme 黑 / light-theme 白
"defaultMenu": "dark-theme",
# 是否允许用户切换主题,false 时关闭自定义主题面板
"allowCustom": True
},
'colors': [
{
"id": "1",
"color": "#2d8cf0"
},
{
"id": "2",
"color": "#5FB878"
},
{
"id": "3",
"color": "#1E9FFF"
},
{
"id": "4",
"color": "#FFB800"
},
{
"id": "5",
"color": "darkgray"
},
],
'links': current_app.config.get("SYSTEM_PANEL_LINKS"),
'other': {
# 主页动画时长
"keepLoad": 1200,
# 布局顶部主题
"autoHead": False
},
'header': False
}
return config
# 生成菜单树
def make_menu_tree():
role = current_user.role
powers = []
for i in role:
# 如果角色没有被启用就直接跳过
if i.enable == 0:
continue
# 变量角色用户的权限
for p in i.power:
# 如果权限关闭了就直接跳过
if p.enable == 0:
continue
# 一二级菜单
if p.type == 0 or p.type == 1:
powers.append(p)
power_dict = [
{
'id': item.id,
'title': item.name,
'type': item.type,
'code': item.code,
'href': item.url,
'openType': item.open_type,
'parent_id': item.parent_id,
'icon': item.icon,
'sort': item.sort,
'enable': item.enable,
} for item in powers
]
power_dict.sort(key=lambda x: x['id'], reverse=True)
menu_dict = OrderedDict()
for _dict in power_dict:
if _dict['id'] in menu_dict:
# 当前节点添加子节点
_dict['children'] = copy.deepcopy(menu_dict[_dict['id']])
_dict['children'].sort(key=lambda item: item['sort'])
# 删除子节点
del menu_dict[_dict['id']]
if _dict['parent_id'] not in menu_dict:
menu_dict[_dict['parent_id']] = [_dict]
else:
menu_dict[_dict['parent_id']].append(_dict)
return sorted(menu_dict.get(0), key=lambda item: item['sort'])
class PowerModel(BaseModel):
icon: str
open_type: Optional[str] = Field(alias='openType')
parent_id: Optional[str] = Field(alias='parentId')
power_code: Optional[str] = Field(alias='powerCode')
power_name: Optional[str] = Field(alias='powerName')
power_type: Optional[str] = Field(alias='powerType')
power_url: Optional[str] = Field(alias='powerUrl')
sort: Optional[int] = Field(alias='sort')
class RightsApi(MethodView):
def get(self):
"""获取选择父节点"""
power = RightModel.query.all()
# power_data = marshal(power, RightModel.fields())
power_data = [
{
'powerId': item.id,
'powerName': item.name,
'powerType': item.type,
'powerUrl': item.url,
'openType': item.open_type,
'parentId': item.parent_id,
'icon': item.icon,
'sort': item.sort,
'enable': item.enable,
} for item in power
]
power_data.append({"powerId": 0, "powerName": "顶级权限", "parentId": -1})
res = {
"status": {"code": 200, "message": "默认"},
"data": power_data
}
return res
def delete(self):
ids = request.form.getlist('ids[]')
for id in ids:
role = RoleModel.query.filter_by(id=id).first()
# 删除该角色的权限和用户
role.power = []
role.user = []
r = RoleModel.query.filter_by(id=id).delete()
db.session.commit()
return success_api(message="批量删除成功")
class PowerApi(MethodView):
@validate()
def post(self, body: PowerModel):
power = RightModel(
icon=body.icon,
open_type=body.open_type,
parent_id=body.parent_id,
code=body.power_code,
name=body.power_name,
type=body.power_type,
url=body.power_url,
sort=body.sort,
enable=1
)
try:
db.session.add(power)
db.session.commit()
except Exception as e:
print(e)
return fail_api(message='数据提交失败')
return success_api(message="成功")
def delete(self, _id):
# 删除权限(目前没有判断父节点自动删除子节点)
power = RightModel.query.filter_by(id=_id).first()
role_id_list = []
roles = power.role
for role in roles:
role_id_list.append(role.id)
roles = RoleModel.query.filter(RoleModel.id.in_(role_id_list)).all()
for p in roles:
power.role.remove(p)
r = RightModel.query.filter_by(id=_id).delete()
db.session.commit()
if r:
return success_api(message="删除成功")
else:
return fail_api(message="删除失败")
@validate()
def put(self, _id, body: PowerModel):
data = {
"icon": body.icon,
"open_type": body.open_type,
"parent_id": body.parent_id,
"code": body.power_code,
"name": body.power_name,
"type": body.power_type,
"url": body.power_url,
"sort": body.sort
}
power = RightModel.query.filter_by(id=_id).update(data)
db.session.commit()
if not power:
return fail_api(message="更新权限失败")
return success_api(message="更新权限成功")
def right_power_enable_resource(_id, action):
power = RightModel.query.get(_id)
if power:
power.enable = not power.enable
db.session.commit()
message = "修改成功"
return success_api(message=message)
else:
return fail_api(message="出错啦")
def admin_configs_resource():
return get_render_config()
def admin_menu_resource():
menu_tree = make_menu_tree()
return jsonify(menu_tree)
+162
View File
@@ -0,0 +1,162 @@
from flask import request
from flask.views import MethodView
from common.utils.http import table_api, success_api, fail_api
from extensions import db
from models import RightModel, RoleModel
def role_deletes():
ids = request.form.getlist('ids[]')
for id in ids:
role = RoleModel.query.filter_by(id=id).first()
# 删除该角色的权限和用户
role.power = []
role.user = []
r = RoleModel.query.filter_by(id=id).delete()
db.session.commit()
return success_api(message="批量删除成功")
class RoleRoleApi(MethodView):
def get(self, _id):
if not _id:
page = request.args.get('page', default=1, type=int)
limit = request.args.get('limit', default=10, type=int)
role_name = request.args.get('roleName', default="", type=str)
role_code = request.args.get('roleCode', default="", type=str)
filters = []
if role_name:
filters.append(RoleModel.name.like('%' + role_name + '%'))
if role_code:
filters.append(RoleModel.code.like('%' + role_code + '%'))
paginate = RoleModel.query.filter(*filters).paginate(page=page, per_page=limit, error_out=False)
return table_api(
result={
'items': [{'id': item.id,
'roleName': item.name,
'roleCode': item.code,
'enable': item.enable,
'comment': item.comment,
'details': item.details,
'sort': item.sort,
} for item in paginate.items],
'total': paginate.total}
, code=0)
def post(self):
# TODO 添加校验
details = request.json.get('details', '')
enable = request.json.get('enable', 0)
role_code = request.json.get('roleCode', '')
role_name = request.json.get('roleName', '')
sort = request.json.get('sort', 0)
role = RoleModel(
details=details,
enable=int(enable),
code=role_code,
name=role_name,
sort=int(sort)
)
db.session.add(role)
db.session.commit()
return success_api(message="成功")
# 更新角色
def put(self, _id):
# TODO 添加校验
role_code = request.json.get('roleCode', 0)
role_name = request.json.get('roleName', '')
sort = request.json.get('sort', 0) # int
enable = request.json.get('enable', 0) # int
details = request.json.get('details', '')
data = {
"code": role_code,
"name": role_name,
"sort": sort,
"enable": enable,
"details": details
}
role = RoleModel.query.filter_by(id=_id).update(data)
db.session.commit()
if not role:
return fail_api(message="更新角色失败")
return success_api(message="更新角色成功")
def role_enable_resource(_id):
"""启用用户"""
ret = RoleModel.query.get(_id)
ret.enable = not ret.enable
db.session.commit()
message = "修改成功"
if not ret:
return fail_api(message="出错啦")
return success_api(message=message)
class RolePowerApi(MethodView):
def get(self, _id):
# 获取角色权限
role = RoleModel.query.filter_by(id=_id).first()
# 获取权限列表的 id
check_powers_list = [rp.id for rp in role.power]
powers = RightModel.query.all() # 获取所有的权限
powers = [
{
'powerId': item.id,
'powerName': item.name,
'powerType': item.type,
'powerUrl': item.url,
'openType': item.open_type,
'parentId': item.parent_id,
'icon': item.icon,
'sort': item.sort,
'enable': item.enable,
} for item in powers]
for i in powers:
if int(i.get("powerId")) in check_powers_list:
i["checkArr"] = "1"
else:
i["checkArr"] = "0"
return {
"data": powers,
"status": {"code": 200, "message": "默认"}
}
# 保存角色权限
def put(self, _id):
power_ids = request.json.get('powerIds', '')
power_list = power_ids.split(',')
""" 更新角色权限 """
role = RoleModel.query.filter_by(id=_id).first()
powers = RightModel.query.filter(RightModel.id.in_(power_list)).all()
role.power = powers
db.session.commit()
return success_api(message="授权成功")
# 角色删除
def delete(self, _id):
role = RoleModel.query.filter_by(id=_id).first()
# 删除该角色的权限和用户
role.power = []
role.user = []
r = RoleModel.query.filter_by(id=_id).delete()
db.session.commit()
if not r:
return fail_api(message="角色删除失败")
return success_api(message="角色删除成功")
+10
View File
@@ -0,0 +1,10 @@
from flask import Blueprint
from common import register_api
from .file import FilePhotoAPI
from .passport import LoginAPI
def register_sys_api(api_bp):
register_api(LoginAPI, 'login_api', '/passport/login', pk='_id', app=api_bp)
register_api(FilePhotoAPI, 'photo_api', '/file/photo/', pk='photo_id', app=api_bp)
+78
View File
@@ -0,0 +1,78 @@
from flask import request, jsonify
from flask.views import MethodView
from sqlalchemy import desc
from common.utils.http import fail_api, success_api, table_api
from common.utils.upload import upload_one, delete_photo_by_id
from models import PhotoModel
class FilePhotoAPI(MethodView):
def get(self, photo_id):
if photo_id is None:
page = request.args.get('page', type=int, default=1)
limit = request.args.get('limit', type=int, default=10)
photo_paginate = PhotoModel.query.order_by(
desc(PhotoModel.create_at)).paginate(
page=page, per_page=limit, error_out=False)
data = [
{
'id': item.id,
'name': item.name,
'href': item.href,
'mime': item.mime,
'size': item.size,
'ext': item.ext if hasattr(item, 'ext') else "",
'create_at': str(item.create_at),
} for item in photo_paginate.items
]
return table_api(
result={
'items': data,
'total': photo_paginate.total,
},
code=0)
else:
# 显示一张图片
item = PhotoModel.query.get(photo_id)
return table_api(
result={
'items': {
'id': item.id,
'name': item.name,
'href': item.href,
'mime': item.mime,
'size': item.size,
'ext': item.ext if hasattr(item, 'ext') else "",
'create_at': str(item.create_at),
},
'total': 1,
},
code=0
)
def post(self):
if 'file' in request.files:
photo = request.files['file']
mime = request.files['file'].content_type
file_url = upload_one(photo=photo, mime=mime)
res = {
"message": "上传成功",
"code": 0,
"success": True,
"data": {"src": file_url},
}
return jsonify(res)
return fail_api()
def delete(self, photo_id):
res = delete_photo_by_id(photo_id)
if res:
return success_api(message="删除成功")
else:
return fail_api(message="删除失败")
def put(self, photo_id):
pass
+52
View File
@@ -0,0 +1,52 @@
from flask import render_template, make_response
from flask import session, redirect, url_for
from flask.views import MethodView
from flask_login import current_user, login_user
from flask_pydantic import validate
from pydantic import BaseModel
from common.gen_captcha import add_auth_session
from common.utils.http import fail_api, success_api
from common.utils.rights import record_logging
from models import UserModel
class LoginModel(BaseModel):
username: str
password: str
captcha: str
class LoginAPI(MethodView):
def get(self):
if current_user.is_authenticated:
return redirect(url_for('admin.index'))
return make_response(render_template('index/login.html'))
@validate()
def post(self, body: LoginModel):
s_code = session.get("code", None)
session["code"] = None
if body.captcha != s_code:
return fail_api(message="验证码错误")
user = UserModel.query.filter_by(username=body.username).first()
if user is None:
return fail_api(message="不存在的用户")
if user.enable == 0:
return fail_api(message="用户被暂停使用")
if user.validate_password(body.password):
# 登录
login_user(user)
# 记录登录日志
record_logging()
# 存入权限
add_auth_session()
return success_api(message="登录成功")
record_logging()
return fail_api(message="用户名或密码错误")
+14
View File
@@ -0,0 +1,14 @@
from common import register_api
from .user import UserApi, user_role_resource, user_info
def register_users_api(api_bp):
api_bp.add_url_rule('/users/user/<int:_id>/<action>',
view_func=user_info,
methods=['PUT'])
register_api(UserApi, 'users_api', '/users/user/', pk='_id', app=api_bp)
api_bp.add_url_rule('/users/user/<int:_id>/role',
view_func=user_role_resource,
methods=['PUT'])
+243
View File
@@ -0,0 +1,243 @@
import typing as t
from flask import request, jsonify
from flask.views import MethodView
from flask_login import current_user
from flask_pydantic import validate
from pydantic import BaseModel, Field
from sqlalchemy import desc
from common.utils.http import fail_api, success_api, table_api
from extensions import db
from models import LogModel
from models import UserModel, RoleModel, DepartmentModel
def get_current_user_logs():
""" 获取当前用户日志 """
log = LogModel.query.filter_by(url='/passport/login').filter_by(uid=current_user.id).order_by(
desc(LogModel.create_at)).limit(10)
return log
def is_user_exists(username):
""" 判断用户是否存在 """
res = UserModel.query.filter_by(username=username).count()
return bool(res)
def delete_by_id(_id):
""" 删除用户 """
user = UserModel.query.filter_by(id=_id).first()
roles_id = []
for role in user.role:
roles_id.append(role.id)
roles = RoleModel.query.filter(RoleModel.id.in_(roles_id)).all()
for r in roles:
user.role.remove(r)
res = UserModel.query.filter_by(id=_id).delete()
db.session.commit()
return res
def batch_remove(ids):
""" 批量删除 """
for _id in ids:
delete_by_id(_id)
def update_user_role(_id, roles_list):
user = UserModel.query.filter_by(id=_id).first()
roles_id = []
for role in user.role:
roles_id.append(role.id)
roles = RoleModel.query.filter(RoleModel.id.in_(roles_id)).all()
for r in roles:
user.role.remove(r)
roles = RoleModel.query.filter(RoleModel.id.in_(roles_list)).all()
for r in roles:
user.role.append(r)
db.session.commit()
def users_delete():
"""批量删除"""
ids = request.form.getlist('ids[]')
batch_remove(ids)
return success_api(message="批量删除成功")
class QueryModel(BaseModel):
page: int = 1
limit: int = 10
real_name: t.Optional[str] = Field(alias='realName')
username: t.Optional[str]
dept_id: t.Optional[str] = Field(alias='deptId', default=0)
phone: t.Optional[str]
sort: t.Optional[int]
status: t.Optional[int]
class PersonModel(BaseModel):
role_ids: str = Field(alias='roleIds')
username: str
real_name: str = Field(alias='realName')
password: str
class UserApi(MethodView):
"""修改用户数据"""
@validate()
def get(self, _id, query: QueryModel):
filters = []
if query.real_name:
filters.append(UserModel.realname.like('%' + query.real_name + '%'))
if query.username:
filters.append(UserModel.username.like('%' + query.username + '%'))
if query.dept_id:
filters.append(UserModel.dept_id == query.dept_id)
paginate = UserModel.query.filter(
*filters).paginate(
page=query.page, per_page=query.limit, error_out=False)
dept_name = lambda _id: DepartmentModel.query.filter_by(id=_id).first().dept_name if _id else ""
user_data = [{
'id': item.id,
'username': item.username,
'realname': item.realname,
'enable': item.enable,
'create_at': str(item.create_at),
'update_at': str(item.update_at),
'dept': dept_name(item.dept_id),
} for item in paginate.items]
return table_api(
result={
'items': user_data,
'total': paginate.total,
}
, code=0
)
@validate()
def post(self, body: PersonModel):
"""新建单个用户"""
role_ids = body.role_ids.split(',')
if is_user_exists(body.username):
return fail_api(message="用户已经存在")
user = UserModel()
user.username = body.username
user.realname = body.real_name
user.set_password(body.password)
db.session.add(user)
db.session.commit()
""" 增加用户角色 """
user = UserModel.query.filter_by(id=user.id).first()
roles = RoleModel.query.filter(RoleModel.id.in_(role_ids)).all()
for r in roles:
user.role.append(r)
db.session.commit()
return success_api(message="增加成功", code=0)
def delete(self, _id):
# 删除用户
res = delete_by_id(_id)
if not res:
return fail_api(message="删除失败")
return success_api(message="删除成功")
class PersonModel2(BaseModel):
role_ids: str = Field(alias='roleIds')
user_id: str = Field(alias='userId')
username: str
real_name: str = Field(alias='realName')
dept_id: str = Field(alias='deptId')
@validate()
def user_role_resource(_id, body: PersonModel2):
role_ids = body.role_ids.split(',')
# 更新用户数据
UserModel.query.filter_by(id=_id).update({'username': body.username,
'realname': body.real_name,
'dept_id': body.dept_id})
db.session.commit()
update_user_role(_id, role_ids)
return success_api(message="更新成功")
def user_info(_id, action):
if action == 'info':
real_name = request.json.get('realName', '')
username = request.json.get('username', '')
remark = request.json.get('remark', '')
details = request.json.get('details', '')
ret = UserModel.query.get(_id)
ret.username = username
ret.realname = real_name
ret.remark = details
db.session.commit()
if not ret:
return fail_api(message="出错啦")
return success_api(message="更新成功")
elif action == 'status':
user_id = int(request.json.get('userId', 0)) # int
operate = int(request.json.get('operate', 0)) # int
if operate not in [0, 1]:
return {'status': 'error', 'message': '请求有误'}
if operate == 1:
user = UserModel.query.get(_id)
user.enable = operate
message = success_api(message="启动成功")
else:
user = UserModel.query.filter_by(id=user_id).update({"enable": operate})
message = success_api(message="禁用成功")
if user:
db.session.commit()
else:
return fail_api(message="出错啦")
return message
elif action == 'avatar':
url = request.json.get("avatar").get("src")
ret = UserModel.query.get(_id)
ret.avatar = url
db.session.commit()
if not ret:
return fail_api(message="出错啦")
return success_api(message="修改成功")
elif action == 'password':
oldPassword = request.json.get('oldPassword', '')
newPassword = request.json.get('newPassword', '')
confirmPassword = request.json.get('confirmPassword', '')
if not all([oldPassword, newPassword, confirmPassword]):
return {'status': 'error', 'message': '密码不能为空'}
if newPassword != confirmPassword:
return fail_api(message='确认密码不一致')
""" 修改当前用户密码 """
user = UserModel.query.get(_id)
is_right = user.validate_password(oldPassword)
if not is_right:
return jsonify(success=False, message="旧密码错误")
user.set_password(newPassword)
db.session.add(user)
db.session.commit()
return jsonify(success=True, message="更改成功")
else:
return jsonify(success=False, message="操作有误")
+17
View File
@@ -0,0 +1,17 @@
from flask import Flask
from .index import index_bp
from .logs_view import logs_bp
from .roles import role_bp
from . import department
from . import file
from . import rights
from . import passport
from . import users
def init_view(app: Flask):
app.register_blueprint(index_bp)
app.register_blueprint(logs_bp)
app.register_blueprint(role_bp)
+28
View File
@@ -0,0 +1,28 @@
from flask import render_template, request
from models import DepartmentModel
from common.utils.rights import permission_required, view_logging_required
from applications.view import index_bp
@index_bp.get('/dept')
@view_logging_required
@permission_required("admin:dept:main")
def dept_index():
return render_template('admin/department/dept.html')
@index_bp.get('/dept/add')
@view_logging_required
@permission_required("admin:dept:add")
def add():
return render_template('admin/department/dept_add.html')
@index_bp.get('/dept/edit')
@view_logging_required
@permission_required("admin:dept:edit")
def edit():
dept_id = request.args.get("deptId", type=int)
dept = DepartmentModel.query.get(dept_id)
return render_template('admin/department/dept_edit.html', dept=dept)
+18
View File
@@ -0,0 +1,18 @@
from flask import render_template
from applications.view import index_bp
from common.utils.rights import view_logging_required, permission_required
@index_bp.get('/file')
@view_logging_required
@permission_required("admin:file:main")
def file_index():
return render_template('admin/file/photo.html')
@index_bp.get('/file/photo/add')
@view_logging_required
@permission_required("admin:file:main")
def file_photo_add():
return render_template('admin/file/photo_add.html')
+24
View File
@@ -0,0 +1,24 @@
from flask import Blueprint
from flask import render_template
from flask_login import login_required, current_user
index_bp = Blueprint('index', __name__)
@index_bp.route('/')
def index():
return render_template('index/index.html')
# 首页
@index_bp.get('/admin/')
@login_required
def admin_index():
return render_template('index/admin_index.html', user=current_user)
# 控制台页面
@index_bp.get('/admin/welcome')
@login_required
def welcome():
return render_template('index/welcome.html')
+69
View File
@@ -0,0 +1,69 @@
from flask import Blueprint, request, render_template
from sqlalchemy import desc
from common.utils.http import table_api
from common.utils.rights import permission_required
from models import LogModel
logs_bp = Blueprint('logs', __name__, url_prefix='/logs')
@logs_bp.get('/')
@permission_required("admin:log:main")
def index():
return render_template('admin/logs_temp/main.html')
@logs_bp.get('/login_log')
@permission_required("admin:log:main")
def login_log():
page = request.args.get('page', type=int)
limit = request.args.get('limit', type=int)
log_paginate = LogModel.query.filter_by(
url='/api/v1/passport/login').order_by(
desc(LogModel.create_at)).paginate(
page=page, per_page=limit, error_out=False)
data = [
{
'id': item.id,
'method': item.method,
'uid': item.uid,
'url': item.url,
'desc': item.desc,
'ip': item.ip,
'success': item.success,
'user_agent': item.user_agent,
'create_at': item.create_at.strftime('%Y-%m-%d %H:%M:%S'),
} for item in log_paginate.items
]
return table_api(result={'items': data,
'total': log_paginate.total, },
code=0)
@logs_bp.get('/access_log')
@permission_required("admin:log:main")
def operate_log():
page = request.args.get('page', type=int)
limit = request.args.get('limit', type=int)
log_paginate = LogModel.query.filter(
LogModel.url != '/api/v1/passport/login').order_by(
desc(LogModel.create_at)).paginate(
page=page, per_page=limit, error_out=False)
data = [
{
'id': item.id,
'method': item.method,
'uid': item.uid,
'url': item.url,
'desc': item.desc,
'ip': item.ip,
'success': item.success,
'user_agent': item.user_agent,
'create_at': item.create_at.strftime('%Y-%m-%d %H:%M:%S'),
} for item in log_paginate.items
]
return table_api(result={'items': data,
'total': log_paginate.total, },
code=0)
+32
View File
@@ -0,0 +1,32 @@
from flask import session, redirect, render_template, url_for
from flask_login import login_required, logout_user, current_user
from common.gen_captcha import get_captcha_image
from common.utils.http import success_api
# 获取验证码
from applications.view import index_bp
@index_bp.get('/passport/getCaptcha')
def get_captcha():
resp, code = get_captcha_image()
session["code"] = code
return resp
# 退出登录
@index_bp.post('/passport/logout')
@login_required
def logout():
logout_user()
session.pop('permissions')
return success_api(message="注销成功")
@index_bp.get('/passport/login')
def login():
if current_user.is_authenticated:
return redirect(url_for('admin.index'))
# TODO 分离视图操作 最终实现接口登录与视图登录两套逻辑
return render_template('index/login.html')
+32
View File
@@ -0,0 +1,32 @@
from flask import render_template
from common.utils.rights import permission_required, view_logging_required
from models import RightModel
from applications.view import index_bp
@index_bp.get('/rights/')
@view_logging_required
@permission_required("admin:power:main")
def rights_index():
return render_template('admin/rights/rights.html')
@index_bp.get('/rights/power/<int:power_id>')
@view_logging_required
@permission_required("admin:power:edit")
def rights_edit(power_id):
power = RightModel.query.filter_by(id=power_id).first()
icon = str(power.icon).split()
if len(icon) == 2:
icon = icon[1]
else:
icon = None
return render_template('admin/rights/rights_edit.html', power=power, icon=icon)
@index_bp.get('/rights/add')
@view_logging_required
@permission_required("admin:power:main")
def rights_add():
return render_template('admin/rights/rights_add.html')
+39
View File
@@ -0,0 +1,39 @@
from flask import Blueprint, render_template
from common.utils.rights import permission_required, view_logging_required
from models import RoleModel
role_bp = Blueprint('role', __name__, url_prefix='/admin/role')
# 角色而管理
@role_bp.get('/')
@view_logging_required
@permission_required("admin:role:main")
def main():
return render_template('admin/roles/roles.html')
# 角色授权操作
@role_bp.get('/power/<int:role_id>')
@view_logging_required
@permission_required("admin:role:power")
def power(role_id):
return render_template('admin/roles/roles_power.html', role_id=role_id)
# 角色编辑
@role_bp.get('/edit/<int:role_id>')
@view_logging_required
@permission_required("admin:role:edit")
def role_editor(role_id):
role = RoleModel.query.filter_by(id=role_id).first()
return render_template('admin/roles/roles_edit.html', role=role)
@role_bp.get('/add')
@view_logging_required
@permission_required("admin:role:edit")
def role_add():
return render_template('admin/roles/roles_add.html')
+49
View File
@@ -0,0 +1,49 @@
from flask import render_template
from flask_login import login_required, current_user
from sqlalchemy import desc
from common.utils.rights import permission_required, view_logging_required
from models import LogModel, RoleModel, UserModel
from . import index_bp
# 用户增加
@index_bp.get('/users/')
@view_logging_required
@permission_required("admin:user:main")
def users_main():
return render_template('admin/users/users.html')
@index_bp.get('/users/add')
@view_logging_required
@permission_required("admin:user:add")
def users_add_view():
roles = RoleModel.query.all()
return render_template('admin/users/users_add.html', roles=roles)
@index_bp.get('/users/<user_id>')
@view_logging_required
@permission_required("admin:user:edit")
def users_user_id_view(user_id):
# 获取编辑用户信息
user = UserModel.query.filter_by(id=user_id).first()
roles = RoleModel.query.all()
checked_roles = []
for r in user.role:
checked_roles.append(r.id)
return render_template('admin/users/users_edit.html', user=user, roles=roles, checked_roles=checked_roles)
@index_bp.get('/users/center')
@login_required
def users_center():
user_logs = LogModel.query.filter_by(url='/passport/login').filter_by(uid=current_user.id).order_by(
desc(LogModel.create_at)).limit(10)
return render_template('admin/users/profile.html', user_info=current_user, user_logs=user_logs)
@index_bp.get('/users/avatar')
def users_avatar_view():
return render_template('admin/users/profile_avatar.html')
+5
View File
@@ -0,0 +1,5 @@
def register_api(view, endpoint, url, pk='id', pk_type='int', app=None):
view_func = view.as_view(endpoint)
app.add_url_rule(url, defaults={pk: None}, view_func=view_func, methods=['GET', ])
app.add_url_rule(url, view_func=view_func, methods=['POST', ])
app.add_url_rule(f'{url}<{pk_type}:{pk}>', view_func=view_func, methods=['GET', 'PUT', 'DELETE'])
+504
View File
@@ -0,0 +1,504 @@
# -*- coding: utf-8 -*-
"""
为了解决 flask_uploads 与 Werkzeug 2.0.1 冲突而直接复制源码进行适配
原始地址: https://github.com/maxcountryman/flask-uploads/blob/master/flask_uploads.py
"""
import sys
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
else:
string_types = basestring,
import os.path
import posixpath
from flask import current_app, send_from_directory, abort, url_for
from itertools import chain
from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename
from flask import Blueprint
# Extension presets
#: This just contains plain text files (.txt).
TEXT = ('txt',)
#: This contains various office document formats (.rtf, .odf, .ods, .gnumeric,
#: .abw, .doc, .docx, .xls, and .xlsx). Note that the macro-enabled versions
#: of Microsoft Office 2007 files are not included.
DOCUMENTS = tuple('rtf odf ods gnumeric abw doc docx xls xlsx'.split())
#: This contains basic image types that are viewable from most browsers (.jpg,
#: .jpe, .jpeg, .png, .gif, .svg, and .bmp).
IMAGES = tuple('jpg jpe jpeg png gif svg bmp'.split())
#: This contains audio file types (.wav, .mp3, .aac, .ogg, .oga, and .flac).
AUDIO = tuple('wav mp3 aac ogg oga flac'.split())
#: This is for structured data files (.csv, .ini, .json, .plist, .xml, .yaml,
#: and .yml).
DATA = tuple('csv ini json plist xml yaml yml'.split())
#: This contains various types of scripts (.js, .php, .pl, .py .rb, and .sh).
#: If your Web server has PHP installed and set to auto-run, you might want to
#: add ``php`` to the DENY setting.
SCRIPTS = tuple('js php pl py rb sh'.split())
#: This contains archive and compression formats (.gz, .bz2, .zip, .tar,
#: .tgz, .txz, and .7z).
ARCHIVES = tuple('gz bz2 zip tar tgz txz 7z'.split())
#: This contains shared libraries and executable files (.so, .exe and .dll).
#: Most of the time, you will not want to allow this - it's better suited for
#: use with `AllExcept`.
EXECUTABLES = tuple('so exe dll'.split())
#: The default allowed extensions - `TEXT`, `DOCUMENTS`, `DATA`, and `IMAGES`.
DEFAULTS = TEXT + DOCUMENTS + IMAGES + DATA
class UploadNotAllowed(Exception):
"""
This exception is raised if the upload was not allowed. You should catch
it in your view code and display an appropriate message to the user.
"""
def tuple_from(*iters):
return tuple(itertools.chain(*iters))
def extension(filename):
ext = os.path.splitext(filename)[1]
if ext.startswith('.'):
# os.path.splitext retains . separator
ext = ext[1:]
return ext
def lowercase_ext(filename):
"""
This is a helper used by UploadSet.save to provide lowercase extensions for
all processed files, to compare with configured extensions in the same
case.
.. versionchanged:: 0.1.4
Filenames without extensions are no longer lowercased, only the
extension is returned in lowercase, if an extension exists.
:param filename: The filename to ensure has a lowercase extension.
"""
if '.' in filename:
main, ext = os.path.splitext(filename)
return main + ext.lower()
# For consistency with os.path.splitext,
# do not treat a filename without an extension as an extension.
# That is, do not return filename.lower().
return filename
def addslash(url):
if url.endswith('/'):
return url
return url + '/'
def patch_request_class(app, size=64 * 1024 * 1024):
"""
By default, Flask will accept uploads to an arbitrary size. While Werkzeug
switches uploads from memory to a temporary file when they hit 500 KiB,
it's still possible for someone to overload your disk space with a
gigantic file.
This patches the app's request class's
`~werkzeug.BaseRequest.max_content_length` attribute so that any upload
larger than the given size is rejected with an HTTP error.
.. note::
In Flask 0.6, you can do this by setting the `MAX_CONTENT_LENGTH`
setting, without patching the request class. To emulate this behavior,
you can pass `None` as the size (you must pass it explicitly). That is
the best way to call this function, as it won't break the Flask 0.6
functionality if it exists.
.. versionchanged:: 0.1.1
:param app: The app to patch the request class of.
:param size: The maximum size to accept, in bytes. The default is 64 MiB.
If it is `None`, the app's `MAX_CONTENT_LENGTH` configuration
setting will be used to patch.
"""
if size is None:
if isinstance(app.request_class.__dict__['max_content_length'],
property):
return
size = app.config.get('MAX_CONTENT_LENGTH')
reqclass = app.request_class
patched = type(reqclass.__name__, (reqclass,),
{'max_content_length': size})
app.request_class = patched
def config_for_set(uset, app, defaults=None):
"""
This is a helper function for `configure_uploads` that extracts the
configuration for a single set.
:param uset: The upload set.
:param app: The app to load the configuration from.
:param defaults: A dict with keys `url` and `dest` from the
`UPLOADS_DEFAULT_DEST` and `DEFAULT_UPLOADS_URL`
settings.
"""
config = app.config
prefix = 'UPLOADED_%s_' % uset.name.upper()
using_defaults = False
if defaults is None:
defaults = dict(dest=None, url=None)
allow_extns = tuple(config.get(prefix + 'ALLOW', ()))
deny_extns = tuple(config.get(prefix + 'DENY', ()))
destination = config.get(prefix + 'DEST')
base_url = config.get(prefix + 'URL')
if destination is None:
# the upload set's destination wasn't given
if uset.default_dest:
# use the "default_dest" callable
destination = uset.default_dest(app)
if destination is None: # still
# use the default dest from the config
if defaults['dest'] is not None:
using_defaults = True
destination = os.path.join(defaults['dest'], uset.name)
else:
raise RuntimeError("no destination for set %s" % uset.name)
if base_url is None and using_defaults and defaults['url']:
base_url = addslash(defaults['url']) + uset.name + '/'
return UploadConfiguration(destination, base_url, allow_extns, deny_extns)
def configure_uploads(app, upload_sets):
"""
Call this after the app has been configured. It will go through all the
upload sets, get their configuration, and store the configuration on the
app. It will also register the uploads module if it hasn't been set. This
can be called multiple times with different upload sets.
.. versionchanged:: 0.1.3
The uploads module/blueprint will only be registered if it is needed
to serve the upload sets.
:param app: The `~flask.Flask` instance to get the configuration from.
:param upload_sets: The `UploadSet` instances to configure.
"""
if isinstance(upload_sets, UploadSet):
upload_sets = (upload_sets,)
if not hasattr(app, 'upload_set_config'):
app.upload_set_config = {}
set_config = app.upload_set_config
defaults = dict(dest=app.config.get('UPLOADS_DEFAULT_DEST'),
url=app.config.get('UPLOADS_DEFAULT_URL'))
for uset in upload_sets:
config = config_for_set(uset, app, defaults)
set_config[uset.name] = config
should_serve = any(s.base_url is None for s in set_config.values())
if '_uploads' not in app.blueprints and should_serve:
app.register_blueprint(uploads_mod)
class All(object):
"""
This type can be used to allow all extensions. There is a predefined
instance named `ALL`.
"""
def __contains__(self, item):
return True
#: This "contains" all items. You can use it to allow all extensions to be
#: uploaded.
ALL = All()
class AllExcept(object):
"""
This can be used to allow all file types except certain ones. For example,
to ban .exe and .iso files, pass::
AllExcept(('exe', 'iso'))
to the `UploadSet` constructor as `extensions`. You can use any container,
for example::
AllExcept(SCRIPTS + EXECUTABLES)
"""
def __init__(self, items):
self.items = items
def __contains__(self, item):
return item not in self.items
class UploadConfiguration(object):
"""
This holds the configuration for a single `UploadSet`. The constructor's
arguments are also the attributes.
:param destination: The directory to save files to.
:param base_url: The URL (ending with a /) that files can be downloaded
from. If this is `None`, Flask-Uploads will serve the
files itself.
:param allow: A list of extensions to allow, even if they're not in the
`UploadSet` extensions list.
:param deny: A list of extensions to deny, even if they are in the
`UploadSet` extensions list.
"""
def __init__(self, destination, base_url=None, allow=(), deny=()):
self.destination = destination
self.base_url = base_url
self.allow = allow
self.deny = deny
@property
def tuple(self):
return (self.destination, self.base_url, self.allow, self.deny)
def __eq__(self, other):
return self.tuple == other.tuple
class UploadSet(object):
"""
This represents a single set of uploaded files. Each upload set is
independent of the others. This can be reused across multiple application
instances, as all configuration is stored on the application object itself
and found with `flask.current_app`.
:param name: The name of this upload set. It defaults to ``files``, but
you can pick any alphanumeric name you want. (For simplicity,
it's best to use a plural noun.)
:param extensions: The extensions to allow uploading in this set. The
easiest way to do this is to add together the extension
presets (for example, ``TEXT + DOCUMENTS + IMAGES``).
It can be overridden by the configuration with the
`UPLOADED_X_ALLOW` and `UPLOADED_X_DENY` configuration
parameters. The default is `DEFAULTS`.
:param default_dest: If given, this should be a callable. If you call it
with the app, it should return the default upload
destination path for that app.
"""
def __init__(self, name='files', extensions=DEFAULTS, default_dest=None):
if not name.isalnum():
raise ValueError("Name must be alphanumeric (no underscores)")
self.name = name
self.extensions = extensions
self._config = None
self.default_dest = default_dest
@property
def config(self):
"""
This gets the current configuration. By default, it looks up the
current application and gets the configuration from there. But if you
don't want to go to the full effort of setting an application, or it's
otherwise outside of a request context, set the `_config` attribute to
an `UploadConfiguration` instance, then set it back to `None` when
you're done.
"""
if self._config is not None:
return self._config
try:
return current_app.upload_set_config[self.name]
except AttributeError:
raise RuntimeError("cannot access configuration outside request")
def url(self, filename):
"""
This function gets the URL a file uploaded to this set would be
accessed at. It doesn't check whether said file exists.
:param filename: The filename to return the URL for.
"""
base = self.config.base_url
if base is None:
return url_for('_uploads.uploaded_file', setname=self.name,
filename=filename, _external=True)
else:
return base + filename
def path(self, filename, folder=None):
"""
This returns the absolute path of a file uploaded to this set. It
doesn't actually check whether said file exists.
:param filename: The filename to return the path for.
:param folder: The subfolder within the upload set previously used
to save to.
"""
if folder is not None:
target_folder = os.path.join(self.config.destination, folder)
else:
target_folder = self.config.destination
return os.path.join(target_folder, filename)
def file_allowed(self, storage, basename):
"""
This tells whether a file is allowed. It should return `True` if the
given `werkzeug.FileStorage` object can be saved with the given
basename, and `False` if it can't. The default implementation just
checks the extension, so you can override this if you want.
:param storage: The `werkzeug.FileStorage` to check.
:param basename: The basename it will be saved under.
"""
return self.extension_allowed(extension(basename))
def extension_allowed(self, ext):
"""
This determines whether a specific extension is allowed. It is called
by `file_allowed`, so if you override that but still want to check
extensions, call back into this.
:param ext: The extension to check, without the dot.
"""
return ((ext in self.config.allow) or
(ext in self.extensions and ext not in self.config.deny))
def get_basename(self, filename):
return lowercase_ext(secure_filename(filename))
def save(self, storage, folder=None, name=None):
"""
This saves a `werkzeug.FileStorage` into this upload set. If the
upload is not allowed, an `UploadNotAllowed` error will be raised.
Otherwise, the file will be saved and its name (including the folder)
will be returned.
:param storage: The uploaded file to save.
:param folder: The subfolder within the upload set to save to.
:param name: The name to save the file as. If it ends with a dot, the
file's extension will be appended to the end. (If you
are using `name`, you can include the folder in the
`name` instead of explicitly using `folder`, i.e.
``uset.save(file, name="someguy/photo_123.")``
"""
if not isinstance(storage, FileStorage):
raise TypeError("storage must be a werkzeug.FileStorage")
if folder is None and name is not None and "/" in name:
folder, name = os.path.split(name)
basename = self.get_basename(storage.filename)
if name:
if name.endswith('.'):
basename = name + extension(basename)
else:
basename = name
if not self.file_allowed(storage, basename):
raise UploadNotAllowed()
if folder:
target_folder = os.path.join(self.config.destination, folder)
else:
target_folder = self.config.destination
if not os.path.exists(target_folder):
os.makedirs(target_folder)
if os.path.exists(os.path.join(target_folder, basename)):
basename = self.resolve_conflict(target_folder, basename)
target = os.path.join(target_folder, basename)
storage.save(target)
if folder:
return posixpath.join(folder, basename)
else:
return basename
def resolve_conflict(self, target_folder, basename):
"""
If a file with the selected name already exists in the target folder,
this method is called to resolve the conflict. It should return a new
basename for the file.
The default implementation splits the name and extension and adds a
suffix to the name consisting of an underscore and a number, and tries
that until it finds one that doesn't exist.
:param target_folder: The absolute path to the target.
:param basename: The file's original basename.
"""
name, ext = os.path.splitext(basename)
count = 0
while True:
count = count + 1
newname = '%s_%d%s' % (name, count, ext)
if not os.path.exists(os.path.join(target_folder, newname)):
return newname
uploads_mod = Blueprint('_uploads', __name__, url_prefix='/_uploads')
@uploads_mod.route('/<setname>/<path:filename>')
def uploaded_file(setname, filename):
config = current_app.upload_set_config.get(setname)
if config is None:
abort(404)
return send_from_directory(config.destination, filename)
class TestingFileStorage(FileStorage):
"""
This is a helper for testing upload behavior in your application. You
can manually create it, and its save method is overloaded to set `saved`
to the name of the file it was saved to. All of these parameters are
optional, so only bother setting the ones relevant to your application.
:param stream: A stream. The default is an empty stream.
:param filename: The filename uploaded from the client. The default is the
stream's name.
:param name: The name of the form field it was loaded from. The default is
`None`.
:param content_type: The content type it was uploaded as. The default is
``application/octet-stream``.
:param content_length: How long it is. The default is -1.
:param headers: Multipart headers as a `werkzeug.Headers`. The default is
`None`.
"""
def __init__(self, stream=None, filename=None, name=None,
content_type='application/octet-stream', content_length=-1,
headers=None):
FileStorage.__init__(self, stream, filename, name=name,
content_type=content_type, content_length=content_length,
headers=None)
self.saved = None
def save(self, dst, buffer_size=16384):
"""
This marks the file as saved by setting the `saved` attribute to the
name of the file it was saved to.
:param dst: The file to save to.
:param buffer_size: Ignored.
"""
if isinstance(dst, string_types):
self.saved = dst
else:
self.saved = dst.name
+43
View File
@@ -0,0 +1,43 @@
from captcha.image import ImageCaptcha
from io import BytesIO
from PIL import Image
from random import choices
from flask import session, make_response
from flask_login import current_user
def gen_captcha(content='0123456789'):
""" 生成验证码 """
image = ImageCaptcha()
# 获取字符串
captcha_text = "".join(choices(content, k=4))
# 生成图像
captcha_image = Image.open(image.generate(captcha_text))
return captcha_text, captcha_image
# 生成验证码
def get_captcha_image():
code, image = gen_captcha()
out = BytesIO()
session["code"] = code
image.save(out, 'png')
out.seek(0)
resp = make_response(out.read())
resp.content_type = 'image/png'
return resp, code
# 授权路由存入session
def add_auth_session():
role = current_user.role
user_power = []
for i in role:
if i.enable == 0:
continue
for p in i.power:
if p.enable == 0:
continue
user_power.append(p.code)
session['permissions'] = user_power
+35
View File
@@ -0,0 +1,35 @@
import typing as t
def success_api(message: str = "成功", code: int = 200) -> t.Dict:
""" 成功响应 默认值”成功“ """
return {'success': True, 'message': message, 'code': code}
def fail_api(message: str = "失败", code: int = 404) -> t.Dict:
""" 失败响应 默认值“失败” """
return {'success': False, 'message': message, 'code': code}
def table_api(success: bool = True,
message: str = "",
result: t.Union[dict, list] = None,
code: int = 0) -> t.Dict:
"""
动态表格渲染响应
此方法返回数据给前端
{
'success': True,
'code': 10002,
'message': '提示消息',
'result':{'items':[],'total': 100}
}
注:lay_ui 表格数据需要指定 code=0
"""
return {
'success': success,
'message': message,
'code': code,
'result': result,
}
+61
View File
@@ -0,0 +1,61 @@
import typing as t
from functools import wraps
from flask import abort, request, jsonify, session
from flask_login import login_required
from flask_login import current_user
from extensions import db
from models import LogModel
def record_logging(success: bool = True) -> None:
"""
记录用户日志数据
"""
info = {
'method': request.method,
'url': request.path,
'ip': request.remote_addr,
'user_agent': request.headers.get('User-Agent'),
'desc': str(dict(request.values)),
'uid': current_user.id,
'success': success
}
log = LogModel()
for key, value in info.items():
setattr(log, key, value)
db.session.add(log)
db.session.commit()
def view_logging_required(func: t.Callable) -> t.Callable:
"""
日志装饰器,用于记录请求
"""
@wraps(func)
def wrapper(*args, **kwargs) -> t.Callable:
record_logging()
return func(*args, **kwargs)
return wrapper
def permission_required(permission: str) -> t.Callable:
"""
权限装饰器,用于过滤需要的权限
"""
def decorator(func: t.Callable):
@wraps(func)
def wrapper(*args, **kwargs) -> t.Callable:
if permission not in session.get('permissions'):
record_logging(success=False)
abort(403)
return func(*args, **kwargs)
return wrapper
return decorator
+30
View File
@@ -0,0 +1,30 @@
import os
from flask import current_app
from common.flask_uploads import UploadSet, IMAGES
from extensions import db
from models import PhotoModel
photos = UploadSet('photos', IMAGES)
def upload_one(photo, mime):
filename = photos.save(photo)
file_url = photos.url(filename)
upload_url = current_app.config.get("UPLOADED_PHOTOS_DEST")
size = os.path.getsize(upload_url + '/' + filename)
photo = PhotoModel(name=filename, href=file_url, mime=mime, size=size)
db.session.add(photo)
db.session.commit()
return file_url
def delete_photo_by_id(_id):
photo_name = PhotoModel.query.filter_by(id=_id).first().name
photo = PhotoModel.query.filter_by(id=_id).delete()
db.session.commit()
upload_url = current_app.config.get("UPLOADED_PHOTOS_DEST")
os.remove(upload_url + '/' + photo_name)
return photo
+51
View File
@@ -0,0 +1,51 @@
import logging
import os
# 主题面板的链接列表配置
SYSTEM_NAME = "Pear Admin"
SYSTEM_PANEL_LINKS = [
{
"icon": "layui-icon layui-icon-auz",
"title": "官方网站",
"href": "http://www.pearadmin.com"
},
{
"icon": "layui-icon layui-icon-auz",
"title": "开发文档",
"href": "http://www.pearadmin.com"
},
{
"icon": "layui-icon layui-icon-auz",
"title": "开源地址",
"href": "https://gitee.com/Jmysy/Pear-Admin-Layui"
}
]
SECRET_KEY = os.getenv('SECRET_KEY', 'dev key')
# mysql 配置
MYSQL_USERNAME = "root"
MYSQL_PASSWORD = "123456"
MYSQL_HOST = "127.0.0.1"
MYSQL_PORT = 3306
MYSQL_DATABASE = "PearAdminFlask"
# redis 配置
REDIS_HOST = "127.0.0.1"
REDIS_PORT = 6379
""" Sqlalchemy 配置 """
SQLALCHEMY_DATABASE_URI = r'sqlite:///pear_admin.db'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLALCHEMY_ECHO = False
SQLALCHEMY_POOL_RECYCLE = 8
# SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{MYSQL_USERNAME}:{MYSQL_PASSWORD}@\
# {MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}"
LOG_LEVEL = logging.ERROR
# 图片文件存放位置
UPLOADED_PHOTOS_DEST = os.path.join(os.path.dirname(os.path.abspath(__name__)), 'static', 'upload')
UPLOADED_FILES_ALLOW = ['gif', 'jpg']
# JSON配置
JSON_AS_ASCII = False
-36
View File
@@ -1,36 +0,0 @@
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}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+17
View File
@@ -0,0 +1,17 @@
from flask import Flask
from .init_databases import register_script
from .init_sqlalchemy import db, init_databases
from .init_login import init_login_manager
from .init_template_directives import init_template_directives
from .init_error_views import init_error_views
def init_plugs(app: Flask) -> None:
init_login_manager(app)
init_databases(app)
init_template_directives(app)
init_error_views(app)
# 生成测试数据的命令,生成环境可以注释
register_script(app)
+82
View File
@@ -0,0 +1,82 @@
from flask import Flask
import re
from datetime import datetime
date_str = re.compile('\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$')
def add_data(data_list, obj):
from extensions import db
for _data in data_list:
dept = obj()
for key, value in _data.items():
if isinstance(value, str) and date_str.match(value):
value = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
setattr(dept, key, value)
db.session.add(dept)
db.session.commit()
def register_script(app: Flask):
@app.cli.command()
def init_db():
import json
import os
path = os.path.dirname(os.path.abspath(__name__))
path = os.path.join(path, 'static', 'data')
cp_dept_data_list = json.loads(open(os.path.join(path, 'cp_dept.json'), encoding='utf-8').read())
cp_user_data_list = json.loads(open(os.path.join(path, 'cp_user.json'), encoding='utf-8').read())
file_photo_data_list = json.loads(open(os.path.join(path, 'file_photo.json'), encoding='utf-8').read())
rt_power_data_list = json.loads(open(os.path.join(path, 'rt_power.json'), encoding='utf-8').read())
rt_role_data_list = json.loads(open(os.path.join(path, 'rt_role.json'), encoding='utf-8').read())
rt_role_power_data_list = json.loads(open(os.path.join(path, 'rt_role_power.json'), encoding='utf-8').read())
rt_user_role_data_list = json.loads(open(os.path.join(path, 'rt_user_role.json'), encoding='utf-8').read())
"""数据库初始化"""
# 创建化部门数据
from models import DepartmentModel
add_data(cp_dept_data_list, DepartmentModel)
# 图片数据
from models import PhotoModel
add_data(file_photo_data_list, PhotoModel)
# 初始化权限表数据
from models import RightModel
add_data(rt_power_data_list, RightModel)
# 初始化角色表
from models import RoleModel
# 角色权限关系表
from extensions import db
for data in rt_role_power_data_list:
db.session.execute('insert into rt_role_power VALUES (%s, %s, %s);' % tuple(data))
db.session.commit()
add_data(rt_role_data_list, RoleModel)
# 管理员用户
from models import UserModel
add_data(cp_user_data_list, UserModel)
# 用户角色表
from extensions import db
for data in rt_user_role_data_list:
db.session.execute('insert into rt_user_role VALUES (%s, %s, %s);' % tuple(data))
db.session.commit()
@app.cli.command()
def turn():
"""清空数据库"""
from extensions import db
db.drop_all()
db.create_all()
+12
View File
@@ -0,0 +1,12 @@
import os
from dotenv import load_dotenv
root_path = os.path.abspath(os.path.dirname(__file__)).split('applications')[0]
dot_env_path = os.path.join(root_path, '.env')
flask_env_path = os.path.join(root_path, '.flaskenv')
if os.path.exists(dot_env_path):
load_dotenv(dot_env_path)
if os.path.exists(flask_env_path):
load_dotenv(flask_env_path)
+15
View File
@@ -0,0 +1,15 @@
from flask import render_template
def init_error_views(app):
@app.errorhandler(403)
def page_not_found(e):
return render_template('errors/403.html'), 403
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('errors/500.html'), 500
+15
View File
@@ -0,0 +1,15 @@
from flask_login import LoginManager
def init_login_manager(app):
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'index.login'
login_manager.login_message = u'请登录以访问此页面'
@login_manager.user_loader
def load_user(user_id):
from models import UserModel
user = UserModel.query.get(int(user_id))
return user
@@ -1,5 +1,11 @@
from flask_migrate import Migrate
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def init_databases(app: Flask):
db.init_app(app)
migrate.init_app(app, db)
+7
View File
@@ -0,0 +1,7 @@
from flask import session
def init_template_directives(app):
@app.template_global()
def authorize(power):
return bool(power in session.get('permissions'))
+4
View File
@@ -0,0 +1,4 @@
from .file import PhotoModel
from .rights import RightModel, RoleModel
from .system import LogModel
from .users import UserModel, DepartmentModel
+15
View File
@@ -0,0 +1,15 @@
from datetime import datetime
from extensions import db
class PhotoModel(db.Model):
__tablename__ = 'file_photo'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
href = db.Column(db.String(255))
mime = db.Column(db.CHAR(50), nullable=False)
size = db.Column(db.CHAR(30), nullable=False)
create_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
update_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
+46
View File
@@ -0,0 +1,46 @@
from extensions import db
# 创建中间表
user_role = db.Table(
"rt_user_role", # 中间表名称
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment='标识'), # 主键
db.Column("user_id", db.Integer, db.ForeignKey("cp_user.id"), comment='用户编号'), # 属性 外键
db.Column("role_id", db.Integer, db.ForeignKey("rt_role.id"), comment='角色编号'), # 属性 外键
)
# 创建中间表
role_power = db.Table(
"rt_role_power", # 中间表名称
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment='标识'), # 主键
db.Column("power_id", db.Integer, db.ForeignKey("rt_power.id"), comment='用户编号'), # 属性 外键
db.Column("role_id", db.Integer, db.ForeignKey("rt_role.id"), comment='角色编号'), # 属性 外键
)
class RightModel(db.Model):
__tablename__ = 'rt_power'
id = db.Column(db.Integer, primary_key=True, comment='权限编号')
name = db.Column(db.String(255), comment='权限名称')
type = db.Column(db.SMALLINT, comment='权限类型')
code = db.Column(db.String(30), comment='权限标识')
url = db.Column(db.String(255), comment='权限路径')
open_type = db.Column(db.String(10), comment='打开方式')
parent_id = db.Column(db.Integer, db.ForeignKey("rt_power.id"), comment='父类编号')
icon = db.Column(db.String(128), comment='图标')
sort = db.Column(db.Integer, comment='排序')
enable = db.Column(db.Boolean, comment='是否开启')
parent = db.relationship("RightModel", remote_side=[id]) # 自关联
class RoleModel(db.Model):
__tablename__ = 'rt_role'
id = db.Column(db.Integer, primary_key=True, comment='角色ID')
name = db.Column(db.String(255), comment='角色名称')
code = db.Column(db.String(255), comment='角色标识')
enable = db.Column(db.Boolean, comment='是否启用')
comment = db.Column(db.String(255), comment='备注')
details = db.Column(db.String(255), comment='详情')
sort = db.Column(db.Integer, comment='排序')
power = db.relationship('RightModel', secondary="rt_role_power", backref=db.backref('role'))
+18
View File
@@ -0,0 +1,18 @@
from datetime import datetime
from extensions import db
class LogModel(db.Model):
__tablename__ = 'lg_logging'
id = db.Column(db.Integer, primary_key=True)
method = db.Column(db.String(10))
uid = db.Column(db.Integer, default=None)
url = db.Column(db.String(255))
desc = db.Column(db.Text)
ip = db.Column(db.String(255))
success = db.Column(db.Boolean, default=True)
user_agent = db.Column(db.Text)
create_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
update_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
+48
View File
@@ -0,0 +1,48 @@
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from extensions import db
class UserModel(db.Model, UserMixin):
__tablename__ = 'cp_user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='用户ID')
username = db.Column(db.String(20), comment='用户名')
realname = db.Column(db.String(20), comment='真实名字')
mobile = db.Column(db.String(11), comment='电话号码')
avatar = db.Column(db.String(255), comment='头像', default="/static/admin/admin/images/avatar.jpg")
comment = db.Column(db.String(255), comment='备注')
password_hash = db.Column(db.String(128), comment='哈希密码')
enable = db.Column(db.Integer, default=0, comment='启用')
dept_id = db.Column(db.Integer, comment='部门id')
role = db.relationship('RoleModel', secondary="rt_user_role", backref=db.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)
create_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
update_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
class DepartmentModel(db.Model):
__tablename__ = 'cp_dept'
id = db.Column(db.Integer, primary_key=True, comment="部门ID")
parent_id = db.Column(db.Integer, comment="父级编号")
dept_name = db.Column(db.String(50), comment="部门名称")
leader = db.Column(db.String(50), comment="负责人")
phone = db.Column(db.String(20), comment="联系方式")
email = db.Column(db.String(50), comment="邮箱")
status = db.Column(db.Boolean, comment='状态(1开启,0关闭)')
comment = db.Column(db.Text, comment="备注")
address = db.Column(db.String(255), comment="详细地址")
sort = db.Column(db.Integer, comment="排序")
create_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
-32
View File
@@ -1,32 +0,0 @@
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
@@ -1,19 +0,0 @@
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
@@ -1,68 +0,0 @@
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
@@ -1,79 +0,0 @@
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
@@ -1,101 +0,0 @@
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
@@ -1,82 +0,0 @@
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
@@ -1,102 +0,0 @@
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
@@ -1,13 +0,0 @@
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)
-27
View File
@@ -1,27 +0,0 @@
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
@@ -1,64 +0,0 @@
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
@@ -1,20 +0,0 @@
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
@@ -1,13 +0,0 @@
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
@@ -1,28 +0,0 @@
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
@@ -1,59 +0,0 @@
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
@@ -1,30 +0,0 @@
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
@@ -1,56 +0,0 @@
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
@@ -1,9 +0,0 @@
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
@@ -1,38 +0,0 @@
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
@@ -1,23 +0,0 @@
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
@@ -1,935 +0,0 @@
# 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
@@ -1,24 +0,0 @@
[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"
+28
View File
@@ -0,0 +1,28 @@
alembic==1.6.5
aniso8601==9.0.1
captcha==0.3
click==8.0.1
colorama==0.4.4
Flask==2.0.1
Flask-Login==0.5.0
Flask-Migrate==3.0.1
Flask-Pydantic==0.9.0
Flask-SQLAlchemy==2.5.1
greenlet==1.1.0
gunicorn==20.1.0
itsdangerous==2.0.1
Jinja2==3.0.1
Mako==1.1.4
MarkupSafe==2.0.1
Pillow==8.2.0
psutil==5.8.0
pydantic==1.9.1
PyMySQL==1.0.2
python-dateutil==2.8.1
python-dotenv==0.18.0
python-editor==1.0.4
pytz==2021.1
six==1.16.0
SQLAlchemy==1.4.18
typing_extensions==4.2.0
Werkzeug==2.0.1
+446
View File
@@ -0,0 +1,446 @@
html,
body,
.layui-layout {
height: 100%;
}
.pear-admin .layui-header,
.pear-admin .layui-body,
.pear-admin .layui-logo,
.pear-admin .layui-side,
.pear-admin .layui-header .layui-layout-left {
transition: all .3s;
}
.pear-admin .layui-logo .title {
font-size: 20px;
}
.pear-admin .layui-layout-right .layui-nav-child {
border: 1px solid whitesmoke;
border-radius: 6px;
width: 150px;
}
.pear-admin .layui-header {
left: 230px;
width: calc(100% - 230px);
background-color: white;
border-bottom: 1px solid whitesmoke;
}
.pear-admin .layui-header .layui-nav-img {
width: 30px;
height: 30px;
}
.pear-admin .layui-layout-control {
left: 140px;
position: absolute;
}
.pear-admin .layui-layout-control .layui-nav{
padding: 0px;
}
.pear-admin .layui-logo {
width: 230px;
height: 60px;
line-height: 60px;
position: relative;
background-color: #28333E;
}
.pear-admin .layui-logo img {
width: 34px;
height: 34px;
}
.pear-admin .layui-logo .title {
font-size: 21px;
font-weight: 550;
color: #5FB878;
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, .35);
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 {
left: 230px;
bottom: 0px;
padding-bottom: 0px;
}
.pear-admin .layui-layout-left {
left: 0px;
}
/** 隐 藏 布 局 样 式 */
.pear-mini .layui-logo .title {
display: none;
}
.pear-mini .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-logo {
width: 60px;
}
.pear-mini .layui-nav-tree .layui-nav-item span {
display: none;
}
.pear-mini .bottom-nav li {
width: 100% !important;
}
.pear-mini .layui-side-scroll {
height: calc(100% - 60px);
}
.pear-admin .layui-header .layui-nav .layui-nav-bar {
top: 0px !important;
height: 2px !important;
background-color: #5FB878;
}
.pear-admin .layui-header .layui-nav .layui-this:after {
display: none;
}
.pear-admin .layui-header .layui-nav-more {
display: none;
}
.pear-collasped-pe {
display: none;
width: 50px;
position: absolute;
z-index: 400000;
bottom: 30px;
right: 30px;
background-color: #5FB878 !important;
height: 50px;
line-height: 50px;
text-align: center;
border-radius: 50px;
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
}
.pear-collasped-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) {
.collaspe {
display: none !important;
}
.pear-collasped-pe {
display: inline-block !important;
}
.layui-layout-control {
left: 45px !important;
}
.layui-layout-left {
padding-left: 10px;
padding-right: 10px;
}
.pear-mini .bottom-nav {
display: none;
}
.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-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 .6s ease-out;
animation: am-horizontal-roll_show .6s ease-out;
}
/** 亮色侧边风格 */
.light-theme .layui-logo {
background-color: white !important;
color: black !important;
}
.light-theme .layui-side-scroll {
background-color: white !important;
color: black !important;
}
.light-theme .layui-side {
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, .05) !important;
}
/** 主 题 选 择 界 面 样 式 */
.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: -5px;
left: -5px;
border: #5FB878 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 .3s;
-webkit-transition: all .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 {
background-color: gray;
width: 30px;
height: 30px;
border-radius: 3px;
float: left;
margin-left: 20px;
color: white;
font-size: 18px;
text-align: center;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .15);
line-height: 30px;
}
/** 友情链接 */
.more-setting {
margin-top: 45px;
}
.more-setting form {
margin-top: 30px;
}
.more-setting-title {
padding: 15px 0 0px 20px;
margin-bottom: 4px;
}
.more-setting .layui-form-label {
width: 60px;
}
.more-menu-list {
width: 100%;
margin-top: 80px;
}
.more-menu-item:first-child {
border-top: 1px solid #e8e8e8;
}
.more-menu-item .layui-icon {
font-size: 18px;
padding-right: 10px;
}
.more-menu-item {
color: #595959;
height: 50px;
line-height: 50px;
font-size: 16px;
padding: 0 25px;
border-bottom: 1px solid #e8e8e8;
font-style: normal;
display: block;
}
.more-menu-item:hover {
background-color: whitesmoke;
}
.more-menu-item:after {
color: #8c8c8c;
right: 16px;
content: "\e602";
position: absolute;
font-family: layui-icon !important;
}
+105
View File
@@ -0,0 +1,105 @@
.loader-main{
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 .5s linear infinite;
}
.loader:after {
content: "";
width: 50px;
height: 50px;
border-radius: 3px;
background-color: #5FB878;
position: absolute;
top: 0;
left: 0;
animation: loading .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);
}
}
+140
View File
@@ -0,0 +1,140 @@
.top-panel {
border-radius: 4px;
text-align: center;
}
.top-panel>.layui-card-body {
height: 60px;
}
.top-panel-number {
line-height: 60px;
font-size: 30px;
border-right: 1px solid #eceff9;
}
.top-panel-tips {
padding-left: 8px;
padding-top: 16px;
line-height: 30px;
font-size: 12px;
}
.pear-container {
background-color: whitesmoke;
margin: 10px;
}
.card {
width: 100%;
height: 160px;
background-color: whitesmoke;
border-radius: 4px;
}
.card .header .avatar {
width: 28px;
height: 28px;
margin: 20px;
border-radius: 50px;
}
.card .header {
color: dimgray;
}
.card .body {
color: gray;
}
.card .body {
margin-left: 20px;
margin-right: 20px;
}
.card .footer {
margin-left: 20px;
margin-right: 20px;
margin-top: 20px;
font-size: 13px;
color: gray;
position: absolute;
}
.custom-tab .layui-tab-title {
border-bottom-width: 0px;
border-bottom-style: none;
}
.custom-tab .layui-tab-title li {
margin-left: 10px;
}
.list .list-item {
height: 32px;
line-height: 32px;
color: gray;
padding: 5px;
padding-left: 15px;
border-radius: 4px;
margin-top: 5.2px;
}
.list .list-item:hover {
background-color: whitesmoke;
}
.list .list-item .title {
font-size: 13px;
width: 100%;
}
.list .list-item .footer {
position: absolute;
right: 30px;
font-size: 12px;
}
.top-panel-tips i {
font-size: 33px;
}
.layuiadmin-card-status {
padding: 0 10px 10px;
}
.layuiadmin-card-status dd {
padding: 15px 0;
border-bottom: 1px solid #EEE;
display: -webkit-flex;
display: flex;
}
.layuiadmin-card-status dd div.layui-status-img,
.layuiadmin-card-team .layui-team-img {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 15px;
}
.layuiadmin-card-status dd div.layui-status-img a {
width: 100%;
height: 100%;
display: inline-block;
text-align: center;
line-height: 32px;
}
.layuiadmin-card-status dd div span {
color: #BBB;
}
.layuiadmin-card-status dd div a {
color: #01AAED;
}
.top-panel-tips svg {
margin-top: -12px;
width: 50px;
height: 50px;
}
+18
View File
@@ -0,0 +1,18 @@
.pear-container {background-color: whitesmoke;margin: 10px;}
.pear-card {width: 100%;height: 66px;background-color: #F8F8F8;display: inline-block;border-radius: 5px;text-align: center;margin-bottom: 3px;}
.pear-card:hover,.pear-card2:hover{box-shadow: 2px 0 8px 0 lightgray!important;}.pear-card2 {width: 100%;height: 90px;background-color: #F8F8F8;display: inline-block;border-radius: 5px;text-align: center;margin-bottom: 3px;}
.pear-card2 i {font-size: 30px;height: 90px;line-height: 90px;}
.pear-card i {font-size: 30px;height: 66px;line-height: 66px;}
.layui-col-md3 {text-align: center;}
.pear-card-title {margin-top: 3px;}
.person img {width: 90px;height: 90px;border-radius: 4px;margin-top: 8px;margin-left: 8px;}
.pear-card2 .count {color: #51A351;font-size: 30px;margin-top: 12px;}
.pear-card2 .title {color: gray;font-size: 14px;margin-top: 14px;}
.pear-card-status {padding: 0 10px 10px;}
.pear-card-status li {position: relative;padding: 10px 0;border-bottom: 1px solid #EEE;}
.pear-card-status li h3 {padding-bottom: 5px;font-weight: 700;}
.pear-card-status li p {padding-bottom: 10px;padding-top: 3px ;}
.pear-card-status li>span {color: #999;}
.pear-reply {position: absolute;right: 20px;}
.person .title{font-size: 17px;font-weight: 600;margin-left: 18px;margin-top: 16px;position: absolute;display: inline-block;}
.person .desc{font-size: 16px;font-weight: 600;margin-left: 115px;margin-top: -30px;position: absolute;display: inline-block;}
@@ -0,0 +1,6 @@
.organizationTree {
width: 100% !important;
height: -webkit-calc(100vh - 130px);
height: -moz-calc(100vh - 130px);
height: calc(100vh - 130px);
}
+76
View File
@@ -0,0 +1,76 @@
* {
padding: 0;
margin: 0;
font-size: 0.38rem;
}
ul {
list-style: none;
}
a {
text-decoration: none;
-webkit-tap-highlight-color: transparent
}
.clearfix:after {
content: '';
width: 0;
height: 0;
display: block;
clear: both;
}
html {
height: 100%;
width: 100%;
}
body {
font-size: 0.28rem;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
position: relative;
background-color: white !important;
}
.content {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
text-align: center;
}
.content>img {
height: 300px;
max-width: 370px;
margin-right: 180px;
}
.content>* {
display: inline-block;
}
.content-r {
vertical-align: top;
}
.content-r>h1 {
font-size: 72px;
color: #434e59;
margin-bottom: 24px;
font-weight: 600;
}
.content-r>p {
font-size: 20px;
color: rgba(0, 0, 0, .45);
margin-bottom: 16px;
}
button {
margin-top: 20px;
}
+539
View File
@@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
+93
View File
@@ -0,0 +1,93 @@
.layui-form {
width: 320px !important;
margin: auto !important;
margin-top: 160px !important;
}
.layui-form button {
width: 100% !important;
height: 44px !important;
line-height: 44px !important;
font-size: 16px !important;
background-color: #5FB878 !important;
font-weight: 550 !important;
}
.layui-form-checked[lay-skin=primary] i {
border-color: #5FB878 !important;
background-color: #5FB878 !important;
color: #fff !important;
}
.layui-tab-content {
margin-top: 15px !important;
padding-left: 0px !important;
padding-right: 0px !important;
}
.layui-form-item {
margin-top: 20px !important;
}
.layui-input {
height: 44px !important;
line-height: 44px !important;
padding-left: 15px !important;
border-radius: 3px !important;
}
.layui-input:focus {
box-shadow: 0px 0px 2px 1px #5FB878 !important;
}
.layui-form-danger:focus{
box-shadow: 0px 0px 2px 1px #f56c6c !important;
}
.logo {
width: 60px !important;
margin-top: 10px !important;
margin-bottom: 10px !important;
margin-left: 20px !important;
}
.title {
font-size: 30px !important;
font-weight: 550 !important;
margin-left: 20px !important;
color: #5FB878 !important;
display: inline-block !important;
height: 60px !important;
line-height: 60px !important;
margin-top: 10px !important;
position: absolute !important;
}
.desc {
width: 100% !important;
text-align: center !important;
color: gray !important;
height: 60px !important;
line-height: 60px !important;
}
body {
background-repeat:no-repeat;
background-color: whitesmoke;
background-size: 100%;
height: 100%;
}
.code {
float: left;
margin-right: 13px;
margin: 0px !important;
border: #e6e6e6 1px solid;
display: inline-block!important;
}
.codeImage {
float: right;
height: 42px;
border: #e6e6e6 1px solid;
}
+80
View File
@@ -0,0 +1,80 @@
.pear-container {
background-color: whitesmoke;
margin: 10px;
}
.layui-body {
padding: 25px;
}
.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;
}
.comment {
position: absolute;
bottom: 3px;
right: 10px;
font-size: 12px;
color: dimgray;
}
.content {
padding-left: 13px;
font-size: 13px;
color: dimgray;
}
.title {
padding-left: 13.5px;
}
.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;
}
.list .list-item {
height: 32px;
line-height: 32px;
color: gray;
padding: 5px;
padding-left: 15px;
border-radius: 4px;
margin-top: 5.2px;
}
.list .list-item:hover {
background-color: whitesmoke;
}
.list .list-item .title {
font-size: 13px;
width: 100%;
}
.list .list-item .footer {
position: absolute;
right: 30px;
font-size: 12px;
}
+39
View File
@@ -0,0 +1,39 @@
.result {
text-align: center;
}
.result .success svg {
color: #32C682;
text-align: center;
margin-top: 40px;
}
.result .error svg {
color: #f56c6c;
text-align: center;
margin-top: 40px;
}
.result .title {
margin-top: 25px;
}
.result .desc {
margin-top: 25px;
width: 60%;
margin-left: 20%;
color: rgba(0, 0, 0, .45);
}
.result .content {
margin-top: 20px;
width: 80%;
border-radius: 10px;
background-color: whitesmoke;
height: 200px;
margin-left: 10%;
}
.result .action {
padding-top: 10px;
border-top: 1px whitesmoke solid;
margin-top: 25px;
}
+58
View File
@@ -0,0 +1,58 @@
.dept-tree {
width: 100%;
height: -webkit-calc(100vh - 247px);
height: -moz-calc(100vh - 247px);
height: calc(100vh - 247px);
margin-top: 20px;
}
.dtree-laySimple-item-this{
background-color: transparent!important;
}
.dtree-nav-div:hover{
background-color: transparent!important;
}
.button{
margin-top: 10px;
width: 94%;
margin-left: 3%;
display: block;
height: 40px;
line-height: 40px;
padding: 0 15px;
white-space: nowrap;
text-align: center;
font-size: 14.5px;
border: none;
cursor: pointer;
box-sizing: border-box;
display:inline-block;
outline: 0;
border-radius: 2px;
-webkit-appearance: none;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.15);
}
.button-primary{
background-color: #5FB878;
color: white;
}
.button-default{
color: #2f495e;
background-color: #edf2f7;
}
.user-main{
width: calc(100% - 312px);
float: right;
}
.user-left{
width: 300px;
float: left;
}
.user-collasped.user-main{
width: 100%;
}
.user-collasped.user-left{
width: 0px;
}
.user-collasped.user-left .user-group{
display: none;
}
+56
View File
@@ -0,0 +1,56 @@
{
"code": 0,
"msg": "...",
"count": 3,
"data": [{
"id": "1",
"image": "https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png",
"title": "Alipay",
"remark": "那是一种内在的东西, 他们到达不了,也无法触及的",
"time": "几秒前"
},{
"id": "2",
"image": "https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png",
"title": "Layui",
"remark": "生命就像一盒巧克力,结果往往出人意料",
"time": "几秒前"
},{
"id": "1",
"image": "https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png",
"title": "Angular",
"remark": "希望是一个好东西,也许是最好的,好东西是不会消亡的",
"time": "几秒前"
},
{
"id": "1",
"image": "https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png",
"title": "React",
"remark": "那是一种内在的东西, 他们到达不了,也无法触及的",
"time": "几秒前"
},{
"id": "1",
"image": "https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png",
"title": "Alipay",
"remark": "那是一种内在的东西, 他们到达不了,也无法触及的",
"time": "几秒前"
},{
"id": "2",
"image": "https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png",
"title": "Layui",
"remark": "生命就像一盒巧克力,结果往往出人意料",
"time": "几秒前"
},{
"id": "1",
"image": "https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png",
"title": "Angular",
"remark": "希望是一个好东西,也许是最好的,好东西是不会消亡的",
"time": "几秒前"
},
{
"id": "1",
"image": "https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png",
"title": "React",
"remark": "那是一种内在的东西, 他们到达不了,也无法触及的",
"time": "几秒前"
}]
}
+372
View File
@@ -0,0 +1,372 @@
[{
"id": 1,
"title": "工作空间",
"type": 0,
"icon": "layui-icon layui-icon-console",
"href": "",
"children": [{
"id": 10,
"title": "控制后台",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "view/console/console1.html"
}, {
"id": 13,
"title": "数据分析",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "view/console/console2.html"
}, {
"id": 14,
"title": "百度一下",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "http://www.baidu.com"
}, {
"id": 15,
"title": "主题预览",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "view/system/theme.html"
}]
},
{
"id": "component",
"title": "常用组件",
"icon": "layui-icon layui-icon-component",
"type": 0,
"href": "",
"children": [{
"id": 201,
"title": "基础组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 2011,
"title": "功能按钮",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/document/button.html"
}, {
"id": 2014,
"title": "表单集合",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/form.html"
}, {
"id": 2010,
"title": "字体图标",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/icon.html"
}, {
"id": 2012,
"title": "多选下拉",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/select.html"
}, {
"id": 2013,
"title": "动态标签",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/tag.html"
}]
}, {
"id": 203,
"title": "进阶组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 2031,
"title": "数据表格",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/table.html"
}, {
"id": 2032,
"title": "分布表单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/step.html"
}, {
"id": 2033,
"title": "树形表格",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/treetable.html"
}, {
"id": 2034,
"title": "树状结构",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/dtree.html"
}, {
"id": 2035,
"title": "文本编辑",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/tinymce.html"
}, {
"id": 2036,
"title": "卡片组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/card.html"
}]
}, {
"id": 202,
"title": "弹层组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 2021,
"title": "抽屉组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/drawer.html"
}, {
"id": 2022,
"title": "消息通知",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/notice.html"
}, {
"id": 2024,
"title": "加载组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/loading.html"
}, {
"id": 2023,
"title": "弹层组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/popup.html"
}]
},{
"id": 60331,
"title": "高级组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 60131,
"title": "多选项卡",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/tab.html"
},{
"id": 60132,
"title": "数据菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/menu.html"
}]
}, {
"id": 204,
"title": "其他组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 2041,
"title": "哈希加密",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/hash.html"
},
{
"id": 2042,
"title": "图标选择",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/iconPicker.html"
},
{
"id": 2043,
"title": "省市级联",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/area.html"
},
{
"id": 2044,
"title": "数字滚动",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/count.html"
},
{
"id": 2045,
"title": "顶部返回",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/topBar.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": "_iframe",
"href": "view/result/success.html"
},
{
"id": "failure",
"title": "失败",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/result/error.html"
}
]
},
{
"id": "error",
"title": "错误页面",
"icon": "layui-icon layui-icon-face-cry",
"type": 0,
"href": "",
"children": [{
"id": 403,
"title": "403",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/error/403.html"
},
{
"id": 404,
"title": "404",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/error/404.html"
},
{
"id": 500,
"title": "500",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/error/500.html"
}
]
},
{
"id": "system",
"title": "系统管理",
"icon": "layui-icon layui-icon-set-fill",
"type": 0,
"href": "",
"children": [{
"id": 601,
"title": "用户管理",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/system/user.html"
},
{
"id": 602,
"title": "角色管理",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/role.html"
},
{
"id": 603,
"title": "权限管理",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/power.html"
},
{
"id": 604,
"title": "部门管理",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/deptment.html"
},
{
"id": 605,
"title": "行为日志",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/log.html"
}, {
"id": 606,
"title": "数据字典",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/dict.html"
}
]
},
{
"id": "common",
"title": "常用页面",
"icon": "layui-icon layui-icon-template-1",
"type": 0,
"href": "",
"children": [{
"id": 701,
"title": "登录页面",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "login.html"
}, {
"id": 702,
"title": "空白页面",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/system/space.html"
}]
}
]
+10
View File
@@ -0,0 +1,10 @@
{
"status":{"code":200,"message":"操作成功"},
"data": [
{"id":"001","title": "湖南省","checkArr": "0","parentId": "0"},
{"id":"002","title": "湖北省","checkArr": "0","parentId": "0"},
{"id":"003","title": "广东省","checkArr": "0","parentId": "0"},
{"id":"004","title": "浙江省","checkArr": "0","parentId": "0"},
{"id":"005","title": "福建省","checkArr": "0","parentId": "0"}
]
}
+36
View File
@@ -0,0 +1,36 @@
{
"code": 0,
"msg": null,
"count": 2,
"data": [{
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": "男 : body",
"params": null,
"dataId": "1317401149287956480",
"dataLabel": "男",
"dataValue": "boy",
"typeCode": "user_sex",
"isDefault": null,
"enable": "0"
}, {
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": "女 : girl",
"params": null,
"dataId": "1317402976670711808",
"dataLabel": "女",
"dataValue": "girl",
"typeCode": "user_sex",
"isDefault": null,
"enable": "0"
}]
}
+90
View File
@@ -0,0 +1,90 @@
{
"code": 0,
"msg": null,
"count": 6,
"data": [{
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": null,
"params": null,
"id": "1304489072256876544",
"typeName": "用户状态",
"typeCode": "user_status",
"description": "用户状态",
"enable": "0"
}, {
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": null,
"params": null,
"id": "1317360314219495424",
"typeName": "登录类型",
"typeCode": "login",
"description": "登录类型",
"enable": "0"
}, {
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": null,
"params": null,
"id": "1317400519127334912",
"typeName": "用户类型",
"typeCode": "user_status",
"description": "用户类型",
"enable": "0"
}, {
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": null,
"params": null,
"id": "1317400823096934400",
"typeName": "配置类型",
"typeCode": "config_type",
"description": "配置类型",
"enable": "0"
}, {
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": null,
"params": null,
"id": "455184568505470976",
"typeName": "用户性别",
"typeCode": "user_sex",
"description": "用户性别",
"enable": "0"
}, {
"createTime": null,
"createBy": null,
"createName": null,
"updateTime": null,
"updateBy": null,
"updateName": null,
"remark": null,
"params": null,
"id": "455184935989415936",
"typeName": "全局状态",
"typeCode": "sys_status",
"description": "状态描述\n",
"enable": "0"
}]
}
+181
View File
@@ -0,0 +1,181 @@
{
"status": {
"code": 200,
"message": "操作成功"
},
"data": [{
"id": "001",
"title": "湖南省",
"parentId": "0",
"children": [{
"id": "001001",
"title": "长沙市",
"last": true,
"parentId": "001"
},
{
"id": "001002",
"title": "株洲市",
"last": true,
"parentId": "001"
},
{
"id": "001003",
"title": "湘潭市",
"last": true,
"parentId": "001"
},
{
"id": "001004",
"title": "衡阳市",
"last": true,
"parentId": "001"
},
{
"id": "001005",
"title": "郴州市",
"last": true,
"parentId": "001"
}
]
},
{
"id": "002",
"title": "湖北省",
"parentId": "0",
"children": [{
"id": "002001",
"title": "武汉市",
"last": true,
"parentId": "002"
},
{
"id": "002002",
"title": "黄冈市",
"last": true,
"parentId": "002"
},
{
"id": "002003",
"title": "潜江市",
"last": true,
"parentId": "002"
},
{
"id": "002004",
"title": "荆州市",
"last": true,
"parentId": "002"
},
{
"id": "002005",
"title": "襄阳市",
"last": true,
"parentId": "002"
}
]
},
{
"id": "003",
"title": "广东省",
"parentId": "0",
"children": [{
"id": "003001",
"title": "广州市",
"last": false,
"parentId": "003",
"children": [{
"id": "003001001",
"title": "天河区",
"last": true,
"parentId": "003001"
},
{
"id": "003001002",
"title": "花都区",
"last": true,
"parentId": "003001"
}
]
},
{
"id": "003002",
"title": "深圳市",
"last": true,
"parentId": "003"
},
{
"id": "003003",
"title": "中山市",
"last": true,
"parentId": "003"
},
{
"id": "003004",
"title": "东莞市",
"last": true,
"parentId": "003"
},
{
"id": "003005",
"title": "珠海市",
"last": true,
"parentId": "003"
},
{
"id": "003006",
"title": "韶关市",
"last": true,
"parentId": "003"
}
]
},
{
"id": "004",
"title": "浙江省",
"parentId": "0",
"children": [{
"id": "004001",
"title": "杭州市",
"last": true,
"parentId": "004"
},
{
"id": "004002",
"title": "温州市",
"last": true,
"parentId": "004"
},
{
"id": "004003",
"title": "绍兴市",
"last": true,
"parentId": "004"
},
{
"id": "004004",
"title": "金华市",
"last": true,
"parentId": "004"
},
{
"id": "004005",
"title": "义乌市",
"last": true,
"parentId": "004"
}
]
},
{
"id": "005",
"title": "福建省",
"parentId": "0",
"children": [{
"id": "005001",
"title": "厦门市",
"last": true,
"parentId": "005"
}]
}
]
}
+211
View File
@@ -0,0 +1,211 @@
{
"createTime": null,
"createBy": null,
"updateTime": null,
"updateBy": null,
"remark": null,
"code": 0,
"msg": null,
"count": 167,
"data": [{
"id": "1305106851892822016",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:31:36.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305101593019940864",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:10:42.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305101247900024832",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:09:20.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305099674977304576",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:03:05.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305095186061197312",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T10:45:15.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305092027951611904",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T10:32:42.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305082862634008576",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T09:56:16.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305071134877679616",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T09:09:40.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305070481803575296",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T09:07:05.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305064691009060864",
"title": "登录",
"description": "登录成功",
"businessType": "OTHER",
"requestMethod": "POST",
"method": "/login",
"operateUrl": "/login",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "LOGIN",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T08:44:04.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}]
}
+409
View File
@@ -0,0 +1,409 @@
[{
"id": 1,
"title": "工作空间",
"type": 0,
"icon": "layui-icon layui-icon-console",
"href": "",
"children": [{
"id": 10,
"title": "控制后台",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "view/console/console1.html"
}, {
"id": 13,
"title": "数据分析",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "view/console/console2.html"
}, {
"id": 14,
"title": "百度一下",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "http://www.baidu.com"
}, {
"id": 15,
"title": "主题预览",
"icon": "layui-icon layui-icon-console",
"type": 1,
"openType": "_iframe",
"href": "view/system/theme.html"
}]
},
{
"id": "component",
"title": "常用组件",
"icon": "layui-icon layui-icon-component",
"type": 0,
"href": "",
"children": [{
"id": 201,
"title": "基础组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [
{
"id": 2011,
"title": "功能按钮",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/document/button.html"
}, {
"id": 2014,
"title": "表单集合",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/form.html"
}, {
"id": 2010,
"title": "字体图标",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/icon.html"
}, {
"id": 2012,
"title": "多选下拉",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/select.html"
}, {
"id": 2013,
"title": "动态标签",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/tag.html"
}]
}, {
"id": 203,
"title": "进阶组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 2031,
"title": "数据表格",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/table.html"
}, {
"id": 2032,
"title": "分布表单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/step.html"
}, {
"id": 2033,
"title": "树形表格",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/treetable.html"
}, {
"id": 2034,
"title": "树状结构",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/dtree.html"
}, {
"id": 2035,
"title": "文本编辑",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/tinymce.html"
}, {
"id": 2036,
"title": "卡片组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/card.html"
}]
}, {
"id": 202,
"title": "弹层组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 2021,
"title": "抽屉组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/drawer.html"
}, {
"id": 2022,
"title": "消息通知",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/notice.html"
}, {
"id": 2024,
"title": "加载组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/loading.html"
}, {
"id": 2023,
"title": "弹层组件",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/popup.html"
}]
},{
"id": 60331,
"title": "高级组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 60131,
"title": "多选项卡",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/tab.html"
},{
"id": 60132,
"title": "数据菜单",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/menu.html"
}]
}, {
"id": 204,
"title": "其他组件",
"icon": "layui-icon layui-icon-console",
"type": 0,
"children": [{
"id": 2041,
"title": "哈希加密",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/hash.html"
},
{
"id": 2042,
"title": "图标选择",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/iconPicker.html"
},
{
"id": 2043,
"title": "省市级联",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/area.html"
},
{
"id": 2044,
"title": "数字滚动",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/count.html"
},
{
"id": 2045,
"title": "顶部返回",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/document/topBar.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": "_iframe",
"href": "view/result/success.html"
},
{
"id": "failure",
"title": "失败",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/result/error.html"
}
]
},
{
"id": "error",
"title": "错误页面",
"icon": "layui-icon layui-icon-face-cry",
"type": 0,
"href": "",
"children": [{
"id": 403,
"title": "403",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/error/403.html"
},
{
"id": 404,
"title": "404",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/error/404.html"
},
{
"id": 500,
"title": "500",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/error/500.html"
}
]
},
{
"id": "system",
"title": "系统管理",
"icon": "layui-icon layui-icon-set-fill",
"type": 0,
"href": "",
"children": [{
"id": 601,
"title": "用户管理",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/system/user.html"
},
{
"id": 602,
"title": "角色管理",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/role.html"
},
{
"id": 603,
"title": "权限管理",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/power.html"
},
{
"id": 604,
"title": "部门管理",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/deptment.html"
},
{
"id": 605,
"title": "行为日志",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/log.html"
}, {
"id": 606,
"title": "数据字典",
"icon": "layui-icon layui-icon-face-cry",
"type": 1,
"openType": "_iframe",
"href": "view/system/dict.html"
}
]
},
{
"id": "common",
"title": "常用页面",
"icon": "layui-icon layui-icon-template-1",
"type": 0,
"href": "",
"children": [{
"id": 701,
"title": "登录页面",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "login.html"
}, {
"id": 702,
"title": "空白页面",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/system/space.html"
}]
}, {
"id": "echarts",
"title": "数据图表",
"icon": "layui-icon layui-icon-chart",
"type": 0,
"href": "",
"children": [{
"id": 12121,
"title": "折线图",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/echarts/line.html"
}, {
"id": 121212,
"title": "柱状图",
"icon": "layui-icon layui-icon-face-smile",
"type": 1,
"openType": "_iframe",
"href": "view/echarts/column.html"
}]
},
{
"id": "code",
"title": "开发工具",
"icon": "layui-icon layui-icon-util",
"type": 0,
"href": "",
"children": [{
"id": 801,
"title": "表单构建",
"icon": "layui-icon layui-icon-util",
"type": 1,
"openType": "_iframe",
"href": "component/code/index.html"
}]
}
]
+88
View File
@@ -0,0 +1,88 @@
[{
"id": 1,
"title": "私信",
"children": [{
"id": 11,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条私人消息",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}, {
"id": 12,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条私人消息",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}]
},
{
"id": 2,
"title": "消息",
"children": [{
"id": 11,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条紧急任务",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}, {
"id": 12,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条紧急任务",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
},
{
"id": 11,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条紧急任务",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}, {
"id": 12,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条紧急任务",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
},
{
"id": 11,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条紧急任务",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}, {
"id": 12,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条紧急任务",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}]
},
{
"id": 3,
"title": "通知",
"children": [{
"id": 11,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条警告通知",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}, {
"id": 12,
"avatar":"admin/images/avatar.jpg",
"title": "收到一条警告通知",
"context": "这是消息内容。",
"form": "就眠仪式",
"time": "2019-02-15"
}]
}
]
+211
View File
@@ -0,0 +1,211 @@
{
"createTime": null,
"createBy": null,
"updateTime": null,
"updateBy": null,
"remark": null,
"code": 0,
"msg": null,
"count": 178,
"data": [{
"id": "1305106857240559616",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:31:37.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305101599714050048",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:10:44.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305101253352620032",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:09:21.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305099681512030208",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T11:03:06.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305095191291494400",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T10:45:16.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305092034448588800",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T10:32:43.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305082868002717696",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T09:56:18.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305071141362073600",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T09:09:42.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305070488145362944",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T09:07:06.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}, {
"id": "1305064697174687744",
"title": "主页",
"description": "返回 Index 主页视图",
"businessType": "ADD",
"requestMethod": "GET",
"method": "/index",
"operateUrl": "/index",
"operateAddress": "127.0.0.1",
"requestParam": null,
"requestBody": "",
"responseBody": null,
"success": true,
"loggingType": "OPERATE",
"errorMsg": null,
"systemOs": "Windows",
"createTime": "2020-09-13T08:44:05.000+0000",
"operateName": "admin",
"map": {},
"browser": "谷歌浏览器"
}]
}
+41
View File
@@ -0,0 +1,41 @@
{
"code": 0,
"msg": "...",
"count": 3,
"data": [{
"name": "软件部",
"userCount": 324,
"location": "二楼201",
"leader": "皮卡丘"
},{
"name": "美工部",
"userCount": 98,
"location": "二楼101",
"leader": "皮卡丘"
},{
"name": "设计部",
"userCount": 43,
"location": "六楼602",
"leader": "皮卡丘"
},{
"name": "销售部",
"userCount": 12,
"location": "一楼131",
"leader": "皮卡丘"
},{
"name": "产品部",
"userCount": 128,
"location": "四楼401",
"leader": "皮卡丘"
},{
"name": "财务部",
"userCount": 23,
"location": "三楼105",
"leader": "皮卡丘"
},{
"name": "人事部",
"userCount": 67,
"location": "二楼201",
"leader": "皮卡丘"
}]
}
@@ -0,0 +1,143 @@
{
"status": {
"code": 200,
"message": "操作成功"
},
"data": [{
"id": "0",
"title": "济南总部",
"last": false,
"parentId": "9527",
"children": [{
"id": "001",
"title": "杭州市",
"last": false,
"parentId": "0",
"children": [{
"id": "001001",
"title": "软件部",
"last": true,
"parentId": "001"
},
{
"id": "001002",
"title": "销售部",
"last": true,
"parentId": "001"
},
{
"id": "001003",
"title": "财务部",
"last": true,
"parentId": "001"
},
{
"id": "001004",
"title": "人事部",
"last": true,
"parentId": "001"
},
{
"id": "001005",
"title": "行政部",
"last": true,
"parentId": "001"
}
]
},
{
"id": "002",
"title": "湖北省",
"last": false,
"parentId": "0",
"children": [{
"id": "002001",
"title": "武汉市",
"last": true,
"parentId": "002"
},
{
"id": "002002",
"title": "黄冈市",
"last": true,
"parentId": "002"
},
{
"id": "002003",
"title": "潜江市",
"last": true,
"parentId": "002"
},
{
"id": "002004",
"title": "荆州市",
"last": true,
"parentId": "002"
},
{
"id": "002005",
"title": "襄阳市",
"last": true,
"parentId": "002"
}
]
},
{
"id": "003",
"title": "广东省",
"last": false,
"parentId": "0",
"children": [{
"id": "003001",
"title": "广州市",
"last": false,
"parentId": "003",
"children": [{
"id": "003001001",
"title": "天河区",
"last": true,
"parentId": "003001"
},
{
"id": "003001002",
"title": "花都区",
"last": true,
"parentId": "003001"
}
]
},
{
"id": "003002",
"title": "深圳市",
"last": true,
"parentId": "003"
},
{
"id": "003003",
"title": "中山市",
"last": true,
"parentId": "003"
},
{
"id": "003004",
"title": "东莞市",
"last": true,
"parentId": "003"
},
{
"id": "003005",
"title": "珠海市",
"last": true,
"parentId": "003"
},
{
"id": "003006",
"title": "韶关市",
"last": true,
"parentId": "003"
}
]
}
]
}]
}
+182
View File
@@ -0,0 +1,182 @@
{
"code": 0,
"msg": "...",
"count": null,
"data": [{
"powerId": "1",
"powerName": "系统管理",
"powerType": "0",
"powerCode": "",
"powerUrl": "",
"openType": null,
"parentId": "0",
"icon": "layui-icon-set-fill",
"sort": 1,
"checkArr": "0"
}, {
"powerId": "2",
"powerName": "用户管理",
"powerType": "1",
"powerCode": "sys:user:main",
"powerUrl": "/system/user/main",
"openType": null,
"parentId": "1",
"icon": "layui-icon-username",
"sort": null,
"checkArr": "0"
}, {
"powerId": "3",
"powerName": "角色管理",
"powerType": "1",
"powerCode": "sys:role:main",
"powerUrl": "/system/role/main",
"openType": null,
"parentId": "1",
"icon": "layui-icon-user",
"sort": null,
"checkArr": "0"
}, {
"powerId": "4",
"powerName": "权限管理",
"powerType": "1",
"powerCode": "sys:power:main",
"powerUrl": "/system/power/main",
"openType": null,
"parentId": "1",
"icon": "layui-icon-vercode",
"sort": null,
"checkArr": "0"
}, {
"powerId": "442359447487123456",
"powerName": "角色列表",
"powerType": "2",
"powerCode": "sys:role:data",
"powerUrl": "",
"openType": null,
"parentId": "3",
"icon": "layui-icon-rate",
"sort": 1,
"checkArr": "0"
}, {
"powerId": "442417411065516032",
"powerName": "敏捷开发",
"powerType": "0",
"powerCode": "",
"powerUrl": "",
"openType": null,
"parentId": "0",
"icon": "layui-icon-senior",
"sort": 2,
"checkArr": "0"
}, {
"powerId": "442418188639145984",
"powerName": "模板管理",
"powerType": "1",
"powerCode": "exp:template:main",
"powerUrl": "/system/user/main",
"openType": null,
"parentId": "442417411065516032",
"icon": "layui-icon-template-1",
"sort": null,
"checkArr": "0"
}, {
"powerId": "442520236248403968",
"powerName": "数据监控",
"powerType": "1",
"powerCode": "/druid/index.html",
"powerUrl": "/druid/index.html",
"openType": null,
"parentId": "694203021537574912",
"icon": "layui-icon-chart",
"sort": 1,
"checkArr": "0"
}, {
"powerId": "442650387514789888",
"powerName": "定时任务",
"powerType": "0",
"powerCode": "",
"powerUrl": "",
"openType": null,
"parentId": "0",
"icon": "layui-icon-log",
"sort": 5,
"checkArr": "0"
}, {
"powerId": "442650770626711552",
"powerName": "任务管理",
"powerType": "1",
"powerCode": "qrt:task:main",
"powerUrl": "/qrt/task/main",
"openType": null,
"parentId": "442650387514789888",
"icon": "layui-icon-chat",
"sort": 1,
"checkArr": "0"
}, {
"powerId": "442651158935375872",
"powerName": "任务日志",
"powerType": "1",
"powerCode": "qrt:log:main",
"powerUrl": "/qrt/log/main",
"openType": null,
"parentId": "442650387514789888",
"icon": "layui-icon-file",
"sort": 2,
"checkArr": "0"
}, {
"powerId": "442722702474743808",
"powerName": "数据字典",
"powerType": "1",
"powerCode": "system:dictType:main",
"powerUrl": "/system/dictType/main",
"openType": null,
"parentId": "1",
"icon": "layui-icon-form",
"sort": 1,
"checkArr": "0"
}, {
"powerId": "5",
"powerName": "工作流程",
"powerType": "0",
"powerCode": "",
"powerUrl": "",
"openType": null,
"parentId": "0",
"icon": "layui-icon-util",
"sort": 3,
"checkArr": "0"
}, {
"powerId": "6",
"powerName": "模型管理",
"powerType": "1",
"powerCode": null,
"powerUrl": null,
"openType": null,
"parentId": "5",
"icon": "layui-icon layui-icon-edit",
"sort": null,
"checkArr": "0"
}, {
"powerId": "694203021537574912",
"powerName": "系统监控",
"powerType": "0",
"powerCode": "",
"powerUrl": "",
"openType": null,
"parentId": "0",
"icon": "layui-icon-console",
"sort": 4,
"checkArr": "0"
}, {
"powerId": "694203311615639552",
"powerName": "接口文档",
"powerType": "1",
"powerCode": "",
"powerUrl": "/swagger-ui.html",
"openType": null,
"parentId": "694203021537574912",
"icon": "layui-icon-chart",
"sort": 1,
"checkArr": "0"
}]
}
+1
View File
@@ -0,0 +1 @@
{"createTime":null,"createBy":null,"updateTime":null,"updateBy":null,"remark":null,"code":0,"msg":"...","count":3,"data":[{"createTime":null,"createBy":null,"updateTime":null,"updateBy":null,"remark":null,"roleId":"1","roleName":"超级管理员","roleCode":"admin","enable":"1","details":"超级管理员","checked":false},{"createTime":null,"createBy":null,"updateTime":null,"updateBy":null,"remark":null,"roleId":"2","roleName":"普通管理员","roleCode":"manager","enable":"0","details":"普通管理员","checked":false},{"createTime":null,"createBy":null,"updateTime":null,"updateBy":null,"remark":null,"roleId":"3","roleName":"普通用户","roleCode":"pearson","enable":"0","details":"普通用户","checked":false}]}
+132
View File
@@ -0,0 +1,132 @@
{
"code": 0,
"msg": "...",
"count": 3,
"data": [{
"userId": "1",
"username": "admin",
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
"salt": null,
"status": null,
"realName": "超级'管'理员",
"email": "854085'4@'qq.com",
"avatar": null,
"sex": "1",
"phone": "15543526531",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "2",
"username": "854085467",
"password": null,
"salt": null,
"status": null,
"realName": "就\"眠\"仪式",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "3",
"username": "970796069",
"password": null,
"salt": null,
"status": null,
"realName": "王二麻子",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
},{
"userId": "4",
"username": "admin",
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
"salt": null,
"status": null,
"realName": "超级管理员",
"email": "8540854@qq.com",
"avatar": null,
"sex": "1",
"phone": "15543526531",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "5",
"username": "854085467",
"password": null,
"salt": null,
"status": null,
"realName": "就眠仪式",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "6",
"username": "970796069",
"password": null,
"salt": null,
"status": null,
"realName": "王二麻子",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
},{
"userId": "7",
"username": "admin",
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
"salt": null,
"status": null,
"realName": "超级管理员",
"email": "8540854@qq.com",
"avatar": null,
"sex": "1",
"phone": "15543526531",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "8",
"username": "854085467",
"password": null,
"salt": null,
"status": null,
"realName": "就眠仪式",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "9",
"username": "970796069",
"password": null,
"salt": null,
"status": null,
"realName": "王二麻子",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
}]
}
+118
View File
@@ -0,0 +1,118 @@
{
"code": 0,
"msg": "...",
"count": 3,
"data": [{
"userId": "1",
"username": "admin",
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
"salt": null,
"status": null,
"realName": "超级管理员",
"email": "8540854@qq.com",
"avatar": null,
"sex": "1",
"phone": "15543526531",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "442488661347536896",
"username": "854085467",
"password": null,
"salt": null,
"status": null,
"realName": "就眠仪式",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "442492965651353600",
"username": "970796069",
"password": null,
"salt": null,
"status": null,
"realName": "王二麻子",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
},{
"userId": "1",
"username": "admin",
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
"salt": null,
"status": null,
"realName": "超级管理员",
"email": "8540854@qq.com",
"avatar": null,
"sex": "1",
"phone": "15543526531",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "442488661347536896",
"username": "854085467",
"password": null,
"salt": null,
"status": null,
"realName": "就眠仪式",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "442492965651353600",
"username": "970796069",
"password": null,
"salt": null,
"status": null,
"realName": "王二麻子",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
},{
"userId": "1",
"username": "admin",
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
"salt": null,
"status": null,
"realName": "超级管理员",
"email": "8540854@qq.com",
"avatar": null,
"sex": "1",
"phone": "15543526531",
"enable": "1",
"login": "1",
"roleIds": null
}, {
"userId": "442488661347536896",
"username": "854085467",
"password": null,
"salt": null,
"status": null,
"realName": "就眠仪式",
"email": null,
"avatar": null,
"sex": "1",
"phone": "1555324324234",
"enable": "1",
"login": "1",
"roleIds": null
}]
}

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