产品介绍
Admin中后台是一款基于前后端分离的WEB自适应框架,旨在为开发者提供高效、灵活、简洁的中后台开发解决方案。它主要面向企业级应用场景,提供丰富的组件、模板和工具,帮助开发者快速构建出美观、易用、高可靠性的中后台系统。
技术框架
Admin中后台框架采用了现代前端开发技术,基于Node.js、Vite、Vue3、Element-Plus,同时也支持多种后端语言和框架。它具有模块化、易扩展等特点,使得开发者可以根据自己的需求进行定制化开发。
在线演示
演示网址:admin.nodcloud.com
依赖项
名称 | 描述 |
---|---|
vue | 核心 |
vue-router | 页面路由 |
pinia | 状态管理 |
pinia-plugin-persistedstate | 状态管理持久化 |
element-plus | 组件库 |
@element-plus/icons-vue | 图标库 |
axios | 网络请求 |
echarts | 数据可视化 |
js-base64 | base64编解码 |
mitt | 事务总线 |
moment | 时间库 |
nprogress | 进度条 |
qrcode | 二维码 |
qs | url库 |
sortablejs | 表格拖动库 |
@icon-park/vue-next | 图标库 |
@pansy/watermark | 水印库 |
@wangeditor/editor | 富文本编辑器 |
@wangeditor/editor-for-vue | 富文本扩展 |
vite | 开发构建工具 |
vite-plugin-pwa | Pwa库 |
@vitejs/plugin-vue | Vite插件 |
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配置
产品安装
下载并安装Node.js
配置npm镜像源
- 阿里源
npm config set registry https://registry.npmmirror.com
- 腾讯源
npm config set registry http://mirrors.cloud.tencent.com/npm/
安装项目依赖
npm install
- 运行项目
npm run dev
产品部署
应用打包
npm run build
将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 范围随机数 md5 md5加密 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 是否禁用 Boolean false multiple 是否多选 Boolean false placeholder 占位文本 String 请选择数据 富文本编辑器
src/components/Editor.vue
基于WangEditor封装,图像与视频采用资源管理器
属性名 说明 类型 默认值 modelValue 数据对象 String - path 资源路径 String / mode 编辑模式 String - 资源管理器
src/components/Explorer.vue
支持文件上传、多选、删除、预览、新建文件夹、名称修改、文件路径的综合资源管理器
属性名 说明 类型 默认值 path 默认路径 String / to 挂载对象 String null single 选择模式 Boolean false choice 选择回调 Function null close 关闭回调 Function null 图标选择器
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 是否返回 Boolean false slotClass 插槽类名 String slot 搜索表单
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 斑马纹 Boolean false border 纵向边框 Boolean false
类库实例
网络请求
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图标组件 参考链接
<!-- 加号图标 --> <el-icon> <Plus/> </el-icon> <!-- 减号图标 --> <el-icon> <Minus/> </el-icon>
图标实例
src/config/icons.js
基于iconPark的全局vue图标组件 参考链接
<!-- 配置图标 --> <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的实例化与封装 参考链接
//报表助手 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 参考链接 菜单路径 path 菜单路径 组件路径 component src/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', '')
配置须知
- 静态菜单与动态菜单数据不可冲突
- 动态菜单需配合后端接口提供数据使用
- 动态菜单在console组件获取
src/page/console/Console.vue > onMounted
- 菜单显示采用el-menu与
src/components/Menu.vue
组合渲染 - 菜单可视化配置需按照示例数据构建后端接口
登录页面
登录页面是应用的主要入口 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.vue record public/api/sys/record.json 数据接口 save public/api/sys/save.json 保存接口 组织架构
src/page/config/frame
名称 接口 参考 描述 Record.vue record public/api/frame/record.json 分页数据接口 destroy public/api/frame/destroy.json 数据删除接口 Form.vue info public/api/frame/info.json 数据获取接口 save public/api/frame/save.json 数据保存接口 用户角色
src/page/config/role
名称 接口 参考 描述 Record.vue record public/api/role/record.json 分页数据接口 destroy public/api/role/destroy.json 数据删除接口 Form.vue info public/api/role/info.json 数据获取接口 save public/api/role/save.json 数据保存接口 用户管理
src/page/config/user
名称 接口 参考 描述 Record.vue record public/api/user/record.json 分页数据接口 destroy public/api/user/destroy.json 数据删除接口 Form.vue info public/api/user/info.json 数据获取接口 save public/api/user/save.json 数据保存接口 通知消息
src/page/config/report
名称 接口 参考 描述 Record.vue record public/api/notification/record.json 分页数据接口 clear public/api/notification/clear.json 清空数据接口 read public/api/notification/read.json 已读数据接口 操作日志
src/page/config/log
名称 接口 参考 描述 Record.vue record public/api/log/record.json 分页数据接口 clear public/api/log/clear.json 数据清空接口 菜单管理
src/page/config/menu
名称 接口 参考 描述 Record.vue record public/api/menu/record.json 分页数据接口 destroy public/api/menu/destroy.json 数据删除接口 Form.vue data public/api/menu/data.json 菜单下拉接口 info public/api/menu/info.json 数据获取接口 save public/api/menu/save.json 数据保存接口 报表模板
src/page/config/report
名称 接口 参考 描述 Record.vue record public/api/report/record.json 分页数据接口 destroy public/api/report/destroy.json 数据删除接口 Form.vue info public/api/report/info.json 数据获取接口 save public/api/report/save.json 数据保存接口 Sheet.vue list public/api/sheet/list.json 模板列表接口 src/config/report.js update public/api/report/update.json 模板更新接口 文件管理
src/page/config/explorer
名称 接口 参考 描述 src/components/Explorer.vue record public/api/explorer/record.json 文件列表接口 destroy public/api/explorer/destroy.json 文件删除接口 edit public/api/explorer/edit.json 名称修改接口 folder public/api/explorer/folder.json 新建文件夹接口 upload public/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', '化繁为简·为高效运作助力', '');