Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55ea1e7252 | ||
|
|
b80b869dd8 | ||
|
|
7fea9743c9 | ||
|
|
c09812f85d | ||
|
|
794a8a57ad | ||
|
|
cab60da886 | ||
|
|
7501ae1dc2 | ||
|
|
9fc2eb0ae3 | ||
|
|
12d4dfe410 | ||
|
|
d54eb4dc3e | ||
|
|
246ce3b3b7 | ||
|
|
36250fad43 | ||
|
|
0d25b479e5 | ||
|
|
7e6aecc99b | ||
|
|
82de7b0273 | ||
|
|
8760f21b03 | ||
|
|
1e34ef339a | ||
|
|
5333a68416 | ||
|
|
decde3a69a | ||
|
|
9dcc05deda | ||
|
|
c0d963628b | ||
|
|
6978002393 | ||
|
|
3453b01938 | ||
|
|
820ab9f5c6 | ||
|
|
9490ccb7c0 | ||
|
|
056c4b7023 | ||
|
|
d4d8d8b294 | ||
|
|
8bc5d6e0c2 | ||
|
|
3fa8ae0a8a | ||
|
|
316ebc2441 | ||
|
|
550f99e468 | ||
|
|
b9c449ad57 | ||
|
|
db5b4e89ed | ||
|
|
733286b090 | ||
|
|
7505e4552b | ||
|
|
e918727984 | ||
|
|
00053da117 | ||
|
|
27f5d78c35 | ||
|
|
97e0160aaf | ||
|
|
04794e583f | ||
|
|
0640255519 | ||
|
|
460984f209 | ||
|
|
635dcc1dac | ||
|
|
b26237e24b | ||
|
|
a35d443fb6 | ||
|
|
ab4bb1ca40 | ||
|
|
2b7304e615 | ||
|
|
64748e513d | ||
|
|
5b07fed518 | ||
|
|
eb85e30b79 | ||
|
|
b8e0173a9c | ||
|
|
0ca75c81d4 | ||
|
|
aa86bbe372 | ||
|
|
86f07817b1 | ||
|
|
3f74011c98 | ||
|
|
ce8adda79d | ||
|
|
5470837707 | ||
|
|
c26f15ae69 | ||
|
|
4d6d998ae2 | ||
|
|
d628432621 | ||
|
|
2bcaa8e071 | ||
|
|
ebf76055a4 | ||
|
|
b2b08769b0 | ||
|
|
aae15fd07a | ||
|
|
945f746d97 | ||
|
|
dce7908041 | ||
|
|
0ba2b3aa56 | ||
|
|
17792ccacf | ||
|
|
e3dd5458ad | ||
|
|
e9bd8a17a0 | ||
|
|
b8870d4c33 | ||
|
|
8fba71c464 | ||
|
|
0cb74c5eb7 | ||
|
|
6da6768f81 | ||
|
|
05dff28523 | ||
|
|
d7c7237cd4 | ||
|
|
9e8a5357be | ||
|
|
5faf88283f | ||
|
|
d9a5bcb0de | ||
|
|
48633729fb | ||
|
|
f274e060f4 | ||
|
|
9784f7816a | ||
|
|
f93a36c094 | ||
|
|
375d3a10b2 | ||
|
|
a1fbf5a0ae | ||
|
|
10035cc158 | ||
|
|
3b1150ad1f | ||
|
|
73222e854c | ||
|
|
3db99422b7 | ||
|
|
d876c8309f | ||
|
|
e0738aa5b2 | ||
|
|
f051693690 | ||
|
|
c6ffbbc062 | ||
|
|
2836fc1139 | ||
|
|
e111f4a763 | ||
|
|
3d5e4b9e9b | ||
|
|
8f4054e576 | ||
|
|
3ea4edd117 | ||
|
|
2f0df0a700 | ||
|
|
a709095dc2 | ||
|
|
4a8ac0ac83 | ||
|
|
b9301046da |
@@ -1,3 +1,3 @@
|
|||||||
FLASK_APP=main.py
|
|
||||||
FLASK_ENV=development
|
FLASK_ENV=development
|
||||||
FLASK_DEBUG=1
|
FLASK_RUN_HOST=0.0.0.0
|
||||||
|
FLASK_APP='applications:create_app'
|
||||||
|
|||||||
@@ -115,4 +115,10 @@ dmypy.json
|
|||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
#ide
|
#ide
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# 数据库文件
|
||||||
|
*.db
|
||||||
|
#上传到本地的图片文件
|
||||||
|
*upload/
|
||||||
|
migrations/
|
||||||
@@ -29,184 +29,112 @@
|
|||||||
<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" />
|
<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>
|
</div>
|
||||||
|
|
||||||
#### 项目简介
|
## 项目简介
|
||||||
>Pear Admin Flask 基于 Flask 的后台管理系统,拥抱应用广泛的python语言,通过使用本系统,即可快速构建你的功能业务
|
Pear Admin Flask 基于 Flask 生态的后台管理系统,该项目旨在为 python 开发者提供一个快速开发前后端半分离的后台管理系统的模板
|
||||||
>
|
|
||||||
>项目旨在为python开发者提供一个后台管理系统的模板,成为您构建信息管理系统,物联网后台....等等应用时灵活,简单的工具
|
|
||||||
>
|
|
||||||
>各位Python爱好者多多指教
|
|
||||||
|
|
||||||
Pear Admin Flask 分为 Common / Simple 两个版本:
|
在使用本项目之前最好掌握以下知识点
|
||||||
|
|
||||||
[** Common 通用版本 **](https://gitee.com/pear-admin/pear-admin-flask/tree/master/)
|
+ [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/)
|
||||||
|
|
||||||
[** Simple 简洁版本 **](https://gitee.com/pear-admin/pear-admin-flask/tree/simple/)
|
|
||||||
|
|
||||||
#### 内置功能
|
## 预览
|
||||||
|
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 + 基于角色的权限管理
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---------------------|---------------------|
|
||||||
|
|  |  |
|
||||||
|
| |  |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
## 内置功能
|
||||||
|
|
||||||
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||||
- [x] 权限管理:配置系统菜单,操作权限,按钮权限标识等。
|
- [x] 权限管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||||
- [x] 角色管理:角色菜单权限分配。
|
- [x] 角色管理:角色菜单权限分配。
|
||||||
- [x] 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
- [x] 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||||
- [x] 登录日志:系统登录日志记录查询包含登录异常。
|
- [x] 登录日志:系统登录日志记录查询包含登录异常。
|
||||||
- [x] 服务监控:监视当前系统CPU、内存、磁盘、python版本,运行时长等相关信息。
|
- [x] 文件上传: 图片上传示例
|
||||||
- [x] 文件上传: 图片上传示例
|
|
||||||
- [ ] 代码生成: 构想中....
|
|
||||||
|
|
||||||
#### 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
Pear Admin Flask
|
|
||||||
│
|
|
||||||
├─applications
|
|
||||||
│ │
|
|
||||||
│ ├─config
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ common.py #普通配置
|
|
||||||
│ │ │
|
|
||||||
│ │ └─ database.py #数据库配置
|
|
||||||
│ │
|
|
||||||
│ ├─models
|
|
||||||
│ │ │
|
|
||||||
│ │ └─admin.py #基本模型
|
|
||||||
│ │
|
|
||||||
│ ├─service
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─admin
|
|
||||||
│ │ │ │
|
|
||||||
│ │ │ ├─ file.py #file视图的数据操作
|
|
||||||
│ │ │ │
|
|
||||||
│ │ │ ├─ index.py #index视图的数据操作
|
|
||||||
│ │ │ │
|
|
||||||
│ │ │ ├─ power.py #power视图的数据操作
|
|
||||||
│ │ │ │
|
|
||||||
│ │ │ ├─ role.py #role视图的数据操作
|
|
||||||
│ │ │ │
|
|
||||||
│ │ │ └─ user.py #user视图的数据操作
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ admin_log.py #存储日志
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ deBug.py #deBug工具栏初始化
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ login.py #flask_login初始化
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ CaptchaTool.py #验证码
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ OriginalDb.py #原生sql查询封装
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ route_auth.py #权限
|
|
||||||
│ │ │
|
|
||||||
│ │ └─ upload.py #上传
|
|
||||||
│ │
|
|
||||||
│ └─views
|
|
||||||
│ │
|
|
||||||
│ ├─admin #前台视图
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─index.py #主视图
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─user.py #用户视图
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─role.py #角色视图
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─power.py #权限视图
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─monitor.py #系统监控
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─file.py #文件上传
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─admin_log.py #系统日志
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─context_processor.py #全局模板函数注册
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─error.py #错误处理
|
|
||||||
│ │ │
|
|
||||||
│ │ └─init.py #蓝图注册
|
|
||||||
│ │
|
|
||||||
│ └─index #前台视图
|
|
||||||
│
|
|
||||||
├─dev #数据库初始化
|
|
||||||
│
|
|
||||||
├─migrations
|
|
||||||
│
|
|
||||||
├─readmes
|
|
||||||
│
|
|
||||||
├─static #静态资源
|
|
||||||
│ │
|
|
||||||
│ └─upload #文件上传地址
|
|
||||||
│
|
|
||||||
└─templates
|
|
||||||
│
|
|
||||||
├─admin #前台模板
|
|
||||||
│
|
|
||||||
├─errors #错误模板
|
|
||||||
│
|
|
||||||
└─index #前台模板
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
## 文档
|
||||||
|
正在编写
|
||||||
|
|
||||||
#### 项目安装
|
|
||||||
|
|
||||||
|
## 安装使用
|
||||||
|
|
||||||
|
+ 下载源码
|
||||||
```bash
|
```bash
|
||||||
# 下 载
|
|
||||||
git clone https://gitee.com/pear-admin/pear-admin-flask
|
git clone https://gitee.com/pear-admin/pear-admin-flask
|
||||||
|
|
||||||
# 安 装
|
# 进入 pear-admin-flask 代码根目录
|
||||||
pip install -r requirement.txt
|
cd pear-admin-flask
|
||||||
|
|
||||||
# 配 置
|
|
||||||
applications\config\database.py
|
|
||||||
|
|
||||||
|
# 切换分支
|
||||||
|
git checkout mini
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 修改配置
|
+ 安装依赖
|
||||||
|
|
||||||
```python
|
|
||||||
# 主 机
|
|
||||||
HOST = '127.0.0.1'
|
|
||||||
|
|
||||||
# 端 口
|
|
||||||
PORT = '3306'
|
|
||||||
|
|
||||||
# 数 据 库
|
|
||||||
DATABASE = 'PearAdminFlask'
|
|
||||||
|
|
||||||
# 账 户
|
|
||||||
USERNAME = 'root'
|
|
||||||
|
|
||||||
# 密 码
|
|
||||||
PASSWORD = 'root'
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Venv 安装
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 创建虚拟环境
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
|
|
||||||
|
# 然后使虚拟环境生效(windows)
|
||||||
|
venv\Scripts\activate
|
||||||
|
# source venv/bin/activate # (Linux激活虚拟环境)
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirement.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 运行项目
|
+ 数据迁移
|
||||||
|
|
||||||
|
默认的使用 `sqlite3` 作为测试环境的数据库进行演示,不需要按照mysql即可查看演示。如果需要二次开发,建议改成 `mysql` 。
|
||||||
|
|
||||||
|
如果需要在开发环境使用 mysql 作为数据库,请查看 `applications/configs/config.py` 文件里面的相关配置文件, 注释掉 sqlite 的配置即可
|
||||||
|
|
||||||
|
如果需要修改数据的配置信息,请在 `.flaskenv` 里面调整即可
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 进 入 目 录
|
flask db init
|
||||||
cd dev
|
flask db migrate -m '数据初始化'
|
||||||
|
flask db upgrade
|
||||||
# 初 始 化 数 据 库
|
|
||||||
python initDb.py
|
|
||||||
|
|
||||||
# 如 果 报 模 块 路 径 错 误
|
|
||||||
python dev/initDb.py
|
|
||||||
|
|
||||||
|
flask init-db
|
||||||
```
|
```
|
||||||
|
|
||||||
执行 flask run 命令启动项目
|
+ 项目启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flask run
|
||||||
|
```
|
||||||
|
|
||||||
#### 预览项目
|
## Git 贡献提交规范
|
||||||
|
|
||||||
| | |
|
- 参考 [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` 开发中
|
||||||
@@ -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('''
|
||||||
|
_____ _ _ ______ _ _
|
||||||
|
| __ \ /\ | | (_) | ____| | | |
|
||||||
|
| |__) |__ __ _ _ __ / \ __| |_ __ ___ _ _ __ | |__ | | __ _ ___| | __
|
||||||
|
| ___/ _ \/ _` | '__| / /\ \ / _` | '_ ` _ \| | '_ \ | __| | |/ _` / __| |/ /
|
||||||
|
| | | __/ (_| | | / ____ \ (_| | | | | | | | | | | | | | | (_| \__ \ <
|
||||||
|
|_| \___|\__,_|_| /_/ \_\__,_|_| |_| |_|_|_| |_| |_| |_|\__,_|___/_|\_\\
|
||||||
|
|
||||||
|
''')
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
# 密钥配置(flask session需要) 记得修改
|
|
||||||
import os
|
|
||||||
|
|
||||||
SECRET_KEY = 'pear-admin-flask'
|
|
||||||
|
|
||||||
UPLOADED_PHOTOS_DEST = 'static/upload'
|
|
||||||
UPLOADED_FILES_ALLOW = ['gif','jpg']
|
|
||||||
|
|
||||||
JSON_AS_ASCII = False
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# 数据库连接
|
|
||||||
HOST = '127.0.0.1'
|
|
||||||
PORT = '3306'
|
|
||||||
DATABASE = 'PearAdminFlask'
|
|
||||||
USERNAME = 'root'
|
|
||||||
PASSWORD = 'root'
|
|
||||||
|
|
||||||
DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD, host=HOST,port=PORT, db=DATABASE)
|
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URI = DB_URI
|
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
|
||||||
SQLALCHEMY_ECHO = False
|
|
||||||
SQLALCHEMY_POOL_RECYCLE=8
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
|
|
||||||
db = SQLAlchemy()
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import datetime
|
|
||||||
from flask_login import UserMixin
|
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
|
||||||
from applications.models import db
|
|
||||||
|
|
||||||
|
|
||||||
class User(db.Model, UserMixin):
|
|
||||||
__tablename__ = 'admin_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='真实名字')
|
|
||||||
password_hash = db.Column(db.String(128), comment='哈希密码')
|
|
||||||
enable = db.Column(db.Integer, default=0, comment='启用')
|
|
||||||
create_at = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
|
|
||||||
update_at = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='创建时间')
|
|
||||||
role = db.relationship('Role',secondary="admin_user_role", backref=db.backref('user'),lazy = 'dynamic')
|
|
||||||
# power = db.relationship('Power',secondary="admin_user_role", backref=db.backref('user'))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
# 创建中间表
|
|
||||||
user_role = db.Table(
|
|
||||||
"admin_user_role", # 中间表名称
|
|
||||||
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment='标识'), # 主键
|
|
||||||
db.Column("user_id", db.Integer, db.ForeignKey("admin_user.id"), comment='用户编号'), # 属性 外键
|
|
||||||
db.Column("role_id", db.Integer, db.ForeignKey("admin_role.id"), comment='角色编号'), # 属性 外键
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Role(db.Model):
|
|
||||||
__tablename__ = 'admin_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.Integer, comment='是否启用')
|
|
||||||
remark = db.Column(db.String(255), comment='备注')
|
|
||||||
details = db.Column(db.String(255), comment='详情')
|
|
||||||
sort = db.Column(db.Integer, comment='排序')
|
|
||||||
create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
|
|
||||||
update_time = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
|
|
||||||
power = db.relationship('Power',secondary="admin_role_power", backref=db.backref('role'))
|
|
||||||
|
|
||||||
# 创建中间表
|
|
||||||
role_power = db.Table(
|
|
||||||
"admin_role_power", # 中间表名称
|
|
||||||
db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment='标识'), # 主键
|
|
||||||
db.Column("power_id", db.Integer, db.ForeignKey("admin_power.id"), comment='用户编号'), # 属性 外键
|
|
||||||
db.Column("role_id", db.Integer, db.ForeignKey("admin_role.id"), comment='角色编号'), # 属性 外键
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Power(db.Model):
|
|
||||||
__tablename__ = 'admin_power'
|
|
||||||
id = db.Column(db.Integer, primary_key=True, comment='权限编号')
|
|
||||||
name = db.Column(db.String(255), comment='权限名称')
|
|
||||||
type = db.Column(db.String(1), 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, comment='父类编号')
|
|
||||||
icon = db.Column(db.String(128), comment='图标')
|
|
||||||
sort = db.Column(db.Integer, comment='排序')
|
|
||||||
create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
|
|
||||||
update_time = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
|
|
||||||
enable = db.Column(db.Integer, comment='是否开启')
|
|
||||||
|
|
||||||
|
|
||||||
class AdminLog(db.Model):
|
|
||||||
__tablename__ = 'admin_admin_log'
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
method = db.Column(db.String(10))
|
|
||||||
uid = db.Column(db.Integer)
|
|
||||||
url = db.Column(db.String(255))
|
|
||||||
desc = db.Column(db.Text)
|
|
||||||
ip = db.Column(db.String(255))
|
|
||||||
success = db.Column(db.Integer)
|
|
||||||
user_agent = db.Column(db.Text)
|
|
||||||
create_time = db.Column(db.DateTime, default=datetime.datetime.now)
|
|
||||||
|
|
||||||
|
|
||||||
class Photo(db.Model):
|
|
||||||
__tablename__ = 'admin_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_time = db.Column(db.DateTime, default=datetime.datetime.now)
|
|
||||||
@@ -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'])
|
||||||
@@ -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="出错啦")
|
||||||
@@ -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)
|
||||||
@@ -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="角色删除成功")
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from captcha.image import ImageCaptcha
|
|
||||||
from PIL import Image
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
# 定义随机方法
|
|
||||||
def random_captcha():
|
|
||||||
# 定义一个容器
|
|
||||||
captcha_text = []
|
|
||||||
for i in range(4):
|
|
||||||
# 定义验证码字符
|
|
||||||
c = random.choice(['0', '1', '2', '4', '6', '8'])
|
|
||||||
captcha_text.append(c)
|
|
||||||
# 返回一个随机生成的字符串
|
|
||||||
return ''.join(captcha_text)
|
|
||||||
|
|
||||||
|
|
||||||
# 生成验证码方法
|
|
||||||
def gen_captcha():
|
|
||||||
# 定义图片对象
|
|
||||||
image = ImageCaptcha()
|
|
||||||
# 获取字符串
|
|
||||||
captcha_text = random_captcha()
|
|
||||||
# 生成图像
|
|
||||||
captcha_image = Image.open(image.generate(captcha_text))
|
|
||||||
return captcha_text, captcha_image
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import json
|
|
||||||
|
|
||||||
import pymysql
|
|
||||||
from applications.config.database import HOST, USERNAME, PASSWORD, DATABASE, PORT
|
|
||||||
|
|
||||||
DB_CONFIG = {
|
|
||||||
"host": HOST,
|
|
||||||
"port": int(PORT),
|
|
||||||
"user": USERNAME,
|
|
||||||
"password": PASSWORD,
|
|
||||||
"db": DATABASE,
|
|
||||||
"charset": "utf8"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SQLManager(object):
|
|
||||||
|
|
||||||
# 初始化实例方法
|
|
||||||
def __init__(self):
|
|
||||||
self.conn = None
|
|
||||||
self.cursor = None
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
# 连接数据库
|
|
||||||
def connect(self):
|
|
||||||
self.conn = pymysql.connect(
|
|
||||||
host=DB_CONFIG.get('host'),
|
|
||||||
port=DB_CONFIG.get('port'),
|
|
||||||
user=DB_CONFIG.get('user'),
|
|
||||||
passwd=DB_CONFIG.get('password'),
|
|
||||||
db=DB_CONFIG.get('db'),
|
|
||||||
)
|
|
||||||
self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
|
|
||||||
|
|
||||||
# 查询多条数据
|
|
||||||
|
|
||||||
def get_list(self, sql, args=None):
|
|
||||||
self.cursor.execute(sql, args)
|
|
||||||
result = self.cursor.fetchall()
|
|
||||||
return result
|
|
||||||
|
|
||||||
# 查询多条数据后关闭
|
|
||||||
def get_list_close(self, sql, args=None):
|
|
||||||
self.cursor.execute(sql, args)
|
|
||||||
result = self.cursor.fetchall()
|
|
||||||
self.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
# 查询单条数据
|
|
||||||
def get_one(self, sql, args=None):
|
|
||||||
self.cursor.execute(sql, args)
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
return result
|
|
||||||
|
|
||||||
# 查询单条数据后关闭
|
|
||||||
def get_one_close(self, sql, args=None):
|
|
||||||
self.cursor.execute(sql, args)
|
|
||||||
result = self.cursor.fetchone()
|
|
||||||
self.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
# 执行单条SQL语句
|
|
||||||
def moddify(self, sql, args=None):
|
|
||||||
self.cursor.execute(sql, args)
|
|
||||||
self.conn.commit()
|
|
||||||
|
|
||||||
# 执行多条SQL语句
|
|
||||||
def multi_modify(self, sql, args=None):
|
|
||||||
self.cursor.executemany(sql, args)
|
|
||||||
self.conn.commit()
|
|
||||||
|
|
||||||
# 创建单条记录的语句
|
|
||||||
def create(self, sql, args=None):
|
|
||||||
self.cursor.execute(sql, args)
|
|
||||||
self.conn.commit()
|
|
||||||
last_id = self.cursor.lastrowid
|
|
||||||
return last_id
|
|
||||||
|
|
||||||
# 关闭数据库cursor和连接
|
|
||||||
def close(self):
|
|
||||||
self.cursor.close()
|
|
||||||
self.conn.close()
|
|
||||||
|
|
||||||
# 进入with语句自动执行
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
# 退出with语句块自动执行
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
原生查询dict的datetime解析器
|
|
||||||
用法
|
|
||||||
json.dumps(dictxxx, cls=DateEncoder)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DateEncoder(json.JSONEncoder):
|
|
||||||
def default(self, obj):
|
|
||||||
if isinstance(obj, datetime.datetime):
|
|
||||||
return obj.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
else:
|
|
||||||
return json.JSONEncoder.default(self, obj)
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import os
|
|
||||||
from flask import current_app
|
|
||||||
from flask_marshmallow import Marshmallow
|
|
||||||
from marshmallow import fields
|
|
||||||
from sqlalchemy import desc
|
|
||||||
from applications.models import db
|
|
||||||
from applications.models.admin import Photo
|
|
||||||
from applications.service.upload import photos
|
|
||||||
ma = Marshmallow()
|
|
||||||
|
|
||||||
|
|
||||||
class PhotoSchema(ma.Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
name = fields.Str()
|
|
||||||
href = fields.Str()
|
|
||||||
mime = fields.Str()
|
|
||||||
size = fields.Str()
|
|
||||||
ext = fields.Str()
|
|
||||||
create_time = fields.DateTime()
|
|
||||||
|
|
||||||
|
|
||||||
def get_photo(page, limit):
|
|
||||||
photo = Photo.query.order_by(desc(Photo.create_time)).paginate(page=page, per_page=limit, error_out=False)
|
|
||||||
count = Photo.query.count()
|
|
||||||
role_schema = PhotoSchema(many=True)
|
|
||||||
output = role_schema.dump(photo.items)
|
|
||||||
return output, count
|
|
||||||
|
|
||||||
|
|
||||||
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 = Photo(name=filename, href=file_url, mime=mime, size=size)
|
|
||||||
db.session.add(photo)
|
|
||||||
db.session.commit()
|
|
||||||
return upload_url
|
|
||||||
|
|
||||||
def delete_photo_by_id(id):
|
|
||||||
photo_name = Photo.query.filter_by(id=id).first().name
|
|
||||||
photo = Photo.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
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
from io import BytesIO
|
|
||||||
from flask import session, make_response
|
|
||||||
from flask_login import current_user
|
|
||||||
from flask_marshmallow import Marshmallow
|
|
||||||
from marshmallow import fields
|
|
||||||
from applications.models.admin import Power
|
|
||||||
from applications.service.CaptchaTool import gen_captcha
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
|
||||||
|
|
||||||
|
|
||||||
# 权限models序列化类
|
|
||||||
class PowerSchema(ma.Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
title = fields.Str(attribute="name")
|
|
||||||
type = fields.Str()
|
|
||||||
code = fields.Str()
|
|
||||||
href = fields.Str(attribute="url")
|
|
||||||
open_type = fields.Str()
|
|
||||||
parent_id = fields.Integer()
|
|
||||||
icon = fields.Str()
|
|
||||||
sort = fields.Integer()
|
|
||||||
create_time = fields.DateTime()
|
|
||||||
update_time = fields.DateTime()
|
|
||||||
enable = fields.Integer()
|
|
||||||
|
|
||||||
|
|
||||||
# 授权路由存入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
|
|
||||||
|
|
||||||
|
|
||||||
# 生成菜单树
|
|
||||||
def make_menu_tree():
|
|
||||||
# power0 = Power.query.filter(
|
|
||||||
# Power.type == 0,
|
|
||||||
# ).all()
|
|
||||||
# power1 = Power.query.filter(
|
|
||||||
# Power.type == 1
|
|
||||||
# ).all()
|
|
||||||
role = current_user.role
|
|
||||||
power0 = []
|
|
||||||
power1 = []
|
|
||||||
for i in role:
|
|
||||||
if i.enable == 0:
|
|
||||||
continue
|
|
||||||
for p in i.power:
|
|
||||||
if p.enable == 0:
|
|
||||||
continue
|
|
||||||
if int(p.type) == 0:
|
|
||||||
power0.append(p)
|
|
||||||
else:
|
|
||||||
power1.append(p)
|
|
||||||
|
|
||||||
power_schema = PowerSchema(many=True) # 用已继承ma.ModelSchema类的自定制类生成序列化类
|
|
||||||
power0_dict = power_schema.dump(power0) # 生成可序列化对象
|
|
||||||
power1_dict = power_schema.dump(power1) # 生成可序列化对象
|
|
||||||
power0_dict = sorted(power0_dict, key=lambda i: i['sort'])
|
|
||||||
power1_dict = sorted(power1_dict, key=lambda i: i['sort'])
|
|
||||||
# print(power0)
|
|
||||||
# print(power1)
|
|
||||||
|
|
||||||
menu = []
|
|
||||||
|
|
||||||
for p0 in power0_dict:
|
|
||||||
for p1 in power1_dict:
|
|
||||||
if p0.get('id') == p1.get('parent_id'):
|
|
||||||
if p0.get("children") is None:
|
|
||||||
p0['children'] = []
|
|
||||||
p0['children'].append(p1)
|
|
||||||
menu.append(p0)
|
|
||||||
return menu
|
|
||||||
|
|
||||||
|
|
||||||
# 生成验证码
|
|
||||||
def get_captcha():
|
|
||||||
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
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
from flask_marshmallow import Marshmallow
|
|
||||||
from marshmallow import fields
|
|
||||||
|
|
||||||
from applications.models import db
|
|
||||||
from applications.models.admin import Power, Role
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
|
||||||
|
|
||||||
|
|
||||||
class PowerSchema(ma.Schema): # 序列化类
|
|
||||||
powerId = fields.Str(attribute="id")
|
|
||||||
powerName = fields.Str(attribute="name")
|
|
||||||
powerType = fields.Str(attribute="type")
|
|
||||||
powerUrl = fields.Str(attribute="url")
|
|
||||||
openType = fields.Str(attribute="pen_type")
|
|
||||||
parentId = fields.Str(attribute="parent_id")
|
|
||||||
icon = fields.Str()
|
|
||||||
sort = fields.Integer()
|
|
||||||
create_time = fields.DateTime()
|
|
||||||
update_time = fields.DateTime()
|
|
||||||
enable = fields.Integer()
|
|
||||||
|
|
||||||
|
|
||||||
def get_power_dict():
|
|
||||||
power = Power.query.all()
|
|
||||||
power_schema = PowerSchema(many=True)
|
|
||||||
power_dict = power_schema.dump(power)
|
|
||||||
return power_dict
|
|
||||||
|
|
||||||
|
|
||||||
# 选择父节点
|
|
||||||
def select_parent():
|
|
||||||
power = Power.query.all()
|
|
||||||
power_schema = PowerSchema(many=True)
|
|
||||||
power_dict = power_schema.dump(power)
|
|
||||||
power_dict.append({"powerId": 0, "powerName": "顶级权限", "parentId": -1})
|
|
||||||
return power_dict
|
|
||||||
|
|
||||||
|
|
||||||
# 增加权限
|
|
||||||
def save_power(req):
|
|
||||||
icon = req.get("icon")
|
|
||||||
openType = req.get("openType")
|
|
||||||
parentId = req.get("parentId")
|
|
||||||
powerCode = req.get("powerCode")
|
|
||||||
powerName = req.get("powerName")
|
|
||||||
powerType = req.get("powerType")
|
|
||||||
powerUrl = req.get("powerUrl")
|
|
||||||
sort = req.get("sort")
|
|
||||||
power = Power(
|
|
||||||
icon=icon,
|
|
||||||
open_type=openType,
|
|
||||||
parent_id=parentId,
|
|
||||||
code=powerCode,
|
|
||||||
name=powerName,
|
|
||||||
type=powerType,
|
|
||||||
url=powerUrl,
|
|
||||||
sort=sort,
|
|
||||||
enable=1
|
|
||||||
)
|
|
||||||
r = db.session.add(power)
|
|
||||||
db.session.commit()
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
# 根据id查询权限
|
|
||||||
def get_power_by_id(id):
|
|
||||||
p = Power.query.filter_by(id=id).first()
|
|
||||||
return p
|
|
||||||
|
|
||||||
|
|
||||||
# 更新权限
|
|
||||||
def update_power(req_json):
|
|
||||||
id = req_json.get("powerId")
|
|
||||||
data = {
|
|
||||||
"icon": req_json.get("icon"),
|
|
||||||
"open_type": req_json.get("openType"),
|
|
||||||
"parent_id": req_json.get("parentId"),
|
|
||||||
"code": req_json.get("powerCode"),
|
|
||||||
"name": req_json.get("powerName"),
|
|
||||||
"type": req_json.get("powerType"),
|
|
||||||
"url": req_json.get("powerUrl"),
|
|
||||||
"sort": req_json.get("sort")
|
|
||||||
}
|
|
||||||
print(data)
|
|
||||||
power = Power.query.filter_by(id=id).update(data)
|
|
||||||
db.session.commit()
|
|
||||||
print(power)
|
|
||||||
return power
|
|
||||||
|
|
||||||
|
|
||||||
# 启动权限
|
|
||||||
def enable_status(id):
|
|
||||||
enable = 1
|
|
||||||
user = Power.query.filter_by(id=id).update({"enable": enable})
|
|
||||||
if user:
|
|
||||||
db.session.commit()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# 停用权限
|
|
||||||
def disable_status(id):
|
|
||||||
enable = 0
|
|
||||||
user = Power.query.filter_by(id=id).update({"enable": enable})
|
|
||||||
if user:
|
|
||||||
db.session.commit()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# 删除权限(目前没有判断父节点自动删除子节点)
|
|
||||||
def remove_power(id):
|
|
||||||
power = Power.query.filter_by(id=id).first()
|
|
||||||
role_id_list = []
|
|
||||||
roles = power.role
|
|
||||||
for role in roles:
|
|
||||||
role_id_list.append(role.id)
|
|
||||||
roles = Role.query.filter(Role.id.in_(role_id_list)).all()
|
|
||||||
for p in roles:
|
|
||||||
power.role.remove(p)
|
|
||||||
r = Power.query.filter_by(id=id).delete()
|
|
||||||
db.session.commit()
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
# 批量删除权限
|
|
||||||
def batch_remove(ids):
|
|
||||||
for id in ids:
|
|
||||||
remove_power(id)
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
from flask import jsonify
|
|
||||||
from flask_marshmallow import Marshmallow
|
|
||||||
from marshmallow import fields
|
|
||||||
from sqlalchemy import and_
|
|
||||||
|
|
||||||
from applications.models import db
|
|
||||||
from applications.models.admin import Role, Power, User
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
|
||||||
|
|
||||||
|
|
||||||
class RoleSchema(ma.Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
roleName = fields.Str(attribute="name")
|
|
||||||
roleCode = fields.Str(attribute="code")
|
|
||||||
enable = fields.Str()
|
|
||||||
remark = fields.Str()
|
|
||||||
details = fields.Str()
|
|
||||||
sort = fields.Integer()
|
|
||||||
create_at = fields.DateTime()
|
|
||||||
update_at = fields.DateTime()
|
|
||||||
|
|
||||||
|
|
||||||
class PowerSchema(ma.Schema): # 序列化类
|
|
||||||
powerId = fields.Str(attribute="id")
|
|
||||||
powerName = fields.Str(attribute="name")
|
|
||||||
powerType = fields.Str(attribute="type")
|
|
||||||
powerUrl = fields.Str(attribute="url")
|
|
||||||
openType = fields.Str(attribute="pen_type")
|
|
||||||
parentId = fields.Str(attribute="parent_id")
|
|
||||||
icon = fields.Str()
|
|
||||||
sort = fields.Integer()
|
|
||||||
create_time = fields.DateTime()
|
|
||||||
update_time = fields.DateTime()
|
|
||||||
enable = fields.Integer()
|
|
||||||
|
|
||||||
|
|
||||||
# 获取角色对象
|
|
||||||
def get_role_data(page, limit, filters):
|
|
||||||
role = Role.query.filter(and_(*[getattr(Role, k).like(v) for k, v in filters.items()])).paginate(page=page,
|
|
||||||
per_page=limit,
|
|
||||||
error_out=False)
|
|
||||||
count = Role.query.count()
|
|
||||||
return role, count
|
|
||||||
|
|
||||||
|
|
||||||
# 获取角色dict
|
|
||||||
def get_role_data_dict(page, limit, filters):
|
|
||||||
role, count = get_role_data(page, limit, filters)
|
|
||||||
role_schema = RoleSchema(many=True) # 用已继承ma.ModelSchema类的自定制类生成序列化类
|
|
||||||
output = role_schema.dump(role.items) # 生成可序列化对象
|
|
||||||
return output, count
|
|
||||||
|
|
||||||
|
|
||||||
# 增加角色
|
|
||||||
def add_role(req):
|
|
||||||
details = req.get("details")
|
|
||||||
enable = req.get("enable")
|
|
||||||
roleCode = req.get("roleCode")
|
|
||||||
roleName = req.get("roleName")
|
|
||||||
sort = req.get("sort")
|
|
||||||
role = Role(
|
|
||||||
details=details,
|
|
||||||
enable=enable,
|
|
||||||
code=roleCode,
|
|
||||||
name=roleName,
|
|
||||||
sort=sort
|
|
||||||
)
|
|
||||||
db.session.add(role)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# 通过id获取角色
|
|
||||||
def get_role_by_id(id):
|
|
||||||
r = Role.query.filter_by(id=id).first()
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
# 更新角色
|
|
||||||
def update_role(req_json):
|
|
||||||
id = req_json.get("roleId")
|
|
||||||
data = {
|
|
||||||
"code": req_json.get("roleCode"),
|
|
||||||
"name": req_json.get("roleName"),
|
|
||||||
"sort": req_json.get("sort"),
|
|
||||||
"enable": req_json.get("enable"),
|
|
||||||
"details": req_json.get("details")
|
|
||||||
}
|
|
||||||
print(data)
|
|
||||||
role = Role.query.filter_by(id=id).update(data)
|
|
||||||
db.session.commit()
|
|
||||||
return role
|
|
||||||
|
|
||||||
|
|
||||||
# 获取角色的权限
|
|
||||||
def get_role_power(id):
|
|
||||||
role = Role.query.filter_by(id=id).first()
|
|
||||||
check_powers = role.power
|
|
||||||
check_powers_list = []
|
|
||||||
for cp in check_powers:
|
|
||||||
check_powers_list.append(cp.id)
|
|
||||||
powers = Power.query.all()
|
|
||||||
power_schema = PowerSchema(many=True) # 用已继承ma.ModelSchema类的自定制类生成序列化类
|
|
||||||
output = power_schema.dump(powers) # 生成可序列化对象
|
|
||||||
for i in output:
|
|
||||||
if int(i.get("powerId")) in check_powers_list:
|
|
||||||
i["checkArr"] = "1"
|
|
||||||
else:
|
|
||||||
i["checkArr"] = "0"
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
# 更新角色权限
|
|
||||||
def update_role_power(id, power_list):
|
|
||||||
role = Role.query.filter_by(id=id).first()
|
|
||||||
power_id_list = []
|
|
||||||
for p in role.power:
|
|
||||||
power_id_list.append(p.id)
|
|
||||||
print(p.id)
|
|
||||||
print(power_id_list)
|
|
||||||
powers = Power.query.filter(Power.id.in_(power_id_list)).all()
|
|
||||||
for p in powers:
|
|
||||||
role.power.remove(p)
|
|
||||||
powers = Power.query.filter(Power.id.in_(power_list)).all()
|
|
||||||
for p in powers:
|
|
||||||
role.power.append(p)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# 启动角色
|
|
||||||
def enable_status(id):
|
|
||||||
enable = 1
|
|
||||||
role = Role.query.filter_by(id=id).update({"enable": enable})
|
|
||||||
if role:
|
|
||||||
db.session.commit()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# 停用角色
|
|
||||||
def disable_status(id):
|
|
||||||
enable = 0
|
|
||||||
role = Role.query.filter_by(id=id).update({"enable": enable})
|
|
||||||
if role:
|
|
||||||
db.session.commit()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 删除角色
|
|
||||||
def remove_role(id):
|
|
||||||
role = Role.query.filter_by(id=id).first()
|
|
||||||
# 删除该角色的权限
|
|
||||||
power_id_list = []
|
|
||||||
for p in role.power:
|
|
||||||
power_id_list.append(p.id)
|
|
||||||
|
|
||||||
powers = Power.query.filter(Power.id.in_(power_id_list)).all()
|
|
||||||
for p in powers:
|
|
||||||
role.power.remove(p)
|
|
||||||
user_id_list = []
|
|
||||||
for u in role.user:
|
|
||||||
user_id_list.append(u.id)
|
|
||||||
users = User.query.filter(User.id.in_(user_id_list)).all()
|
|
||||||
for u in users:
|
|
||||||
role.user.remove(u)
|
|
||||||
r = Role.query.filter_by(id=id).delete()
|
|
||||||
db.session.commit()
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
# 批量删除
|
|
||||||
def batch_remove(ids):
|
|
||||||
# role = Role.query.filter(Role.id.in_(ids)).delete(synchronize_session=False)
|
|
||||||
# db.session.commit()
|
|
||||||
for id in ids:
|
|
||||||
remove_role(id)
|
|
||||||
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
from flask import jsonify
|
|
||||||
from flask_login import current_user
|
|
||||||
from flask_marshmallow import Marshmallow
|
|
||||||
from marshmallow import fields
|
|
||||||
from sqlalchemy import and_
|
|
||||||
from sqlalchemy.sql import exists
|
|
||||||
|
|
||||||
from applications.models import db
|
|
||||||
from applications.models.admin import User, Role
|
|
||||||
from sqlalchemy.orm.dynamic import AppenderQuery
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
|
||||||
|
|
||||||
|
|
||||||
# 用户models的序列化类
|
|
||||||
class UserSchema(ma.Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
username = fields.Str()
|
|
||||||
realname = fields.Str()
|
|
||||||
enable = fields.Integer()
|
|
||||||
create_at = fields.DateTime()
|
|
||||||
update_at = fields.DateTime()
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
获取用户的sqlalchemy对象
|
|
||||||
分页器
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_data(page, limit, filters):
|
|
||||||
user = User.query.filter(and_(*[getattr(User, k).like(v) for k, v in filters.items()])).paginate(page=page,
|
|
||||||
per_page=limit,
|
|
||||||
error_out=False)
|
|
||||||
count = User.query.count()
|
|
||||||
return user, count
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
获取用户的dict数据
|
|
||||||
分页器
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_data_dict(page, limit, filters):
|
|
||||||
user, count = get_user_data(page, limit, filters)
|
|
||||||
user_schema = UserSchema(many=True) # 用已继承ma.ModelSchema类的自定制类生成序列化类
|
|
||||||
output = user_schema.dump(user.items) # 生成可序列化对象
|
|
||||||
return output, count
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
通过名称获取用户
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_name(username):
|
|
||||||
return User.query.filter_by(username=username).first()
|
|
||||||
|
|
||||||
|
|
||||||
# 判断用户是否存在
|
|
||||||
def is_user_exists(username):
|
|
||||||
res = User.query.filter_by(username=username).count()
|
|
||||||
return bool(res)
|
|
||||||
|
|
||||||
|
|
||||||
# 增加用户
|
|
||||||
def add_user(username, realName, password):
|
|
||||||
user = User(username=username, realname=realName)
|
|
||||||
user.set_password(password)
|
|
||||||
db.session.add(user)
|
|
||||||
db.session.commit()
|
|
||||||
return user.id
|
|
||||||
|
|
||||||
|
|
||||||
# 增加用户角色
|
|
||||||
def add_user_role(id, roles_list):
|
|
||||||
user = User.query.filter_by(id=id).first()
|
|
||||||
roles = Role.query.filter(Role.id.in_(roles_list)).all()
|
|
||||||
for r in roles:
|
|
||||||
user.role.append(r)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# 更新用户信息
|
|
||||||
def update_user(id, username, realname):
|
|
||||||
user = User.query.filter_by(id=id).update({'username': username, 'realname': realname})
|
|
||||||
db.session.commit()
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
def delete_by_id(id):
|
|
||||||
user = User.query.filter_by(id=id).first()
|
|
||||||
roles_id = []
|
|
||||||
for role in user.role:
|
|
||||||
roles_id.append(role.id)
|
|
||||||
roles = Role.query.filter(Role.id.in_(roles_id)).all()
|
|
||||||
for r in roles:
|
|
||||||
user.role.remove(r)
|
|
||||||
res = User.query.filter_by(id=id).delete()
|
|
||||||
db.session.commit()
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
# 启动用户
|
|
||||||
def enable_status(id):
|
|
||||||
enable = 1
|
|
||||||
user = User.query.filter_by(id=id).update({"enable": enable})
|
|
||||||
if user:
|
|
||||||
db.session.commit()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# 停用用户
|
|
||||||
def disable_status(id):
|
|
||||||
enable = 0
|
|
||||||
user = User.query.filter_by(id=id).update({"enable": enable})
|
|
||||||
if user:
|
|
||||||
db.session.commit()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# 批量删除
|
|
||||||
def batch_remove(ids):
|
|
||||||
for id in ids:
|
|
||||||
delete_by_id(id)
|
|
||||||
|
|
||||||
|
|
||||||
def update_user_role(id, roles_list):
|
|
||||||
user = User.query.filter_by(id=id).first()
|
|
||||||
roles_id = []
|
|
||||||
for role in user.role:
|
|
||||||
roles_id.append(role.id)
|
|
||||||
roles = Role.query.filter(Role.id.in_(roles_id)).all()
|
|
||||||
for r in roles:
|
|
||||||
user.role.remove(r)
|
|
||||||
roles = Role.query.filter(Role.id.in_(roles_list)).all()
|
|
||||||
for r in roles:
|
|
||||||
user.role.append(r)
|
|
||||||
db.session.commit()
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
from flask_login import current_user
|
|
||||||
|
|
||||||
from applications.models import db
|
|
||||||
from applications.models.admin import AdminLog
|
|
||||||
|
|
||||||
|
|
||||||
def login_log(request,uid, is_access):
|
|
||||||
info = {
|
|
||||||
'method': request.method,
|
|
||||||
'url': request.path,
|
|
||||||
'ip': request.remote_addr,
|
|
||||||
'user_agent': request.headers.get('User-Agent'),
|
|
||||||
'desc': request.form.get('username'),
|
|
||||||
'uid': uid,
|
|
||||||
'success': int(is_access)
|
|
||||||
|
|
||||||
}
|
|
||||||
log = AdminLog(
|
|
||||||
url=info.get('url'),
|
|
||||||
ip=info.get('ip'),
|
|
||||||
user_agent=info.get('user_agent'),
|
|
||||||
desc=info.get('desc'),
|
|
||||||
uid=info.get('uid'),
|
|
||||||
method=info.get('method'),
|
|
||||||
success = info.get('success')
|
|
||||||
)
|
|
||||||
db.session.add(log)
|
|
||||||
db.session.flush()
|
|
||||||
db.session.commit()
|
|
||||||
return log.id
|
|
||||||
|
|
||||||
|
|
||||||
def admin_log(request, is_access):
|
|
||||||
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':int(is_access)
|
|
||||||
|
|
||||||
}
|
|
||||||
log = AdminLog(
|
|
||||||
url=info.get('url'),
|
|
||||||
ip=info.get('ip'),
|
|
||||||
user_agent=info.get('user_agent'),
|
|
||||||
desc=info.get('desc'),
|
|
||||||
uid=info.get('uid'),
|
|
||||||
method=info.get('method'),
|
|
||||||
success=info.get('success')
|
|
||||||
)
|
|
||||||
db.session.add(log)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return log.id
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from flask_debugtoolbar import DebugToolbarExtension
|
|
||||||
|
|
||||||
|
|
||||||
def OpenDug(app):
|
|
||||||
toolbar = DebugToolbarExtension()
|
|
||||||
toolbar.init_app(app)
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
from functools import wraps
|
|
||||||
from flask import abort, request, jsonify, session
|
|
||||||
from flask_login import login_required
|
|
||||||
from applications.service.admin_log import admin_log
|
|
||||||
|
|
||||||
|
|
||||||
def authorize(power: str):
|
|
||||||
def decorator(func):
|
|
||||||
@login_required
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
if not power in session.get('permissions'):
|
|
||||||
if request.method == 'GET':
|
|
||||||
abort(403)
|
|
||||||
else:
|
|
||||||
return jsonify(success=False, msg="权限不足!")
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def authorize_and_log(power: str):
|
|
||||||
def decorator(func):
|
|
||||||
@login_required
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
if not power in session['permissions']:
|
|
||||||
admin_log(request=request, is_access=False)
|
|
||||||
if request.method == 'GET':
|
|
||||||
abort(403)
|
|
||||||
else:
|
|
||||||
return jsonify(success=False, msg="权限不足!")
|
|
||||||
admin_log(request=request, is_access=True)
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
from flask_uploads import UploadSet, IMAGES
|
|
||||||
|
|
||||||
|
|
||||||
photos = UploadSet('photos', IMAGES)
|
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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="用户名或密码错误")
|
||||||
@@ -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'])
|
||||||
@@ -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="操作有误")
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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')
|
||||||
@@ -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')
|
||||||
@@ -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)
|
||||||
@@ -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')
|
||||||
@@ -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')
|
||||||
@@ -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')
|
||||||
@@ -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')
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from applications.views.admin import init_adminViews
|
|
||||||
from applications.views.index import init_indexViews
|
|
||||||
|
|
||||||
|
|
||||||
def init_view(app):
|
|
||||||
init_adminViews(app)
|
|
||||||
init_indexViews(app)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
from applications.views.admin.index import admin_index
|
|
||||||
from applications.views.admin.user import admin_user
|
|
||||||
from applications.views.admin.file import admin_file
|
|
||||||
from applications.views.admin.monitor import admin_Monitor
|
|
||||||
from applications.views.admin.admin_log import admin_log
|
|
||||||
from applications.views.admin.power import admin_power
|
|
||||||
from applications.views.admin.role import admin_role
|
|
||||||
"""
|
|
||||||
初始化蓝图
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def init_adminViews(app):
|
|
||||||
|
|
||||||
app.register_blueprint(admin_index)
|
|
||||||
app.register_blueprint(admin_user)
|
|
||||||
app.register_blueprint(admin_file)
|
|
||||||
app.register_blueprint(admin_Monitor)
|
|
||||||
app.register_blueprint(admin_log)
|
|
||||||
app.register_blueprint(admin_power)
|
|
||||||
app.register_blueprint(admin_role)
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
from flask import Blueprint, request, render_template, jsonify
|
|
||||||
from flask_login import login_required
|
|
||||||
from flask_marshmallow import Marshmallow
|
|
||||||
from marshmallow import fields
|
|
||||||
from sqlalchemy import desc
|
|
||||||
from applications.models.admin import AdminLog
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
|
||||||
admin_log = Blueprint('adminLog', __name__, url_prefix='/admin/log')
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------
|
|
||||||
# ------------------------- 日志管理 --------------------------
|
|
||||||
# ----------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
@admin_log.route('/')
|
|
||||||
@login_required
|
|
||||||
def index():
|
|
||||||
return render_template('admin/admin_log/main.html')
|
|
||||||
|
|
||||||
|
|
||||||
class LogSchema(ma.Schema): # 序列化类
|
|
||||||
id = fields.Integer()
|
|
||||||
method = fields.Str()
|
|
||||||
uid = fields.Str()
|
|
||||||
url = fields.Str()
|
|
||||||
desc = fields.Str()
|
|
||||||
ip = fields.Str()
|
|
||||||
user_agent = fields.Str()
|
|
||||||
success = fields.Bool()
|
|
||||||
create_time = fields.DateTime()
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 登录日志
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
|
|
||||||
@admin_log.route('/loginLog')
|
|
||||||
@login_required
|
|
||||||
def loginLog():
|
|
||||||
page = request.args.get('page', type=int)
|
|
||||||
limit = request.args.get('limit', type=int)
|
|
||||||
log = AdminLog.query.filter_by(url='/admin/login').order_by(desc(AdminLog.create_time)).paginate(page=page,
|
|
||||||
per_page=limit,
|
|
||||||
error_out=False)
|
|
||||||
count = AdminLog.query.filter_by(url='/admin/login').count()
|
|
||||||
role_schema = LogSchema(many=True)
|
|
||||||
output = role_schema.dump(log.items)
|
|
||||||
res = {
|
|
||||||
'msg': "",
|
|
||||||
'code': 0,
|
|
||||||
'data': output,
|
|
||||||
'count': count,
|
|
||||||
'limit': "10"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 操作日志
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
|
|
||||||
@admin_log.route('/operateLog')
|
|
||||||
@login_required
|
|
||||||
def operateLog():
|
|
||||||
page = request.args.get('page', type=int)
|
|
||||||
limit = request.args.get('limit', type=int)
|
|
||||||
log = AdminLog.query.filter(AdminLog.url != '/admin/login').order_by(desc(AdminLog.create_time)).paginate(page=page,
|
|
||||||
per_page=limit,
|
|
||||||
error_out=False)
|
|
||||||
count = AdminLog.query.filter(AdminLog.url != '/admin/login').count()
|
|
||||||
role_schema = LogSchema(many=True)
|
|
||||||
output = role_schema.dump(log.items)
|
|
||||||
res = {
|
|
||||||
'msg': "",
|
|
||||||
'code': 0,
|
|
||||||
'data': output,
|
|
||||||
'count': count,
|
|
||||||
'limit': "10"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonify(res)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from flask import session
|
|
||||||
|
|
||||||
|
|
||||||
def init_template_global(app):
|
|
||||||
@app.template_global()
|
|
||||||
def authorize(power):
|
|
||||||
# print(power)
|
|
||||||
# print(session.get('permissions'))
|
|
||||||
return bool(power in session.get('permissions'))
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import os
|
|
||||||
from flask import Blueprint, request, render_template, jsonify, current_app
|
|
||||||
from flask_login import login_required
|
|
||||||
from applications.models import db
|
|
||||||
from applications.models.admin import Photo
|
|
||||||
from applications.service.admin.file import get_photo, upload_one, delete_photo_by_id
|
|
||||||
from applications.service.route_auth import authorize_and_log
|
|
||||||
|
|
||||||
admin_file = Blueprint('adminFile', __name__, url_prefix='/admin/file')
|
|
||||||
|
|
||||||
|
|
||||||
# 图片管理
|
|
||||||
@admin_file.route('/')
|
|
||||||
@authorize_and_log("admin:file:main")
|
|
||||||
def index():
|
|
||||||
return render_template('admin/photo/photo.html')
|
|
||||||
|
|
||||||
|
|
||||||
# 图片数据
|
|
||||||
@admin_file.route('/table')
|
|
||||||
@authorize_and_log("admin:file:main")
|
|
||||||
def table():
|
|
||||||
page = request.args.get('page', type=int)
|
|
||||||
limit = request.args.get('limit', type=int)
|
|
||||||
data, count = get_photo(page=page, limit=limit)
|
|
||||||
res = {
|
|
||||||
'msg': "",
|
|
||||||
'code': 0,
|
|
||||||
'data': data,
|
|
||||||
'count': count,
|
|
||||||
'limit': "10"
|
|
||||||
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
# 上传接口
|
|
||||||
@admin_file.route('/upload', methods=['GET', 'POST'])
|
|
||||||
@authorize_and_log("admin:file:add")
|
|
||||||
def upload():
|
|
||||||
if request.method == 'POST' and 'file' in request.files:
|
|
||||||
photo = request.files['file']
|
|
||||||
mime = request.files['file'].content_type
|
|
||||||
file_url = upload_one(photo=photo, mime=mime)
|
|
||||||
res = {
|
|
||||||
"msg": "上传成功",
|
|
||||||
"code": 0,
|
|
||||||
"data":
|
|
||||||
{"src": file_url}
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
return render_template('admin/photo/photo_add.html')
|
|
||||||
|
|
||||||
|
|
||||||
# 图片删除
|
|
||||||
@admin_file.route('/delete', methods=['GET', 'POST'])
|
|
||||||
@authorize_and_log("admin:file:delete")
|
|
||||||
def delete():
|
|
||||||
id = request.form.get('id')
|
|
||||||
res = delete_photo_by_id(id)
|
|
||||||
if res:
|
|
||||||
return jsonify(msg="删除成功", code=200)
|
|
||||||
else:
|
|
||||||
return jsonify(msg="删除失败", code=999)
|
|
||||||
|
|
||||||
|
|
||||||
# 图片批量删除
|
|
||||||
@admin_file.route('/batchRemove', methods=['GET', 'POST'])
|
|
||||||
@authorize_and_log("admin:file:delete")
|
|
||||||
def batchRemove():
|
|
||||||
ids = request.form.getlist('ids[]')
|
|
||||||
photo_name = Photo.query.filter(Photo.id.in_(ids)).all()
|
|
||||||
upload_url = current_app.config.get("UPLOADED_PHOTOS_DEST")
|
|
||||||
for p in photo_name:
|
|
||||||
os.remove(upload_url + '/' + p.name)
|
|
||||||
photo = Photo.query.filter(Photo.id.in_(ids)).delete(synchronize_session=False)
|
|
||||||
db.session.commit()
|
|
||||||
if photo:
|
|
||||||
return jsonify(msg="删除成功", code=200)
|
|
||||||
else:
|
|
||||||
res = {"msg": "删除失败", "code": 999}
|
|
||||||
return res
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
from flask import Blueprint, render_template, jsonify, request, session, redirect, url_for
|
|
||||||
from flask_login import login_user, login_required, logout_user, current_user
|
|
||||||
from applications.service.admin.index import add_auth_session, make_menu_tree, get_captcha
|
|
||||||
from applications.service.admin_log import login_log
|
|
||||||
from applications.models.admin import User
|
|
||||||
|
|
||||||
admin_index = Blueprint('adminIndex', __name__, url_prefix='/admin')
|
|
||||||
|
|
||||||
|
|
||||||
# 首页
|
|
||||||
@admin_index.route('/')
|
|
||||||
@login_required
|
|
||||||
def index():
|
|
||||||
realname = current_user.realname
|
|
||||||
return render_template('admin/index.html', realname=realname)
|
|
||||||
|
|
||||||
|
|
||||||
# 获取验证码
|
|
||||||
@admin_index.route('/getCaptcha', methods=["GET"])
|
|
||||||
def getCaptcha():
|
|
||||||
resp,code=get_captcha()
|
|
||||||
session["code"] = code
|
|
||||||
return resp
|
|
||||||
|
|
||||||
|
|
||||||
# 登录
|
|
||||||
@admin_index.route('/login', methods=['GET', 'POST'])
|
|
||||||
def login():
|
|
||||||
if request.method == 'POST':
|
|
||||||
req = request.form
|
|
||||||
username = req.get('username')
|
|
||||||
password = req.get('password')
|
|
||||||
code = req.get('captcha')
|
|
||||||
|
|
||||||
if not username or not password or not code:
|
|
||||||
return jsonify(msg="用户名或密码没有输入", code=0,success=False)
|
|
||||||
s_code = session.get("code", None)
|
|
||||||
|
|
||||||
if not all([code, s_code]):
|
|
||||||
return jsonify(msg="参数错误", code=0,success=False)
|
|
||||||
|
|
||||||
if code != s_code:
|
|
||||||
return jsonify(msg="验证码错误", code=0,success=False)
|
|
||||||
user = User.query.filter_by(username=username).first()
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return jsonify(msg="不存在的用户", code=0,success=False)
|
|
||||||
|
|
||||||
if user.enable is 0:
|
|
||||||
return jsonify(msg="用户被暂停使用", code=0,success=False)
|
|
||||||
|
|
||||||
if username == user.username and user.validate_password(password):
|
|
||||||
# 登录
|
|
||||||
login_user(user)
|
|
||||||
# 记录登录日志
|
|
||||||
login_log(request, uid=user.id, is_access=True)
|
|
||||||
# 存入权限
|
|
||||||
add_auth_session()
|
|
||||||
return jsonify(msg="登录成功", code=1,success=True)
|
|
||||||
login_log(request,uid=user.id, is_access=False)
|
|
||||||
return jsonify(msg="用户名或密码错误", code=0,success=False)
|
|
||||||
print(current_user.is_authenticated)
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
return redirect(url_for('adminIndex.index'))
|
|
||||||
return render_template('admin/login.html')
|
|
||||||
|
|
||||||
|
|
||||||
# 退出登录
|
|
||||||
@admin_index.route('/logout', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def logout():
|
|
||||||
logout_user()
|
|
||||||
session.pop('permissions')
|
|
||||||
return jsonify(msg="注销成功", success=True)
|
|
||||||
|
|
||||||
|
|
||||||
# 菜单
|
|
||||||
@admin_index.route('/menu')
|
|
||||||
@login_required
|
|
||||||
def menu():
|
|
||||||
menu_tree = make_menu_tree()
|
|
||||||
return jsonify(menu_tree)
|
|
||||||
|
|
||||||
|
|
||||||
# 控制台页面
|
|
||||||
@admin_index.route('/welcome')
|
|
||||||
@login_required
|
|
||||||
def welcome():
|
|
||||||
return render_template('admin/console/console.html')
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import os
|
|
||||||
import platform
|
|
||||||
import re
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
from flask import Blueprint, render_template
|
|
||||||
from flask_marshmallow import Marshmallow
|
|
||||||
|
|
||||||
from applications.service.route_auth import authorize_and_log
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
|
||||||
admin_Monitor = Blueprint('adminMonitor', __name__, url_prefix='/admin/monitor')
|
|
||||||
|
|
||||||
|
|
||||||
# 系统监控
|
|
||||||
@admin_Monitor.route('/')
|
|
||||||
@authorize_and_log("admin:monitor:main")
|
|
||||||
def main():
|
|
||||||
# 主机名称
|
|
||||||
hostname = platform.node()
|
|
||||||
# 系统版本
|
|
||||||
system_version = platform.platform()
|
|
||||||
# python版本
|
|
||||||
python_version = platform.python_version()
|
|
||||||
# 逻辑cpu数量
|
|
||||||
cpu_count = psutil.cpu_count()
|
|
||||||
# cup使用率
|
|
||||||
cpus_percent = psutil.cpu_percent(interval=0.1)
|
|
||||||
# 内存
|
|
||||||
memory_information = psutil.virtual_memory()
|
|
||||||
# 内存使用率
|
|
||||||
memory_usage = memory_information.percent
|
|
||||||
memory_used = str(round(memory_information.used / 1024 / 1024))
|
|
||||||
memory_total = str(round(memory_information.total / 1024 / 1024))
|
|
||||||
memory_free = str(round(memory_information.free / 1024 / 1024))
|
|
||||||
# 磁盘信息
|
|
||||||
|
|
||||||
disk_partitions_list = []
|
|
||||||
# 判断是否在容器中
|
|
||||||
if not os.path.exists('/.dockerenv'):
|
|
||||||
disk_partitions = psutil.disk_partitions()
|
|
||||||
for i in disk_partitions:
|
|
||||||
a = psutil.disk_usage(i.device)
|
|
||||||
disk_partitions_dict = {
|
|
||||||
'device': i.device,
|
|
||||||
'fstype': i.fstype,
|
|
||||||
'total': str(round(a.total / 1024 / 1024)),
|
|
||||||
'used': str(round(a.used / 1024 / 1024)),
|
|
||||||
'free': str(round(a.free / 1024 / 1024)),
|
|
||||||
'percent': a.percent
|
|
||||||
}
|
|
||||||
disk_partitions_list.append(disk_partitions_dict)
|
|
||||||
|
|
||||||
# 开机时间
|
|
||||||
boot_time = datetime.fromtimestamp(psutil.boot_time()).replace(microsecond=0)
|
|
||||||
up_time = datetime.now().replace(microsecond=0) - boot_time
|
|
||||||
up_time_list = re.split(r':', str(up_time))
|
|
||||||
up_time_format = " {} 小时{} 分钟{} 秒".format(up_time_list[0], up_time_list[1], up_time_list[2])
|
|
||||||
|
|
||||||
return render_template('admin/monitor.html',
|
|
||||||
hostname=hostname,
|
|
||||||
system_version=system_version,
|
|
||||||
python_version=python_version,
|
|
||||||
cpus_percent=cpus_percent,
|
|
||||||
memory_usage=memory_usage,
|
|
||||||
cpu_count=cpu_count,
|
|
||||||
memory_used=memory_used,
|
|
||||||
memory_total=memory_total,
|
|
||||||
memory_free=memory_free,
|
|
||||||
boot_time=boot_time,
|
|
||||||
up_time_format=up_time_format,
|
|
||||||
disk_partitions_list=disk_partitions_list
|
|
||||||
)
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
from flask import Blueprint, render_template, request, jsonify
|
|
||||||
from flask_login import login_required
|
|
||||||
from applications.service.admin.power import get_power_dict, select_parent, save_power, remove_power, get_power_by_id, \
|
|
||||||
update_power, enable_status, disable_status, batch_remove
|
|
||||||
from applications.service.route_auth import authorize_and_log
|
|
||||||
|
|
||||||
admin_power = Blueprint('adminPower', __name__, url_prefix='/admin/power')
|
|
||||||
|
|
||||||
|
|
||||||
@admin_power.route('/')
|
|
||||||
@authorize_and_log("admin:power:main")
|
|
||||||
def index():
|
|
||||||
return render_template('admin/power/main.html')
|
|
||||||
|
|
||||||
|
|
||||||
@admin_power.route('/data')
|
|
||||||
@authorize_and_log("admin:power:main")
|
|
||||||
def data():
|
|
||||||
power_data = get_power_dict()
|
|
||||||
res = {
|
|
||||||
"data": power_data
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
@admin_power.route('/add')
|
|
||||||
@authorize_and_log("admin:power:add")
|
|
||||||
def add():
|
|
||||||
return render_template('admin/power/add.html')
|
|
||||||
|
|
||||||
|
|
||||||
@admin_power.route('/selectParent')
|
|
||||||
@authorize_and_log("admin:power:main")
|
|
||||||
def selectParent():
|
|
||||||
power_data = select_parent()
|
|
||||||
res = {
|
|
||||||
"status": {"code": 200, "message": "默认"},
|
|
||||||
"data": power_data
|
|
||||||
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
# 增加
|
|
||||||
@admin_power.route('/save', methods=['POST'])
|
|
||||||
@authorize_and_log("admin:power:add")
|
|
||||||
def save():
|
|
||||||
req = request.json
|
|
||||||
save_power(req)
|
|
||||||
return jsonify(msg="成功", success=True)
|
|
||||||
|
|
||||||
|
|
||||||
# 权限编辑
|
|
||||||
@admin_power.route('/edit/<int:id>', methods=['GET', 'POST'])
|
|
||||||
@authorize_and_log("admin:power:edit")
|
|
||||||
def edit(id):
|
|
||||||
power = get_power_by_id(id)
|
|
||||||
icon = str(power.icon).split()
|
|
||||||
if len(icon) == 2:
|
|
||||||
icon = icon[1]
|
|
||||||
else:
|
|
||||||
icon = None
|
|
||||||
return render_template('admin/power/edit.html', power=power, icon=icon)
|
|
||||||
|
|
||||||
|
|
||||||
# 权限更新
|
|
||||||
@admin_power.route('/update', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:power:edit")
|
|
||||||
def update():
|
|
||||||
res = update_power(request.json)
|
|
||||||
if not res:
|
|
||||||
return jsonify(success=False, msg="更新权限失败")
|
|
||||||
return jsonify(success=True, msg="更新权限成功")
|
|
||||||
|
|
||||||
|
|
||||||
# 启用用户
|
|
||||||
@admin_power.route('/enable', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:power:edit")
|
|
||||||
def enable():
|
|
||||||
id = request.json.get('powerId')
|
|
||||||
print(id)
|
|
||||||
if id:
|
|
||||||
res = enable_status(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(msg="出错啦", success=False)
|
|
||||||
return jsonify(msg="启动成功", success=True)
|
|
||||||
return jsonify(msg="数据错误", success=False)
|
|
||||||
|
|
||||||
|
|
||||||
# 禁用用户
|
|
||||||
@admin_power.route('/disable', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:power:edit")
|
|
||||||
def disenable():
|
|
||||||
id = request.json.get('powerId')
|
|
||||||
print(id)
|
|
||||||
if id:
|
|
||||||
res = disable_status(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(msg="出错啦", success=False)
|
|
||||||
return jsonify(msg="禁用成功", success=True)
|
|
||||||
return jsonify(msg="数据错误", success=False)
|
|
||||||
|
|
||||||
|
|
||||||
# 权限删除
|
|
||||||
@admin_power.route('/remove/<int:id>', methods=['DELETE'])
|
|
||||||
@authorize_and_log("admin:power:remove")
|
|
||||||
def remove(id):
|
|
||||||
r = remove_power(id)
|
|
||||||
if r:
|
|
||||||
return jsonify(success=True, msg="删除成功")
|
|
||||||
else:
|
|
||||||
return jsonify(success=False, msg="删除失败")
|
|
||||||
|
|
||||||
|
|
||||||
# 批量删除
|
|
||||||
@admin_power.route('/batchRemove', methods=['DELETE'])
|
|
||||||
@authorize_and_log("admin:power:remove")
|
|
||||||
def batchRemove():
|
|
||||||
ids = request.form.getlist('ids[]')
|
|
||||||
batch_remove(ids)
|
|
||||||
return jsonify(success=True, msg="批量删除成功")
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
from flask import Blueprint, render_template, request, jsonify
|
|
||||||
from flask_login import login_required
|
|
||||||
|
|
||||||
from applications.service.admin.role import get_role_data_dict, add_role, get_role_power, update_role_power, \
|
|
||||||
get_role_by_id, update_role, remove_role, batch_remove, enable_status, disable_status
|
|
||||||
from applications.service.route_auth import authorize_and_log
|
|
||||||
|
|
||||||
admin_role = Blueprint('adminRole', __name__, url_prefix='/admin/role')
|
|
||||||
|
|
||||||
|
|
||||||
# 用户管理
|
|
||||||
@admin_role.route('/')
|
|
||||||
@authorize_and_log("admin:role:main")
|
|
||||||
def main():
|
|
||||||
return render_template('admin/role/main.html')
|
|
||||||
|
|
||||||
|
|
||||||
# 表格数据
|
|
||||||
@admin_role.route('/data')
|
|
||||||
@authorize_and_log("admin:role:main")
|
|
||||||
def table():
|
|
||||||
page = request.args.get('page', type=int)
|
|
||||||
limit = request.args.get('limit', type=int)
|
|
||||||
roleName = request.args.get('roleName', type=str)
|
|
||||||
roleCode = request.args.get('roleCode', type=str)
|
|
||||||
filters = {}
|
|
||||||
if roleName:
|
|
||||||
filters["name"] = ('%' + roleName + '%')
|
|
||||||
if roleCode:
|
|
||||||
filters["code"] = ('%' + roleCode + '%')
|
|
||||||
data, count = get_role_data_dict(page=page, limit=limit, filters=filters)
|
|
||||||
res = {
|
|
||||||
'msg': "",
|
|
||||||
'code': 0,
|
|
||||||
'data': data,
|
|
||||||
'count': count,
|
|
||||||
'limit': "10"
|
|
||||||
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
# 角色增加
|
|
||||||
@admin_role.route('/add')
|
|
||||||
@authorize_and_log("admin:role:add")
|
|
||||||
@login_required
|
|
||||||
def add():
|
|
||||||
return render_template('admin/role/add.html')
|
|
||||||
|
|
||||||
|
|
||||||
# 角色增加
|
|
||||||
@admin_role.route('/save', methods=['POST'])
|
|
||||||
@authorize_and_log("admin:role:add")
|
|
||||||
def save():
|
|
||||||
req = request.json
|
|
||||||
add_role(req=req)
|
|
||||||
return jsonify(msg="成功", success=True)
|
|
||||||
|
|
||||||
|
|
||||||
# 角色授权
|
|
||||||
@admin_role.route('/power/<int:id>')
|
|
||||||
@authorize_and_log("admin:role:power")
|
|
||||||
def power(id):
|
|
||||||
return render_template('admin/role/power.html', id=id)
|
|
||||||
|
|
||||||
|
|
||||||
# 获取角色权限
|
|
||||||
@admin_role.route('/getRolePower/<int:id>')
|
|
||||||
@authorize_and_log("admin:role:main")
|
|
||||||
def getRolePower(id):
|
|
||||||
powers = get_role_power(id)
|
|
||||||
res = {
|
|
||||||
"data": powers,
|
|
||||||
"status": {"code": 200, "message": "默认"}
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
# 保存角色权限
|
|
||||||
@admin_role.route('/saveRolePower', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:role:edit")
|
|
||||||
def saveRolePower():
|
|
||||||
req_form = request.form
|
|
||||||
powerIds = req_form.get("powerIds")
|
|
||||||
power_list = powerIds.split(',')
|
|
||||||
roleId = req_form.get("roleId")
|
|
||||||
update_role_power(id=roleId, power_list=power_list)
|
|
||||||
return jsonify(success=True, msg="授权成功")
|
|
||||||
|
|
||||||
|
|
||||||
# 角色编辑
|
|
||||||
@admin_role.route('/edit/<int:id>', methods=['GET', 'POST'])
|
|
||||||
@authorize_and_log("admin:role:edit")
|
|
||||||
def edit(id):
|
|
||||||
role = get_role_by_id(id)
|
|
||||||
return render_template('admin/role/edit.html', role=role)
|
|
||||||
|
|
||||||
|
|
||||||
# 更新角色
|
|
||||||
@admin_role.route('/update', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:role:edit")
|
|
||||||
def update():
|
|
||||||
res = update_role(request.json)
|
|
||||||
if not res:
|
|
||||||
return jsonify(success=False, msg="更新角色失败")
|
|
||||||
return jsonify(success=True, msg="更新角色成功")
|
|
||||||
|
|
||||||
# 启用用户
|
|
||||||
@admin_role.route('/enable', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:role:edit")
|
|
||||||
def enable():
|
|
||||||
id = request.json.get('roleId')
|
|
||||||
print(id)
|
|
||||||
if id:
|
|
||||||
res = enable_status(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(msg="出错啦", success=False)
|
|
||||||
return jsonify(msg="启动成功", success=True)
|
|
||||||
return jsonify(msg="数据错误", success=False)
|
|
||||||
|
|
||||||
|
|
||||||
# 禁用用户
|
|
||||||
@admin_role.route('/disable', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:role:edit")
|
|
||||||
def disenable():
|
|
||||||
id = request.json.get('roleId')
|
|
||||||
print(id)
|
|
||||||
if id:
|
|
||||||
res = disable_status(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(msg="出错啦", success=False)
|
|
||||||
return jsonify(msg="禁用成功", success=True)
|
|
||||||
return jsonify(msg="数据错误", success=False)
|
|
||||||
|
|
||||||
|
|
||||||
# 角色删除
|
|
||||||
@admin_role.route('/remove/<int:id>', methods=['DELETE'])
|
|
||||||
@authorize_and_log("admin:role:remove")
|
|
||||||
def remove(id):
|
|
||||||
res = remove_role(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(success=False, msg="角色删除失败")
|
|
||||||
return jsonify(success=True, msg="角色删除成功")
|
|
||||||
|
|
||||||
|
|
||||||
# 批量删除
|
|
||||||
@admin_role.route('/batchRemove', methods=['DELETE'])
|
|
||||||
@authorize_and_log("admin:role:remove")
|
|
||||||
@login_required
|
|
||||||
def batchRemove():
|
|
||||||
ids = request.form.getlist('ids[]')
|
|
||||||
batch_remove(ids)
|
|
||||||
return jsonify(success=True,msg="批量删除成功")
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
from flask import Blueprint, render_template, request, jsonify
|
|
||||||
from flask_login import login_required
|
|
||||||
from applications.models.admin import User, Role
|
|
||||||
from applications.service.admin.user import get_user_data_dict, add_user, delete_by_id, \
|
|
||||||
batch_remove, update_user, update_user_role, is_user_exists, add_user_role, enable_status, disable_status
|
|
||||||
from applications.service.route_auth import authorize_and_log
|
|
||||||
|
|
||||||
admin_user = Blueprint('adminUser', __name__, url_prefix='/admin/user')
|
|
||||||
|
|
||||||
|
|
||||||
# 用户管理
|
|
||||||
@admin_user.route('/')
|
|
||||||
@authorize_and_log("admin:user:main")
|
|
||||||
def main():
|
|
||||||
return render_template('admin/user/main.html')
|
|
||||||
|
|
||||||
|
|
||||||
# 用户分页查询
|
|
||||||
@admin_user.route('/data')
|
|
||||||
@authorize_and_log("admin:user:main")
|
|
||||||
def data():
|
|
||||||
page = request.args.get('page', type=int)
|
|
||||||
limit = request.args.get('limit', type=int)
|
|
||||||
realName = request.args.get('realName', type=str)
|
|
||||||
username = request.args.get('username', type=str)
|
|
||||||
filters = {}
|
|
||||||
if realName:
|
|
||||||
filters["realname"] = ('%' + realName + '%')
|
|
||||||
if username:
|
|
||||||
filters["username"] = ('%' + username + '%')
|
|
||||||
data, count = get_user_data_dict(page=page, limit=limit, filters=filters)
|
|
||||||
res = {
|
|
||||||
'msg': "",
|
|
||||||
'code': 0,
|
|
||||||
'data': data,
|
|
||||||
'count': count,
|
|
||||||
'limit': "10"
|
|
||||||
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
|
|
||||||
|
|
||||||
# 用户增加
|
|
||||||
@admin_user.route('/add')
|
|
||||||
@authorize_and_log("admin:user:add")
|
|
||||||
def add():
|
|
||||||
roles = Role.query.all()
|
|
||||||
return render_template('admin/user/add.html', roles=roles)
|
|
||||||
|
|
||||||
|
|
||||||
@admin_user.route('/save', methods=['POST'])
|
|
||||||
@authorize_and_log("admin:user:add")
|
|
||||||
def save():
|
|
||||||
req_json = request.json
|
|
||||||
a = req_json.get("roleIds")
|
|
||||||
username = req_json.get('username')
|
|
||||||
realName = req_json.get('realName')
|
|
||||||
password = req_json.get('password')
|
|
||||||
role_ids = a.split(',')
|
|
||||||
|
|
||||||
if not username or not realName or not password:
|
|
||||||
return jsonify(success=False, msg="账号姓名密码不得为空")
|
|
||||||
|
|
||||||
if is_user_exists(username):
|
|
||||||
return jsonify(success=False, msg="用户已经存在")
|
|
||||||
|
|
||||||
id = add_user(username, realName, password)
|
|
||||||
add_user_role(id, role_ids)
|
|
||||||
|
|
||||||
return jsonify(success=True, msg="增加成功")
|
|
||||||
|
|
||||||
|
|
||||||
# 删除用户
|
|
||||||
@admin_user.route('/remove/<int:id>', methods=['DELETE'])
|
|
||||||
@authorize_and_log("admin:user:remove")
|
|
||||||
def delete(id):
|
|
||||||
res = delete_by_id(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(msg="删除失败", success=False)
|
|
||||||
return jsonify(msg="删除成功", success=True)
|
|
||||||
|
|
||||||
|
|
||||||
# 编辑用户
|
|
||||||
@admin_user.route('/edit/<int:id>', methods=['GET', 'POST'])
|
|
||||||
@authorize_and_log("admin:user:edit")
|
|
||||||
def edit(id):
|
|
||||||
user = User.query.filter_by(id=id).first()
|
|
||||||
roles = Role.query.all()
|
|
||||||
checked_roles = []
|
|
||||||
for r in user.role:
|
|
||||||
checked_roles.append(r.id)
|
|
||||||
return render_template('admin/user/edit.html', user=user, roles=roles, checked_roles=checked_roles)
|
|
||||||
|
|
||||||
# 编辑用户
|
|
||||||
@admin_user.route('/update', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:user:edit")
|
|
||||||
def update():
|
|
||||||
req_json = request.json
|
|
||||||
a = req_json.get("roleIds")
|
|
||||||
id = req_json.get("userId")
|
|
||||||
username = req_json.get('username')
|
|
||||||
realName = req_json.get('realName')
|
|
||||||
role_ids = a.split(',')
|
|
||||||
update_user(id, username, realName)
|
|
||||||
update_user_role(id, role_ids)
|
|
||||||
return jsonify(success=True, msg="更新成功")
|
|
||||||
|
|
||||||
|
|
||||||
# 启用用户
|
|
||||||
@admin_user.route('/enable', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:user:edit")
|
|
||||||
def enable():
|
|
||||||
id = request.json.get('userId')
|
|
||||||
print(id)
|
|
||||||
if id:
|
|
||||||
res = enable_status(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(msg="出错啦", success=False)
|
|
||||||
return jsonify(msg="启动成功", success=True)
|
|
||||||
return jsonify(msg="数据错误", success=False)
|
|
||||||
|
|
||||||
|
|
||||||
# 禁用用户
|
|
||||||
@admin_user.route('/disable', methods=['PUT'])
|
|
||||||
@authorize_and_log("admin:user:edit")
|
|
||||||
def disenable():
|
|
||||||
id = request.json.get('userId')
|
|
||||||
print(id)
|
|
||||||
if id:
|
|
||||||
res = disable_status(id)
|
|
||||||
if not res:
|
|
||||||
return jsonify(msg="出错啦", success=False)
|
|
||||||
return jsonify(msg="禁用成功", success=True)
|
|
||||||
return jsonify(msg="数据错误", success=False)
|
|
||||||
|
|
||||||
|
|
||||||
# 批量删除
|
|
||||||
@admin_user.route('/batchRemove', methods=['DELETE'])
|
|
||||||
@authorize_and_log("admin:user:remove")
|
|
||||||
def batchRemove():
|
|
||||||
ids = request.form.getlist('ids[]')
|
|
||||||
batch_remove(ids)
|
|
||||||
return jsonify(success=True, msg="批量删除成功")
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
from applications.views.index.index import index_index
|
|
||||||
|
|
||||||
|
|
||||||
def init_indexViews(app):
|
|
||||||
"""
|
|
||||||
初始化蓝图
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
app.register_blueprint(index_index)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from flask import Blueprint,render_template
|
|
||||||
|
|
||||||
index_index = Blueprint('Index', __name__, url_prefix='/')
|
|
||||||
from flask_marshmallow import Marshmallow
|
|
||||||
|
|
||||||
ma = Marshmallow()
|
|
||||||
|
|
||||||
|
|
||||||
@index_index.route('/')
|
|
||||||
def index():
|
|
||||||
return render_template('index/index.html')
|
|
||||||
@@ -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'])
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import sys
|
|
||||||
sys.path.append('../')
|
|
||||||
import sqlparse
|
|
||||||
import pymysql
|
|
||||||
from applications.config.database import HOST, USERNAME, PASSWORD, DATABASE,PORT
|
|
||||||
|
|
||||||
|
|
||||||
def is_exist_database():
|
|
||||||
db = pymysql.connect(host=HOST,port = int(PORT), user=USERNAME, password=PASSWORD, charset='utf8mb4')
|
|
||||||
cursor1 = db.cursor()
|
|
||||||
sql = "select * from information_schema.SCHEMATA WHERE SCHEMA_NAME = '%s' ; " % DATABASE
|
|
||||||
res = cursor1.execute(sql)
|
|
||||||
db.close()
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def init_database():
|
|
||||||
db = pymysql.connect(host=HOST,port = int(PORT), user=USERNAME, password=PASSWORD, charset='utf8mb4')
|
|
||||||
cursor1 = db.cursor()
|
|
||||||
sql = "CREATE DATABASE IF NOT EXISTS %s" % DATABASE
|
|
||||||
res = cursor1.execute(sql)
|
|
||||||
db.close()
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def execute_fromfile(filename):
|
|
||||||
db = pymysql.connect(host=HOST, user=USERNAME, password=PASSWORD, database=DATABASE, charset='utf8mb4')
|
|
||||||
fd = open(filename, 'r', encoding='utf-8')
|
|
||||||
cursor = db.cursor()
|
|
||||||
sqlfile = fd.read()
|
|
||||||
sqlfile = sqlparse.format(sqlfile, strip_comments=True).strip()
|
|
||||||
|
|
||||||
sqlcommamds = sqlfile.split(';')
|
|
||||||
|
|
||||||
for command in sqlcommamds:
|
|
||||||
try:
|
|
||||||
cursor.execute(command)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
except Exception as msg:
|
|
||||||
|
|
||||||
db.rollback()
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
|
||||||
if is_exist_database():
|
|
||||||
print('数据库已经存在,为防止误初始化,请收动删除 %s 数据库' % str(DATABASE))
|
|
||||||
return
|
|
||||||
if init_database():
|
|
||||||
print('数据库%s创建成功' % str(DATABASE))
|
|
||||||
execute_fromfile('pear.sql')
|
|
||||||
print('表创建成功')
|
|
||||||
print('欢迎使用pear-admin-flask,请使用 flask run 命令启动程序')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
init_db()
|
|
||||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
|
|
||||||
def init_error(app):
|
def init_error_views(app):
|
||||||
|
|
||||||
@app.errorhandler(403)
|
@app.errorhandler(403)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
return render_template('errors/403.html'), 403
|
return render_template('errors/403.html'), 403
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
|
|
||||||
|
|
||||||
def init_flask_login(app):
|
def init_login_manager(app):
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
|
|
||||||
login_manager.login_view = 'adminIndex.login'
|
login_manager.login_view = 'index.login'
|
||||||
login_manager.login_message = u'请登录以访问此页面'
|
login_manager.login_message = u'请登录以访问此页面'
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
from applications.models.admin import User
|
from models import UserModel
|
||||||
user = User.query.get(int(user_id))
|
user = UserModel.query.get(int(user_id))
|
||||||
return user
|
return user
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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)
|
||||||
@@ -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'))
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
from flask import Flask, session
|
|
||||||
from flask_uploads import configure_uploads, patch_request_class
|
|
||||||
from applications.config import database, common
|
|
||||||
from applications.service.deBug import OpenDug
|
|
||||||
from applications.service.upload import photos
|
|
||||||
from applications.views import init_view
|
|
||||||
from applications.models import db
|
|
||||||
from applications.service.login import init_flask_login
|
|
||||||
from applications.views.admin.error import init_error
|
|
||||||
from applications.views.admin.context_processor import init_template_global
|
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
|
||||||
app = Flask(__name__)
|
|
||||||
# 注册路由
|
|
||||||
init_view(app)
|
|
||||||
# 注册错误页面
|
|
||||||
init_error(app)
|
|
||||||
# 注册自定义上下文函数
|
|
||||||
init_template_global(app)
|
|
||||||
# 引入数据库配置
|
|
||||||
app.config.from_object(database)
|
|
||||||
app.config.from_object(common)
|
|
||||||
# sqlalchemy初始化
|
|
||||||
db.init_app(app)
|
|
||||||
# flask_login初始化
|
|
||||||
init_flask_login(app)
|
|
||||||
# 文件上传
|
|
||||||
configure_uploads(app, photos)
|
|
||||||
patch_request_class(app)
|
|
||||||
# 调试工具栏
|
|
||||||
OpenDug(app)
|
|
||||||
# logo
|
|
||||||
logo()
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
def logo():
|
|
||||||
print('''
|
|
||||||
_____ _ _ ______ _ _
|
|
||||||
| __ \ /\ | | (_) | ____| | | |
|
|
||||||
| |__) |__ __ _ _ __ / \ __| |_ __ ___ _ _ __ | |__ | | __ _ ___| | __
|
|
||||||
| ___/ _ \/ _` | '__| / /\ \ / _` | '_ ` _ \| | '_ \ | __| | |/ _` / __| |/ /
|
|
||||||
| | | __/ (_| | | / ____ \ (_| | | | | | | | | | | | | | | (_| \__ \ <
|
|
||||||
|_| \___|\__,_|_| /_/ \_\__,_|_| |_| |_|_|_| |_| |_| |_|\__,_|___/_|\_\\
|
|
||||||
|
|
||||||
''')
|
|
||||||
|
|
||||||
|
|
||||||
app = create_app()
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
from flask_migrate import Migrate, MigrateCommand
|
|
||||||
from flask_script import Manager, Server
|
|
||||||
|
|
||||||
from main import app
|
|
||||||
from applications.models import db
|
|
||||||
|
|
||||||
manager = Manager(app)
|
|
||||||
Migrate(app=app, db=db)
|
|
||||||
manager.add_command('db', MigrateCommand) # 创建数据库映射命令
|
|
||||||
|
|
||||||
# import models不能省
|
|
||||||
from applications.models import *
|
|
||||||
|
|
||||||
manager.add_command('start', Server(port=8080, use_debugger=True)) # 创建启动命令
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
manager.run()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Generic single-database configuration.
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# A generic, single database configuration.
|
|
||||||
|
|
||||||
[alembic]
|
|
||||||
# template used to generate migration files
|
|
||||||
# file_template = %%(rev)s_%%(slug)s
|
|
||||||
|
|
||||||
# set to 'true' to run the environment during
|
|
||||||
# the 'revision' command, regardless of autogenerate
|
|
||||||
# revision_environment = false
|
|
||||||
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
[loggers]
|
|
||||||
keys = root,sqlalchemy,alembic
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
keys = console
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
keys = generic
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
level = WARN
|
|
||||||
handlers = console
|
|
||||||
qualname =
|
|
||||||
|
|
||||||
[logger_sqlalchemy]
|
|
||||||
level = WARN
|
|
||||||
handlers =
|
|
||||||
qualname = sqlalchemy.engine
|
|
||||||
|
|
||||||
[logger_alembic]
|
|
||||||
level = INFO
|
|
||||||
handlers =
|
|
||||||
qualname = alembic
|
|
||||||
|
|
||||||
[handler_console]
|
|
||||||
class = StreamHandler
|
|
||||||
args = (sys.stderr,)
|
|
||||||
level = NOTSET
|
|
||||||
formatter = generic
|
|
||||||
|
|
||||||
[formatter_generic]
|
|
||||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
||||||
datefmt = %H:%M:%S
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from logging.config import fileConfig
|
|
||||||
|
|
||||||
from sqlalchemy import engine_from_config
|
|
||||||
from sqlalchemy import pool
|
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
|
||||||
# access to the values within the .ini file in use.
|
|
||||||
config = context.config
|
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
|
||||||
# This line sets up loggers basically.
|
|
||||||
fileConfig(config.config_file_name)
|
|
||||||
logger = logging.getLogger('alembic.env')
|
|
||||||
|
|
||||||
# add your model's MetaData object here
|
|
||||||
# for 'autogenerate' support
|
|
||||||
# from myapp import mymodel
|
|
||||||
# target_metadata = mymodel.Base.metadata
|
|
||||||
config.set_main_option(
|
|
||||||
'sqlalchemy.url',
|
|
||||||
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
|
|
||||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
|
||||||
# can be acquired:
|
|
||||||
# my_important_option = config.get_main_option("my_important_option")
|
|
||||||
# ... etc.
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline():
|
|
||||||
"""Run migrations in 'offline' mode.
|
|
||||||
|
|
||||||
This configures the context with just a URL
|
|
||||||
and not an Engine, though an Engine is acceptable
|
|
||||||
here as well. By skipping the Engine creation
|
|
||||||
we don't even need a DBAPI to be available.
|
|
||||||
|
|
||||||
Calls to context.execute() here emit the given string to the
|
|
||||||
script output.
|
|
||||||
|
|
||||||
"""
|
|
||||||
url = config.get_main_option("sqlalchemy.url")
|
|
||||||
context.configure(
|
|
||||||
url=url, target_metadata=target_metadata, literal_binds=True
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_online():
|
|
||||||
"""Run migrations in 'online' mode.
|
|
||||||
|
|
||||||
In this scenario we need to create an Engine
|
|
||||||
and associate a connection with the context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# this callback is used to prevent an auto-migration from being generated
|
|
||||||
# when there are no changes to the schema
|
|
||||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
|
||||||
def process_revision_directives(context, revision, directives):
|
|
||||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
|
||||||
script = directives[0]
|
|
||||||
if script.upgrade_ops.is_empty():
|
|
||||||
directives[:] = []
|
|
||||||
logger.info('No changes in schema detected.')
|
|
||||||
|
|
||||||
connectable = engine_from_config(
|
|
||||||
config.get_section(config.config_ini_section),
|
|
||||||
prefix='sqlalchemy.',
|
|
||||||
poolclass=pool.NullPool,
|
|
||||||
)
|
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
|
||||||
context.configure(
|
|
||||||
connection=connection,
|
|
||||||
target_metadata=target_metadata,
|
|
||||||
process_revision_directives=process_revision_directives,
|
|
||||||
**current_app.extensions['migrate'].configure_args
|
|
||||||
)
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
if context.is_offline_mode():
|
|
||||||
run_migrations_offline()
|
|
||||||
else:
|
|
||||||
run_migrations_online()
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
"""${message}
|
|
||||||
|
|
||||||
Revision ID: ${up_revision}
|
|
||||||
Revises: ${down_revision | comma,n}
|
|
||||||
Create Date: ${create_date}
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
${imports if imports else ""}
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = ${repr(up_revision)}
|
|
||||||
down_revision = ${repr(down_revision)}
|
|
||||||
branch_labels = ${repr(branch_labels)}
|
|
||||||
depends_on = ${repr(depends_on)}
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
${upgrades if upgrades else "pass"}
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
${downgrades if downgrades else "pass"}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 58fc2c4c30a5
|
|
||||||
Revises: 715a8ecc5856
|
|
||||||
Create Date: 2021-03-19 21:31:06.761526
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '58fc2c4c30a5'
|
|
||||||
down_revision = '715a8ecc5856'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('admin_role', sa.Column('enable', sa.Integer(), nullable=True, comment='是否启用'))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('admin_role', 'enable')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 59add0790978
|
|
||||||
Revises:
|
|
||||||
Create Date: 2021-03-11 22:39:09.829159
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '59add0790978'
|
|
||||||
down_revision = None
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('admin_admin_log',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('method', sa.String(length=10), nullable=True),
|
|
||||||
sa.Column('uid', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('url', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('desc', sa.Text(), nullable=True),
|
|
||||||
sa.Column('ip', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('user_agent', sa.Text(), nullable=True),
|
|
||||||
sa.Column('create_time', sa.DateTime(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('admin_photo',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('name', sa.String(length=255), nullable=False),
|
|
||||||
sa.Column('href', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('mime', sa.CHAR(length=50), nullable=False),
|
|
||||||
sa.Column('size', sa.CHAR(length=30), nullable=False),
|
|
||||||
sa.Column('create_time', sa.DateTime(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('admin_power',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False, comment='权限编号'),
|
|
||||||
sa.Column('name', sa.String(length=255), nullable=True, comment='权限名称'),
|
|
||||||
sa.Column('type', sa.String(length=1), nullable=True, comment='权限类型'),
|
|
||||||
sa.Column('code', sa.String(length=30), nullable=True, comment='权限标识'),
|
|
||||||
sa.Column('url', sa.String(length=255), nullable=True, comment='权限路径'),
|
|
||||||
sa.Column('open_type', sa.String(length=10), nullable=True, comment='打开方式'),
|
|
||||||
sa.Column('parent_id', sa.String(length=19), nullable=True, comment='父类编号'),
|
|
||||||
sa.Column('icon', sa.String(length=128), nullable=True, comment='图标'),
|
|
||||||
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
|
|
||||||
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
|
|
||||||
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
|
|
||||||
sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('admin_role',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False, comment='角色ID'),
|
|
||||||
sa.Column('name', sa.String(length=255), nullable=True, comment='角色名称'),
|
|
||||||
sa.Column('code', sa.String(length=255), nullable=True, comment='角色标识'),
|
|
||||||
sa.Column('enable', sa.CHAR(length=1), nullable=True, comment='是否启用'),
|
|
||||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
|
||||||
sa.Column('details', sa.String(length=255), nullable=True, comment='详情'),
|
|
||||||
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
|
|
||||||
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
|
|
||||||
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('admin_user',
|
|
||||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='用户ID'),
|
|
||||||
sa.Column('username', sa.String(length=20), nullable=True, comment='用户名'),
|
|
||||||
sa.Column('password_hash', sa.String(length=128), nullable=True, comment='哈希密码'),
|
|
||||||
sa.Column('status', sa.Integer(), nullable=True, comment='启用'),
|
|
||||||
sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
|
||||||
sa.Column('update_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('admin_user_role',
|
|
||||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'),
|
|
||||||
sa.Column('user_id', sa.Integer(), nullable=True, comment='用户编号'),
|
|
||||||
sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'),
|
|
||||||
sa.ForeignKeyConstraint(['role_id'], ['admin_role.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['admin_user.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('admin_user_role')
|
|
||||||
op.drop_table('admin_user')
|
|
||||||
op.drop_table('admin_role')
|
|
||||||
op.drop_table('admin_power')
|
|
||||||
op.drop_table('admin_photo')
|
|
||||||
op.drop_table('admin_admin_log')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 64de3323bc01
|
|
||||||
Revises: 95b59a101b26
|
|
||||||
Create Date: 2021-03-18 20:54:17.724297
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '64de3323bc01'
|
|
||||||
down_revision = '95b59a101b26'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('admin_user', sa.Column('realname', sa.String(length=20), nullable=True, comment='真实名字'))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('admin_user', 'realname')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 715a8ecc5856
|
|
||||||
Revises: 64de3323bc01
|
|
||||||
Create Date: 2021-03-19 21:30:52.200812
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import mysql
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '715a8ecc5856'
|
|
||||||
down_revision = '64de3323bc01'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('admin_role', 'enable')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('admin_role', sa.Column('enable', mysql.CHAR(collation='utf8_unicode_ci', length=1), nullable=True, comment='是否启用'))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 816f66c2def4
|
|
||||||
Revises: ce8825111f8f
|
|
||||||
Create Date: 2021-03-14 22:57:37.092157
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '816f66c2def4'
|
|
||||||
down_revision = 'ce8825111f8f'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('admin_admin_log', sa.Column('success', sa.Integer(), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('admin_admin_log', 'success')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 95b59a101b26
|
|
||||||
Revises: 816f66c2def4
|
|
||||||
Create Date: 2021-03-17 22:28:41.595865
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import mysql
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '95b59a101b26'
|
|
||||||
down_revision = '816f66c2def4'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('admin_user', sa.Column('enable', sa.Integer(), nullable=True, comment='启用'))
|
|
||||||
op.drop_column('admin_user', 'status')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('admin_user', sa.Column('status', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True, comment='启用'))
|
|
||||||
op.drop_column('admin_user', 'enable')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: ce8825111f8f
|
|
||||||
Revises: 59add0790978
|
|
||||||
Create Date: 2021-03-11 22:41:25.156430
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'ce8825111f8f'
|
|
||||||
down_revision = '59add0790978'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('admin_role_power',
|
|
||||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'),
|
|
||||||
sa.Column('power_id', sa.Integer(), nullable=True, comment='用户编号'),
|
|
||||||
sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'),
|
|
||||||
sa.ForeignKeyConstraint(['power_id'], ['admin_power.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['role_id'], ['admin_role.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('admin_role_power')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from .file import PhotoModel
|
||||||
|
from .rights import RightModel, RoleModel
|
||||||
|
from .system import LogModel
|
||||||
|
from .users import UserModel, DepartmentModel
|
||||||
@@ -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='更新时间')
|
||||||
@@ -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'))
|
||||||
@@ -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='更新时间')
|
||||||
@@ -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='创建时间')
|
||||||
@@ -1,29 +1,28 @@
|
|||||||
alembic==1.5.4
|
alembic==1.6.5
|
||||||
blinker==1.4
|
aniso8601==9.0.1
|
||||||
captcha==0.3
|
captcha==0.3
|
||||||
click==7.1.2
|
click==8.0.1
|
||||||
Flask==1.1.2
|
colorama==0.4.4
|
||||||
Flask-DebugToolbar==0.11.0
|
Flask==2.0.1
|
||||||
Flask-Login==0.5.0
|
Flask-Login==0.5.0
|
||||||
flask-marshmallow==0.14.0
|
Flask-Migrate==3.0.1
|
||||||
Flask-Migrate==2.6.0
|
Flask-Pydantic==0.9.0
|
||||||
Flask-Script==2.0.6
|
Flask-SQLAlchemy==2.5.1
|
||||||
Flask-SQLAlchemy==2.4.4
|
greenlet==1.1.0
|
||||||
-e git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc93e684fa0a3a4350a38e41ae00483a796#egg=Flask_Uploads
|
gunicorn==20.1.0
|
||||||
itsdangerous==1.1.0
|
itsdangerous==2.0.1
|
||||||
Jinja2==2.11.3
|
Jinja2==3.0.1
|
||||||
Mako==1.1.4
|
Mako==1.1.4
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==2.0.1
|
||||||
marshmallow==3.10.0
|
Pillow==8.2.0
|
||||||
marshmallow-sqlalchemy==0.24.2
|
|
||||||
Pillow==8.1.0
|
|
||||||
psutil==5.8.0
|
psutil==5.8.0
|
||||||
|
pydantic==1.9.1
|
||||||
PyMySQL==1.0.2
|
PyMySQL==1.0.2
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
python-dotenv==0.15.0
|
python-dotenv==0.18.0
|
||||||
python-editor==1.0.4
|
python-editor==1.0.4
|
||||||
PyYAML==5.4.1
|
pytz==2021.1
|
||||||
six==1.15.0
|
six==1.16.0
|
||||||
SQLAlchemy==1.3.23
|
SQLAlchemy==1.4.18
|
||||||
sqlparse==0.4.1
|
typing_extensions==4.2.0
|
||||||
Werkzeug==1.0.1
|
Werkzeug==2.0.1
|
||||||
|
|||||||
@@ -35,17 +35,20 @@ body,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pear-admin .layui-layout-control {
|
.pear-admin .layui-layout-control {
|
||||||
left: 120px;
|
left: 140px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pear-admin .layui-layout-control .layui-nav{
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.pear-admin .layui-logo {
|
.pear-admin .layui-logo {
|
||||||
width: 230px;
|
width: 230px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
line-height: 60px;
|
line-height: 60px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #28333E;
|
background-color: #28333E;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pear-admin .layui-logo img {
|
.pear-admin .layui-logo img {
|
||||||
@@ -93,11 +96,11 @@ body,
|
|||||||
.pear-admin .layui-body {
|
.pear-admin .layui-body {
|
||||||
left: 230px;
|
left: 230px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pear-admin .layui-layout-left {
|
.pear-admin .layui-layout-left {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 隐 藏 布 局 样 式 */
|
/** 隐 藏 布 局 样 式 */
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: whitesmoke;
|
background-color: whitesmoke;
|
||||||
z-index: 9999999;
|
z-index: 9999999;
|
||||||
margin-top: -61px;
|
|
||||||
}
|
}
|
||||||
.loader {
|
.loader {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
|
|||||||
@@ -37,7 +37,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layui-input:focus {
|
.layui-input:focus {
|
||||||
box-shadow: 0px 0px 3px 1px #5FB878 !important;
|
box-shadow: 0px 0px 2px 1px #5FB878 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-form-danger:focus{
|
||||||
|
box-shadow: 0px 0px 2px 1px #f56c6c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -45,7 +45,8 @@
|
|||||||
"title": "基础组件",
|
"title": "基础组件",
|
||||||
"icon": "layui-icon layui-icon-console",
|
"icon": "layui-icon layui-icon-console",
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"children": [{
|
"children": [
|
||||||
|
{
|
||||||
"id": 2011,
|
"id": 2011,
|
||||||
"title": "功能按钮",
|
"title": "功能按钮",
|
||||||
"icon": "layui-icon layui-icon-face-smile",
|
"icon": "layui-icon layui-icon-face-smile",
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
[{
|
|
||||||
"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"
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
|
"password": "$2a$10$1K7E1.IYCrsoZVCb6utOo.5jENtfOzhdKWhc49t2lk.UQd7Oam4FG",
|
||||||
"salt": null,
|
"salt": null,
|
||||||
"status": null,
|
"status": null,
|
||||||
"realName": "超级管理员",
|
"realName": "超级'管'理员",
|
||||||
"email": "8540854@qq.com",
|
"email": "854085'4@'qq.com",
|
||||||
"avatar": null,
|
"avatar": null,
|
||||||
"sex": "1",
|
"sex": "1",
|
||||||
"phone": "15543526531",
|
"phone": "15543526531",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"password": null,
|
"password": null,
|
||||||
"salt": null,
|
"salt": null,
|
||||||
"status": null,
|
"status": null,
|
||||||
"realName": "就眠仪式",
|
"realName": "就\"眠\"仪式",
|
||||||
"email": null,
|
"email": null,
|
||||||
"avatar": null,
|
"avatar": null,
|
||||||
"sex": "1",
|
"sex": "1",
|
||||||
@@ -129,4 +129,4 @@
|
|||||||
"login": "1",
|
"login": "1",
|
||||||
"roleIds": null
|
"roleIds": null
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 74 KiB |