diff --git a/README.md b/README.md index ec8c69cc4c65455926239af4638633229e11e2c4..7954902c470866c8f8a115153e1b5dfa941b4b41 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 开 箱 即 用 的 Flask 快 速 开 发 平 台 - [预 览](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)
@@ -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   # 主页模板
 ```
 
 #### 项目安装
diff --git a/applications/common/script/admin.py b/applications/common/script/admin.py
index fec84ab203e565335bad8cc287fdeb4d93c24eac..b49920dc363077c7b9e9d3db6efcd8654def9ea8 100644
--- a/applications/common/script/admin.py
+++ b/applications/common/script/admin.py
@@ -602,58 +602,6 @@ powerdata = [
         create_time=now_time,
         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,
-
     )
 
 ]
diff --git a/applications/config.py b/applications/config.py
index 64959863dbb7639e7d886b752edd5124301374cc..15b278149117190f55bac46f229ab3708a00020e 100644
--- a/applications/config.py
+++ b/applications/config.py
@@ -80,8 +80,8 @@ class BaseConfig:
         'max_instances': 3
     }
 
-    # 插件配置
-    PLUGIN_ENABLE_FOLDERS = ["helloworld"]
+    # 插件配置,填写插件的文件名名称,默认不启用插件。
+    PLUGIN_ENABLE_FOLDERS = []
 
     # 配置多个数据库连接的连接串写法示例
     # HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称
diff --git a/applications/view/__init__.py b/applications/view/__init__.py
index ef00903e369c0c1ac26ac2ce7651505f0c095eed..6b5cf78b6fe07f81df34aeba4829e5a1639a64d5 100644
--- a/applications/view/__init__.py
+++ b/applications/view/__init__.py
@@ -11,4 +11,4 @@ def init_view(app):
     register_rights_view(app)
     register_passport_views(app)
     register_dept_views(app)
-    # register_plugin_views(app)
+    register_plugin_views(app)
diff --git a/applications/view/plugin/__init__.py b/applications/view/plugin/__init__.py
index 4eb6c34d8dd8729e4fee5c9c341835cfa5c7ebeb..ec94ef5a48701b2da9cd308828d402de98b6dc36 100644
--- a/applications/view/plugin/__init__.py
+++ b/applications/view/plugin/__init__.py
@@ -18,7 +18,7 @@ def register_plugin_views(app: Flask):
     app.register_blueprint(plugin_bp)
     # 载入插件过程
     # 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:
         plugin_info = {}
         try:
@@ -39,124 +39,3 @@ def register_plugin_views(app: Flask):
             info += 'repr(e):\t' + repr(e) + "\n"
             info += 'traceback.format_exc():\n%s' + traceback.format_exc()
             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/Hello
")
+```
+
++ 基于二次开发的邮件发送函数
+
+### 函数原型
+
+函数调用位于项目代码 ```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", "Hello
", 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="成功")    
+```
diff --git a/docs/group.md b/docs/group.md
deleted file mode 100644
index 0da9fe9cb001905e4914ba30ba1bb095b0171858..0000000000000000000000000000000000000000
--- a/docs/group.md
+++ /dev/null
@@ -1,2 +0,0 @@
-
-微信群不定期在qq群更新二维码
\ No newline at end of file
diff --git a/docs/install.md b/docs/install.md
new file mode 100644
index 0000000000000000000000000000000000000000..7b54e72a0a8f1f17f325d7c741f502c78dd98258
--- /dev/null
+++ b/docs/install.md
@@ -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) 章节
+
+其它章节等待更新。
diff --git a/docs/list.md b/docs/list.md
new file mode 100644
index 0000000000000000000000000000000000000000..891fa730731909584abb523fc463fda1268bf095
--- /dev/null
+++ b/docs/list.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   # 主页模板
+```
\ No newline at end of file
diff --git a/docs/plugin.md b/docs/plugin.md
new file mode 100644
index 0000000000000000000000000000000000000000..9ccb1d479995e76f965e27e20ed4a1c1402432e6
--- /dev/null
+++ b/docs/plugin.md
@@ -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("加载完毕后,我会输出一句话。")
+```
\ No newline at end of file
diff --git a/plugins/helloworld/__init__.py b/plugins/helloworld/__init__.py
index efbe8aef6ebd3855f422ec6d0b3935131e3f3173..d6b218ea575edceb9d88aa2ee42363cfb2940455 100644
--- a/plugins/helloworld/__init__.py
+++ b/plugins/helloworld/__init__.py
@@ -10,14 +10,6 @@ from .main import helloworld_blueprint
 dir_path = os.path.dirname(__file__).replace("\\", "/")
 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):
     """初始化完成时会调用这里"""
     app.register_blueprint(helloworld_blueprint)
\ No newline at end of file
diff --git a/plugins/realip/__init__.py b/plugins/realip/__init__.py
index f6971fd981b0dc6f1a2cd8ad46a25bf09da1caf3..50e18b4fdaa71c5d11466c1c97c6bb59c1cfa35f 100644
--- a/plugins/realip/__init__.py
+++ b/plugins/realip/__init__.py
@@ -10,15 +10,6 @@ from . import console
 dir_path = os.path.dirname(__file__).replace("\\", "/")
 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):
     """初始化完成时会调用这里"""
     # 移除原有的输出日志
diff --git a/plugins/replacePage/__init__.py b/plugins/replacePage/__init__.py
index 3756f0a66c66b50c9d6dfc0276bace98854bfdba..6e4cd7e40952ddd4c6610b6ac28b33ba36d1ce0a 100644
--- a/plugins/replacePage/__init__.py
+++ b/plugins/replacePage/__init__.py
@@ -8,15 +8,6 @@ from flask import Flask, render_template_string
 dir_path = os.path.dirname(__file__).replace("\\", "/")
 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):
     """初始化完成时会调用这里"""
     # 使用下面的代码 查看所有注册的视图函数。对于 Flask app.route 函数的实现,请参考 https://www.jianshu.com/p/dff3bc2f4836
diff --git a/templates/admin/plugin/main.html b/templates/admin/plugin/main.html
deleted file mode 100644
index b0e4f181ff2731fb97f0a58851f571e121676162..0000000000000000000000000000000000000000
--- a/templates/admin/plugin/main.html
+++ /dev/null
@@ -1,288 +0,0 @@
-
-
-
-    
-