Compare commits
136
Commits
1.0.0.RELEASE
...
1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c7517be4a | ||
|
|
9b8d632995 | ||
|
|
9bfdac0953 | ||
|
|
2e6067fd31 | ||
|
|
a3c61d3291 | ||
|
|
ef8044ea22 | ||
|
|
56dcc7eff8 | ||
|
|
4eeb2cfec9 | ||
|
|
28d503eb8b | ||
|
|
0bec251f08 | ||
|
|
027481ce56 | ||
|
|
dd6cbf2694 | ||
|
|
7d179aae2e | ||
|
|
b5fbd9867b | ||
|
|
2acc3cce67 | ||
|
|
0cd0c414ee | ||
|
|
e51c86c83f | ||
|
|
71a25f4774 | ||
|
|
56563657e8 | ||
|
|
6bfb10e4a4 | ||
|
|
70cb8eb35b | ||
|
|
cdea48c7d4 | ||
|
|
fba22bbcbf | ||
|
|
14f96abdb3 | ||
|
|
28ff95bf0d | ||
|
|
6cee5a6104 | ||
|
|
3add95e910 | ||
|
|
1db1064c11 | ||
|
|
f75928119e | ||
|
|
9051e8138c | ||
|
|
8396f49105 | ||
|
|
c664c70bbc | ||
|
|
7a503cd94a | ||
|
|
7a9aeec8db | ||
|
|
2c57bc36ea | ||
|
|
c0fc70425b | ||
|
|
f95944a781 | ||
|
|
589e0c8ce7 | ||
|
|
6e0ff64893 | ||
|
|
044e432eb7 | ||
|
|
5887079ade | ||
|
|
724e0817a5 | ||
|
|
9caa7a22e5 | ||
|
|
c2042d5f80 | ||
|
|
51f17c9539 | ||
|
|
27ed99ee27 | ||
|
|
7eb41e9754 | ||
|
|
c7f266a482 | ||
|
|
f2d444b877 | ||
|
|
9e59035300 | ||
|
|
b0ed2100a2 | ||
|
|
6f0c86fba6 | ||
|
|
c71562a12f | ||
|
|
a7fa415545 | ||
|
|
24d2523385 | ||
|
|
d30a3d79de | ||
|
|
860d5778cc | ||
|
|
23dbe9ee9c | ||
|
|
9114964ef2 | ||
|
|
56615528b9 | ||
|
|
1b5c54daf9 | ||
|
|
c0a3f5243b | ||
|
|
d22eb22f74 | ||
|
|
7eeb4c2489 | ||
|
|
d691c444ae | ||
|
|
83c23ea276 | ||
|
|
cff67d152f | ||
|
|
ad67c289d3 | ||
|
|
4a4d8f763d | ||
|
|
dfc734c13a | ||
|
|
654f441d05 | ||
|
|
48eebc1c95 | ||
|
|
3c1764e58d | ||
|
|
233241c962 | ||
|
|
30f9774ab1 | ||
|
|
a255c746d4 | ||
|
|
eac2a1c941 | ||
|
|
110a5faaff | ||
|
|
f3cfdcf580 | ||
|
|
34a1066cd0 | ||
|
|
67d05c39f3 | ||
|
|
fa49130c4f | ||
|
|
5b76973d9c | ||
|
|
0d57a2a8f2 | ||
|
|
78b7dae880 | ||
|
|
04a89144ba | ||
|
|
3b3eecc1d1 | ||
|
|
ca92d949dd | ||
|
|
663c7925e3 | ||
|
|
76b2ad6f58 | ||
|
|
fd8dcfee40 | ||
|
|
0a60c1960d | ||
|
|
28021df2ab | ||
|
|
6d42b6d56c | ||
|
|
3b467f42fe | ||
|
|
e37749c669 | ||
|
|
2cbbf25060 | ||
|
|
986ae4e4bc | ||
|
|
103aaf4911 | ||
|
|
7038ce64a6 | ||
|
|
9b8525ca1e | ||
|
|
254ce53387 | ||
|
|
0ff0c456b0 | ||
|
|
57a54090bb | ||
|
|
e3d3424ee1 | ||
|
|
99b2f611ec | ||
|
|
a675d78b82 | ||
|
|
62bd10cc60 | ||
|
|
9e8a5357be | ||
|
|
5faf88283f | ||
|
|
14a32e6d3c | ||
|
|
5e08f3e49b | ||
|
|
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,14 @@
|
||||
FLASK_APP=main.py
|
||||
# flask配置
|
||||
FLASK_APP="entrance:create_app()"
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=1
|
||||
FLASK_DEBUG=1
|
||||
FLASK_RUN_HOST = 127.0.0.1
|
||||
FLASK_RUN_PORT = 5000
|
||||
|
||||
# MySql配置信息
|
||||
MYSQL_HOST=127.0.0.1
|
||||
# MYSQL_HOST=dbserver
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DATABASE=next
|
||||
MYSQL_USERNAME=root
|
||||
MYSQL_PASSWORD=root
|
||||
@@ -1,7 +1,6 @@
|
||||
<div align="center">
|
||||
<br/>
|
||||
<br/>
|
||||
<img src="https://gitee.com/pear-admin/Pear-Admin-Layui/raw/master/admin/images/logo.png" width="90px" style="margin-top:30px;"/>
|
||||
<h1 align="center">
|
||||
Pear Admin Flask
|
||||
</h1>
|
||||
@@ -30,183 +29,28 @@
|
||||
</div>
|
||||
|
||||
#### 项目简介
|
||||
>Pear Admin Flask 基于 Flask 的后台管理系统,拥抱应用广泛的python语言,通过使用本系统,即可快速构建你的功能业务
|
||||
>
|
||||
>项目旨在为python开发者提供一个后台管理系统的模板,成为您构建信息管理系统,物联网后台....等等应用时灵活,简单的工具
|
||||
>
|
||||
>各位Python爱好者多多指教
|
||||
>Pear Admin Flask 新版本开发中
|
||||
|
||||
Pear Admin Flask 分为 Common / Simple 两个版本:
|
||||
#### 安装
|
||||
|
||||
[** Common 通用版本 **](https://gitee.com/pear-admin/pear-admin-flask/tree/master/)
|
||||
|
||||
[** Simple 简洁版本 **](https://gitee.com/pear-admin/pear-admin-flask/tree/simple/)
|
||||
|
||||
#### 内置功能
|
||||
|
||||
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
- [x] 权限管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
- [x] 角色管理:角色菜单权限分配。
|
||||
- [x] 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
- [x] 登录日志:系统登录日志记录查询包含登录异常。
|
||||
- [x] 服务监控:监视当前系统CPU、内存、磁盘、python版本,运行时长等相关信息。
|
||||
- [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 #前台模板
|
||||
```shell
|
||||
pip install -r require/common.txt
|
||||
```
|
||||
|
||||
#### 目录结构
|
||||
|
||||
pear-admin-flask
|
||||
|
||||
#### 项目安装
|
||||
common -> 公共
|
||||
|
||||
```bash
|
||||
# 下 载
|
||||
git clone https://gitee.com/pear-admin/pear-admin-flask
|
||||
entrance -> 核心
|
||||
|
||||
# 安 装
|
||||
pip install -r requirement.txt
|
||||
logs -> 日志
|
||||
|
||||
# 配 置
|
||||
applications\config\database.py
|
||||
modules -> 蓝图,视图,模型,服务
|
||||
|
||||
```
|
||||
require -> 安装需要的文件
|
||||
|
||||
#### 修改配置
|
||||
.flaskenv -> 配置文件
|
||||
|
||||
```python
|
||||
# 主 机
|
||||
HOST = '127.0.0.1'
|
||||
|
||||
# 端 口
|
||||
PORT = '3306'
|
||||
|
||||
# 数 据 库
|
||||
DATABASE = 'PearAdminFlask'
|
||||
|
||||
# 账 户
|
||||
USERNAME = 'root'
|
||||
|
||||
# 密 码
|
||||
PASSWORD = 'root'
|
||||
|
||||
```
|
||||
|
||||
#### Venv 安装
|
||||
|
||||
```bash
|
||||
python -m venv venv
|
||||
|
||||
```
|
||||
|
||||
#### 运行项目
|
||||
|
||||
```bash
|
||||
# 进 入 目 录
|
||||
cd dev
|
||||
|
||||
# 初 始 化 数 据 库
|
||||
python initDb.py
|
||||
|
||||
# 如 果 报 模 块 路 径 错 误
|
||||
python dev/initDb.py
|
||||
|
||||
```
|
||||
|
||||
执行 flask run 命令启动项目
|
||||
|
||||
|
||||
#### 预览项目
|
||||
|
||||
| | |
|
||||
|---------------------|---------------------|
|
||||
|  |  |
|
||||
| |  |
|
||||
|  |  |
|
||||
app.py -> 启动模块(使用flask run命令的话可以删除)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
from entrance import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
@@ -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)
|
||||
@@ -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,15 +0,0 @@
|
||||
from flask_login import LoginManager
|
||||
|
||||
|
||||
def init_flask_login(app):
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
|
||||
login_manager.login_view = 'adminIndex.login'
|
||||
login_manager.login_message = u'请登录以访问此页面'
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
from applications.models.admin import User
|
||||
user = User.query.get(int(user_id))
|
||||
return user
|
||||
@@ -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)
|
||||
@@ -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,16 +0,0 @@
|
||||
from flask import render_template
|
||||
|
||||
|
||||
def init_error(app):
|
||||
|
||||
@app.errorhandler(403)
|
||||
def page_not_found(e):
|
||||
return render_template('errors/403.html'), 403
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template('errors/404.html'), 404
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
return render_template('errors/500.html'), 500
|
||||
@@ -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,19 @@
|
||||
from flask import current_app
|
||||
from cachelib.file import FileSystemCache
|
||||
from cachelib.memcached import MemcachedCache
|
||||
from cachelib.redis import RedisCache
|
||||
from cachelib.simple import SimpleCache
|
||||
from cachelib.uwsgi import UWSGICache
|
||||
|
||||
cacher: dict = {
|
||||
'filesystem': FileSystemCache,
|
||||
'memcached': MemcachedCache,
|
||||
'redis': RedisCache,
|
||||
'simple': SimpleCache,
|
||||
'uwsgi': UWSGICache
|
||||
}
|
||||
cache = RedisCache()
|
||||
|
||||
|
||||
def get_cache():
|
||||
return cache.get(current_app.config.get("CACHE_TYPE") or "filesystem")()
|
||||
@@ -0,0 +1,18 @@
|
||||
from celery import Celery
|
||||
|
||||
from entrance.config import celery as config
|
||||
from entrance.config.celery import broker_url
|
||||
|
||||
celery = Celery(__name__, broker=broker_url)
|
||||
|
||||
|
||||
def register_celery(app):
|
||||
celery.config_from_object(config)
|
||||
|
||||
class ContextTask(celery.Task):
|
||||
def __call__(self, *args, **kwargs):
|
||||
with app.app_context():
|
||||
return self.run(*args, **kwargs)
|
||||
|
||||
celery.Task = ContextTask
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
|
||||
limiter = Limiter(
|
||||
key_func=get_remote_address,
|
||||
default_limits=["200 per day", "50 per hour"]
|
||||
)
|
||||
@@ -0,0 +1,55 @@
|
||||
from logging.config import dictConfig
|
||||
|
||||
|
||||
def config_logger():
|
||||
dictConfig(
|
||||
{
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"simple": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"}
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "simple",
|
||||
"stream": "ext://sys.stdout",
|
||||
},
|
||||
"info_file_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "INFO",
|
||||
"formatter": "simple",
|
||||
"filename": "logs/info.log",
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 50,
|
||||
"encoding": "utf8",
|
||||
},
|
||||
"error_file_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "ERROR",
|
||||
"formatter": "simple",
|
||||
"filename": "logs/errors.log",
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 20,
|
||||
"encoding": "utf8",
|
||||
},
|
||||
"debug_file_handler": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "simple",
|
||||
"filename": "logs/debug.log",
|
||||
"maxBytes": 10485760,
|
||||
"backupCount": 50,
|
||||
"encoding": "utf8",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"my_module": {"level": "ERROR", "handlers": ["console"], "propagate": "no"}
|
||||
},
|
||||
"root": {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["error_file_handler", "debug_file_handler"],
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
from flask_mail import Mail
|
||||
|
||||
mail = Mail()
|
||||
@@ -0,0 +1,7 @@
|
||||
from flask_migrate import Migrate
|
||||
|
||||
from common.extend.orm import db
|
||||
from entrance.models import model_lists
|
||||
|
||||
migrate = Migrate(db=db, directory='entrance/migrations')
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import datetime
|
||||
from typing import Tuple
|
||||
|
||||
from flask import request
|
||||
from flask_sqlalchemy import SQLAlchemy, BaseQuery
|
||||
|
||||
|
||||
class Query(BaseQuery):
|
||||
|
||||
def all_json(self) -> list:
|
||||
return [dict(i) for i in self.all()]
|
||||
|
||||
def soft_delete(self):
|
||||
return self.update({"delete_at": datetime.datetime.now()})
|
||||
|
||||
def logic_all(self):
|
||||
return self.filter_by(delete_at=None).all()
|
||||
|
||||
def layui_paginate(self):
|
||||
return self.paginate(page=request.args.get('page', type=int),
|
||||
per_page=request.args.get('limit', type=int),
|
||||
error_out=False)
|
||||
|
||||
def layui_paginate_json(self) -> Tuple[list, int]:
|
||||
p = self.paginate(page=request.args.get('page', type=int),
|
||||
per_page=request.args.get('limit', type=int),
|
||||
error_out=False)
|
||||
|
||||
return [dict(i) for i in p.items], p.total
|
||||
|
||||
|
||||
db = SQLAlchemy(query_class=Query)
|
||||
@@ -0,0 +1,89 @@
|
||||
import datetime
|
||||
|
||||
from entrance.extensions import db
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import backref
|
||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
||||
|
||||
|
||||
class BaseModel(db.Model):
|
||||
__abstract__ = True
|
||||
|
||||
# insert and update
|
||||
def save(self):
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
||||
# delete
|
||||
def delete(self):
|
||||
db.session.delete(self)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class CommonModel(BaseModel):
|
||||
__abstract__ = True
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='用户ID')
|
||||
create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = db.Column(db.DateTime, onupdate=datetime.datetime.now, comment='更新时间')
|
||||
|
||||
|
||||
class CurdModel(BaseModel):
|
||||
__abstract__ = True
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='用户ID')
|
||||
create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = db.Column(db.DateTime, onupdate=datetime.datetime.now, comment='更新时间')
|
||||
delete_at = db.Column(db.DateTime, comment='删除时间')
|
||||
|
||||
|
||||
Column = db.Column
|
||||
Table = db.Table
|
||||
relationship = relationship
|
||||
ForeignKey = ForeignKey
|
||||
backref = backref
|
||||
|
||||
TypeDecorator = db.TypeDecorator
|
||||
INT = db.INT
|
||||
CHAR = db.CHAR
|
||||
VARCHAR = db.VARCHAR
|
||||
NCHAR = db.NCHAR
|
||||
NVARCHAR = db.NVARCHAR
|
||||
TEXT = db.TEXT
|
||||
Text = db.Text
|
||||
FLOAT = db.FLOAT
|
||||
NUMERIC = db.NUMERIC
|
||||
REAL = db.REAL
|
||||
DECIMAL = db.DECIMAL
|
||||
TIMESTAMP = db.TIMESTAMP
|
||||
DATETIME = db.DATETIME
|
||||
CLOB = db.CLOB
|
||||
BLOB = db.BLOB
|
||||
BINARY = db.BINARY
|
||||
VARBINARY = db.VARBINARY
|
||||
BOOLEAN = db.BOOLEAN
|
||||
BIGINT = db.BIGINT
|
||||
SMALLINT = db.SMALLINT
|
||||
INTEGER = db.INTEGER
|
||||
DATE = db.DATE
|
||||
TIME = db.TIME
|
||||
TupleType = db.TupleType
|
||||
String = db.String
|
||||
Integer = db.Integer
|
||||
SmallInteger = db.SmallInteger
|
||||
BigInteger = db.BigInteger
|
||||
Numeric = db.Numeric
|
||||
Float = db.Float
|
||||
DateTime = db.DateTime
|
||||
Date = db.Date
|
||||
Time = db.Time
|
||||
LargeBinary = db.LargeBinary
|
||||
Boolean = db.Boolean
|
||||
Unicode = db.Unicode
|
||||
UnicodeText = db.UnicodeText
|
||||
PickleType = db.PickleType
|
||||
Interval = db.Interval
|
||||
Enum = db.Enum
|
||||
ARRAY = db.ARRAY
|
||||
JSON = db.JSON
|
||||
|
||||
SQLAlchemyAutoSchema = SQLAlchemyAutoSchema
|
||||
@@ -0,0 +1,121 @@
|
||||
import random, math
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
||||
|
||||
|
||||
class vieCode:
|
||||
__fontSize = 20 # 字体大小
|
||||
__width = 120 # 画布宽度
|
||||
__heigth = 45 # 画布高度
|
||||
__length = 4 # 验证码长度
|
||||
__draw = None # 画布
|
||||
__img = None # 图片资源
|
||||
__code = None # 验证码字符
|
||||
__str = None # 自定义验证码字符集
|
||||
__inCurve = True # 是否画干扰线
|
||||
__inNoise = True # 是否画干扰点
|
||||
__type = 2 # 验证码类型 1、纯字母 2、数字字母混合
|
||||
__fontPatn = 'applications/common/utils/fonts/2.ttf' # 字体
|
||||
|
||||
def GetCodeImage(self, size=80, length=4):
|
||||
'''获取验证码图片
|
||||
@param int size 验证码大小
|
||||
@param int length 验证码长度
|
||||
'''
|
||||
# 准备基础数据
|
||||
self.__length = length
|
||||
self.__fontSize = size
|
||||
self.__width = self.__fontSize * self.__length
|
||||
self.__heigth = int(self.__fontSize * 1.5)
|
||||
|
||||
# 生成验证码图片
|
||||
self.__createCode()
|
||||
self.__createImage()
|
||||
self.__createNoise()
|
||||
self.__printString()
|
||||
self.__cerateFilter()
|
||||
|
||||
return self.__img, self.__code
|
||||
|
||||
def __cerateFilter(self):
|
||||
'''模糊处理'''
|
||||
self.__img = self.__img.filter(ImageFilter.BLUR)
|
||||
filter = ImageFilter.ModeFilter(8)
|
||||
self.__img = self.__img.filter(filter)
|
||||
|
||||
def __createCode(self):
|
||||
'''创建验证码字符'''
|
||||
# 是否自定义字符集合
|
||||
if not self.__str:
|
||||
# 源文本
|
||||
number = "3456789"
|
||||
srcLetter = "qwertyuipasdfghjkzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
|
||||
srcUpper = srcLetter.upper()
|
||||
if self.__type == 1:
|
||||
self.__str = number
|
||||
else:
|
||||
self.__str = srcLetter + srcUpper + number
|
||||
|
||||
# 构造验证码
|
||||
self.__code = random.sample(self.__str, self.__length)
|
||||
|
||||
def __createImage(self):
|
||||
'''创建画布'''
|
||||
bgColor = (random.randint(200, 255), random.randint(200, 255), random.randint(200, 255))
|
||||
self.__img = Image.new('RGB', (self.__width, self.__heigth), bgColor)
|
||||
self.__draw = ImageDraw.Draw(self.__img)
|
||||
|
||||
def __createNoise(self):
|
||||
'''画干扰点'''
|
||||
if not self.__inNoise:
|
||||
return
|
||||
font = ImageFont.truetype(self.__fontPatn, int(self.__fontSize / 1.5))
|
||||
for i in range(5):
|
||||
# 杂点颜色
|
||||
noiseColor = (random.randint(150, 200), random.randint(150, 200), random.randint(150, 200))
|
||||
putStr = random.sample(self.__str, 2)
|
||||
for j in range(2):
|
||||
# 绘杂点
|
||||
size = (random.randint(-10, self.__width), random.randint(-10, self.__heigth))
|
||||
self.__draw.text(size, putStr[j], font=font, fill=noiseColor)
|
||||
pass
|
||||
|
||||
def __createCurve(self):
|
||||
'''画干扰线'''
|
||||
if not self.__inCurve:
|
||||
return
|
||||
x = y = 0
|
||||
|
||||
# 计算曲线系数
|
||||
a = random.uniform(1, self.__heigth / 2)
|
||||
b = random.uniform(-self.__width / 4, self.__heigth / 4)
|
||||
f = random.uniform(-self.__heigth / 4, self.__heigth / 4)
|
||||
t = random.uniform(self.__heigth, self.__width * 2)
|
||||
xend = random.randint(self.__width / 2, self.__width * 2)
|
||||
w = (2 * math.pi) / t
|
||||
|
||||
# 画曲线
|
||||
color = (random.randint(30, 150), random.randint(30, 150), random.randint(30, 150))
|
||||
for x in range(xend):
|
||||
if w != 0:
|
||||
for k in range(int(self.__heigth / 10)):
|
||||
y = a * math.sin(w * x + f) + b + self.__heigth / 2
|
||||
i = int(self.__fontSize / 5)
|
||||
while i > 0:
|
||||
px = x + i
|
||||
py = y + i + k
|
||||
self.__draw.point((px, py), color)
|
||||
i -= i
|
||||
|
||||
def __printString(self):
|
||||
'''打印验证码字符串'''
|
||||
font = ImageFont.truetype(self.__fontPatn, self.__fontSize)
|
||||
x = 0
|
||||
# 打印字符到画板
|
||||
for i in range(self.__length):
|
||||
# 设置字体随机颜色
|
||||
color = (random.randint(30, 150), random.randint(30, 150), random.randint(30, 150))
|
||||
# 计算座标
|
||||
x = random.uniform(self.__fontSize * i * 0.95, self.__fontSize * i * 1.1)
|
||||
y = self.__fontSize * random.uniform(0.3, 0.5)
|
||||
# 打印字符
|
||||
self.__draw.text((x, y), self.__code[i], font=font, fill=color)
|
||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
from flask_mail import Message
|
||||
|
||||
# send_mail(subject='title', recipients=['123@qq.com'], content='body')
|
||||
from common.extend.mail import mail
|
||||
|
||||
|
||||
def send_mail(subject, recipients, content):
|
||||
try:
|
||||
message = Message(subject=subject, recipients=recipients, body=content)
|
||||
mail.send(message)
|
||||
except Exception as e:
|
||||
print('邮箱发送出错了')
|
||||
raise
|
||||
@@ -0,0 +1,14 @@
|
||||
from file_system_uploader import FilesystemUploader
|
||||
from qiniu_uploader import QiniuUploader
|
||||
from upyun_uploader import UpyunUploader
|
||||
from flask import current_app
|
||||
|
||||
uploader: dict = {
|
||||
"filesystem": FilesystemUploader,
|
||||
"qiniu": QiniuUploader,
|
||||
'upyun': UpyunUploader
|
||||
}
|
||||
|
||||
|
||||
def get_uploader():
|
||||
return uploader.get(current_app.config.get("UPLOAD_PROVIDER") or "filesystem")()
|
||||
@@ -0,0 +1,19 @@
|
||||
class BaseUploader(object):
|
||||
def __init__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def store(self, fileobj, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def upload(self, file_obj, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def download(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def sync(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import os
|
||||
import posixpath
|
||||
from pathlib import PurePath
|
||||
from shutil import copyfileobj, rmtree
|
||||
|
||||
from PIL.features import codecs
|
||||
from flask import current_app, send_file
|
||||
from werkzeug.utils import safe_join, secure_filename
|
||||
|
||||
from common.utils.uploads.base_uploader import BaseUploader
|
||||
|
||||
|
||||
def hexencode(s):
|
||||
if isinstance(s, (str,)):
|
||||
s = s.encode("utf-8")
|
||||
encoded = codecs.encode(s, "hex")
|
||||
try:
|
||||
encoded = encoded.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return encoded
|
||||
|
||||
|
||||
class FilesystemUploader(BaseUploader):
|
||||
def __init__(self, base_path=None):
|
||||
super(BaseUploader, self).__init__()
|
||||
self.base_path = base_path or current_app.config.get("UPLOAD_FOLDER")
|
||||
|
||||
def store(self, fileobj, filename):
|
||||
location = os.path.join(self.base_path, filename)
|
||||
directory = os.path.dirname(location)
|
||||
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
with open(location, "wb") as dst:
|
||||
copyfileobj(fileobj, dst, 16384)
|
||||
|
||||
return filename
|
||||
|
||||
def upload(self, file_obj, filename):
|
||||
if len(filename) == 0:
|
||||
raise Exception("Empty filenames cannot be used")
|
||||
|
||||
filename = secure_filename(filename)
|
||||
md5hash = hexencode(os.urandom(16))
|
||||
file_path = posixpath.join(md5hash, filename)
|
||||
|
||||
return self.store(file_obj, file_path)
|
||||
|
||||
def download(self, filename):
|
||||
return send_file(safe_join(self.base_path, filename), as_attachment=True)
|
||||
|
||||
def delete(self, filename):
|
||||
if os.path.exists(os.path.join(self.base_path, filename)):
|
||||
file_path = PurePath(filename).parts[0]
|
||||
rmtree(os.path.join(self.base_path, file_path))
|
||||
return True
|
||||
return False
|
||||
|
||||
def sync(self):
|
||||
pass
|
||||
@@ -0,0 +1,38 @@
|
||||
from qiniu import Auth, put_data
|
||||
from base_uploader import BaseUploader
|
||||
|
||||
access_key = '自己账号的access key'
|
||||
|
||||
secret_key = '自己账号的secret key'
|
||||
|
||||
bucket_name = '空间名'
|
||||
|
||||
|
||||
class QiniuUploader(BaseUploader):
|
||||
|
||||
def __init__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def store(self, fileobj, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def upload(self, file_obj, filename):
|
||||
|
||||
token = Auth(access_key=access_key, secret_key=secret_key).upload_token(bucket=bucket_name)
|
||||
ret, res = put_data(token, None, data=file_obj)
|
||||
ret.get('key')
|
||||
print(ret)
|
||||
print(res)
|
||||
|
||||
if res.status_code != 200:
|
||||
raise Exception("upload failed")
|
||||
return ret, res
|
||||
|
||||
def download(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def sync(self):
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,22 @@
|
||||
from base_uploader import BaseUploader
|
||||
import upyun
|
||||
|
||||
|
||||
class UpyunUploader(BaseUploader):
|
||||
def __init__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def store(self, fileobj, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def upload(self, file_obj, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def download(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def sync(self):
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,67 @@
|
||||
from flask import Blueprint as FlaskBlueprint
|
||||
from flask.views import MethodViewType, MethodView
|
||||
|
||||
|
||||
class Blueprint(FlaskBlueprint):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._endpoints = []
|
||||
|
||||
def add_url_rule(
|
||||
self,
|
||||
rule,
|
||||
endpoint=None,
|
||||
view_func=None,
|
||||
provide_automatic_options=None,
|
||||
*,
|
||||
parameters=None,
|
||||
tags=None,
|
||||
**options,
|
||||
):
|
||||
|
||||
if view_func is None:
|
||||
raise TypeError("view_func must be provided")
|
||||
|
||||
if endpoint is None:
|
||||
endpoint = view_func.__name__
|
||||
|
||||
# Ensure endpoint name is unique
|
||||
# - to avoid a name clash when registering a MethodView
|
||||
# - to use it as a key internally in endpoint -> doc mapping
|
||||
if endpoint in self._endpoints:
|
||||
endpoint = f"{endpoint}_{len(self._endpoints)}"
|
||||
self._endpoints.append(endpoint)
|
||||
|
||||
if isinstance(view_func, MethodViewType):
|
||||
func = view_func.as_view(endpoint)
|
||||
else:
|
||||
func = view_func
|
||||
|
||||
# Add URL rule in Flask and store endpoint documentation
|
||||
super().add_url_rule(rule, endpoint, func, **options)
|
||||
|
||||
def route(self, rule, *, parameters=None, tags=None, **options):
|
||||
"""Decorator to register view function in application and documentation
|
||||
|
||||
Calls :meth:`add_url_rule <Blueprint.add_url_rule>`.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
endpoint = options.pop("endpoint", None)
|
||||
self.add_url_rule(
|
||||
rule, endpoint, func, parameters=parameters, tags=tags, **options
|
||||
)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def register_child_bp(self, bp_lists):
|
||||
for bp in bp_lists:
|
||||
self.register_blueprint(bp)
|
||||
|
||||
|
||||
class View(MethodView):
|
||||
pass
|
||||
@@ -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()
|
||||
-1144
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
from flask import Flask
|
||||
import os
|
||||
from common.extend.logger import config_logger
|
||||
from entrance.bps import sys, register_parent_bp
|
||||
from entrance.extensions import init_extensions
|
||||
|
||||
|
||||
def create_app():
|
||||
config_logger()
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile('config/config.py')
|
||||
register_parent_bp(app)
|
||||
init_extensions(app)
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||
print(
|
||||
"""
|
||||
|
||||
___ _ _ _ ___ _ _
|
||||
| _ \___ __ _ _ _ /_\ __| |_ __ (_)_ _ | __| |__ _ __| |__
|
||||
| _/ -_) _` | '_| / _ \/ _` | ' \| | ' \ | _|| / _` (_-< / /
|
||||
|_| \___\__,_|_| /_/ \_\__,_|_|_|_|_|_||_| |_| |_\__,_/__/_\_\\
|
||||
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
return app
|
||||
@@ -0,0 +1,19 @@
|
||||
from flask import Flask
|
||||
from typing import List
|
||||
from common.view import Blueprint
|
||||
from modules.sys.view.user import user
|
||||
|
||||
sys = Blueprint('sys', __name__, url_prefix='/sys')
|
||||
sys_lists: List[Blueprint] = [
|
||||
user
|
||||
]
|
||||
sys.register_child_bp(sys_lists)
|
||||
|
||||
parent_lists: List[Blueprint] = [
|
||||
sys
|
||||
]
|
||||
|
||||
|
||||
def register_parent_bp(app: Flask):
|
||||
for bp in parent_lists:
|
||||
app.register_blueprint(bp)
|
||||
@@ -0,0 +1,8 @@
|
||||
broker_url = 'redis://127.0.0.1:6379/1'
|
||||
result_backend = 'rpc://'
|
||||
|
||||
task_serializer = 'json'
|
||||
result_serializer = 'json'
|
||||
accept_content = ['json']
|
||||
timezone = 'Asia/Shanghai'
|
||||
enable_utc = True
|
||||
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
|
||||
# mysql 配置
|
||||
MYSQL_USERNAME: str = os.getenv('MYSQL_USERNAME') or "root"
|
||||
MYSQL_PASSWORD: str = os.getenv('MYSQL_PASSWORD') or "123456"
|
||||
MYSQL_HOST: str = os.getenv('MYSQL_HOST') or "127.0.0.1"
|
||||
MYSQL_PORT: int = int(os.getenv('MYSQL_PORT') or 3306)
|
||||
MYSQL_DATABASE: str = os.getenv('MYSQL_DATABASE') or "PearAdminFlask"
|
||||
|
||||
# mysql 数据库的配置信息
|
||||
SQLALCHEMY_DATABASE_URI: str = f"mysql+pymysql://{MYSQL_USERNAME}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}"
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_POOL_RECYCLE = 8
|
||||
@@ -0,0 +1,13 @@
|
||||
from flask import Flask
|
||||
|
||||
from common.extend.celeryer import register_celery
|
||||
from common.extend.limit import limiter
|
||||
from common.extend.orm import db
|
||||
from common.extend.migrate import migrate
|
||||
|
||||
|
||||
def init_extensions(app: Flask):
|
||||
db.init_app(app)
|
||||
migrate.init_app(app)
|
||||
register_celery(app)
|
||||
limiter.init_app(app)
|
||||
@@ -0,0 +1 @@
|
||||
Single-database configuration for Flask.
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
keys = root,sqlalchemy,alembic,flask_migrate
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
@@ -34,6 +34,11 @@ level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[logger_flask_migrate]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = flask_migrate
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
@@ -3,8 +3,6 @@ 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
|
||||
@@ -24,7 +22,8 @@ logger = logging.getLogger('alembic.env')
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
config.set_main_option(
|
||||
'sqlalchemy.url',
|
||||
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
|
||||
str(current_app.extensions['migrate'].db.get_engine().url).replace(
|
||||
'%', '%%'))
|
||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
@@ -72,11 +71,7 @@ def run_migrations_online():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
connectable = current_app.extensions['migrate'].db.get_engine()
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
@@ -0,0 +1,32 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 74133ba99af7
|
||||
Revises:
|
||||
Create Date: 2022-01-22 18:50:29.097473
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '74133ba99af7'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('admin_user',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='用户ID'),
|
||||
sa.Column('username', sa.String(length=20), nullable=True, comment='用户名'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('admin_user')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,168 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 8293d5eb9040
|
||||
Revises: d51040631f21
|
||||
Create Date: 2022-01-23 15:56:22.001914
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8293d5eb9040'
|
||||
down_revision = 'd51040631f21'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('sys_admin_log',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('method', sa.String(length=10), nullable=True),
|
||||
sa.Column('uid', sa.Integer(), nullable=True),
|
||||
sa.Column('url', sa.String(length=255), nullable=True),
|
||||
sa.Column('desc', sa.Text(), nullable=True),
|
||||
sa.Column('ip', sa.String(length=255), nullable=True),
|
||||
sa.Column('success', sa.Integer(), nullable=True),
|
||||
sa.Column('user_agent', sa.Text(), nullable=True),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_dept',
|
||||
sa.Column('id', sa.Integer(), nullable=False, comment='部门ID'),
|
||||
sa.Column('parent_id', sa.Integer(), nullable=True, comment='父级编号'),
|
||||
sa.Column('dept_name', sa.String(length=50), nullable=True, comment='部门名称'),
|
||||
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
|
||||
sa.Column('leader', sa.String(length=50), nullable=True, comment='负责人'),
|
||||
sa.Column('phone', sa.String(length=20), nullable=True, comment='联系方式'),
|
||||
sa.Column('email', sa.String(length=50), nullable=True, comment='邮箱'),
|
||||
sa.Column('status', sa.Integer(), nullable=True, comment='状态(1开启,0关闭)'),
|
||||
sa.Column('remark', sa.Text(), nullable=True, comment='备注'),
|
||||
sa.Column('address', sa.String(length=255), nullable=True, comment='详细地址'),
|
||||
sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('update_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_dict_data',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('data_label', sa.String(length=255), nullable=True, comment='字典类型名称'),
|
||||
sa.Column('data_value', sa.String(length=255), nullable=True, comment='字典类型标识'),
|
||||
sa.Column('type_code', sa.String(length=255), nullable=True, comment='字典类型描述'),
|
||||
sa.Column('is_default', sa.Integer(), nullable=True, comment='是否默认'),
|
||||
sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'),
|
||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_dict_type',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('type_name', sa.String(length=255), nullable=True, comment='字典类型名称'),
|
||||
sa.Column('type_code', sa.String(length=255), nullable=True, comment='字典类型标识'),
|
||||
sa.Column('description', sa.String(length=255), nullable=True, comment='字典类型描述'),
|
||||
sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_photo',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('href', sa.String(length=255), nullable=True),
|
||||
sa.Column('mime', sa.CHAR(length=50), nullable=False),
|
||||
sa.Column('size', sa.CHAR(length=30), nullable=False),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_power',
|
||||
sa.Column('id', sa.Integer(), nullable=False, comment='权限编号'),
|
||||
sa.Column('name', sa.String(length=255), nullable=True, comment='权限名称'),
|
||||
sa.Column('type', sa.String(length=1), nullable=True, comment='权限类型'),
|
||||
sa.Column('code', sa.String(length=30), nullable=True, comment='权限标识'),
|
||||
sa.Column('url', sa.String(length=255), nullable=True, comment='权限路径'),
|
||||
sa.Column('open_type', sa.String(length=10), nullable=True, comment='打开方式'),
|
||||
sa.Column('parent_id', sa.Integer(), nullable=True, comment='父类编号'),
|
||||
sa.Column('icon', sa.String(length=128), nullable=True, comment='图标'),
|
||||
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_role',
|
||||
sa.Column('id', sa.Integer(), nullable=False, comment='角色ID'),
|
||||
sa.Column('name', sa.String(length=255), nullable=True, comment='角色名称'),
|
||||
sa.Column('code', sa.String(length=255), nullable=True, comment='角色标识'),
|
||||
sa.Column('enable', sa.Integer(), nullable=True, comment='是否启用'),
|
||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
||||
sa.Column('details', sa.String(length=255), nullable=True, comment='详情'),
|
||||
sa.Column('sort', sa.Integer(), nullable=True, comment='排序'),
|
||||
sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_user',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='用户ID'),
|
||||
sa.Column('username', sa.String(length=20), nullable=True, comment='用户名'),
|
||||
sa.Column('realname', sa.String(length=20), nullable=True, comment='真实名字'),
|
||||
sa.Column('avatar', sa.String(length=255), nullable=True, comment='头像'),
|
||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
||||
sa.Column('password_hash', sa.String(length=128), nullable=True, comment='哈希密码'),
|
||||
sa.Column('enable', sa.Integer(), nullable=True, comment='启用'),
|
||||
sa.Column('dept_id', sa.Integer(), nullable=True, comment='部门id'),
|
||||
sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.Column('update_at', sa.DateTime(), nullable=True, comment='创建时间'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_role_power',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'),
|
||||
sa.Column('power_id', sa.Integer(), nullable=True, comment='用户编号'),
|
||||
sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'),
|
||||
sa.ForeignKeyConstraint(['power_id'], ['sys_power.id'], ),
|
||||
sa.ForeignKeyConstraint(['role_id'], ['sys_role.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('sys_user_role',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True, comment='用户编号'),
|
||||
sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'),
|
||||
sa.ForeignKeyConstraint(['role_id'], ['sys_role.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['sys_user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.drop_table('admin_test')
|
||||
op.drop_table('admin_user')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('admin_user',
|
||||
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False, comment='用户ID'),
|
||||
sa.Column('username', mysql.VARCHAR(collation='utf8mb4_general_ci', length=20), nullable=True, comment='用户名'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_default_charset='utf8mb4',
|
||||
mysql_engine='MyISAM'
|
||||
)
|
||||
op.create_table('admin_test',
|
||||
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False, comment='ID'),
|
||||
sa.Column('name', mysql.VARCHAR(collation='utf8mb4_general_ci', length=20), nullable=True, comment='名'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_default_charset='utf8mb4',
|
||||
mysql_engine='MyISAM'
|
||||
)
|
||||
op.drop_table('sys_user_role')
|
||||
op.drop_table('sys_role_power')
|
||||
op.drop_table('sys_user')
|
||||
op.drop_table('sys_role')
|
||||
op.drop_table('sys_power')
|
||||
op.drop_table('sys_photo')
|
||||
op.drop_table('sys_dict_type')
|
||||
op.drop_table('sys_dict_data')
|
||||
op.drop_table('sys_dept')
|
||||
op.drop_table('sys_admin_log')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,32 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: d51040631f21
|
||||
Revises: 74133ba99af7
|
||||
Create Date: 2022-01-22 23:30:04.372043
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'd51040631f21'
|
||||
down_revision = '74133ba99af7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('admin_test',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='ID'),
|
||||
sa.Column('name', sa.String(length=20), nullable=True, comment='名'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('admin_test')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
from modules.sys.models.user_role import user_role
|
||||
from modules.sys.models.role_power import role_power
|
||||
from modules.sys.models.dict import DictType, DictData
|
||||
from modules.sys.models.admin_log import AdminLog
|
||||
from modules.sys.models.photo import Photo
|
||||
from modules.sys.models.power import Power
|
||||
from modules.sys.models.role import Role
|
||||
from modules.sys.models.dept import Dept
|
||||
|
||||
from modules.sys.models.user import User
|
||||
|
||||
|
||||
model_lists: list = [
|
||||
role_power,
|
||||
user_role,
|
||||
Dept,
|
||||
DictType, DictData,
|
||||
AdminLog,
|
||||
Photo,
|
||||
Power,
|
||||
Role,
|
||||
User,
|
||||
|
||||
]
|
||||
@@ -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,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,23 @@
|
||||
import datetime
|
||||
|
||||
from common.model import BaseModel, Column, Integer, String, Text, DateTime, SQLAlchemyAutoSchema
|
||||
|
||||
|
||||
class AdminLog(BaseModel):
|
||||
__tablename__ = 'sys_admin_log'
|
||||
id = Column(Integer, primary_key=True)
|
||||
method = Column(String(10))
|
||||
uid = Column(Integer)
|
||||
url = Column(String(255))
|
||||
desc = Column(Text)
|
||||
ip = Column(String(255))
|
||||
success = Column(Integer)
|
||||
user_agent = Column(Text)
|
||||
create_time = Column(DateTime, default=datetime.datetime.now)
|
||||
|
||||
|
||||
class AdminLogSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = AdminLog
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
@@ -0,0 +1,26 @@
|
||||
import datetime
|
||||
|
||||
from common.model import BaseModel, Column, Integer, String, Text, DateTime, SQLAlchemyAutoSchema
|
||||
|
||||
|
||||
class Dept(BaseModel):
|
||||
__tablename__ = 'sys_dept'
|
||||
id = Column(Integer, primary_key=True, comment="部门ID")
|
||||
parent_id = Column(Integer, comment="父级编号")
|
||||
dept_name = Column(String(50), comment="部门名称")
|
||||
sort = Column(Integer, comment="排序")
|
||||
leader = Column(String(50), comment="负责人")
|
||||
phone = Column(String(20), comment="联系方式")
|
||||
email = Column(String(50), comment="邮箱")
|
||||
status = Column(Integer, comment='状态(1开启,0关闭)')
|
||||
remark = Column(Text, comment="备注")
|
||||
address = Column(String(255), comment="详细地址")
|
||||
create_at = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='创建时间')
|
||||
|
||||
|
||||
class DeptSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = Dept
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
@@ -0,0 +1,41 @@
|
||||
import datetime
|
||||
|
||||
from common.model import BaseModel, Column, Integer, String, DateTime, SQLAlchemyAutoSchema
|
||||
|
||||
|
||||
class DictType(BaseModel):
|
||||
__tablename__ = 'sys_dict_type'
|
||||
id = Column(Integer, primary_key=True)
|
||||
type_name = Column(String(255), comment='字典类型名称')
|
||||
type_code = Column(String(255), comment='字典类型标识')
|
||||
description = Column(String(255), comment='字典类型描述')
|
||||
enable = Column(Integer, comment='是否开启')
|
||||
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
|
||||
|
||||
|
||||
class DictTypeSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = DictType
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
|
||||
|
||||
class DictData(BaseModel):
|
||||
__tablename__ = 'sys_dict_data'
|
||||
id = Column(Integer, primary_key=True)
|
||||
data_label = Column(String(255), comment='字典类型名称')
|
||||
data_value = Column(String(255), comment='字典类型标识')
|
||||
type_code = Column(String(255), comment='字典类型描述')
|
||||
is_default = Column(Integer, comment='是否默认')
|
||||
enable = Column(Integer, comment='是否开启')
|
||||
remark = Column(String(255), comment='备注')
|
||||
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
|
||||
|
||||
|
||||
class DictDataSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = DictData
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
@@ -0,0 +1,20 @@
|
||||
import datetime
|
||||
|
||||
from common.model import BaseModel, Integer, Column, String, CHAR, DateTime, SQLAlchemyAutoSchema
|
||||
|
||||
|
||||
class File(BaseModel):
|
||||
__tablename__ = 'sys_file'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
href = Column(String(255))
|
||||
mime = Column(CHAR(50), nullable=False)
|
||||
size = Column(CHAR(30), nullable=False)
|
||||
create_time = Column(DateTime, default=datetime.datetime.now)
|
||||
|
||||
|
||||
class AdminLogSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = False
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
@@ -0,0 +1,20 @@
|
||||
import datetime
|
||||
|
||||
from common.model import BaseModel, Integer, Column, String, CHAR, DateTime, SQLAlchemyAutoSchema
|
||||
|
||||
|
||||
class Photo(BaseModel):
|
||||
__tablename__ = 'sys_photo'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
href = Column(String(255))
|
||||
mime = Column(CHAR(50), nullable=False)
|
||||
size = Column(CHAR(30), nullable=False)
|
||||
create_time = Column(DateTime, default=datetime.datetime.now)
|
||||
|
||||
|
||||
class AdminLogSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = Photo
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
@@ -0,0 +1,26 @@
|
||||
import datetime
|
||||
|
||||
from common.model import BaseModel, Column, Integer, String, DateTime, SQLAlchemyAutoSchema
|
||||
|
||||
|
||||
class Power(BaseModel):
|
||||
__tablename__ = 'sys_power'
|
||||
id = Column(Integer, primary_key=True, comment='权限编号')
|
||||
name = Column(String(255), comment='权限名称')
|
||||
type = Column(String(1), comment='权限类型')
|
||||
code = Column(String(30), comment='权限标识')
|
||||
url = Column(String(255), comment='权限路径')
|
||||
open_type = Column(String(10), comment='打开方式')
|
||||
parent_id = Column(Integer, comment='父类编号')
|
||||
icon = Column(String(128), comment='图标')
|
||||
sort = Column(Integer, comment='排序')
|
||||
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
|
||||
enable = Column(Integer, comment='是否开启')
|
||||
|
||||
|
||||
class PowerSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = Power
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
@@ -0,0 +1,23 @@
|
||||
import datetime
|
||||
from common.model import BaseModel, Column, String, Integer, DateTime, relationship, SQLAlchemyAutoSchema, backref
|
||||
|
||||
|
||||
class Role(BaseModel):
|
||||
__tablename__ = 'sys_role'
|
||||
id = Column(Integer, primary_key=True, comment='角色ID')
|
||||
name = Column(String(255), comment='角色名称')
|
||||
code = Column(String(255), comment='角色标识')
|
||||
enable = Column(Integer, comment='是否启用')
|
||||
remark = Column(String(255), comment='备注')
|
||||
details = Column(String(255), comment='详情')
|
||||
sort = Column(Integer, comment='排序')
|
||||
create_time = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
|
||||
power = relationship('Power', secondary="sys_role_power", backref=backref('role'))
|
||||
|
||||
|
||||
class RoleSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = Role
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
@@ -0,0 +1,8 @@
|
||||
from common.model import Column, Table, Integer, ForeignKey
|
||||
|
||||
role_power = Table(
|
||||
"sys_role_power",
|
||||
Column("id", Integer, primary_key=True, autoincrement=True, comment='标识'),
|
||||
Column("power_id", Integer, ForeignKey("sys_power.id"), comment='用户编号'),
|
||||
Column("role_id", Integer, ForeignKey("sys_role.id"), comment='角色编号'),
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
import datetime
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
from common.model import BaseModel, Column, Integer, String, DateTime, relationship, SQLAlchemyAutoSchema, backref
|
||||
|
||||
|
||||
class User(BaseModel, UserMixin):
|
||||
__tablename__ = 'sys_user'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID')
|
||||
username = Column(String(20), comment='用户名')
|
||||
realname = Column(String(20), comment='真实名字')
|
||||
avatar = Column(String(255), comment='头像', default="/static/admin/admin/images/avatar.jpg")
|
||||
remark = Column(String(255), comment='备注')
|
||||
password_hash = Column(String(128), comment='哈希密码')
|
||||
enable = Column(Integer, default=0, comment='启用')
|
||||
dept_id = Column(Integer, comment='部门id')
|
||||
create_at = Column(DateTime, default=datetime.datetime.now, comment='创建时间')
|
||||
update_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='创建时间')
|
||||
role = relationship('Role', secondary="sys_user_role", backref=backref('user'), lazy='dynamic')
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def validate_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
|
||||
# 如果要全换全部字段,就不要声明fields或exclude字段即可
|
||||
class UserRoleSchema(SQLAlchemyAutoSchema):
|
||||
class Meta:
|
||||
model = User
|
||||
include_fk = True
|
||||
load_instance = True
|
||||
exclude = ['password_hash']
|
||||
@@ -0,0 +1,8 @@
|
||||
from common.model import Table, Column, Integer, ForeignKey
|
||||
|
||||
user_role = Table(
|
||||
"sys_user_role",
|
||||
Column("id", Integer, primary_key=True, autoincrement=True, comment='标识'),
|
||||
Column("user_id", Integer, ForeignKey("sys_user.id"), comment='用户编号'),
|
||||
Column("role_id", Integer, ForeignKey("sys_role.id"), comment='角色编号'),
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
from common.view import Blueprint, View
|
||||
|
||||
|
||||
user = Blueprint('sys', __name__, url_prefix='/user')
|
||||
|
||||
|
||||
@user.route('/')
|
||||
class UserView(View):
|
||||
def get(self):
|
||||
pass
|
||||
# data, total = db.session.query(User.id, User.username).layui_paginate_json()
|
||||
# print(data)
|
||||
# return jsonify(data)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
@@ -0,0 +1,2 @@
|
||||
-r min.txt
|
||||
-r extra.txt
|
||||
@@ -0,0 +1,4 @@
|
||||
Flask-Mail==0.9.1
|
||||
celery==5.2.3
|
||||
qiniu==7.5.0
|
||||
upyun==2.5.5
|
||||
@@ -0,0 +1,11 @@
|
||||
-i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
Flask==2.0.2
|
||||
flask-restx==0.5.1
|
||||
pymysql==1.0.2
|
||||
Flask-Migrate==3.1.0
|
||||
flask-marshmallow==0.14.0
|
||||
marshmallow-sqlalchemy==0.27.0
|
||||
Flask-Login==0.5.0
|
||||
Pillow==9.0.0
|
||||
flask-limiter-2.1
|
||||
cachelib-0.6.0
|
||||
@@ -1,29 +0,0 @@
|
||||
alembic==1.5.4
|
||||
blinker==1.4
|
||||
captcha==0.3
|
||||
click==7.1.2
|
||||
Flask==1.1.2
|
||||
Flask-DebugToolbar==0.11.0
|
||||
Flask-Login==0.5.0
|
||||
flask-marshmallow==0.14.0
|
||||
Flask-Migrate==2.6.0
|
||||
Flask-Script==2.0.6
|
||||
Flask-SQLAlchemy==2.4.4
|
||||
-e git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc93e684fa0a3a4350a38e41ae00483a796#egg=Flask_Uploads
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.11.3
|
||||
Mako==1.1.4
|
||||
MarkupSafe==1.1.1
|
||||
marshmallow==3.10.0
|
||||
marshmallow-sqlalchemy==0.24.2
|
||||
Pillow==8.1.0
|
||||
psutil==5.8.0
|
||||
PyMySQL==1.0.2
|
||||
python-dateutil==2.8.1
|
||||
python-dotenv==0.15.0
|
||||
python-editor==1.0.4
|
||||
PyYAML==5.4.1
|
||||
six==1.15.0
|
||||
SQLAlchemy==1.3.23
|
||||
sqlparse==0.4.1
|
||||
Werkzeug==1.0.1
|
||||
@@ -1,443 +0,0 @@
|
||||
html,
|
||||
body,
|
||||
.layui-layout {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pear-admin .layui-header,
|
||||
.pear-admin .layui-body,
|
||||
.pear-admin .layui-logo,
|
||||
.pear-admin .layui-side,
|
||||
.pear-admin .layui-header .layui-layout-left {
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.pear-admin .layui-logo .title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-layout-right .layui-nav-child {
|
||||
border: 1px solid whitesmoke;
|
||||
border-radius: 6px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-header {
|
||||
left: 230px;
|
||||
width: calc(100% - 230px);
|
||||
background-color: white;
|
||||
border-bottom: 1px solid whitesmoke;
|
||||
}
|
||||
|
||||
.pear-admin .layui-header .layui-nav-img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-layout-control {
|
||||
left: 120px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.pear-admin .layui-logo {
|
||||
width: 230px;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
position: relative;
|
||||
background-color: #28333E;
|
||||
|
||||
}
|
||||
|
||||
.pear-admin .layui-logo img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-logo .title {
|
||||
font-size: 21px;
|
||||
font-weight: 550;
|
||||
color: #5FB878;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-logo .logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pear-admin .layui-side {
|
||||
top: 0px;
|
||||
width: 230px;
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.pear-admin .layui-side-scroll::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-side-scroll {
|
||||
height: calc(100% - 60px) !important;
|
||||
background-color: #28333E;
|
||||
width: 247px;
|
||||
|
||||
}
|
||||
|
||||
.pear-admin .layui-header .layui-nav .layui-nav-item>a {
|
||||
color: black;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-body {
|
||||
left: 230px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-layout-left {
|
||||
left: 0px;
|
||||
|
||||
}
|
||||
|
||||
/** 隐 藏 布 局 样 式 */
|
||||
.pear-mini .layui-logo .title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pear-mini .layui-logo .logo {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.pear-mini .layui-side {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.pear-mini .layui-header {
|
||||
left: 60px;
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.pear-mini .layui-body {
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.pear-mini .layui-logo {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.pear-mini .layui-nav-tree .layui-nav-item span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pear-mini .bottom-nav li {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.pear-mini .layui-side-scroll {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.pear-admin .layui-header .layui-nav .layui-nav-bar {
|
||||
top: 0px !important;
|
||||
height: 2px !important;
|
||||
background-color: #5FB878;
|
||||
}
|
||||
|
||||
.pear-admin .layui-header .layui-nav .layui-this:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pear-admin .layui-header .layui-nav-more {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pear-collasped-pe {
|
||||
display: none;
|
||||
width: 50px;
|
||||
position: absolute;
|
||||
z-index: 400000;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
background-color: #5FB878 !important;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
border-radius: 50px;
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
|
||||
}
|
||||
|
||||
.pear-collasped-pe a {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.layui-hide-sm {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 769px) {
|
||||
.layui-hide-sm {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/** 新增兼容 */
|
||||
@media screen and (max-width:768px) {
|
||||
.collaspe {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.pear-collasped-pe {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
.layui-layout-control {
|
||||
left: 45px !important;
|
||||
}
|
||||
|
||||
.layui-layout-left {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.pear-mini .bottom-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pear-mini .layui-side-scroll {
|
||||
height: calc(100% - 62px);
|
||||
}
|
||||
|
||||
/** 隐 藏 布 局 样 式 */
|
||||
.pear-mini .layui-side {
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.pear-mini .layui-header {
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pear-mini .layui-body {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.pear-mini .layui-logo {
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-body {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.pear-admin .layui-header {
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pear-admin .pear-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #1E1E1E;
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
margin-top: -60px;
|
||||
}
|
||||
|
||||
.pear-mini .pear-cover {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes am-horizontal-roll_show {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateX(2000px);
|
||||
transform: translateX(2000px)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes am-horizontal-roll_show {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateX(800px);
|
||||
-ms-transform: translateX(800px);
|
||||
transform: translateX(800px)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0)
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-right {
|
||||
-webkit-animation: am-horizontal-roll_show .6s ease-out;
|
||||
animation: am-horizontal-roll_show .6s ease-out;
|
||||
|
||||
}
|
||||
|
||||
/** 亮色侧边风格 */
|
||||
.light-theme .layui-logo {
|
||||
background-color: white !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.light-theme .layui-side-scroll {
|
||||
background-color: white !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.light-theme .layui-side {
|
||||
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, .05) !important;
|
||||
}
|
||||
|
||||
/** 主 题 选 择 界 面 样 式 */
|
||||
.pearone-color .color-title {
|
||||
padding: 15px 0 0px 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pearone-color .color-content {
|
||||
padding: 15px 10px 0 20px;
|
||||
}
|
||||
|
||||
.pearone-color .color-content ul {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.pearone-color .color-content ul li {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
margin: 0 20px 20px 0;
|
||||
padding: 2px 2px 2px 2px;
|
||||
background-color: #f2f2f2;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pearone-color .color-content li.layui-this:after,
|
||||
.pearone-color .color-content li:hover:after {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 4px;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
border: #5FB878 2px solid;
|
||||
opacity: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pearone-color .color-content li:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 1px;
|
||||
height: 0;
|
||||
border: 2px solid #F2F2F2;
|
||||
transition: all .3s;
|
||||
-webkit-transition: all .3s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.select-color {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.select-color .select-color-title {
|
||||
padding: 15px 0 0px 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.select-color .select-color-content {
|
||||
padding: 20px 0 0px 0px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.select-color .select-color-content .select-color-item {
|
||||
background-color: gray;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 3px;
|
||||
float: left;
|
||||
margin-left: 20px;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .15);
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
/** 友情链接 */
|
||||
.more-setting {
|
||||
margin-top: 45px;
|
||||
}
|
||||
|
||||
.more-setting form {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.more-setting-title {
|
||||
padding: 15px 0 0px 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.more-setting .layui-form-label {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.more-menu-list {
|
||||
width: 100%;
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
.more-menu-item:first-child {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.more-menu-item .layui-icon {
|
||||
font-size: 18px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.more-menu-item {
|
||||
color: #595959;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
padding: 0 25px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
font-style: normal;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.more-menu-item:hover {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.more-menu-item:after {
|
||||
color: #8c8c8c;
|
||||
right: 16px;
|
||||
content: "\e602";
|
||||
position: absolute;
|
||||
font-family: layui-icon !important;
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
.loader-main{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: whitesmoke;
|
||||
z-index: 9999999;
|
||||
margin-top: -61px;
|
||||
}
|
||||
.loader {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 30px auto 40px;
|
||||
margin-top: 20%;
|
||||
position: relative;
|
||||
z-index: 999999;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
.loader:before {
|
||||
content: "";
|
||||
width: 50px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #000;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
top: 59px;
|
||||
left: 0;
|
||||
animation: shadow .5s linear infinite;
|
||||
}
|
||||
.loader:after {
|
||||
content: "";
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 3px;
|
||||
background-color: #5FB878;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
animation: loading .5s linear infinite;
|
||||
}
|
||||
@-webkit-keyframes loading {
|
||||
17% {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateY(9px) rotate(22.5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
|
||||
border-bottom-right-radius: 40px;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateY(9px) rotate(67.5deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0) rotate(90deg);
|
||||
}
|
||||
}
|
||||
@keyframes loading {
|
||||
17% {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateY(9px) rotate(22.5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(18px) scale(1, 0.9) rotate(45deg);
|
||||
border-bottom-right-radius: 40px;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateY(9px) rotate(67.5deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0) rotate(90deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes shadow {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.2, 1);
|
||||
}
|
||||
}
|
||||
@keyframes shadow {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.2, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
.top-panel {
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.top-panel>.layui-card-body {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.top-panel-number {
|
||||
line-height: 60px;
|
||||
font-size: 30px;
|
||||
border-right: 1px solid #eceff9;
|
||||
}
|
||||
|
||||
.top-panel-tips {
|
||||
padding-left: 8px;
|
||||
padding-top: 16px;
|
||||
line-height: 30px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pear-container {
|
||||
background-color: whitesmoke;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
background-color: whitesmoke;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.card .header .avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 20px;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.card .header {
|
||||
color: dimgray;
|
||||
}
|
||||
|
||||
.card .body {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.card .body {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.card .footer {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
color: gray;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.custom-tab .layui-tab-title {
|
||||
border-bottom-width: 0px;
|
||||
border-bottom-style: none;
|
||||
}
|
||||
|
||||
.custom-tab .layui-tab-title li {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.list .list-item {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
color: gray;
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
border-radius: 4px;
|
||||
margin-top: 5.2px;
|
||||
}
|
||||
|
||||
.list .list-item:hover {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.list .list-item .title {
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list .list-item .footer {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.top-panel-tips i {
|
||||
font-size: 33px;
|
||||
}
|
||||
|
||||
.layuiadmin-card-status {
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
|
||||
.layuiadmin-card-status dd {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #EEE;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.layuiadmin-card-status dd div.layui-status-img,
|
||||
.layuiadmin-card-team .layui-team-img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.layuiadmin-card-status dd div.layui-status-img a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.layuiadmin-card-status dd div span {
|
||||
color: #BBB;
|
||||
}
|
||||
|
||||
.layuiadmin-card-status dd div a {
|
||||
color: #01AAED;
|
||||
}
|
||||
|
||||
.top-panel-tips svg {
|
||||
margin-top: -12px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user