!89 doc: 完善 master 分支文档,并移除不需要的图片 & fix: 简化插件结构同步主项目
Merge pull request !89 from 我叫以赏/master
@@ -8,7 +8,7 @@
|
|||||||
开 箱 即 用 的 Flask 快 速 开 发 平 台
|
开 箱 即 用 的 Flask 快 速 开 发 平 台
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
[预 览](http://flask.pearadmin.com) | [官 网](http://www.pearadmin.com/) | [群聊](docs/group.md) | [文档](docs/detail.md)
|
[预览](http://flask.pearadmin.com) | [官网](http://www.pearadmin.com/) | [群聊](docs/group.md) | [文档](docs/detail.md)
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -46,8 +46,48 @@ Pear Admin Flask 基于 Flask 的后台管理系统,拥抱应用广泛的pytho
|
|||||||
|
|
||||||
#### 项目结构
|
#### 项目结构
|
||||||
|
|
||||||
|
## 应用结构
|
||||||
|
```应用结构
|
||||||
|
Pear Admin Flask
|
||||||
|
├─applications # 应用
|
||||||
|
│ ├─configs # 配置文件
|
||||||
|
│ │ ├─ common.py # 普通配置
|
||||||
|
│ │ └─ config.py # 配置文件对象
|
||||||
|
│ ├─extensions # 注册插件
|
||||||
|
│ ├─models # 数据模型
|
||||||
|
│ ├─static # 静态资源文件
|
||||||
|
│ ├─templates # 静态模板文件
|
||||||
|
│ └─views # 视图部分
|
||||||
|
│ ├─admin # 后台管理视图模块
|
||||||
|
│ └─index # 前台视图模块
|
||||||
|
├─docs # 文档说明
|
||||||
|
├─migrations # 迁移文件记录
|
||||||
|
├─requirement # 依赖文件
|
||||||
|
└─.env # 项目的配置文件
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 资源结构
|
||||||
|
```资源结构
|
||||||
|
Pear Admin Flask
|
||||||
|
├─static # 项目设定的 Flask 资源文件夹
|
||||||
|
│ ├─admin # pear admin flask 的后端资源文件(与 pear admin layui 同步)
|
||||||
|
│ ├─index # pear admin flask 的前端资源文件
|
||||||
|
│ └─upload # 用户上传保存目录
|
||||||
|
└─templates # 项目设定的 Flask 模板文件夹
|
||||||
|
├─admin # pear admin flask 的后端管理页面模板
|
||||||
|
│ ├─admin_log # 日志页面
|
||||||
|
│ ├─common # 基本模板页面(头部模板与页脚模板)
|
||||||
|
│ ├─console # 系统监控页面模板
|
||||||
|
│ ├─dept # 部门管理页面模板
|
||||||
|
│ ├─dict # 数据自动页面模板
|
||||||
|
│ ├─mail # 邮件管理页面模板
|
||||||
|
│ ├─photo # 图片上传页面模板
|
||||||
|
│ ├─power # 权限(菜单)管理页面模板
|
||||||
|
│ ├─role # 角色管理页面模板
|
||||||
|
│ ├─task # 任务设置页面模板
|
||||||
|
│ └─user # 用户管理页面模板
|
||||||
|
├─errors # 错误页面模板
|
||||||
|
└─index # 主页模板
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 项目安装
|
#### 项目安装
|
||||||
|
|||||||
@@ -602,58 +602,6 @@ powerdata = [
|
|||||||
create_time=now_time,
|
create_time=now_time,
|
||||||
enable=1,
|
enable=1,
|
||||||
|
|
||||||
), Power(
|
|
||||||
id=60,
|
|
||||||
name='拓展插件',
|
|
||||||
type='0',
|
|
||||||
code='',
|
|
||||||
url='',
|
|
||||||
open_type='',
|
|
||||||
parent_id='0',
|
|
||||||
icon='layui-icon layui-icon-senior',
|
|
||||||
sort=2,
|
|
||||||
create_time=now_time,
|
|
||||||
enable=1,
|
|
||||||
|
|
||||||
), Power(
|
|
||||||
id=61,
|
|
||||||
name='插件管理',
|
|
||||||
type='1',
|
|
||||||
code='admin:plugin:main',
|
|
||||||
url='/plugin',
|
|
||||||
open_type='_iframe',
|
|
||||||
parent_id='60',
|
|
||||||
icon='layui-icon layui-icon',
|
|
||||||
sort=2,
|
|
||||||
create_time=now_time,
|
|
||||||
enable=1,
|
|
||||||
|
|
||||||
), Power(
|
|
||||||
id=62,
|
|
||||||
name='启禁插件',
|
|
||||||
type='2',
|
|
||||||
code='admin:plugin:enable',
|
|
||||||
url='',
|
|
||||||
open_type='',
|
|
||||||
parent_id='61',
|
|
||||||
icon='layui-icon layui-icon',
|
|
||||||
sort=1,
|
|
||||||
create_time=now_time,
|
|
||||||
enable=1,
|
|
||||||
|
|
||||||
), Power(
|
|
||||||
id=63,
|
|
||||||
name='删除插件',
|
|
||||||
type='2',
|
|
||||||
code='admin:plugin:remove',
|
|
||||||
url='',
|
|
||||||
open_type='',
|
|
||||||
parent_id='61',
|
|
||||||
icon='layui-icon layui-icon',
|
|
||||||
sort=2,
|
|
||||||
create_time=now_time,
|
|
||||||
enable=1,
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ class BaseConfig:
|
|||||||
'max_instances': 3
|
'max_instances': 3
|
||||||
}
|
}
|
||||||
|
|
||||||
# 插件配置
|
# 插件配置,填写插件的文件名名称,默认不启用插件。
|
||||||
PLUGIN_ENABLE_FOLDERS = ["helloworld"]
|
PLUGIN_ENABLE_FOLDERS = []
|
||||||
|
|
||||||
# 配置多个数据库连接的连接串写法示例
|
# 配置多个数据库连接的连接串写法示例
|
||||||
# HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称
|
# HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ def init_view(app):
|
|||||||
register_rights_view(app)
|
register_rights_view(app)
|
||||||
register_passport_views(app)
|
register_passport_views(app)
|
||||||
register_dept_views(app)
|
register_dept_views(app)
|
||||||
# register_plugin_views(app)
|
register_plugin_views(app)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def register_plugin_views(app: Flask):
|
|||||||
app.register_blueprint(plugin_bp)
|
app.register_blueprint(plugin_bp)
|
||||||
# 载入插件过程
|
# 载入插件过程
|
||||||
# plugin_folder 配置的是插件的文件夹名
|
# plugin_folder 配置的是插件的文件夹名
|
||||||
PLUGIN_ENABLE_FOLDERS = json.loads(app.config['PLUGIN_ENABLE_FOLDERS'])
|
PLUGIN_ENABLE_FOLDERS = app.config['PLUGIN_ENABLE_FOLDERS']
|
||||||
for plugin_folder in PLUGIN_ENABLE_FOLDERS:
|
for plugin_folder in PLUGIN_ENABLE_FOLDERS:
|
||||||
plugin_info = {}
|
plugin_info = {}
|
||||||
try:
|
try:
|
||||||
@@ -39,124 +39,3 @@ def register_plugin_views(app: Flask):
|
|||||||
info += 'repr(e):\t' + repr(e) + "\n"
|
info += 'repr(e):\t' + repr(e) + "\n"
|
||||||
info += 'traceback.format_exc():\n%s' + traceback.format_exc()
|
info += 'traceback.format_exc():\n%s' + traceback.format_exc()
|
||||||
print(info)
|
print(info)
|
||||||
|
|
||||||
|
|
||||||
@plugin_bp.get('/')
|
|
||||||
@authorize("admin:plugin:main", log=True)
|
|
||||||
def main():
|
|
||||||
"""此处渲染管理模板"""
|
|
||||||
return render_template('admin/plugin/main.html')
|
|
||||||
|
|
||||||
|
|
||||||
@plugin_bp.get('/data')
|
|
||||||
@authorize("admin:plugin:main", log=True)
|
|
||||||
def data():
|
|
||||||
"""请求插件数据"""
|
|
||||||
plugin_name = escape(request.args.get("plugin_name"))
|
|
||||||
all_plugins = []
|
|
||||||
count = 0
|
|
||||||
for filename in os.listdir("plugins"):
|
|
||||||
try:
|
|
||||||
with open("plugins/" + filename + "/__init__.json", "r", encoding='utf-8') as f:
|
|
||||||
info = json.loads(f.read())
|
|
||||||
|
|
||||||
if plugin_name is None:
|
|
||||||
if info['plugin_name'].find(plugin_name) == -1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
all_plugins.append(
|
|
||||||
{
|
|
||||||
"plugin_name": info["plugin_name"],
|
|
||||||
"plugin_version": info["plugin_version"],
|
|
||||||
"plugin_description": info["plugin_description"],
|
|
||||||
"plugin_folder_name": filename,
|
|
||||||
"enable": "1" if filename in PLUGIN_ENABLE_FOLDERS else "0"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
count += 1
|
|
||||||
except BaseException as error:
|
|
||||||
print(filename, error)
|
|
||||||
continue
|
|
||||||
return table_api(data=all_plugins, count=count)
|
|
||||||
|
|
||||||
|
|
||||||
@plugin_bp.put('/enable')
|
|
||||||
@authorize("admin:plugin:enable", log=True)
|
|
||||||
def enable():
|
|
||||||
"""启用插件"""
|
|
||||||
plugin_folder_name = request.get_json(force=True).get('plugin_folder_name')
|
|
||||||
if plugin_folder_name:
|
|
||||||
try:
|
|
||||||
if plugin_folder_name not in PLUGIN_ENABLE_FOLDERS:
|
|
||||||
PLUGIN_ENABLE_FOLDERS.append(plugin_folder_name)
|
|
||||||
with open(".flaskenv", "r", encoding='utf-8') as f:
|
|
||||||
flaskenv = f.read() # type: str
|
|
||||||
pos1 = flaskenv.find("PLUGIN_ENABLE_FOLDERS")
|
|
||||||
pos2 = flaskenv.find("\n", pos1)
|
|
||||||
with open(".flaskenv", "w", encoding='utf-8') as f:
|
|
||||||
if pos2 == -1:
|
|
||||||
f.write(flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS))
|
|
||||||
else:
|
|
||||||
f.write(
|
|
||||||
flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS) + flaskenv[
|
|
||||||
pos2:])
|
|
||||||
# 启用插件事件
|
|
||||||
try:
|
|
||||||
getattr(importlib.import_module('plugins.' + plugin_folder_name), "event_enable")()
|
|
||||||
except AttributeError: # 没有插件启用事件就不调用
|
|
||||||
pass
|
|
||||||
except BaseException as error:
|
|
||||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
|
||||||
|
|
||||||
except BaseException as error:
|
|
||||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
|
||||||
return success_api(msg="启用成功,要使修改生效需要重启程序。")
|
|
||||||
return fail_api(msg="数据错误")
|
|
||||||
|
|
||||||
|
|
||||||
@plugin_bp.put('/disable')
|
|
||||||
@authorize("admin:plugin:enable", log=True)
|
|
||||||
def disable():
|
|
||||||
"""禁用插件"""
|
|
||||||
plugin_folder_name = request.get_json(force=True).get('plugin_folder_name')
|
|
||||||
if plugin_folder_name:
|
|
||||||
try:
|
|
||||||
if plugin_folder_name in PLUGIN_ENABLE_FOLDERS:
|
|
||||||
PLUGIN_ENABLE_FOLDERS.remove(plugin_folder_name)
|
|
||||||
with open(".flaskenv", "r", encoding='utf-8') as f:
|
|
||||||
flaskenv = f.read() # type: str
|
|
||||||
pos1 = flaskenv.find("PLUGIN_ENABLE_FOLDERS")
|
|
||||||
pos2 = flaskenv.find("\n", pos1)
|
|
||||||
with open(".flaskenv", "w", encoding='utf-8') as f:
|
|
||||||
if pos2 == -1:
|
|
||||||
f.write(flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS))
|
|
||||||
else:
|
|
||||||
f.write(
|
|
||||||
flaskenv[:pos1] + "PLUGIN_ENABLE_FOLDERS = " + json.dumps(PLUGIN_ENABLE_FOLDERS) + flaskenv[
|
|
||||||
pos2:])
|
|
||||||
|
|
||||||
# 禁用插件事件
|
|
||||||
try:
|
|
||||||
getattr(importlib.import_module('plugins.' + plugin_folder_name), "event_disable")()
|
|
||||||
except AttributeError: # 没有插件禁用事件就不调用
|
|
||||||
pass
|
|
||||||
except BaseException as error:
|
|
||||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
|
||||||
|
|
||||||
except BaseException as error:
|
|
||||||
return fail_api(msg="Crash a error! Info: " + str(error))
|
|
||||||
return success_api(msg="禁用成功,要使修改生效需要重启程序。")
|
|
||||||
return fail_api(msg="数据错误")
|
|
||||||
|
|
||||||
|
|
||||||
# 删除
|
|
||||||
@plugin_bp.delete('/remove/<string:plugin_folder_name>')
|
|
||||||
@authorize("admin:mail:remove", log=True)
|
|
||||||
def delete(plugin_folder_name):
|
|
||||||
if plugin_folder_name in PLUGIN_ENABLE_FOLDERS:
|
|
||||||
return fail_api(msg="您必须先禁用插件!")
|
|
||||||
try:
|
|
||||||
shutil.rmtree(os.path.abspath("plugins/" + plugin_folder_name))
|
|
||||||
return success_api(msg="删除成功")
|
|
||||||
except BaseException as error:
|
|
||||||
return fail_api(msg="删除失败!原因:" + str(error))
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 71 KiB |
@@ -1,140 +1,44 @@
|
|||||||
### 权限管理 :id=authorize
|
## 项目介绍 :id=start
|
||||||
|
|
||||||
使用装饰器 @authorize时需要注意,该装饰器需要写在 @app.route 之后
|
欢迎阅读 Pear Admin Flask 的开发文档!Pear Admin Flask 是一个基于 Flask 的后台管理系统,拥抱应用广泛的 Python 语言,通过使用本系统,即可快速构建你的功能业务。
|
||||||
|
|
||||||
```python
|
项目旨在为 Python 开发者提供一个后台管理系统的模板,成为您构建信息管理系统、物联网后台等应用时灵活、简单的工具。
|
||||||
@authorize(power: str, log: bool)
|
|
||||||
```
|
|
||||||
|
|
||||||
第一个参数为权限 code
|
同时,Pear Admin Flask 项目也是一个对于 Python 初学者友好的项目。此项目处于开发初期,欢迎各位 Python 爱好者加入到 Pear Admin Flask 项目建设中来。如果您在使用此项目的过程中,发现项目代码存在问题,请在 Gitee 上提交 PR ,一起开源共建!
|
||||||
|
|
||||||
第二个参数为是否生成日志
|
接下来,我们将会为您详细介绍该项目搭建方法与开发架构。
|
||||||
|
|
||||||
```python
|
> 如果对项目有不理解的地方欢迎加入我们的讨论群。
|
||||||
# 例如
|
|
||||||
@authorize("admin:power:remove", log=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
在前端中,例如增加,删除按钮,对于没有编辑权限的用户不显示的话,可以使用
|

|
||||||
|
|
||||||
`{% **if** authorize("admin:user:edit") %}`
|
> 微信群不定期在qq群更新二维码。
|
||||||
|
|
||||||
`{% endif %}`
|

|
||||||
|
|
||||||
例如
|
**[master分支版本](https://gitee.com/pear-admin/pear-admin-flask/tree/master/)**
|
||||||
|
|
||||||
```python
|
flask 2.0.1 + flask-sqlalchemy + 权限验证 + Flask-APScheduler 定时任务 + marshmallow 序列化与数据验证
|
||||||
{% if authorize("admin:user:edit") %}
|
|
||||||
<button class="pear-btn pear-btn-primary pear-btn-sm" lay-event="edit">
|
|
||||||
<i class="pear-icon pear-icon-edit"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if authorize("admin:user:remove") %}
|
|
||||||
<button class="pear-btn pear-btn-danger pear-btn-sm" lay-event="remove">
|
|
||||||
<i class="pear-icon pear-icon-ashbin"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
```
|
|
||||||
|
|
||||||
## model序列化 :id=Schema
|
master 分支为主分支,是功能最全、页面最多的分支。
|
||||||
|
|
||||||
- sqlalchemy查询的model对象转dict
|

|
||||||
|
|
||||||
|
## 下载使用 :id=download
|
||||||
|
|
||||||
|
|
||||||
```
|
#### 1. 官网地址
|
||||||
model_to_dicts(Schema, model)
|
|
||||||
```
|
|
||||||
|
|
||||||
Schema 是 序列化类,我把他放在了models文件里,觉得没有必要见一个文件夹叫Schema,也方便看着模型写序列化类
|
官网提供稳定版本的 Release 发行版本 [前往](http://www.pearadmin.com)
|
||||||
|
|
||||||
```python
|

|
||||||
# 例如
|
|
||||||
class DeptSchema(ma.Schema): # 序列化类
|
|
||||||
deptId = fields.Integer(attribute="id")
|
|
||||||
parentId = fields.Integer(attribute="parent_id")
|
|
||||||
deptName = fields.Str(attribute="dept_name")
|
|
||||||
leader = fields.Str()
|
|
||||||
phone = fields.Str()
|
|
||||||
email = fields.Str()
|
|
||||||
address = fields.Str()
|
|
||||||
status = fields.Str()
|
|
||||||
sort = fields.Str()
|
|
||||||
```
|
|
||||||
|
|
||||||
>这一部分有问题的话请看marshmallow文档
|
#### 2. 源码仓库
|
||||||
|
|
||||||
model写的是查询后的对象
|
如果你需要最新代码,请前往 Gitee 仓库 [前往](https://gitee.com/pear-admin/pear-admin-flask)
|
||||||
|
|
||||||
```python
|

|
||||||
dept = Dept.query.order_by(Dept.sort).all()
|
|
||||||
```
|
|
||||||
|
|
||||||
进行序列化
|
|
||||||
|
|
||||||
```python
|
|
||||||
res = model_to_dicts(Schema=DeptSchema, model=dept)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 构造查询过滤
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 准确查询字段
|
|
||||||
# 不等于查询字段
|
|
||||||
# 大于查询字段
|
|
||||||
# 小于查询字段
|
|
||||||
# 模糊查询字段(%+xxx+%)
|
|
||||||
# 左模糊 (% + xxx)
|
|
||||||
# 右模糊查询字段(xxx+ %)
|
|
||||||
# 包含查询字段
|
|
||||||
#范围查询字段
|
|
||||||
# 查询
|
|
||||||
```
|
|
||||||
|
|
||||||
## xss过滤
|
|
||||||
|
|
||||||
```python
|
|
||||||
from applications.common.utils.validate import str_escape
|
|
||||||
details = str_escape(req.get("details"))
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
如果您完成了这一步,请参阅[下载安装](install.md)章节。
|
||||||
|
|
||||||
## 邮件发送
|
|
||||||
|
|
||||||
```python
|
|
||||||
#在.flaskenv中配置邮箱
|
|
||||||
|
|
||||||
from applications/common/utils/mail import send_main
|
|
||||||
send_mail(subject='title', recipients=['123@qq.com'], content='body')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 返回格式
|
|
||||||
|
|
||||||
```
|
|
||||||
from applications/common/utils/http import success_api,fail_api,table_api
|
|
||||||
|
|
||||||
# 这是源代码
|
|
||||||
def success_api(msg: str = "成功"):
|
|
||||||
""" 成功响应 默认值”成功“ """
|
|
||||||
return jsonify(success=True, msg=msg)
|
|
||||||
|
|
||||||
|
|
||||||
def fail_api(msg: str = "失败"):
|
|
||||||
""" 失败响应 默认值“失败” """
|
|
||||||
return jsonify(success=False, msg=msg)
|
|
||||||
|
|
||||||
|
|
||||||
def table_api(msg: str = "", count=0, data=None, limit=10):
|
|
||||||
""" 动态表格渲染响应 """
|
|
||||||
res = {
|
|
||||||
'msg': msg,
|
|
||||||
'code': 0,
|
|
||||||
'data': data,
|
|
||||||
'count': count,
|
|
||||||
'limit': limit
|
|
||||||
|
|
||||||
}
|
|
||||||
return jsonify(res)
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
## 用户权限判断
|
||||||
|
|
||||||
|
Pear Admin Flask 项目中集成很多实用的功能,为了便于二次开发,同样也提供了许多便于开发的自定义函数。
|
||||||
|
|
||||||
|
Pear Admin Flask 项目支持多用户,不同用户有不同的权限,此处将介绍 Pear Admin Flask 中的权限管理函数的用法。
|
||||||
|
|
||||||
|
### 函数原型
|
||||||
|
|
||||||
|
函数调用位于项目代码 ```applications/common/utils/rights.py``` 中,函数原型如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def authorize(power: str, log: bool = False):
|
||||||
|
"""
|
||||||
|
用户权限判断,用于判断目前会话用户是否拥有访问权限
|
||||||
|
|
||||||
|
:param power: 权限标识
|
||||||
|
:type power: str
|
||||||
|
:param log: 是否记录日志, defaults to False
|
||||||
|
:type log: bool, optional
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
+ 后端用法
|
||||||
|
|
||||||
|
```python
|
||||||
|
from applications.common.utils.rights import authorize
|
||||||
|
|
||||||
|
@app.route("/test")
|
||||||
|
@authorize("admin:power:remove", log=True)
|
||||||
|
def test_index():
|
||||||
|
return 'You are allowed.'
|
||||||
|
```
|
||||||
|
|
||||||
|
> 使用装饰器 @authorize时需要注意,该装饰器需要写在 @app.route之后
|
||||||
|
|
||||||
|
+ 前端用法
|
||||||
|
|
||||||
|
在前端中,例如增加,删除按钮,对于没有编辑权限的用户不显示的话,可以使用
|
||||||
|
|
||||||
|
`{% **if** authorize("admin:user:edit") %}`
|
||||||
|
|
||||||
|
`{% endif %}`
|
||||||
|
|
||||||
|
例如
|
||||||
|
|
||||||
|
```python
|
||||||
|
{% if authorize("admin:user:edit") %}
|
||||||
|
<button class="pear-btn pear-btn-primary pear-btn-sm" lay-event="edit">
|
||||||
|
<i class="pear-icon pear-icon-edit"></i>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if authorize("admin:user:remove") %}
|
||||||
|
<button class="pear-btn pear-btn-danger pear-btn-sm" lay-event="remove">
|
||||||
|
<i class="pear-icon pear-icon-ashbin"></i>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema 序列化
|
||||||
|
|
||||||
|
项目中时常会涉及到数据库的读写,在读入数据时可以采用SQLalchemy,将模型查询的数据对象转化为字典。
|
||||||
|
|
||||||
|
> Schema 是序列化类,我们把他放在了models文件里,因为觉得没有必要新建一个文件夹叫 Schema ,也方便看着模型写序列化类。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 例如
|
||||||
|
class DeptSchema(ma.Schema): # 序列化类
|
||||||
|
deptId = fields.Integer(attribute="id")
|
||||||
|
parentId = fields.Integer(attribute="parent_id")
|
||||||
|
deptName = fields.Str(attribute="dept_name")
|
||||||
|
leader = fields.Str()
|
||||||
|
phone = fields.Str()
|
||||||
|
email = fields.Str()
|
||||||
|
address = fields.Str()
|
||||||
|
status = fields.Str()
|
||||||
|
sort = fields.Str()
|
||||||
|
```
|
||||||
|
|
||||||
|
> 这一部分有问题的话请看 marshmallow 文档
|
||||||
|
|
||||||
|
### 模型到字典
|
||||||
|
|
||||||
|
#### 函数原型
|
||||||
|
|
||||||
|
函数调用位于项目代码 ```applications/common/curd.py``` 中,函数原型如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
def model_to_dicts(schema: ma.Schema, data):
|
||||||
|
"""
|
||||||
|
将模型查询的数据对象转化为字典
|
||||||
|
|
||||||
|
:param schema: schema类
|
||||||
|
:param model: sqlalchemy查询结果
|
||||||
|
:return: 返回单个查询结果
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 基本用法
|
||||||
|
|
||||||
|
+ model写的是查询后的对象
|
||||||
|
|
||||||
|
```python
|
||||||
|
from applications.common import curd
|
||||||
|
from applications.models import Dept
|
||||||
|
from applications.schemas import DeptOutSchema
|
||||||
|
|
||||||
|
def test(): # 某函数内
|
||||||
|
dept = Dept.query.order_by(Dept.sort).all()
|
||||||
|
res = curd.model_to_dicts(Schema=DeptOutSchema, model=dept)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 查询多字段构造器
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 准确查询字段
|
||||||
|
# 不等于查询字段
|
||||||
|
# 大于查询字段
|
||||||
|
# 小于查询字段
|
||||||
|
# 模糊查询字段(%+xxx+%)
|
||||||
|
# 左模糊 (% + xxx)
|
||||||
|
# 右模糊查询字段(xxx+ %)
|
||||||
|
# 包含查询字段
|
||||||
|
# 范围查询字段
|
||||||
|
# 查询
|
||||||
|
```
|
||||||
|
|
||||||
|
## xss过滤
|
||||||
|
|
||||||
|
### 函数原型
|
||||||
|
|
||||||
|
函数调用位于项目代码 ```applications/common/utils/validate.py``` 中,函数原型如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
def str_escape(s: str) -> str:
|
||||||
|
"""
|
||||||
|
xss过滤,内部采用flask自带的过滤函数。
|
||||||
|
与原过滤函数不同的是此过滤函数将在 s 为 None 时返回 None。
|
||||||
|
|
||||||
|
:param s: 要过滤的字符串
|
||||||
|
:type s: str
|
||||||
|
:return: s 为 None 时返回 None,否则过滤字符串后返回。
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
```python
|
||||||
|
from applications.common.utils.validate import str_escape
|
||||||
|
real_name = xss_escape(request.args.get('realName', type=str))
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 邮件发送
|
||||||
|
|
||||||
|
+ 原邮件发送函数
|
||||||
|
|
||||||
|
### 函数原型
|
||||||
|
|
||||||
|
函数调用位于项目代码 ```applications/common/utils/mail.py``` 中,函数原型如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
def send_mail(subject, recipients, content):
|
||||||
|
"""原发送邮件函数,不会记录邮件发送记录
|
||||||
|
|
||||||
|
失败报错,请注意使用 try 拦截。
|
||||||
|
|
||||||
|
:param subject: 主题
|
||||||
|
:param recipients: 接收者 多个用英文分号隔开
|
||||||
|
:param content: 邮件 html
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
#在.flaskenv中配置邮箱
|
||||||
|
from applications.common.utils import mail
|
||||||
|
|
||||||
|
mail.send_mail("subject", "test@test.com", "<h1>Hello</h1>")
|
||||||
|
```
|
||||||
|
|
||||||
|
+ 基于二次开发的邮件发送函数
|
||||||
|
|
||||||
|
### 函数原型
|
||||||
|
|
||||||
|
函数调用位于项目代码 ```applications/common/utils/mail.py``` 中,函数原型如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
def add(receiver, subject, content, user_id):
|
||||||
|
"""
|
||||||
|
发送一封邮件,若发送成功立刻提交数据库。
|
||||||
|
|
||||||
|
:param receiver: 接收者 多个用英文逗号隔开
|
||||||
|
:param subject: 邮件主题
|
||||||
|
:param content: 邮件 html
|
||||||
|
:param user_id: 发送用户ID(谁发送的?) 可以用 from flask_login import current_user ; current_user.id 来表示当前登录用户
|
||||||
|
:return: 成功与否
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
#在.flaskenv中配置邮箱
|
||||||
|
from applications.common.utils import mail
|
||||||
|
|
||||||
|
mail.add("test@test.com", "subject", "<h1>Hello</h1>", current_user)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 返回格式
|
||||||
|
|
||||||
|
> 后端响应时我们推荐使用规定的API响应格式。
|
||||||
|
|
||||||
|
### 函数原型
|
||||||
|
|
||||||
|
函数调用位于项目代码 ```applications/common/utils/http.py``` 中,函数原型如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
def success_api(msg: str = "成功"):
|
||||||
|
""" 成功响应 默认值“成功” """
|
||||||
|
return jsonify(success=True, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def fail_api(msg: str = "失败"):
|
||||||
|
""" 失败响应 默认值“失败” """
|
||||||
|
return jsonify(success=False, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def table_api(msg: str = "", count=0, data=None, limit=10):
|
||||||
|
""" 动态表格渲染响应 """
|
||||||
|
res = {
|
||||||
|
'msg': msg,
|
||||||
|
'code': 0,
|
||||||
|
'data': data,
|
||||||
|
'count': count,
|
||||||
|
'limit': limit
|
||||||
|
|
||||||
|
}
|
||||||
|
return jsonify(res)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
from applications.common.utils.http import success_api, fail_api, table_api
|
||||||
|
|
||||||
|
@admin_log.get('/operateLog')
|
||||||
|
@authorize("admin:log:main")
|
||||||
|
def operate_log():
|
||||||
|
# orm查询
|
||||||
|
# 使用分页获取data需要.items
|
||||||
|
log = AdminLog.query.filter(
|
||||||
|
AdminLog.url != '/passport/login').order_by(
|
||||||
|
desc(AdminLog.create_time)).layui_paginate()
|
||||||
|
count = log.total
|
||||||
|
return table_api(data=model_to_dicts(schema=LogOutSchema, data=log.items), count=count)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
from applications.common.utils.http import success_api, fail_api, table_api
|
||||||
|
|
||||||
|
@admin_power.post('/save')
|
||||||
|
@authorize("admin:power:add", log=True)
|
||||||
|
def save():
|
||||||
|
... # 若干操作
|
||||||
|
if success:
|
||||||
|
return success_api(msg="成功")
|
||||||
|
return fail_api(msg="成功")
|
||||||
|
```
|
||||||
@@ -1,2 +0,0 @@
|
|||||||

|
|
||||||
微信群不定期在qq群更新二维码
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
### 环境要求 :id=install
|
||||||
|
- Python >= 3.6
|
||||||
|
- Mysql >= 5.7.0
|
||||||
|
|
||||||
|
### 安装配置
|
||||||
|
|
||||||
|
#### 克隆远程仓库
|
||||||
|
|
||||||
|
您可以使用 git 来克隆远程仓库:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 进入项目主目录
|
||||||
|
cd Pear Admin Flask
|
||||||
|
|
||||||
|
# 使用 git 克隆远程仓库
|
||||||
|
git clone https://gitee.com/pear-admin/pear-admin-flask.git
|
||||||
|
|
||||||
|
# 切换分支
|
||||||
|
git checkout master # master, main or mini
|
||||||
|
```
|
||||||
|
|
||||||
|
或者直接前往 Pear Admin Flask 项目的[Gitee 主页](https://gitee.com/pear-admin/pear-admin-flask)下载项目仓库。
|
||||||
|
|
||||||
|
#### 搭建开发环境
|
||||||
|
|
||||||
|
我们推荐使用 Python 的虚拟环境来开发该项目,这样便于项目的迁移与二次开发。当然,您也可以选择使用原 Python 环境。
|
||||||
|
|
||||||
|
如果你想创建 Python 虚拟环境,你可以使用下面的命令行:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 在当前目录的venv文件夹创建虚拟环境
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# Windows 激活虚拟环境
|
||||||
|
.\test_env\Scripts\Activate.ps1
|
||||||
|
|
||||||
|
# Linux 和 Mac 激活虚拟环境
|
||||||
|
source ./test_env/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果在创建虚拟环境时报错 “ModuleNotFoundError” ,这说明您的 Python 版本小于 3.3 。**
|
||||||
|
|
||||||
|
#### 安装项目依赖
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 使用 pip 安装必要模块(对于 master 分支)
|
||||||
|
pip install -r requirement\requirement.txt
|
||||||
|
|
||||||
|
# 使用 pip 安装必要模块(对于 mini 分支)
|
||||||
|
pip install -r requirement.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
或者您可以尝试:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 使用 pip 安装必要模块
|
||||||
|
python -m pip install -r requirement.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 配置数据库
|
||||||
|
|
||||||
|
在 `applications/config.py` 中配置好数据库。
|
||||||
|
|
||||||
|
> 由于结构与目录调整,我们简化了项目结构,废弃了文件 ```.flaskenv``` ,请使用 config.py 配置程序。
|
||||||
|
|
||||||
|
并使用以下命令生成数据库文件:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 初始化数据库
|
||||||
|
flask db init
|
||||||
|
flask db migrate
|
||||||
|
flask db upgrade
|
||||||
|
flask admin init
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 运行项目
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python app.py
|
||||||
|
|
||||||
|
# 或者可以使用
|
||||||
|
flask run
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用docker-compose运行项目
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 安装docker-compose
|
||||||
|
curl -L https://github.com/docker/compose/releases/download/1.26.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
|
||||||
|
chmod +x /usr/local/bin/docker-compose
|
||||||
|
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
|
||||||
|
|
||||||
|
docker-compose --version
|
||||||
|
docker-compose up -d # -d后台运行
|
||||||
|
docker-compose stop # 停止启动
|
||||||
|
docker-compose down # 清除容器
|
||||||
|
|
||||||
|
dockerdata/config.py # 配置文件
|
||||||
|
dockerdata/mysql/initdb/ # MySQL初始化数据在
|
||||||
|
rm -rf dockerdata/mysql/{log,data}/* # down掉容器后启动需要清除删除log,dat
|
||||||
|
```
|
||||||
|
|
||||||
|
### 二次开发
|
||||||
|
|
||||||
|
恭喜!现在您已经成功搭建并运行了 Pear Admin Flask ,是时候参与开发了:
|
||||||
|
|
||||||
|
请阅读:
|
||||||
|
|
||||||
|
+ Pear Admin Flask [目录结构](list.md) 章节
|
||||||
|
+ Pear Admin Flask [开发函数](function.md) 章节
|
||||||
|
+ Pear Admin Flask [插件开发](plugin.md) 章节
|
||||||
|
|
||||||
|
其它章节等待更新。
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
## 应用结构 :id=config
|
||||||
|
```应用结构
|
||||||
|
Pear Admin Flask
|
||||||
|
├─applications # 应用
|
||||||
|
│ ├─configs # 配置文件
|
||||||
|
│ │ ├─ common.py # 普通配置
|
||||||
|
│ │ └─ config.py # 配置文件对象
|
||||||
|
│ ├─extensions # 注册插件
|
||||||
|
│ ├─models # 数据模型
|
||||||
|
│ ├─static # 静态资源文件
|
||||||
|
│ ├─templates # 静态模板文件
|
||||||
|
│ └─views # 视图部分
|
||||||
|
│ ├─admin # 后台管理视图模块
|
||||||
|
│ └─index # 前台视图模块
|
||||||
|
├─docs # 文档说明
|
||||||
|
├─migrations # 迁移文件记录
|
||||||
|
├─requirement # 依赖文件
|
||||||
|
└─.env # 项目的配置文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 资源结构 :id=static
|
||||||
|
```资源结构
|
||||||
|
Pear Admin Flask
|
||||||
|
├─static # 项目设定的 Flask 资源文件夹
|
||||||
|
│ ├─admin # pear admin flask 的后端资源文件(与 pear admin layui 同步)
|
||||||
|
│ ├─index # pear admin flask 的前端资源文件
|
||||||
|
│ └─upload # 用户上传保存目录
|
||||||
|
└─templates # 项目设定的 Flask 模板文件夹
|
||||||
|
├─admin # pear admin flask 的后端管理页面模板
|
||||||
|
│ ├─admin_log # 日志页面
|
||||||
|
│ ├─common # 基本模板页面(头部模板与页脚模板)
|
||||||
|
│ ├─console # 系统监控页面模板
|
||||||
|
│ ├─dept # 部门管理页面模板
|
||||||
|
│ ├─dict # 数据自动页面模板
|
||||||
|
│ ├─mail # 邮件管理页面模板
|
||||||
|
│ ├─photo # 图片上传页面模板
|
||||||
|
│ ├─power # 权限(菜单)管理页面模板
|
||||||
|
│ ├─role # 角色管理页面模板
|
||||||
|
│ ├─task # 任务设置页面模板
|
||||||
|
│ └─user # 用户管理页面模板
|
||||||
|
├─errors # 错误页面模板
|
||||||
|
└─index # 主页模板
|
||||||
|
```
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
### 说明
|
||||||
|
|
||||||
|
插件功能旨在最大限度不修改原框架的前提下添加新功能,我们提供了三个示例插件。
|
||||||
|
|
||||||
|
|
||||||
|
### 插件配置
|
||||||
|
|
||||||
|
将插件文件夹放置在 ```applications/config.py``` 文件夹中,并且在 .flaskenv 中配置。再配置项中填入插件的文件夹名,已 json 格式写入其中。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 插件配置
|
||||||
|
PLUGIN_ENABLE_FOLDERS = ["helloworld"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件目录
|
||||||
|
|
||||||
|
```
|
||||||
|
Plugin
|
||||||
|
│ __init__.json
|
||||||
|
└─ __init__.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这是一个非常简单的插件。
|
||||||
|
|
||||||
|
### 插件信息
|
||||||
|
|
||||||
|
插件信息保存在 ```__init__.py``` 中,以测试插件“helloword”为例。插件的数据应该不少于下面三项:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin_name": "Hello World",
|
||||||
|
"plugin_version": "1.0.0.1",
|
||||||
|
"plugin_description": "一个测试的插件。"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件格式
|
||||||
|
|
||||||
|
插件的入口点为 ```__init__.py``` 文件,在插件被启用后,程序启动时此 Python 文件中的 ```event_init``` 函数。代码如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
def event_init(app: Flask):
|
||||||
|
"""初始化完成时会调用这里"""
|
||||||
|
print("加载完毕后,我会输出一句话。")
|
||||||
|
```
|
||||||
@@ -10,14 +10,6 @@ from .main import helloworld_blueprint
|
|||||||
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
||||||
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
||||||
|
|
||||||
def event_enable():
|
|
||||||
"""当此插件被启用时会调用此处"""
|
|
||||||
print(f"启用插件,dir_path: {dir_path} ; folder_name: {folder_name}")
|
|
||||||
|
|
||||||
def event_disable():
|
|
||||||
"""当此插件被禁用时会调用此处"""
|
|
||||||
print(f"禁用插件,dir_path: {dir_path} ; folder_name: {folder_name}")
|
|
||||||
|
|
||||||
def event_init(app: Flask):
|
def event_init(app: Flask):
|
||||||
"""初始化完成时会调用这里"""
|
"""初始化完成时会调用这里"""
|
||||||
app.register_blueprint(helloworld_blueprint)
|
app.register_blueprint(helloworld_blueprint)
|
||||||
@@ -10,15 +10,6 @@ from . import console
|
|||||||
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
||||||
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
||||||
|
|
||||||
def event_enable():
|
|
||||||
"""当此插件被启用时会调用此处"""
|
|
||||||
print(f"启用插件,dir_path: {dir_path} ; folder_name: {folder_name}")
|
|
||||||
|
|
||||||
|
|
||||||
def event_disable():
|
|
||||||
"""当此插件被禁用时会调用此处"""
|
|
||||||
print(f"禁用插件,dir_path: {dir_path} ; folder_name: {folder_name}")
|
|
||||||
|
|
||||||
def event_init(app: Flask):
|
def event_init(app: Flask):
|
||||||
"""初始化完成时会调用这里"""
|
"""初始化完成时会调用这里"""
|
||||||
# 移除原有的输出日志
|
# 移除原有的输出日志
|
||||||
|
|||||||
@@ -8,15 +8,6 @@ from flask import Flask, render_template_string
|
|||||||
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
dir_path = os.path.dirname(__file__).replace("\\", "/")
|
||||||
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
folder_name = dir_path[dir_path.rfind("/") + 1:] # 插件文件夹名称
|
||||||
|
|
||||||
def event_enable():
|
|
||||||
"""当此插件被启用时会调用此处"""
|
|
||||||
print(f"启用插件,dir_path: {dir_path} ; folder_name: {folder_name}")
|
|
||||||
|
|
||||||
|
|
||||||
def event_disable():
|
|
||||||
"""当此插件被禁用时会调用此处"""
|
|
||||||
print(f"禁用插件,dir_path: {dir_path} ; folder_name: {folder_name}")
|
|
||||||
|
|
||||||
def event_init(app: Flask):
|
def event_init(app: Flask):
|
||||||
"""初始化完成时会调用这里"""
|
"""初始化完成时会调用这里"""
|
||||||
# 使用下面的代码 查看所有注册的视图函数。对于 Flask app.route 函数的实现,请参考 https://www.jianshu.com/p/dff3bc2f4836
|
# 使用下面的代码 查看所有注册的视图函数。对于 Flask app.route 函数的实现,请参考 https://www.jianshu.com/p/dff3bc2f4836
|
||||||
|
|||||||
@@ -1,288 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>插件管理</title>
|
|
||||||
{% include 'admin/common/header.html' %}
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='/admin/admin/css/other/user.css') }}"/>
|
|
||||||
</head>
|
|
||||||
<body class="pear-container">
|
|
||||||
{# 查询表单 #}
|
|
||||||
<div class="layui-card">
|
|
||||||
<div class="layui-card-body">
|
|
||||||
<form class="layui-form" action="" lay-filter="plugin-query-form">
|
|
||||||
<div class="layui-form-item">
|
|
||||||
<label class="layui-form-label">插件名称</label>
|
|
||||||
<div class="layui-input-inline">
|
|
||||||
<input type="text" name="plugin_name" placeholder="" class="layui-input">
|
|
||||||
</div>
|
|
||||||
<button class="pear-btn pear-btn-md pear-btn-primary" lay-submit lay-filter="plugin-query">
|
|
||||||
<i class="layui-icon layui-icon-search"></i>
|
|
||||||
查询
|
|
||||||
</button>
|
|
||||||
<button type="reset" class="pear-btn pear-btn-md">
|
|
||||||
<i class="layui-icon layui-icon-refresh"></i>
|
|
||||||
重置
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{# 用户表格 #}
|
|
||||||
<div class="user-plugin user-collasped">
|
|
||||||
<div class="layui-card">
|
|
||||||
<div class="layui-card-body">
|
|
||||||
<table id="plugin-table" lay-filter="plugin-table"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
{# 表格操作 #}
|
|
||||||
<script type="text/html" id="plugin-toolbar">
|
|
||||||
{% if authorize("admin:plugin:enable") %}
|
|
||||||
{# <button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add">#}
|
|
||||||
{# <i class="pear-icon pear-icon-add"></i>#}
|
|
||||||
{# 新增#}
|
|
||||||
{# </button>#}
|
|
||||||
{% endif %}
|
|
||||||
{% if authorize("admin:plugin:remove") %}
|
|
||||||
{# <button class="pear-btn pear-btn-md" lay-event="batchRemove">#}
|
|
||||||
{# <i class="pear-icon pear-icon-ashbin"></i>#}
|
|
||||||
{# 删除#}
|
|
||||||
{# </button>#}
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{# 用户修改操作 #}
|
|
||||||
<script type="text/html" id="plugin-bar">
|
|
||||||
{% if authorize("admin:plugin:remove") %}
|
|
||||||
<button class="pear-btn pear-btn-danger pear-btn-sm" lay-event="remove"><i
|
|
||||||
class="pear-icon pear-icon-ashbin"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{# 启动与禁用 #}
|
|
||||||
<script type="text/html" id="plugin-enable">
|
|
||||||
<input type="checkbox" name="enable" value="{{ "{{ d.plugin_folder_name }}" }}" lay-skin="switch"
|
|
||||||
lay-text="启用|禁用"
|
|
||||||
lay-filter="plugin-enable"
|
|
||||||
{{ "{{# if(d.enable==1){ }} checked {{# } }}" }} />
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{# 用户注册时间 #}
|
|
||||||
<script type="text/html" id="plugin-createTime">
|
|
||||||
{{ ' {{layui.util.toDateString(d.create_at, "yyyy-MM-dd HH:mm:ss")}' |safe }}}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% include 'admin/common/footer.html' %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// 重启程序
|
|
||||||
function restart() {
|
|
||||||
layui.use(['layer'], function () {
|
|
||||||
var $ = layui.jquery;
|
|
||||||
let popup = layui.popup;
|
|
||||||
layer.confirm('需要重启程序吗?这样可以使插件更改生效。(如果没有守护进程需要手动启动。)', {
|
|
||||||
icon: 3,
|
|
||||||
title: '提示'
|
|
||||||
}, function (index) {
|
|
||||||
layer.close(index)
|
|
||||||
let loading = layer.load()
|
|
||||||
$.ajax({
|
|
||||||
url: '/admin/monitor/kill',
|
|
||||||
dataType: 'json',
|
|
||||||
type: 'get',
|
|
||||||
error: function (result) {
|
|
||||||
setInterval(function () {
|
|
||||||
tip = false
|
|
||||||
$.ajax({
|
|
||||||
url: '/admin/monitor',
|
|
||||||
type: 'get',
|
|
||||||
success: function (result) {
|
|
||||||
layer.close(loading)
|
|
||||||
if (tip == false) {
|
|
||||||
popup.success("重启程序成功!", function () {
|
|
||||||
window.location.reload()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
layui.use(['table', 'form', 'jquery', 'popup', 'common'], function () {
|
|
||||||
let table = layui.table
|
|
||||||
let form = layui.form
|
|
||||||
let $ = layui.jquery
|
|
||||||
let dtree = layui.dtree
|
|
||||||
let popup = layui.popup
|
|
||||||
let common = layui.common
|
|
||||||
let MODULE_PATH = '/plugin/'
|
|
||||||
|
|
||||||
// 表格数据
|
|
||||||
let cols = [
|
|
||||||
[
|
|
||||||
{% if authorize("admin:plugin:remove") %}
|
|
||||||
{type: 'checkbox'},
|
|
||||||
{% endif %}
|
|
||||||
{field: 'plugin_name', minWidth: 100, title: '插件名称'},
|
|
||||||
{field: 'plugin_version', title: '插件版本'},
|
|
||||||
{field: 'plugin_description', title: '插件描述'},
|
|
||||||
{field: 'plugin_folder_name', hide: true},
|
|
||||||
{field: 'enable', title: '状态', templet: '#plugin-enable', width: 100, align: 'center'},
|
|
||||||
{title: '操作', templet: '#plugin-bar', width: 120, align: 'center'}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
// 渲染表格数据
|
|
||||||
table.render({
|
|
||||||
elem: '#plugin-table',
|
|
||||||
url: MODULE_PATH + 'data',
|
|
||||||
page: true,
|
|
||||||
cols: cols,
|
|
||||||
skin: 'line',
|
|
||||||
height: 'full-148',
|
|
||||||
toolbar: '#plugin-toolbar', /*工具栏*/
|
|
||||||
text: {none: '暂无人员信息'},
|
|
||||||
defaultToolbar: [{layEvent: 'refresh', icon: 'layui-icon-refresh'}, 'filter', 'print', 'exports'] /*默认工具栏*/
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// 禁用启用
|
|
||||||
form.on('switch(plugin-enable)', function (obj) {
|
|
||||||
let operate
|
|
||||||
if (obj.elem.checked) {
|
|
||||||
operate = 'enable'
|
|
||||||
} else {
|
|
||||||
operate = 'disable'
|
|
||||||
}
|
|
||||||
let loading = layer.load()
|
|
||||||
$.ajax({
|
|
||||||
url: MODULE_PATH + operate,
|
|
||||||
data: JSON.stringify({plugin_folder_name: this.value}),
|
|
||||||
dataType: 'json',
|
|
||||||
contentType: 'application/json',
|
|
||||||
type: 'put',
|
|
||||||
success: function (result) {
|
|
||||||
layer.close(loading)
|
|
||||||
if (result.success) {
|
|
||||||
popup.success(result.msg)
|
|
||||||
restart()
|
|
||||||
} else {
|
|
||||||
popup.failure(result.msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
table.on('tool(plugin-table)', function (obj) {
|
|
||||||
if (obj.event === 'remove') {
|
|
||||||
window.remove(obj)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
table.on('toolbar(plugin-table)', function (obj) {
|
|
||||||
if (obj.event === 'add') {
|
|
||||||
window.add()
|
|
||||||
} else if (obj.event === 'refresh') {
|
|
||||||
window.refresh()
|
|
||||||
} else if (obj.event === 'batchRemove') {
|
|
||||||
window.batchRemove(obj)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
form.on('submit(plugin-query)', function (data) {
|
|
||||||
window.refresh(data.field)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
window.add = function () {
|
|
||||||
layer.open({
|
|
||||||
type: 2,
|
|
||||||
title: '新增',
|
|
||||||
shade: 0.1,
|
|
||||||
area: ['550px', '550px'],
|
|
||||||
content: MODULE_PATH + 'add'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
window.remove = function (obj) {
|
|
||||||
layer.confirm('确定要删除', {icon: 3, title: '提示'}, function (index) {
|
|
||||||
layer.close(index)
|
|
||||||
let loading = layer.load()
|
|
||||||
$.ajax({
|
|
||||||
url: MODULE_PATH + 'remove/' + obj.data['plugin_folder_name'],
|
|
||||||
dataType: 'json',
|
|
||||||
type: 'delete',
|
|
||||||
success: function (result) {
|
|
||||||
layer.close(loading)
|
|
||||||
if (result.success) {
|
|
||||||
popup.success(result.msg, function () {
|
|
||||||
obj.del()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
popup.failure(result.msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
window.batchRemove = function (obj) {
|
|
||||||
let data = table.checkStatus(obj.config.id).data
|
|
||||||
if (data.length === 0) {
|
|
||||||
layer.msg('未选中数据', {
|
|
||||||
icon: 3,
|
|
||||||
time: 1000
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var ids = []
|
|
||||||
var hasCheck = table.checkStatus('plugin-table')
|
|
||||||
var hasCheckData = hasCheck.data
|
|
||||||
if (hasCheckData.length > 0) {
|
|
||||||
$.each(hasCheckData, function (index, element) {
|
|
||||||
ids.push(element.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{#console.log(ids);#}
|
|
||||||
layer.confirm('确定要删除选中数据', {
|
|
||||||
icon: 3,
|
|
||||||
title: '提示'
|
|
||||||
}, function (index) {
|
|
||||||
layer.close(index)
|
|
||||||
let loading = layer.load()
|
|
||||||
$.ajax({
|
|
||||||
|
|
||||||
url: MODULE_PATH + 'batchRemove',
|
|
||||||
data: {ids: ids},
|
|
||||||
dataType: 'json',
|
|
||||||
type: 'delete',
|
|
||||||
success: function (result) {
|
|
||||||
layer.close(loading)
|
|
||||||
if (result.success) {
|
|
||||||
popup.success(result.msg, function () {
|
|
||||||
table.reload('plugin-table')
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
popup.failure(result.msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
window.refresh = function (param) {
|
|
||||||
table.reload('plugin-table', {where: param})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||