产品介绍

​ Admin中后台是一款基于前后端分离的WEB自适应框架,旨在为开发者提供高效、灵活、简洁的中后台开发解决方案。它主要面向企业级应用场景,提供丰富的组件、模板和工具,帮助开发者快速构建出美观、易用、高可靠性的中后台系统。

技术框架

​ Admin中后台框架采用了现代前端开发技术,基于Node.js、Vite、Vue3、Element-Plus,同时也支持多种后端语言和框架。它具有模块化、易扩展等特点,使得开发者可以根据自己的需求进行定制化开发。

在线演示

​ 演示网址:admin.nodcloud.comopen in new window

依赖项

名称描述
vue核心
vue-router页面路由
pinia状态管理
pinia-plugin-persistedstate状态管理持久化
element-plus组件库
@element-plus/icons-vue图标库
axios网络请求
echarts数据可视化
js-base64base64编解码
mitt事务总线
moment时间库
nprogress进度条
qrcode二维码
qsurl库
sortablejs表格拖动库
@icon-park/vue-next图标库
@pansy/watermark水印库
@wangeditor/editor富文本编辑器
@wangeditor/editor-for-vue富文本扩展
vite开发构建工具
vite-plugin-pwaPwa库
@vitejs/plugin-vueVite插件
autoprefixer自动前缀
postcss-color-mix颜色混合
postcss-preset-env样式特性
unplugin-auto-import自动加载
unplugin-element-plus自动加载
unplugin-vue-components自动加载

目录结构

根目录
├─public                         应用目录
│  ├─api			            接口数据
│  ├─static                      静态资源
│  │  ├─img                      图片资源
│  │  │  ├─explorer              文件图标
│  │  │  ├─logo                  品牌图标
│  │  │  ├─pwa                   PWA图标
│  │  │  ├─user                  用户头像
│  ├─favicon.ico                 应用图标
├─src                            主要入口
│  ├─assets                      静态资源
│  │  ├─css                      公用样式
│  │  │  ├─element.css           组件样式
│  │  │  ├─style.css             通用样式
│  │  │  ├─theme.css             主题样式
│  │  ├─js                       公用函数
│  │  │  ├─fun.js                常用函数
│  │  │  ├─helper.js             助手函数
│  ├─components                  公用组件
│  │  ├─Attachment.vue           文件上传
│  │  ├─Column.vue               表格配置
│  │  ├─ContextMenu.vue          右键菜单
│  │  ├─Editor.vue               富文本编辑器
│  │  ├─Elect.vue                下拉菜单
│  │  ├─Explorer.vue             资源管理器
│  │  ├─Icons.vue             	 图标选择器
│  │  ├─Media.vue                文件上传
│  │  ├─Menu.vue                 菜单组件
│  │  ├─Operate.vue              区域头部
│  │  ├─Screen.vue               搜索表单
│  │  ├─Search.vue               搜索下拉
│  │  ├─Sheet.vue                公用表格
│  ├─config                      类库配置
│  │  ├─axios.js                 请求实例
│  │  ├─bus.js                   事务总线
│  │  ├─directive.js             扩展指令
│  │  ├─icon.js                  图标库
│  │  ├─icons.js                 图标库
│  │  ├─ins.js                   应用实例
│  │  ├─layer.js                 遮罩实例
│  │  ├─moment.js                时间实例
│  │  ├─report.js                报表助手
│  │  ├─router.js                路由实例
│  │  ├─store.js                 状态管理
│  │  ├─style.js                 样式引入
│  │  ├─url.js                   接口配置
│  │  ├─vnode.js                 DOM节点
│  │  ├─watermark.js             水印实例
│  ├─page                        应用页面
│  │  ├─alone                    独立组
│  │  │  ├─Browser.vue           内置浏览
│  │  │  ├─Error.vue             错误页面
│  │  │  ├─Login.vue             登录页面
│  │  ├─config                   配置组
│  │  │  ├─explorer              资源管理
│  │  │  ├─menu                  菜单管理
│  │  │  ├─report                数据报表
│  │  ├─console                  控制组
│  │  │  ├─Console.vue           首页框架
│  │  │  ├─Home.vue              应用主页
│  │  ├─system                   系统组
│  │  │  ├─notification          消息通知
│  │  │  ├─role                  用户角色
│  │  │  ├─sys                   系统设置
│  │  │  ├─user                  用户管理
│  ├─plugins                     应用插件
│  │  ├─load.js                  按需加载
│  │  ├─pwa.js                   PWA实例
│  │  ├─script.js                组件名称
│  ├─App.vue                     应用入口
│  ├─main.js                     应用实例
├─.gitignore                     Git配置
├─.prettierrc                    格式标准
├─index.html                     应用入口
├─package.json                   应用配置
├─postcss.config                 CSS配置
├─vite.config.js                 Vite配置

产品安装

  1. 下载并安装Node.jsopen in new window

  2. 配置npm镜像源

    • 阿里源
    npm config set registry https://registry.npmmirror.com
    
    • 腾讯源
    npm config set registry http://mirrors.cloud.tencent.com/npm/
    
  3. 安装项目依赖

npm install
  1. 运行项目
npm run dev
  1. 访问 http://localhost:3000open in new window

产品部署

  1. 应用打包

    npm run build
    
  2. 将dist目录文件部署即可

数据接口

​ 产品基于前后端分离设计,数据交互采用API方式,交互格式为JSON,演示版采用静态JSON作为模拟数据接口,您需在实际开发过程中依据如下标准配置后端数据接口

  • 配置基础数据接口 src/config/url.js

    app.config.globalProperties.$api = 'https://api.nodcloud.com/';
    
  • 基于Axios二次封装作为网络请求库 src/config/axios.js

    //GET请求
    proxy.$axios.get('login/base', { params: { id: item } }).then((result) => {
        if (result.state == 'success') {
            //请求成功
        } else {
            //请求失败
        }
    };
    //POST请求
    proxy.$axios.post('login/handle.json', {user:'admin',pwd:'123456'}).then((result) => {
        if (result.state == 'success') {
            //请求成功
        } else {
            //请求失败
        }
    });
    //DELETE请求
    proxy.$axios.delete('user/destroy', { data: { id:1 } }).then((result) => {
        if (result.state == 'success') {
            //请求成功
        } else {
            //请求失败
        }
    });
    
  • 如需请求过程中附加遮罩层,可在Axios调用中传入layer为true的配置信息 src/config/layer.js

    proxy.$axios.get('login/base', { params,layer: true });
    
  • 请求会自动获取Cookie并传输,同时支持头部自动携带Bearer Token

    //登录成功设置Token src/page/alone/Login.vue > login
    localStorage.setItem('token', result.info);
    //请求自动附加Token src/config/axios.js > config
    let token = localStorage.getItem('token');
    token && (config.headers.Authorization = 'Bearer ' + token);
    
  • 当后端返回401状态时,认定为登录凭证失效

    //可根据实际业务需求改写逻辑 src/config/axios.js > config
    if (error.response && error.response.status) {
        let status = error.response.status;
        if (status == 401) {
            ElMessage({
                type: 'warning',
                message: '登录凭证失效'
            });
            setTimeout(() => {
                localStorage.removeItem('token');
                window.location.href = '/';
            }, 3000);
        } else {
            ElMessage({
                type: 'error',
                message: '服务器响应错误'
            });
        }
    }
    
  • 为确保接口数据的一致性,建议采用统一的接口返回格式

    //成功
    {
        "state":"success",
        "info":"1|EsaXFTa3c65IYj2r9BCtt2OUi10t5GQrFi7bT2Tr"
    }
    //警告
    {
        "state":"warning",
        "message":"账号密码错误"
    }
    //错误
    {
        "state":"error",
        "message":"内部错误"
    }
    

公用函数

​ 公用函数库内置了多种常用方法,同时您可根据实际业务需求扩展该函数库

  • 调用方法

    proxy.$fun.fn();
    
  • 内置函数

    名称描述
    ustr随机字符串
    inSuffix后缀名匹配
    qrcode生成二维码
    url打开链接
    getBearer获取令牌
    downBase64File下载编码文件
    downFile下载文件
    domParent获取上级节点
    validator验证数据
    querySort参数排序
    objToParm对象转查询
    emptyStr是否空字符串
    strFirstToUpperCase字符串首字母大写
    scopeRandom范围随机数
    md5md5加密
    flatten树结构扁平化
    orderBy数组排序
    isJson判断JSON
    emptyObj判断空对象
    equals变量对比
    deepClone深度克隆

内置组件

  • 文件上传 src/components/Attachment.vue

    该组件适用于表单多附件上传,可指定文件后缀

    属性名说明类型默认值
    modelValue数据对象Array-
    text文本内容String上传附件
    path资源路径String/
    suffix文件后缀Array-
  • 表格字段 src/components/Column.vue

    该组件适用于el-table组件的自定义字段顺序、字段宽度、显示隐藏

    属性名说明类型默认值
    modelValue数据对象Object-
    name唯一标识String-
  • 右键菜单 src/components/ContextMenu.vue

    该组件与el-dropdown组合可实现指定区域内触发右键菜单

    属性名说明类型默认值
    area触发区域String-
    target触发目标String-
  • 数据选择器 src/components/Elect.vue

    基于数据接口的多模式选择器,支持单选、多选、分页

    属性名说明类型默认值
    url数据接口String-
    modelValue数据对象String|Array-
    disabled是否禁用Booleanfalse
    multiple是否多选Booleanfalse
    placeholder占位文本String请选择数据
  • 富文本编辑器 src/components/Editor.vue

    基于WangEditor封装,图像与视频采用资源管理器

    属性名说明类型默认值
    modelValue数据对象String-
    path资源路径String/
    mode编辑模式String-
  • 资源管理器 src/components/Explorer.vue

    支持文件上传、多选、删除、预览、新建文件夹、名称修改、文件路径的综合资源管理器

    属性名说明类型默认值
    path默认路径String/
    to挂载对象Stringnull
    single选择模式Booleanfalse
    choice选择回调Functionnull
    close关闭回调Functionnull
  • 图标选择器 src/components/Icons.vue

    支持中英文搜索的图标选择器

    属性名说明类型默认值
    modelValue数据对象String-
  • 图像选择器 src/components/Media.vue

    基于资源管理器的图像选择器

    属性名说明类型默认值
    modelValue数据对象String-
    path资源路径String/
  • 菜单组件 src/components/Menu.vue

    适用于常规菜单、常规分类、组合菜单、扩展菜单、外部链接的菜单组件

    属性名说明类型默认值
    value数据对象Array-
  • 区域头部 src/components/Operate.vue

    适用于页面公用头部组件,基于菜单树结构的返回功能,支持插槽传入内容

    属性名说明类型默认值
    back是否返回Booleanfalse
    slotClass插槽类名Stringslot
  • 搜索表单 src/components/Screen.vue

    基于配置项实现的时间搜索、时间区间、节点多选、节点单选的表单组件

    属性名说明类型默认值
    modelValue数据对象Object-
    field字段配置Object-
  • 搜索下拉 src/components/Search.vue

    基于配置项实现的多字段多内容的数据组件

    属性名说明类型默认值
    modelValue数据对象Object-
    field字段配置Object-
    placeholder占位文字String综合搜索
  • 公用表格 src/components/Screen.vue

    通用el-table表格样式组件

    属性名说明类型默认值
    stripe斑马纹Booleanfalse
    border纵向边框Booleanfalse

类库实例

  • 网络请求 src/config/axios.js

    axios网络请求库,集成请求遮罩layer,请求自动携带cookie和token

    //请求方法
    proxy.$axios.get(url[, config]);
    proxy.$axios.delete(url[, config]);
    proxy.$axios.head(url[, config]);
    proxy.$axios.post(url[, data[, config]]);
    proxy.$axios.put(url[, data[, config]]);
    proxy.$axios.patch(url[, data[, config]]);
    
  • 事件总线 src/config/bus.js

    基于mitt的全局事件总线,支持跨页面事件订阅与触发

    //事件订阅
    proxy.$bus.on('event', () => {});
    //事件触发
    proxy.$bus.emit('event', parm);
    
  • 自定义指令 src/config/directive.js

    名称指令描述
    分页自适应v-pagination适用于el-pagination在页面尺寸改变情况下节点的显示隐藏
    表单多列自适应v-form-column适用于el-form表单el-form-item行内显示数量
    表单标签分布v-form-label-justify适用于el-form表单lable标签的显示位置
  • 图标实例 src/config/icon.js

    基于element-plus的全局vue图标组件 参考链接open in new window

    <!-- 加号图标 -->
    <el-icon>
        <Plus/>
    </el-icon>
    <!-- 减号图标 -->
    <el-icon>
        <Minus/>
    </el-icon>
    
  • 图标实例 src/config/icons.js

    基于iconPark的全局vue图标组件 参考链接open in new window

    <!-- 配置图标 -->
    <icon-park type="config" />
    <!-- 书签图标 -->
    <icon-park type="bookmark" />
    
  • 应用实例 src/config/ins.js

    适用于全局场景下的应用实例化获取

    import ins from '@/config/ins';
    let proxy = ins.app.config.globalProperties;
    
  • 遮罩层 src/config/layer.js

    基于el-loading的指令调用

    //显示遮罩
    proxy.$layer.show();
    //隐藏遮罩
    proxy.$layer.hide();
    
  • 时间实例 src/config/moment.js

    基于moment的全局时间实例

    //获取日期
    proxy.$moment().format('dddd');
    proxy.$moment().format('MMMM Do YYYY, h:mm:ss a');
    proxy.$moment().format('dddd');
    proxy.$moment().format("MMM Do YY");
    proxy.$moment().format('YYYY [escaped] YYYY');
    proxy.$moment().format();
    
  • 报表助手 src/config/report.js

    基于report的实例化与封装 参考链接open in new window

    //报表助手
    proxy.$report.helper.fun();
    //打印报表
    proxy.$report.scene.print(parm);
    //预览报表
    proxy.$report.scene.view(parm);
    //设计报表
    proxy.$report.scene.design(parm);
    //输出报表
    proxy.$report.scene.document(parm);
    
  • 页面路由 src/config/router.js

    基于vue-router的页面路由,支持动态加载与懒加载

  • 状态管理 src/config/store.js

    基于pinia的状态存储,通过pinia-plugin-persistedstate实现数据持久化

    //获取实例
    let store = proxy.$pinia.state.value.store;
    //深色模式
    store.theme.dark=true;
    
  • 样式引用 src/config/style.js

  • 接口配置 src/config/url.js

    //获取
    let api = proxy.$api;
    
  • 接口配置 src/config/vnode.js

    基于vnode的动态dialog、drawer渲染,支持slot和组件传递

    let vnode = proxy.$vnode;
    //渲染对话框
    vnode.dialog().render();
    //渲染抽屉
    vnode.drawer().render();
    //节点卸载
    vnode.unrender();
    //节点关闭
    vnode.close();
    
  • 页面水印 src/config/watermark.js

    基于watermark封装的全局页面水印

    //渲染水印
    proxy.$watermark.render(parm);
    //删除水印
    proxy.$watermark.destroy();
    
  • 自动加载 src/plugins/load.js

    组件自动加载,实现无需import引入直接使用

  • pwa实例 src/plugins/pwa.js

    基于vite-plugin-pwa实现的pwa实现

  • 组件名称 src/plugins/script.js

    扩展vue组合模式下简略组件命名

    <script setup name="Console">
    	//...
    </script>
    

菜单配置

  • 静态菜单

    可在route.js文件中按照vue-route文档配置 src/config/router.js

  • 动态菜单

    您可通过配置-菜单管理进行可视化菜单配置 src/page/menu

    名称字段说明
    所属菜单pid上级菜单
    菜单名称name菜单名称
    菜单标识key唯一标识(不可重复)
    菜单图标ico参考链接open in new window
    菜单路径path菜单路径
    组件路径componentsrc/page/ 下组件路径
    组件参数query默认参数
    菜单类型type常规菜单|常规分类|组合菜单|扩展菜单|外部链接
    菜单模式mold标签模式|页面模式
    菜单排序sort同级菜单排序
    权限标识auth权限标识
    备注信息data备注信息

    菜单类型

    名称父级子级描述
    常规菜单常规分类-常规场景下的菜单节点
    常规分类常规分类常规菜单|组合菜单|外部链接常规场景下的分类节点
    组合菜单常规分类扩展菜单与扩展菜单(子级)配合实现同行显示
    扩展菜单扩展菜单-与组合菜单(父级)配合实现同行显示
    外部链接常规分类-适用于URL访问

    菜单模式

    名称描述
    标签模式在tabs标签栏显示
    页面模式新窗口独立页面显示

    菜单数据

    应用在console组件加载数据并存储到store,数据应按照tree树结构传递,子节点键名为sub

    [
      {
        "id": 1,
        "pid": 0,
        "name": "首页",
        "key": "home",
        "ico": "home",
        "path": "/home",
        "component": "console/Home.vue",
        "query": "{}",
        "type": 0,
        "mold": 0,
        "sort": 0,
        "auth": "",
        "data": "",
        "sub": []
      },
      {
        "id": 2,
        "pid": 0,
        "name": "产品",
        "key": "produce",
        "ico": "application-one",
        "path": "",
        "component": "",
        "query": "{}",
        "type": 1,
        "mold": 0,
        "sort": 1,
        "auth": "",
        "data": "",
        "sub": [
          {
            "id": 3,
            "pid": 2,
            "name": "产品列表",
            "key": "product",
            "ico": "file-pdf",
            "path": "/product",
            "component": "produce/product/Record.vue",
            "query": "{}",
            "type": 3,
            "mold": 0,
            "sort": 1,
            "auth": "product",
            "data": "",
            "sub": []
          }
        ]
      }
    ]
    

    结构数据

    -- mysql
    CREATE TABLE `menus` (
      `id` int NOT NULL AUTO_INCREMENT,
      `pid` int NOT NULL COMMENT '所属菜单',
      `name` varchar(32) NOT NULL COMMENT '菜单名称',
      `key` varchar(32) NOT NULL COMMENT '菜单标识',
      `ico` varchar(32) DEFAULT '' COMMENT '菜单图标',
      `path` varchar(128) DEFAULT '' COMMENT '菜单路径',
      `component` varchar(128) DEFAULT '' COMMENT '组件路径',
      `query` varchar(256) DEFAULT '{}' COMMENT '组件参数',
      `type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '菜单类型[0:常规菜单|1:常规分类|2:组合菜单|3:扩展菜单|4:外部链接]',
      `mold` tinyint(1) NOT NULL DEFAULT '0' COMMENT '菜单模式[0:标签模式|1:页面模式]',
      `sort` int NOT NULL DEFAULT '0' COMMENT '菜单排序',
      `auth` varchar(32) DEFAULT '' COMMENT '权限标识',
      `data` varchar(256) DEFAULT '' COMMENT '备注信息',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='菜单管理';
    -- data
    INSERT INTO `menus` (`id`, `pid`, `name`, `key`, `ico`, `path`, `component`, `query`, `type`, `mold`, `sort`, `auth`, `data`) VALUES
    (1,	0,	'首页',	'home',	'home',	'/home',	'console/Home.vue',	'{}',	0,	0,	0,	'',	''),
    (2,	0,	'产品',	'produce',	'application-one',	'',	'',	'{}',	1,	0,	1,	'',	''),
    (3,	2,	'产品列表',	'product',	'file-pdf',	'/product',	'produce/product/Record.vue',	'{}',	3,	0,	1,	'product',	'')
    
  • 配置须知

    1. 静态菜单与动态菜单数据不可冲突
    2. 动态菜单需配合后端接口提供数据使用
    3. 动态菜单在console组件获取 src/page/console/Console.vue > onMounted
    4. 菜单显示采用el-menu与 src/components/Menu.vue 组合渲染
    5. 菜单可视化配置需按照示例数据构建后端接口

登录页面

​ 登录页面是应用的主要入口 src\page\alone\Login.vue

  • 表单字段

    名称字段名必填
    用户名user
    密码pwd
    验证码code
  • 登录接口

    //账户登录
    function login() {
        el.form.value.validate((valid) => {
            if (valid) {
                load.value = true;
                proxy.$axios.post('login/handle.json', form).then((result) => {
                    load.value = false;
                    if (result.state == 'success') {
                        //result.info返回值为用户的token令牌
                        localStorage.setItem('token', result.info);
                        //跳转控制台首页
                        proxy.$router.push({ path: '/console' });
                    } else {
                        ElMessage({
                            type: result.state,
                            message: result.message
                        });
                    }
                });
            }
        });
    }
    
  • 基础数据

    该代码块默认为注释状态,如需动态获取系统配置可启用该代码块

    //获取基础数据
    proxy.$axios.get('login/base', { layer: true }).then((result) => {
        if (result.state == 'success') {
            store.sys.base = result.info;
        } else {
            ElMessage({
                type: result.state,
                message: result.message
            });
        }
    });
    
  • 登录状态

    该代码块默认为注释状态,您应在开发阶段配置验证用户登录状态接口,以实现登录用户自动跳转

    //登录状态验证
    if (localStorage.getItem('token')) {
        proxy.$axios.get('login/verify', { layer: true }).then((result) => {
            if (result.state == 'success') {
            	proxy.$router.push({ path: '/console' });
            } else {
            	localStorage.removeItem('token');
            }
        });
    }
    

控制台

​ 控制台为应用的主操作界面 src\page\console\Console.vue

  • 导航区域基于menu组件递归渲染,样式受应用主题影响

  • 快捷访问功能数据源取自store.menu并自动渲染,支持快捷键

  • 通知消息功能数据源取自store.message并自动渲染,支持快捷清空与查看

    //通知消息|清空
    function messageClear() {
        load.messageClear = true;
        proxy.$axios.post('service/messageClear.json').then((result) => {
            el.message.value.hide();
            load.messageClear = false;
            if (result.state == 'success') {
                message.list = [];
                proxy.$bus.emit('notificationRefresh', 0);
                ElMessage({
                    type: 'success',
                    message: '已读消息成功'
                });
            } else {
                ElMessage({
                    type: result.state,
                    message: result.message
                });
            }
        });
    }
    
  • 屏幕锁定功能适用于保密场景下使用,支持快捷操作

    //锁定|验证
    function lockVerify() {
        //...
        proxy.$axios.post('service/unlock.json', { pwd: lock.pwd }).then((result) => {
            if (result.state == 'success') {
                lock.pwd = '';
                store.lock = false;
            } else {
                ElMessage({
                    type: result.state,
                    message: result.message
                });
            }
        });
    }
    
  • 主题配置

    全局主题样式配置,store数据存储,支持随机主题和恢复默认

    名称属性描述
    色彩模式开启深色模式
    关闭浅色模式
    主题配色蓝色-
    绿色-
    橙色-
    粉色-
    紫色-
    棕色-
    布局模式分列模式主菜单-子菜单-主区域
    综合模式顶部-子菜单-主区域
    侧边模式主菜单-主区域
    顶部模式顶部-主区域
    常规模式顶部-主菜单-主区域
    精简模式组合菜单-主区域
    浮动模式浮动菜单-主区域
    页宽模式自适应-
    自适应(有最小宽度)最小宽度1024像素
    定宽居中主区域居中1320像素
    顶宽居中(有最大宽度)最大宽度1320像素
    导航风格填充菜单填充样式
    圆角菜单圆角样式
    视图模式标签显示标签栏
    页面隐藏标签栏
    标签风格卡片标签栏节点卡片样式
    灵动标签栏节点灵动样式
    圆滑标签栏节点圆滑样式
    表格条纹启用表格跨行背景色
    关闭表格无背景色
    表格边框开启显示表格边框
    关闭隐藏表格边框
    组件尺寸较大全局组件较大尺寸
    常规全局组件常规尺寸
    较小全局组件较小尺寸
    表单模式页面表单独立页面显示
    对话框表单对话框显示
    抽屉表单抽屉显示
    表单标签左侧表单标签位置左侧
    顶部表单标签位置顶部
  • 标签栏

    标签栏是多页面的主要切换区域,支持标签关闭、标签刷新、标签最大化、标签最大化

表单模块

  • 文件组成

    为了工程文件的一致性,建议在开发过程中遵守该规范,如下目录结构实现vnode表单动态渲染挂载,支持主题配置下的表单模式切换,采用事务总线实现跨页面通信

    名称描述
    Record.vue列表模块
    Form.vue表单模块
    Detail.vue兼容模块
  • 列表模块 Record.vue

    名称区域描述
    头部左侧搜索区域
    右侧操作区域
    中间-数据表格
    底部左侧数据分页
    右侧字段配置

    搜索表单 src/components/Screen.vue

    <template>
        <search v-model="search.data" :field="search.field" @change="()=>{}"></search>
    </template>
    <script setup>
        const search = reactive({
            data: { name: [],data: [] },
            field: { name: '用户名称',data: '备注信息' }
        });
    </script>
    

    搜索下拉 src/components/Search.vue

    <template>
    	<screen v-model="screen.data" :field="screen.field" @change="()=>{}"></screen>
    </template>
    <script setup>
        const screen = reactive({
            data: { time: [null, null], read: '' },
            field: {
                time: {
                    label: '消息时间',
                    type: 'dates'
                },
                read: {
                    label: '消息状态',
                    type: 'list',
                    options: [
                        { label: '未读', value: '0' },
                        { label: '已读', value: '1' }
                    ]
                }
            }
        });
    </script>
    

    数据表格

    <template>
     	<!-- 数据表格  -->
    	<el-table :data="table.data" :stripe="store.theme.stripe" :border="store.theme.border" v-loading="table.load">
            <template v-for="row in table.column">
                <el-table-column v-if="row.show && row.key == 'time'" :label="row.label" prop="created_at" :width="row.width" align="center" />
                <el-table-column v-if="row.show && row.key == 'title'" :label="row.label" prop="data.title" :width="row.width" align="center" />
                <el-table-column v-if="row.show && row.key == 'details'" :label="row.label" prop="data.details" :width="row.width" align="center" />
                <el-table-column v-if="row.show && row.key == 'state'" :label="row.label" prop="str.state" :width="row.width" align="center" />
            </template>
        </el-table>
    	<!-- 数据分页  -->
    	<el-pagination v-model:current-page="table.page.page" v-model:page-size="table.page.size" :total="table.page.total" :page-sizes="table.page.sizes" :pager-count="table.page.count" :small="true" :background="true" @current-change="record(0)" @size-change="record(1)" layout="total,prev,pager,next,sizes" v-pagination />
    	<!-- 字段配置  -->
        <column v-model="table.column" :name="_.type.name" />
    </template>
    <script setup name="Notification">
        //表格数据
        const table = reactive({
            load: false,
            data: [],
            //分页配置
            page: {
                page: 1,
                size: 30,
                total: 0,
                sizes: [30, 60, 90, 150, 300],
                count: 5
            },
            //表格字段
            column: [
                { label: '消息时间', key: 'time', width: '160', show: true },
                { label: '消息标题', key: 'title', width: '220', show: true },
                { label: '消息详情', key: 'details', width: '360', show: true },
                { label: '消息状态', key: 'state', width: '120', show: true }
            ]
        });
        //获取数据
        function record(page = 0) {
            table.load = true;
            page == 0 || (table.page.page = page);
            let params = Object.assign({}, { page: table.page.page, size: table.page.size });
            proxy.$axios.get('module/record', { params }).then((result) => {
                table.load = false;
                if (result.state == 'success') {
                    table.data = result.info.data;
                    table.page.total = result.info.total;
                } else {
                    ElMessage({
                        type: result.state,
                        message: result.message
                    });
                }
            });
        }
    </script>
    
  • 表单模块 Form.vue

    <template>
    	<!-- 数据表单  -->
        <el-form class="center" :ref="el.form" :model="form" :rules="rules" :label-position="store.theme.label == 'left' ? 'right' : 'top'" label-width="auto" v-form-label-justify>
            <el-form-item label="消息标题" prop="title">
                <el-input v-model="form.title" placeholder="请输入消息标题" />
            </el-form-item>
            <el-form-item label="消息内容" prop="details">
                <el-input v-model="form.details" :autosize="{ minRows: 4, maxRows: 6 }" type="textarea" placeholder="请输入消息内容" />
            </el-form-item>
        </el-form>
    	<!-- 节点挂载  -->
        <Teleport v-if="props.to" :to="props.to">
            <el-button v-if="props.close" @click="props.close">取消</el-button>
            <el-button type="primary" @click="publish" :loading="load">发布</el-button>
        </Teleport>
    </template>
    
    <script setup name="NotificationForm">
        const { proxy } = getCurrentInstance();
        
        const store = proxy.$pinia.state.value.store;
        const props = defineProps({
            to: { require: false, type: String, default: null },
            close: { require: false, type: Function, default: null }
        });
    
        const el = { form: ref() };
        const load = ref(false);
        const form = reactive({ title: '', details: '' });
    
        //验证规则
        const rules = {
            title: [{ required: true, message: '请输入消息标题', trigger: 'blur' }],
            details: [{ required: true, message: '请输入消息内容', trigger: 'blur' }]
        };
    
        //发布消息
        function publish() {
            el.form.value.validate((valid) => {
                if (valid) {
                    load.value = true;
                    proxy.$axios.post('notification/publish', form).then((result) => {
                        load.value = false;
                        if (result.state == 'success') {
                            proxy.$bus.emit('notificationRefresh', 0);
                            props.close && props.close();
                            ElMessage({ type: 'success', message: '成功向' + result.info + '人发布消息' });
                        } else {
                            ElMessage({ type: result.state, message: result.message });
                        }
                    });
                }
            });
        }
    </script>
    

内置模版

​ 应用内置了常用的数据模块,根据下述接口配置后端接口即可

  • 系统设置 src/page/config/sys

    名称接口参考描述
    Record.vuerecordpublic/api/sys/record.json数据接口
    savepublic/api/sys/save.json保存接口
  • 组织架构 src/page/config/frame

    名称接口参考描述
    Record.vuerecordpublic/api/frame/record.json分页数据接口
    destroypublic/api/frame/destroy.json数据删除接口
    Form.vueinfopublic/api/frame/info.json数据获取接口
    savepublic/api/frame/save.json数据保存接口
  • 用户角色 src/page/config/role

    名称接口参考描述
    Record.vuerecordpublic/api/role/record.json分页数据接口
    destroypublic/api/role/destroy.json数据删除接口
    Form.vueinfopublic/api/role/info.json数据获取接口
    savepublic/api/role/save.json数据保存接口
  • 用户管理 src/page/config/user

    名称接口参考描述
    Record.vuerecordpublic/api/user/record.json分页数据接口
    destroypublic/api/user/destroy.json数据删除接口
    Form.vueinfopublic/api/user/info.json数据获取接口
    savepublic/api/user/save.json数据保存接口
  • 通知消息 src/page/config/report

    名称接口参考描述
    Record.vuerecordpublic/api/notification/record.json分页数据接口
    clearpublic/api/notification/clear.json清空数据接口
    readpublic/api/notification/read.json已读数据接口
  • 操作日志 src/page/config/log

    名称接口参考描述
    Record.vuerecordpublic/api/log/record.json分页数据接口
    clearpublic/api/log/clear.json数据清空接口
  • 菜单管理 src/page/config/menu

    名称接口参考描述
    Record.vuerecordpublic/api/menu/record.json分页数据接口
    destroypublic/api/menu/destroy.json数据删除接口
    Form.vuedatapublic/api/menu/data.json菜单下拉接口
    infopublic/api/menu/info.json数据获取接口
    savepublic/api/menu/save.json数据保存接口
  • 报表模板 src/page/config/report

    名称接口参考描述
    Record.vuerecordpublic/api/report/record.json分页数据接口
    destroypublic/api/report/destroy.json数据删除接口
    Form.vueinfopublic/api/report/info.json数据获取接口
    savepublic/api/report/save.json数据保存接口
    Sheet.vuelistpublic/api/sheet/list.json模板列表接口
    src/config/report.jsupdatepublic/api/report/update.json模板更新接口
  • 文件管理 src/page/config/explorer

    名称接口参考描述
    src/components/Explorer.vuerecordpublic/api/explorer/record.json文件列表接口
    destroypublic/api/explorer/destroy.json文件删除接口
    editpublic/api/explorer/edit.json名称修改接口
    folderpublic/api/explorer/folder.json新建文件夹接口
    uploadpublic/api/explorer/upload.json文件上传接口

角色权限

​ 用户所属的角色权限是基于内置模板的用户角色-功能权限为验证依据的,通过预设配置和助手函数可快速实现权限功能

  • 权限配置 src/page/config/role

    √ 表示拥有权限 | × 表示没有权限 | - 表示无此权限项

    模块查看新增修改删除
    系统参数-×-
    组织架构
    用户角色
    用户管理××

    配置项

    const tab = [
        { name: '系统参数', key: 'sys', list: ['see', 'edit'] },
        { name: '组织架构', key: 'frame', list: ['see', 'add', 'edit', 'destroy'] },
        { name: '用户角色', key: 'role', list: ['see', 'add', 'edit', 'destroy'] },
        { name: '用户管理', key: 'user', list: ['see', 'add', 'edit', 'destroy'] }
    ];
    

    配置解析

    {
        "sys":{"see":true,"edit":false},
        "frame":{"see":true,"add":true,"edit":true,"destroy":true},
        "role":{"see":true,"add":true,"edit":true,"destroy":true},
        "user":{"see":true,"add":true,"edit":false,"destroy":false}
    }
    
  • 权限验证

    权限验证是基于console模块获取并赋值的store.role.fun数据项作为验证依据 public/api/console/base.json

    //验证系统参数查看权限
    proxy.$helper.auth('sys.add');
    //验证组织架构修改权限
    proxy.$helper.auth('frame.edit');
    //验证用户管理删除权限
    proxy.$helper.auth('user.destroy');
    
    <template>
        <!-- 模板助手函数  -->
        <el-button v-if="$helper.auth('user.destroy')">删除用户</el-button>
    </template>
    
  • 菜单权限

    您可参照如下PHP代码示例或用其他后端语言来实现菜单权限控制 public/api/console/base.json

    //查找当前用户角色
    $role=DB::table('roles')->where('id',Auth::user()->role)->first();
    //JSON数据解码
    $fun=json_decode($role->fun,true);
    //按照排序检索所有菜单
    $menu=DB::table('menus')->orderBy('sort')->get()->toList();
    //循环遍历菜单列表
    foreach ($menu as $key=>$vo) {
        //检查菜单的auth属性
        if(empty($vo['auth'])){
            //跳出当前循环
            continue;
        }else{
            //检查用户是否具有查看该菜单的权限
            if(isset($fun[$vo['auth']])){
                if(empty($fun[$vo['auth']]['see'])){
                    //无该菜单的查看权限则从删除该菜单
                    unset($menu[$key]);
                }
            }else{
                //跳出当前循环
                continue;
            }
        }
    }
    // 循环遍历菜单列表
    // 0:常规菜单|1:常规分类|2:组合菜单|3:扩展菜单|4:外部链接
    foreach ($menu as $key=>$vo) {
        //检查每个菜单的类型
        if(in_array($vo['type'],[1,2])){
            //匹配上级菜单[常规菜单,扩展菜单,外部链接]
            $find=search($menu)->where([['pid','=',$vo['id']],['type','in',[0,3,4]]])->first();
            if(empty($find)){
                //无匹配结果则删除该菜单
                unset($menu[$key]);
            }
        }
    }
    //将菜单列表转换为分层树形结构
    $menu=Tree::hTree($menu,0);
    //返回菜单数据
    return $menu;
    

示例数据

​ 为了方便快速集成,可导入如下Mysql语句作为应用基础数据

SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

CREATE TABLE `frames` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pid` int(11) NOT NULL COMMENT '所属组织',
  `name` varchar(32) NOT NULL COMMENT '组织名称',
  `sort` int(11) NOT NULL DEFAULT '0' COMMENT '组织排序',
  `data` varchar(256) DEFAULT '' COMMENT '备注信息',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='组织架构';

INSERT INTO `frames` (`id`, `pid`, `name`, `sort`, `data`) VALUES
(1,	0,	'销售部',	0,	''),
(2,	0,	'技术部',	1,	''),
(3,	0,	'财务部',	2,	''),
(4,	0,	'运维部',	3,	''),
(5,	1,	'电商部',	0,	'');

CREATE TABLE `logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `time` int(11) NOT NULL COMMENT '操作时间',
  `info` varchar(256) NOT NULL COMMENT '操作内容',
  `user` int(11) NOT NULL COMMENT '操作人员',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='操作日志';

INSERT INTO `logs` (`id`, `time`, `info`, `user`) VALUES
(1,	1679337072,	'登录系统',	1),
(2,	1679371557,	'新增客户',	1);

DROP TABLE IF EXISTS `menus`;
CREATE TABLE `menus` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pid` int(11) NOT NULL COMMENT '所属菜单',
  `name` varchar(32) NOT NULL COMMENT '菜单名称',
  `key` varchar(32) NOT NULL COMMENT '菜单标识',
  `ico` varchar(32) DEFAULT '' COMMENT '菜单图标',
  `path` varchar(128) DEFAULT '' COMMENT '菜单路径',
  `component` varchar(128) DEFAULT '' COMMENT '组件路径',
  `query` varchar(256) DEFAULT '{}' COMMENT '组件参数',
  `type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '菜单类型[0:常规菜单|1:常规分类|2:组合菜单|3:扩展菜单|4:外部链接]',
  `mold` tinyint(1) NOT NULL DEFAULT '0' COMMENT '菜单模式[0:标签模式|1:页面模式]',
  `sort` int(11) NOT NULL DEFAULT '0' COMMENT '菜单排序',
  `auth` varchar(32) DEFAULT '' COMMENT '权限标识',
  `data` varchar(256) DEFAULT '' COMMENT '备注信息',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='菜单管理';

INSERT INTO `menus` (`id`, `pid`, `name`, `key`, `ico`, `path`, `component`, `query`, `type`, `mold`, `sort`, `auth`, `data`) VALUES
(2,	0,	'首页',	'home',	'home',	'/home',	'console/Home.vue',	'{}',	0,	0,	0,	'',	''),
(16,	100,	'报表模板',	'report',	'printer',	'/report',	'config/report/Record.vue',	'{}',	3,	0,	1,	'report',	''),
(31,	0,	'系统',	'system',	'system',	'',	'',	'{}',	1,	0,	9,	'',	''),
(32,	31,	'系统参数',	'sys',	'setting-one',	'/sys',	'system/sys/Record.vue',	'{}',	0,	0,	0,	'sys',	''),
(33,	31,	'用户管理',	'user',	'avatar',	'/user',	'system/user/Record.vue',	'{}',	3,	0,	3,	'user',	''),
(34,	33,	'用户详情',	'userDetail',	'editor',	'/user/detail',	'system/user/Detail.vue',	'{}',	0,	0,	0,	'',	''),
(35,	31,	'操作日志',	'log',	'log',	'/log',	'system/log/Record.vue',	'{}',	0,	0,	5,	'log',	''),
(37,	31,	'组织架构',	'frame',	'chart-graph',	'/frame',	'system/frame/Record.vue',	'{}',	3,	0,	1,	'frame',	''),
(38,	37,	'组织详情',	'frameDetail',	'editor',	'/frame/detail',	'system/frame/Detail.vue',	'{}',	0,	0,	0,	'',	''),
(39,	31,	'用户角色',	'role',	'permissions',	'/role',	'system/role/Record.vue',	'{}',	3,	0,	2,	'role',	''),
(40,	39,	'角色详情',	'roleDetail',	'editor',	'/role/detail',	'system/role/Detail.vue',	'{}',	0,	0,	0,	'',	''),
(41,	31,	'通知消息',	'notification',	'remind',	'/notification',	'system/notification/Record.vue',	'{}',	3,	0,	4,	'notification',	''),
(49,	100,	'文件管理',	'explorer',	'folder-close',	'/explorer',	'config/explorer/Detail.vue',	'{}',	0,	0,	2,	'explorer',	''),
(100,	0,	'配置',	'config',	'config',	'',	'',	'{}',	1,	0,	99,	'',	''),
(101,	100,	'菜单管理',	'menu',	'application-menu',	'/menu',	'config/menu/Record.vue',	'{}',	3,	0,	0,	'menu',	''),
(102,	101,	'菜单详情',	'menuDetail',	'editor',	'/menu/detail',	'config/menu/Detail.vue',	'{}',	0,	0,	0,	'',	'');

CREATE TABLE `notifications` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user` int(11) NOT NULL,
  `data` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `read` timestamp NULL DEFAULT NULL,
  `created` timestamp NULL DEFAULT NULL,
  `updated` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知消息';

INSERT INTO `notifications` (`id`, `user`, `data`, `read`, `created`, `updated`) VALUES
(2,	1,	'{\"title\":\"审核通知\",\"details\":\"有新的订单[NOD2565874217602]待审核,请您及时处理。\"}',	NULL,	'2023-06-28 04:33:59',	'2023-06-28 04:33:59'),
(3,	1,	'{\"title\":\"计划任务\",\"details\":\"当日计划任务已执行完毕,可前往任务中心查看详情。\"}',	NULL,	'2023-06-27 12:03:03',	'2023-06-27 12:03:03');

CREATE TABLE `reports` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL COMMENT '报表名称',
  `key` varchar(32) NOT NULL COMMENT '报表标识',
  `source` text NOT NULL COMMENT '数据配置',
  `template` longtext NOT NULL COMMENT '报表代码',
  `size` varchar(64) DEFAULT '' COMMENT '报表尺寸',
  `sort` int(11) DEFAULT '0' COMMENT '报表排序',
  `data` varchar(256) DEFAULT '' COMMENT '备注信息',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报表模板';

INSERT INTO `reports` (`id`, `name`, `key`, `source`, `template`, `size`, `sort`, `data`) VALUES
(44,	'测试报表',	'form',	'[{\"key\":\"order\",\"url\":\"\\/report\\/order.json\",\"parm\":\"{}\"}]',	'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVwb3J0IFNjcmlwdExhbmd1YWdlPSJDU2hhcnAiIFJlcG9ydEluZm8uQ3JlYXRlZD0iMDYvMjAvMjAyMyAwMjoyNzoyNCIgUmVwb3J0SW5mby5Nb2RpZmllZD0iMDYvMjAvMjAyMyAwMjo0NzozMCIgUmVwb3J0SW5mby5DcmVhdG9yVmVyc2lvbj0iMjAyMy4yLjE3LjAiPjxEaWN0aW9uYXJ5Lz48UmVwb3J0UGFnZSBOYW1lPSJQYWdlMSIgV2F0ZXJtYXJrLkZvbnQ9IuWui+S9kywgNjBwdCI+PFJlcG9ydFRpdGxlQmFuZCBOYW1lPSJSZXBvcnRUaXRsZTEiIFdpZHRoPSI3MTguMiIgSGVpZ2h0PSI5MDcuMiI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDEiIExlZnQ9IjI2OS4zMyIgVG9wPSIxOC45IiBXaWR0aD0iMTc5LjU1IiBIZWlnaHQ9IjI4LjM1IiBUZXh0PSJSZXBvcnTmiqXooajliqnmiYsiIEhvcnpBbGlnbj0iQ2VudGVyIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCAxNHB0Ii8+PFBpY3R1cmVPYmplY3QgTmFtZT0iUGljdHVyZTEiIExlZnQ9Ijc1LjYiIFRvcD0iMjgzLjUiIFdpZHRoPSI3NS42IiBIZWlnaHQ9Ijc1LjYiIEltYWdlRm9ybWF0PSJQbmciIEltYWdlPSJpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBTUFBQUFEQUNBWUFBQUJTM0d3SEFBQUFCR2RCVFVFQUFMR1BDL3hoQlFBQUFBbHdTRmx6QUFBTEV3QUFDeE1CQUpxY0dBQUFGbFpKUkVGVWVGN3RuVmVRRkZlV2h2VTA4elNyalpuWWlaallpWG1lblIxSmVBL2ROSTFwR204a25BYnZYZU85RlVJTTNuc1BhaG9yR204RUNFL2poVEJDQ0NjYTA0RHdLd2ZTM2Z0blpFRlY5YW1xektxc2RQZjhFVjlJWkZYZE5IMytxbXZQZmV2di95eVJMTjZXMUpBTWxheVNISlBja2p5WENJWWhRR3dnUnZJa2lCbkVEbUlJc1VURldNS1FCeFBncjVJK2tzT1NWeExxSmhuR0xJZ2x4QlJpNjI4U0t2YmlnandZQjVVa215VWM5RXl5UVl3aDFsSWtWQ3lhZ2p4b2dsVEpBUWwxb1F5VGJCQjdpRUVxTmcxQkhqVEFYeVFySmRSRk1ZemRJQllSazFTc1JvVThHSVBHa2ljUzZrSVl4aWtRazRoTkttWWpRaDZNd084a015VFV5Um5HTFNCR2Z5K2hZcmdRNUVHQ1AwZytsMUFuWkJpM3NWL3lSd2tWeXlHUUI4UDRMOGxKQ1hVaWhuRXJaeVdJWFNxbVgwTWVEQUlERUtjazFBa1l4dTJjbGtRZFJDTVA2cURPdjBkQ0Zjd3dYZ0V4akZpbVlqeXFBV1pKcUFJWnhtc2dscWtZajJnQWRDZFJCVEdNVjNsZlVpaldDeDJRL0xma3NZUXFoR0c4Q21JYWM5VkM0ajNrSHpvYkpGUUJET04xRU5zaDhSN3lEMGxsQ2ZWQmh2RUxWU1N2WXo0NCtNRkJDZlVoaHZFTGlQSFhNUjhjL0pqU1RIMkFZZndHWXIyUUFUREhtbm96dy9nTnhIcUlBZjRzZVNtaDNzd3dmZ094anBoL2JZQXNDZlZHaHZFcmlQblhCdURHTDZNYVdtTVl3WS9KUXJ5V2wxRU54UHpiTUVCRzBFR0dVWWtNR0dCSTJFR0dVWVZoTUVCTzJFR0dVWVVjR0FCWnVLZ1hHY2J2NU1FQVNFVkh2Y2d3ZmljZkJ1QmNuWXlxUElNQnFCY1lSZ25ZQUl6U3NBRVlwV0VETUVyREJtQ1VoZzNBS0EwYmdGRWFOZ0NqTkd3QVJtbllBSXpTc0FFWXBXRURNRXJEQm1DVWhnM0FLQTBiZ0ZFYU5nQ2pOR3dBUm1uWUFEWlFwbnlhcUY2enZtalhvWnNZTjM2S21ETnZrVmk0ZUxuSTNieE5iTjY2WFd6YXNsMXMyYnBEWk9lczFWNzc1TitUUlBlc2ZxSkI0K1lpTlQxVC9NODdKY2x5bWNSaEF5U0I0cVVyaWNaTi9pV0dqdmhJTEZtNlF1UWRQeW55ODIrTFgzLzlWWmpSbzBlUHhlWExWOFNHenpaSjQwd1dyZHQyRmhWU3FwSG5aT0tERFdBUjVXVmc5aDg0Vkd6ZHRrUGNLeWpRUTloNi9mREREMkwvZ1VOaXpOanhvbHJOZXVTMU1NWmhBeVRBdThYS2lvNmRlNGpQY2plTEZ5LytUdzlSKzRSZmxIMWZIQkM5K2c0VXhVdFZJcStSaVE0YklBN3diVDkrNGxSeDdkcDFQUlNkMTkyNzk4VFU2Yk5FU2xvR2VjME1EUnZBQk9VcVZSVVRKMDF6NU52ZXFINzY2U2N4ZmNZY1VhNWlPbmtQVENoc0FBUDhzMGdaOGZFbkUxd2QrT0Y2K3ZTcEdEN3lZL0orbURld0FXTFFzblZIY2ZIUzEzcFllVTlIanVhSlduVWJrL2ZHc0FFaThtNnhjbUwrd2lWNkdIbGJhQ3lQSFRlUnZFL1ZZUU1RMUs3M3ZxZS85U05wWSs1bVViUkVCZktlVllVTkVFYlAzZ1BFcTFldjlKQ3hWai8vL0xONDlQaXh1SEhqcGppV2QwS2NPWHRPZkhudXZEaCs0cFFHZW5JZVAzNGlYcjU4cVgvQ2VwMzc2anozRkFYQkJnaGl4cXk1ZXBoWW8xdjUrV0xMMXUxYUE3cFZtMDZpYW8wNm9sVFp5dUlmNzVZaXo0OXFWNmx5bFVYTjJvMUV4eTQ5eFlTSlU4WHV6L2RxcHJCU0tDK3pEcmNMQUJ0QVorbXlsWHA0SktadnZya2lwczJZTFpyL3E1MG9XdEthNmticDhtbWlYY2R1WXRYcXRWcnZqaFZDT2FqcVVlZFRDVGFBWk1mTzNYcFl4Szl0MjNkcVFZb3VVK29jVm9HeGlFRkRSb2p6NXkvcVo0NWZ6NTQ5RjVXclpwTG5VUVhsRFpDN2FZc2VEdkZwWmZacVVhdXUvZCtrbUNIYXU5OGdjZm55Ti9xVnhDZTBPMUR0b3M2aEFrb2JJSkZxejY3ZGUwVEQ5MXVRNWRvSmZuRkdmZlNKMXNDT1Z5ZFBuU2JMVmdGbERUQmgwbFQ5ejI5T0R4OStMM3BrOVNQTGRKS3FOZXFLZ3djUDYxZHBYZ3NYTHlQTDlUdEtHZ0IxOVhpNk90ZHZ5QlZsSzdoN2pzM0V5ZFAwcXpXdnRoMjZrbVg2R2VVTVVDa3R3M1R3Ly9iYmIyTG84TkZrZVc2a1o2LysrcFdiRTNxR2lwYXNTSmJwVjVRendKbXpYK3AvYm1PNmYvK0JlTDlwUzdJc04vTkJzNWJpeHg5LzFPL0N1TmFzM1VDVzUxZVVNc0NrS1RQMFA3TXhYYjEyM2RPanB2VWJOWXRyVkxsZXc2WmtlWDVFR1FOazFHNm8vM2tMNjVkZmZ0RkdiVEZ6Y3VmdVBlTFQ3TlZpOUpoeHZ1Z2ViTm1tbzM2WHhuWDR5Rkd5TEQraWhBRktsa25WMXRKQ21BWnc2UEJSc1hqcEN0Ri8wRERScEZrcmtWcWxwcStYRlBidFAwUzdkek5xOW1FYnNpeS9vWVFCYW1RMjBMcjVXclJzcCt5Z3o2SWx5L1hRTmlaTXpxUEs4UnZLTllKVkJqTkJ6UWh0Q0tvY1A4RUdVQWlrVVRFakdBWmpBelZxTmZCdEZaRU5ZQUQwalplcFVFVlVTSzJ1VmFjeWFqVjgvZC8wR25WRjJZcnA0cjNpNWNuUHVvMXBNK2JvNFcxY1dGRjI3OTQ5c1dmdkYyTFNsT215ZmRCVzNtODVzbnl2d1FiUVFVTVphMmZiZCtxdUpaMWF2akpiYXl5Zk9QbG1vY3J6NXkrMFFURW84RjkwTXo1NThsVEwvSGIwMkhHeGQ5OStNWC9CWWpGNDZDaXRCd1pURlA3M3ZkTGtPWjBBYzRjZVBIaW9YWHNpK3U2N1cxcTdDaG53cVBONEJXVU5nRzl3akpqT25ydEFITXM3TGdvSzdwdE9YV2hFR0hYR2pNMVZPV3UxYWN3MWF6Y2tyOGRPaG8wWW8xK2ROVHB3OEpEbzBxMFhlUzYzbzR3QlNwUk9FVzNhZHhFTEZpN1Y2cmFCYjNDN0JaT2RPblZHako4NFJkUnlhRUVLZnBGZ2VLdVZkL3lFdGhDSU9xZGI4YlVCc01RUVFaKzlhazFTL3VDSkNpWThjUEN3L0dVWWFYdlMyMUVmamRPdnducGw1NnpSMmtYVWVkMkdMdzJBYWdheW85MjRlVlAvazdoZm1JaTI0dE1jMGVpREQ4bDdzaG8wNnBOUjVRdm9ybXcwWTl5Rk9yZWI4SlVCbXJab3JTMUM5N293cjc5VDE2eUlpK2V0WW1OdVlxdmhqQWg3SFZEbmRndStNRUFMV2UvY3VldHovWkg3UjJmUG5oTzkrZ3hNV2k4Uyt2anRFT1pXVWVkM0E1NDJBTG90TjIveC9qZCtMSjIvY0ZGMDY5R0hmQWFKZ0draHlGT0V0Z2pBc2twTURFeUdjamR2SmEvQmFUeHBnQ0xGeTR1WnMrZnBqMVlkWVp5aGFRdHJKNm1oOFkxRi9kVXo2NHZLNlpsYVRxSVBXN1hYOWh5WU4zK3hPSFg2akdVOVprZ2dRRjJEazNqT0FFaFdlK1hicS9valZWTTVxOWVKaXBWcmtNOG5HYUJUQWNtOWtQTW9VUTBZTkp3OGgxTjR4Z0JvRU02WnQxQi9qQ3pzSDlaM3dCRHlXU1dMZDRxVTBhcGlxSklsSWpkazB3amdDUU5VcmxwTG01TEFLaXcwL2xQVGE1TFBMVmtnSnhGU3NjUmJOYnA5KzQ1cjVoSzUzZ0JZbUlHR0dpdXluajE3SnJwMjcwMCt2MlNDNmRMWGI5elFyOEtja0pPSkt0TnVYRzBBL01RbnExZkNqNW83YnhINUhKTUpCdFRNSmhxQU1FY3Ewd1ViZDdqV0FNTkhXanRoU3hWOXZtZWZsb0dhZXFiSkFsUEJMMTI2ckYrQmNSMDVjb3dzejA1Y2FZQ1JzbjdKaWwrWXFweFpweEg1YkpORmtSSXdnZmxOUlp4T09lTTZBN1J1MTlteWZtZVZoWFVLZHZlMm9ERnU5bStISEt0VVdYYmhLZ1BnVzR1RDN6cWgvV1IzdWtOTTNUQ3JLdFZxazJYWmdXc01VS3hrUlhHdm9FQi9KQ3lyaEJtZmRwc0E2d0xNYU1yVW1XUTVkdUFhQSt6WWFkMWtOdjRWQ1JVMno3YXpybDAxbzY1K1ptUENob1JVT1hiZ0NnT2dEOXVzTUJLS1JkcFRwczBVUTRhTkZ0MnorbW1ya1ZxMGJLOHQyczdxUFVCTGFMdHcwVkl0LzMweU41N3pnakRSclU2REp1VHpUd2FidG16VHoyeE02ZFdkcVFZNWJnQXNWWHorNG9YK0dLSUxpOUt4dGhZTDEwdVhTeVBMaTBTYXJHZjJHekJVR3psVjlSY0NBNHJZWW9sNlBsYURYeHd6d3FvNHFweGs0N2dCNWkxWXJEK0N5UHIrKzBmYVpDeXJsZzFpR3ZXeUZkbEtHdUdyOHhlU3Z0QW1BTll6R0pWVEk4T09HZ0RUYjJNRjRSTDVZTW9sYVgwcHB2NmEvYW4yZzlhdDMwZytENnZCZkNHanlzczc0VWp5TFVjTnNHVHBDdjMyQyt2bXplKzBKWTdVNTZ3RzdZY1hCcXRoZmhIYVRkU3pzSklHalp2clo0c3RySWwySW1XTVl3WkFJcXBJRGRQangwOXFjMHlvenlVTGJCZUs4NnFrR3BuMXlXZGhGZThVTFdzcUNSZXlXRlBsSkJQSEREQnc4QWo5dGtPRlJxcFRtZFJ3M3JYclB0T3Z4UCs2OVBWbDhqbFlDWGE2TjZyeEU2ZVNaU1FUeHd5QXJzbHduVGg1V3B0clRyM2ZUcEF0VGhYTm1EV1hmQVpXTVhYNkxQMU1zYlhhZ2UyWkhERUFOcVFJYi94aU82SmlMc3BBakIxaVZCSHlsMUxQd0FyR2paK3NueVcya0thRktpT1pPR0tBck42aHV4aGl1TDVhUmozeXZVNkNIV1JVR0VCTDVrYlovUWNPMDg4U1d4czJiaWJMU0NhT0dDQjhoM2FrNlF0K0hmbm9NWm9iZk13cE9uZk5VbUpSVHBja3JTakRuc3hHcFlRQnNGamp6SmszSzRndVhMZ1U4anJhQU1nKzBMWjlsNURqOFlCR3JSWFZxcDY5QitoWDYxL2R1WE0zS1FOa2ZRY00xYzhRVzR1V3JDRExTQ2EyR3dDNUw0UDNydzNQTDc5cythZmE4VVQyOHVyVXBhZll1bTJIdUpWL1c1c3pkRm9hRG8yOVJPcTZTUEhuZDJGZkJPcmVFK0hmRTZib3BjZldtTEVUeURLU2llMEd3SnlQZ0E0ZU9oTHlXbUFMSDJ4R2djek93YThaQWJseTluMnhYeXVERXVyem1GSkJmZFlJazAzdU0rdzFZVEN3UkprVTh0N2paZHg0WTE4Y21Lelh6T0trWDBhdzNRQlRwcjNwRm12YlBuU2VPblpYZ1c3Y3VLa05vZ1MvRmdzTW8xKy9iaXhEd1lRRStwdHpOMjNWUy9HbnNJVVNkZC94Z2xUcFJuVG43bDJ0ZDVBcUk1bllib0RBUUJPbU9nUWZ4MVRkUU5kb1BBWllzM2E5OWxrandua3dURStWWXdUTVcvR3JuajE3cnUySlJ0MTNQQmpOSm9jQlVDVGVvc3BJSnJZYllPdTJuZG9OSTdkbjhQSGxLN0sxNHhBV2RSY3BVU0hrOVdoZ1VwMVpJYWt1VlpZUlNwZFBFdzhmSnI3UGxsczFlS2cxVTVNcnBkVXd2QWZCM1BuMnAzUUJ0aHRnNzc0RDJnMEhOMzZSN1BiNTgrZmFjUWdybU1xYW1BdUVoUzltaGNsWHhVdkgzME5rWnFLWDEzVHZYb0VsUFVMdFRYU0JPclhIbU8wR1FJOE1Bank0a2R1cVRTZjlNYnhSSXhNWkRYSTN4emVsT2RGbGdpTkdmYXlYNUQ5MXRTQWQrMmNiTittbFJSZCtKZkNyU3BXUmJHdzN3Sm16NXdwTndwbzlwL0RjbTJFalBncDVUelF3aHlRZTFVK2dIUkRBekdRdkx3bHAwYW43TlVxWjhsVzBMem9qK21ML1FiSU1PN0RkQU9oRm1iY2d0TDUzL0VUaHhMZm94dzkrVHpUNjloK3NmOHE0SHNnNnZCV2JXNlBiRUxrNS9hamFDZXhpaVQ1OW84TE1ZS29NTzdEZEFCTW5UdzhaY01IR3plZ1JDdGZqeDQ4TjkwWlVUSzF1ZW5uajZqWHJ5YkxpQVFsOC9haDRseWxpQk43b09nQk1NeW5qVVBVSDJHNkFtblVhaVl4YWIxYitJUFU1TmVFTUFZMk5ySU0vRzQxWmMrYnJuNHd0SkdhMU9oblRqSmx6OWRMOW95ZFBuc2d2SWVPOWNRSE16QUJkdDhHZTVabVJzTjBBNFdUV2Fhdy9pc0pDMVlqNlRDUlFselNpM3YwR2s1OVBsSFBuenV0bjhJK1FYb2E2MTBoZ01Ndk05cXRJc1U2Vll4ZXVOZ0JrWnE5Wi9QUXVYTHhNLzJSaFlhUzRVNWNzOHJOV2tKS1dZZXFQN3dWaGdJcTYxMGlZU1pXT1RjS3BNdXpFY1FPa3BtZEduWE4vL2NaTjhuUFJxRjMvQXpGNTZneHg2UEJSY2ZiTGMyTDlobHl0T29YMGk5VDdyUVRuOFpOZzZBcXlqVVhkYXpnVEowL1RQeFZicU9LR1Q0UjBBc2NOb0UxL3Z2S3QvbGhvelYrNGhQeXNFMkNUdCs0OSs1S3ZCVEF6TGNNTEdqbDZMSG1md1dEZGhCbmhTNGtxeDI0Y053RFlzWE8zL2xnaUMyblRxYy9hU1pQbXJmV3JFVkd6VnJ4VHRJeTRmZWVPL2s3djY5VHBzK1I5QnNEV3JlaFlNQ3IwL0pRc20wcVdaVGV1TU1Da3lkUDFSeE5aZU1CT3JoSkQzdEhnM0VFSFk5UmYwWWR1dG12V3JjS3pqN1prMVd3Nm1kNzlCcEhsT0lFckRHQ21IOTJKZW1PbkNEL3Z5SHhHdlQ4QVJyUDlvbWpyS01aUE5MN29KWkZKaU1uQUZRYkF4S3Y4MjdmMVJ4UmRhREJqeFJkVlRqS1lNQ2x5d3c3ZjhMRjJZZGtVNXp3bHQrbkNoWXZrL1lHMGFyWDBkMFhYMWF2WFRFOXpUemF1TUFDSUZtaVVwczJZSGRlcU1hUFViZEJFSEQ1eVZEOWJaR0hFczFpcHlMMUxtT09PaExSK1VMUjl4MktOd2FBYUJhTlFuM1VTMXhpZ2JNVjAwMzNvMzhwdmxBNmRlNURseFV0YTFWcGF4bW96MXhLclA3dENTblZ0K3JYWGhmVzkxUDJCWG4waUp3NUFvN2RKczFiazU1ekdOUVlBU0kwWGo3RGRKaHBXWnZjTUNJRDVTTTFsQTN0bDltcFR2Um5CQ2wvZ0U0NGYxZzljdUJpYXdTTVlwSzZQOUtWaFo1WFZMSzR5QU5vQ043OHJQREhPcUxDWUhrc3UrL1FickFVY0RCRmU1OFE1a0pvRmV3UjA3dFpMTEZxeVhGdzJ1R3d2bGpwMGl2NXJoRG4yOFJyTUxZclc1a0dTZzJEQkVGbDlCcEx2ZFF1dU1nQkFZRnJSZllneXNMRUcxaGQvZWU0cmNlbnJiN1JSWVFUNy9mc1BURmUzb2ltNHJGaFZNa3o5OVhMM0tQS21VdmNGc0xOL1FNanlnUEVCNm4xdXduVUdBR2JTNlRrcExQakF0MzcxbXZWbFlBL1gxakJjdTNaZG05NUIzVmNBVk5lOCtrdHdLejgvNG5MSjhwV3FhdThwdUgvZmxha3VLVnhwQU9EMlJGVFkyUkNCSDM3ZEdPRTBrbHVuby95bDhLTHdhL2RocXc3a1BRRjhlVG1SM2lSZVhHc0FnSVV6YmhReVdMeG5RUmNzTnZ2ell2SmR6SFdpN3NlTHVOb0FBSFZxckE1emc3Qnl6ZXB1MTNvTm0yblZDaS9wcGF5K3VXVXVUNks0M2dBZ3BVcUcyTEoxdS83NDdSY2FkTWhvWjJYQ3FHQlFkemE2bU1jdGlqVU54Q3Q0d2dBQlVHV2dGdEFuUzJqa0xscTh6TFlSVERPN3FUaXRlTlpwdUJGUEdTQUFjb3FpeHlWWlBTa1hMMTdTMmg4cERqVG1zRE1tZXBLOElEVGtxWHZ3RXA0MFFJQ3FHWFcxNUZUYmQrelNCc0hpRmI3cGtlOFRDOXN4NTkvcGZjb3d0d2lwQXQydUs5OWVKYS9mUzNqYUFNRmdnUXFDRjRiSXpsa3JkdTNlbzJXaHk3OTlSd043QmR5Vy8wVlNMcVJuWENYZk0yTFVXQzBySGRieVVtVTZEVWF6M2Q0MjZOM1hQWFA3NDhFM0JvZ0U1dmtFY050VVhLTjA2OUZIRzNkd281NDhlU3FLbEVnOHdaaFQrTjRBZmdFR1JoTGdXT3VublZDT2hVbkc3SVlONERId0s1YlZaNENzM3AzVnc4OGRpcFVvd0syd0FUd00yaS9Jd095VzBlUzJIYnE2WXFOek03QUJmRUI2OVRwaStNZ3hZditCUTQ1T3NzUE1Xek1ibTdnQk5vRFB3RTZZYUN0czNiNVQzQ3NvU09yVWE1U05BYkdjMWV0RWwrNjlSUG1VYXVJZjcvSXZBT01TaXBkTzBiYWxIVFJraEZpOGRJVTRlaXhQMnc4NG5vMi84Y3VDOVJYSFQ1d1V5MWV1RWtPR2pSWjE2bi9nMlo2MUFHd0FoY0FLT1dUbTd0aWxweGcxWnB5V1BuTGQrbzFpMi9aZEd0aS9EUVQrZjkyR1hERi93Ukl0TTF5UFh2MUZ2WVpOVFcxZDVRWFlBSXpTc0FFWXBXRURNRXJEQm1DVWhnM0FLQTBiZ0ZFYU5nQ2pOR3dBUm1uWUFJelNzQUVZcFdFRE1FckRCbUNVaGczQUtBMGJnRkVhTmdDak5Hd0FSbW5ZQUl6Sy9Bd0RQQTg3eURDcThEME1jQ3ZzSU1Pb3dnMFlJQy9zSU1Pb1FoNE1rQk4ya0dGVUlRY0dHQnAya0dGVVlSZ01rQkYya0dGVUlRTUdlRnZ5S3VnZ3c2Z0FZdjQvWVFCd1VEL0lNS3FBbUg4cllJQXMvU0REcUFKaS9yVUIvaXg1S2FIZXlEQitBN0dPbUg5dEFMQkZRcjJaWWZ3R1lsMkwrMkFEVkpKUWIyWVl2NEZZTDJRQXdJMWh4dTlvamQ4QXdjRVBxa2lvRHpHTVgwaVh2STc1NE9BUHNFRkNmWkJodkE1aU95VGVRLzZoODFmSll3bFZBTU40RmNRMFlqc2sza1ArRWNUN0Vxb1FodkVxaU9sQ3NWN29RQkN6SlZSQkRPTTFFTXRVakVjMXdPOGsreVJVZ1F6akZmWkxFTXRVakVjMUFQaVQ1SnlFS3BoaDNBNWlGekZNeGJZR2VUQU1EQm1ma1ZBbllCaTNndURYcGp0RWd6eEk4QitTdlJMcVJBempOZzVJL2lpaFlqa0U4bUFFZmkrWkthRk95REJ1WVpZRXNVckZjQ0hJZ3pGb0xIa2lvVTdPTUU3eFZJTFlwR0kySXVSQkEveEY4cW1FdWhDR3NadHNDV0tTaXRXb2tBZE5rQ3JoQ1hTTVV4eVNWSlpRc1drSThtQWNwRWd3eC9wWENYV2hER01WaURIRUdtS09pa1ZUa0FjVDRHK1N2cElqRWw1b3oxZ0ZZZ2t4aGRoQ2pGR3hGeGZrUVl0QXRva2FFdVFkV2kwNUxya21lU1Q1UlVMZEtLTXVpQW5FQm1JRXNZS1lRZXdnaGhCTFZJd2xTSW0zL2g4QzhneFlSYlVGZWdBQUFBQkpSVTVFcmtKZ2dnPT0iLz48U2hhcGVPYmplY3QgTmFtZT0iU2hhcGUxIiBMZWZ0PSIzMDIuNCIgVG9wPSI2MzMuMTUiIFdpZHRoPSI3NS42IiBIZWlnaHQ9Ijc1LjYiIFNoYXBlPSJUcmlhbmdsZSIvPjxTaGFwZU9iamVjdCBOYW1lPSJTaGFwZTIiIExlZnQ9IjE3MC4xIiBUb3A9IjYzMy4xNSIgV2lkdGg9Ijc1LjYiIEhlaWdodD0iNzUuNiIgU2hhcGU9IkVsbGlwc2UiLz48U2hhcGVPYmplY3QgTmFtZT0iU2hhcGUzIiBMZWZ0PSI0MjUuMjUiIFRvcD0iNjMzLjE1IiBXaWR0aD0iNzUuNiIgSGVpZ2h0PSI3NS42IiBTaGFwZT0iRGlhbW9uZCIvPjxCYXJjb2RlT2JqZWN0IE5hbWU9IkJhcmNvZGUxIiBMZWZ0PSIzMjkuNDUiIFRvcD0iNDI1LjI1IiBXaWR0aD0iMTE2IiBIZWlnaHQ9IjExNiIgVGV4dD0id3d3Lm5vZGNsb3VkLmNvbSIgU2hvd1RleHQ9ImZhbHNlIiBBbGxvd0V4cHJlc3Npb25zPSJ0cnVlIiBCYXJjb2RlPSJRUiBDb2RlIiBCYXJjb2RlLkVycm9yQ29ycmVjdGlvbj0iTCIgQmFyY29kZS5FbmNvZGluZz0iVVRGOCIgQmFyY29kZS5RdWlldFpvbmU9InRydWUiLz48QmFyY29kZU9iamVjdCBOYW1lPSJCYXJjb2RlMiIgTGVmdD0iNzUuNiIgVG9wPSI0NjMuMDUiIFdpZHRoPSIxMTIuNSIgSGVpZ2h0PSI1Ni43IiBUZXh0PSIxMjM0NTY3ODkwIiBTaG93VGV4dD0iZmFsc2UiIEFsbG93RXhwcmVzc2lvbnM9InRydWUiIEJhcmNvZGU9IkNvZGUxMjgiIEJhcmNvZGUuQXV0b0VuY29kZT0idHJ1ZSIvPjxUZXh0T2JqZWN0IE5hbWU9IlRleHQyIiBMZWZ0PSI3NS42IiBUb3A9IjEzMi4zIiBXaWR0aD0iNDQ0LjE1IiBIZWlnaHQ9IjU2LjciIFRleHQ9IuWfuuS6jkZhc3RSZXBvcnQuTmV054mI6ZuG5oiQ5LiO5bCB6KOF77yMJiMxMzsmIzEwO+W4ruWKqeeUqOaIt+iOt+WPlumrmOaViOaJk+WNsOOAgemihOiniOOAgeiuvuiuoeeahOaKpeihqOS9k+mqjOOAgiIgVmVydEFsaWduPSJDZW50ZXIiIEZvbnQ9IuWui+S9kywgMTRwdCIvPjxUYWJsZU9iamVjdCBOYW1lPSJUYWJsZTEiIExlZnQ9Ijc1LjYiIFRvcD0iODAzLjI1IiBXaWR0aD0iNTY2Ljk3IiBIZWlnaHQ9Ijg1LjA1IiBCb3JkZXIuTGluZXM9IkFsbCI+PFRhYmxlQ29sdW1uIE5hbWU9IkNvbHVtbjEiIFdpZHRoPSI5OS4yMiIvPjxUYWJsZUNvbHVtbiBOYW1lPSJDb2x1bW4yIiBXaWR0aD0iMTA4LjY3Ii8+PFRhYmxlQ29sdW1uIE5hbWU9IkNvbHVtbjMiIFdpZHRoPSI4OS43NyIvPjxUYWJsZUNvbHVtbiBOYW1lPSJDb2x1bW40IiBXaWR0aD0iMjY5LjMxIi8+PFRhYmxlUm93IE5hbWU9IlJvdzEiIEhlaWdodD0iMjguMzUiPjxUYWJsZUNlbGwgTmFtZT0iQ2VsbDEiIEJvcmRlci5MaW5lcz0iQWxsIiBUZXh0PSLlp5PlkI0iIEhvcnpBbGlnbj0iQ2VudGVyIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCA5cHQiLz48VGFibGVDZWxsIE5hbWU9IkNlbGwyIiBCb3JkZXIuTGluZXM9IkFsbCIgVGV4dD0i5oCn5YirIiBIb3J6QWxpZ249IkNlbnRlciIgVmVydEFsaWduPSJDZW50ZXIiIEZvbnQ9IuWui+S9kywgOXB0Ii8+PFRhYmxlQ2VsbCBOYW1lPSJDZWxsMyIgQm9yZGVyLkxpbmVzPSJBbGwiIFRleHQ9IuW5tOm+hCIgSG9yekFsaWduPSJDZW50ZXIiIFZlcnRBbGlnbj0iQ2VudGVyIiBGb250PSLlrovkvZMsIDlwdCIvPjxUYWJsZUNlbGwgTmFtZT0iQ2VsbDE0IiBCb3JkZXIuTGluZXM9IkFsbCIgVGV4dD0i5LuL57uNIiBIb3J6QWxpZ249IkNlbnRlciIgVmVydEFsaWduPSJDZW50ZXIiIEZvbnQ9IuWui+S9kywgOXB0Ii8+PC9UYWJsZVJvdz48VGFibGVSb3cgTmFtZT0iUm93MiIgSGVpZ2h0PSIyOC4zNSI+PFRhYmxlQ2VsbCBOYW1lPSJDZWxsNiIgQm9yZGVyLkxpbmVzPSJBbGwiIFRleHQ9IuW8oOS4iSIgSG9yekFsaWduPSJDZW50ZXIiIFZlcnRBbGlnbj0iQ2VudGVyIiBGb250PSLlrovkvZMsIDlwdCIvPjxUYWJsZUNlbGwgTmFtZT0iQ2VsbDciIEJvcmRlci5MaW5lcz0iQWxsIiBUZXh0PSLnlLciIEhvcnpBbGlnbj0iQ2VudGVyIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCA5cHQiLz48VGFibGVDZWxsIE5hbWU9IkNlbGw4IiBCb3JkZXIuTGluZXM9IkFsbCIgVGV4dD0iMTgiIEhvcnpBbGlnbj0iQ2VudGVyIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCA5cHQiLz48VGFibGVDZWxsIE5hbWU9IkNlbGwxNSIgQm9yZGVyLkxpbmVzPSJBbGwiIFRleHQ9IuWWnOasouWKqOa8qyIgSG9yekFsaWduPSJDZW50ZXIiIFZlcnRBbGlnbj0iQ2VudGVyIiBGb250PSLlrovkvZMsIDlwdCIvPjwvVGFibGVSb3c+PFRhYmxlUm93IE5hbWU9IlJvdzMiIEhlaWdodD0iMjguMzUiPjxUYWJsZUNlbGwgTmFtZT0iQ2VsbDExIiBCb3JkZXIuTGluZXM9IkFsbCIgVGV4dD0i5p2O5ZubIiBIb3J6QWxpZ249IkNlbnRlciIgVmVydEFsaWduPSJDZW50ZXIiIEZvbnQ9IuWui+S9kywgOXB0Ii8+PFRhYmxlQ2VsbCBOYW1lPSJDZWxsMTIiIEJvcmRlci5MaW5lcz0iQWxsIiBUZXh0PSLnlLciIEhvcnpBbGlnbj0iQ2VudGVyIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCA5cHQiLz48VGFibGVDZWxsIE5hbWU9IkNlbGwxMyIgQm9yZGVyLkxpbmVzPSJBbGwiIFRleHQ9IjIwIiBIb3J6QWxpZ249IkNlbnRlciIgVmVydEFsaWduPSJDZW50ZXIiIEZvbnQ9IuWui+S9kywgOXB0Ii8+PFRhYmxlQ2VsbCBOYW1lPSJDZWxsMTYiIEJvcmRlci5MaW5lcz0iQWxsIiBUZXh0PSLllpzmrKLov5DliqgiIEhvcnpBbGlnbj0iQ2VudGVyIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCA5cHQiLz48L1RhYmxlUm93PjwvVGFibGVPYmplY3Q+PFRleHRPYmplY3QgTmFtZT0iVGV4dDMiIExlZnQ9Ijc1LjYiIFRvcD0iNzUuNiIgV2lkdGg9IjExMy40IiBIZWlnaHQ9IjM3LjgiIFRleHQ9IuaWh+Wtl+ekuuS+i++8miIgVmVydEFsaWduPSJDZW50ZXIiIEZvbnQ9IuWui+S9kywgMTRwdCIvPjxUZXh0T2JqZWN0IE5hbWU9IlRleHQ0IiBMZWZ0PSI3NS42IiBUb3A9IjIyNi44IiBXaWR0aD0iMTEzLjQiIEhlaWdodD0iMzcuOCIgVGV4dD0i5Zu+54mH56S65L6L77yaIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCAxNHB0Ii8+PFRleHRPYmplY3QgTmFtZT0iVGV4dDUiIExlZnQ9Ijc1LjYiIFRvcD0iMzk2LjkiIFdpZHRoPSIxNDEuNzUiIEhlaWdodD0iMzcuOCIgVGV4dD0i5p2h5b2i56CB56S65L6L77yaIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCAxNHB0Ii8+PFRleHRPYmplY3QgTmFtZT0iVGV4dDYiIExlZnQ9IjMzMC43NSIgVG9wPSIzOTYuOSIgV2lkdGg9IjE0MS43NSIgSGVpZ2h0PSIzNy44IiBUZXh0PSLkuoznu7TnoIHnpLrkvovvvJoiIFZlcnRBbGlnbj0iQ2VudGVyIiBGb250PSLlrovkvZMsIDE0cHQiLz48VGV4dE9iamVjdCBOYW1lPSJUZXh0NyIgTGVmdD0iNzUuNiIgVG9wPSI1NjciIFdpZHRoPSIxNDEuNzUiIEhlaWdodD0iMzcuOCIgVGV4dD0i5Zu+5b2i56S65L6L77yaIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCAxNHB0Ii8+PFRleHRPYmplY3QgTmFtZT0iVGV4dDgiIExlZnQ9Ijc1LjYiIFRvcD0iNzM3LjEiIFdpZHRoPSIxNDEuNzUiIEhlaWdodD0iMzcuOCIgVGV4dD0i6KGo5qC856S65L6L77yaIiBWZXJ0QWxpZ249IkNlbnRlciIgRm9udD0i5a6L5L2TLCAxNHB0Ii8+PC9SZXBvcnRUaXRsZUJhbmQ+PC9SZXBvcnRQYWdlPjwvUmVwb3J0Pg==',	'210mm*297mm',	0,	'');

CREATE TABLE `roles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL COMMENT '角色名称',
  `data` varchar(256) DEFAULT '' COMMENT '备注信息',
  `fun` text NOT NULL COMMENT '功能权限',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色';

INSERT INTO `roles` (`id`, `name`, `data`, `fun`) VALUES
(1,	'超级管理员',	'#',	'{\"sys\":{\"see\":true,\"edit\":true},\"frame\":{\"see\":true,\"add\":true,\"edit\":true,\"destroy\":true},\"role\":{\"see\":true,\"add\":true,\"edit\":true,\"destroy\":true},\"user\":{\"see\":true,\"add\":true,\"edit\":true,\"destroy\":true},\"notification\":{\"see\":true,\"add\":true,\"edit\":true,\"destroy\":true},\"log\":{\"see\":true,\"destroy\":true}}');

CREATE TABLE `sys` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL COMMENT '名称',
  `key` varchar(32) NOT NULL COMMENT '标识',
  `info` text NOT NULL COMMENT '配置',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统配置';

INSERT INTO `sys` (`id`, `name`, `key`, `info`) VALUES
(1,	'基础资料',	'base',	'{\"title\":\"Admin中后台\",\"company\":\"山西点可云科技有限公司\",\"slogan\":\"化繁为简·为高效运作助力\",\"icp\":\"晋ICP备000000号\",\"notice\":\"欢迎使用Admin中后台\"}'),
(2,	'功能参数',	'fun',	'{\"report\":{\"add\":\"127.0.0.1\",\"port\":\"3676\",\"token\":\"\"}}');

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `frame` int(11) NOT NULL COMMENT '所属组织',
  `role` int(11) NOT NULL COMMENT '用户角色',
  `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名称',
  `user` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
  `pwd` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
  `avatar` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '头像',
  `email` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱地址',
  `intro` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '介绍',
  `data` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '备注信息',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户';

INSERT INTO `users` (`id`, `frame`, `role`, `name`, `user`, `pwd`, `avatar`, `email`, `intro`, `data`) VALUES
(1,	1,	1,	'管理员',	'admin',	'21232f297a57a5a743894a0e4a801fc3',	'#',	'ceo@nodcloud.com',	'化繁为简·为高效运作助力',	'');