commit 41fb7d0ac8cf78fd4bb751ddfa4fc7cfd7c22ff0 Author: 磷叶 <14103883+leaf-phos@user.noreply.gitee.com> Date: Thu Apr 17 10:19:13 2025 +0800 初始化项目 diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..f30d901 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,34 @@ +# 项目背景 +这是一个基于RuoYi-Vue的共享电动车后台管理系统,主要用于管理车辆、用户、订单等信息。 + +# 主要框架 +- Vue2 +- Element-UI +- Axios +- Vuex +- Vue-Router + +# 编码标准 +- 变量和函数名使用 camelCase 规范,组件名使用 PascalCase +- 组件的样式使用 scoped 属性,避免样式污染 +- 样式使用scss +- props、data、methods、computed、watch 生命周期函数等都使用驼峰命名 +- 对于全局能够重复使用的组件,封装成组件,并放在 src/components 目录下 +- 对于业务能够重复使用的组件,封装成组件,并放在 src/views/{模块名}/components 目录下 +- 所有请求都需要在 src/api/bst/{模块名}.js 文件中定义,并保持统一的命名规范,使用时统一调用 +- 优先封装成组件 +- 生成的代码需要自动导入相关依赖,这点很重要 +- 遵循阿里巴巴开发手册 + +# 项目结构 +- src/api 目录下存放接口文件 +- src/assets 目录下存放静态资源 +- src/components 目录下存放全局组件 +- src/views 目录下存放页面 +- src/utils/index.js 文件下存放工具方法 +- src/utils/constants.js 文件下存放常量 +- src/views/bst 目录下存放业务相关页面 +- src/views/system 目录下存放系统相关页面 + +# 文档规范 +- 使用 JSDoc 格式编写函数和组件的注释 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7034f9b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# 告诉EditorConfig插件,这是根文件,不用继续往上查找 +root = true + +# 匹配全部文件 +[*] +# 设置字符集 +charset = utf-8 +# 缩进风格,可选space、tab +indent_style = space +# 缩进的空格数 +indent_size = 2 +# 结尾换行符,可选lf、cr、crlf +end_of_line = lf +# 在文件结尾插入新行 +insert_final_newline = true +# 删除一行中的前后空格 +trim_trailing_whitespace = true + +# 匹配md结尾的文件 +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..3f8e523 --- /dev/null +++ b/.env.development @@ -0,0 +1,17 @@ +# 页面标题 +VUE_APP_TITLE = 电动车v2 + +# 开发环境配置 +ENV = 'development' + +# 若依管理系统/开发环境 +VUE_APP_BASE_API = '/dev-api' + +# 路由懒加载 +VUE_CLI_BABEL_TRANSPILE_MODULES = true + +# 七牛云域名 +VUE_APP_QINIU_DOMAIN = 'https://api.ccttiot.com' + +# WebSocket地址 +VUE_APP_WS_HOST = 'ws://localhost:4001' diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..ee5838a --- /dev/null +++ b/.env.production @@ -0,0 +1,14 @@ +# 页面标题 +VUE_APP_TITLE = 小鹿骑行管理系统 + +# 生产环境配置 +ENV = 'production' + +# 若依管理系统/生产环境 +VUE_APP_BASE_API = '/prod-api' + +# 七牛云域名 +VUE_APP_QINIU_DOMAIN = 'https://api.ccttiot.com' + +# WebSocket地址 +VUE_APP_WS_HOST = 'wss://pm.chuangtewl.com/prod-api' \ No newline at end of file diff --git a/.env.staging b/.env.staging new file mode 100644 index 0000000..a6f477a --- /dev/null +++ b/.env.staging @@ -0,0 +1,10 @@ +# 页面标题 +VUE_APP_TITLE = 小鹿骑行管理系统 + +NODE_ENV = production + +# 测试环境配置 +ENV = 'staging' + +# 若依管理系统/测试环境 +VUE_APP_BASE_API = '/stage-api' diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..89be6f6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,10 @@ +# 忽略build目录下类型为js的文件的语法检查 +build/*.js +# 忽略src/assets目录下文件的语法检查 +src/assets +# 忽略public目录下文件的语法检查 +public +# 忽略当前目录下为js的文件的语法检查 +*.js +# 忽略当前目录下为vue的文件的语法检查 +*.vue \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..82bbdee --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,199 @@ +// ESlint 检查配置 +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint', + sourceType: 'module' + }, + env: { + browser: true, + node: true, + es6: true, + }, + extends: ['plugin:vue/recommended', 'eslint:recommended'], + + // add your custom rules here + //it is base on https://github.com/vuejs/eslint-config-vue + rules: { + "vue/max-attributes-per-line": [2, { + "singleline": 10, + "multiline": { + "max": 1, + "allowFirstLine": false + } + }], + "vue/singleline-html-element-content-newline": "off", + "vue/multiline-html-element-content-newline":"off", + "vue/name-property-casing": ["error", "PascalCase"], + "vue/no-v-html": "off", + 'accessor-pairs': 2, + 'arrow-spacing': [2, { + 'before': true, + 'after': true + }], + 'block-spacing': [2, 'always'], + 'brace-style': [2, '1tbs', { + 'allowSingleLine': true + }], + 'camelcase': [0, { + 'properties': 'always' + }], + 'comma-dangle': [2, 'never'], + 'comma-spacing': [2, { + 'before': false, + 'after': true + }], + 'comma-style': [2, 'last'], + 'constructor-super': 2, + 'curly': [2, 'multi-line'], + 'dot-location': [2, 'property'], + 'eol-last': 2, + 'eqeqeq': ["error", "always", {"null": "ignore"}], + 'generator-star-spacing': [2, { + 'before': true, + 'after': true + }], + 'handle-callback-err': [2, '^(err|error)$'], + 'indent': [2, 2, { + 'SwitchCase': 1 + }], + 'jsx-quotes': [2, 'prefer-single'], + 'key-spacing': [2, { + 'beforeColon': false, + 'afterColon': true + }], + 'keyword-spacing': [2, { + 'before': true, + 'after': true + }], + 'new-cap': [2, { + 'newIsCap': true, + 'capIsNew': false + }], + 'new-parens': 2, + 'no-array-constructor': 2, + 'no-caller': 2, + 'no-console': 'off', + 'no-class-assign': 2, + 'no-cond-assign': 2, + 'no-const-assign': 2, + 'no-control-regex': 0, + 'no-delete-var': 2, + 'no-dupe-args': 2, + 'no-dupe-class-members': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-empty-character-class': 2, + 'no-empty-pattern': 2, + 'no-eval': 2, + 'no-ex-assign': 2, + 'no-extend-native': 2, + 'no-extra-bind': 2, + 'no-extra-boolean-cast': 2, + 'no-extra-parens': [2, 'functions'], + 'no-fallthrough': 2, + 'no-floating-decimal': 2, + 'no-func-assign': 2, + 'no-implied-eval': 2, + 'no-inner-declarations': [2, 'functions'], + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 2, + 'no-iterator': 2, + 'no-label-var': 2, + 'no-labels': [2, { + 'allowLoop': false, + 'allowSwitch': false + }], + 'no-lone-blocks': 2, + 'no-mixed-spaces-and-tabs': 2, + 'no-multi-spaces': 2, + 'no-multi-str': 2, + 'no-multiple-empty-lines': [2, { + 'max': 1 + }], + 'no-native-reassign': 2, + 'no-negated-in-lhs': 2, + 'no-new-object': 2, + 'no-new-require': 2, + 'no-new-symbol': 2, + 'no-new-wrappers': 2, + 'no-obj-calls': 2, + 'no-octal': 2, + 'no-octal-escape': 2, + 'no-path-concat': 2, + 'no-proto': 2, + 'no-redeclare': 2, + 'no-regex-spaces': 2, + 'no-return-assign': [2, 'except-parens'], + 'no-self-assign': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-shadow-restricted-names': 2, + 'no-spaced-func': 2, + 'no-sparse-arrays': 2, + 'no-this-before-super': 2, + 'no-throw-literal': 2, + 'no-trailing-spaces': 2, + 'no-undef': 2, + 'no-undef-init': 2, + 'no-unexpected-multiline': 2, + 'no-unmodified-loop-condition': 2, + 'no-unneeded-ternary': [2, { + 'defaultAssignment': false + }], + 'no-unreachable': 2, + 'no-unsafe-finally': 2, + 'no-unused-vars': [2, { + 'vars': 'all', + 'args': 'none' + }], + 'no-useless-call': 2, + 'no-useless-computed-key': 2, + 'no-useless-constructor': 2, + 'no-useless-escape': 0, + 'no-whitespace-before-property': 2, + 'no-with': 2, + 'one-var': [2, { + 'initialized': 'never' + }], + 'operator-linebreak': [2, 'after', { + 'overrides': { + '?': 'before', + ':': 'before' + } + }], + 'padded-blocks': [2, 'never'], + 'quotes': [2, 'single', { + 'avoidEscape': true, + 'allowTemplateLiterals': true + }], + 'semi': [2, 'never'], + 'semi-spacing': [2, { + 'before': false, + 'after': true + }], + 'space-before-blocks': [2, 'always'], + 'space-before-function-paren': [2, 'never'], + 'space-in-parens': [2, 'never'], + 'space-infix-ops': 2, + 'space-unary-ops': [2, { + 'words': true, + 'nonwords': false + }], + 'spaced-comment': [2, 'always', { + 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] + }], + 'template-curly-spacing': [2, 'never'], + 'use-isnan': 2, + 'valid-typeof': 2, + 'wrap-iife': [2, 'any'], + 'yield-star-spacing': [2, 'both'], + 'yoda': [2, 'never'], + 'prefer-const': 2, + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, + 'object-curly-spacing': [2, 'always', { + objectsInObjects: false + }], + 'array-bracket-spacing': [2, 'never'] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82c3521 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +**/*.log + +tests/**/coverage/ +tests/e2e/reports +selenium-debug.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock +dist.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..00c0ab8 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +## 开发 + +```bash +# 克隆项目 +git clone https://gitee.com/y_project/RuoYi-Vue + +# 进入项目目录 +cd ruoyi-ui + +# 安装依赖 +npm install + +# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 +npm install --registry=https://registry.npmmirror.com + +# 启动服务 +npm run dev +``` + +浏览器访问 http://localhost:80 + +## 发布 + +```bash +# 构建测试环境 +npm run build:stage + +# 构建生产环境 +npm run build:prod +``` \ No newline at end of file diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..c8267b2 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,13 @@ +module.exports = { + presets: [ + // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app + '@vue/cli-plugin-babel/preset' + ], + 'env': { + 'development': { + // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). + // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. + 'plugins': ['dynamic-import-node'] + } + } +} \ No newline at end of file diff --git a/bin/build.bat b/bin/build.bat new file mode 100644 index 0000000..dda590d --- /dev/null +++ b/bin/build.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅdistļ +echo. + +%~d0 +cd %~dp0 + +cd .. +npm run build:prod + +pause \ No newline at end of file diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..0e5bc0f --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] װWeḅnode_modulesļ +echo. + +%~d0 +cd %~dp0 + +cd .. +npm install --registry=https://registry.npmmirror.com + +pause \ No newline at end of file diff --git a/bin/run-web.bat b/bin/run-web.bat new file mode 100644 index 0000000..d30deae --- /dev/null +++ b/bin/run-web.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] ʹ Vue CLI Web ̡ +echo. + +%~d0 +cd %~dp0 + +cd .. +npm run dev + +pause \ No newline at end of file diff --git a/build/index.js b/build/index.js new file mode 100644 index 0000000..0c57de2 --- /dev/null +++ b/build/index.js @@ -0,0 +1,35 @@ +const { run } = require('runjs') +const chalk = require('chalk') +const config = require('../vue.config.js') +const rawArgv = process.argv.slice(2) +const args = rawArgv.join(' ') + +if (process.env.npm_config_preview || rawArgv.includes('--preview')) { + const report = rawArgv.includes('--report') + + run(`vue-cli-service build ${args}`) + + const port = 9526 + const publicPath = config.publicPath + + var connect = require('connect') + var serveStatic = require('serve-static') + const app = connect() + + app.use( + publicPath, + serveStatic('./dist', { + index: ['index.html', '/'] + }) + ) + + app.listen(port, function () { + console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) + if (report) { + console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) + } + + }) +} else { + run(`vue-cli-service build ${args}`) +} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..af62672 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..1832293 --- /dev/null +++ b/package.json @@ -0,0 +1,97 @@ +{ + "name": "ruoyi", + "version": "3.8.8", + "description": "若依管理系统", + "author": "若依", + "license": "MIT", + "scripts": { + "dev": "vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "preview": "node build/index.js --preview", + "lint": "eslint --ext .js,.vue src" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "src/**/*.{js,vue}": [ + "eslint --fix", + "git add" + ] + }, + "keywords": [ + "vue", + "admin", + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" + ], + "repository": { + "type": "git", + "url": "https://gitee.com/y_project/RuoYi-Vue.git" + }, + "dependencies": { + "@amap/amap-jsapi-loader": "^1.0.1", + "@pansy/watermark": "^2.3.0", + "@riophae/vue-treeselect": "0.4.0", + "axios": "0.28.1", + "clipboard": "2.0.8", + "core-js": "3.37.1", + "decimal.js": "^10.4.3", + "echarts": "5.4.0", + "element-ui": "2.15.14", + "file-saver": "2.0.5", + "fuse.js": "6.4.3", + "highlight.js": "9.18.5", + "js-beautify": "1.13.0", + "js-cookie": "3.0.1", + "jsencrypt": "3.0.0-rc.1", + "nprogress": "0.2.0", + "qrcode": "^1.5.4", + "quill": "2.0.2", + "screenfull": "5.0.2", + "sortablejs": "1.10.2", + "vant": "^2.13.2", + "vue": "2.6.12", + "vue-count-to": "1.0.13", + "vue-cropper": "0.5.5", + "vue-masonry-css": "^1.0.3", + "vue-meta": "2.4.0", + "vue-router": "3.4.9", + "vuedraggable": "2.24.3", + "vuex": "3.6.0", + "weixin-js-sdk": "^1.6.5" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "4.4.6", + "@vue/cli-plugin-eslint": "4.4.6", + "@vue/cli-service": "4.4.6", + "babel-eslint": "10.1.0", + "babel-plugin-dynamic-import-node": "2.3.3", + "chalk": "4.1.0", + "compression-webpack-plugin": "6.1.2", + "connect": "3.6.6", + "eslint": "7.15.0", + "eslint-plugin-vue": "7.2.0", + "lint-staged": "10.5.3", + "runjs": "4.4.2", + "sass": "1.32.13", + "sass-loader": "10.1.1", + "script-ext-html-webpack-plugin": "2.1.5", + "svg-sprite-loader": "5.1.1", + "vue-template-compiler": "2.6.12" + }, + "engines": { + "node": ">=8.9", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions" + ] +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..05726a9 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/html/ie.html b/public/html/ie.html new file mode 100644 index 0000000..052ffcd --- /dev/null +++ b/public/html/ie.html @@ -0,0 +1,46 @@ + + + + + + 请升级您的浏览器 + + + + + + +

请升级您的浏览器,以便我们更好的为您提供服务!

+

您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。

+
+

请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束

+

自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明

+
+

您可以选择更先进的浏览器

+

推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。

+ +
+ + \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..2089af7 --- /dev/null +++ b/public/index.html @@ -0,0 +1,213 @@ + + + + + + + + + <%= webpackConfig.name %> + + + + +
+
+
+
+
+
正在加载系统资源,请耐心等待
+
+
+ + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..77470cb --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..bfaad31 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,28 @@ + + + + diff --git a/src/api/bst/commandLog.js b/src/api/bst/commandLog.js new file mode 100644 index 0000000..ffaa21c --- /dev/null +++ b/src/api/bst/commandLog.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询命令日志列表 +export function listCommandLog(query) { + return request({ + url: '/bst/commandLog/list', + method: 'get', + params: query + }) +} + +// 查询命令日志详细 +export function getCommandLog(id) { + return request({ + url: '/bst/commandLog/' + id, + method: 'get' + }) +} + +// 新增命令日志 +export function addCommandLog(data) { + return request({ + url: '/bst/commandLog', + method: 'post', + data: data + }) +} + +// 修改命令日志 +export function updateCommandLog(data) { + return request({ + url: '/bst/commandLog', + method: 'put', + data: data + }) +} + +// 删除命令日志 +export function delCommandLog(id) { + return request({ + url: '/bst/commandLog/' + id, + method: 'delete' + }) +} diff --git a/src/api/common.js b/src/api/common.js new file mode 100644 index 0000000..eacfa19 --- /dev/null +++ b/src/api/common.js @@ -0,0 +1,9 @@ +import request from '@/utils/request'; + +// 获取七牛云token +export function getQiniuToken() { + return request({ + url: '/common/qiniuToken', + method: 'get' + }); +} \ No newline at end of file diff --git a/src/api/dashboard/dashboard.js b/src/api/dashboard/dashboard.js new file mode 100644 index 0000000..1a93c4e --- /dev/null +++ b/src/api/dashboard/dashboard.js @@ -0,0 +1,27 @@ +import request from '@/utils/request' + +/** + * 获取统计数据 + * @param {Object} query 查询参数 + * @returns {Promise} 返回统计数据 + */ +export function getStat(query) { + return request({ + url: '/dashboard/stat', + method: 'get', + params: query + }) +} + +/** + * 获取每日统计数据 + * @param {Object} query 查询参数 + * @returns {Promise} 返回每日统计数据 + */ +export function getDailyAmount(query) { + return request({ + url: '/dashboard/dailyAmount', + method: 'get', + params: query + }) +} diff --git a/src/api/dashboard/dashboardOrder.js b/src/api/dashboard/dashboardOrder.js new file mode 100644 index 0000000..50a98e0 --- /dev/null +++ b/src/api/dashboard/dashboardOrder.js @@ -0,0 +1,27 @@ +import request from '@/utils/request' + +/** + * 获取统计数据 + * @param {Object} query 查询参数 + * @returns {Promise} 返回统计数据 + */ +export function getOrderDailyAmount(query) { + return request({ + url: '/dashboard/order/dailyAmount', + method: 'get', + params: query + }) +} + +/** + * 获取订单排行榜数据 + * @param {Object} query 查询参数 + * @returns {Promise} 返回排行榜数据 + */ +export function getOrderRank(query) { + return request({ + url: '/dashboard/order/rank', + method: 'get', + params: query + }) +} diff --git a/src/api/login.js b/src/api/login.js new file mode 100644 index 0000000..7b7388f --- /dev/null +++ b/src/api/login.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 登录方法 +export function login(username, password, code, uuid) { + const data = { + username, + password, + code, + uuid + } + return request({ + url: '/login', + headers: { + isToken: false, + repeatSubmit: false + }, + method: 'post', + data: data + }) +} + +// 注册方法 +export function register(data) { + return request({ + url: '/register', + headers: { + isToken: false + }, + method: 'post', + data: data + }) +} + +// 获取用户详细信息 +export function getInfo() { + return request({ + url: '/getInfo', + method: 'get' + }) +} + +// 退出方法 +export function logout() { + return request({ + url: '/logout', + method: 'post' + }) +} + +// 获取验证码 +export function getCodeImg() { + return request({ + url: '/captchaImage', + headers: { + isToken: false + }, + method: 'get', + timeout: 20000 + }) +} \ No newline at end of file diff --git a/src/api/menu.js b/src/api/menu.js new file mode 100644 index 0000000..faef101 --- /dev/null +++ b/src/api/menu.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取路由 +export const getRouters = () => { + return request({ + url: '/getRouters', + method: 'get' + }) +} \ No newline at end of file diff --git a/src/api/monitor/cache.js b/src/api/monitor/cache.js new file mode 100644 index 0000000..72c5f6a --- /dev/null +++ b/src/api/monitor/cache.js @@ -0,0 +1,57 @@ +import request from '@/utils/request' + +// 查询缓存详细 +export function getCache() { + return request({ + url: '/monitor/cache', + method: 'get' + }) +} + +// 查询缓存名称列表 +export function listCacheName() { + return request({ + url: '/monitor/cache/getNames', + method: 'get' + }) +} + +// 查询缓存键名列表 +export function listCacheKey(cacheName) { + return request({ + url: '/monitor/cache/getKeys/' + cacheName, + method: 'get' + }) +} + +// 查询缓存内容 +export function getCacheValue(cacheName, cacheKey) { + return request({ + url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, + method: 'get' + }) +} + +// 清理指定名称缓存 +export function clearCacheName(cacheName) { + return request({ + url: '/monitor/cache/clearCacheName/' + cacheName, + method: 'delete' + }) +} + +// 清理指定键名缓存 +export function clearCacheKey(cacheKey) { + return request({ + url: '/monitor/cache/clearCacheKey/' + cacheKey, + method: 'delete' + }) +} + +// 清理全部缓存 +export function clearCacheAll() { + return request({ + url: '/monitor/cache/clearCacheAll', + method: 'delete' + }) +} diff --git a/src/api/monitor/job.js b/src/api/monitor/job.js new file mode 100644 index 0000000..3815569 --- /dev/null +++ b/src/api/monitor/job.js @@ -0,0 +1,71 @@ +import request from '@/utils/request' + +// 查询定时任务调度列表 +export function listJob(query) { + return request({ + url: '/monitor/job/list', + method: 'get', + params: query + }) +} + +// 查询定时任务调度详细 +export function getJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'get' + }) +} + +// 新增定时任务调度 +export function addJob(data) { + return request({ + url: '/monitor/job', + method: 'post', + data: data + }) +} + +// 修改定时任务调度 +export function updateJob(data) { + return request({ + url: '/monitor/job', + method: 'put', + data: data + }) +} + +// 删除定时任务调度 +export function delJob(jobId) { + return request({ + url: '/monitor/job/' + jobId, + method: 'delete' + }) +} + +// 任务状态修改 +export function changeJobStatus(jobId, status) { + const data = { + jobId, + status + } + return request({ + url: '/monitor/job/changeStatus', + method: 'put', + data: data + }) +} + + +// 定时任务立即执行一次 +export function runJob(jobId, jobGroup) { + const data = { + jobId, + jobGroup + } + return request({ + url: '/monitor/job/run', + method: 'put', + data: data + }) +} \ No newline at end of file diff --git a/src/api/monitor/jobLog.js b/src/api/monitor/jobLog.js new file mode 100644 index 0000000..6e0be61 --- /dev/null +++ b/src/api/monitor/jobLog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询调度日志列表 +export function listJobLog(query) { + return request({ + url: '/monitor/jobLog/list', + method: 'get', + params: query + }) +} + +// 删除调度日志 +export function delJobLog(jobLogId) { + return request({ + url: '/monitor/jobLog/' + jobLogId, + method: 'delete' + }) +} + +// 清空调度日志 +export function cleanJobLog() { + return request({ + url: '/monitor/jobLog/clean', + method: 'delete' + }) +} diff --git a/src/api/monitor/logininfor.js b/src/api/monitor/logininfor.js new file mode 100644 index 0000000..4d112b7 --- /dev/null +++ b/src/api/monitor/logininfor.js @@ -0,0 +1,34 @@ +import request from '@/utils/request' + +// 查询登录日志列表 +export function list(query) { + return request({ + url: '/monitor/logininfor/list', + method: 'get', + params: query + }) +} + +// 删除登录日志 +export function delLogininfor(infoId) { + return request({ + url: '/monitor/logininfor/' + infoId, + method: 'delete' + }) +} + +// 解锁用户登录状态 +export function unlockLogininfor(userName) { + return request({ + url: '/monitor/logininfor/unlock/' + userName, + method: 'get' + }) +} + +// 清空登录日志 +export function cleanLogininfor() { + return request({ + url: '/monitor/logininfor/clean', + method: 'delete' + }) +} diff --git a/src/api/monitor/online.js b/src/api/monitor/online.js new file mode 100644 index 0000000..bd22137 --- /dev/null +++ b/src/api/monitor/online.js @@ -0,0 +1,18 @@ +import request from '@/utils/request' + +// 查询在线用户列表 +export function list(query) { + return request({ + url: '/monitor/online/list', + method: 'get', + params: query + }) +} + +// 强退用户 +export function forceLogout(tokenId) { + return request({ + url: '/monitor/online/' + tokenId, + method: 'delete' + }) +} diff --git a/src/api/monitor/operlog.js b/src/api/monitor/operlog.js new file mode 100644 index 0000000..a04bca8 --- /dev/null +++ b/src/api/monitor/operlog.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 查询操作日志列表 +export function list(query) { + return request({ + url: '/monitor/operlog/list', + method: 'get', + params: query + }) +} + +// 删除操作日志 +export function delOperlog(operId) { + return request({ + url: '/monitor/operlog/' + operId, + method: 'delete' + }) +} + +// 清空操作日志 +export function cleanOperlog() { + return request({ + url: '/monitor/operlog/clean', + method: 'delete' + }) +} diff --git a/src/api/monitor/server.js b/src/api/monitor/server.js new file mode 100644 index 0000000..e1f9ca2 --- /dev/null +++ b/src/api/monitor/server.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +// 获取服务信息 +export function getServer() { + return request({ + url: '/monitor/server', + method: 'get' + }) +} \ No newline at end of file diff --git a/src/api/system/config.js b/src/api/system/config.js new file mode 100644 index 0000000..a404d82 --- /dev/null +++ b/src/api/system/config.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询参数列表 +export function listConfig(query) { + return request({ + url: '/system/config/list', + method: 'get', + params: query + }) +} + +// 查询参数详细 +export function getConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'get' + }) +} + +// 根据参数键名查询参数值 +export function getConfigKey(configKey) { + return request({ + url: '/system/config/configKey/' + configKey, + method: 'get' + }) +} + +// 新增参数配置 +export function addConfig(data) { + return request({ + url: '/system/config', + method: 'post', + data: data + }) +} + +// 修改参数配置 +export function updateConfig(data) { + return request({ + url: '/system/config', + method: 'put', + data: data + }) +} + +// 删除参数配置 +export function delConfig(configId) { + return request({ + url: '/system/config/' + configId, + method: 'delete' + }) +} + +// 刷新参数缓存 +export function refreshCache() { + return request({ + url: '/system/config/refreshCache', + method: 'delete' + }) +} diff --git a/src/api/system/dept.js b/src/api/system/dept.js new file mode 100644 index 0000000..186633c --- /dev/null +++ b/src/api/system/dept.js @@ -0,0 +1,64 @@ +import request from '@/utils/request' + +// 查询部门列表 +export function listDept(query) { + return request({ + url: '/system/dept/list', + method: 'get', + params: query + }) +} + +// 查询部门列表 +export function listDeptByIds(ids) { + return request({ + url: '/system/dept/listByIds', + method: 'post', + headers: { + repeatSubmit: false, + }, + data: ids + }) +} + +// 查询部门列表(排除节点) +export function listDeptExcludeChild(deptId) { + return request({ + url: '/system/dept/list/exclude/' + deptId, + method: 'get' + }) +} + +// 查询部门详细 +export function getDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'get' + }) +} + +// 新增部门 +export function addDept(data) { + return request({ + url: '/system/dept', + method: 'post', + data: data + }) +} + +// 修改部门 +export function updateDept(data) { + return request({ + url: '/system/dept', + method: 'put', + data: data + }) +} + +// 删除部门 +export function delDept(deptId) { + return request({ + url: '/system/dept/' + deptId, + method: 'delete' + }) +} diff --git a/src/api/system/dict/data.js b/src/api/system/dict/data.js new file mode 100644 index 0000000..6c9eb79 --- /dev/null +++ b/src/api/system/dict/data.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +// 查询字典数据列表 +export function listData(query) { + return request({ + url: '/system/dict/data/list', + method: 'get', + params: query + }) +} + +// 查询字典数据详细 +export function getData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'get' + }) +} + +// 根据字典类型查询字典数据信息 +export function getDicts(dictType) { + return request({ + url: '/system/dict/data/type/' + dictType, + method: 'get' + }) +} + +// 新增字典数据 +export function addData(data) { + return request({ + url: '/system/dict/data', + method: 'post', + data: data + }) +} + +// 修改字典数据 +export function updateData(data) { + return request({ + url: '/system/dict/data', + method: 'put', + data: data + }) +} + +// 删除字典数据 +export function delData(dictCode) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'delete' + }) +} diff --git a/src/api/system/dict/type.js b/src/api/system/dict/type.js new file mode 100644 index 0000000..a7a6e01 --- /dev/null +++ b/src/api/system/dict/type.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询字典类型列表 +export function listType(query) { + return request({ + url: '/system/dict/type/list', + method: 'get', + params: query + }) +} + +// 查询字典类型详细 +export function getType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'get' + }) +} + +// 新增字典类型 +export function addType(data) { + return request({ + url: '/system/dict/type', + method: 'post', + data: data + }) +} + +// 修改字典类型 +export function updateType(data) { + return request({ + url: '/system/dict/type', + method: 'put', + data: data + }) +} + +// 删除字典类型 +export function delType(dictId) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'delete' + }) +} + +// 刷新字典缓存 +export function refreshCache() { + return request({ + url: '/system/dict/type/refreshCache', + method: 'delete' + }) +} + +// 获取字典选择框列表 +export function optionselect() { + return request({ + url: '/system/dict/type/optionselect', + method: 'get' + }) +} \ No newline at end of file diff --git a/src/api/system/menu.js b/src/api/system/menu.js new file mode 100644 index 0000000..f6415c6 --- /dev/null +++ b/src/api/system/menu.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +// 查询菜单列表 +export function listMenu(query) { + return request({ + url: '/system/menu/list', + method: 'get', + params: query + }) +} + +// 查询菜单详细 +export function getMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'get' + }) +} + +// 查询菜单下拉树结构 +export function treeselect() { + return request({ + url: '/system/menu/treeselect', + method: 'get' + }) +} + +// 根据角色ID查询菜单下拉树结构 +export function roleMenuTreeselect(roleId) { + return request({ + url: '/system/menu/roleMenuTreeselect/' + roleId, + method: 'get' + }) +} + +// 新增菜单 +export function addMenu(data) { + return request({ + url: '/system/menu', + method: 'post', + data: data + }) +} + +// 修改菜单 +export function updateMenu(data) { + return request({ + url: '/system/menu', + method: 'put', + data: data + }) +} + +// 删除菜单 +export function delMenu(menuId) { + return request({ + url: '/system/menu/' + menuId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/src/api/system/notice.js b/src/api/system/notice.js new file mode 100644 index 0000000..c274ea5 --- /dev/null +++ b/src/api/system/notice.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询公告列表 +export function listNotice(query) { + return request({ + url: '/system/notice/list', + method: 'get', + params: query + }) +} + +// 查询公告详细 +export function getNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'get' + }) +} + +// 新增公告 +export function addNotice(data) { + return request({ + url: '/system/notice', + method: 'post', + data: data + }) +} + +// 修改公告 +export function updateNotice(data) { + return request({ + url: '/system/notice', + method: 'put', + data: data + }) +} + +// 删除公告 +export function delNotice(noticeId) { + return request({ + url: '/system/notice/' + noticeId, + method: 'delete' + }) +} \ No newline at end of file diff --git a/src/api/system/post.js b/src/api/system/post.js new file mode 100644 index 0000000..1a8e9ca --- /dev/null +++ b/src/api/system/post.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询岗位列表 +export function listPost(query) { + return request({ + url: '/system/post/list', + method: 'get', + params: query + }) +} + +// 查询岗位详细 +export function getPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'get' + }) +} + +// 新增岗位 +export function addPost(data) { + return request({ + url: '/system/post', + method: 'post', + data: data + }) +} + +// 修改岗位 +export function updatePost(data) { + return request({ + url: '/system/post', + method: 'put', + data: data + }) +} + +// 删除岗位 +export function delPost(postId) { + return request({ + url: '/system/post/' + postId, + method: 'delete' + }) +} diff --git a/src/api/system/role.js b/src/api/system/role.js new file mode 100644 index 0000000..f13e6f4 --- /dev/null +++ b/src/api/system/role.js @@ -0,0 +1,119 @@ +import request from '@/utils/request' + +// 查询角色列表 +export function listRole(query) { + return request({ + url: '/system/role/list', + method: 'get', + params: query + }) +} + +// 查询角色详细 +export function getRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'get' + }) +} + +// 新增角色 +export function addRole(data) { + return request({ + url: '/system/role', + method: 'post', + data: data + }) +} + +// 修改角色 +export function updateRole(data) { + return request({ + url: '/system/role', + method: 'put', + data: data + }) +} + +// 角色数据权限 +export function dataScope(data) { + return request({ + url: '/system/role/dataScope', + method: 'put', + data: data + }) +} + +// 角色状态修改 +export function changeRoleStatus(roleId, status) { + const data = { + roleId, + status + } + return request({ + url: '/system/role/changeStatus', + method: 'put', + data: data + }) +} + +// 删除角色 +export function delRole(roleId) { + return request({ + url: '/system/role/' + roleId, + method: 'delete' + }) +} + +// 查询角色已授权用户列表 +export function allocatedUserList(query) { + return request({ + url: '/system/role/authUser/allocatedList', + method: 'get', + params: query + }) +} + +// 查询角色未授权用户列表 +export function unallocatedUserList(query) { + return request({ + url: '/system/role/authUser/unallocatedList', + method: 'get', + params: query + }) +} + +// 取消用户授权角色 +export function authUserCancel(data) { + return request({ + url: '/system/role/authUser/cancel', + method: 'put', + data: data + }) +} + +// 批量取消用户授权角色 +export function authUserCancelAll(data) { + return request({ + url: '/system/role/authUser/cancelAll', + method: 'put', + params: data + }) +} + +// 授权用户选择 +export function authUserSelectAll(data) { + return request({ + url: '/system/role/authUser/selectAll', + method: 'put', + params: data + }) +} + +// 根据角色ID查询部门树结构 +export function deptTreeSelect(roleId) { + return request({ + url: '/system/role/deptTree/' + roleId, + method: 'get' + }) +} diff --git a/src/api/system/user.js b/src/api/system/user.js new file mode 100644 index 0000000..ec886ac --- /dev/null +++ b/src/api/system/user.js @@ -0,0 +1,156 @@ +import request from '@/utils/request' +import { parseStrEmpty } from '@/utils/ruoyi' + +// 查询用户列表 +export function listUser(query) { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }) +} + +// 查询所有用户 +export function listAllUser() { + return request({ + url: '/system/user/listAll', + method: 'get' + }) +} + +// 查询用户列表ByIds +export function listUserByIds(userIds) { + return request({ + url: '/system/user/listByIds', + headers: { + repeatSubmit: false, + }, + method: 'post', + data: userIds + }) +} + +// 查询用户详细 +export function getUser(userId) { + return request({ + url: '/system/user/' + parseStrEmpty(userId), + method: 'get' + }) +} + +// 新增用户 +export function addUser(data) { + return request({ + url: '/system/user', + method: 'post', + data: data + }) +} + +// 修改用户 +export function updateUser(data) { + return request({ + url: '/system/user', + method: 'put', + data: data + }) +} + +// 删除用户 +export function delUser(userId) { + return request({ + url: '/system/user/' + userId, + method: 'delete' + }) +} + +// 用户密码重置 +export function resetUserPwd(userId, password) { + const data = { + userId, + password + } + return request({ + url: '/system/user/resetPwd', + method: 'put', + data: data + }) +} + +// 用户状态修改 +export function changeUserStatus(userId, status) { + const data = { + userId, + status + } + return request({ + url: '/system/user/changeStatus', + method: 'put', + data: data + }) +} + +// 查询用户个人信息 +export function getUserProfile() { + return request({ + url: '/system/user/profile', + method: 'get' + }) +} + +// 修改用户个人信息 +export function updateUserProfile(data) { + return request({ + url: '/system/user/profile', + method: 'put', + data: data + }) +} + +// 用户密码重置 +export function updateUserPwd(oldPassword, newPassword) { + const data = { + oldPassword, + newPassword + } + return request({ + url: '/system/user/profile/updatePwd', + method: 'put', + params: data + }) +} + +// 用户头像上传 +export function uploadAvatar(data) { + return request({ + url: '/system/user/profile/avatar', + method: 'post', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: data + }) +} + +// 查询授权角色 +export function getAuthRole(userId) { + return request({ + url: '/system/user/authRole/' + userId, + method: 'get' + }) +} + +// 保存授权角色 +export function updateAuthRole(data) { + return request({ + url: '/system/user/authRole', + method: 'put', + params: data + }) +} + +// 查询部门下拉树结构 +export function deptTreeSelect() { + return request({ + url: '/system/user/deptTree', + method: 'get' + }) +} diff --git a/src/api/tool/gen.js b/src/api/tool/gen.js new file mode 100644 index 0000000..2075677 --- /dev/null +++ b/src/api/tool/gen.js @@ -0,0 +1,85 @@ +import request from '@/utils/request' + +// 查询生成表数据 +export function listTable(query) { + return request({ + url: '/tool/gen/list', + method: 'get', + params: query + }) +} +// 查询db数据库列表 +export function listDbTable(query) { + return request({ + url: '/tool/gen/db/list', + method: 'get', + params: query + }) +} + +// 查询表详细信息 +export function getGenTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'get' + }) +} + +// 修改代码生成信息 +export function updateGenTable(data) { + return request({ + url: '/tool/gen', + method: 'put', + data: data + }) +} + +// 导入表 +export function importTable(data) { + return request({ + url: '/tool/gen/importTable', + method: 'post', + params: data + }) +} + +// 创建表 +export function createTable(data) { + return request({ + url: '/tool/gen/createTable', + method: 'post', + params: data + }) +} + +// 预览生成代码 +export function previewTable(tableId) { + return request({ + url: '/tool/gen/preview/' + tableId, + method: 'get' + }) +} + +// 删除表数据 +export function delTable(tableId) { + return request({ + url: '/tool/gen/' + tableId, + method: 'delete' + }) +} + +// 生成代码(自定义路径) +export function genCode(tableName) { + return request({ + url: '/tool/gen/genCode/' + tableName, + method: 'get' + }) +} + +// 同步数据库 +export function synchDb(tableName) { + return request({ + url: '/tool/gen/synchDb/' + tableName, + method: 'get' + }) +} diff --git a/src/assets/401_images/401.gif b/src/assets/401_images/401.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/src/assets/401_images/401.gif differ diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/src/assets/404_images/404.png differ diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ diff --git a/src/assets/fileicons/excel.png b/src/assets/fileicons/excel.png new file mode 100644 index 0000000..5180e73 Binary files /dev/null and b/src/assets/fileicons/excel.png differ diff --git a/src/assets/fileicons/pdf.png b/src/assets/fileicons/pdf.png new file mode 100644 index 0000000..6b7fcc9 Binary files /dev/null and b/src/assets/fileicons/pdf.png differ diff --git a/src/assets/fileicons/ppt.png b/src/assets/fileicons/ppt.png new file mode 100644 index 0000000..0e7ba8d Binary files /dev/null and b/src/assets/fileicons/ppt.png differ diff --git a/src/assets/fileicons/unknown.png b/src/assets/fileicons/unknown.png new file mode 100644 index 0000000..cf7bd02 Binary files /dev/null and b/src/assets/fileicons/unknown.png differ diff --git a/src/assets/fileicons/video.png b/src/assets/fileicons/video.png new file mode 100644 index 0000000..867e175 Binary files /dev/null and b/src/assets/fileicons/video.png differ diff --git a/src/assets/fileicons/word.png b/src/assets/fileicons/word.png new file mode 100644 index 0000000..f2230cb Binary files /dev/null and b/src/assets/fileicons/word.png differ diff --git a/src/assets/fileicons/zip.png b/src/assets/fileicons/zip.png new file mode 100644 index 0000000..d86c4b3 Binary files /dev/null and b/src/assets/fileicons/zip.png differ diff --git a/src/assets/icons/index.js b/src/assets/icons/index.js new file mode 100644 index 0000000..d5c1697 --- /dev/null +++ b/src/assets/icons/index.js @@ -0,0 +1,9 @@ +import Vue from 'vue' +import SvgIcon from '@/components/SvgIcon' // svg component + +// register globally +Vue.component('svg-icon', SvgIcon) + +const req = require.context('./svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys().map(requireContext) +requireAll(req) diff --git a/src/assets/icons/svg/.svg b/src/assets/icons/svg/.svg new file mode 100644 index 0000000..e69de29 diff --git a/src/assets/icons/svg/404.svg b/src/assets/icons/svg/404.svg new file mode 100644 index 0000000..6df5019 --- /dev/null +++ b/src/assets/icons/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/area.svg b/src/assets/icons/svg/area.svg new file mode 100644 index 0000000..4cba631 --- /dev/null +++ b/src/assets/icons/svg/area.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/bug.svg b/src/assets/icons/svg/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/src/assets/icons/svg/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/build.svg b/src/assets/icons/svg/build.svg new file mode 100644 index 0000000..10b1be0 --- /dev/null +++ b/src/assets/icons/svg/build.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/button.svg b/src/assets/icons/svg/button.svg new file mode 100644 index 0000000..ac333dc --- /dev/null +++ b/src/assets/icons/svg/button.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/car.svg b/src/assets/icons/svg/car.svg new file mode 100644 index 0000000..702b697 --- /dev/null +++ b/src/assets/icons/svg/car.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/cascader.svg b/src/assets/icons/svg/cascader.svg new file mode 100644 index 0000000..127ae02 --- /dev/null +++ b/src/assets/icons/svg/cascader.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/chart.svg b/src/assets/icons/svg/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/src/assets/icons/svg/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/checkbox.svg b/src/assets/icons/svg/checkbox.svg new file mode 100644 index 0000000..bf98924 --- /dev/null +++ b/src/assets/icons/svg/checkbox.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/clipboard.svg b/src/assets/icons/svg/clipboard.svg new file mode 100644 index 0000000..90923ff --- /dev/null +++ b/src/assets/icons/svg/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/code.svg b/src/assets/icons/svg/code.svg new file mode 100644 index 0000000..647e217 --- /dev/null +++ b/src/assets/icons/svg/code.svg @@ -0,0 +1,4 @@ + + diff --git a/src/assets/icons/svg/color.svg b/src/assets/icons/svg/color.svg new file mode 100644 index 0000000..61c8c20 --- /dev/null +++ b/src/assets/icons/svg/color.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/component.svg b/src/assets/icons/svg/component.svg new file mode 100644 index 0000000..6a990c4 --- /dev/null +++ b/src/assets/icons/svg/component.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/dashboard.svg b/src/assets/icons/svg/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/src/assets/icons/svg/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/date-range.svg b/src/assets/icons/svg/date-range.svg new file mode 100644 index 0000000..5913e6e --- /dev/null +++ b/src/assets/icons/svg/date-range.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/date.svg b/src/assets/icons/svg/date.svg new file mode 100644 index 0000000..a304e0f --- /dev/null +++ b/src/assets/icons/svg/date.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/dict.svg b/src/assets/icons/svg/dict.svg new file mode 100644 index 0000000..16aa1cd --- /dev/null +++ b/src/assets/icons/svg/dict.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/documentation.svg b/src/assets/icons/svg/documentation.svg new file mode 100644 index 0000000..7043122 --- /dev/null +++ b/src/assets/icons/svg/documentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/down.svg b/src/assets/icons/svg/down.svg new file mode 100644 index 0000000..b0ce6ca --- /dev/null +++ b/src/assets/icons/svg/down.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/svg/download.svg b/src/assets/icons/svg/download.svg new file mode 100644 index 0000000..4ef36c5 --- /dev/null +++ b/src/assets/icons/svg/download.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/drag.svg b/src/assets/icons/svg/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/src/assets/icons/svg/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/druid.svg b/src/assets/icons/svg/druid.svg new file mode 100644 index 0000000..48d7b76 --- /dev/null +++ b/src/assets/icons/svg/druid.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/edit.svg b/src/assets/icons/svg/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/src/assets/icons/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/education.svg b/src/assets/icons/svg/education.svg new file mode 100644 index 0000000..7bfb01d --- /dev/null +++ b/src/assets/icons/svg/education.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/email.svg b/src/assets/icons/svg/email.svg new file mode 100644 index 0000000..74d25e2 --- /dev/null +++ b/src/assets/icons/svg/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/example.svg b/src/assets/icons/svg/example.svg new file mode 100644 index 0000000..46f42b5 --- /dev/null +++ b/src/assets/icons/svg/example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/excel.svg b/src/assets/icons/svg/excel.svg new file mode 100644 index 0000000..74d97b8 --- /dev/null +++ b/src/assets/icons/svg/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/exit-fullscreen.svg b/src/assets/icons/svg/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/src/assets/icons/svg/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/eye-open.svg b/src/assets/icons/svg/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/src/assets/icons/svg/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/eye.svg b/src/assets/icons/svg/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/src/assets/icons/svg/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/form.svg b/src/assets/icons/svg/form.svg new file mode 100644 index 0000000..dcbaa18 --- /dev/null +++ b/src/assets/icons/svg/form.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/fullscreen.svg b/src/assets/icons/svg/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/src/assets/icons/svg/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/github.svg b/src/assets/icons/svg/github.svg new file mode 100644 index 0000000..29a36dd --- /dev/null +++ b/src/assets/icons/svg/github.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/guide.svg b/src/assets/icons/svg/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/src/assets/icons/svg/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/icon.svg b/src/assets/icons/svg/icon.svg new file mode 100644 index 0000000..82be8ee --- /dev/null +++ b/src/assets/icons/svg/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/input.svg b/src/assets/icons/svg/input.svg new file mode 100644 index 0000000..c848d45 --- /dev/null +++ b/src/assets/icons/svg/input.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/international.svg b/src/assets/icons/svg/international.svg new file mode 100644 index 0000000..e9b56ee --- /dev/null +++ b/src/assets/icons/svg/international.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/job.svg b/src/assets/icons/svg/job.svg new file mode 100644 index 0000000..e6c1e54 --- /dev/null +++ b/src/assets/icons/svg/job.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/join.svg b/src/assets/icons/svg/join.svg new file mode 100644 index 0000000..fb24ec9 --- /dev/null +++ b/src/assets/icons/svg/join.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/language.svg b/src/assets/icons/svg/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/src/assets/icons/svg/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/link.svg b/src/assets/icons/svg/link.svg new file mode 100644 index 0000000..48197ba --- /dev/null +++ b/src/assets/icons/svg/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/link2.svg b/src/assets/icons/svg/link2.svg new file mode 100644 index 0000000..28132a6 --- /dev/null +++ b/src/assets/icons/svg/link2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/list.svg b/src/assets/icons/svg/list.svg new file mode 100644 index 0000000..20259ed --- /dev/null +++ b/src/assets/icons/svg/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/lock.svg b/src/assets/icons/svg/lock.svg new file mode 100644 index 0000000..74fee54 --- /dev/null +++ b/src/assets/icons/svg/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/log.svg b/src/assets/icons/svg/log.svg new file mode 100644 index 0000000..7549644 --- /dev/null +++ b/src/assets/icons/svg/log.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/logininfor.svg b/src/assets/icons/svg/logininfor.svg new file mode 100644 index 0000000..62e4fe5 --- /dev/null +++ b/src/assets/icons/svg/logininfor.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/material.svg b/src/assets/icons/svg/material.svg new file mode 100644 index 0000000..9f630ef --- /dev/null +++ b/src/assets/icons/svg/material.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/svg/message.svg b/src/assets/icons/svg/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/src/assets/icons/svg/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/money.svg b/src/assets/icons/svg/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/src/assets/icons/svg/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/monitor.svg b/src/assets/icons/svg/monitor.svg new file mode 100644 index 0000000..9a4467f --- /dev/null +++ b/src/assets/icons/svg/monitor.svg @@ -0,0 +1,5 @@ + + diff --git a/src/assets/icons/svg/nested.svg b/src/assets/icons/svg/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/src/assets/icons/svg/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/number.svg b/src/assets/icons/svg/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/src/assets/icons/svg/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/online.svg b/src/assets/icons/svg/online.svg new file mode 100644 index 0000000..330a202 --- /dev/null +++ b/src/assets/icons/svg/online.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/password.svg b/src/assets/icons/svg/password.svg new file mode 100644 index 0000000..110c7be --- /dev/null +++ b/src/assets/icons/svg/password.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/pay.svg b/src/assets/icons/svg/pay.svg new file mode 100644 index 0000000..548a83e --- /dev/null +++ b/src/assets/icons/svg/pay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/pdf.svg b/src/assets/icons/svg/pdf.svg new file mode 100644 index 0000000..957aa0c --- /dev/null +++ b/src/assets/icons/svg/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/people.svg b/src/assets/icons/svg/people.svg new file mode 100644 index 0000000..2bd54ae --- /dev/null +++ b/src/assets/icons/svg/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/peoples.svg b/src/assets/icons/svg/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/src/assets/icons/svg/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/phone.svg b/src/assets/icons/svg/phone.svg new file mode 100644 index 0000000..ed679c7 --- /dev/null +++ b/src/assets/icons/svg/phone.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/post.svg b/src/assets/icons/svg/post.svg new file mode 100644 index 0000000..8e2a50b --- /dev/null +++ b/src/assets/icons/svg/post.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/qq.svg b/src/assets/icons/svg/qq.svg new file mode 100644 index 0000000..ee13d4e --- /dev/null +++ b/src/assets/icons/svg/qq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/question.svg b/src/assets/icons/svg/question.svg new file mode 100644 index 0000000..f73b79b --- /dev/null +++ b/src/assets/icons/svg/question.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/radio.svg b/src/assets/icons/svg/radio.svg new file mode 100644 index 0000000..8594e95 --- /dev/null +++ b/src/assets/icons/svg/radio.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/rate.svg b/src/assets/icons/svg/rate.svg new file mode 100644 index 0000000..d7ceea0 --- /dev/null +++ b/src/assets/icons/svg/rate.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/redis-list.svg b/src/assets/icons/svg/redis-list.svg new file mode 100644 index 0000000..93a9dfb --- /dev/null +++ b/src/assets/icons/svg/redis-list.svg @@ -0,0 +1,4 @@ + + diff --git a/src/assets/icons/svg/redis.svg b/src/assets/icons/svg/redis.svg new file mode 100644 index 0000000..e0e9fd0 --- /dev/null +++ b/src/assets/icons/svg/redis.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/row.svg b/src/assets/icons/svg/row.svg new file mode 100644 index 0000000..c2a0f68 --- /dev/null +++ b/src/assets/icons/svg/row.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/search.svg b/src/assets/icons/svg/search.svg new file mode 100644 index 0000000..84233dd --- /dev/null +++ b/src/assets/icons/svg/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/select.svg b/src/assets/icons/svg/select.svg new file mode 100644 index 0000000..0e8a4a5 --- /dev/null +++ b/src/assets/icons/svg/select.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/server.svg b/src/assets/icons/svg/server.svg new file mode 100644 index 0000000..349f48f --- /dev/null +++ b/src/assets/icons/svg/server.svg @@ -0,0 +1,4 @@ + + diff --git a/src/assets/icons/svg/shopping.svg b/src/assets/icons/svg/shopping.svg new file mode 100644 index 0000000..87513e7 --- /dev/null +++ b/src/assets/icons/svg/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/size.svg b/src/assets/icons/svg/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/src/assets/icons/svg/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/skill.svg b/src/assets/icons/svg/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/src/assets/icons/svg/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/slider.svg b/src/assets/icons/svg/slider.svg new file mode 100644 index 0000000..dd32e32 --- /dev/null +++ b/src/assets/icons/svg/slider.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/star.svg b/src/assets/icons/svg/star.svg new file mode 100644 index 0000000..6cf86e6 --- /dev/null +++ b/src/assets/icons/svg/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/swagger.svg b/src/assets/icons/svg/swagger.svg new file mode 100644 index 0000000..e4d999a --- /dev/null +++ b/src/assets/icons/svg/swagger.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/switch.svg b/src/assets/icons/svg/switch.svg new file mode 100644 index 0000000..bf7f53c --- /dev/null +++ b/src/assets/icons/svg/switch.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/system.svg b/src/assets/icons/svg/system.svg new file mode 100644 index 0000000..c95041c --- /dev/null +++ b/src/assets/icons/svg/system.svg @@ -0,0 +1,5 @@ + + diff --git a/src/assets/icons/svg/tab.svg b/src/assets/icons/svg/tab.svg new file mode 100644 index 0000000..b4b48e4 --- /dev/null +++ b/src/assets/icons/svg/tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/table.svg b/src/assets/icons/svg/table.svg new file mode 100644 index 0000000..0e3dc9d --- /dev/null +++ b/src/assets/icons/svg/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/textarea.svg b/src/assets/icons/svg/textarea.svg new file mode 100644 index 0000000..4356f6d --- /dev/null +++ b/src/assets/icons/svg/textarea.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/theme.svg b/src/assets/icons/svg/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/src/assets/icons/svg/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/time-range.svg b/src/assets/icons/svg/time-range.svg new file mode 100644 index 0000000..7f6e061 --- /dev/null +++ b/src/assets/icons/svg/time-range.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/time.svg b/src/assets/icons/svg/time.svg new file mode 100644 index 0000000..7ec35e1 --- /dev/null +++ b/src/assets/icons/svg/time.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/tool.svg b/src/assets/icons/svg/tool.svg new file mode 100644 index 0000000..972c0f4 --- /dev/null +++ b/src/assets/icons/svg/tool.svg @@ -0,0 +1,4 @@ + + diff --git a/src/assets/icons/svg/tree-table.svg b/src/assets/icons/svg/tree-table.svg new file mode 100644 index 0000000..8aafdb8 --- /dev/null +++ b/src/assets/icons/svg/tree-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tree.svg b/src/assets/icons/svg/tree.svg new file mode 100644 index 0000000..dd4b7dd --- /dev/null +++ b/src/assets/icons/svg/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/up.svg b/src/assets/icons/svg/up.svg new file mode 100644 index 0000000..6dc7d58 --- /dev/null +++ b/src/assets/icons/svg/up.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/svg/upload.svg b/src/assets/icons/svg/upload.svg new file mode 100644 index 0000000..36d6207 --- /dev/null +++ b/src/assets/icons/svg/upload.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/user.svg b/src/assets/icons/svg/user.svg new file mode 100644 index 0000000..0ba0716 --- /dev/null +++ b/src/assets/icons/svg/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/validCode.svg b/src/assets/icons/svg/validCode.svg new file mode 100644 index 0000000..4d0ecb2 --- /dev/null +++ b/src/assets/icons/svg/validCode.svg @@ -0,0 +1,3 @@ + + diff --git a/src/assets/icons/svg/wechat.svg b/src/assets/icons/svg/wechat.svg new file mode 100644 index 0000000..c586e55 --- /dev/null +++ b/src/assets/icons/svg/wechat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/zip.svg b/src/assets/icons/svg/zip.svg new file mode 100644 index 0000000..f806fc4 --- /dev/null +++ b/src/assets/icons/svg/zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svgo.yml b/src/assets/icons/svgo.yml new file mode 100644 index 0000000..d11906a --- /dev/null +++ b/src/assets/icons/svgo.yml @@ -0,0 +1,22 @@ +# replace default config + +# multipass: true +# full: true + +plugins: + + # - name + # + # or: + # - name: false + # - name: true + # + # or: + # - name: + # param1: 1 + # param2: 2 + +- removeAttrs: + attrs: + - 'fill' + - 'fill-rule' diff --git a/src/assets/images/dark.svg b/src/assets/images/dark.svg new file mode 100644 index 0000000..f646bd7 --- /dev/null +++ b/src/assets/images/dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/light.svg b/src/assets/images/light.svg new file mode 100644 index 0000000..ab7cc08 --- /dev/null +++ b/src/assets/images/light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/login-background.png b/src/assets/images/login-background.png new file mode 100644 index 0000000..8969f01 Binary files /dev/null and b/src/assets/images/login-background.png differ diff --git a/src/assets/images/pay.png b/src/assets/images/pay.png new file mode 100644 index 0000000..bb8b967 Binary files /dev/null and b/src/assets/images/pay.png differ diff --git a/src/assets/images/profile.jpg b/src/assets/images/profile.jpg new file mode 100644 index 0000000..b3a940b Binary files /dev/null and b/src/assets/images/profile.jpg differ diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png new file mode 100644 index 0000000..4856c70 Binary files /dev/null and b/src/assets/logo/logo.png differ diff --git a/src/assets/styles/btn.scss b/src/assets/styles/btn.scss new file mode 100644 index 0000000..e6ba1a8 --- /dev/null +++ b/src/assets/styles/btn.scss @@ -0,0 +1,99 @@ +@import './variables.scss'; + +@mixin colorBtn($color) { + background: $color; + + &:hover { + color: $color; + + &:before, + &:after { + background: $color; + } + } +} + +.blue-btn { + @include colorBtn($blue) +} + +.light-blue-btn { + @include colorBtn($light-blue) +} + +.red-btn { + @include colorBtn($red) +} + +.pink-btn { + @include colorBtn($pink) +} + +.green-btn { + @include colorBtn($green) +} + +.tiffany-btn { + @include colorBtn($tiffany) +} + +.yellow-btn { + @include colorBtn($yellow) +} + +.pan-btn { + font-size: 14px; + color: #fff; + padding: 14px 36px; + border-radius: 8px; + border: none; + outline: none; + transition: 600ms ease all; + position: relative; + display: inline-block; + + &:hover { + background: #fff; + + &:before, + &:after { + width: 100%; + transition: 600ms ease all; + } + } + + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + height: 2px; + width: 0; + transition: 400ms ease all; + } + + &::after { + right: inherit; + top: inherit; + left: 0; + bottom: 0; + } +} + +.custom-button { + display: inline-block; + line-height: 1; + white-space: nowrap; + cursor: pointer; + background: #fff; + color: #fff; + -webkit-appearance: none; + text-align: center; + box-sizing: border-box; + outline: 0; + margin: 0; + padding: 10px 15px; + font-size: 14px; + border-radius: 4px; +} diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss new file mode 100644 index 0000000..29cca37 --- /dev/null +++ b/src/assets/styles/element-ui.scss @@ -0,0 +1,96 @@ +// cover some element-ui styles + +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type="file"] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +.cell { + .el-tag { + margin-right: 0px; + } +} + +.small-padding { + .cell { + padding-left: 5px; + padding-right: 5px; + } +} + +.fixed-width { + .el-button--mini { + padding: 7px 10px; + width: 60px; + } +} + +.status-col { + .cell { + padding: 0 10px; + text-align: center; + + .el-tag { + margin-right: 0px; + } + } +} + +// to fixed https://github.com/ElemeFE/element/issues/2461 +.el-dialog { + transform: none; + left: 0; + position: relative; + margin: 0 auto; +} + +// refine element ui upload +.upload-container { + .el-upload { + width: 100%; + + .el-upload-dragger { + width: 100%; + height: 200px; + } + } +} + +// dropdown +.el-dropdown-menu { + a { + display: block + } +} + +// fix date-picker ui bug in filter-item +.el-range-editor.el-input__inner { + display: inline-flex !important; +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +.el-menu--collapse + > div + > .el-submenu + > .el-submenu__title + .el-submenu__icon-arrow { + display: none; +} + +.el-scrollbar__wrap { + overflow-x: hidden; +} diff --git a/src/assets/styles/element-variables.scss b/src/assets/styles/element-variables.scss new file mode 100644 index 0000000..1615ff2 --- /dev/null +++ b/src/assets/styles/element-variables.scss @@ -0,0 +1,31 @@ +/** +* I think element-ui's default theme color is too light for long-term use. +* So I modified the default color and you can modify it to your liking. +**/ + +/* theme color */ +$--color-primary: #1890ff; +$--color-success: #13ce66; +$--color-warning: #ffba00; +$--color-danger: #ff4949; +// $--color-info: #1E1E1E; + +$--button-font-weight: 400; + +// $--color-text-regular: #1f2d3d; + +$--border-color-light: #dfe4ed; +$--border-color-lighter: #e6ebf5; + +$--table-border: 1px solid #dfe6ec; + +/* icon font path, required */ +$--font-path: '~element-ui/lib/theme-chalk/fonts'; + +@import "~element-ui/packages/theme-chalk/src/index"; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + theme: $--color-primary; +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss new file mode 100644 index 0000000..4d58dd1 --- /dev/null +++ b/src/assets/styles/index.scss @@ -0,0 +1,289 @@ +@import './variables.scss'; +@import './mixin.scss'; +@import './transition.scss'; +@import './element-ui.scss'; +@import './sidebar.scss'; +@import './btn.scss'; + +body { + height: 100%; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +.no-padding { + padding: 0px !important; +} + +.padding-content { + padding: 4px 0; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.pr-5 { + padding-right: 5px; +} + +.pl-5 { + padding-left: 5px; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.inlineBlock { + display: block; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +} + +aside { + background: #eef1f6; + padding: 8px 24px; + margin-bottom: 20px; + border-radius: 2px; + display: block; + line-height: 32px; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + color: #2c3e50; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + a { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } + } +} + +//main-container全局样式 +.app-container { + padding: 20px; +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +.pagination-container { + margin-top: 30px; +} + +.text-center { + text-align: center +} + +.sub-navbar { + height: 50px; + line-height: 50px; + position: relative; + width: 100%; + text-align: right; + padding-right: 20px; + transition: 600ms ease position; + background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); + + .subtitle { + font-size: 20px; + color: #fff; + } + + &.draft { + background: #d0d0d0; + } + + &.deleted { + background: #d0d0d0; + } +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } +} + +.filter-container { + padding-bottom: 10px; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: 10px; + } +} +.edit-title { + border-left:3px solid #409EFF; + padding: 0 0.5em; + margin: 1em 0; +} +.edit-table-operate { + margin-bottom: 1em; + display: flex; +} +.edit-table-operate-right { + flex: 1; + display: flex; + justify-content: right; + align-items: center; +} + +.required-label::before { + content: "* "; + color: #ff4949; + font-size: 14px; +} + +// mini版本的树选择器 +.mini-tree-select { + /*TreeSelect 样式*/ + .el-input__inner, + .vue-treeselect__input, + .vue-treeselect__placeholder, + .vue-treeselect__single-value, + .popper__arrow::after, + .vue-treeselect__value-container{ + height: 26px; + line-height: 26px; + } + .vue-treeselect__control { + height:28px; + } +} + +// small版本的树选择器 +.small-tree-select { + /*TreeSelect 样式*/ + .el-input__inner, + .vue-treeselect__input, + .vue-treeselect__placeholder, + .vue-treeselect__single-value, + .popper__arrow::after, + .vue-treeselect__value-container{ + height: 30px; + line-height: 30px; + } + .vue-treeselect__control { + height:32px; + } +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + + .card-operator { + display: flex; + justify-content: flex-end; + // 卡片上的按钮 + .card-btn { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + } +} + +.fixed { + position: fixed; +} + +.drawer-container { + position: relative; + width: 100%; + overflow: hidden; + + .drawer-content { + position: relative; + width: 100%; + padding: 8px; + flex: 1; + overflow-x: hidden; + overflow-y: auto; + height: calc(100vh - 100px); + } + .drawer-operator-box { + position: relative; + height: fit-content; + background-color: #fff; + padding: 8px; + box-sizing: border-box; + display: -webkit-flex; /* 添加 WebKit 前缀 */ + display: flex; + justify-content: flex-end; /* 使用标准值 */ + } +} + +.flex-row { + display: flex; + align-items: center; + flex-direction: row; +} \ No newline at end of file diff --git a/src/assets/styles/ipad.scss b/src/assets/styles/ipad.scss new file mode 100644 index 0000000..7dfd079 --- /dev/null +++ b/src/assets/styles/ipad.scss @@ -0,0 +1,27 @@ +.ipad-container { + position: relative; + padding-top: 64px; + height: calc(100vh - 86px); + display: flex; + flex-direction: column; + .top-bar { + position: absolute; + top: 0; + left: 0; + width: 100%; + padding: 20px 20px 0; + } + .content { + flex: 1; + width: 100%; + overflow-y: auto; + overflow-x: hidden; + padding: 8px 20px 32px; + } +} + +.form-container { + padding: 20px; + background-color: #f9f9f9; + min-height: calc(100vh - 180px); +} diff --git a/src/assets/styles/mixin.scss b/src/assets/styles/mixin.scss new file mode 100644 index 0000000..06fa061 --- /dev/null +++ b/src/assets/styles/mixin.scss @@ -0,0 +1,66 @@ +@mixin clearfix { + &:after { + content: ""; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} + +@mixin pct($pct) { + width: #{$pct}; + position: relative; + margin: 0 auto; +} + +@mixin triangle($width, $height, $color, $direction) { + $width: $width/2; + $color-border-style: $height solid $color; + $transparent-border-style: $width solid transparent; + height: 0; + width: 0; + + @if $direction==up { + border-bottom: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==right { + border-left: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } + + @else if $direction==down { + border-top: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } + + @else if $direction==left { + border-right: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } +} diff --git a/src/assets/styles/ruoyi.scss b/src/assets/styles/ruoyi.scss new file mode 100644 index 0000000..3f27e19 --- /dev/null +++ b/src/assets/styles/ruoyi.scss @@ -0,0 +1,297 @@ +/** +* 通用css样式布局处理 +* Copyright (c) 2019 ruoyi +*/ + +/** 基础通用 **/ +.pt5 { + padding-top: 5px; +} + +.pr5 { + padding-right: 5px; +} + +.pb5 { + padding-bottom: 5px; +} + +.mt5 { + margin-top: 5px; +} + +.mr5 { + margin-right: 5px; +} + +.mb5 { + margin-bottom: 5px; +} + +.mb8 { + margin-bottom: 8px; +} + +.ml5 { + margin-left: 5px; +} + +.mt10 { + margin-top: 10px; +} + +.mr10 { + margin-right: 10px; +} + +.mb10 { + margin-bottom: 10px; +} +.ml10 { + margin-left: 10px; +} + +.mt20 { + margin-top: 20px; +} + +.mr20 { + margin-right: 20px; +} + +.mb20 { + margin-bottom: 20px; +} +.ml20 { + margin-left: 20px; +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.el-message-box__status + .el-message-box__message{ + word-break: break-word; +} + +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + .el-table__header-wrapper, .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #fff; + color: #515a6e; + height: 40px; + font-size: 13px; + } + } + + .el-table__body-wrapper { + .el-button [class*="el-icon-"] + span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size: 15px; + color: #6379bb; + border-bottom: 1px solid #ddd; + margin: 8px 10px 25px 10px; + padding-bottom: 5px +} + +/** 表格布局 **/ +.pagination-container { + position: relative; + margin-bottom: 10px; + margin-top: 15px; +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid #e5e6e7; + background: #FFFFFF none; + border-radius: 4px; +} + +.pagination-container .el-pagination { + right: 0; +} + +@media (max-width: 768px) { + .pagination-container .el-pagination > .el-pagination__jump { + display: none !important; + } + .pagination-container .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-table .fixed-width .el-button--mini { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link,.el-table .el-dropdown-selfdefine { + cursor: pointer; + margin-left: 5px; +} + +.el-table .el-dropdown, .el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content > .el-checkbox { + margin-right: 8px; +} + +.list-group-striped > .list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 14px; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px; +} + +.card-box { + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48D1CC; + border-color: #48D1CC; + color: #FFFFFF; +} + +.el-button--cyan { + background-color: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: relative; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost { + opacity: .8; + color: #fff !important; + background: #42b983 !important; +} + +.top-right-btn { + position: relative; + float: right; +} + +.expand-container { + position:relative; + width: 100%; + height: fit-content; + padding: 0 1em; +} +.el-table__cell:has(.expand-container), +.el-table__cell:has(.expand-container):hover { + background-color: #f5f7fa !important; +} diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss new file mode 100644 index 0000000..689dba4 --- /dev/null +++ b/src/assets/styles/sidebar.scss @@ -0,0 +1,228 @@ +#app { + + .main-container { + height: 100vh; + overflow: auto; + transition: margin-left .28s; + margin-left: $base-sidebar-width; + position: relative; + } + + .sidebarHide { + margin-left: 0!important; + } + + .sidebar-container { + -webkit-transition: width .28s; + transition: width 0.28s; + width: $base-sidebar-width !important; + background-color: $base-menu-background; + height: 100%; + position: fixed; + font-size: 0px; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); + box-shadow: 2px 0 6px rgba(0,21,41,.35); + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0px; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + .el-menu-item, .el-submenu__title { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } + + // menu hover + .submenu-title-noDropdown, + .el-submenu__title { + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .is-active > .el-submenu__title { + color: $base-menu-color-active !important; + } + + & .nest-menu .el-submenu>.el-submenu__title, + & .el-submenu .el-menu-item { + min-width: $base-sidebar-width !important; + + &:hover { + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + & .theme-dark .nest-menu .el-submenu>.el-submenu__title, + & .theme-dark .el-submenu .el-menu-item { + background-color: $base-sub-menu-background !important; + + &:hover { + background-color: $base-sub-menu-hover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .submenu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-submenu { + overflow: hidden; + + &>.el-submenu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + } + } + + .el-menu--collapse { + .el-submenu { + &>.el-submenu__title { + &>span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-submenu { + min-width: $base-sidebar-width !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform .28s; + width: $base-sidebar-width !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$base-sidebar-width, 0, 0); + } + } + } + + .withoutAnimation { + + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + &>.el-menu { + .svg-icon { + margin-right: 16px; + } + } + + .nest-menu .el-submenu>.el-submenu__title, + .el-menu-item { + &:hover { + // you can use $subMenuHover + background-color: rgba(0, 0, 0, 0.06) !important; + } + } + + // the scroll bar appears when the subMenu is too long + >.el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/src/assets/styles/transition.scss b/src/assets/styles/transition.scss new file mode 100644 index 0000000..073f8c6 --- /dev/null +++ b/src/assets/styles/transition.scss @@ -0,0 +1,49 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform--move, +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all .5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all .5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all .5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss new file mode 100644 index 0000000..34484d4 --- /dev/null +++ b/src/assets/styles/variables.scss @@ -0,0 +1,54 @@ +// base color +$blue:#324157; +$light-blue:#3A71A8; +$red:#C03639; +$pink: #E65D6E; +$green: #30B08F; +$tiffany: #4AB7BD; +$yellow:#FEC171; +$panGreen: #30B08F; + +// 默认菜单主题风格 +$base-menu-color:#bfcbd9; +$base-menu-color-active:#f4f4f5; +$base-menu-background:#304156; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#1f2d3d; +$base-sub-menu-hover:#001528; + +// 自定义暗色菜单风格 +/** +$base-menu-color:hsla(0,0%,100%,.65); +$base-menu-color-active:#fff; +$base-menu-background:#001529; +$base-logo-title-color: #ffffff; + +$base-menu-light-color:rgba(0,0,0,.70); +$base-menu-light-background:#ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background:#000c17; +$base-sub-menu-hover:#001528; +*/ + +$base-sidebar-width: 200px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuColor: $base-menu-color; + menuLightColor: $base-menu-light-color; + menuColorActive: $base-menu-color-active; + menuBackground: $base-menu-background; + menuLightBackground: $base-menu-light-background; + subMenuBackground: $base-sub-menu-background; + subMenuHover: $base-sub-menu-hover; + sideBarWidth: $base-sidebar-width; + logoTitleColor: $base-logo-title-color; + logoLightTitleColor: $base-logo-light-title-color +} diff --git a/src/components/AttachCard/index.vue b/src/components/AttachCard/index.vue new file mode 100644 index 0000000..c739d9f --- /dev/null +++ b/src/components/AttachCard/index.vue @@ -0,0 +1,212 @@ + + + + + \ No newline at end of file diff --git a/src/components/AttachList/index.vue b/src/components/AttachList/index.vue new file mode 100644 index 0000000..332a6da --- /dev/null +++ b/src/components/AttachList/index.vue @@ -0,0 +1,144 @@ + + + + + \ No newline at end of file diff --git a/src/components/Avatar/index.vue b/src/components/Avatar/index.vue new file mode 100644 index 0000000..21c6031 --- /dev/null +++ b/src/components/Avatar/index.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/src/components/AvatarList/index.vue b/src/components/AvatarList/index.vue new file mode 100644 index 0000000..c6b1242 --- /dev/null +++ b/src/components/AvatarList/index.vue @@ -0,0 +1,237 @@ + + + + + \ No newline at end of file diff --git a/src/components/BaseRemoteSelect/index.vue b/src/components/BaseRemoteSelect/index.vue new file mode 100644 index 0000000..1608bb5 --- /dev/null +++ b/src/components/BaseRemoteSelect/index.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/src/components/BaseRemoteSelect/mixins.js b/src/components/BaseRemoteSelect/mixins.js new file mode 100644 index 0000000..0d24bfb --- /dev/null +++ b/src/components/BaseRemoteSelect/mixins.js @@ -0,0 +1,74 @@ +export const $remoteSelect = { + props: { + // 选中值 + value: { + type: [String, Array], + default: null + }, + // 自定义查询参数 + query: { + type: Object, + default: () => ({}) + }, + // 是否多选 + multiple: { + type: Boolean, + default: false + }, + // 初始化选项 + initOptions: { + type: Array, + default: () => [] + }, + // 选中值的属性 + prop: { + type: String, + default: 'id' + }, + // 展示值的属性 + showProp: { + type: String, + default: 'name' + }, + // 搜索关键字属性 + keywordProp: { + type: String, + default: 'keyword' + }, + // 列表接口 + listApi: { + type: Function, + default: () => {} + }, + // 加载接口 + loadApi: { + type: Function, + default: null + }, + // 空数据文本 + emptyText: { + type: String, + default: '请选择' + }, + // 获取选项前回调 + beforeGetOptions: { + type: Function, + default: () => {return true;} + }, + // 是否可清空 + clearable: { + type: Boolean, + default: false + }, + // 尺寸 + size: { + type: String, + default: null + }, + // 空数据值 + emptyValue: { + type: [String, Number, Boolean], + default: null + } + }, +} \ No newline at end of file diff --git a/src/components/BooleanTag/index.vue b/src/components/BooleanTag/index.vue new file mode 100644 index 0000000..3618164 --- /dev/null +++ b/src/components/BooleanTag/index.vue @@ -0,0 +1,35 @@ + + diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..1696f54 --- /dev/null +++ b/src/components/Breadcrumb/index.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/components/Business/Dept/DeptCheck.vue b/src/components/Business/Dept/DeptCheck.vue new file mode 100644 index 0000000..4b3e861 --- /dev/null +++ b/src/components/Business/Dept/DeptCheck.vue @@ -0,0 +1,223 @@ + + + diff --git a/src/components/Business/Dept/DeptDialog.vue b/src/components/Business/Dept/DeptDialog.vue new file mode 100644 index 0000000..c8c3984 --- /dev/null +++ b/src/components/Business/Dept/DeptDialog.vue @@ -0,0 +1,79 @@ + + + + + + diff --git a/src/components/Business/Dept/DeptInput.vue b/src/components/Business/Dept/DeptInput.vue new file mode 100644 index 0000000..ac0f15f --- /dev/null +++ b/src/components/Business/Dept/DeptInput.vue @@ -0,0 +1,85 @@ + + + + + + diff --git a/src/components/Business/Dept/DeptSelect.vue b/src/components/Business/Dept/DeptSelect.vue new file mode 100644 index 0000000..7d90038 --- /dev/null +++ b/src/components/Business/Dept/DeptSelect.vue @@ -0,0 +1,76 @@ + + + \ No newline at end of file diff --git a/src/components/Business/Dept/DeptTreeSelect.vue b/src/components/Business/Dept/DeptTreeSelect.vue new file mode 100644 index 0000000..3acda9a --- /dev/null +++ b/src/components/Business/Dept/DeptTreeSelect.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/components/Business/User/UserDialog.vue b/src/components/Business/User/UserDialog.vue new file mode 100644 index 0000000..d79af69 --- /dev/null +++ b/src/components/Business/User/UserDialog.vue @@ -0,0 +1,184 @@ + + + diff --git a/src/components/Business/User/UserInput.vue b/src/components/Business/User/UserInput.vue new file mode 100644 index 0000000..3fb7f41 --- /dev/null +++ b/src/components/Business/User/UserInput.vue @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/src/components/Business/User/UserRemoteSelect.vue b/src/components/Business/User/UserRemoteSelect.vue new file mode 100644 index 0000000..eeaecb2 --- /dev/null +++ b/src/components/Business/User/UserRemoteSelect.vue @@ -0,0 +1,143 @@ + + + + + \ No newline at end of file diff --git a/src/components/Business/User/UserSelect.vue b/src/components/Business/User/UserSelect.vue new file mode 100644 index 0000000..4fa113c --- /dev/null +++ b/src/components/Business/User/UserSelect.vue @@ -0,0 +1,120 @@ + + + + + \ No newline at end of file diff --git a/src/components/Business/mixins.js b/src/components/Business/mixins.js new file mode 100644 index 0000000..37923ec --- /dev/null +++ b/src/components/Business/mixins.js @@ -0,0 +1,159 @@ +import { isDeepEqual } from '@/utils'; + +export const $businessInput = { + props:{ + // 标题 + title: { + type: String, + default: `选择` + }, + placeholder: { + type: String, + default: `点击选择`, + }, + // 展示值的属性 + showProp: { + type: String, + default: 'name' + }, + // 选择的属性值 + prop: { + type: String, + default: 'priceId' + }, + // 是否多选 + multiple: { + type: Boolean, + default: false, + }, + // 双向绑定的值(为id) + value: { + type: [Array, String, Number], + default: null + }, + // 查询条件 + query: { + type: Object, + default: () => ({}) + }, + // 打开弹窗前 + beforeOpen: { + type: Function, + default: () => { + return true; + } + }, + // 关闭弹窗前 + beforeClose: { + type: Function, + default: () => { + return true; + } + }, + // 大小 + size: { + type: String, + default: null + }, + // 是否禁用 + disabled: { + type: Boolean, + default: false, + }, + // 对象处理器,参数类型:对象,返回值类型:字符串 + objectParser: { + type: Function, + default: (obj) => { + return JSON.stringify(obj); + } + }, + listApi: { + type: Function, + default: null + }, + loadApi: { + type: Function, + default: null, + } + }, + data() { + return { + selected: [], // 当前选中的值 + dialogShow: false, // 展示对话框 + } + }, + computed: { + // 显示绑定的值 + inputBindValue() { + if (this.selected == null || this.selected.length === 0) { + return null; + } + return this.selected.map(item => item[this.showProp]).join(","); + }, + }, + watch: { + value(nv, ov) { + this.loadSelected(nv); + } + }, + created() { + this.loadSelected(this.value); + }, + methods: { + onCloseSelected(index) { + this.selected.splice(index, 1); + this.onSubmit(this.selected); + }, + // 加载选中的值 + loadSelected(ids) { + if (ids == null || ids.length === 0) { + this.selected = []; + return; + } + if (ids instanceof Array) { + this.doLoad(ids); + } else { + this.doLoad([ids]); + } + }, + // 加载选中值 + doLoad(ids) { + this.loadApi(ids).then(res => { + this.selected = res.data; + }) + }, + // 修改值 + inputValue(val){ + this.$emit('input', val); + }, + // 确定 + onSubmit(selected){ + let value = null; + console.log('onSubmit', selected); + if (this.multiple) { + value = selected.map(item => item[this.prop]); + } else { + value = selected[this.prop]; + } + this.$emit('submit', selected); + if (!isDeepEqual(this.value, value)) { + this.$emit('change', selected); + } + this.inputValue(value); + this.closeDialog(); + }, + closeDialog() { + console.log('closeDialog'); + if (this.beforeClose()) { + console.log('closeDialog : false'); + this.dialogShow = false; + } + }, + // 打开对话框 + openDialog(){ + if (this.beforeOpen()) { + this.dialogShow = true; + } + } + } +} diff --git a/src/components/CardTab/index.vue b/src/components/CardTab/index.vue new file mode 100644 index 0000000..1101504 --- /dev/null +++ b/src/components/CardTab/index.vue @@ -0,0 +1,77 @@ + + + + + \ No newline at end of file diff --git a/src/components/CheckDialog/index.vue b/src/components/CheckDialog/index.vue new file mode 100644 index 0000000..7d4c712 --- /dev/null +++ b/src/components/CheckDialog/index.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/src/components/CollapsePanel/index.vue b/src/components/CollapsePanel/index.vue new file mode 100644 index 0000000..2831490 --- /dev/null +++ b/src/components/CollapsePanel/index.vue @@ -0,0 +1,78 @@ + + + + diff --git a/src/components/CollapseTitle/index.vue b/src/components/CollapseTitle/index.vue new file mode 100644 index 0000000..4d6ef6d --- /dev/null +++ b/src/components/CollapseTitle/index.vue @@ -0,0 +1,54 @@ + + + + diff --git a/src/components/Crontab/day.vue b/src/components/Crontab/day.vue new file mode 100644 index 0000000..fe3eaf0 --- /dev/null +++ b/src/components/Crontab/day.vue @@ -0,0 +1,161 @@ + + + diff --git a/src/components/Crontab/hour.vue b/src/components/Crontab/hour.vue new file mode 100644 index 0000000..3216c33 --- /dev/null +++ b/src/components/Crontab/hour.vue @@ -0,0 +1,120 @@ + + + diff --git a/src/components/Crontab/index.vue b/src/components/Crontab/index.vue new file mode 100644 index 0000000..0a2a0ea --- /dev/null +++ b/src/components/Crontab/index.vue @@ -0,0 +1,430 @@ + + + + diff --git a/src/components/Crontab/min.vue b/src/components/Crontab/min.vue new file mode 100644 index 0000000..43cab90 --- /dev/null +++ b/src/components/Crontab/min.vue @@ -0,0 +1,116 @@ + + + \ No newline at end of file diff --git a/src/components/Crontab/month.vue b/src/components/Crontab/month.vue new file mode 100644 index 0000000..fd0ac38 --- /dev/null +++ b/src/components/Crontab/month.vue @@ -0,0 +1,114 @@ + + + diff --git a/src/components/Crontab/result.vue b/src/components/Crontab/result.vue new file mode 100644 index 0000000..aea6e0e --- /dev/null +++ b/src/components/Crontab/result.vue @@ -0,0 +1,559 @@ + + + diff --git a/src/components/Crontab/second.vue b/src/components/Crontab/second.vue new file mode 100644 index 0000000..e7b7761 --- /dev/null +++ b/src/components/Crontab/second.vue @@ -0,0 +1,117 @@ + + + diff --git a/src/components/Crontab/week.vue b/src/components/Crontab/week.vue new file mode 100644 index 0000000..1cec700 --- /dev/null +++ b/src/components/Crontab/week.vue @@ -0,0 +1,202 @@ + + + diff --git a/src/components/Crontab/year.vue b/src/components/Crontab/year.vue new file mode 100644 index 0000000..5487a6c --- /dev/null +++ b/src/components/Crontab/year.vue @@ -0,0 +1,131 @@ + + + diff --git a/src/components/Dashboard/StatCard.vue b/src/components/Dashboard/StatCard.vue new file mode 100644 index 0000000..97190e8 --- /dev/null +++ b/src/components/Dashboard/StatCard.vue @@ -0,0 +1,121 @@ + + + + + \ No newline at end of file diff --git a/src/components/DateRangePicker/index.vue b/src/components/DateRangePicker/index.vue new file mode 100644 index 0000000..c736ed8 --- /dev/null +++ b/src/components/DateRangePicker/index.vue @@ -0,0 +1,64 @@ + + diff --git a/src/components/DictData/index.js b/src/components/DictData/index.js new file mode 100644 index 0000000..7b85d4a --- /dev/null +++ b/src/components/DictData/index.js @@ -0,0 +1,49 @@ +import Vue from 'vue' +import store from '@/store' +import DataDict from '@/utils/dict' +import { getDicts as getDicts } from '@/api/system/dict/data' + +function searchDictByKey(dict, key) { + if (key == null && key == "") { + return null + } + try { + for (let i = 0; i < dict.length; i++) { + if (dict[i].key == key) { + return dict[i].value + } + } + } catch (e) { + return null + } +} + +function install() { + Vue.use(DataDict, { + metas: { + '*': { + labelField: 'dictLabel', + valueField: 'dictValue', + request(dictMeta) { + const storeDict = searchDictByKey(store.getters.dict, dictMeta.type) + if (storeDict) { + return new Promise(resolve => { resolve(storeDict) }) + } else { + return new Promise((resolve, reject) => { + getDicts(dictMeta.type).then(res => { + store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data }) + resolve(res.data) + }).catch(error => { + reject(error) + }) + }) + } + }, + }, + }, + }) +} + +export default { + install, +} \ No newline at end of file diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue new file mode 100644 index 0000000..0f4c055 --- /dev/null +++ b/src/components/DictTag/index.vue @@ -0,0 +1,97 @@ + + + + diff --git a/src/components/EditHeader/index.vue b/src/components/EditHeader/index.vue new file mode 100644 index 0000000..6d684bf --- /dev/null +++ b/src/components/EditHeader/index.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue new file mode 100644 index 0000000..4e2b47a --- /dev/null +++ b/src/components/Editor/index.vue @@ -0,0 +1,370 @@ + + + + + diff --git a/src/components/FileUpload/index.vue b/src/components/FileUpload/index.vue new file mode 100644 index 0000000..8166506 --- /dev/null +++ b/src/components/FileUpload/index.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/components/FormCol/index.vue b/src/components/FormCol/index.vue new file mode 100644 index 0000000..221ffd1 --- /dev/null +++ b/src/components/FormCol/index.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue new file mode 100644 index 0000000..368b002 --- /dev/null +++ b/src/components/Hamburger/index.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/components/HeaderSearch/index.vue b/src/components/HeaderSearch/index.vue new file mode 100644 index 0000000..7d6780b --- /dev/null +++ b/src/components/HeaderSearch/index.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/components/HoverShow/index.vue b/src/components/HoverShow/index.vue new file mode 100644 index 0000000..73f228b --- /dev/null +++ b/src/components/HoverShow/index.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue new file mode 100644 index 0000000..24a58dd --- /dev/null +++ b/src/components/IconSelect/index.vue @@ -0,0 +1,105 @@ + + + + + + diff --git a/src/components/IconSelect/requireIcons.js b/src/components/IconSelect/requireIcons.js new file mode 100644 index 0000000..99e5c54 --- /dev/null +++ b/src/components/IconSelect/requireIcons.js @@ -0,0 +1,11 @@ + +const req = require.context('../../assets/icons/svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys() + +const re = /\.\/(.*)\.svg/ + +const icons = requireAll(req).map(i => { + return i.match(re)[1] +}) + +export default icons diff --git a/src/components/ImagePreview/index.vue b/src/components/ImagePreview/index.vue new file mode 100644 index 0000000..688d71d --- /dev/null +++ b/src/components/ImagePreview/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue new file mode 100644 index 0000000..1a55396 --- /dev/null +++ b/src/components/ImageUpload/index.vue @@ -0,0 +1,644 @@ + + + + + diff --git a/src/components/ImportDialog/index.vue b/src/components/ImportDialog/index.vue new file mode 100644 index 0000000..407364a --- /dev/null +++ b/src/components/ImportDialog/index.vue @@ -0,0 +1,111 @@ + + + diff --git a/src/components/LineField/index.vue b/src/components/LineField/index.vue new file mode 100644 index 0000000..1bc6dc6 --- /dev/null +++ b/src/components/LineField/index.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue new file mode 100644 index 0000000..21cdf49 --- /dev/null +++ b/src/components/Pagination/index.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/src/components/PanThumb/index.vue b/src/components/PanThumb/index.vue new file mode 100644 index 0000000..1bcf417 --- /dev/null +++ b/src/components/PanThumb/index.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/src/components/ParentView/index.vue b/src/components/ParentView/index.vue new file mode 100644 index 0000000..7bf6148 --- /dev/null +++ b/src/components/ParentView/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/QrCode/index.vue b/src/components/QrCode/index.vue new file mode 100644 index 0000000..4806622 --- /dev/null +++ b/src/components/QrCode/index.vue @@ -0,0 +1,119 @@ + + + diff --git a/src/components/RightPanel/index.vue b/src/components/RightPanel/index.vue new file mode 100644 index 0000000..5abeecb --- /dev/null +++ b/src/components/RightPanel/index.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/RightToolbar/index.vue b/src/components/RightToolbar/index.vue new file mode 100644 index 0000000..d667517 --- /dev/null +++ b/src/components/RightToolbar/index.vue @@ -0,0 +1,134 @@ + + + diff --git a/src/components/RuoYi/Doc/index.vue b/src/components/RuoYi/Doc/index.vue new file mode 100644 index 0000000..75fa864 --- /dev/null +++ b/src/components/RuoYi/Doc/index.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/src/components/RuoYi/Git/index.vue b/src/components/RuoYi/Git/index.vue new file mode 100644 index 0000000..bdafbae --- /dev/null +++ b/src/components/RuoYi/Git/index.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/src/components/Screenfull/index.vue b/src/components/Screenfull/index.vue new file mode 100644 index 0000000..9dd12ea --- /dev/null +++ b/src/components/Screenfull/index.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/components/SearchFormItem/index.vue b/src/components/SearchFormItem/index.vue new file mode 100644 index 0000000..541bfbf --- /dev/null +++ b/src/components/SearchFormItem/index.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..069b5de --- /dev/null +++ b/src/components/SizeSelect/index.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/components/StartEndDatePicker/index.vue b/src/components/StartEndDatePicker/index.vue new file mode 100644 index 0000000..e56478b --- /dev/null +++ b/src/components/StartEndDatePicker/index.vue @@ -0,0 +1,58 @@ + + diff --git a/src/components/StatisticsCard/index.vue b/src/components/StatisticsCard/index.vue new file mode 100644 index 0000000..5e63495 --- /dev/null +++ b/src/components/StatisticsCard/index.vue @@ -0,0 +1,217 @@ + + + + + \ No newline at end of file diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..e4bf5ad --- /dev/null +++ b/src/components/SvgIcon/index.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/components/TableFormCol/index.vue b/src/components/TableFormCol/index.vue new file mode 100644 index 0000000..f35c350 --- /dev/null +++ b/src/components/TableFormCol/index.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/components/ThemePicker/index.vue b/src/components/ThemePicker/index.vue new file mode 100644 index 0000000..1714e1f --- /dev/null +++ b/src/components/ThemePicker/index.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/src/components/TopNav/index.vue b/src/components/TopNav/index.vue new file mode 100644 index 0000000..d3ecf4a --- /dev/null +++ b/src/components/TopNav/index.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/components/iFrame/index.vue b/src/components/iFrame/index.vue new file mode 100644 index 0000000..426857f --- /dev/null +++ b/src/components/iFrame/index.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue new file mode 100644 index 0000000..d500238 --- /dev/null +++ b/src/layout/components/Navbar.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..bb3c9ce --- /dev/null +++ b/src/layout/components/Settings/index.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/src/layout/components/Sidebar/FixiOSBug.js b/src/layout/components/Sidebar/FixiOSBug.js new file mode 100644 index 0000000..6823726 --- /dev/null +++ b/src/layout/components/Sidebar/FixiOSBug.js @@ -0,0 +1,25 @@ +export default { + computed: { + device() { + return this.$store.state.app.device + } + }, + mounted() { + // In order to fix the click on menu on the ios device will trigger the mouseleave bug + this.fixBugIniOS() + }, + methods: { + fixBugIniOS() { + const $subMenu = this.$refs.subMenu + if ($subMenu) { + const handleMouseleave = $subMenu.handleMouseleave + $subMenu.handleMouseleave = (e) => { + if (this.device === 'mobile') { + return + } + handleMouseleave(e) + } + } + } + } +} diff --git a/src/layout/components/Sidebar/Item.vue b/src/layout/components/Sidebar/Item.vue new file mode 100644 index 0000000..be3285d --- /dev/null +++ b/src/layout/components/Sidebar/Item.vue @@ -0,0 +1,33 @@ + diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..8b0bc93 --- /dev/null +++ b/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..148832e --- /dev/null +++ b/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..82ba407 --- /dev/null +++ b/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..15d4c10 --- /dev/null +++ b/src/layout/components/Sidebar/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..bb753a1 --- /dev/null +++ b/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..96585a5 --- /dev/null +++ b/src/layout/components/TagsView/index.vue @@ -0,0 +1,332 @@ + + + + + + + diff --git a/src/layout/components/index.js b/src/layout/components/index.js new file mode 100644 index 0000000..104bd3a --- /dev/null +++ b/src/layout/components/index.js @@ -0,0 +1,5 @@ +export { default as AppMain } from './AppMain' +export { default as Navbar } from './Navbar' +export { default as Settings } from './Settings' +export { default as Sidebar } from './Sidebar/index.vue' +export { default as TagsView } from './TagsView/index.vue' diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..dba4393 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/layout/mixin/ResizeHandler.js b/src/layout/mixin/ResizeHandler.js new file mode 100644 index 0000000..e8d0df8 --- /dev/null +++ b/src/layout/mixin/ResizeHandler.js @@ -0,0 +1,45 @@ +import store from '@/store' + +const { body } = document +const WIDTH = 992 // refer to Bootstrap's responsive design + +export default { + watch: { + $route(route) { + if (this.device === 'mobile' && this.sidebar.opened) { + store.dispatch('app/closeSideBar', { withoutAnimation: false }) + } + } + }, + beforeMount() { + window.addEventListener('resize', this.$_resizeHandler) + }, + beforeDestroy() { + window.removeEventListener('resize', this.$_resizeHandler) + }, + mounted() { + const isMobile = this.$_isMobile() + if (isMobile) { + store.dispatch('app/toggleDevice', 'mobile') + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_isMobile() { + const rect = body.getBoundingClientRect() + return rect.width - 1 < WIDTH + }, + $_resizeHandler() { + if (!document.hidden) { + const isMobile = this.$_isMobile() + store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') + + if (isMobile) { + store.dispatch('app/closeSideBar', { withoutAnimation: true }) + } + } + } + } +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..ee24aed --- /dev/null +++ b/src/main.js @@ -0,0 +1,216 @@ +import Vue from 'vue'; + +import Cookies from 'js-cookie'; + +import Element from 'element-ui'; +import './assets/styles/element-variables.scss'; + +import '@/assets/styles/index.scss'; // global css +import '@/assets/styles/ipad.scss'; // ipad global css +import '@/assets/styles/ruoyi.scss'; // ruoyi css +import { download } from '@/utils/request'; +import App from './App'; +import directive from './directive'; // directive +import plugins from './plugins'; // plugins +import router from './router'; +import store from './store'; + +import { getConfigKey } from '@/api/system/config'; +import { getDicts } from '@/api/system/dict/data'; +import { checkPermi, checkRole, isAgent, isSysAdmin } from '@/utils/permission'; +import { addDateRange, handleTree, parseTime, resetForm, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'; +import './assets/icons'; // icon +import './permission'; // permission control +// 分页组件 +import Pagination from '@/components/Pagination'; +// 自定义表格工具组件 +import RightToolbar from '@/components/RightToolbar'; +// 富文本组件 +import Editor from '@/components/Editor'; +// 文件上传组件 +import FileUpload from '@/components/FileUpload'; +// 图片上传组件 +import ImageUpload from '@/components/ImageUpload'; +// 图片预览组件 +import ImagePreview from '@/components/ImagePreview'; +// 字典标签组件 +import DictTag from '@/components/DictTag'; +// 头部标签组件 +import VueMeta from 'vue-meta'; +// 字典数据组件 +import DictData from '@/components/DictData'; +// 过滤器 +import filter from '@/utils/filter'; +// 行内表单组件 +import FormCol from '@/components/FormCol'; + +filter(Vue); + +// 全局方法挂载 +Vue.prototype.getDicts = getDicts +Vue.prototype.getConfigKey = getConfigKey +Vue.prototype.parseTime = parseTime +Vue.prototype.resetForm = resetForm +Vue.prototype.addDateRange = addDateRange +Vue.prototype.selectDictLabel = selectDictLabel +Vue.prototype.selectDictLabels = selectDictLabels +Vue.prototype.download = download +Vue.prototype.handleTree = handleTree +Vue.prototype.checkPermi = checkPermi +Vue.prototype.checkRole = checkRole +Vue.prototype.isSysAdmin = isSysAdmin +Vue.prototype.isAgent = isAgent + +// 全局组件挂载 +Vue.component('DictTag', DictTag) +Vue.component('Pagination', Pagination) +Vue.component('RightToolbar', RightToolbar) +Vue.component('Editor', Editor) +Vue.component('FileUpload', FileUpload) +Vue.component('ImageUpload', ImageUpload) +Vue.component('ImagePreview', ImagePreview) +Vue.component('FormCol', FormCol) + +Vue.use(directive) +Vue.use(plugins) +Vue.use(VueMeta) +DictData.install() + +/** + * If you don't want to use mock-server + * you want to use MockJs for mock api + * you can execute: mockXHR() + * + * Currently MockJs will be used in the production environment, + * please remove it before going online! ! ! + */ + +Vue.use(Element, { + size: Cookies.get('size') || 'medium' // set element-ui default size +}) + +Vue.config.productionTip = false + +new Vue({ + el: '#app', + router, + store, + render: h => h(App) +}) + + +// 全局添加table左右拖动效果的指令 +Vue.directive('tableMove', { + bind: function (el, binding, vnode) { + var odiv = el // 获取当前表格元素 + + // 修改样式小手标志 + // el.style.cursor = 'pointer' + el.querySelector('.el-table .el-table__body-wrapper').style.cursor = 'pointer' + + var mouseDownAndUpTimer = null + var mouseOffset = 0 + var mouseFlag = false + + let bindRef = binding.value[0] //绑定的表格的ref属性 + + + odiv.onmousedown = (e) => { + const ePath = composedPath(e) + // 拖拽表头不滑动 + if (ePath.some(res => { + return res && res.className && res.className.indexOf('el-table__header') > -1 + })) return + + if (e.which !== 1) return + + mouseOffset = e.clientX + mouseDownAndUpTimer = setTimeout(function () { + mouseFlag = true + }, 80) + } + + odiv.onmouseup = (e) => { + setTimeout(() => { + // 解决拖动列宽行不对齐问题--渲染表格 + vnode.context.$refs[bindRef].doLayout() + }, 200) + if (mouseFlag) { + mouseFlag = false + } else { + clearTimeout(mouseDownAndUpTimer) // 清除延迟时间 + } + } + + odiv.onmouseleave = (e) => { + setTimeout(() => { + // 解决拖动列宽行不对齐问题--渲染表格 + vnode.context.$refs[bindRef].doLayout() + }, 200) + mouseFlag = false + } + + odiv.onmousemove = (e) => { + if (e.which !== 1) return + + const divData = odiv.querySelector('.el-table .el-table__body-wrapper') + if (mouseFlag && divData) { + // 设置水平方向的元素的位置 + divData.scrollLeft -= (-mouseOffset + (mouseOffset = e.clientX)) + } + } + + // 解决有些时候,在鼠标松开的时候,元素仍然可以拖动; + odiv.ondragstart = (e) => { + e.preventDefault() + } + + odiv.ondragend = (e) => { + e.preventDefault() + } + + // 点击复制文本 + // odiv.onclick = (e) => { + // setTimeout(() => { + // if (mouseFlag) return + // if (e.target.nodeName === 'SPAN') { + // console.log(mouseFlag, e.target.nodeName, this) + // var aux = document.createElement('input') + // aux.setAttribute('value', e.target.outerText) + // document.body.appendChild(aux) + // aux.select() + // document.execCommand('copy') + // document.body.removeChild(aux) + // if (document.execCommand('copy')) { + // console.log('复制成功') + // } else { + // console.log('复制失败') + // } + // } + // }, 300) + // } + + // 是否拖拽可选中文字 + odiv.onselectstart = () => { + return false + } + + //浏览器Event.path属性不存在 + function composedPath(e) { + // 存在则直接return + if (e.path) { + return e.path + } + // 不存在则遍历target节点 + let target = e.target + e.path = [] + while (target.parentNode !== null) { + e.path.push(target) + target = target.parentNode + } + // 最后补上document和window + e.path.push(document, window) + return e.path + } + } +}) diff --git a/src/permission.js b/src/permission.js new file mode 100644 index 0000000..e516486 --- /dev/null +++ b/src/permission.js @@ -0,0 +1,58 @@ +import { getToken } from '@/utils/auth' +import { isRelogin } from '@/utils/request' +import { Message } from 'element-ui' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import router from './router' +import store from './store' + +NProgress.configure({ showSpinner: false }) + +const whiteList = ['/login', '/register', '/liveness'] + +router.beforeEach((to, from, next) => { + NProgress.start() + if (getToken()) { + to.meta.title && store.dispatch('settings/setTitle', to.meta.title) + /* has token*/ + if (to.path === '/login') { + next({ path: '/' }) + NProgress.done() + } else if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + if (store.getters.roles.length === 0) { + isRelogin.show = true + // 判断当前用户是否已拉取完user_info信息 + store.dispatch('GetInfo').then(() => { + isRelogin.show = false + store.dispatch('GenerateRoutes').then(accessRoutes => { + // 根据roles权限生成可访问的路由表 + router.addRoutes(accessRoutes) // 动态添加可访问路由表 + next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + }) + }).catch(err => { + store.dispatch('LogOut').then(() => { + Message.error(err) + next({ path: '/' }) + }) + }) + } else { + next() + } + } + } else { + // 没有token + if (whiteList.indexOf(to.path) !== -1) { + // 在免登录白名单,直接进入 + next() + } else { + next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页 + NProgress.done() + } + } +}) + +router.afterEach(() => { + NProgress.done() +}) diff --git a/src/plugins/auth.js b/src/plugins/auth.js new file mode 100644 index 0000000..6c6bc24 --- /dev/null +++ b/src/plugins/auth.js @@ -0,0 +1,60 @@ +import store from '@/store' + +function authPermission(permission) { + const all_permission = "*:*:*"; + const permissions = store.getters && store.getters.permissions + if (permission && permission.length > 0) { + return permissions.some(v => { + return all_permission === v || v === permission + }) + } else { + return false + } +} + +function authRole(role) { + const super_admin = "admin"; + const roles = store.getters && store.getters.roles + if (role && role.length > 0) { + return roles.some(v => { + return super_admin === v || v === role + }) + } else { + return false + } +} + +export default { + // 验证用户是否具备某权限 + hasPermi(permission) { + return authPermission(permission); + }, + // 验证用户是否含有指定权限,只需包含其中一个 + hasPermiOr(permissions) { + return permissions.some(item => { + return authPermission(item) + }) + }, + // 验证用户是否含有指定权限,必须全部拥有 + hasPermiAnd(permissions) { + return permissions.every(item => { + return authPermission(item) + }) + }, + // 验证用户是否具备某角色 + hasRole(role) { + return authRole(role); + }, + // 验证用户是否含有指定角色,只需包含其中一个 + hasRoleOr(roles) { + return roles.some(item => { + return authRole(item) + }) + }, + // 验证用户是否含有指定角色,必须全部拥有 + hasRoleAnd(roles) { + return roles.every(item => { + return authRole(item) + }) + } +} diff --git a/src/plugins/cache.js b/src/plugins/cache.js new file mode 100644 index 0000000..6b5c00b --- /dev/null +++ b/src/plugins/cache.js @@ -0,0 +1,77 @@ +const sessionCache = { + set (key, value) { + if (!sessionStorage) { + return + } + if (key != null && value != null) { + sessionStorage.setItem(key, value) + } + }, + get (key) { + if (!sessionStorage) { + return null + } + if (key == null) { + return null + } + return sessionStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + sessionStorage.removeItem(key); + } +} +const localCache = { + set (key, value) { + if (!localStorage) { + return + } + if (key != null && value != null) { + localStorage.setItem(key, value) + } + }, + get (key) { + if (!localStorage) { + return null + } + if (key == null) { + return null + } + return localStorage.getItem(key) + }, + setJSON (key, jsonValue) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)) + } + }, + getJSON (key) { + const value = this.get(key) + if (value != null) { + return JSON.parse(value) + } + }, + remove (key) { + localStorage.removeItem(key); + } +} + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, + /** + * 本地缓存 + */ + local: localCache +} diff --git a/src/plugins/download.js b/src/plugins/download.js new file mode 100644 index 0000000..8a9de95 --- /dev/null +++ b/src/plugins/download.js @@ -0,0 +1,79 @@ +import axios from 'axios' +import { Loading, Message } from 'element-ui' +import { saveAs } from 'file-saver' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate } from '@/utils/ruoyi' + +const baseURL = process.env.VUE_APP_BASE_API +let downloadLoadingInstance; + +export default { + name(name, isDelete = true) { + var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + resource(resource) { + var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource); + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + zip(url, name) { + var url = baseURL + url + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/zip' }) + this.saveAs(blob, name) + } else { + this.printErrMsg(res.data); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) + }, + saveAs(text, name, opts) { + saveAs(text, name, opts); + }, + async printErrMsg(data) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } +} + diff --git a/src/plugins/index.js b/src/plugins/index.js new file mode 100644 index 0000000..d000f2d --- /dev/null +++ b/src/plugins/index.js @@ -0,0 +1,20 @@ +import tab from './tab' +import auth from './auth' +import cache from './cache' +import modal from './modal' +import download from './download' + +export default { + install(Vue) { + // 页签操作 + Vue.prototype.$tab = tab + // 认证对象 + Vue.prototype.$auth = auth + // 缓存对象 + Vue.prototype.$cache = cache + // 模态框对象 + Vue.prototype.$modal = modal + // 下载文件 + Vue.prototype.$download = download + } +} diff --git a/src/plugins/modal.js b/src/plugins/modal.js new file mode 100644 index 0000000..9c86a51 --- /dev/null +++ b/src/plugins/modal.js @@ -0,0 +1,83 @@ +import { Loading, Message, MessageBox, Notification } from 'element-ui' + +let loadingInstance; + +export default { + // 消息提示 + msg(content) { + Message.info(content) + }, + // 错误消息 + msgError(content) { + Message.error(content) + }, + // 成功消息 + msgSuccess(content) { + Message.success(content) + }, + // 警告消息 + msgWarning(content) { + Message.warning(content) + }, + // 弹出提示 + alert(content) { + MessageBox.alert(content, "系统提示") + }, + // 错误提示 + alertError(content) { + MessageBox.alert(content, "系统提示", { type: 'error' }) + }, + // 成功提示 + alertSuccess(content) { + MessageBox.alert(content, "系统提示", { type: 'success' }) + }, + // 警告提示 + alertWarning(content) { + MessageBox.alert(content, "系统提示", { type: 'warning' }) + }, + // 通知提示 + notify(content) { + Notification.info(content) + }, + // 错误通知 + notifyError(content) { + Notification.error(content); + }, + // 成功通知 + notifySuccess(content) { + Notification.success(content) + }, + // 警告通知 + notifyWarning(content) { + Notification.warning(content) + }, + // 确认窗体 + confirm(content) { + return MessageBox.confirm(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 提交内容 + prompt(content) { + return MessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 打开遮罩层 + loading(content) { + loadingInstance = Loading.service({ + lock: true, + text: content, + spinner: "el-icon-loading", + background: "rgba(0, 0, 0, 0.7)", + }) + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close(); + } +} diff --git a/src/plugins/tab.js b/src/plugins/tab.js new file mode 100644 index 0000000..104e680 --- /dev/null +++ b/src/plugins/tab.js @@ -0,0 +1,76 @@ +import store from '@/store' +import router from '@/router' + +export default { + // 刷新当前tab页签 + refreshPage(obj) { + const { path, query, matched } = router.currentRoute; + if (obj === undefined) { + matched.forEach((m) => { + if (m.components && m.components.default && m.components.default.name) { + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { + obj = { name: m.components.default.name, path: path, query: query }; + } + } + }); + } + return store.dispatch('tagsView/delCachedView', obj).then(() => { + const { path, query } = obj + router.replace({ + path: '/redirect' + path, + query: query + }) + }) + }, + // 关闭当前tab页签,打开新页签 + closeOpenPage(obj) { + store.dispatch("tagsView/delView", router.currentRoute); + if (obj !== undefined) { + return router.push(obj); + } + }, + // 关闭当前tab页签,并回到上个页签 + closeBack() { + store.dispatch("tagsView/delView", router.currentRoute); + return router.back(); + }, + // 关闭指定tab页签 + closePage(obj) { + if (obj === undefined) { + return store.dispatch('tagsView/delView', router.currentRoute).then(({ visitedViews }) => { + const latestView = visitedViews.slice(-1)[0] + if (latestView) { + return router.push(latestView.fullPath) + } + return router.push('/'); + }); + } + return store.dispatch('tagsView/delView', obj); + }, + // 关闭所有tab页签 + closeAllPage() { + return store.dispatch('tagsView/delAllViews'); + }, + // 关闭左侧tab页签 + closeLeftPage(obj) { + return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute); + }, + // 关闭右侧tab页签 + closeRightPage(obj) { + return store.dispatch('tagsView/delRightTags', obj || router.currentRoute); + }, + // 关闭其他tab页签 + closeOtherPage(obj) { + return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute); + }, + // 添加tab页签 + openPage(title, url, params) { + const obj = { path: url, meta: { title: title } } + store.dispatch('tagsView/addView', obj); + return router.push({ path: url, query: params }); + }, + // 修改tab页签 + updatePage(obj) { + return store.dispatch('tagsView/updateVisitedView', obj); + } +} diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..5b6b676 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,233 @@ +import Vue from 'vue' +import Router from 'vue-router' +/* Layout */ +import Layout from '@/layout' + +Vue.use(Router) + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect') + } + ] + }, + { + path: '/login', + component: () => import('@/views/login'), + hidden: true + }, + { + path: '/register', + component: () => import('@/views/register'), + hidden: true + }, + { + path: '/404', + component: () => import('@/views/error/404'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/error/401'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: 'index', + children: [ + { + path: 'index', + component: () => import('@/views/bst/index/index'), + name: 'Index', + meta: { title: '首页', icon: 'dashboard', affix: true } + }, + ] + }, + /** + * 人脸识别 + */ + { + path: '/liveness', + component: () => import('@/views/mobile/liveness/index') + }, + /** + * 编辑页 + */ + { + path: '/edit', + component: Layout, + hidden: true, + children: [ + { + path: 'area/:id?', + component: () => import('@/views/bst/area/edit/edit.vue'), + name: 'AreaEdit', + meta: { title: '编辑运营区' } + } + ] + }, + /** + * 查看页 + */ + { + path: '/view', + component: Layout, + hidden: true, + children: [ + { + path: 'order/:id?', + component: () => import('@/views/bst/order/view/view.vue'), + name: 'OrderView', + meta: { title: '订单详情' } + }, + { + path: 'device/:id?', + component: () => import('@/views/bst/device/view/view.vue'), + name: 'DeviceView', + meta: { title: '设备详情' } + }, + { + path: 'user/:userId?', + component: () => import('@/views/system/user/view/view.vue'), + name: 'UserView', + meta: { title: '用户详情' } + } + ] + }, + { + path: '/user', + component: Layout, + hidden: true, + redirect: 'noredirect', + children: [ + { + path: 'profile', + component: () => import('@/views/system/user/profile/index'), + name: 'Profile', + meta: { title: '个人中心', icon: 'user' } + } + ] + }, +] + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole'), + name: 'AuthRole', + meta: { title: '分配角色', activeMenu: '/system/user' } + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser'), + name: 'AuthUser', + meta: { title: '分配用户', activeMenu: '/system/role' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data'), + name: 'Data', + meta: { title: '字典数据', activeMenu: '/system/dict' } + } + ] + }, + { + path: '/monitor/job-log', + component: Layout, + hidden: true, + permissions: ['monitor:job:list'], + children: [ + { + path: 'index/:jobId(\\d+)', + component: () => import('@/views/monitor/job/log'), + name: 'JobLog', + meta: { title: '调度日志', activeMenu: '/monitor/job' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + path: 'index/:tableId(\\d+)', + component: () => import('@/views/tool/gen/editTable'), + name: 'GenEdit', + meta: { title: '修改生成配置', activeMenu: '/tool/gen' } + } + ] + } +] + +// 防止连续点击多次路由报错 +let routerPush = Router.prototype.push; +let routerReplace = Router.prototype.replace; +// push +Router.prototype.push = function push(location) { + return routerPush.call(this, location).catch(err => err) +} +// replace +Router.prototype.replace = function push(location) { + return routerReplace.call(this, location).catch(err => err) +} + +export default new Router({ + mode: 'history', // 去掉url中的# + scrollBehavior: () => ({ y: 0 }), + routes: constantRoutes +}) diff --git a/src/settings.js b/src/settings.js new file mode 100644 index 0000000..bca9317 --- /dev/null +++ b/src/settings.js @@ -0,0 +1,44 @@ +module.exports = { + /** + * 侧边栏主题 深色主题theme-dark,浅色主题theme-light + */ + sideTheme: 'theme-light', + + /** + * 是否系统布局配置 + */ + showSettings: false, + + /** + * 是否显示顶部导航 + */ + topNav: false, + + /** + * 是否显示 tagsView + */ + tagsView: true, + + /** + * 是否固定头部 + */ + fixedHeader: true, + + /** + * 是否显示logo + */ + sidebarLogo: true, + + /** + * 是否显示动态标题 + */ + dynamicTitle: true, + + /** + * @type {string | array} 'production' | ['production', 'development'] + * @description Need show err logs component. + * The default is only used in the production env + * If you want to also use it in dev, you can pass ['production', 'development'] + */ + errorLog: 'production' +} diff --git a/src/store/getters.js b/src/store/getters.js new file mode 100644 index 0000000..74490a9 --- /dev/null +++ b/src/store/getters.js @@ -0,0 +1,23 @@ +const getters = { + sidebar: state => state.app.sidebar, + size: state => state.app.size, + device: state => state.app.device, + dict: state => state.dict.dict, + visitedViews: state => state.tagsView.visitedViews, + cachedViews: state => state.tagsView.cachedViews, + token: state => state.user.token, + avatar: state => state.user.avatar, + userId: state => state.user.id, + name: state => state.user.name, + nickName: state => state.user.nickName, + introduction: state => state.user.introduction, + roles: state => state.user.roles, + permissions: state => state.user.permissions, + userType: state => state.user.userType, + deptId: state => state.user.deptId, + permission_routes: state => state.permission.routes, + topbarRouters:state => state.permission.topbarRouters, + defaultRoutes:state => state.permission.defaultRoutes, + sidebarRouters:state => state.permission.sidebarRouters, +} +export default getters diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..97aaef8 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,25 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import app from './modules/app' +import dict from './modules/dict' +import user from './modules/user' +import tagsView from './modules/tagsView' +import permission from './modules/permission' +import settings from './modules/settings' +import getters from './getters' + +Vue.use(Vuex) + +const store = new Vuex.Store({ + modules: { + app, + dict, + user, + tagsView, + permission, + settings + }, + getters +}) + +export default store diff --git a/src/store/modules/app.js b/src/store/modules/app.js new file mode 100644 index 0000000..3e22d1c --- /dev/null +++ b/src/store/modules/app.js @@ -0,0 +1,66 @@ +import Cookies from 'js-cookie' + +const state = { + sidebar: { + opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, + withoutAnimation: false, + hide: false + }, + device: 'desktop', + size: Cookies.get('size') || 'medium' +} + +const mutations = { + TOGGLE_SIDEBAR: state => { + if (state.sidebar.hide) { + return false; + } + state.sidebar.opened = !state.sidebar.opened + state.sidebar.withoutAnimation = false + if (state.sidebar.opened) { + Cookies.set('sidebarStatus', 1) + } else { + Cookies.set('sidebarStatus', 0) + } + }, + CLOSE_SIDEBAR: (state, withoutAnimation) => { + Cookies.set('sidebarStatus', 0) + state.sidebar.opened = false + state.sidebar.withoutAnimation = withoutAnimation + }, + TOGGLE_DEVICE: (state, device) => { + state.device = device + }, + SET_SIZE: (state, size) => { + state.size = size + Cookies.set('size', size) + }, + SET_SIDEBAR_HIDE: (state, status) => { + state.sidebar.hide = status + } +} + +const actions = { + toggleSideBar({ commit }) { + commit('TOGGLE_SIDEBAR') + }, + closeSideBar({ commit }, { withoutAnimation }) { + commit('CLOSE_SIDEBAR', withoutAnimation) + }, + toggleDevice({ commit }, device) { + commit('TOGGLE_DEVICE', device) + }, + setSize({ commit }, size) { + commit('SET_SIZE', size) + }, + toggleSideBarHide({ commit }, status) { + commit('SET_SIDEBAR_HIDE', status) + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/src/store/modules/dict.js b/src/store/modules/dict.js new file mode 100644 index 0000000..7a1b2f0 --- /dev/null +++ b/src/store/modules/dict.js @@ -0,0 +1,50 @@ +const state = { + dict: new Array() +} +const mutations = { + SET_DICT: (state, { key, value }) => { + if (key !== null && key !== "") { + state.dict.push({ + key: key, + value: value + }) + } + }, + REMOVE_DICT: (state, key) => { + try { + for (let i = 0; i < state.dict.length; i++) { + if (state.dict[i].key == key) { + state.dict.splice(i, 1) + return true + } + } + } catch (e) { + } + }, + CLEAN_DICT: (state) => { + state.dict = new Array() + } +} + +const actions = { + // 设置字典 + setDict({ commit }, data) { + commit('SET_DICT', data) + }, + // 删除字典 + removeDict({ commit }, key) { + commit('REMOVE_DICT', key) + }, + // 清空字典 + cleanDict({ commit }) { + commit('CLEAN_DICT') + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js new file mode 100644 index 0000000..b3c216a --- /dev/null +++ b/src/store/modules/permission.js @@ -0,0 +1,137 @@ +import auth from '@/plugins/auth' +import router, { constantRoutes, dynamicRoutes } from '@/router' +import { getRouters } from '@/api/menu' +import Layout from '@/layout/index' +import ParentView from '@/components/ParentView' +import InnerLink from '@/layout/components/InnerLink' + +const permission = { + state: { + routes: [], + addRoutes: [], + defaultRoutes: [], + topbarRouters: [], + sidebarRouters: [] + }, + mutations: { + SET_ROUTES: (state, routes) => { + state.addRoutes = routes + state.routes = constantRoutes.concat(routes) + }, + SET_DEFAULT_ROUTES: (state, routes) => { + state.defaultRoutes = constantRoutes.concat(routes) + }, + SET_TOPBAR_ROUTES: (state, routes) => { + state.topbarRouters = routes + }, + SET_SIDEBAR_ROUTERS: (state, routes) => { + state.sidebarRouters = routes + }, + }, + actions: { + // 生成路由 + GenerateRoutes({ commit }) { + return new Promise(resolve => { + // 向后端请求路由数据 + getRouters().then(res => { + const sdata = JSON.parse(JSON.stringify(res.data)) + const rdata = JSON.parse(JSON.stringify(res.data)) + const sidebarRoutes = filterAsyncRouter(sdata) + const rewriteRoutes = filterAsyncRouter(rdata, false, true) + const asyncRoutes = filterDynamicRoutes(dynamicRoutes); + rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) + router.addRoutes(asyncRoutes); + commit('SET_ROUTES', rewriteRoutes) + commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) + commit('SET_DEFAULT_ROUTES', sidebarRoutes) + commit('SET_TOPBAR_ROUTES', sidebarRoutes) + resolve(rewriteRoutes) + }) + }) + } + } +} + +// 遍历后台传来的路由字符串,转换为组件对象 +function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { + return asyncRouterMap.filter(route => { + if (type && route.children) { + route.children = filterChildren(route.children) + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === 'Layout') { + route.component = Layout + } else if (route.component === 'ParentView') { + route.component = ParentView + } else if (route.component === 'InnerLink') { + route.component = InnerLink + } else { + route.component = loadView(route.component) + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type) + } else { + delete route['children'] + delete route['redirect'] + } + return true + }) +} + +function filterChildren(childrenMap, lastRouter = false) { + var children = [] + childrenMap.forEach((el, index) => { + if (el.children && el.children.length) { + if (el.component === 'ParentView' && !lastRouter) { + el.children.forEach(c => { + c.path = el.path + '/' + c.path + if (c.children && c.children.length) { + children = children.concat(filterChildren(c.children, c)) + return + } + children.push(c) + }) + return + } + } + if (lastRouter) { + el.path = lastRouter.path + '/' + el.path + if (el.children && el.children.length) { + children = children.concat(filterChildren(el.children, el)) + return + } + } + children = children.concat(el) + }) + return children +} + +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes) { + const res = [] + routes.forEach(route => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route) + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route) + } + } + }) + return res +} + +export const loadView = (view) => { + if (process.env.NODE_ENV === 'development') { + return (resolve) => require([`@/views/${view}`], resolve) + } else { + // 使用 import 实现生产环境的路由懒加载 + return () => import(`@/views/${view}`) + } +} + +export default permission diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js new file mode 100644 index 0000000..2455a1e --- /dev/null +++ b/src/store/modules/settings.js @@ -0,0 +1,42 @@ +import defaultSettings from '@/settings' + +const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings + +const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' +const state = { + title: '', + theme: storageSetting.theme || '#409EFF', + sideTheme: storageSetting.sideTheme || sideTheme, + showSettings: showSettings, + topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, + tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, + fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, + sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, + dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle +} +const mutations = { + CHANGE_SETTING: (state, { key, value }) => { + if (state.hasOwnProperty(key)) { + state[key] = value + } + } +} + +const actions = { + // 修改布局设置 + changeSetting({ commit }, data) { + commit('CHANGE_SETTING', data) + }, + // 设置网页标题 + setTitle({ commit }, title) { + state.title = title + } +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/src/store/modules/tagsView.js b/src/store/modules/tagsView.js new file mode 100644 index 0000000..5fc011c --- /dev/null +++ b/src/store/modules/tagsView.js @@ -0,0 +1,228 @@ +const state = { + visitedViews: [], + cachedViews: [], + iframeViews: [] +} + +const mutations = { + ADD_IFRAME_VIEW: (state, view) => { + if (state.iframeViews.some(v => v.path === view.path)) return + state.iframeViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + ADD_VISITED_VIEW: (state, view) => { + if (state.visitedViews.some(v => v.path === view.path)) return + state.visitedViews.push( + Object.assign({}, view, { + title: view.meta.title || 'no-name' + }) + ) + }, + ADD_CACHED_VIEW: (state, view) => { + if (state.cachedViews.includes(view.name)) return + if (view.meta && !view.meta.noCache) { + state.cachedViews.push(view.name) + } + }, + DEL_VISITED_VIEW: (state, view) => { + for (const [i, v] of state.visitedViews.entries()) { + if (v.path === view.path) { + state.visitedViews.splice(i, 1) + break + } + } + state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) + }, + DEL_IFRAME_VIEW: (state, view) => { + state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) + }, + DEL_CACHED_VIEW: (state, view) => { + const index = state.cachedViews.indexOf(view.name) + index > -1 && state.cachedViews.splice(index, 1) + }, + + DEL_OTHERS_VISITED_VIEWS: (state, view) => { + state.visitedViews = state.visitedViews.filter(v => { + return v.meta.affix || v.path === view.path + }) + state.iframeViews = state.iframeViews.filter(item => item.path === view.path) + }, + DEL_OTHERS_CACHED_VIEWS: (state, view) => { + const index = state.cachedViews.indexOf(view.name) + if (index > -1) { + state.cachedViews = state.cachedViews.slice(index, index + 1) + } else { + state.cachedViews = [] + } + }, + DEL_ALL_VISITED_VIEWS: state => { + // keep affix tags + const affixTags = state.visitedViews.filter(tag => tag.meta.affix) + state.visitedViews = affixTags + state.iframeViews = [] + }, + DEL_ALL_CACHED_VIEWS: state => { + state.cachedViews = [] + }, + UPDATE_VISITED_VIEW: (state, view) => { + for (let v of state.visitedViews) { + if (v.path === view.path) { + v = Object.assign(v, view) + break + } + } + }, + DEL_RIGHT_VIEWS: (state, view) => { + const index = state.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + state.visitedViews = state.visitedViews.filter((item, idx) => { + if (idx <= index || (item.meta && item.meta.affix)) { + return true + } + const i = state.cachedViews.indexOf(item.name) + if (i > -1) { + state.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = state.iframeViews.findIndex(v => v.path === item.path) + state.iframeViews.splice(fi, 1) + } + return false + }) + }, + DEL_LEFT_VIEWS: (state, view) => { + const index = state.visitedViews.findIndex(v => v.path === view.path) + if (index === -1) { + return + } + state.visitedViews = state.visitedViews.filter((item, idx) => { + if (idx >= index || (item.meta && item.meta.affix)) { + return true + } + const i = state.cachedViews.indexOf(item.name) + if (i > -1) { + state.cachedViews.splice(i, 1) + } + if(item.meta.link) { + const fi = state.iframeViews.findIndex(v => v.path === item.path) + state.iframeViews.splice(fi, 1) + } + return false + }) + } +} + +const actions = { + addView({ dispatch }, view) { + dispatch('addVisitedView', view) + dispatch('addCachedView', view) + }, + addIframeView({ commit }, view) { + commit('ADD_IFRAME_VIEW', view) + }, + addVisitedView({ commit }, view) { + commit('ADD_VISITED_VIEW', view) + }, + addCachedView({ commit }, view) { + commit('ADD_CACHED_VIEW', view) + }, + delView({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delVisitedView', view) + dispatch('delCachedView', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delVisitedView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_VISITED_VIEW', view) + resolve([...state.visitedViews]) + }) + }, + delIframeView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_IFRAME_VIEW', view) + resolve([...state.iframeViews]) + }) + }, + delCachedView({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_CACHED_VIEW', view) + resolve([...state.cachedViews]) + }) + }, + delOthersViews({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delOthersVisitedViews', view) + dispatch('delOthersCachedViews', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delOthersVisitedViews({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_OTHERS_VISITED_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, + delOthersCachedViews({ commit, state }, view) { + return new Promise(resolve => { + commit('DEL_OTHERS_CACHED_VIEWS', view) + resolve([...state.cachedViews]) + }) + }, + delAllViews({ dispatch, state }, view) { + return new Promise(resolve => { + dispatch('delAllVisitedViews', view) + dispatch('delAllCachedViews', view) + resolve({ + visitedViews: [...state.visitedViews], + cachedViews: [...state.cachedViews] + }) + }) + }, + delAllVisitedViews({ commit, state }) { + return new Promise(resolve => { + commit('DEL_ALL_VISITED_VIEWS') + resolve([...state.visitedViews]) + }) + }, + delAllCachedViews({ commit, state }) { + return new Promise(resolve => { + commit('DEL_ALL_CACHED_VIEWS') + resolve([...state.cachedViews]) + }) + }, + updateVisitedView({ commit }, view) { + commit('UPDATE_VISITED_VIEW', view) + }, + delRightTags({ commit }, view) { + return new Promise(resolve => { + commit('DEL_RIGHT_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, + delLeftTags({ commit }, view) { + return new Promise(resolve => { + commit('DEL_LEFT_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, +} + +export default { + namespaced: true, + state, + mutations, + actions +} diff --git a/src/store/modules/user.js b/src/store/modules/user.js new file mode 100644 index 0000000..972d1bd --- /dev/null +++ b/src/store/modules/user.js @@ -0,0 +1,141 @@ +import { getInfo, login, logout } from '@/api/login'; +import { getToken, removeToken, setToken } from '@/utils/auth'; +// import { Watermark } from '@pansy/watermark'; + +// let watermark = null; + +const user = { + state: { + token: getToken(), + id: '', + name: '', + nickName: '', + avatar: '', + roles: [], + permissions: [], + deptId: null, + userType: null, + }, + + mutations: { + SET_TOKEN: (state, token) => { + state.token = token + }, + SET_ID: (state, id) => { + state.id = id + }, + SET_NAME: (state, name) => { + state.name = name + }, + SET_AVATAR: (state, avatar) => { + state.avatar = avatar + }, + SET_ROLES: (state, roles) => { + state.roles = roles + }, + SET_PERMISSIONS: (state, permissions) => { + state.permissions = permissions + }, + SET_DEPT_ID: (state, deptId) => { + state.deptId = deptId; + }, + SET_NICK_NAME: (state, nickName) => { + state.nickName = nickName; + + // if (watermark) { + // watermark.destroy(); + // } + // // 创建水印 + // watermark = new Watermark({ + // // 水印文案,支持多行 + // text: nickName, + // // 水印文案样式 + // font: { + // fontSize: 16, + // color: 'rgba(0, 0, 0)', + // fontFamily: 'sans-serif' + // }, + // // 水印之间的间距 + // gap: [300, 300], + // // 旋转角度 + // rotate: -20, + // // 设置水印层级 + // zIndex: 9999, + // opacity: 0.1, + // }) + }, + SET_USER_TYPE: (state, userType) => { + state.userType = userType; + } + }, + + actions: { + // 登录 + Login({ commit }, userInfo) { + const username = userInfo.username.trim() + const password = userInfo.password + const code = userInfo.code + const uuid = userInfo.uuid + return new Promise((resolve, reject) => { + login(username, password, code, uuid).then(res => { + setToken(res.token) + commit('SET_TOKEN', res.token) + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // 获取用户信息 + GetInfo({ commit, state }) { + return new Promise((resolve, reject) => { + getInfo().then(res => { + const user = res.user + const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar; + if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 + commit('SET_ROLES', res.roles) + commit('SET_PERMISSIONS', res.permissions) + } else { + commit('SET_ROLES', ['ROLE_DEFAULT']) + } + commit('SET_ID', user.userId) + commit('SET_NAME', user.userName) + commit('SET_NICK_NAME', user.nickName) + commit('SET_AVATAR', avatar) + commit('SET_DEPT_ID', user.deptId) + commit('SET_USER_TYPE', user.userType) + resolve(res) + }).catch(error => { + reject(error) + }) + }) + }, + + // 退出系统 + LogOut({ commit, state }) { + return new Promise((resolve, reject) => { + logout(state.token).then(() => { + commit('SET_TOKEN', '') + commit('SET_ROLES', []) + commit('SET_PERMISSIONS', []) + removeToken() + resolve() + }).catch(error => { + reject(error) + }) + }) + }, + + // 前端 登出 + FedLogOut({ commit }) { + return new Promise(resolve => { + commit('SET_TOKEN', '') + removeToken() + resolve() + }) + } + } +} + +export default user diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..08a43d6 --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,15 @@ +import Cookies from 'js-cookie' + +const TokenKey = 'Admin-Token' + +export function getToken() { + return Cookies.get(TokenKey) +} + +export function setToken(token) { + return Cookies.set(TokenKey, token) +} + +export function removeToken() { + return Cookies.remove(TokenKey) +} diff --git a/src/utils/config/globalConfig.js b/src/utils/config/globalConfig.js new file mode 100644 index 0000000..0573e6a --- /dev/null +++ b/src/utils/config/globalConfig.js @@ -0,0 +1,30 @@ +export default { + /** + * 高德地图配置 浅蓝 + */ + aMap: { + key: '11da89fddf9340d0a69d4fff53c0ec4b', + secret: '32dca5ef246f3b96234cd8ef891e4d59' + }, + /** + * 正常:blue, + * 离线:red, + * 临时锁车:light_blue, + * 下线:gray + * 未上架:gray + * 预约中:yellow + * 骑行中:yellow + * 轨迹 + */ + icon: { + 'blue': 'data:image/image/png;base64,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', + 'red': 'data:image/image/png;base64,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', + 'gray': 'data:image/image/png;base64,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', + 'yellow': 'data:image/image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAsCAYAAAAJpsrIAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAXjSURBVFiFxZhNbBtFFMf/M7ted9dO4jZ2VEtRbYFUkqhIJi3HqOmFqFxaS71VosmhkThUTQQSiANNeigcStOcKtFLqAQnhAMHUE91lSOQRCClH6hlDS1BdQJO4ng/7J3hMHZix3a8zkd5kuXV7uzMb9/7z5t5Q7AD45wHTNOMUUovAAhwzmOEkEDxWgegE0IynPP7hUIh6ff755sdgzTT2DCMfkLIFUJIjKw9CNCVWRArDWIvAU4OxF4CV4LgShCQNLDAcfCWbnAlqBNCph3HmVRVVd8zsBIQzS/307++AV2ZBXFyrj+Iq0fgdAyAtffpAKa8Xu/4rsFs257ghfURaTEB6cVd1zA1AZUgnHAcrL1PZ4yd2s57dcE45wHLshI0+7BffjrZlIcaGWvrRSE6DE7V0QMHDtx0DWYYRpRSek9aTESlxcSeAZUbV4IoHP0IXAmO1QptFRjnPGDb9tx+Qm2Fc+RDQ6qqTpU/o1sb5/P5K/TF3X2HAgBiL0F+fA2UGRPZbDZWF8wwjEGs6yPysy/3HaoCLnU74PF4EpzzQBVYUVdX5KeT7nr0hoDQSfE7eGJXcHRlFmTtQdS27cule/LGQ0ov0OWZKLGXGvd08ARw6E3xv7ogIGWfeLa6AFjppuHk1G3kj90Y4ZxPEkIyFBCCBzDmWlehk0BrD5BLAX//UOm91p6moQARUvribqDkNRkATNM8K6/OwZW3AODxZ4AWEV4DgH9/Ap7c2hFQudHMz3BCbw0CGJcBgFJ6hq7MNteLlQY6zwGFdeDXD3cNBQA0+xCEGdFsNhsraayfrD1orpfw25taKqxv3pe0Tb2Vm0vd0eUZeDoGzsjZbDZGcqmA6zACIoyd5wD9C6Gr8uWq52PAF61+59F1EfIGRnIpAIjJkiQFYDa5DkYvAP/8KK7LB5M0MRlqmRiwMZhwUFQG4C5FlJsWERPg8GkgdWfzvuwDXn1XhG1r6A6fdqVFYi+Bcx6QKaWRpsAOnxYApYG9oc1rKw2k7wvwhauV75UScaNwOjkQQqIyYyxFlaA7qM5zYoAntwRE6o7QlJUG1nWhNS1SW/yrC+J9FzoDkJEZYxlIWuOmJcHPXar00C8fiHymRYT3VhfEb6u5XQ2Ek3SZc65zNx5zciItvP6p+OqSh6x0NYykiRVA0sQMLUG78BaXNHDOM7LP59NtI9QYrJSfHl0Xg7b2CE9ZaTFoPVvXxYx89rWQQSMwLQJK6bxMCMmYpplk/q5+mn3YGDCXEl+uRQTc3KXa7Vp7hP5KM1HSXIGxtl7k8/n7FAAIIUkWON4YaF0Hjr4ntPba+2IG1rPSLiPyzqY+a2mvzLgSBG/pzvh8vmkKAPl8/lvW3tfwa7BwVXirtQdY/L4yh9Vr7w2JhFzaiWwH1tINAEmgbM9vmuY9+flX/bst0XZj+WM34MiHTqmqmtzYwXLOx51wHNxN6tgHY+194EpwSlXVJFC2tVZVNQlJm3bC8ZcOtVEIM7ZRxlUUI4yxUdYxoLO23pcKVnjlMrgSHCuvzCvAVFXV8/l8vBAdhqukuxdQnefBtcj01qK3qq70+/3znKqjxSp5X6GccBysY0BXFGVo67O6ZxemaY4QZkzI+udoetvdwLikwYlchNPWm+ScD9U6XNn2tCebzcY8Hk9CWkxE6fKM+2JlG2P+LjjRYcAbuqkoymi9dg2PoUqFMLGXBqXFBOjyzI6AuHpE6KmlW2eMjW89q2garBag/PhaU94rdJ4H6xjIALipKMokISTT6J2mjjqLgIOUGROe3z4JEOOPxlCRi2DtfdOKogy5AdoxGFDUHrHvNYIremrK6/VWzbp9AQMAy7LOEnspUS+sTjgOJxyf93q9b+yk/6o85ta8Xu80V4JjhcjFqmfM3wUnHNcZYzte33YMVoQbZ/6upNMxsHGPK0E40WEwxmrmp5cCBgCc8yGn83yGq0cAiBAW173kbvvetZmmOWKvPef5P7/jlmX9/n/zVJhhGIO2bU8YhhHdi/7+A/nroVl4eaRiAAAAAElFTkSuQmCC', + 'green':'data:image/image/png;base64,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', + 'orange':'data:image/image/png;base64,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', + 'orangered':'data:image/image/png;base64,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', + 'redyellow':'data:image/image/png;base64,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', + 'trajectory': 'data:image/image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAS4AAAHWCAYAAADJgWQ3AAAACXBIWXMAAAsTAAALEwEAmpwYAAAF+mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDI0LTA3LTA4VDIyOjA1OjE2KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wNy0wOFQyMjowODowNSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyNC0wNy0wOFQyMjowODowNSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpiYWY4MWYxZS02ZjcwLTJhNDctYTdhMC1jNmNhM2U5YmQ0MjgiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpmODdiOTQ2ZS03NzU4LTY5NDctOTI4ZC01MDQ3MGI2Mzg3NWEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZjA5N2YxZC04MjU5LWRmNGYtOWYyMS00MmZiOTgwZWQwZGEiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjRmMDk3ZjFkLTgyNTktZGY0Zi05ZjIxLTQyZmI5ODBlZDBkYSIgc3RFdnQ6d2hlbj0iMjAyNC0wNy0wOFQyMjowNToxNiswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiYWY4MWYxZS02ZjcwLTJhNDctYTdhMC1jNmNhM2U5YmQ0MjgiIHN0RXZ0OndoZW49IjIwMjQtMDctMDhUMjI6MDg6MDUrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz490NCZAAEXDElEQVR4nOz9d7xt2VXfiX7HnHOFHU68uXJSqSRRiiigHEABJQQ0wQYD7Ta2AWM/29jGfg1GuN22adxtdzs83DZ+3TQmCGUhIQkJZVBEEgpVipWrbj5p773WmnOO/mOutc85N1fVDefW3b9P7brn7LPD2muv9VtjjvEbvyGqygwzzDDD5QRzqTdghhlmmOHhYkZcM8www2WHGXHNMMMMlx1mxDXDDDNcdpgR1wwzzHDZYUZcM8www2WHGXHNMMMMlx1mxDXDDDNcdpgR1wwzzHDZYUZcM8www2WHGXHNMMMMlx1mxDXDDDNcdpgR1wwzzHDZYUZcM8www2UHdyFfXEQu5MvPsMORZbulaQ7PfJOuYFwo2yy5kH5cM+K6MjBcuEaaupG6Glu0cpCVxrp5a7I8apwE71eVukJ9gDwUvaFW44dmhHYFYEZcM+woFP19LjRNYazsE3FPrKrJU4hrt0E8ALIEeQHOQgzQTMCvAQfBfslmi1/InP2qcdl9/X6/8iE0xw9/c0Zkj0HMiGuGHYHhwjVFXdW7QwjfEZr1F4N/CmbuwPz8/L7hcDjcu29fLkjRHVfdMaAaOXTooY3VlbVja2trB9Hxg1DeMVxY+NMY46dE5EFn3WTl2LdnBPYYwoy4ZrikmF+81nrv94YQn1uNj78O8icu7d59w9XXXD3nrCuMcagqIUZQwRiDMQZVJcYAgLXpvqge7339wAMPbqyurD5UjVa/YPP+24s8/4Sx9t71lXvqS/xxZzhPmBHXDJcMg7mrBjHE28ej9R+xefHdu3YtX793375hnuctMUVijIgkwhIxaEwHrQggCigxdlEYgIAKMSox+NGdd955d1ONP1P0hv/JWPP5zGUrq8fvmkVflzlmxDXDRcfi8vXSNM3uqq5f7avxX11c3v2k666/bikvCqrJBJdloIqi0B1Gp/jKBSH9J9O/qyqCwfuItZYYPA899ODag/c/+AWx9reLonindfa+jdV7w0X7wDOcd8yIa4aLiuXdN5i6aW4Yj6u/aoz88BOf8MRrjTW5YFANxBgw1oJ237MQY0Q1AiBi2n8FYwRViDGgUaGNzJQUpWmURF4x4r2v7rjzzgfrSfXHRZn/axH96mR0cEZelylmxDXDRcOuPTeZuqlvHY02/l6vN3j1DTfecMAalyImIEZPljka32BNxtZEfEdi02UigmpEdevfARRjhKZpEDEYcYSW9EC56667jh0/8uD7s7z3P4N+oamPzsjrMsSMuGa4aBguXH39xsboF4Zzwx+45aab91+q7fj2Xd8+euzwfe8ytvcvRMxXgz8ez/6sGXYSLhS/zFp+ZtiGwdyBwXg0/rF+r3z9TZeQtACuufa65bn5vd8bw+RnNcYbsnzX7Eo4AzAjrhm2YH7x2n4M8YezzP7kddddf40Re0m3J3M5tzzu1l293sLrY/T/fQhh4ZJu0Aw7BjPiusLR5Z2Wdt1ggGeMx6OfvPHGm28pyx4hXNq0UpJYGG5+3OOvNrb4QTH2ZS7fPYu6ZpgR15WMVPGzWJthrd0/mUx+dt/+A08vyx6q4OyljbiMpEpkXpTc+vjH3xhC+Ft5nt8wmLt6Rl5XOGbEdYVCxJBlOWU5YP9Vt5o8z1+yvLz8/H379g0Qg7VmKm24VAihQSQlePO8zPft2/eMpml+3BjTu6QbNsMlx4y4rkCICC7L6fXnWFjcxWDQ3z0YDP67/fv3Xy1t605q07m0wnUxgqJJWmEs+/YdGKrqq2OMN13SDZvhkmNGXFcgrHX0ygHz88vs2n2VK8vyOYPB4Jkp36UYadXw57Qgk6nY9OGWvrv82mmfp0k5ZgREFIgsLi7cFGN47XD+mgvqJTfDzsaMuK4wiBhcljMYzjGcW8BaWxhjXi0iu2CTqwQ5a8B1IuE8HN1eR1hnIrtNMWtEiFhrOHBg/27r7GuyPJs/5zeb4TGHGXFdYTDGUOQl/f4ceV7SEtYzgWLr486FhNJj9BHlwrYS1rkSnqpirWNpcfHmLMtu273vcXKOYeEMjzHMiOsKgojgXEbZG5IXPYyxAjwLuObEx17IjopHim5pubCwOMyz/Gl5nrnOjWKGKwuzb/wKQqokFhRliXMZpO//GcDc1sftRNLqICLMzc0NiqJ4Sp7nLi9KnHMYMzuUryTMvu0rCMZasrygKHrYTY3WLUB5CTfrYUFVaeqasldeNxwMsvn55fbzZLPI6wrCrDJzhUDEYK2jKEoyV3R5JQMcOP/vJeeUfH+kCDGSObcEZAsLuxGR1swwEEIyLZzhsY3ZJeoKgTEGZx1ZVmCdo01qW+Ccq3PbbWm2I5FUJASPiJBljhgD3jeE4AkhEGPEGLM12nvEEJFSRPJef8BgME9elBhjmRmSXBmYRVxXAEQERLDW4Vy2NR/kgIelQu+im+nPIZkKZlnG8vIyB/bv58BVBxgMBsQYmFQVk/GE1dVVjh0/zsGDhxiPx62hoDmBCFOV8hyRA31jLEXZpyz7jMcb4B/Wa8xwmWJGXFcQRAQxhi0Sgqy9nfPzO9KaTMYYY7jm6qt56lOewuNvu5U9u3dTFAVlWWJtUr1bY+rGN4xHk7zxniNHjnDHV+/kK1+9g7vvvpu68bg2Akx8KtutoE8PB/RERJzLNM9LMpfR1BU7uLYww3nCjLiuAKiyxRt+Ww5oG4udDcYYvPc0vuaGG6/ne172Mp70xCcwHA6w1mFMO4oMTREVWoNQ5AWZy2trTb53z25uuflmXvziF/K1b3yTj37s4xw+dJiNjRF13UyXkwhTkjzd5tAWFYyxFEWPrCgxk9F0qtAMj13MiOsKwXQaj4atEck55ziNSd7xUT0vefEL6zd8//fp8uIiAlLXTd5N+mlfuxsvpkmgCoB0U36stSwuLvKcZz+Tpz/9qfz55z7P7/ze73H8+HGyLMdaM50IdKZNohXNGmPI85Ky6DN264Tgd7SkY4ZHj1ly/opANx4sEkOETaX7uXUjtvkxI8IP/cD31z/6oz+kSwvzhBCYTKo0BEP1FKS15c1BVbXuKo0xRoIPWGO4/fYn8Xf+1s9x043Xg0aaxp/LUtGyZZlrXUZZ9snyfCaLuAIw+4avEKimvFHUmJaM5wghRTS9XsmLXvSC+sUveZHmLlPfeHzjUVVVpYbpDbaT1omou+0JIeCbBmssN910I3/3//O32btnz7kaGBpSgj7pOowhywvyvMRaO5t38BjHjLiuEIikcWAa49ZllHCGqCtFWpayyHnhC55Xv+jFz1fbViRj3NRoteFUd9NT/sv0obBJcCk6EphMKpaWFvk7f+fn2b1rmXMIuSyJuNrXAecczmYzFf0VgNk3fMUgJcxjS1wt6ZwxLDEiFEXBM57xtPqpT709DgdDNdZqCJuvEVXRGImqhBhPusUTHhM3Ca2eUlMbCTZNw9LyYv36172GpaWls+m9XHvb8hkkeeDMDuvHPGbf8BWCxFOK9w3eN2d9fDIbzLj2ugM877uerfv27qPIC9W4PbKKpyCpGKOGGIlR1YdA4702IWiMUVOBQDV25KWbsViMCqr5rbc+rj5wYC/e+zMt+RxbclyqJJcKjSBsLUDM8BjEjLiuGCghBnxT09RNZ0VzWrWmc46iKHjRC15QX3PtNTocDnQarbVPaglI46mXhdNcWkc+HbGFtgIZY0RhSl5piKyQ5zkvfvGL67IszrTsM5ygQYsxpvyYxpmC/jGOGXFdIVCF4D0+1IRQE1MC/JSnd2rLMezbt5fbbrtVe2Wpiex0+mK6ielbnH0bVLbkwjjh+dPkfpY5du9a5hnPeHqdZWfUx051aKqR4D0xBMLZpRQzXOaYEdcVA01RiJppC9Cp0PUjjsYjXvmq76l3LS+ryzJCiram0gbYxlQnMsVZ452pFDZFaPWWF6izLKPXK3n6059Gnp9dapgqlJ6qGtH4Gp0JUB/zmBHXFQRjHHmeJ8mAOXXiW1VpfMO+vbt5yUterFmW0TQNW0Oj84AzEpuIUJYliwvzXHPNNSTSPeVTcgCNkWoyZjzeaFt+4izH9RjHjLiuEBhjyfKcsjcgywvETIlgW3giIvR7Ja961avqyXjEpKpomhOS+W1UZozBPMJkUhvziYicUpNhraUsy/zZz37m6VwpLLALEB8axuN1JuONFHFtb2ua4TGIGXFdEWgtm4s+vd6wdT8VIUUs20Ivay1ihNuf/CTNi4w8yzHG6PZXS6TjrBXr3Dby2prDOhFdTksAMYZku3xq4jPGMOj3uPVxjyPL3OlEqQ5ol4kT6ibl7mbtPo99zIjrCoBtnU/L3pCi6G1tiTllF3PmHAsLCzRNo3XTdGLTbWxgjAFVrIhY60RThTC9aKsRU0RUka3hnQDWGmyKonLhtMMy8qIo2L17N0tLi2nSz8mPE0BijGgMM9K6gjAjrsc4OvV7npeUvQHWZV1eXoGTBF0CDPp9ekVObDwIerrVoLNOMlvgTIZzuVisaADBApboo8QQRSIiGEGNxKAimNwYk5uWvNiigG+RQxoIW5YZ1197LTHEE0lJ6EwCWplFN85shsc+Zu4Qj3F0DdJ5XpDneWs9c/qTW4xQFgVZ65GlVUMa+jod3CrdK4cYqfyI0WjEZFJhnMVYK1mWdTotBaiaihhV2lxVbp0hM9mJW5Gz2QpUg+Yi4H1DUeZ43+C2SyMMMGw3pa2UpjXsLOh67GNGXI9xiBiMsa1flmULaQlJwLlpFdEm3IteD+tyaaox1tlWBR9FVduVnWkbmQ3OWRQVH0Mb9SiqgcY3xBglm07gCcluJka0V2Ct7bRkp4SqJhudGOn3+tsS9A9HOzbDYxMz4npMY9PYb1M5tQ3bkvNGBCOGIksrt6qqEOukXdIhYgRUjJG2xUZTkt1luTEuEZEarM3ouYJqMqGqaqw1CIIxrpWMWnR7FLcNqponsrUURbH1/hM/nAOkU9zPFolXDmbE9RiHqhJDoK4rqnqCdQ5jHNZaVLVmS54zxsjK8eMcPPgQhw8dyo8dPVxHhSzLybIc55xYa/Nu4EUISlU1U8scjamdp9I6tQTFiDMp4goh4GtPjJEyLzGlJUo8kUzzbX5dwQMFdV0TYzzVMjBLn3GzFWmGKwMz4npMI03e8d4zmYywa8fTYAtXYIzR/mDOhxBMtwyLmpqwEVhe3kXwTS4mq9P0ngASBRHEpEbmLDNktmjdHxqUAmsFxKCkyqOGQFVXNLViywwjhiyz1NUYkdg6QKT3RlMF1BiDKhRFsqhxzuK9J8ZtQlQBsunAjhBmya0rCDPieoxDFWIM1NU4Kcyr8XTSz2A4F5ummZKFqGKdI3MZiwsLTMYbRDU5aBVjEETzvMjInMXaRCi9ok9Zlq3avUAMND7gfcA5i2utaXxI941HY5qmoaor6rohhEDTeJrGo1Gnk4gSmUaKokjyCWPolqgdRMQZI1T1hMbXZ/Oon+ExhBlxPebRRV2JJOq6aqf9CDeYJ0gIUbMsRUrqA0aEEDxN0xBDRKxQlnlRFAVFmdMrC2xm6JRZoqkS6RtPXY9T9KVp4EUMgUmTyEQ16bcGgxxjSmAOYxx14xltjJhUybWirn0bXUViVLIsQ8S2y8dkD52iLs2zzDkQGY/WaZp6q330DI9xzIjrCkB3QouEbVGJqor3UUCIEapqwvr6Ct/5jCdjjLK8tEg5yCiKgs6lIYSAhri9T6g1mujUEoloupVbSqGJQPfWm9uQ1A/9fsnc3AARoWkaRqMR4/GYauIhptxZXTUM5pIZhJK883tlzzXeMx6t431n1TNjrisBM+K6YtBFI1tPbLUQGI1WCcHzxNsez+7di/zAD76Bvft2MR5NCLGZJte7xPlWWcKj9XbvKoUhhKlYdn5+nvn5eaqqwRrH9ddfw569u1hZXSUveiBC8A1lWRgdK3U9mQ7smOHKwIy4rlC89GWvFR/8cjhcxRtvvKl6/K23FK9//et4yUtegEalaWpCrLHWTat8wDbSOh/EtdUosHuProexKCzO5fzlH/sR+sM+H/3IJ/jiX3yZBw8eJHOWzFlbOzMjrSsQM+K6QvHKV77Mjkbj29bWVnnhC58v3/PylyIITV210Q90I806Uuiqj+eDsOBk8uteu3u/GBs6S5sf+qE38P3f/338wR+8hT/58Ef5+Ec/RpabganUzEjryoNcyC99NiJqZ+IDH/xoMR6Pbs8z98+e/oynvWBuOOiHGGjqps1FhVaSoJyqnXUq+DyNgPRccWL0tvU+ACV5zsegGOuw1pFlOQcfOsTv/LffJWj87MbG6GVv/JX/7/FHvBEzXFBcKH6ZEdcVhI989JOm1yt3q4bX5Xn2huWlxectLi4sdGPvVRVr7bQCWRTFtjFksEky3XLufB4/pyYy2v5KqGtPv99Pwlab8ZnPfnbl6NFjP3Ps+Mqbf+wv//DkvG3IDOcNM+Ka4VHhgx/608xZ811zc4PXLC7Mv74s86vKshgaY9AYQWQqNeiS8Ukcqq1qfbvnFjz67/fsr2OBiPc1eZ4RNWDE4n0gywpi9Nxx59c//dCDB//T8ZW19zbe3/2Tf+VHZ2KuHYQZcc3wiPG+P/5YX4SXqOrP7d2z6ynXX3v1gSxz+KYCSSaDqhFF8N7jXEp9xhhOymmdzyjrxKbpra/bEWiSOLTbYASNqfk6hCRwbZrIZz77+XsPHjrysaj66xr1z3/sL/3A2eevzXBRcKH4ZebH9RjHH73vI7tijD/RNP4fNz68YH19fAAMQjs7VQQffDsMI5JlDtU4JS3glNHW+cCJTqld3mxKaDRE9UDyy0eFEBKRJQ2q0PhIUHNNbzD3vc7l/yTL8uf+7pveccbRQDNc/phFXI9R/OF7PiQish/051T1B13mbjDG5cE33Pq4G9m1NI+1EFWmtsjd5OitItVL+h2KIgjeB/K8pK5rrJXWOgdQxwMPHeTr37ybvChR1dHK8aOfNML/Zq19z/e97hXVpdv4GWC2VJzhYeCdf/hBa8TcjvDzBl5ts2yvafsRq2rCsF9w++23pXB7S1TVDVTt3B8ude+ftk3XqkrTeKwViqJgY2Odfr/PysqEr3z1DnwExGJE8L6qJ6PRNxH+k7X2t4wxh773lS+Z6SUuEWZLxRnOCW9/5wecKs9V9J+JyOuLordXxKIxecEXRc7a+jpHjhwHsVOdVpfHMu0QC7hwB93DQWwbr0USiTVNMzVEvP+Bh/AhTknXWIOzLu/1+rdZa38G+DkRufo97/3w7Ar6GMOMuB5DeNs73u9U9Tkxxn9grHmRy7JlHwJGEhkFH7DOgbF869t3M57U5Hkac981NneJee/9JY+YRQwiaYqPc7aNCCO9sscDDxziyNEVQlTyPEdQmrrCGEtelBRF72ZjzF+LUX9OVW96z3s/PDvWH0OYfZmPEbz17e83Ck9X1b/nsuyFWZYPITkyGJsak0WgaZKUIAT45jfvZjKZTG1tulxX1+LT5bwuFURSBdH7ut02IXMlo1HN3XffR2i3MUk3JFUbY0he1MaS5cV+Y81PxRj/lqpeP4u8HjuYEddjAG99+/sNwhNR/QdZ5l7c65Xzaelk2xPbT40C05LQgDhWVja45557aZqGsizbUfbhgohLHwmSgaHHGMEYyPM0VfvOO7+Bb5Qsz1uS9YhEjKGN0EJr3WPJsnyvMfKjMcafjzFe9e4ZeT0mMCOuyxxvefv7ReFxMeovWOe+u+z1FrvISQAfIyoG2qEZyX5eUyTmDA89dISDB49S15v6LVXFYIg+TvNJMTJV0Z/vJeSmLMK0Mx/TJCIhzUs0xpLnPcbjmm/dfTdHV45j8wwhed6rCqpm+nwjyT8/tVsaymK4V8T+WIz8rRj1wHnd+BkuCWbEdRnjrW9/vwDXqvK3nXOv6vV6C1205JxrE+8AW/VPnegzeVepCvff/yCHDh8lS1Or09LLWYyxrc+V4lxyPDXGbEvoP1ps7XuEpB+DtEQUMW0EaIlReejgYY4cPY6xlhBDMiycPldOaE/SqWFiVMW5fDciPxaj/uQ73/0nC496w2e4pJgR12UMhd0a9Wetle/r9co9nYQhnbBmm5gTkuA0NVHHab/hcDhPvz9kbW2dgwePAEKWZTS+IajHOYOITvsXYdMX/tFGXl2PZCd56AZidPMUNQqDwRzeR+677wHW19dZmJtjOBx2cxtPigA3VfhJUNvtE2MMeZ5fDfGnVPW/e+e7P9R/VBs/wyXFTMd1meItb3vfHOhfNcb8/HBu7sYTLWFEBCNCVEFpm5Xb/I+qkllLWRb0yv60cgeB4WDAnr276PXSdJ0Qmm0SiRiZkh48OsmEc46maaZtRllmp4SU3jNjMp5w8OBh1kcjyrKHRmFSVQSNrK+Ppq914tzFpPxXnMsJXoEuee8ZjUZ/Ksb8U0H++DWvepF/xB9ghrPiQvHLzI/rMsRb3vZeo6ovFfgrw+FwG2ltEkwkkiKPNHOwWy5Ckbk0rTrLiCgEbZP3huOrq3jfsHvPLgaDEmMyYtQpYXU40VjwkaCu69YwMM1O9D5MvemttRw/tsbBQ4eoq4a8zFsffMizLLUq+Tglvs7ZYrO30mBE8d4nM8TYJu2NodcrnzIaTf6mtfYQ8NlH/AFmuGSYRVyXGd7ytvcZ4JYY43+Ym5t7rjG2RHSb0v3EimBHaqpKkWUM+j2yLDVWByBzGT54JKak/aQaYwzs3rXE8vKu6Wt25oJbX++RfMdbI6OtObM0uzFjdXWVQ4cOMhrVCBZjTTutKE7JKYSIOMdkMmE8Hk8riZs+Yt2yuP2ZLumviIHJZLLa+PBma8w/FJFDr37lCy+92vYxiJlyfoYE4foYwy/PzQ+fY21WRlWMbOazuqXW1qVTd0KXeUavLNrJOcnGxhhhPNloiSPpu8qiT56XPPTQEb79rXtYOb6OYHGuICX6Q0s6j5y00s8plwVQFCWTScM3v/Ft7r7rXoJPjdXGJhublI8bAilSyzLX5a3o9XrkeZq+3eXhUIhBMO3gDlCsS5q2GCO93mDeGvv6GOPPq+ryI/4+ZrgkmEVclxHe8rb3LqrGv59l2U/1B3NXqVpUAyIpYrHGJuuXLbmuLirK85x+UbTuD63A1AhBAtBJCoTM2JaYPM5lhCYSYoO1MDc3ZHFpgX6/bK1lPCE8vONn6zHhnCFGGI1GHDu2wmhjTIqKLMFHbDtZKBFyGpmWZVkqHjQNXplOH6rrmtFoRNM0LVFbiEmQaq0B2a5R86nBkaoaf0U1/ksR+d3Xfu9LZmaE5xmzJusrHG9+6/usoq8H+WdLi4tPQCDqpsQhBp9ae2wa7KoxoEBsnUwHg8EWicRmX+K5NlJvHZiRZRm9Xo/+oGQ4V0wrg8YYBEE5WSqx1aBwNBoxGo1YObZB0yRFfNqm7pg53XGj2/7WvZ6YZNMzmVSMRhsp4jSGyGbEeaL3VwiBLMsIwVerK2t/5pz9e4h85nWvfulsyXgeMSOuKxhvftv7RFVviyH+L4tLiy91Nit98KgErHXJk73d1Uk8qimbo2mgalmWqZ+vXUrCphvE2b6jrSf91hFl1qZcUuPTgNmOuIw1GDHT56pq2wcZ6A61aSFBXNuPKNve71yx9fN0ua1p5OUDxrnpNpy4fN7a4lRNJut1Xf8XY+yvGWPue+2rZ24S5wuzquKVDNWlGMNPZLl7tjGmTMMs2gR5CKBJ/NSdlDFETOum0JHW1ujq4cxF3PqcrRVL77vewU05VHKgECJbCUhAMwTHVlFo2h7/qGygO9lEl9dzzlGWZfp9nKZqn+51u+dYa+n3+sOmaV6rMd4Z0f8CjB/2xsxwUTFLzu9wvPlt77OKvMoY8wOLi0u7YgwgijEWa1ITtGyrpLWVMxF6vd40Eb9VsPlwyaKLWDoSs9aSZRnOuS1iVOj86WMMeO/xPkkVkhLebJMrTNuSThDJPlxsrSR2soiiKOj1esBmlGjbvs2tEVjX4hQ0srS4cCOiPy4it7/9XR+YLRV2OGYR187H41T1ZxcXF27pEs+ootqe+Gazt68jJmctZVFMK21bl0knWiWfDZ2qvbtti9qkm33Y5ZK6iTzbX1e3bG/a5tSJ2D3ukS4nturXti4bU6TZw0elqqptkWX384lur02I5Fn+xLpp/rK19mvAsUe0UTNcFMwirh2MN7/1vYMYwg9nmX3iNI8kpk1RGwgKcVN42hFLlueUZdm+ymYv4IlJ6nMhri5iOTEyUlU0JoV61IASUGL779b7AqqBqB4lgkQgpt81PmLSArZFksYYXJvTSr8L/X5/W25v63Bb1dhKMQRrHDFA0evPIfIaH+IL3vbOD8wu6jsYM+LaoXjzW99rUJ4hyOvn5ucXfAhpaZNWgoiCiG1dETajqrIsKYtii5fWo4tqOjLcurzcerM2wxqXEu1YUINGAypoFFCDiMOazceh7ePOU952q16t2+bO9rksy1Zsq9sIuBOspsenSqlGGPQHV8WoP6QxXve2d/7xbMm4QzEjrh0KhfkQw3/f7/VujW3pfjwet2RhCLHNc9kkBQje46yl3+tNT9Tpa2nykj9R+nAuZNblsU7avunykfbWLf1MWym0GOMQ2bTF8T7ifUeCaYm7dRn6sPdRG2lujTZhM8+nbR6rK04Im9XH5KCRgaY+zc2G76x0zr5EVV+hUWfTgnYoZsS1A/Hm1NbzbGvdS/KyHBhjiO2AVlVNllqiNLEhRg+hwRkhdxZnuhxYsq1JkEfs5nBipNWhawHqXBhSDituu6mG6f3JzRSM0Xa70n0nLmEfyfadivREI0YDRiNl5igzB9uKAp0GDIwVkNgucT2DQf8qVV4vYq562ztmifqdiBlx7USo7kH1bywszu/fat63tSIGm+RhraVok/Gbrg1weiHnw9kUPe0NmEY850I8aXvNSQM5zpe317btPuF9syzDZeeetiry/DkhhL8GOjyvGzbDecGMuHYY/uCtf5RFjS8VI882xuZdC8+Jyu9plKJMe/a69pcUbV0YAfD2CEkeMemk55xcNHi0Edi299iyzVmWUeTFSZXV06EosgVBX6kxPult75jlunYaZsS106DsU40/sjA/dyDGgJGTk+vT6CsqSMpDuS0qcVXaZdz5Vy2fqjp5pqjppErkCcn+rR88OU/EbZqxR4y2sbrbJ6lfM9tC7qfeN5sEaijy/HEhxtfGGOce3cbMcL4xI64dhN9/87tNiOFl1rrvFGPScAvZLluYquNjJGrEWUdRFNOk8+YJ+cijoTPhRJI6ldRiy6NPS2bbH7s1yb/5PucDW7VextrpcvpM+yap+x1lrzenGr8b+I63vO19s6hrB2FGXDsIRmRO0B+dmxvu5hQNwt3v0xPRGLJ2ibj9Medvm04kphPJ6fRLuo6MumT8yU3Xmz+bk96jy4E98g1PItdtLhnItMrYdR2cjrxUI5rkJY+PMX6vosWj26AZzidmxLVD8Oa3/pFV1ZdlWf40Y2zOdDm4uWQ6UTya5zl5nm2LgrrcU/f4R5sr2rp0CyGc8tb9ffsScOsSkdZFdfN1vPfT525t3j6fULY3V4eYbG2KokiDcc+AGCNGDIPBcEGEVwo88c1ve+8s6tohmKmDdw4WFf3xfr+/NzUmp1P/xDmHiZYEm3IwWGOSvY1uyX1tWcadTCQnRz9olxLS6SOkfYq1W0lw+wCOzcgoVTdPFIKml9ST/j3dfVtzW6pK7LqEpB01Jp0Lq3Repls+03Yke53u6e1+UEVEyZwjzxzBN61Dqk7D1K05sRgjSqQoitvGk8kbrLF3Auun/vpmuJiY2drsALzl7e8T1fh8Qf6vubnFG7qm4K5dBsx0YjOqGGTqsSXSabZO9pzaXFZ2J/ym7qqD0BGQtIJWu8VfS3DZpndXd+ualbfKGk6HUx1fWwnrVNFcd59vNoWlXWN2+rkdu2bslGA7K53udc/U0pSU85HRaExVJVsesWbqJrFtedkS9dGjRz9mrf1pY8xXXv+al81sb84RF4pfZhHXDkCMsYgxvG44HOzriCCJI5M6XnWzpQdSNLE9r7VdEzUN11Ta0CluDnM1YJ0lb3VNRhTbkpF1jizL0s9tlBein772iVDV1v3h3HGq6mhXFd16X3qw2VxahkDT1PimXWKq4kMghJhucXvUeaaL5pZxZTRNQxexTY0JSdY7W7c5y7LHee9fLCJfA5qH9aFnOO+YEdcOgKruVdWXFkXZ61Zyxsg0R9NVCLtIJ3cZWeba4ant4rE9z1IE1t5QrNg0yNWmga6Zc2RZTpZnWGtwFrYutdJzPY3vtGAnt/t0jztfEfX29qStxBbSZ3CCdY6y3CQ3VaiqGu8DddPQNB7vA8GHlqRTVCatK+vW194kzBRheu/RGDG2swnaQlokopubGy4ePXL0e7Dm94DD5+WDz/CIMSOuS4w3veU9ElVf5rLsRmsdddWgXdMvbeQlm04IRoSyzFO/YjvlOaVvAp1Xl7WGPEvklrkskVWetQMmtsgpYiCEU2+XyGYUdyqCOnFZeurXOP3zzvTam3/ffNyJ7yMiFIWlLHNE+qgKTeOpqoa6qmmahtq3ZBZCytcZO/1ckPZTWRZsbASidpbWXQ9l3LIdihibG2eerKpPAf74tBs9w0XBjLguMURkoDH+4HAwWEpe6SRh6ZblS4cY4zRy6hqDVSMaFecsRZkiqbzIKXKHyzKs2TxRk3NpV2WjtV/eLiQ9eftOvd3nkrvYGuGc5rOfUsm++XvnNXbya8jUC6zLQ6XI0jlHv99Do9IET11VjCcVdVWlBu8YtyTiDVnmcM4SfdrvIpbgA9aliq5Ce3FQer3e/rW19Ve86a3v+ZMf/L5XnobyZ7gYmBHXJcTb3vF+E6M+Nc/s05zLtywHY/Kzah0goobp0qYbnqpEjIXMbfYpFkVO5lxqGm6x2VR8YsK+G07RRWyn01ltT1Sf+jGnx5kI7kQh6+le91TRnermMja9TlpST59joLCWIu8zGPTTkrKqGY8nTCZVkmCkR6Z9HiJtI0L7fjpdQCfiiljr+iLyQmvsXuCBs374GS4YZjquS4tCY/y+QX+4FINOk/DQnjyySRidLCLPM2IM9Ho9du3axe7du1henmdurkeeW5DW3K+9beJkklEVonoggKQxZ2LSDUJ7Y3N7tr7aw8hvna2R+vRLyu0uE9tvMW1/3FxSJiKGaeuQNsTYIBIpS8f8wpDl5UV27V5iYXGesiyQtmUqcVVH6CblvNoCRYpswYilV5a3ijEvevsffnB20b+EmO38SwlhEaMvdLkp66rGSYYR01YQwXb5lqgYVXJnGA5KiiJL1sxF3k7zCZyoZdos5U9VWdvfeprr2axUbl8Xnl7GsPkaj8wm51R6rxMf00WEZ3q/E6O1bfowoZ1enX5vGp8kHNbQ76d9GEKgrgNraxtUdYUPzbQSK7hUXSTNmFSS5KLXGywdP77yCqy8C1h72DtghvOCGXFdIrztne8XVX2CsfaGpplQ5AU+KFHTYNcYmnZuYiKw3ctLLO1aYDAoNydIa8rBnCqQObEX8PR/M6d52JkT6+eKC/mc0z1sU05x8mO7aqM1aQnonKPslfR6PR46dJjV1Q0Qwdm8nZidcopN04BNFxWN+lQs+5gR1yXDbKl4iRBCNI0PLyjL3lxmi0RY1gCRGDb92YfDkuuuv4qrrtnHwvzcNmV3J86c4eEjtn5lxiRH2fmFIddfdw3XXneAwaCg8WOMbZeiMRFZJ+R1md0fNT79LW//49n5c4kwi7guGaTQqC/MsrI0xlDXEyBSZI66rshzx/79+5mfn6NXlkAkhBrVmHIwF7Dj4UpAR/ypeCEYZ+n1M7JikYWFPseOrnLo0BF8E1DNUo7LGKJ6hsP+8rHjqy+3xrwNqC71Z7kSMSOuSwRr7R4RuVlE8I3HOYcGT12NWd61zP79e+m1ei3vK0KYeqK3sgY/bc+ZkdjDR9dGtOlZH+nGv/V7BcX+3QwHAx544BBra2NEDDFqOw4u5AJPFZEhM+K6JJiFupcA73jXBw3wXUWR7w4hkllDbFL167rrr+Omm66nLDNC8FsIavsg1U1PqdlS8ZFgq4asW26bthBSVxOEyOLCkJtuvI69exYRSd75nVtGlmVXO+euf9d7PjRryL0EmBHXJUCIwQXvX5pn5SCEwGi0Tq+f87jH3cL+/XsIoYGu0VqVuq4RsVibI5LU32ky8+yceaTo8oTdBCPvPTEq1jjyLKepGmJoKArDNdft48Ybr8ZltMv4HkXem9eoT9eos3PoEmC20y8BYghljOFJMQYEw+7dS9xww/UsLMzR+Jp4wsw/a7NWsxSnYtFNndZsmfhI0O3fLoK11iWhaYyEJjnLpibyCiOBXbvnuf76a1lYWGQyrjDG9Ou6fmaMMb+Un+NKxYy4LgWEqxG9OmrN8vKAG264mn4vJ/gaNOWyuhzMpuYptQEJW8eObbbEzPDIcLL0otVxGZOU9MYRo1BXDbuXF7jphv30S03CXdFbVLV/STb8CseMuC4y3vaO91przfPzzC3t3r2Lq68+QJY5On92OaE3z2xxKthibkrnCjEjrvOM1PODGOk0WxgxaFR80zAc9Lnxxuvo9Qq8b65SDVe97Z0zP/qLjRlxXWRYawqDfvfuXcvzV1+1j+Gg3GKQt10JPjNivHTYqo/rqrdN0xBCYHFxkVtvfRwLc4PlXq94ijXm1N4/M1wwzIjrIiNzNneZu/maaw4wGJTU9fi0vXwXwod9hnPDVv+zrk/UGIP3nslkQmaF229/0jDP3bPKMs8u9fZeaZjpuC4ydu/eNTBGFhbm54k+YJJGHjh7L94MFw9dxTGEQNM0WJvag6Z20+qZmxv0l5cWb7Bnm7wxw3nHbIdfZDhnn7UwP7+oqmiIrRJ+ttLYaegirc7muYu2IC0di8Ix2hiztLhwc1Sd9S1eZMyWihcRH/rwJ4oY4g/Pz8/tTqSlBH9mB9FZtHVp0A3sENn099861OPY0cNkzlCWxfXW2jd86ctf3fPOd/3R7Ap0kTCLuC4wnvu8V1tjbHHVgd3zx44ee+au5cXnmHYYQ13XlGVJE04/cOJUDqEzXHhsbaXqIq3k25UIbDgcto6pDFD9u/fdd//j77//gd//lTf+2l9UtR9trK2P1jdGzX/+P//XWWvDBcCMuM4zXvKS15vGx6yqqn6MYSn48OQq+O/+iy995fYsy/bt27fneu9rsqIEI/h4ZgfgGWldGpw4jai7r7u/qZWiyPG+YmlpYf/Rw0ff8Efvef/L82JwMMuLbxj0Q6Px+JPf8/IfuA9kvd/vVVmeN2/6vf88+0LPA2ZzFc8DXvz877ZBKRuv88YVN9V187y1tbVngV7b7w32DIbDpUE/7/2X//zvi6XlAeONESoWEZ3mUGa4vJDnOevr68zPzxNC5NDBo/zV/+FnWFjai6qu15PRsbW1tUNr62tH+v3B1+fm5z+e5/nnVXnAWjNy1tZvefN/fXiz3S5DzOYqngX3feAfi7MiSjQx1gYUa6wacdGI010v+NXzGrK/7IUvl6quc+AqH+IzRuPmFZO6fqLL/P7BYDj/hCc+YeicK4NXNjY2eMZ33s5gWFJVEzrRqLV25qd1maJpGmKMVFUyh1heXmDP3j2EaPBBh/3BwrA/nLt2dwjV6urqs0aj8evW19dXEfNgkeefKfLsgy9+yes+45vmiLUufuhD77ggB8LxT/xTQdWY6K1ozKwGAs6ryX00Ehef+8uXZQR4WUdcd77nHxpnYs+acHNm3AsK554V8VeFUC2oqrE2G1vJjhkxdwcNHw7ox4zLD+557j972AM9v/Npz5MY1ZRFXjjnbosxvnhjNH4+yI29wXBfXvTn5xaXBr1eb9oYDWmSzvr6Gj/6w9/PG77vVdT1Rpq8IxkQZzmsyxTdJO+qqiiKAo2WX/nVf8HXvn43g8GwdaZNw2q7qUpNkyYzVZPR8clo48j6+tpxlHt7/cHHReSPrHNft8ZVPvj4kQ+//RET2crHfilXzCKiT8KY1xLD04yvlyyhsBppNKvVlkej0Y8p/s1R5E4w413PfeN5n1x0oY7ty5K4/uIdv+hyF/day4ss/kctoydk+XDR5UXPZUVmMDlA1IiPvo4xeNds1BONR7J88B5V+/8YMX+B2I3d3/VL8c3/9ufk4LE18+VvHrcf+9wDZlJ5M6kjeRpdZRUd5Hn2ZOAFddU8pyjKA3Nz83uXlvctIJJjALN9rLwxBpelJeChhw7zd//Oz/DSlz6XEGqC9yDJDrgoilnUdRmic+6YTsXOCv7dv/9N/viDH6U/GBA1ab9Q0w7xMK0lTrLPIXZ2OqE+euzY+vr6+nFj7IbL8j8F/ZPRxsanVtfWjoA0qMSoMYrYUDeNv//ePz/lAXP/B//xcp7Ls23gb1rrbguqe6KRvrEmF+MIYtBokShkTDBxo2pCGAeTPQD2vUb190G+BLK6/Pw3npeDcrZUbPGFd/ziYmb0xU6an87s+DuWF67dHf21vWgMiAcqVGtEwJiMwg5yyHPJD/bzen7Ra/2TE7/6CuvyDwT0d+54769+48Of+NbSPQ+sLj54aP3AxsZkeVw1PcVGY4yrvb8phHBbH7N3cXFpz7U3HJg3xhTGWmitfCOeNN8vbaN1dtM7S7ePBhOTDti8KNoJNbNo63JEZzoIm+1BaemfnD0ym6Uhs4REWgRC+zfFgrFkWY73Pl/etXd59579yyEEjh9fue7YscMvW1lZPTKpJgczl9/b+PBQjPGwMfaBxoe7du+7/b6yl40KG0bPvnUpvPCZB7JnP3nvTT7Ef2J8/aJssGd3iKEgKs4YjMnAOASXBuM6j5oeUUyhoS6o/aI08TqJ8QeisZ9E+I9HP/bLn1h+3q+sX8p9fCZcNsT1qT/4RckzrnI2/lRm6x89MFi+Kc9vKDfiKpNsjFXBYLGUGBkAgkokSkB1A5/PM8jAjNxgUB64ZeSPXFMTXlo12Tc/+bn79nzujm/PH9vQbGUjOhDb+ApZH5ler9fbu2//cO/evfT7PfI8J2oAPKYdnpD6cjtHh05QahCSw2ZRFKysHiNG306mTiV15xwhNFdMEeOxhG6p2P5GXQdWVlew1uFchm9q0oQmWtvnTaNCWlNCHzzIpgtrjAGX2QXnioXeYO4mm+X1+tp60zS+DtFXTdPUo43ROO8v3G1t+em5of3I3uXyyOIwf1bU+kcXdw2f5qpdvVoV6xxOLJGIj56oFSrtdtQmLWXFkNkBUhhioYMY/IAQd0lTXQf6X4987Jf/YNfzfuXBS7eXT4/Lgrj+7E2/aDLL4zPLz+WFfcPcwq4DjetR1wJxkYEbEQzEdk6gthIDJaJtIjyrPbVaKCIVx5AiL13du+XGa+ave8l37c//9M7j2H6PPfMWUcE40iRjDTSN595772V+fo49e/fQ75cEH4gxDbWIUTBi28GhBlSS8YwyPXAPHTpMbC1rxEJTNxjjkl7oHDy1NoeMdcMymN5zdk+uUxGjnvCznOJ1trhPTIcXnvB6J0aMD4uDT3htOOWItFMNoz0r2o8kyLb9u31Y24mj2049yu1UsNbifYN1juA9eWZ56KGDWGdTfjMqWZ5hjBJDaMnLTT9imjYU030KGxtrHD++wrFjx/FesS6nLAZ5nhV5lueDEJQQE9EdZ/T48QPrz6p19Yev33fj+k1X969+8i1X7xpVkcZkqFFiFBo1iFqcFqmC3Z4RjTOYaBCF2CQiVROIRslN0xOj3zlRux/CVUc+9sv/UcXcu3uHJfEvC+LKMq7Pnf7CsOdePz83WDZYJChia9RAiBaImO4A7ZZmSHvst/P1UCRaLAM0KIWJZDLKX//KZ9CfW+CX/8NnOT5qWBwsUcd1RA3OREJMQ0JXVlap64YDBw4wHA7SYNJ2ovS2adCiW85vJbM9vvn1u2l8wNl0GlnjUn7EWsKWJusudzLtU2yJLbb2Kmnuok9RnZp0QG6xb042OJuvJxhE0o3W0wu2ziBMFXmRE5etaRksJK910n8YWjW/dESnbJ36LNIa82wdEdY+9CSC1kQq3Yg1aYlm+gTREzht6/zE9MLdz5tdBimaRcFgU1SsqXWns6xpLbcIPmKtad9Dp9uwjdpOQZpp36bJ4o2f0B/MsbHecNfd99Lvp8S8cUKM7b41m8/bun+NZAiW9fUNHnjwIOPxGGsNWZH2u2jEGA/BIDHDSk6UirxyXLt/38Iv/eyLFr77BSUxKBuhhNwjDdjOlFXSZ45bB/sCTrt9wbR3RjAYNUSEWBhykWvCaOWnIzqH8K+Ae9lB2PECos+89ReXnePv9PrZ983PDZalNc/b9Ft/5A4KMXYDEpTnfNfj+Om/9HQKG1mdHMMqGNOglDjJpk23dd3w4IMPMhqNMMZOW0POhKzIufPrX2e0McFlGT5ExAi9Xo+6rrc1U28OQ5XpJGVUW8/5NMXZWtOGcp5UmVRElLRy0W09dmIiSEPQCT5MCKEixgZIk6utdVP3T+cysiyf3pzLEBuwWcQVkJdCVgquFLICXC5kpcXmFpsJYhWVQJRAxKPtlRwTwSrGgc2kvRlMprgc8tJQ9i15z+IKwWYR4+J07mGe5+R5PnVr2CSp9Jm7CCaENLnaGMUYRUn7K8awbTBGCLElKUuMJGLGthFRIrKteSs4WZDafe/GWLwPPPTQYdbWVtttPPtx1z13NBrx4EMPUtfN5ucjYNQiaoEMxaG2AuOJjaHnA3/rZ5/OC164G8I8qhlBG4glWfboAqNoLFYsNgim2L07WvkRJfz8oY//0uKjeuHzjB0dcX3qzf+o74z+zX4pP7IwLJaMnLy5ibzOdYDoyY+LMaK+Ym5oed3LHs8d336AN//RXQTjyfKSJgiuNZYDCCGyvr7BsWPHyXK3zejvtFClqmu+/KWv8vwXPoc8A+8DVTWhKApq35x0gpxodWOtkMa/dp83tNFDitBox2cpKd9mjMFYizFt3q09EUXSsra7ZoUQ8T4th9Otpq7qqfdU5VNezje+9WVPPZaKEkKkqVPvnvfp+SFsPmbryZ9liRTzPMO5DGsNxoKRNNswEbXFZcnz3TlH5hxZ5sjzjDwvyDKHyyxFXuBcWqp125KCQG1JpyUqTCJx1XYZr1OnhzTsWlLiXHQ6QXzzONk+nKS7cHUTlrKsoGmatE0u50N/8mF6vQHe+y0R6ZmPw7qpOXbsOOvrG+0MgbYCSUQiQEqsByBogyOj2jjG3/hL38n3vvg6ctYIYUggvZ0zDt9EeBSCZsGh2oDYFPRauyc21Y+L6hcOf+x//J3dz/vVHSGa3bHE9ae//w8lM/qd/Tz+2O653t7MDVqP9VO3YzxcbO1F0xioV0bs7+X8xPc/mS9+ZZW7H1jDhYhRJViPUdkypMKysrJCr1ewtLzU5itOf6XLMku/P+A9f/QBnvXsZ1LkWep/E8H7kz9TF2l1UWWKtmi3NdGXayuXGMG0UYJYg0HI81SxHI3GTCYT6rpmNBox2hgxnoypq4a6DvgmcvDQg4xGY9bW1lhdXWdlZZWV46usra0zmUxY26ioJlV6/nhM09RbIkxpx3UxXR5K9510S8l2iYnqyf+2kWQiqYyiKOj1ewwHQ8peycJcmjA9Nz9kcWGBpeUFlpcXWVxapN/rkeWWPMspeyX9fp9+v0e/36MoC5y1OGfJXZ4KIu0FRkyq/Jl2GKxGxfsGjEVph/KKImKnkVf33Wz603dRVXrN1ZU1PvShj9DvD4lRkwzmDIFPt3RfOX6MlZUV8jxv708XYiMOjZFuN/o4xtiCjaM1z3r6Pn70+69jqBnjVSHvrVNPFJf1iDrCmKy9wD0ySIwglkCFVYsxcwTL/thM/geFbxz+2C/92e7nnR+pxKPBjiUua5hzzvz43qXspszM4YOeIg/TEdf2XMipsFXoeeLSTNSSMYdqxc275/mh193Gv/oPf47GgDWRxngIebu8SEu4yWTCxsaI+YX5Nk9y+g0I6imKkr/40lf59rfv5eabr20rigEwOLvpMd8taTZfT1DdNLNzWQYoxlrGozGTaoMYIcsy1tfWufuee1hbXWN9fcyRw8d48KHDHDlyjEOHDvPQQwc5evQYGxsjmjpNtckymSbcrXVkrkgnJm2BwaTlS1H06ffnEGNSBqqLdMzmdkKbx9LNfNHWC4tO/9fdIVOr6i66aerI0WoFkVXu8w2xHQfmQ00ITSsxaSM555ifm2Np1xK7lpfZtWuJvfv2sGfPHpaXl1hcnKPfLxkMBgwHA/r9HmWvJM8LyjItPfPC4YJDNbYXk5Qz1KDT77uLhkMI0zxkCL59fo/3vu+9HD50jIWlPWTOohJAzxxxhRDS99B4yrLcQpBCDA5jIkaEKBFsg69K5gvLX/mhZ7B7KDQbDUXex8fjODskkwGNPwwW0OKM58IZIYEuARaNYnFkMs9Ymmeo6E+i4UvA6iN/g/ODHUtcPoTnZs68ysl87qMhSrpKnghF21PszJHX1rH13e+bryFgMsbjVeaKIc992gJPvHWBL3/1OINBg9FuKIUQoxJjOmirqqauKnr93lneO7Zl8pLf/d0/4B/+w5+nKOx01JXqZvK0y3N0BCYiNI3H+8B4Mql9E/KoSuMDo9GYalK1xFLwqU9+hv/7//5/+PrXvwlqyLOcrOghxk1PQGtzFhZ7GGnfB7+NJFMyv432AJcx9V5XPEwJDUR1aoO4NeJKa4xu354A2fxH01QKOvoS0xY5SHk9ydJ+Tfq37kKT3i/GzZmSx45tcOjQcZqmJsTQZvoTcZZFztLSAlcd2M+zn/2dfMftTyLLEuElc8AkVyl7JYsL8xRlgYkRk5mpjc3WJeT0YidCnuccOXyUd7z9XZS9PkVREmJqBTKc2eGmriqqqm6rk2m/TnVhWKz1iBpUA8bkbKxv8JqXP5Gn3baAVI7IBGNAfUlue4SmAXFga3g0izkjoAETCxoTIEwwFEi+a0hz5JUgbz300f/xvXue/6vnXWX/cLDjiOtHv/+18toXXNtvPH9530J2tYiikqpjm9At/26tVZ2evGKM20hh298INLpBT4Z433DN7pzvefE+Pv3FFXKxFD6idrNilcSthrquaXygdxbStM7SNEq/N+STn/osf/zHf8JrX/dy8nbJuLXZWkSoqorxeDyJMVbG2JEqxxvvHzx46PDg0MHDT40qeVU39Hp9FhcWmB8OWVxcYmF+F2UxZHlpH3lWJG0Z3UmcqoRt2Iqqb/Nh2z2/UqW03cOi+GZTBjF9DZgSG7Gr2G5FtyxrX3saap0ouWijM7o8VZvrIpFXGt+WGEj15Mi6W651CXyR4WZuTcEYS4ieQ4dWWFvd4HnPez633Px47rrrW3zzW/ewcnwNJTIcDMiLjOuuu3Y8GA4QwYg2xfz8HPPz89PcVrd9MUbyPOXhfue//T4PPXSIhfl907+JlVMw9vb90/hAXddbBvvqpi7MKlECeEGsITY5Zb7Oy1++n0WnRC9ECXi1WFsS/RiNNS4rCdFtVtcfAUQBcXgRxAsYT5U1aMzI63zfmPhDYD4JHHnEb3IesCOI6xlPe4ExRrOicHMb4+Zph45uPP3WG5a+29g+ampESjRa4BQk35bUzyXT1V0pT8yLpRNasU6omwn9XsnTb9vP1bvuYWXSkOftadedvKQrf6oObVa42lc76X1jiEkbZoThYI7f+90/YN/e3dx00w2rqlpbayvVGEQkxhirEMLxum6OxBgPWhvus859zhjzp3meP3Vx1/J/BbtbsCAyjUI7QvYhghiMTV+tIbaygu3Jfmk1Ad3yZOs+6l4vcUa2SfTaSa60JZKTP690/xcgsiXf0kVjWy9AW/4q0HUidNKS7aPYtkjJ2oS+TiNhtn+2KcGkC95gMMRawWU5g8GAa6+9luuuu5H1jQlVXRGC59Dhg8eOr6x9eX20MbJGsjwzedM0i6PRaNE5l1trrXPO9vt9l2WZRbLig3/yUf7w3e9jaXkveV4wriaJrKef8cRjIv1uTCTGGt+AzTKMDUmY2pIzxrYRbkVwjvGq8PTHL3HLNRnOCVWoQByqeZI6SENWWBSoa8G4R1FZjIJag0rEoqRWoYjVCmPyIsb6ZRr1Rq5U4nraU54nde3zGLWoquYmJTy58v7po3HzzMOH+7tuun5hv9fUiKwxgDo6fdYmutPk7LTVXc1OlYuStJKnljFqIDYF1ywNeP7tS7zzI/fgewaX+nOwIqg6VG0q+9sKlT4tC2wJLjb1PxoVjZHRZFxvbGw0qxqat7z5nYdf/ZpXfvLAgT3fGAyG9zpnqxjjGPSgiNyT59lBVR2JSDTGcv1N1+nH/+wzgrLhY9jtrIEY0WmOr0EkldgRIRKZViC3kMy2PXWK43sbqauw6Yh/4uPO8CJTPd2paPxUsdnm3zbJCU6n1lHdGnmd7iRVjCRq7KKlNhqrfVPnIgHfTPBNAyIUeX5PURQ/3iuzQ/1+IRD71pjbmsa/0Ht/g3NZCUhdNxKi9u+656Fb3/2e9+8ZzM0X6+sj03j6gpLn2ebH3yJvoUtV0K5k1UyjyKghVadjRFQhOhwlxo4YE5hUNa94/k3s6w2pfIVqhsVCl2IQh48AgezRntGSEvQWwKaiUObbinpuKPxoz/E1Xvix//b37r/7wfXVxuv4r/zCb1z0ZeNFJ66nP/V5UjdNf2M02e99eGLThFsReW7TVE+oQ9i3f9+ewTXX7Sn6ZcFkHNHYCQYv3L4RFJUIoYczY4SKxaXALU/oM3lfoCSHmDr9rXWoCEpANOXdBDtNGKNpynSInqZpJhsbGyMf6rUiLx8wYu82xtyZuWwtKvd/+9v3fKAs8oNFUcYQfEeuetNNN57ybAwhjkWkni6xToiWZnj4mFYMo6r38cgLnv+srj9v7XOf+/ODqvpxa21mjBhVdTEGE6q46Jv6FU94wq03FUXhNjYmc6sra7esrK4cOHLk+DDGWAzn5vuZy3rWJaEwrfQiBfcZlprMRWIct8l/BZNSGl5HlM5B3UO9p5Ca2269ml5eMhm7lEA/6SJ+cVDmZbk22fjrX/32sVseOjL6ovfxo7/4Mz94tw+y9mv/v9+/aNXGi0pcT7n9OWY8qQ6Mx9VLGh9fVDfhGd6HA4Ph3OLC4nzZeOHaq+Z53I1zSXSJIuLaLNa567UeNkRBAhpyxFliE8iLggP75xFvqScp6yOSEzGIVhg8pckpm3moLd5XeF9X6+sbVYhh1Tn3oDHmyyLy58bYu0TMt4y191trV0U0rq+Pwl/7az/+sNKoqgRjjCe0eSjVGW89SnTUL4KNMZRsqZg97WlPVaBpb1tx9It/8ZX/vHfP7mxtdd0OBlUvhnhN09TXVuN6rw/+qqZunjoZj29pmmbZGFvOz80X1rq+MTYJfwtPnhdMqhEikRAcSI4SiVKxUU9wIadpGnYtW/oLMG5GqPQwsn0JfTFhJKO3WN76gU98c9fBo+NXLC/2X2qtfa8Y94Gf+MFX3f3/f9O7H7Zl1CPBRSOu25/07Gw8rm/eGI1/tG78D4lkVy8u7p6zmcUawYrFN8r8wHHNvl4bMgcCihWDSDzNouX8wCBEqYghSQHwQ3bNDXnS4+eospqAY31VOX5shDVCngsmhyMrD66FY/V6RB8oe72vizFftNbeaa39lrHmm2JkXevgRYjvf+/vPdqjTbsCgen2xqxB+5HhhN0mQgaSn+vTb/+OJ9RA3f66+nf/3i8fnEwmn1/PN6yNJg8h7hGRm5x1V4kx+0aj0Xc0TfOEpqn3GmuGveGgH23seTxochoJtadpJthSWZzrM1eWNMFxw7VzzPcdJjMEnZxRanGhEYPhqqsXGTdzu752z/qu8O1DB5YX3FPn5/pPLHPzW69/+Yu/8Lb3/skFJ6+LQly3P/FZtqrq71hZHf3tEPUVu3Yf2O9cm4vRABIIUfFRKEvL7rkSbRSituvsVlV0oU5SNWgA48YQ+2kZ6CM3Xzvgb//MUwg25bfW1gN33TPmzz55L5//0v309u26p4r125qq+kR/0P+Cde5eY+yGCME6p+99z++c18ti1LgpWKUTaMzwMFBzKoVGqs6UCmfWtZwB//rXf0VJQoTkrQRr3/09P/Bt56wRY60gi1H1QIjh6hj1upXjG8+Ial/aBL1RUPxGzVV75njGM3fxuJsX2b97nl4OhJy5smB/3yCTSCGKT2uRR7qpjwoiFuOE4fwceb5Cls31JtHfcuz+lb+80I8Hhv3if/ru5z/3S+//6Mcv6LLxohDXpKqvWVvb+DshmFfv2bN/l7W2rVhpW/0RjKa8UZE5BlmP6E3Sqbi2g11Pmek9P1DB4FDWaboktx+x1Pe88DsXQUtcNOA8GzU8/2kHePsH7l77xt3H/6+jx0f/dix6zHvv/+SDb7mg8btGlRijMbMo69GiBlAl77L8RqQQWHjHuz4or331S87L9/j+9/1BVyHxwEMve9kPHrTGfkHVW2vlTQX2e2Sw+CsP3nPklmc/+Xp+8PU38/xnz7Nnfg5nBGGM1gOsChpXCHVO6qI+H1v3yJAsnQp6A4d1Bo2QFRni9uw5euiel0/G1bHGD/+nl3zXd933wU984oKdDxecuG66/snF+trotdWkfslVV9+wy1iA1M+VXBcc1hStfqTGaMRoNtUfRYlJWKfJAeKCQECiENSgVjFaQABTCdIoURUvY6zAQOA7HzfH4256pvvgx7528Phafehn/tG/vSgJh5Cac8W5pM+Y+Xg9CnTykM17HOiidqW+C4A//uM3dRK5CBx64Yte94fzg+zJC72ln3n9666ae81372ZY5/h1SyUjxNZYX9DECVIEYla0DqqXKt5KppkGiysiKh5rctAGMZaFpX1Lxw7f+73GTL7pffw/gPGF2o4L6g5x6y3PkLqJ162sjd6wvHfvPussGIPSlYI7v49IJKKiRJPUWoKiRgg+Dak39uEdS0oSrna3pLzW9j03b6nSo6g0iBapK5/0Z5Uc1QwkEkWJaog4fDOmqB7qHZh3tz/uqv553GPngE5A25X3t1UXu3NOuFgLSZXN2+YmStvALJ347YJvx0nbxSnYZ4usSqebNtX6G5CFc9LWnCdcvX+48vgbF3/z9a+47cHvecoyJY6qCQQqkAhqUROSRCdmbfOzEKUArxRADEoVLcEkuZDGJnU/BE+hkUwsKjkTsVROqKVBCTQakwzIKNFO8KYmWE29r7H1aSJADJgIzgDGg8lwYUzdWJrUxk5sG9idzRku7r3u0NGN16ytN9deyH13QYmrrmPRNPGFRTF4Sl6Ueex0OpLIS6RTXSeLWwWqWhk1DVifrG6jTcSiZ5ZDnOpoi22aLFXgwrQ6uVUDpJqkECqR5EkkSdogERUSYbUHUVRLwIBARmT/gr3mhj3uogXuIqKSGm3qzXu3n57bBJ0XEVur890l6eLR52lwYgK+/beVqerW+yQZVQxl6p514fHffve39buevO/rT7117/HdhaWZQLQRtT4VpDT1UIpYpFXEG1WijKmcp7YeEyb0VTEhI5AjmdBYIeaOsfq2WXpMGTyDaCg0R8RRmozGKFV05E2fLFhitKha1NRgQW0OpkdECL5BvMU3gERGI08IklwsjE3ndIxkRYGS31bVvPyGa554wfblBf2S1tbWskOHjrxkfnF56M6ijDMCToTRhnJ0fQK2ggjWmNan/eEd/km/3Hl3naiU1/aWCOpsS1BB28PZpOnFgNgMl1Umyy6i9i6pW6cUsROWiqKbpNU2IEBso8CoiHa37TxyaYr529BtTt06ygoivYsZcQH8+M//q1DYZs37CeqrlujNyWFse9EVARsMJmYELaDIkXxM1ENYrYi1o2iUTHrUMScaR+YCg7wmY4zRCkKDp8LFGhOUEDKypmTQKCZWNCJ4BR89KhZjczCKlZzcWYI3rK2uIBpT8NH6EiKpAX1hac/c4cNHX7G2tnHBLuoXlLiOr66ayMrNZVkWZ3NvACWzhvUNuPfQBjiPtS4RjDKNzk777JNcI5JbqG1vqJkKATfbdVKj7tm3DWjdTokg0eCjp9/Th4pCL5roruvT2RrFXOTz7CR0xDW9neLvnOL+HVgOFVR7lyJ9VDD+Kq03mYaIBs54TPazHBMijY/EbEATM4xX+r2C3BnM3CouO8T8sMar5f7RHPdPFtjwOYWL9AbrFNmILPPYYoLvbTAeesZZTYWizGO1hwtjTFhFNIAx1KbG5MrBI5NkimkErENDJEbfOgFHyrLsTXx9W+MvXA79gibnRUVAes45kobv9MeERnACx46N+dp96zz3O5dgQ9CYJCFnPZpOjD60FY12Vy3j2Mz/bHkI2p78ZzhSuuUmDZYcgjCOTaViPqMXMXhoe/imq7Kun/BSYSuBKqeQF+2AsOrMEIBWuyUCPAo/mEeOGOP7D67yI3t22WWj0lotn9j83vVhwsZoHckE44Tajxk4yHtD1m3Opw4e5l13HeCOwxaWn8y4LtGoZA68MTgnDIvjPG24wXOHB3nKwiGWhhVVaGgwlDYQmg0yHSJZSaNjJtEmNxGtUTF8+RvHaHyylo5isBi892ju0ntlGYItjTUXLOK6oMQV1AP+HKM6wVnD0dUN7rhnDa8D0PW22TObGqudE7bYrWze1+5DCdOzLVm4pHaLM/lppYcbMIqJaTae1vFuQ/bBq17wzy/m6dl1eF9Qdci54pRkteW+M10OLlHHSs6maJRN44rWQRUKuQTrbxX3sVHDZysfX9hzWS54tF0MnWjnrURszxEpGKhHzITVyYBf/7jhfd8YsF7t50E2ODSCm+b3JtIBqDxt7h1/vOTzh4Q39a5hVzbh+fOH+OFrv85t80cRqQhs4McZ3g6pc8FHKGJBD4srF/j0l77KseMV1ghNjDiJZFmG93HqZisi6lx2wfbZhY24iCjqYwjYs3Bv+rCOOk644+7jfOOuDW7aZZAQzmk51A3cTCfLZj5A2tf2VK2ItT1j1LTWKSmfYM5kd9sdNGIQmQBMHPJWFXvnue2J84W2R/FSM9Yp0G3TtBXpBKQU0iUjrBOxjcCA7pJQnFve4PwiYo54z28fXxvfVixyTeeQKltcPTpPsjQlqqYsPQ+u5PyHT83x/rsWuPdIRc2YSelYqCt2z80xNCNEwVpHDAFjLEJOlvWZyDpHxgOOVfN8a7TE+w5dy2v2fZPvv/4urh6OcVKhDUiwWJOWr2IsG2s1X7pzlWNrnoX5nKadVpTbjNqHZMIYA1G1iSFesATwBc1xKU0EPerD2be/O+Bdafn2/Uf57OfvxhSOqBBUpxbB54KuzO2sIcsMWS70B5bB0DEY5gyGOf1hRtnLKApHlp1D/kxBNSNQ4ZvVrxkj/23fC984OeeNOh9o89yt296OwFQOAVMTGgWipFswmz+fLkK7REhLxE05iUA7FeUi47k/9D+HoLyz8fV71kbr69OpVLpp2S0GrDU45ziysYtffXfO6353mf/whQPceTzDW4fLe+Q+4mNksHwta2GZSndhZA5EqB2s5YGxHZMFWDYT5kyDi8KRKuM3772df/7nz+fzD+2iKieU5hjDyZiBbydZzQuf/sIXuef+dfJyDlCwaflZVRXOZoiYztn3aFC5YMR1gQWoeQP6iYMP3feMA1dfu5jlEHzASOqUj0Fx08EFAJbC9Tl6aJ0//8r9vOYFV9PLIHiwtWBNhVqlUSFE1w4XUIJ6JArW5lgjOBkhpmHCgC+s7uVLxxb55Ffu5bOTBdbWPZkx7O5HXrT3KD/1FMPVQ08IhgbFqBDEM4oF1lh6dtJ65QXM5CGiyJ2Y7FcF88ULu+9ORmxN8qxzdDbWrWNK6kBo2WzqvdXm9M54JsaCiQs4qRgo2JAxUUftPM5WqFe8seSNpfAOMYI3kYnWjFAmtiQaJUjE4OlFZR7DEEM/QiCyKpEVgXURGrEIDoOl0DUWA/QpCGKJCqn3KoIEahGi5rjowDW4uIFpGmrbx5g5VB+RvjGHVieom+aAmoRd2aVbgMvhxsu/GVXuGiP1C3o9N1A8Qo++7XM0TJgfBN721Zx//THhW2uL1CZHTI0zPuWgAKceHw39QZ8JaYJVjAGjisMQg6TxZMbgRYDYarSSDOgjG3vZ+PZ38A+KNZ7Zr2hMljRiuSVS8Qd/8iD3PrTBoNcHtRStJ11u8jR8tlHW1o6vzy/OfW7Q71+wwtUFJS7nTBVC+daVlfteffU11zxNY1qeiVhCCFiTvHo1Row1BEZEEXIJfP7zx/nEF47w8hfuJxwTJplSmwF4R6ER4xoaiViBHgY7GNEEzzdXdvGBB2/gDw9exR1re6jigDmxHP76H+HsBsHsgXqDB4t1PvPg7Xzoa/fyW9/3EEs9YWyXME1NIZE8o3UJddR1hWo1EjWfE+TfKubtu577xos+7WRzVuL5QxEq+hKYOOGoFlgRFmRM5iNrwWGzgsHE403kYL9h1dQ0UZkj50YVnj05yvVa8PiYc11wzEWPoUatpzZp3HweHRIt6yj3SsU37Br3mIrP2r18vhC+Zcb0AywGwbWVECPCsBKCG7PhGtzEEO0C1bBHXk+gXkXPRw6l3ZnWGkFxXCLmeub3/0/62bf+j1+u6/DGSfR/X2N42fzeqxYCBn/8IXZpRJznNz99PV8+5rCZJcsE0UA3PxPA2FRjMGaL8aYmcbcomC5IEE4ooApWIoWv+eThvXxw6TqeesNXqPqK+IqeVT78ZxVf/kZD2ctQrYgacZnBB48xGSKJ/w4efPDI8q7d/005i/jyUeCCElfTHI7O7f1GjOZP7r337puvueaG+SLPCSHZFIcYMc6BRrxvMLlijTBwBfc8UPGW932LW28ecsO+IVXtsNEzcBtkMbT2tMKaL7ijXuC3vvRMPnVkD9/cmGdDMwZuROlGUMDxYpHBwjKrqxtgj+HKPk09D/2KT64Iv/ixA/yf33cUrWtimECsKFSJwaAmw4RwOMb4Iaz5N4p8cvm5b6wu5H47A3TL7bygKdfw1ZCRlgykxpqKipwy9ihzZdKsMbElKzZHvPKCOuPlEZ7rK66pK3AuRVymxhPTsjCC8Y5CPMqEiCEaQx/LE9Ty5KaH1T6qaxyZ5HzODnibM3wkjxAtuyI0VKwMoaiFgXf43BKjp7eyRrTQFCXmHFIQ5woBUSG7lDm4p3/fr8bPv/2X/rSO+nOx9n893P+NvzzX719TZ5S5maMJwqHxEOMCRZZ0iCFGiAE1mmYjqOKKOWi9v7rpebGT0ZylgG5sQCl438HreNb8Qzx37yFiYdiYCG961x18+97j5KVMh73UTUizDJylbhrGk41xluefEpEvPXD/Fy7fXsWiKFeKovj91ZUHn97sPfDsEGxZ5AUuc2lOnyrGpcGcjbcQM4wKtq98/HP38N6P7Ocnfqhk0HhcI5jeCCkjX1nfw5vuuZl3338td68PkcygeExeMZc1GKCqc5gIC01DtbgHP7qHxbiKLzPGDnavfpNetshnDuUcPWxYyo8wNsuMsh5FM0Hq1VUfN+5Hyg+Iyf99VHPHruf900s9V+40B4OeLP4ROWsy3E9KfA92NYGREUbGUsSGgz2hP+6xLn2y3ogfnqzx4xPLNSZi6phsfnoFwU/PDJC28uuSFb3RHGIqlkSldWVVxhJTa4nrsziueEl9mJdkhuNjx1sK+J2eYbXKaeIiu7SiHzfwVIyyDJM5slDjqgp1j67danoSdxWc1N91SbNwT3ndGxV44Cvv/ke/pjH71GR19cfLXvlUyZurDj2QuYlOes60FdugECOmjaQgReV5fx7FtYLgzZkNqVXszAeEOkMWR3xlbZG/2LiO57OCK0v+99/7Ch//0kP0exYx4L2nKHJAqOs0hHc0GnP3XXf9+Z69+/63LHNHL+R+uuDEtbFxty4s3PSFwXDPf/ja175sb77l8c9wzvYmG02yuUVpmgDWYqUgI8dnGyBKM8n5/Xd9k+uum+e1L9zFvYeEt975FH7rnpv5yniZLFfm3QTnanJTompotCFGxRpLmVtsCBBW0d1PwH/rU5CVVBs1w4FjYpdp6h524zD/+9cCv/oMqdYPrTbRbKzEMLpfTfFmtfmbEffA8vPfuHah99WjwSPO1y/Mk6+vslGP6dsFSjdgRVbJ6jXor/JPRoGXP9RjyeYcc2PW6gBZThAhm9TpPLcGTPKoTaO9IjQBCSmXiTVg20EbqohGCClaODTnmAvC3KhiyXr+Zujxl1bg08U6/ywPPKjQVIZ5HbA8iYziBJyy6HocO4/7z4gQVfNLrjFp8YRX/YuNr/zhP3i3keITdSXPeXC88oOfPjT3nJHaJyB+mtd0YtrC1aZsIh8sEsQgMaTBI1MR/tnTDC5CbjJWtWTkFTfM+f13f5u3vufbHF1v6NtIkedY49JsTu9xzrGxscF9997/pV279/yGtfbT9979uQt6gb8otjYrK9/cWFq65Y/M3GL2ja9/6ccf9/gnPn/Qn++DMBqN6Pd71CGgOsLICB8jxgmijq9++xi/+m8+svGPP/2Tgzuufh0FY5azMfvnJoxjjybMsUBN5WusFXq2BK+oV6JYJk7xFubCOmXZww8L4sQxXlkhn+9TYWFtyGfvrjb+cE/+x9eY8eeW++5NmS3uE+M2ll/4L+uzf8KLirMJzrY0D5+dyuzGYZz2qebnqJvAPbpK7SI/v17yP1SePSHjvkHkkHH0dZmIp57UiIN6YJL1cBCsNymJLhnqMmLmUhigHhMC0ng0RqJRvEu9dFYbhmsVEcdKMYdEYaWqUAsvDHP82dEx/zUqv7gr41A0XGuEqrAM1wzfcBnL/vz41WnHApDthDaqDk/43n8VgaN3vOcf/dHX13of+9a4/x+9jJ+QFn02DQvpppe3Y/NUI7acI2o7KynZrqBiiWhrQHn6z1jHMVkqCcBgiQ9+4j7+j9+9m4cOjlhaFKyZQxrFh0CeWYwRvK/ru+769tf27t3/W9aaP7z7rs9c8FTKRXNAPXbs68eXlm75nfn53V/52h13/gTYVw6G8/tuuunGsm58HkMkd4KXpvYTDYfuPzRZW5usD+bnV+2eZ31l//zy96yblYWyiPioaG1ZltQMelwjS34CIWOsGU20ZHhyq1gXqbJIHTZY2rPAoW/eR29xkWaP0IwM0gSq3hp1WG7e8/X+v3r1DUc++ZRX/ZOLYj97/vDIUwkTO0+Z1/TWjvDlgeFlccC/OdJnb6/mWOM52hTkQbBNwOuYcV9g3iLVhDAOhKKHdYqzitEGoxPEe9R7GmNpTI4VwRQGi7TLGsVGMLFAM0uliq89AzWURYmnYWVjjY1+5KeyJX7ioOO/33ecPzCRxx+eo5rP2NPU53UKQZuwzi6hY8xp8fhX/ovwj/+3X1/95lp/+ci60HPdhCNNesROcC2AtZAPCH6CiXEzES8p+WjagR2ng8sc+ICwwe/+4eeP/PZH/jQ/9GCYW9w1hw8rxDpgTYmKcvTYsfH9Dz54yBr57Pz8/N8XI3fffddnHlvWzQDHjn3dLy8/7rPz80vfrpvw9o31tZd/8QsffhKUA7AOxIOMwTw4N7f48bn5ufeVg8EDZs91V0dbP2k96oJpBpQxgo6obCSgOCIrdg4kYGUNl6X8yiRkxEbQJpJrAfkNmIHHNkpe1xhZpckK6mbIHcd8cWQsV3/x4O5LOujyHKDbf9k6cuzUDz7TmTjXFNxTBwbDjH9ZO/7SqMawwvqaMOoVNOWIfgOhp6gx9IKgG0mrmWOQkScgeCMEU6ImRQEYoafKMCbfDx/b5Yom00gDEBu8rREn5LlSxQZfT8jUIoM+Y4V711YpS+W/HBvygy7yv/RWuHdcELM5cj0fF/bNPSTKoxnsdUFx/3GxG6PqwED34Fxo05mbw2kxBmMEYy1iTOru0G7SuGV7c9YZjoi6zxhwfc94+Ynvaq55BpP1zz7prnvuKKqxSxLiZqNB2OgvzH9xadeed0j0Hzv44Jcu6nTriz7l5+jRrylwZHHx1j+JIXwqxvnCZXO5c85pxMeok8xJVfZ69cGDX6w4Ajfu/hurR8Yu5mUgkw2iDURN4a9gsBpx+LbeVrbz/MAQSLpVS51P6NfXgf8C5J7aRtQvgh5jaAYca2wvi+FlFPI2kvXujoNGlRiUNFUotX90SxvpSkYi07mEijLB0TcZwgYVNZkfUqijNhUTzXlAV3jO0PDGo5YnVmMeyhsMJaO+Q2KkCI7QXtwJ4DGIS+85RttOqu50TzLU7reAEFJf+NQ5QkmJ+0iqeFkE69uIAAs2/S0GT1NkDJo+dVPxzfI4L6ktT656/Pq84bfcKvvGBYPMMFbHQuOp84p1MfSjoyZF3adD6oRoZ6B3a2sjdoco+09CsLoYMnZHswqxRzSKkQJ0HZocZzOOV/dxzf5XQrWOFVAjdFPBDQpiztoY4G1FE5TFEA82xfA37VVP+Ex56KG5zFhXNrZQEFMdr1Cd9MpyZGIzuu+uzz/2x5N1OH78Tg+sAHi/fbbkGFjdkgqv3Hwmxp3zIINTIU1R95T9RULzIEZKoolAqzkMkTr4244Ek7NDieuRSIzmIxhdpXEW/DzGCetxHa+Gjd6Yn2gyfv7rQtwXuavnmFuxHCsMAx9YKxsG1XatlAIa4/S3s53pW+cvToXGXQVsmjQ+GQboTyqiONCM/kR4cC6yUBn+0UHHE+cc/+vChAfUsDcGGjKoC+b7DROvzEvN+OFba9mdt1CE7/+lX5eDI7nhUOXmoiuSlx0BjRFF0vJPI0YyXJ49Kp9gkVa2rHqEYnjnN37jb64BpyxMHX8U7/NocdFM0x4VxBSIFJtuCA/fzEVViaZhbm4vdWwQzRCp0ZgcTjMrqIb9UcPcBfgE5wc6Nao4Z4zsiCrL8D7DaoOlATtPHQP/ZFX5xcMNdndF5idkI+GYy8h8hgrsGsdT2NZETHuyYARvOMNNaUykMRFvtL3F6S21icp0dO2JJ9w4a8hDxKnjeJFjx4KGEfn8iB9rPP98xTKMGZOo1D3PuoNYFwz6NavN2YtaHam2S21VVXOmZvtLhWPj4B5ca556/7jIsGUaHqMQ1aPRIEYI0ePcHC5/VNd3rEAMgRD1iIhsnKePcN5xySKuh4XB4sAWw347jSXdN/1ZzyqqA5AIURrKwS78YdNaHnugQKmxYhmJLDnvnwDcfyE/zqPCwzyvgrOIz3GiiJ0w8TnKhF/TRb5n/RjGKau2Ia+UQI46S3+iBAO1cSdfvc3mzk5K7HO/hLRjUae/J3fa03+gMlhWS6EI0I+CCcpEG8Z5ZK4Hr13vsYcJf78/z4p6clMRxTKa9JDSn9Ef8sR3FVCNai+FO8TZsFaRrVc8txItzDZeSs68YiBET5YvIi6D+MgL4aKKBkULd1hUd2yR6rKIuExWDsW6vLP42HrYnfDraSEKUQKSDzBuQAw+OTi23O1EsC4brk+qF+z6qf/lstgv54Jh1afvI0YCI+mjEnjjxPNd/hAhy3io6DG3UYI6xCrD6Jk4TyWwZty0QXp6a33uO697F8w532w0mGinN7pc3JbbVuRNjkYYuYb50JBZQ3AlSxsl45BxqJxwG/DvN8ZcXzkaoI6RLArF2UYkdsK3jqbScmtHRlwiWWZtcZsTg1VP1zwhBJxk6fKtgaJYwrds/Ug/R4yRqFqFGI8Ezmvh9rzisjhBNSt7puhv29aHe2EUEYJCEOj39+J1DNGkAEI0+RTVsQf2yY1eOAO0iw0lUGnFhqaK4BvHlufkIyr1rFglq4VRlhFswbA2uMZT2UAQWJ5YMJLyUN0qndhOYkqLu9rqud9MZGJCutl0TjhNNwvYLd9pBMaZpfTQqxs2bNM64WYcK3KsN2yYmg0MN5oJ/7w6yrIZsG57RK1Qfw4N2DL9H6Sloj1TBHip4HG7cMU1WMGoZypokICII2oa5lKUy4T46LY/phmmsfHhmPd+5+2MFjueuJ75658Wr1o2ITyqKN6gqApBPYPhXpQJQmoMjSKIKFYKirJ/S5YVOzfP9TAXi5V4Rrmj1Am/PDK8QBviSJFiEfENkQ1s9HgjTMRCdDiEAhBJHsJGwWrKK+RqyLEUGHoIA4mnvQ2JzIkyFGUgkZ4ofYn0TKSUSA5kbNprd3n+SGcl1OAzKILFq2VNLERDNA21qcjrHgOU41G5WuCfj2quRdmwAf8wCUiSYYTZgbzFqPZPrIMuh7Z2YMSkJbYkqUMMATFKr7eYNI6PJmo0loiJTdAjIe7EvZGw43NcKgYVkazoiXpt+++SuVO6YCY7l6nfeSqJ0Bbgp3veInhVVDx5sZReB0ACGtMSyGQFeL8/BH8L8MlL8HHPhk3dQZdq2rJWNioYtRhM26MmaHD0Ms9fD44X1CtEDD4b0js+ZtzLKesNRoWQa4Yay8RYRD2VUxpRspg0V5mkqCiIMEZYF6WStuhBWlOEbuva6MyJkG3xQTUkAkxEmCRBuUCpkGunuIh4bZuCtSEPjnEhaHRtGVLZMwnUNjAyJVEn9NRQx5Lb64ZfiKv8y/k+D8Q+Q+/BBCSAjWxWGDhZhJlkUbrjElw3//Vfl2Njf7vP+gOyLr+bPoNRC3iiBqwpcFlBDBWmy/0Cm4dK99y2rVSTrXmS1gmdCVIUQ57Zhsj9rex+R2LHE9en/+7T9fZf+6x4jTLVKrUJYdP2FUfSzk+jFyNJvSJtxcoQVci8BbuBugKNQ4xdTEZfGjDqCBKoraFomFur9TmDn/x3n9n4rz+7s9b47YknJp3l0qqhkYgYj1FBtIeJFqGhpsQ4zw9PIq+pI+JgQ0ClYVxA5mFicvIGQlbTOMEFA1iyqPSiYqMwssphAysiPIThGyLcaSN3W7hfHBUwEqhUCK0kyiH0NdLTkEgMpUSYU8O8Wvpq2B9r9qtydTTsVRgQGKiyoMqCJhL1kuyybYw0IlRWUOMYhIyBVKw4pecdQRrWJfDiZo5jG/Cvi3XGDBDj6aul8BBpEMO08UVb4jfGEmKUdsjmzoJYa4TbaymYswHVkMbltfGqlxWwUOQHwKQ+UTUWNCIa2nMhQUlLwSBJUGFaESsARogCPgSGLo595B65YBOYHz12PHEBeM1kTiwDicQYulECraIxmSglh83WFK51StDWvtmYNGU2nfAOEU+/v0y1+iBW8/YxFnwkhFAGH55qbJZzASfxPhJo5xq47c4SVUfQEQ2eiW0YuwnqLA2BN9RjXlFlOM1YU0cWlJAFahOxsSGL0GQ5eSVImKDOsyhCjeWYCdydV3zZGv7MWr4sjmNqk8TcKLlG+iFgxFCISSdMe0KItpVckixiLMq6KIdMxBAApdaMQErSO1WWgVsjfKeH27xwlc+YB3oWYtZQRI9T8OpYsw7bOATBasRkgSpaYsh4WQ0rZsxv5husxx6Vi2ADNY6gUudKrgon2temIGQHebQC3pS7bMFTrclQaqLJEGIaAUc6tusQyMs+sRsH1+lmtujl0ERMqOAiqRhh2qpumwoojMEbELHrKvEh3YkJvxY7nrie9GtflBCCSAxkRvBiCKeyQmiXRonLZFqxSglMRbGta6hBpaI/2Mf6yn2Ibnp751lBqIQidzcba+fYYcR1KgQrRBWM5jgRXBwTM0MTDM8Oyl+qhOsFNiJoqPE2EMWhOIqQXDmIkdopC+qoJXCH83whCh/r9fio9gnGY6Sh1MAuUZw6YizxoqidYFriEQ2bqzFVgjGotWSaRCfTEZYtAUeTYVAybfA0jKzyiczyoSxHo+PJoebpVeD5Hq5RYReWvldqCazmgZ7kQEkVK3LfYNWzkk2YV+VVGw3fQnmXKzBqiERyUxA04jWSGbdNz5vSDCo7yVv66r/5H2Wj0dvE5lenYzSiklxoY+hcDw1eI73+fLI4lyQQ7pod2imgbVeFmS6Sdfq/zXjMaupYUKNHjMqxO/7ebTuWuHZ8cv5Lv3C7Rh/CxmisPvpk+3xCPD9NrbR9W6btlRPSBzQak9EagkQhUJOXS6hJFRlIZWBrBDEWZ93VqnLd8K/+xs45ik+DwAghkDGPtAShwXCVGn58LOxGOaKeERXGTlBXMbZpqKiaiDceFytsHPOVfuDNeZ9/li/wS0vCh+waCxbmBYZiKAS6tZREEBW8cXiT5tL4GGg0UEukNoqXCMFjYsDEiNE0iVkRgklkEhQqHF5KnBbMR2GPn3BVs8I9JvD/9JW/sRj4hb7h963j61mOOsNuH9G4RmXWqSWitsA6IKwz1ppd0uNHxjlP0Zom1ljvyFTJVJM41kJ30rbuEO3ot53DXKO6MRtV86RJtKXVJN+h61roIitAbI4rh0RNRShpB1iItO4RKU9M569/ajYSmuBZH63jfXMfwe9oG6cdH3EBmMx575xOfCBTRczJm90lIlNfiUn5Hytt2Nxee9QmO2ZVxJW4ckAYj8gpQAPBNxjrqMejXU3Qp1prPsfO0rKcdMwZbXB4Mg+NCiu2wGnghyeGp6tSq0kVQQvBOWiUsjaoi0zyml7jOO5yPpoZ/sDBZ21DEQPXjTIMOZUbpwlKGCIOFcGIgtaAYoJtCyXt0r3rATTJBcLEmAoFmq7+UZTQasJMW2jRIBAMRhxGLMZEcIFeCGQup4kZdxjhy4OaPwwNrxnDc0LGE0xJxoRxGNPEHuoyjInUWLx13BrghyvPN4uGkTi8n+AwiLF4tJbpTMUUk2jUSz1fdxuM0AP9jqghtyKYdvJPwLS5KyXESF4ugCuIjaTqOd2IMDNNm6Rz4/QfLmnzItE6aq/3WRMvtWHmGXFZEJdPRkOIzabLutMZVSptL93WIQgKYiLEDKhQHAGhN9jF+mQV1aIV4UesyymyppzUk9uNsMW4e8dgG3kZHKIwkTFRHDXKCxrlxbFOxQZvwKRKXTZxVCpkxjLAUPseH89z3msiHzWeCcI+ETJJRFRZS+EHbdUwDcSIElAJGEkyCRNsV8BNpAVoVDS2qxMM2va/pWbfNsxXSSJWIsZErEnvGTB4sTRkZAbyCH1VloJn3TR8I4ffGPZ578TxhrrmFaHHgptw2ESkcuTSQwzU6lnL4KlqeH1l+O3hiLEJ2GjUInVgc75mclEQtG1dvojf5RlhhcXCyVPFgjFCxOIkpjZ2EaxA7SP95QV8FKKClYiSZIgpJ5oitLM1lyggxhKNo6qb+41cxAntjwCXBXEZpDFqomLQ5IdyhkfL5qDPFipdATxDzBgJPYJG+v1l1o58barG76qW4oqyV8SbjGB3Vs/DyYdeEMfEWYJJY9xui4EfmAT2i3C0sFTWol11SQyZ8fRN5HDMeWee8Vu5YdV4+iEysAUqDlVPZTwRz9hq8tFKFJlyJmpJ+lElmiR+TPO101XdYFIrUJsQjiJEk0r4gk6T95aISlo2Blodl4ISsRKRkBNpaGQDzRSHY0+VU2O4o1D+59Jz58TyhknB44iIVSYhVQ0xgaDKfOjxvRr5duVprGHDGDHBwwkS46Q+n1LwJcf+v/ZvTBPCrU7M48UK0TiakMgqasTSuraGSK8/pAnaLTamqRRtnTq2ySFOi/TRhYyg4SCyA1sItuCyIC4xdixiGg2Cms0ryBmfA1sqcC2TacCqI9hIHZQym0fMANW6LUsG1AgikSzPb1Dos4MS9ApE4rQ0Mf2MYlFb0nOR19WBJ3rPuNj0Y3dkBBvxWWDOK3cGz5syw9uGORIC87VFTfL9dz7grUWMUBIZte+s2toGtYNxo6TOQySyqSTrqrrdv+0J05bf2yaFLQn8LOVeOmuedvmTNEZK4xpsFCIlXiI2QjAeMcpAhTpmvL00fD1G3kDglUbIgXWJBAmU3oLJuL4e84rGsGYttW1FtVim2dJUic5V1arujByXxpj5Jj43K+zAaqSOFtGKoBlqFJVAEEWtw2aLNAJiKoib7hCdFOKcvBFFCWoocovX/NDmRMedicuCuFQZeY2jILqckXIn3bLkXNAKJEhNqQ6MB7GI9HBuGfX3YmSQkp+iWAmMPEuq8cbeT/3G0fFv/vSO+BKn/Xy6KaJ0KMZHagKPO3acwcoGuUtRGLEmD9A4g9OGudryYVfwuz3LFw24Rii189RKS7hoFKMBFDzCSSbsuiWfCKAnF0tgqyuXbpvPoDBtIZrWvXTzQrSVBE1k+r259myMrY4viWIjthH+ojAcw7JRR34oWuaMZx0hWMOGBnpGeFIdmRw6Rqgmecjz1LLUJqq7fv346DTn5xXWOpdn8RmNuCIn0UjPVPjourU2XmtcuYTYeSI16BjHAiK+/STndo50j4oqLJSgao/GGHf0UnHHVxUB1NpRVDamoktog6iHeXHsnhrBSYpfBoM+4BBjQQwawVqLxGbeN9XTNNQ7p28xndVRWtW/RCWIQuGwDz7I0ic+R+/wGmpLVAOlT0tr0TELTeTDheHf5Y5PSY7mlmE92ekrgtNCAIzS2MAcniPR8BtFzm8XYLxhaAR1nhBr6lwYBtj9iS8gd9xN0y/xEuiMF9OgXW2T3zsi4EJtueyKuaeqK/HisISUcNcGEwSDRYOhKIcEbTB4rMmJhG0XtrNhs3gFIUYE3SicPDAo7I4+MC4P4lKdhKgrsY02thZ0H4lFuLStI4pSDnfhKYlRgWTNKcbQK11hJN6O6HmYOnreoAJqVLQb7GlUkMzCJ7+A/tlnaZynkZTfaFzSsS1GywfLnH/nHHerkEefXEez7VqmywtKFtIUZhcdSsbRrOQ/lYbfKdIgtCJEMgNBKmJmcV/+FvYjH8eMNxBxSTJjNiURqvGE7Oilwe6f/g0ZNfoUsuEBEUNEWlcIkzonSJqsWg15MdcWShQTHYie+znRRprGKMYkqycrejw3HPrS337cpd8RZ8BlQVwx0oSoa6qgXW5lW6Ln4edUhZTMjq6PlEsklb1JyyUFsSa31jzRGLNwnj/OI4YBJLUGtu6hgi16cPeD1B/8U3R1hPQzvGvS3EICA4QPl33+Y1FwT3DMqVJkNbFax9BHLo9D4BQQRAt6ocE3AeP6zAVoouH3e4Z3G0ce+4Qs4Alk6tCqhj/9GL2vfgOTFYQQiDHZX1trQGRHrBXrJkhdN08cTSZFTkRMRCSkCrlJLTteAtiCrJxvZyumCvpZt79bdRhp3aq7VYwQQiQEfUD1pKaCHYfL4qhV1RCjrgXtkr1b/wYpdxXOMTjeioixOa6cB5s8pqSrwwiIMbc0Ptyc/5X/fUfsJ0UUkaikLgGcQa2SffIv6H/+a9j+ABEhp6HwSl9zvuAy/q8MvuYtmY1IGZnEhlDkEHea0uPhwVvwjYMyw9Uj+k3D0EQOm4zftgWfsYblRhlIHxFD0wP7jXuwn/gsRTXB5tnUV8xIm67fAUvFzGpeOL4zMzWFiTiJSTsHKboionhsVpBlvbb3/MzbnahJkegRiduSiqoQgxJCrGsfDzZh5x8YO+KEPBu8Erzq+ql69bpKlOnkNy25KSc+9GRSa3vj6ZclGgNR4/T7FGNQzFKI3B5i2y90qdF+2Jj0mqizuHvuY/jpPycbrxMNuNqQN2AiPGgy3iSGO9RjXMAKVKFCsh5GBkRToTu3j/aMUBQvI3yxgFVHYSc01IxEsMFwT2b53dzzUDD0a0M0AdPLQDL0E5/BfuVOsqLAWEtUJYR0rppLzFx7fvrfixqusbl9irO0U6hjm45LPzsCmYHh3BzGZsQYznjJ7oopQmzPk+3Gjd3PIsSNOj4waXZA2HkWXBbEFVMwtHFiHN8pT0AIKgRJAjoVi7aJVmm/sFNdkOT/Ze+/w2xLr/pO/LPW++69zzlVdVPnbkktJBRRDiBMMJhkkjEOY2CAwZgZM+PHM4/nsf3DBmsIThgTjAEBxiR5nCQskpMYogEhhCQkIaml7lar8+2bK5y09/uu9fvj3aequvv2vbdv1b236/b59nO6uiucs+Pa613ru75fcWqFQ8OmcJ0WrIme8zQYDJoY4ktUQy2xuRq7elF4X9YyAY9KfdfdNB/8EBYySRNd3eA0rEnFO6s5/6MpdInDnnCMmFYZdpEqFx/Kg1rkEodhp1jcZE6itQYJiriRxQmSeHcF/2moWEhES+SkRKkZ3P1J5h/+CNPZFFQIofRfrNzQ1/SATDvTSZtfnJybi5LF48vsAgQValVqF3JKhSDzFFjQPZDF6I+yoyTMYnFR5h8JeZblkdae+dfEgQhc2bBsulWJtNoTRbMn5sQyX7g4B4tBX4zF7wHb/fedc9Uzis0QSyjGkdU1KhGSlx68GATVpm4GL4+xukFXDiNV85SM/auBrEgOEs2TaAisHl9n7QN/ArMZIVS4B8xhEOBDFfx6iIxFCCiFuVtqJG651+u6Zruyd4iAVGhKZQymtzeLXlRto0FHxX+vKn4zOkNPZJwulKre6F3vofrEQ0WDrcqAFiUYv7b3hJVxypdXGgayIL6dB13KZFkludDr0zwOQj8DsJt2sk0MKqTiLIArHmqyG7VKB3r/w3/vJc/4lOtgBK7s1mVbd8u+4KeUk6Jl7eQ7ch6FwNhzdPqB3oVqxPZysS/wuxs5G21yUi6GmttjKxRfiCrKS0KQl1d/4XtEqmH/t9cGYi6aLdamqjFi9z9A/JP7sDpiASoLqGc6h9+KxsMIo1wu1yyFAmqSegkg3SbzHlRkAbFYmPi6sD9TgpcZyGESjqvxW5VwViKH+sHjblgjH7+P8PF70JzIqSsDyiGKyDUc+dFAVEaDSl4VlcH5arYCuGVSFrRaxaPisvBO6FcYu1Q1Zddru6PlCy7k4h0FNyMqs6Dcf/V2+PJxIALXPJvPsm0m3BajIepaxh/Myo14CZnQonuyoJ67KEkgS43HFdque1IpTIUjMcirKxXRlcNIqK5Z1tWMZwznFqowwLemhI9+hPrEBkTFpEPEWXHnrqj8vgitO/EZ0N6/FhAvUjsmHX9UCe8JA0ZmRAeLkTydUf3Rh+DUSSQOaRCCB5X0xGGgq7jNISBwS6XhtfoUd6ZTut6haoixKQoQYqgI6kXM5sI99kUYK6mliJeJkZwJ6uOgHL8Cu7bvOBCBS1RcVU63RsoSFpKnvUSK4U/rISnYrrEVkwqXBppD573FRWQQNLw0xljp8BAS62uXdYUAsaq6QVPrAyc48t4PEQalriduKB2K8dtB+YQGCIo9c2aGrypcIQdn4HDCA78dKx4NQsRQE7RqWPvQXeijD0M9IKosxHOvUeBSVr7uB9RcXh+C3KnnOW+lN1UIs1VTSheLjqL0pRG5pGZLKdAXGFjGsuFmJ9X99IX+8pmCAxG4QlSvKz0do7YLiTRBis5Wr8F16UmQ9aM9pWCJBrJGqFd7ydsnha9GhFeGIDdJNbimS8UUAnMlJsmM7n+YwSceYN70HSWvWHPjoxHeR6QVQUJf13oWwqRosFVWU1vgAwp/WNc0bqgBGqnWzzC89358c8w8GCaG6jUKXAKxamKI1Rurul47f6Jc/Dw6hGpwtGhteUZMcU+XSOVYECO8cL8oAoXZvO1SejTnPNnfHbsyOBCBqxKsVs7U4rOcDXBciy6UsJjfu/B7WK9PtBiXUbdtSxdH0TigGh7ZPq274e6fklN+cfNn/rpeSxluURGJquHsOsO7P457Lkx/B6Ehi/DbAR6KgUbKMTkQJ/gKQN2RbGSJDAVOBOc9oWKrV55IMdBGZ/DejxLOnEGqChdT59rN6IlwQxXj5yezx02HANu1WlchixDro+TSB0WlAslg+ZKCV+FCliZU7qdKs6jNsh9v8x7cZK8iDsR1/dh3vMIHUU81KltuVpQz+7EWcUEvgS7n3g/oQk9WtV5exRCMpqoIoxvJnsHSThbnjuW82rbplanL0XM6X1Z2VRC3xtQpSfXwcZoPfRhGQ2qtEJyI8slQ8X4C56JRAVW2a7at1xrBQVGSGuIJwfgoykdiUXKVSrFY03z4PoaPPEqFgrdq3l0T4YGj3/yWkFJ+ZSP+qdbl84TP8uDN2UEjsVotYUscIZaHGJdQfu2L84tfMwTXgEtlM5OT0yzPePIpHJDABaCim4icWwiXm5dXGVsIF10QPbHLLV7ccsQN9UwlgtRHCFUgSlG5ye7F4TqEWkRfr6ojUnfNgkFOSVPXqZ44xconH8JyIKOYBmoSd7vwSBXo6LBKqcyR/OwMXNabd6gbnltW3XkoGB+uhEqEqsuEOGA0nTP82IOkSQuDOHTx1Wuxve4eLduL2tlkWIWAEzEPGIHsRSSwlAQMjUqIg52sTHrhwEvUvPS+w15G6ARRZZxIneuJfD5uxTMQByZw4TLOJg9jRlXX6KJIL07SfNEFXCQjlnv11IBJINtOCj5DWKtHVNUaJkZwIWrA+gdQNnttNnvuyjf92DUbxK2On9X6sY1w6MMfZF4ZxIoOoc7KBOOPonMcZyUr0kayhm3JmmcbTAwjE1ODaw0h06nxYWruwwg5o1SklQGDj7wfO3sK0ZW14NU1uSccvRGtvmAeVuhCUY1FwLMDgeyxPE1Ty2DlTma2QdRAsIDZDNEGQiDlC2++ihGlJemgKM+mTDJloN10GPyTVTgYoxQHJnDNsqVxlx/MlknJe7bwYras1L32hF7quWpWyO69zFxfwwdS192ecn51Nrt27fK2CzJrNdz7CFQ17sWwdSDGRxrno8FZzcJIakQSmXxZ6hnXBfrh4aLeWigwEeEB7XhEnEGomUcjVVDd9xjV+iY+qhrBr7rW+pFv/nFNOb9E8NfEEMAWE7PSdwzDjsmrKHW98oQmkfdZ1MVnLb3Xli8+AFKUahGqGLaaKjxchYNB7jswgSubWzZ7JODMzQsdYkEs3eOhFnpbM4F6sIJJ8Q4UcxChqoS1QT0UCW8U0dHF3/HKoPmTD8f4wP0h3vNIT8soW2+a+ECEezTQ0I93MKMfELlWm/uMgCycq11oLPJANO6OAdOarJkUnObkJvVDj1K1bdCt8crV3sZpsjBP+UXz3N1Y60JYUfv6k7AwqUxmEJS6GfSTEjtByr2YxT4V/2v79/prIuIYgSBG7jpUZL0WTp389meuJdluHJjAFQPWBHlwpVaSSc8Oll3SNnuD9GqY2qxCqDEvQnNuPeelboau+qeS+fP2/mmXh7C+MYgnT4d6a4prVZaBIXNOnPs90klNF5UpM9QSQcIzQu3gmsDAzcmeyFoaM00WOhHurgJnzRh1VuRsgjK470Hq8QSZzu5858++/aoeNJcw9FC9ohoOGun15HHBXctKACf03XOpRoRYIRTyNbB9jkuB/iJPcVFEq6IAqxGFIu+T/TTOM9qSbDcOTuBSyU2UPwHZQGO5KV37p84+8JX62T0JA6pmlWxdybis2JIHEbouv6BL+XX7sT+Xg3ji9JH6keOijRbuDjAAHiHwCZTGhWwBsQ40kkS2fSOfbVAXgoGLFfkbVcjOwGruCYHjZqy2jgRlPorUDzyIjreoJrPbm8dOXdVtzdlud/wzpapql2IiUhhWUpi0CwFNV2KzBqLbcjywO3DtfO9CcCDnVLS4tDS2LNspcXvG63AtcGAC14Pf9jIPqg8Ax4vOeTG9dN+PKk5RQzUFCwPq4dq2P4pLYZ/H6BwZhGEM4VX1N/2ra9Iyj9PpcwfTScwrFSEoahBcuJeKj1cZkY5BglUDjwMSZZzj2YjC2StL/sL3gyyZURaOG9yvhsequONUFf7QQ+hsSp38hngV1RHk6/6l5NS93Lr2BU0o1ATr1e+L7hos6rdOoBoeLuz5XdnWzitwsVu6F7UpYpnW0VkZIUrmp9MBesodmMAF4NC22e/POSNm/RND+nbwPoQvd0wiVTMqqhM4Kj272Do05GGI4UuAW/f8YU8Tf/LqL5IgPDc+8mBsQ6l5VOZseeKTQdlSoQzaJcyVzoVohc/0bEQhzZTbtMpGzI6FMiK2DjxcZdoQkCSoVNQnzhI2Jy6dHQ0nTl617VSlQuy1a42sDKy3a6M4TTkOUpjthdgVqQZr29pcqruzLL2kwNWP1yMhojan7Q1ys+hjWQ7O1XKgAtc8mU3afL9bAnqzg36ZuJewtXgX7xUnYmwIMWwrY5bOolMHmM3md7bt/NXxG37sqmZdGkRCFW70s2dq60m4lSvHJfFwEIYu1G6kkJkg5JxpHMKu7tNuN50dPPFalQv+dPG9xcvY6eme7/sX+tnu7z09lL/a/XfyhJ8akMXAjZAgmmMBWjE64LEIm7ljRSscWJll9MwWrfhavkrk+cH/8qOi4s+rK/5sVcdafXfNamfvvK+/IkIVB9ArQOxeIi6yroujBENCcck0CdRNJFTxuMZl4LoiyClbzvlP6NoxeU4KDTnlYn6xh9BVLgpHvCKlLUI4Qog3kbxFMCqRcqRCw+FB09TKp9fKVVUWDKMVwurqDSEPGbBGKxGC8VhseEAjowxYhbgSRBk6zEJmFopWlWZh0AnRBVUneK+t76kU8k0IXlGcCSuK/KKRUDoJdBpIGugQOhe6DHOHiTpzoN31mgMzhUkQZirMRcrf7fr5RJ2ZKC2BFmUuShIhAQknuxFS2W7xWHSjWHTYMq6JTgOZ0liJLiiRrJGkgouhYpgonSoWSjbiKgzIHLcRp6pM8C1MW8arK4zuvb+pJ+thdPLqZFzmxOzyus7kZRIDSSpqM8SLAECUTOUBkYZOM7pSkazBPfT68ruDW7604jwUzbpkuDaITzlS5ckwcH/Ug8HhggPiq7iABs1B9X0EWe9IK+ItEkBKNWdPcHFwI6oiJtSDIbOtUAqZKeMENDTEqqt96p+bsJ8H7t2H3bokSIyiMd4uIog60SGKsC7OaUllidE/cQsTp3CY5t4xSIaosFFHPEfIgaCJVeuY6wpigpFopWUWi/HCKAdGKaIYMzEmmkhqjBBukpqbteIGh5tmUyJCEC1cI4HsRnInU1yro8h25udAxklubIlzFue4Jo6rs6FFraPxhoqAh03aYCR11JVBrtHckIiYKGt6Dsk1cyJbmlCbMcyGaqALAbNe4qVPMvoFFQHhjGQ2NeBWXLcF8M0xMp4mmV8d//KgOgI+m0pWXEA0k734Gy6aRd7npubCoB6gYW8lkZKNKrUKKXd0JmwmWQ9B7vnk337hgcm4DlTgOv7tL7db/sld9ycJj8zcbl/xrpf1yCzEAS8X7mXwVDSAG/VghIcKMyOIkoh07mgUOvOXIPK68PU/+sn8b/7GVal+i4oKfqtExQWiGVmVE+KcFucQgUVNOUpZPgSEAQM8QCeOWqbxDpOSiW2oYDYhKlRAg6O5ZLCVd9SqvFESd+bI502VY9YbyMoMk3GpIXm5hGQxbrKYK9j5V+HJkbfPzjZlWDoCQjRFqZgKnNCOB3XKY9rytniI9exseiZJptVMlBYI4LBpmRDnDCUxTMpMKiaxTBLUqaidFmbmYjvK4FdAOK6ZMxrKrJ+XwO5nzuEnTs3S1vjKn1DAzJ4v+Bc2oxViVLxt6YiIBHb8DvuXRwbNKqqhUHUuE46QRQsHMhudBxrktIhd3VbqHnGgAheAGeud2QN1jG/AHUMJ6J4Z4rb4twkuoNWQUA3I7RZBlSL13KExsLK2cng6nf8ZUd4JrO/Dbl0cKSukQ14pnRi1O2PgUVGSKk2KJMlFzkWkcObNwRNdgMqV2mFaQSdKbCPmFaGe0uWMds7zc+AzbMQXe+JWjIFnkgjuuTToRZgIOJFy6fSpzPmwra75FFi8j/fqQp5RhJtNuNVrNNX8uTRhTuQxafgoyi9H4V5NTIKAKiKrpDzlnLcQOmoqGotkNcZi1NbXPnszAXEIUgLpOTHOSXlIBXdEA7q+gW+NJz698qyA5ht+NORsb3C356xphJwhd0VKWpSdKWsjuyNSUderRTRzT7OygksAz6j0HcWuO+X41Ukz9wkHLnC1nc3b7HevVnVrJrVDORF7GPspz7TiQ+h94JLQEJs15vPNwtJ3o2TpSt3E0Wwy+Xw3eSnw7v3atwtu43wW3H3oIZQlmMBZcU5K4W9VuUj8pNgnnyp4Bg2ZgRnaBWYSmEikcVhNhuqc58+UN/iQL82ZWyzhecKcTBJlJloczBZzT7syl73v0I5UejmHTqK8fHtBMwLP3JZaXuDCl+eKkzHwxznzzjDnLjPGQZiGcrNHc4J3QOrft2FRFus3HCjlyiSBcyhdr4pKEKqtMbNzG2OZzfe+fxeBCiuofF5Tj9ZiT5aNGkiLkZyFwJIUM4xYDYlxRNtf55f9mO6dglL20oV0b7dm3YOqB2O4eoEDF7hUPEX1u7qc5ynUdcVimbiXumKv0YWRLeLBcInUgxXazTJCE5RiYZYzA4HNnJ/jMX5m/IYfe2966/9xxefbPKXGzWqkzK1FcdYVTmugtmIQWtjVRsldSgBOXY0ptKOMu3F47jzXnc/Qlq+cGrcSMJ8zFzgnFHdnYi/p68j2DMn+X9d23mytmPIiAcktCHRBmIkAHY05fzplvgB4VCZ8oIr8Jyo+gTDHyapEVogmZClJxBNNQQSoiJzGGKtzyMvIVz2dIdP5TOdXXpKqy+m5Kv6nQrNK8MLRcolF1VfKTKK74z0toq5XQeoiHLiHzy2eDInsiojSaGdjs0/4tRSauwwcuMAVlBxUPh7UtxK+NtBM6pujezmhJevKqDSYZFQDsR6iodeoDxFzQXKHqLCyurayPpl9Tgj8B+DR/di3C25f29buFoIptSmBzJbCRj8SMq1g1QpHp/VUOnEmzKoONeXIvOIlZL51PuXOnKg1MI81k7yz3HvcVOP23X7lHsTnP187n2d9IVocgi0MUaBVpRXhqClf2ib+dEp8OFT8p7jCh7JwWlvQRGWx5zz1A8re37g4lQdOS2YzGKsuhAzNLMPGpAtXOHBVX//DMZu9AfVbYsjkjnJhi1CJ99ScnhIh5cqsmxEQcSkOTZcLcSdKxqQmu+PepRjCJ+XSNJ+fMThQdAiAU//g01xEHszGY9komlrsVdyvZ3IZlIxZyxxXrAmxwczJ2RAJBFGyQTOswLrPzCl9hn79j1zxSWaxrOQUTBUkoi6MVdhUQaUUr7MIeMBNyJYwErcn54ss85PzMd87mfICE1wrpiiWvXf82cHlc6v2H7KoS9NPvvTqn4lA54EO56xEkle8roPvmo75vnaLP+uZ21BEMp2nnmVePAUzjuGIwroK897KzgnEnIk5p3CFjZxztqPWpS8ZDZoVFdneN9dAtpIZlyKgI15qi6EeYlJky/dydhacRWIpOQTVeRX1wXBAVCEWOHCBCyCZnJx23CUZOoTYs4EvF2WFpGCKkoBIZwKhpm4OY73YoORERvAqgLQcG+ajmH+OpVTv2849BUJOUuWkbXTmoRTGp6psiVK5MOgKBWEaKoyKG815k3f86LTj70wTNzgkEVrxfn6x3AB6XpPdfZlb3xdsb0+/mYVS5wSKjXzoM6iJQCdwZ4a/MzF+aJz5nGzclgrhtFXtxSGN3GcyGzogZSOmjraq6MjQzRP5ygUu/bofDua8EQ2fH6sRIjUEQTxRmxE04KHfa+sz51BBNSTvgxSKI5jHnpLS4cYZ1fjIo3/v5c+EZ9Ul48AtFQFStq7L/lHq4olYx1CeTJf9jn0xWPvw1z/xYgzkqsFES60nU6brJZQZtzgakLe+EA0/xhXmdLkoriKjPAWbgM9JnZNihUpDJZkt6UAmvJzIm+dw1EvTwtnpRG132XbhmRKkLgcLjrn0jKfFrt3g8HdnymOq/EgtvN9nzFDWdEAwwVNmopn16HSd0Uki546UOmvzlUs+RHUgql86GtZHRXax4pGeS+hlme4ZpVAf6uZwL1cje5wRWbyDEswQgy7L8RDkQDj77MaBzLiiYHWQu+s6TFwi2fMTBkCeHsqNDaKKLd7GMhoCsRmBFj4XWnSdcme4K2EwQurqDlS+MHz9D1/ZrCtlT+5mnuh0SgpOJ0pHps2JLVFus8jXTjL/bDLnmJfBmgP1GH2akCd8hZ1hoLI4NJ5jHd+1NeMbkvDiuEoYriJNRR0C58xoLVBJTZ0yK7EBxPXKSgG9GLevqEJsVHr2+8KcddsTQYDcX4/OoDmEhJ0F4t5quYK5EDzh5u2ktQfaZAfC2Wc3DmTgOvudn+aDKn5gmnxdUdhTttVj2/W6OFjTT+iHeoVYD8toTN9KFjOaEIiq3HTT0RsQ+Xo3v33ve/bUUFfUAlGUWpTICKixENGB8MYw58enc74pKRWyH5qwBxwlICQKc/9rZ5nvP3eOV8zPkkOiRZi40oURWWKp9VURFQ1XyoKu+sa3RBf9ymHT3FzF0MepnTPVX1194Cq1OHMh1mv9RWn9eNvlw+nJImKAe7L8cDI7UBwuOKCBC0CVR9zTce/aPbPmF3UUW4gJak+NyAZxQN2sEMzAU/FkVYhuRM/U6gTxV1nqvkD+yvcP9mn3ngRD3KRc1FUbmOaKN0ji1qHx7zfmfNdmLqz2PeWe1xfKyIxsq1sN3fiOjTl/ftbxnJCYNkZKglnJeNo60Ar1/Aocwfobfkws+02W/S8O6jBS7cUwH7/F28RdgSLbFGo0NqVUwA6/63Kx4MyZQqjUNPh9KldwbXyFcGADV9d13Ww2+5jnzN6YLT1kRwrFvBT73RzXiliPEC8pdlbBxMmpJYoTxTg0DIeYjL/WU/fivW/IUyCIqxs2bOhiJFWJI+78h5NzVs16V+8lnohS2N/RZI8I37g148fWZ7xSnM0wwXqlBQkB0aCE/W8SW7bKsn1uUHlBE8qgM14aCzsqD2UbxaXn4SViHKGhAiJO2rOgwEJN1cpMZB7W4d46hmXgulrIZl02/72mqWfme88zyoOuHI6cE6rFtskIxGpEiAOSBOZW1BYkBNwy5MygApTXk/Kf4y997xVRjZAQRUVEVPv5TKc2p94XIcXrH/KEV3TnJ892fHnW7e4komSVqov7HLg0EPDbA/zVGw6NVrclxxfb9oQT2Of9OJmqGiEhgMY+2O2xOO8QLWMpU2mcD6r4YIwHiwoBBzhwxaBdXcXfHycbuy12pO/P9G7VizlVucSahfSjFD3tsfzjVubHBodwM4KVcRtXwbwu3C+M4aGVI+T0l+m618pf+qf7flyTZy3uLAv1h11dwiUuC4Ei70Of6aCCY7Hbz9xVlLWv/f5hHfi60bD+U7U6C+FLP18QEkBTn/kLdT2CUDzDzPc22lbg4BnPjjibATn14N95yYFL1g9s4Dr15ld4UL13nu2BlHfL0vWQ3WHo0m5v8cJaVtnROxJy4dE0K+CZBiG4FXs0Xfg7CoO6YjSKL8Ltr3vON+/3/k6tEzdX/Onu1RKXhJ4uokrQSven9hAiR77un2sQPkvVv2UwiGudQ/btj+uvs8f9VQlcDhCJ9QC0EGd9u+2yt81yVQRt510+kbNdHSmMfcaBDVwA82TjSZs/3pmTrR/N2XUVLDKvvUzTuzsaAqGqS61BereVvliKO56gTh1x9egQCV9OTn+Br/7H++qIbNlU3OXiHutLXC56oqvu1alcQkQGK8TDN6OxukPq6v+qm+YOCdKP8pwfvl2LK8tFDQM0RJxeRlxlz08rRzAJuIhNu/TAPOUD11GEAx64cM/i/v7WdGy7Oos77if0kxN7e0qJaJlbjA3mZehVykTZdsmhk5qAcfjo4Cbgf8f8C/gL/3TfCL7nclKeXA5ZYp9RFb76ZRzmMowv9RBdu5Hqthdx6Ku/Y0jT/E+haf6U1nV9adVIR0xwM2I1Qqt6W5YrbI9n7W28LXlgq82py/mj3dXSqd5nHOjAtf5dr/Smiu9C5Kyx43YCi+Dl/ZDt3j7HgFCNCHFIttKSLrWzYsdAXWHq1O06I08cWl19Ba7/N9lfwV/Yn3qXyHJxeFVg1j39kR9BYoWuHKa65QUMPvUNHPnib61D3Xx2qKpv0RCPXSxo7Vy7hfLingn1CmifcbkDebvzeNkQwURRaCuVu8KTORkHAgc6cAFk8w9NZvO7LKXekqov01+ix9ylwBwkNsR6VLjoQpEF7t0nOstUPiFUNWJOrcLKqHkTZt9FTi/lq//R3o/zMte6KpBs8/B0aE2iZVl40/NoXvB6Bp/2p1l9459fJdZfJiG8WUReeslvtbAZQ0AhNquYRtz7wFXE0S5jr3bBvTSZVMdVDB879eZPO5CB60DOKu5G16WtNuX3qeln56yDsIuDsxO4yjTb5cIcQqiomyHzsHAVUpAEDpUbUw8EEUwjUZwq1PWaTb94c5rWcPlOvuq7/4BfevNe9FIO5AV24ODeLRLp80J6d2kNIIquHCHe9DyOffbXi9WDocfqOS7yzcDXAhd1PX/y86jIYagKVbWCS+k+KhQtLg17fCA7njscPxdUP7mHN7qmOPCBy91yUH3f+ixtHNEwUFVkMQMG7EzTXzxVf8oLQsqQc6yKbZm11gvs9XpJOiQGIedJIaiGhsoTTdMMnPqztqbj76NLb5Gv+u5f9F9687nL2s9njtrM9Q13EfPHZ8gaiuNzrCDUpfjejNCVw9z4ed8iuarrXFW3oPpXQb4MeAlw+Ck/QkoGtTtoPfHaC0EIVUMWLZeZCDkXMxffAyVCKJlbm/1ELQfHufqJOPCBa/qPX2uj7/jgH7QWjpv6zRWJiRsih6hZR3LAK+ViJcgLPcUEKyXbsIpUx+jaRxlqwHMgu2M+BzIuxc5crXdmCUI91PpwGH76+ubkRk/ptfz5f/gvBfmk/+K3Py3VVDcy4rasc11ZKFSClO6tKFqPSpAarqHDVWSwig7WOPyqL62sqlZyVd3qGr4KkT8DvB644WKfUeap++LrExpHZUWYaUY34Rr7IZ+EuuBeXZJRxqJMIiJF8WTx/wBSdMmmqX0UOXiM+QUOfOACyM7paUofXMvVq1ZD6fSpQZDSfu5M9qjX5eCGaIVWw6L+5YYQy7IBK51L0Z0Rk/KHqDiD1Yi7vWArxW+ylpe42dv0q//JL8Qqrrf/8f/nl6Kx1Kq4+8EspB4ouIuLu1QDwspRwtFbiTc8h9tf8QUyDzEmDQMTvT2rfhWin4vqpwC3A0cu9tYLCZ7FQD++kzmJ7PoNkcKY12ICo+L9GJCW8aSLsOefyhh2QVnWEGcS8seRg8eYX+C6CFxBZV6L/hqh+Z9az7WooNaS1QkCFio0X355qXDBQINQ1QM0NGTriOHSGtxzlFSNWK390MqR4ZecODV9LcgXapAfqP7id34I0bZ7+3dc8CKqXVWug2bKMxyzJDr+xMoR/eYv+NZwz6EbwonByspmMxptxXinifxZR14HPB+R53CB5eD54Bf4v11hDTQS6gYN2i8ppawYeoWInd8/P3YHrifyGpNDZ6RRFT4URa64V8KVwnURuCbf88ru0Hd/9L/M2rxeR7spxoYqZFJWVBwPFewhcBXPRUCEWA2RMCDljvpSp/V9yLCqqXxKns9YW61vjqOVr86pff1Wmz+B6y9Wf/673+4pr6df/a7zEgLHqsmvb3mtZwTODVaPvevm537mA2vHXnVi9chr1+vBp6UQ74BeRwiGT/9dFxTSHcjj/rVD4XF3PDRliYqCd8WtzAWRgPf+lU+3YLAIZuZQBZkHCR/lgBlk7MZ1EbgAui6dmc273x+uhS+tjTpGSFbh2uJ5bw8Wp9TgTTJa1Wi1Qjcf9xeRXvQi0jSn8ZbOjRxHxKAE75oYeVFaO/wi6+yzZ+ubfz1n+8/xq/7hvw2qJyI2VphtvuM7HGBWVZmr5LD8LMbgHUfu+CsfPnLT/3Zy7eihjeFK5cgeh+YX+qxPRsnkpR/u977X42U2Ng4wd8QMpGhzBSmzs8KFh8AXWdYOEXuH32iWycXQ6fiDf/fFB/ZBeN0Erun3vNKa7/jAr407PmcldMfm7mSvqYLge5xqWNQUzA0JFVW9QgoR8wlIvCjHKuUtqIa4R8DwbKR+aTDA8JBXDt1w6DWtxZee3Zz9zymlh5P471civ9B8+Xc+cGvemB8/8fHbaM/EZc51ZfHAp77q+R9bOTqsB7AHw2h2KDjnvzZkUX8oTObyn/1vx2YFQo33rk2O4z0/UXatKJ8OtvXmcsesTY/EEA5sRxGuo8AFEGP4tXn2b5FufmweGsQD4NQk9l4e6gv0Eon1ChIrLJen4UVKpYRmQLaFhdpubhmYFAuthILq4OjR0fPd0vNzSq/d3Jj/T5bs1Mkg6y9pZxP1y1mmLPF0MO/mw9Asng8XWEn5YipDehch2SlTXSQFF5wi22z44rrcLl851WAEocYyiHgROnTBhN7v8tL2ZTfFxwEzI4rPO+MjiB/Y+hZcZ4FrlvLDubUPxia8zJpR465kS4gryOVLwpeBaidLP7cYa0KIkMsF+FRdnAUsh8J8Pu+vOa4RzKmkw7x8TqzC6IbDwzth5c5xdRPfPrTp297/7ivuJvSsR57TaPGOajl/6CoxpgxER1WClqWb97OrF+KHqkBUIaiQELxEo226gjjEqkE0Qs6oaKFAaO+wvk3ne3q8RDPDzGjRpFF+d+8yE9cW11WXKgZmoeG/PtDm9eHmmBBhXB3G9+yo3uK0mA1QieT5GXx6mkjEdQVzoQpSVEitdwtyKbSJRVn2gteZgzpJvH+qlierV+CVU6tCOLTMtq4CJFR0VpPMET3/uqyEjfKPufdcvqIqeqGgtbgEuuxMs/VZdsm81K00DUXJkzNIOyZrw8wE9Y6hzYi5oyNysRy/yuCWyEFwNQQrUx2FnjYfRj5y6jsO5qjPAtdV4Jp/16tz8PAbW1O996SuMhg/RJyPGRy9aU/vKxIQiVQR2tkGk81ziCuWy5MshFBSeXY9PfeTJyqO7YdE1BIXhbggevEsejG9vyNDczEs/AD6nMkf/7PdmGyu42mGeEZD8VW0vqt9USY1gAbMC/9rMaBmrrg2zDpbN/OHLv4mz2xcV4GrQDbA39lVHe1wlapNjDdP7ukdnYBojTJnuvUoabaOugKBTEajlpmyXj98O0XfpyFvxMh6HZ6qZyC6nDBp+/oT7HVSYcGSF6SIACy+OsXTYFGD6uv4IkKajum2ThOYIQLZAyYRdwhy8S62hzKYHXoOGCIkUbJWTOfdJ9vM5p526hmA6+5uUGVe1/q2zc3NM5tWMRgeQdnLbDM4Zf6xnZ6lHZ+kVgdXRCuMXISS+ie0PC7j6hUk9ii1655pL9ICX2LvcCCpoEG2Ge778Z70kxTujpk9LiN/XB0KMAFVY7xxArUpWMYslPEfMeIlOFolHFdB3fqVAGRRLKVp9vCH2eXA82quu8A1++5Xepe6e9qtye9VkuaT1oijyxcjFRGCguQ5043TSJpRhWJYYSIYhvYzYbIdvBZ/XYoeiyz/osuPp4TTanXZ+7DEpWMeq6JDv4943NttB6zzUyVEIIZINx8zG58ikFAtM7GickmU0eyGaiBaJvXjaY6xOZ2O66r6AxHdE9HjmYDrLnABuEuLxH/1yZP2WJcntE+TuPmkSX0S3fg03dYG6kV/ywIkz6UrRCDGGtW4LfS2eI+FbKlwobrJU98piyf1TCP/4o5XPq39WOLpwUTYrIsByoJXdfVRvBJEnc31E2iaU4lgntEg5CwXTQSdrlxr7hi92a0lsHw8qn/w3JtfdqAL83CdBi7+yetcXf8Hs/m7ZVRTXYKl04WyoW62yXT9EaTrUI+lixTAxHGjCLPFiIbCG4MSbBbOQ0V08KnefdEjOv+1VFj7Shcij60sG4tXEj9854uZNAMisR/lurJ4Us4l9LSHgMZAN9+iHa+jlgjiZM+getFtE3JJ9rNhEkqmludks7scP3EFd+mq4foMXICLbxL9F+4/PTnlUpUp+34q33rFeCi9nvKVbSbz4sIQLVSG6WSd+Xi9F3MrM2OIF5cphJS96DVp0aBQKZ+FFwt1h75V/oTg1JtuPJ5l/eSL0t3Jopxrrohl4xI9zgwGdFXErR+/uYqfvWgECPQUiaJoMt06S+5mRBVSlwghXLTkoAhK6uk4iiKI5VZU3i/iB76+Bddz4Pre12VR+U3bmr13noQcK1bTnFYrJhppKAEhiaFWAguWqdyJEjDX0tFpz9FunUIVLBimxTc7ZiWkcgG1BqY1GmuCCBVGpZAzZJp+qv/JgUsQxAQ8lJf0r2KSRnAICKpC1pYz9TLjupI4Wa/AIJC8mN1fydBVtLIAz4hYyaQ04NkgbVJ7pM4N89ljzNMpsleoNhRS4oULXZ2s0uQJk8ExQpqi8xnzFM6u1uE/NzEsA9czHm5nwd/+0LnxmcbnnOAQa905jsTERqdEMmiFaygmrwAhkpyidNrNmW2cJc0nu970yRdzHQKWElWs0VCT3cETdZ0JOkXtKZaB6pi2YJlsHblrSakrM5FQVFZVMVGGnXB6sAxcVwpzVU4Pj1GlkpEvWFf7WelahELt+V+WDZXykBIRHCslBa0wNUQNcWG+dQ7yDPFAtotvUZifYxLXqLsNGoVZWGU6796bc/7oI9/2sgPNmF/gug5c/s/e0BHkF9Ns9oFxEgajYQkE0zGVOp00xNiQU4u6IxppDTwobh3WjZlunoaLDGlbNlKXkTggDFeZm9C5gxjiZeRo94T+9va5kK0hRqUJSl0F6kqJQRFxcnZyMoIVLtnZuuF7n/+yK3nInpXIovzcc17A6ZUjhahpizolQClyK0UH/vFu5wuqfD+32H9foRhbWN6eS5Re1NIlgDZIaPqsvh8X8r4WCrhURTAwGNGVdnyWND1LJGCXIIlZDVYY2BQkMPfAuUm7GWL13wS9LrItuM5mFc8POYfyjvtPzl71grXxDd3azawyJk+22KpuRudzhlpkQ0QDySDieJox2zqLpykxlOT8fKUFd2eeMiurR1i94VYQwXRIN16nSzPAEJ0SdYSh5NxXMwwUJ3gm59wHtb6Vta2JqgRAs+EI0xi4f+1m4KNX59A9SzCJgY+uPYdp00CfbT0R0rPQU3+uqkFN6hKWO1yEajCi62tjIUR0PsMpfpz1ylESisYKkYCGiLVjJhunyGlSNLisz/ME3BW0w3FUIqQ5882TNIOb0ai9289TI0nEZzNYXaWbJVrTB4eheueJ73jJge8mLnD9B67ve0OSv/1H/97H4y/KNxz54i55M1LFq4qAITkVnWcPxX89Ctgcb7eYbJyi0p2n6ROXidus51gjo8Pkao2qGnLTc47QbZ1jPtlkNt0kdevEKhKCMp/P8ZzBEnQdanNyHPTt64yRC+/GyxybiCDeUWUheOSR1dFVP4TXM1oNPDZY4d5Dx0DDU6zDSkAaNANyNcTcGR69E7NM1yVEA8PVQ5hEQAkK3k7K2E0VCdWgOK2rklGCAGlE286YbU4J/UNLFooRLmhPgQgIlQRm4zOMDp1D49E+53vqGGSpRQ7dSphv4DQTc97pGh68IgfwGuH6D1yAI6doqrfcf2ryquff3NzZzTvycEidprho76RSbKcUgzxjvnkauinExy8OzotqhDeHoFqlc6WKkaxz4iiytnIUT5lQgXiLTDaxNMdyi3cTvK1oO8pcmggqAQ1aaBb0HUUcUViZw0Ojmu+788X8nfs/frUO33ULE+FsPeTe1WOcWV3dTnifBHdEAvXqTehgjeRQjQ4h7oS2JZvj1aCoiDhkHJMaxHEq3JXssnNOFUKoqUdHaMfr4HNUF5pv9MINWvR1xQlS4+2M6fgxVgajogF3of2yTKWC58SpTXt0UMd/oy7T/T5+1xLXdY1rG//89S7iv0vb/nrbplldD6Cb7LqKKhAt+lrW0U3WmW2doQrgothFDtPakRtpVo6QTEipLB+aJuCamducpBlCwOOQanSMeu0WmkO3Ux1+LqzdymDtGFoPSS7kXORSVLWw8UuVhGmoiNaRbchHD99+dY7bdY5ZiJxuhvz0p7ycVDfnDVrFzqsQaKReQ6oVEgMsK1AhWiFab1MoFijD0XGbUiO9SZN4AjMygXowom4a3HZmXEWUqEXexkVLELRIFQOTrcfI7fSivc5ajG68wcSbWRD/jUD+6MlvP7hqp+fDsyNwAYhuEasff+TE+t1zdxrJtG6E4CSq0o72hKQp43Onimqq6DY9tCTnul3/kJ6XFeohVbOGStUbapQl3mDUUNUR7Y15Us6klPrlpSAaifUqw7XbOHTDbRy+8fkcvvF5aDXEct4pqmmRJEmhYloZgy7y0MqRgy2m9AyAiTALkUeGq5wcHeYplUp7B+kQG1wqOis8PrO+DilVb9K6i+7i4L292XbNrJcqUnFErJdoHhIHh7B+iYgtLMQWPLJSozC81MXSnG58mpLTBYwIAkEysXwonShD6UgSOTHuHolBfiqKX1fZFjyLApf/s9e5wIcQ/3/vO71xdiwjhEwSI5uQgEo6us0TpOk6IYQil2tG7MOES8CkV9ny0iWq124i1itAuU47M1qHuSlVNWAQKkLfmXqyd2MZws5USH2IwaE7GB25FZfYS+1mMh1ihppjBEzmnBis8H++7E+T91U759kFQ5iGwC/c9nym9fC8a0TFIScEo7nh+RAawIkhYFihJmgfoGTB1etnIRbGxLJTaijziVqWnm6YN8TVW9BmVKYv+lGx3ItOqhfyKNqR3anDgOn6w+Q8xok4DWYJDQ6p1MMsQpeFMXEm4u+s1T/w2AHX3jofnjWBC8C/7/UzVH66m7S/t9VVxHqNzhsGaZMgia6dcPbsKaoqYF6m+FUVs16yxhNqsh1UYn2IanC4XJy9TpL0lunZwCWizbCXNXlqlMs6kXPHYOUY9epNRX/LDbVAJlBZInomBUM888c3HmE91svgdZlwgVYCDxy+AT+PVEw/fUPOxqE7XkU1XEOrBo0NqLLb4KIn3T1B4G+hGLIzWyi9GlcRuVHcEzFUDAZHeppD7q+Fp7otS8u53TyDe0sTDDdj7AOmMqDRzCCN2dQh57a6j65U+rOqsjdplGconlWBC4B//oaThPCWUye3PjLrYJjHjCol+JTJ1mlyNy1EQO3nF71fIrogngheZhNdlXrlBsJgrVz4i3Gh/imbvPCDpBqgMV7SqIYIGBWjI7eSY0MVCpPetpU4heBCxJgMVvn7r/sCpiH2LO8lni7+zQtfxdlDRwny5NtApZj5jg7dQLVyDA+RjPeuPD0vi4WDwFMd/yd8f/v89+YYnpEQGazdgsSGJAkWfLAn8f5821sjnXsM66ZAolahs0gYrtJ2HU7FyXFar+l+CuQDj3379ZdtwbMxcAES9Hfw9O8eOX3uTLaWqQe6yVlmm2doqkDOHaqBbZWHhVwNGXHI5sTBiHp0FNdBv1TYVeMQwVxIpmRX6mZI1TTEcAmaWuKEOGR06CZc636WsWDneQ0DhPtvuJmNWJOX6qhPG12IfOSWF5DqutBi3HeCkJSj3KbM8OYXY1KRfUcx4qJd5gtgO4psy9tAiAOq0WFMdJvH9+TSgmzHPWk36dpNOuvAMiOF+XzCLNRspgG5nf3BQP1tp77jZQfayedCeFYGLv/e12xJ4N92Kf/+NK6y5TWTc6fwNCMG7zk0DqLbLW7YYUu71tSjY8RmpSjpLq4vLzfAQmPeoGReGtDYEKuGGOPFdblUWDl0E/VwjVjV52HcF5upDcv8g1d/AUnO79u3xPmRRfje13wu96+soe2MuSRSSKgrlcdeDkbRehVCUwrt24Fq70e6xKbCDzT3knUduhmJo/5aOx8FFkrwKhyvjTOP4JJQUZo8JahhsebkmfW7h1F+VLDTe97QZzCelYELQLAHEfupB05s3Js3TzHf3CACnjMhhMLNce/HPHKZsPeACVSDNeLoGK7FoJPdgc0NdS+M916ytyOSNKBNzWAwvKTgpVpTD9Z6fs+TfzebMZ/P+eDo2BNqK0tcCAJMQ8Xv3PhiZhnIgEuR4lbDBDpNJFEO3fZKvHfw2c8jLNtnrNx+Jk6II+pBTy71C/eMszbIrIwBEapCr3B47MTJk4MoP1+p/MbpN7/yum48P2sDl33v6zpS+1+Zrv/UI/fd+wiWUaeYv/qijU3v12k9HaKIstUrhwn1GuaGYuXvFiqo0BfqndJwErJUdB7pkiIhEkPsFxvnvx3cnZQdDQ1dlzlvPmWO5sK0/8uf+eVX7Dhdb2g18C2f9dWcbgJV7nCPiAfUIlkMxBiQGQ6GxVEa2M9Ba1gQJPqFqUhvNBwZDG9CNZRsjCfNaZRM251WGtaqzNbJB8geyFKzMc/zjvjf6qg/derNrxzv6wY/A/GsDVwAfP+bWtL0Z8jpF85MdSMwB4TOFFEHLYOyahV9hQttDlGPDpcZMgSTjMsuycz+itvWFXAIUi64TgItFR4HaFUXcuL55CzdyeZkrZFqiOeEPOHXRMo21nmDR6sBf/mNf/rKHqvrAC7Cd376V/Dhw7dR5QlBiieqO7g4FYJYS5WV4Q0v5JJ0ki8TKn0Ak3LRKIFmMESbARnBxDE1DEUkgmdUc3HwITOjQeebdJPHWPcwG2+N/3g1yj8XeOyKbfQzCM/uwAXg+SRV9ZPe2W9szNJkLhDFybn40ZlUvQxJxjUwWLmBWI36GCWA4WLbdYtS3O0ztj6WaV/7QpSWQI411fAQ9WBIqGLvwbi9Qf07ZTxUNGs39e9nO0sMd8zL8gBXmqTcdfhW/vdXfvrVPnoHBg784CvewO/ecisjbRlaWZFtz6EKYIZZIgxWEI37nGc9fmNKvPLy0KOMA8UYicPDIGW4XlByzyOLGsAEs1y8N1EqT5x8+J724UcevGto43/q1n3k1P/zymdFuXMZuN7y5YbIXYTwfRtb4cPzzkmSaYiojXB1Om0xS8RqRDM6hoeKLAnIBA/FKfuCWBhmLAiGgokS6hH1aI3haEgIsUib5BLkoghBhFgNGa7diKtiNkOYoXSlCWAVeINSljfvuf1OvvtFr78KB+3g4f998av5pTtewNS2WLEJlVSIVIiX4xwwyB2xHhBv+rQycH0Fbw/vuQ3aRzHDSf08ZF0PkATaBSpxxOdlxMhq6qiYO7XNSZ2xfnp+VzM78U+t3Xrnue95Q7piG/wMwzJwAfzrv5wQ/hDVH9w8sfH+lKEjYl0ieC78miA0oyPE5hBZKqyYSaGXELjcfTtDKzWNopo6y0Kioh6s0IxG1M2AECPmQs4Zz5kYKwZrN0McYV5GbyuFKIWm2OFMdcpgNqee1bzz1lfyfc9bmmrsxjs+5TX8p099IxaHHJ4HNDXMkpXjKVArhNyiwPCWlyOx4UrdGtvZ3eNKBGXJmF2JzRqxWSNb+bXYd7hNBDSSu8xIM5MkHD/H3VrbD3o7+eWt7//iyXk/8DrFMnAt8NZvSgi/goTvP/vY+v0mRlCnS4V0GgdrDFaOYtTbOvLQB6OLJOcLvSV6ZVPtmdcmSkKZJoMwYLByiNHaEerhCAmxF55TpFljuHozzehWkEN0qSJZxmSKa0fWmqQNdetMY8cvfcqL+YE777zSR+xA4L/e+Xp+9KWv56FRZNQ6Va7wqiLXDqEj5RlmLalrEYloHJaa0hWaSNhdEhD3voFYIqiJ4FJTr96MNkNcE8kN1wZTcO1QieQ2cebs7F5two/HKvzn9qe+/rqbRbwYloFrN/7dt24R9VdBf/LMydOnTRI1AjlSr9xINVghW8Ktp0dQnoYXMxFSLfIkbrmnS/TMaNWyJNFAm61kAVoxWD3M6uGjNKNVXAKdwWD1Bo7c+CkcuuGFDA/dThgeIglkmRIcMpk2btL4OtPa+cXnfzr//M7XXJXD9kyEA2/91E/lJ17+WZypD/fLMZgzJbFBlBbESDgdAa1XGD3vdUivFHJ1INtNnbIwLU2cwegY3qzQktCqwgh03uEyJ3ctJ87NH6IKbwlV+Pn2Z75lbzbtBxTy5MHffXzzgzpH91f+5WFy+g5R+6u33bBygw1vY+2WF1I3Q9pUKrrazxIWFQcuSPQJyraDcf8dtg3eRYrevZUWuYqiUjqRgUKrsGTk1JVX12K5RWxOTpuMZ2fJ002q1kjMMVfUK+ZxhZEobzr9Pn7wQ39yhQ/YMw8/9MpP5R3PfzWT5jkMtxSGMzSNCVPIA2VmmRAPs3LT86mqIY4iocYlXHFenOCIpZJ1UxyjVBfXQCBoZPPMg8w3HyBYxzzl0sSZTzh1cvxxmtGPhBj+Vf65v/6MZ8ZfqfiyDFxPha/5l7cC3yMh/OXnfuqrDo+O3tkXx4s2k0pNtoz15psXGqMuJQ3bdrMuddmiciq6mEkT8i7JnHJJFy5Y5WCS8ABmjncGKWNti7czOpsxzTNsvMVkvkGYTYk2JdeBrKu8+syD/PQH/8ezgqbqInzv67+Ydx67g62mopGAzcZ0TabONcO8gt24xmj1KEGGeLWKiZZunVk/vnXlMq4FSRnrEIm41oXoLAnEwAWXimCJ9UfvZj55GK2Udi6cPbXxEQbDfxVjeGv62f/tQDDjl4HrGkD+l3/1Kq9Gf290+PavfO5tL1oJ+RxUDZkKdyOIoSmTVYq8yQWxmIRb1MeKftJONBF4UgPeEacES90Z91i008VBPQOJLKAOWMa7Kd10nel0kzzfYkuVO7fO8Lbfelvxe7xOkUX4pi/8Ku4b3E6YJpLP0WZAqEdUoyM09SFCNcQGFWqJ3M4J9QqmoYx4UR4Mu2Vq9hM7I9YGuWRcrgEsUXmHEmi1YRqGrDIjn7ybx84+ClnaM6fO3sWg+cFQx3fkn/nr6/u+cVcIy8B1DVB9y8+rVKOXV6vHvmt0+OYvu3FQD3IQOjIDKWMiMwF1Ry/BLfvysE1lLQXJXiJH+qCnfQRzK1lbkF5Hylosz6naihNxg25ylmZjkz99/5/w5j/+rT58HuzzU1D2YxYiX/kV30gXBuRKWAtrDNeOYtrg2qChQSWSELIUkxLPGUIkW6lVLugq5V3399jsfj6Je3ngRAGpyBbpcLquZRhqKgUPwrmts2x+8hPzjfWTH5Raf1CD/1L+mf/tQHUPl4HrGmHt//olbUarL19bW3v7uLn1JTdEw9sJHmsSZeQmEAlXIXDtfOtJNHqEUKR3KI0AVSNI0cokwzwoc63ICZ534iF+7u1/jyqn0tk6oOdJKNMIX/uVf5XTo5eQhqs0TUBHqzQW6XxS7MAQ1Oj9LQ1XK5mvUaS5+8Op8uRDu3/bWuCUB0tU6LyidSMLQKQJDZLG5PkWNjhE58PZg/d94L1Vnv4D0tYfdD958LqHy8B1DfGcv/V2afLkZ89stt9486f/ZeJkHZNMp06dWkSEdAXrIraQM/EdhYoFFt5/oqWF714aAVDm7oJWVFaRcFqdk0MHBAZdg7uw0p3ghSfu5x/9yr8gWD4QS0kTIWng//jz3829N90J9RortkWrU5KFUhvESNqUJZ8Lmo3ouWSoi1qhsUsUkKKndsX48ouRsDJZUWbzc8mOVUkSsdSySouhPDpm3Ob0+2rtP8qzc783/udfdCDJpcvAdY1x21f9/W/1x+753uEbvuqQvvrLCOMNBtnospEko+HK8X52J3NPPKa64AO57Bod6rWbFFpmjOZCQOkqw4MS0gDJNWcGDrrBKDdUvsasEto45YbNh3nVQ/fz0uOP8rV/+B+v+YLSRPk/v+Efc/eNdzCuh1TzxLBNpKoiRTg8L0u8qms5NUo0KdLkSA6pTBwgSDakZ3WWGfqyNDRbLJmvbMD2filapJJCHyRbslQkDzQ+ppLEhq9x6uz4ZFT7N1UMP255/onT3/m6Axm0YBm4rjlu/ZK/8Wlx/cFf3Xz0rucf+XN/m9FLv5jcepll9BbxC5t07heeGMiUMgvp5r2SRdEQK4PeDjKkCx3ibTEFcWGQpnR1hxAJVpGBDhBz1BTRQCvQVsKsLp6PdUrcMN7g+Wce4Uf//ZuvyL79va/8m3zyhufx2OFbGderYONCCHFn0MJqF6gkMG8ibYTBfJ0kLXgqzHIqstbUuaJOGbcprgqqKIq44iJkFQRDxfvARZ+ZOQu55SuDXoO+N8aoJJKj0KlgSfB5ao9vTe4ZNeFfR+GtJ7/j5Qeeo7UMXNcYd3zRNx8N09NvH3/k9/7M2ugQ8vnfyuANX47G1eK2nq6ctPeCmb8IWEVuZ4f4WugTFFLs9jykYWZM4wAVZZSd4A5ShrWTBDoRLLTFz9Ei0SPqkIIxD4kUIbSHqJNRWSJpZl4JXQggESfQVhFHCeY08xmHJ+e4cXyaI5NzHJ1vMpTEVqw5Uw85vXKYM6tHODc8TDsYETuoshPciGSUjNHSecKs47Z2QBuUmQZITtNlhMS0Tsw0s+oRJDIOK6zMG2qbstqtM9XMuKlpCMUZmp2GhgOJMsmgzraf4c5xvSJnkIUsTfmcQHIFZqzlCVKt8lg4ynTr7B9U2LepyHtP/P2Xbl2JLbnauFLx5VlhCLsf0K6beKfvo20+M8Rq2P7Kv2R+8lHCF/91aFbQK5rMlxrM9miR9AkChbeEauGFmWO9lbuqIjGwKltUOWAyoAvFtDua0riQm8TKLBV3Ip2XQV8TQgoc8gqVCuRRTIROAi6BmJUmQbQioFjUwjISFFNhOlQeXL2D+7mT1grrLbgRLVFJ2ZO1WcLnm6j1ckC9XKy4UKOsMSDKiC2fYpLJModotKI0HlhJA4YGkzowaJUbbEoXTzMLMA1DXGsCztRzqWWRCdBbeEHGUIm9LZhvRyvplWQXXgOXu3x8cr/28TLMQRIVGdMBGyu3EPKU9v2/emZ4+Ka3snLj7x3/ns88sEvDq4Vl4LpEjCezbOPxPfPBarda+5CmYv19b+eW4+9Bv/I7see8Guk2GKeOQe4Ydk6Smq3BgKHAfDomxmJZlfvhactlbtEucoMU6ZMno2QRQL9M9W0tsP62MQcGdABShBIRSAESmZCgjdXj31PBFFocaIHh9s+ClxeUuhOyGBqIZWwlQ8AhzYE5g6fYn/O2MXZ9s+tfZRksxBQoQzGQoOikKdSWsQDzADDot80hz5/wlvK4rxF9fGDatTJYyAbtBb2TYnF5Si3Rh3SVE/KcqM6WOhZWCXXDcPNh1n/5h6i8e1Cf84rff+Tf/d1l0LoELGcVLxExVlbVg/sOHT02m81nIEIVIyePP8b6v/97zD/wK8x1xDAcpZUbWF89xLjKrGYjt4lYNdRNsWlX1SIs2HWktLxOrz8UN6Y6Gl0YkOot3GdMQsWYQ1RymMOqNB/7TaY/8U1M//Ct02mX3pVSd++13vKDgmXgukSc+P1/a4Mm3r12+Mi5LiVUAoN6QBWUbuMB2nd8O/Lr/5p21tKMyggJYZVZNjLFrmwyT6TsxKpGQyCEwHDwVHnJEgcVguOambctURJjVoki3KAzRsMNcnuWjV/6p2z89P9NGp9icvOnzXF/t802DxxP61phuVR8GtA8P+mWPmGeX1zqSYkqVHT1TaTZFPsv/5Tmwd9l/qXfRn3LS9F2jtURRUr9yTMuwjxZUbgUoe3aKyahssS1gTkkdwY6JNFyVAVjxCRlxn/ya/gv/RPSxgaDtZvJMiFvntnqqgfv93OPXNcGF/uJZcb1NDAdb3WbWxvvKUokhTZg1lHPjToo01tvZXLvu5Cf+mbSu9+G25xRcEhtcS2OAVElpYy70zQNOS+XitcbRJRa15BkRM1siWAnPsGp//i3mL31/2FNGoY33Uobp+TpnNUYPjHsNj+5+bHfXgauS8QycD0NpPnYu/ns3jBa20wpI6poUGI0kIBmoz18Bx0wf/vfZ/aL/5Dp6UcYDioqEeazCVAClkkpylfbTjJLXDcQENqioxWOUr3/1zj1lq9j7RO/y82rkTMW2egUCRVT63DR94nIgedsXU0sA9fTQAihizH8j2Y4emwym5FyxtyYqRVBwDykSS2Ikw/dzviud9H96j+ke+w+mkppguI5EYIWjtV02o/nLHE9wRxmoaKp5oT3vJ3ZO/4RozoSdZWtakilWxyWlpxXmepoNk/pfSn3rdAlLgnLwPU0cPLj7/YmhoeqqrofN8LC2DV1mGSkrpnnVTSsMqw7RvUcv+d93Pf7/4X21AMMhsWpOKVEFSIhgIRwrXdricuC4YV/X6SG6H2YREEraoTZx36N9f/8/YyHAYbDMuSeK0JcJadMNTkJk8k57+afaCcbyyfY08AycD1NpK7L89nsg27tPHfzMjYTBoiB5CkxzMjekb0GHZHXjnLkfT/L+Nd/mMnp04TRYUQcyYoyJ+lTBy4Rua6mD64nBJ+RpKKjolZBDDoqvGrI3tJ+9Nc5+8s/zg2+zuE6MreKoE60SZl60Iq5BCTGu6qgH52euf/qzIxdJ1gGrqeJedv5dDb/gIVmuljmXYyvKGt30H34t+h+8wdh8zFkDm0Dnd5Inc495d/tZnUv8cyChyEZGMicydYGkueEOGBFnOqu36D91R+AdJpzq0fZ6AbQBYg1KVZkd7JlNjY2xrjfLaLXxXjP1cQycD1NnH3owxZjfF8zGB5PKT1BS/78qLyllhH2obcx/rW3EPKcQRBcBkUN8wJYhq1nJmYdrGqHOcQ6IqtHUJyNj76byX/7QY6M76cejOjCIYYijEJH5x3ZjSgQgtClbsvd7y8uKks8HSwD12UgBH0khPAxx1DVxw3qng9umWY4xJuj8P5f5uzvvIXR1hiVCW08epW2eon9RAy9QGGe0dWrzHWV9hN/xOwdf584PsPWyg10vkaYdqz4BpENsnVEEaJlUupwOOlmH/dtiYolLhXLwHUZmM9m4/F4/P7UddOcu4t2Bt0bNurIiMPMjxyG3/l5xr/zo9T1CJMlHeIgQkJgOu/wMGDQjJB7fpv5L34bVTemGqwxlQHqSh0MC5m5CcoI0YrWOubtvBXRj2vQD29tPLoszD9NLAPXZaCdz3LXtR9ts88uic4QlZwDXcj4LDK44RD5t36O+T2/gVYrV36Dl9h3VDZnLgPCyo3kj/0W/OLfZTA5SW4OM6fisGQiE1LITOOApGtAQ3YlK4y3tjrcP+xmD13rfTmIWAauy0DTNHk4GH5sZXX1jJldfKnoc4YkJu6s+BZdfSvtaAX7xe+BKi47hwcQ2RJro4Z08n7O/PI/w86dINdHUIEkSrZMJpMdvCs+maIdaonKlJTzNFv+eMppOZ94GVgGrsvA+on7rKqq+9z5aNd1T2r8+S59p/KNBG5UCiqZ7Mq4XmV+5jjNPb9JV40IqjhK9o5gYBroiETvrvLeLQFF114QFDA3HCuDvS50EmlzQkdD+O0fZLx+grB2C2HekeMQ1Y45ofC5JFAjBMmItKhnvMsAJ2MInxBYFuYvA8vAdZnoujyZzdp3qcj8qSgLi8DlWmEiRJTOlMY3iLFmY3AM/72fQkeHyObFPkuNmMFVSFIRbBm4rgUMxUWL9Zx4cZwGlFw8NOshsvUY5z7031kZGB2BmjL2lcUJApIT7k4SxVwRE1yVqaUZ6MeqWH+yivWyMH8ZWAauy4SDI/qx5Ezbri2kwidkWru/Z0Dyolaa246GxCozTt3zAdbGx5kYRE8Q1kgKMU0ZkJjpsnh/LRDNcXGytzRa0zFiKlDrjJU0R47cTvtbP0NIhmpNEmEWlSgJTeW8xxhRFVJK29dBzplz586Nc0rvNbfTk/GJZeC6DCwD12Vi49R9edDUv9MMBsdzzo9jue8OXADi1uvAQ6wG2wPZawFkss7m+/4r1aGjWEqoVyRNiBWd906rp9yGJa4c1DKIYHiRqSEUJzPv6GgI7Zjufb9ApQ2VRzKQoiNk1Et25l7MOEIIqJZbLedMzrYF8iGQZTp9mVgGrj2gqqqxanjQcilTPLHI7u6oCkomSO/lpxFCzaxzXCoGwxGn/+AdDKOQQ4WmFtVElrpYWi0pqNcELtYbWkZmlhCmDD2TUmS6eiP2J/+Nbv0UsV6lMcMdgibMDekpLouMO4SwTVQWEaqqfrQZDD4UY1ye3MvEMnDtAbP5PG+Nx+9vUx4vLsrFxboTxATciKEc6pQSRiBpRasVOQyJp+5mfs/7qFbXCN2s6L6HmiwQr5Lt2RKPh8eApIzQYAoDmSLJSLJKNaiY/e6/o62OYLFCLBNwgjjZDJeSYS2uBzNjkZWb2VRV74shnBhvPbYMXJeJZeDaA1JKXc72TkTPdN2Ts/5F8DIrZq1BgJwQMnVVgUYSyqgypn/wDkIMuAqZGnFwTYRrbsf6LIVU4AkXCCFQ0zGlRgcrVPe9G7v3g1APSN7SSUVlQmKAkjByb9Po268Yy/Lx3Ln1eZfSfSnn5TJxD1gGrj1g88wDFjR8RETuLbQIP0/GBYSGLjmKUKkQckugJXhCJdMSqO/+HbrZJlYN6GRISHNEErCscV0LuAU0OFkzmh1JzqRZo1ppmP72v2YUGkZMcZ+QwhDNMLUhtRhCb4vWF+jLNcGiSL8O3CMiy1R6D1gGrj2i7bpzk+nsj91zC6BaZth2OowgxUOMxTcWxqPujorg4RA+fgT/0H9nfniNkAU0oDaglStnNLvEU8PEcRS34rFosSZKJp19DPvI/4eNBqgElArEyJKp6EjEIlsksu3mVOBY7jDLnwxB348vawB7wTJw7RGOd8AfdtmmbdsCjmooXcSci6a8G7rteUgRm0N7/0MlCuRmxOw9v0QVjAqndRCLLB/M1wZmLQLUEkn9dEQ9WGX2h2/Ds5PUyVQIFULC1anpyEQWBrBlqkK3611meW45f1jgvun01LK+tQcsA9ceEWOVm+HgvSHG46kkXb1pz+NNSC8I28KrQ+RH7yLe9wFy3aAK2ROVL2tc1wLBM+5GlEylwixHYp5jf/jv6JrDF/zbkm3tPLwWX8+tb0zN011mNrlKu3HdYhm49ojxuQcd9wfarvtA6CVuinNPKcjqBRROF3AVxIxoGfvDd5CHqyVwqaJLE6BrgiCQUNw7KjGmo5vxD/4Ksv7gRWdTzYyUOmIv7V08BjKqeqqqmg/GGJdp9B6xDFz7gJxz27bd+7cmk80FS3pBPsz54teohYYomZkH5BPvppqdY+pKrAKZ+irswRJPRCYWzS0NTNvEYDBg8vtvJTeHqeTCT5MnT04IbTufufu9VVXdNZmcWsrY7BHLwLUPiDF6Xde/I6pnu25O6A0wcs7bjOkLwbNAAFRpt86hH/5NZHQErMOqZeC6FkhElExyQVdvRO95F+2jn8CqNS6mS1tIphXz+Xz7/8fjyXQ+n78v53z6Kmz+dY9l4NoHbJ172EPQD7nLvYVoyHZXKYRwUdmauoNWjEETGXcd+Y9+hdXgdFmXxflrhChCNiPlxMpohfHv/DwNpWvcXYIGm5kV/lfdkFLHbDbdBHtf27bLE7oPWAaufcJ83qbxZPLhrfF03HWpH/dRuu7iCqlDUVxS6V4FRx+5h3ziXnK1htpSrulaIOZZkaWJwvTso6QH3kuTp5g2xIv0SxZLxKqqSKllNpsC8lCsmg+6T5bdxH3AMnDtF0Q7kfCfHXnMzYgxYmaXZDE2VSO2iVlT0XSBpFtU730bDBvMavCMCgj9e4mQzDEEuYTi/xJPhnnxQURD4dYteHc4uBHyABXIhw5x7j1vY2W2QVcdputmhPriqrWL4FUycMWdTwjy2JXer2cLloFrn9COT6QY4x+4+/3ZMm3bbneVLoYcjcAKDR3doKGbzTl317sJeUaMDY72hf5Uiv0Oob/hzJd13svB4gFQyMA7SZCgZakfjc6UMJ/TfeidmCjZldVBxXy6cUmfUcoESs42g3SfmS/dqvcJy8C1j7CcN9q2/XDXtXN32x6yvTiULCBzh0rRWDE//RDhE+8l1TVoKIO7YREIi7idusGlaN4v8WT0dcgywrD7+wISsNhhoxtJH/htRqfvQQJIVHKXiHppx3yhCDGdTrZiHP7Jkgaxf1gGrn1Emp+xEOJvtl3aCqHUty4p40rgVUvuBtR5Th4E6hAJ7/4VUq04veF7CDsSKWYIzlKu/vIgDmKLwF9mGEQE71nvM0llzvAP3kYVhyAJgjKdzmgGg4u/v0DOia6bk1L6SF3X76uqasnK2ycsA9c+o26qd+WcPplSR8m6yuzihRBQJEaiKpaN2XxOBOz+9xHWH0FVt6VRWLybl6Cly8h1mfDt13bQ6sd03J3crCH3vZvqxD3MrCabk3CGg4rN7mK3jaBaAULOqW3b+UdyTg9ubS3VTvcLy8C1z+ja9sx0Mn2fmc12looXvl5jdJgPiLJFV48Y5IBYy9Q2SO//NUbDIVWMsNB1UgVVzCEvvUT3hEXzpNS6BJcAITCqVkjv/jm2RIjiZBlgLjRRyfliih2+fd7H48kE+JDDclp+H7EMXPuMoJpCDL8znoy3dmvOXxCe8c5pdYoSGVYDcmjITEkf+i1SO6cKSujFCKWvwySXPl9Y4ulCcMT7o7dguguICjFWxIfvYn7/e5lJoq4E0YZAYjMZq5fQyDXLpVSgemI0Wvn9dn5u+YTZRywD1z5jPjubm7r+zZzthFm+pBpUnhtVnemqEc1kytw7JrpG020Qxsc5deJRUtciCGaGuW8L3MklMPOXOD+K3NACjuOoKDFGxn/8m8yzMtIx47ajFifkKWNqBvni3DoRoUvdbD6ffdzxT1y5vXh2YnnVXwF0XXd2Opm+P6UOPD2uDrWjimrbJFWpA8mcJkW8UbIII9+kDTdiszGH//CtzG+4kSTKoIq05mQTInnZVLxMWMhEF3xeOropZDIKaUCbE7O738lg6yTiQ6oQaC2RdY01MhON582kF98r/K2O8XiccprdNS8M1CX2EcvAdQUgqinE+KvDwXCiqtudqt32ZU90AnoqpG7G1iP30qyfAE9MszAQJ2qiy4nIslF1WUiBLig+UDQ7VRYaEuFwzexDvw5bZ5AQEY040i/IHcUQ7ILd4sWoV1DdqJvV36qbwZIGsc9YBq4rgHa+3g4Hw185t75xcjKdPc4v9kLei+cLbqLg5x6k/eDvoKuHqYOiCPgcVSXKsnRyOVCPdCJYNNSd6IprxPI6/sf/BZls9EFrp64YRIoXQC/N/cTgtfuctm1HjHFrdWX1Q7PpmWVevM9YBq4rhKapJ4NBsyWi20PXu7HdzdoVtJ4IEUFjpEpb+Id/mywBIZG1tOcFwWQ58nM5cFHIjqSOQjAJdIMjzO97P+Hk3SiOa8Xje8IGYshTTCvsDmZmRt003XA4XC4TrwCWgesK4Y47nivPfe6d86apcT//8vBS5hgdyJbRs4+QHvwYpnW5kcKIbNAtE67LgmsmmKM5kwUsRNCIffC3SfMpHut+hrGw65VdwapXNz2feznQD1g3vPCFL6pvueXWm171qjctW7/7jGXgukJ4wQtf+NzXvu51jajO2rZ9nPPPeZeETxHABEFizXzrDPKR/484XCVbi0iFEvAlj+uyYGSiQERJCLmKVKfuhvv+mHl2TGP/m77dXHF3zAVnx6n6Se9rRtclqiryGZ/+6asvfenLPu95z3ve0qppn7EMXFcA3/C/fKveetutf+Mz3vQZx26+5fYEbCtFnA8XzLpE8dhgszPofe/Hth7DYqDqWoIIYUmHuCyIC66gaCGdVjV86FfJGw8TZefG2HFpKgRVQ3uzkydjN/P+2LEb+NzP/Zy1l73spV/48pe//OartFvPGiyv+iuAY8eOHXruc5/zJZ/yghdUr33d63JVVbskTnZqIPDkYv0TYQjJhUFI2LlHmdz7HmS4iljCc8dF5M+XeCr0xaskUrqHs03aj70LyzMGIRDYOSeLU+MA7jhPXvrv/u8YAy960Yt40YtfsnrHc+94/dEbjn3Wd37XP1ueqX3EMnBdAayurb7s6LEbbtRQ+Wd99menEMJURLYdX3YX5Xc7Hp8Pi45XCmvY9DHk479L7Zn5YEQCwtIF6LLgmlEPzEJNbAL+4V9nsnkKDYHsYOycF+uX4yoLVY78uPO1yKYX51ZD4DM/67PZ2Bxz5NixY0ePHXvTHc997rKLso9YBq59xpu/65/JysrKVw1Hg2FKyV/0ohfl226/o5vNZgwGg20d+rqu+6HpiwUeQ72lqiIpJbrjn0Aeu7u06jUAy0775SBgJIFalJhmTO9+Dzo9TQ5DlA69iF/r7sAVQug9NMvfjIZDXvva14DAyupKvbq2+rK6qdeu5P4827AMXPuMG268YfXGG2/8s0ePHtOUOg1B5XM+57NzVVXtfD7flnMGiDFekguQYlhOSGwIZx6g/djvEashokZ7SXpfSzwRpdObIDZMP/lB4iN/QpVbJDRkz/hFBuN3Y+FYvci6XvrSl3LjjTfSzltw6rquXozIq3765/9jvPi7LXEpWAaufYaKvnJlZeX24WDkIqLn1s/K53zO59ja2lpOKW2bZ+we+bkYDCeLQjWkasd0n/xjfLIBIZTvL/G04RIw63AV7O4/gM1HUB0gnkkhYhc5rk+kPxT/xKKV9nmf9/mMJ1uknAghEmN1Y1D9PBFZBq59wvKq30f88I/8jAQN/3NTDyozcxERd5NbbrnFX/ziF+eFplaMcbtYf0mBSwI5Nrhl0ICdeYR8//vQZmVZnL9MKALVCvn0w4RH7mLezXCpcUsQGrjEwLW7XmlmrK6u8MY3voHpdIKqIKKsrq6tVVX9muFwtPSa2ycsA9c+omnq0XAw/PSFQqYX2RRJKfElX/Il2d3ni1rI4gm9WDY+NQqrO+dEoKWLQ9L4LH7v7xYFz6expFliB2JC1YzoPvYbdI/dA/WgdBfNduRuLoDd3eBSwC8+A5/2aa/gyJEj2wV7M0ckUNf1C1T1liu/Z88OLAPXPsKdFzRNc1PUyhemC9kys9mUV7/6NfnWW29Ni0zrUh2AQFBXGp8jkum0InRb+PG7sJOfhHBxGeElngwJQ7qNx/D73gOzs0hVYTjRQXOCi9QOd9e0ikJt6UB+0Rd9EZPpBLO0TaMQIGi4UdDXv+0//bdljrwPWAaufcKP/cRbVYN+aYxh6KX06wIu4t51iVhX+dPf9KaUFvLLPT0ixguXPRzHxAgImGCi1CEgpz5Juu99xFgjgHph2bsGcu8BoVnBY//akZCWBYnJDfUyOIxTdL4o5hHigl5rqoXb9ksX2+2+HVQyYCK4sNCzpkgxl9HoCDiJTjJZIsFrYlZUBBk2tPf9EdXxuwh1hVvAU4urIHpx1VrYOYchBLIZq2trvP4Nb2A6nZWtdSeE7SXlqoi8WmRZlNwPLA/iPqGqq0EzaD6/HjZBRFxEyJYRhOxGl5J/8Z/9kixBp7vJpxdXSHWyGFkCwaS8nw7xrVN0d7+bbv1R8nCEiCGWye5kEVwSqAEJ6BAy6o6641aCoKNF9nNx56vg22xLv+arUN124dnZEN8OTlLkZTyDpfJyKz+VgEmkQ3A1VB1xwy1haqS6Yrr5MPbx/0HYfIwUVsBqKsvkqEWX65KUZX3bD6BNHa99/etYObRGdkNEsX6bBKeumyZW8SVVVS3rXPuAZeDaJ6jqSozhdi1qDWWqTcTxks+0bcudd96ZX/SiF6Wu64gxbrfRLwgXNCuqCRNDDFoi83pAfugDzP74v5FmY7p6Ba8G1KljmOZUCjkmXBNIxulwSbg4roJrxKUmqWPBIZRAIYCJk9XJ15gy6SHiGsmiJCjjNijWjzxHAsEFMQXXMkPoAXMhuZMcpBswsIogM7p6hg0DljLzP/510n3vp6sGJKkQA4kC0iJ5EcyfGiXbMlSVlBKY8RVf8eVMxuNtvfntwr0IKtoEDZ8iIseuxrG73rEMXPsF909V1cOLpYGbuVv5EkKgSx2T8cS+8iv/XEo5zYt12UXmFCkGpYr22YKi6iBOrkYM5ucYvuc/EH/v38PmOqwcQ+sV1BvMhliOQIVQ4UR8sVxUw7Qj6xxXK0KH5ngyyFZcb1TgGgeubF7otaIIWpKg7YTQSwDzsvQLogSR7S6rSiZGw7TB0hDhMDK4GSYT9F1vp3rXv6PeOEEnDe4ZVcNDwC2Vz7pELf9FvfLGm27i0z7tFbRt22/fon7ZuzGpoiK3uvlLf/lXf3NZ59ojlrySfcBP//zbxN2/qqqqpmRcviAwuuO9gEOph7zpTW/KR48eSxvr682RI0do2/bClIji5oAk8LrGpaPKmeQ1xCGyeZz0R/8ROfdx8ks+l/C8N1KtPYcQIqmbkLtJMREMAe816z1nAk4jSouVZVdf1xLV4pBNxlJGrmX0skQQLU0MHHd7XDdP6TCVhSZpeRDgRAwLkGJFUweESLt5mvTh/4Hc9Zv4x95F2HoMHRwhZaVmiodI56Fkb1oCzoWwKMrnnJjP53zFV/05YoxMJpO+m9w7ZXvv2yiKw4q5v1LgtygluiUuE8vAtQ+oqzqo6mfUVR0WrSRB3NxdULdiKSZd6jh8+LB9/ud9fn7b2/5jKyL1xZaKXpxL0RRwAtlmDOkgRyZhQBgFtNtEPvLfSQ99GL/5d2iOPZd40/PIt72YcPOnIvUIM4Gc8ZRQbwnWETCiCKCYCEZflAfEgGwQrl3gKoPOZdllljEvla3tgWa8bLcEXCpEQ3kIqCLdnHDiQezkR5gev5fu5IPoyY8h6w+RQoMfvpHaBlTtDNWOGQJeEyWWJflFzsvOsLyTUuLLvuzLmM/npVuMlOaIlq+lVwN1VVVd171ERAbA+IoevOscy8C1DxDVNQ3hthBjadhtLwTcpc8Y3NxFhcl04l/xFV+R3v72t6XpdFovlCOeEn2nz7RkReoBFQjqzCnF6BhHVDZkuLGFnfk9Oo3klUNUh4+ia7fih27HbvpUuO1l6E3PR1aPkjzTpRmhnSIaQITULxkDECRwrRVzXKR0OxGMULZT+8wRp1YvWafWSO5g/Tjp+F20xz+On36UcOY4tvUwPn6MKnVIaLCwRggwx+ksEUtxC6xMNPiiIXARlGwrM5/PeclLXsLzn/981tfXd6YhtkdIy/krprM6UNWXuPsxloFrT1gGrn1Azvbqpm7Wds+sCeKeHVf33tpdBKGdt/6c5zyne8Mb3hg/+MEPUFUX1pgrF7yT1cAz0QfMvcOlZSRFpcAkkrTBSGjIqCTyfIPu4Q1mdg+hjtSjEYyO0I6OkY88F73x+VRHbiPf+nLqtSOE4QoiQkoZsVwCQWfXtrGoAUNBlKCB2JNEUzLa+YT20Y/QnXmY9Ng9yJn7qbZO4OPT5OkYTy3ZjCxKXVVIiGSPZd7TW5rU9UtNSFYjXiGa6DxR913WC+27mfWigR1f/dVfTdu2O1JF5iAB9x0jk23OnnOnmb3kF37x1x7+i3/+i5YT8peJZeDaI976b38pqOpXaO+5roUt7Qilq8g2y9rdnOwZs5y//uu/rv2bf/Nds7quBnXdbBNSQwjM5/PiEtPPvhWn0lBMTCVh7iz6KmXx5Lh3mDhORMTREHCJODWZTJpsoVtn8XwPHv4YGxwiDVYJzRqzagiHbiDccAdy7LnY0TvQI7dTrx6hrY4hUsxTsYzlBDn3WUnGre23Ypu51ics3ncwdzhRsvjNXrPdqUACqCAhoDHiYVFjc1AnT7fw9eNw5iHszENw+hHy2eOkyTptO8Zm6zA5h3ZjEo5oVUZ2Yl2Ow6L+tQhD3lGOXkAwrF/yqZR9CosGhgjWc7QWtJXd0jXumbadMxoN+Iqv+DLWt8aPk7fBM6pl6qEwOjJFs96PgL8G+G2W0h6XjWXg2iNUdRCr+OoQVPGdLroXjo97z4daPHFTSpJz4jM/80122223duPxeADlCb7gBDVNg3upnfSfwaLF5yxMZpVt1WZnlya6ls+jVz/wwtey0IDWaCx8L2Yb+PQcrVKyLA3UwxU0Dum0YV6vMW9GVMMVZLCKHroRP3wLduhmfO0mZOUoDA/D6i0lQAiIGL2SFY6hKROsLPWQounuEihcWielCdZOYLyObp7BN0/i6ydh8zQ2HWPnHsC6Gd5O8W5MaidI/zeWOoIUSomEiNQrheaBAlrGdty2jxr0goDbRhc7EsxalP374xgKF8yMGCNd123bje1WOFUVuq7lL/7Fr+HwkSM88tgJQghUVdWPce08XLabNQJ1XQ3mc15ZWMFcbN5riafAMnDtEeZ+FLg5lCL2Qg1lu7AhIp7dCKWrRNd1hBBYWVm1v/SX/lL7Qz/0Q7Njx8JgdXWVyWTytJUjLgaRwiQXeq4pIKpI0D64VYgaASPkhHXrSJdJfpwswiwENCgSy3LLVXENSAgErah1CDHQNSN8sEJuVrB6hFU1Gkeo1Hg3h3YK7Rifb0E3xdOMMN+C1JbB5pwQL6quluZYzuhMwArvDAUJoQwAhBEaiqjfNkcV3c70ZB8SGTPb1kxbcLXcnbou/NHpdMx8Pucbv/EbmU2nhe7QP3gWbPonnwtBVUca5FPdvAGWDkCXiWXg2iPc/SWCHFHVJ5VEvKwrSjDK5o5LjNFjjJw9e9a/7uv+5/yzP/uznbsPNjY2GA6H21mWiBBjEQ+8+DzjU2N3t6AsWQXbNSOplBsON5JlHEHrmgrFRcg+QyQjOaMdYBnMi30XwqSfyVQJiITCuZKyEHP1snx1AyujO77rq4puL+JcBEL/mYC5sNZoz9fykslqKl+9vK8x6Jdx23vI47KrPWD3APxuQ5PFkPzW1hZf+qVfygte8AJOnDxJ0zQsTFEu1HApDyO/2eE5wLm9beWzF0sC6h7wb//Dr6qqfkGMYfi47KivcywsFlTVNSju7qPRyB18Op1y6NAh+1//1/+1O3v2zLSqqu1sqyxN4nYBeO+wne3qsy7zEktCa1TJiOYErCwvtcPjjKwTBl7ReEVNjWqEWON1TWoarKmoKohRCAGCGhEjmBEtIzmRc4dbLgszhToqgyoybGpsEGkHSm4CXkdUI1EqGhoG3rDZOFsDZ1LDvFJaIm41nhskDTBnkdZCT4Momc/e+Z2LWtUiUC1mShff77qOb/u2b2Nzc7OMfe4a3zpfprwt66yKIIdwf+0v/eqvL++/y8Qy49oDqioOgsbPrKoquG8XTx7/qHVIqaOqKgckxoiAV1Xk1KlT/lf+ytekX/iFX+xOnToZzGybHuGecWdbu+tysfB0hMXXnlzp5YafDqwnbipiFXhEXZEkqEGSXjqnl3opxEovhFUUKEsnWxTjZYd+G8TLoHNPwDUR8nbxHsQWTtxl4Ntl0YVzCE4zKwPkrouUqjQnihyfk73atV+7Kkr7oAq7KLTHGLcbJiJC0zQcP36cr/mar+G5z30uJ06c2Jbi7rqO0C8rd+t07ZyL7cA2NOPlWtjKywL9ZWAZ8fcAEV0RlVtCLAXnJ8r9Lgrz5u6LG0FVfTHnBk7TNPnbv/3bu7Nnz3UppT5jCL2O035kDovCfhmoLv9XRmMUQx1IBl0LuUNyi1hLlJYmduTQkjWRNZNEyETMB5iPSF7TakerHV3oSFp+3+Icr2ZlTrKf23PAKJ1O05qsNYuAtR3JbFE7L6TYKBCl315TxBXzgFko1AbPiFv/NW9nqJc2vH5xLGS1F/WqGAOnT5/m0KFDvPnNb+bUqVPbfK6m6TvDqk953hbbVMWqUtVPEZHlwPVlYplx7QFm/iIVX1HUbZfzi6rS22CI415XFZaNEII3TYO5SQlkKuPx2N/4xjfaX/tr39y+9a1vlRDCKIRAXdfM5/OLyt5cDOXJX/7bF0oLu8Zm6lTm6krBvcLcSRitlxpUsPL53ncLReeFGiGL2lno448WiZw+6IhrUbXQhTBfccdRSb2MjtPpbunpBfFT+/eApLs1rXpdCF/4U3r/1PU+NkrPYpCd99sjCy3GiIgwm816fwBjc3OTt7zlLdvLvsU5Ei3BbRE8dy8Xd2dfZehaByJyB7DKkoh6WVhmXHuABvlijdoURRiVoGE310ekn6I2y5KsYzhsEDcsZ+q6QkRJXSdmlv/aX/uW9LKXvXw+Ho+nAGa5LwhDuTlLC353x/F8y5Enoqxgy0ukX3LpQsIGsoKFwhg3y+CZ4E5EUOtpAuTtzuRChcG9DG279z8RMC1E2ayZrF2xANv1uaXwrmRRspauZnDvX0YQJ0imCMvkkid6rzXWfw2UAKlelCK8J6iiTxRlvJTjsvPfT6xRlYmHQEqFFjGfzzlz5gxf8zVfwxd+4Z9hvLW5Xbfquo4owmAweMrzsbvAr0HIlm5LuXvuL/7qry8Hri8Dy8B1mXjHr/x/EmN4naqGHTrVrot21+Xobmj/hN75qWzfPP3yw37gB34gHzp0uNvY2JjnbNtdLFjIPKfttvt8Pr/4gPbTxs727xJieALkCa/zv48valdP8SkXFkd+6r/dL4QQt4NVVdWPkxkqLPjFzwLT6RQz44UvfCF/62/9rf7/d1RsoZzjRT3youekpI4r7n6Huy/vwcvA8qBdLtwrwW/t29vl9eR7rSQpXuokGnT7qeu92JyIsLKywng89ttuuy3/8A//cDcajbrpdNp2XUddNwAsHIKgZASDwYDBYLBPXcdnHxbmvCEEUkrMZjPMjBACw+GwDz62XWjvuo4f+ZEf4ejRw9tZ1a66JWa2TYO4eG3SodS37mB5D14WlgftMmHmR938iGxXWs4PASm27LEsJbeD2bZSnZgl5vMJGxvn7BWveHn+F//ih1pVuvl81o7HW4iwbWsmItvZ1n4UoJ/NWMyJppSo63qbcDqdThEpGddkMmF9fZ2f+Zl/zW233cJ4PN7m2u32DciLwHUJmaIDqqES9HkiuqwzXwaWgesy4WavRFgBe1y95LwQpKoqKXZlpf7lC44A0LZzGQyGIiKysbFhr3nNq9NP/uS/am+88cZ2Op3Mc7bHFX1DKPIts9nsyu/odYrFsjCltE15aNu2NCx6dvx4PGYymfATP/ETvPrVr+4DmjyOFLyTQffeihoumgULQlXVUYTnCzRXfGevQywD12XgHb/0axJC+DxVrUSfVBF+0iNXRLc7VLsyre2Lv6pqQgi+tbUlIiLz+dxf/vKXp5/4iR9vX/Oa17SnT5+ep5QwM+bzOVBuvJWVlX2ucT17sFB22D3YvghiKSXOnTuHqvKzP/vTfNZnfSabm5tslwWkBKad+lY/UlUy64tnwkXKWUFuFdG1K7un1yeWV/1lIAQNMYRXBAlhMdLDBVaLQUUerzLQ/2CXesTGxobceOMNpNTJeDyRyWTst9xyS/7hH/7h9q/9tW+Zj8db08UM3KJoP5vNlsvFy8Qia11kXlVVMRwO2draYjwec/vtt/NzP/czvPa1r93ObFX//+29a7As13Ue9q21d/fMOee+CIIASVAwRfAlIjAlRbJN23zKJF4EQYJkIpctWeWSbEtK4krMVKqSquRPqlIp/cjDZmQlUsksRX6UzBdsSXwgJGXKFmiSFkVSFE1SIik+AIG4uPeex0x3773Xyo+9d3fP65yZe8+9wL2nP9TFOWemp7unZ/bX6/GttRi7e7ttETzQKebn41yHITZD5BEUt6jqrQ//5ieGzOKGGIjrMsDEJTPfAkqdt+YyZPNfWyLOlhGpKhHFmpRMeAqPre0RDib7MNbAFkyNq6mupzDWyN//+/+Ve/e7f7G69dZb9y5evFhNp1Mwp5HvR7mpA5ai3wLaOQfnHHZ3d1HXNV71qlfhV37ll/Hyl78cIcRBr957+NBgNCohQRaIK7uLRayMOBLJxTxNoNvz92HA+hgCg5eBILpDwLPK0QiKQPP5RFVJiSMiEdHT21ukPqAoLNRwFp/GWAgRSC1UAIKhyENR4xQCMJ1UcI2XV73qL+p73vOr+LVf+zV53/veG3b3LlljzGhUbkEEadx71iShtQCiLKEVPoIpDp1QodjtNLa/ahdefyGuoxO7EhxpmfS88L5b1v2k1B456vLzdlHrxogt0rKIlZFDT6mtfqt6D8HBe4+trS28613/Dd7+9rdDRHBwcNDGq4gIwSuYLEQAYguka5Xdy8JaCHjGGuhf1+59aE64jEX1djqOEokThoG4LgOqeBYI2wpVynlD7QppASgxtcW3nNySrmeB5sxiJwGbFUX1/laK8ReHoijxrnf9A/mJn/jr7h//0v8dPvzhD/u6cgURSsBCVaKIFEnpTgJDUegaF08kKyBRIxFUfCpbnCWJdZfSlRDbUeu1v9hnF35+LFYFiCTFPRGIDKzNBdIeRDF54j1gTQlihkgD713ba6soCrzuda/Dz/3cz+H222/HZDJpM4fLjnsUoRN33VOXbxdzj0RUQvU2mjfZBxyJgbguAyL6UmaMEr0kOXosPOnrePKXNnYz7YtPO2Q9V/5z/vm4sEp479A0tX7nO4/Rc597K37hF35Bf+Zn/k74h//w3fSvHv7tcns7yyU49oqneB4SssXALSG1pT8gsOH2sJ1loIkMZkSzC++r/9jl4KjXLjvePJnlQuj8nARB8LE8iS2irEGA8XiM4BXBe/jgAShOnzmNH/6hH8Lf+ls/hTvvvLN1FwHMZAaXWX7LzjVfP8MMN1drukB20VIrQtM8DyCDOLl3wJoYiGtDfODhjxGAHyTAxJKe7KbMfKFTkikGbSM5xIziIW5YJq0Z8gpB4FwFZiLm2M55b/8ACtFz587qubM36amds9jaGsP5Bk1TIcsnOA15zd0mYuF2OkGKfbSi29pZWdH16t5Lv9h7WbeDKyGudV3FVZ0WmJGszNwyOVo7hmP76iAuxQAJddNAQmzkeNNNZ/D6N7wWP/7j/zl++Id/EJcu7uHixUutiz3fh+uw88slWP1zNcbCyWJz0/Y6dh+xIeBWPO0TLK8/DMS1IZiZCLgD6JSn8WsYy4BFBYZtq+0y1iApJmiZ2zGXH1kipSCMRmXSHAViZlhj4LzHZDJR5xVBLIrRaYy2Yo2j9w0m04NIRBQQvINIaBcMEUEhEFEYzj3R0wnobBypGwACZD5dZgEtwzqkdtjr+9ervy/V3DgnJKrn9j1E1zGeqkJBnFoxE2NrZweYHuCn/85P4+d//u/iYH+Cvb197B/sw4cAQtfGpl/OM39O859j/r21sK2BpsGwK68BCYipVNVnkcoYwOTIizWgxUBcGyIZJjdx7FYnCkBFQZy+pBqpKC+pwhatxie+fnbxr4iX5AcI0FSjmELMHIe2FtbGwLIXUAoYS4hHtXaMs2dGOHv2LEAek+keptMpqmkF5+o2E6kASLQlq87yakX9S63Dvvu4SYB9xfU87Nn2UvQJI7uzmfNFAlSiS25tbsgYe90XhcVoNAbAMFxgMplgOm2wu3sA76J2K5ZTxbY/se/W0a1p8nvryyH6oYGjELVfDFU9LaLnADx15IsGtBiIa0PEpaRbXQp7VZEHgTjFX4g7kyZhjaC25v0wE4XgldlQzG55SGEohABFICIBUxqAlphVJC6m7Z1zOHXqDHIMy7kGVT3FdDKJosvkWsbavZTm1/yuuuEQ85bGui7iYdutE9ifJ4qsvcquoWGTJscajEexfpNg4IPEeYwSXWRrinTXMXFaN+K1MMaiKApUVYXY1w9gjv39ZnMn8bM46lz72rDVrm7vJqZ6SlRv/+C/+vjXH3zg9YMob00MxLUhFEqqukXMpKqkvfF7OTjezygZY7LdQPPWi6oQkdH5I6S9pV3Enu1FUSAEVRGlJH1QVdKqqhUk0HahEURiRi0ED+8D4rS0tIi4wM5OiVOnnhW7HUMQgodrXKtnylZIkICmiSJXkdgnvj27uTQ/VGfe97L303HYnNWZKjj7hNgrL2gJyxgDa0ybpR2VWxiPxyjLEYyxsLaAKjCdTHHx0i6qqgazTbEwSi47YTqt4X1IBNXF6joZiW/Pd5Y4Zy3mVcTL1LUcWpWBJEISotKWqr4AraE+YB0MxLUhgiip6lgpdXhQtAsAAAxzJApKOilrc9oRwHzcZhVp5d8JFMf0wHsBUe6hrABA3im8y1nJvFCQ9h8XOnF0B/v7jgmzgBCyq8Yw5Rh2tIUtdAQyv0gzgWUrLE+0md+mR8wx1pfy/TPElOQMSAuYmcFEbQfRvnWV/3XnP9fllAi193ApLuUR4EITrcZWoMsIQaKrDULWy7WxLM36quzydfHAVcgWVj6ndtueRqxP8AuSk/i+CyiejYG4NsJAXJvDgGgr/hrv4LkrZ5sxTBuy2aQwQRd+QTdQTAFABLGVaCtfCC1JAkfFi1YcVeMh13Hb8nFWxXDmrYvOQtH2Xc0f5bDD9kkyE2R8zeFyjFnSzeeBNlvqvU9jx3rHp8hxV5Ak7Y6/4ry651PAAQpj2IqXswTa5Mty4jEQ14YQ1R0AY0W2bmZWXv62JinEulnuZaTV/1uIYvxFKZGYAuS9aPD9m/S83oiW5CkPOYulLg0tPL8uycWg/2xJ0roasH7A+zC3rHXH5o7dEmZq7dwmHkA94uJu+5QVXpYx3BzUxuFWPN2eO6Ji5RQdFUAbMIOBuDaEqj43Kp6hMZ4VWjV6eh6I0gctrAXnFbzWrhcfzEqK9k8ipRgu0xACfPBLLa5u0azPXPMLrf/nrHbyaPJZRTSbBvU7AppVzy/sf4HcaGY//f1mSy7dDDY9xyb9XD3ogrrM66EkH0m3BLCDoW54IwzEtTleSvFLpjk+1PFKD2tbXNGA6kuj5nYDIqWZhRjV+hRb3cxbXJtZDIeR0Ko1dzn7XrWPZRbdUa8/zDKcjbFlQa22LnHMuApCrwng5eBwV3CWuJYdp5/kIGCbeHAVN8FAXBuCiF5IOUqeXJD5ZZS+pGRMnm69dAHO59pXbohFQwKcF2Do4mtxkc4GjY+yt45y165E9nCUsdnKL9Y8n1WC0Hnyy5KQuS3zXuC9T5q3jjw7y25tNADK1VnD1ZZo/1AUBYDbGNTzG2Egrg3BzDdTq1TQPm0RUaq2xrqLvrMMcMiqiZZDsr5ScTShc3muFtYhrfm41+xrUpztMvfft6LWfa7L7KUAuObPgrO1Cud8Usovfz99MenlYt7iWnKkeJoxy7lFNHSE3AQDcW2Ah3/rEwTFmSjxydmyNsnXxrckqTiN4TiDcBHLrK1lzwOgJLDuJQNUFBQHPnAKeM2+WKOsYM02T1cSkD78NYsygLlXY9kl2ESYupjJpN4x+xZVIqUkdzisiHpN0iqXnatq2/lhZt8z56WKrmJMoKRbBB3W4gYYWH4DpLv2VueLaBSkJh2QIs86jV9mJoCOaPS3xNpayCyScpqzCOQanRirCXC+ayrQ04Kmbdd3864si3bYvoGc3Vv+7/LPZ5nAsyWImQwrWssrardyRUDPwsJmWdN1sEDa/c8nkbZAoNCRkhbHctATgoHlN0CyD2y7rNq751LL4Yhv//qLI9/FFdCeeaUiQhIWi4E3Ia0bDZ0llv9e3MY7l8Sh3WMbXK3V2cQ1MWPhRZFriWFoxkYYLK4NwNH9snOaG2XM3OCBXnDnuCwZpsVuc957jb2l5nEySSuDsuJ05rHOZfQ+tKr2hdcd5zlk6IrHgSS1Uwtg/Fsf+Tcn+4PbAANxbQqNJb3x9xjT6j9N3BU68/KFcJipdaQZRrEESKOr6BG8p2Wu1UlbAave/6JMIvXQmmv01ye64yCwGfX+UZUJqkYxuIqbYCCujRCVzus5edT/5fAI9hoHXub+eR/g/ZLythPiKq6SR6RnW+nBfPA9SGj7/bd1hMd5XhttS7FBj8KuM0x2QMRAXBsgxpk0E1E/ULECR6TcDv97fl9ILSbaQ4YQOzjMq+Q7a+PwPd4oWObyzXhqc1nTXPIzY5Ud88Va22ojAKrQ3A1xwFoYiGtjzEZ0c50b9S2sjlv6X+Bj8D/azCZAsX2NiizIIQYscxF7xOVy3/ne9u1v15w8mjYbfOIc/MvHQFwbgLL8M+f42xjGkk0TrnwZxD2EYFLrL4VqGgrrA1wjM9tFOUBq33ICCG0ZMS22kMli0AAiTYSP9jVxPmXnYl4xjrjurVyGGW3vHe210BhwJAbi2gTU/piLyuvM8zNYbf6vs0LaQDyBgdwEIU03i51LpRVX9g85H9u50bE8+J2lKrPC0jhMZK7CYc2uFVd2Piu2BURVw8Bb62Mgrk2gQPpKzomF4l9r7oVmfqyzOcXundFFjAcTAXwI8N4vlV2cJNKaD75nLKsIUFW4Vse10mK+JkjWomrs/SNDiGt9DMS1GSJzqGq+S8/dWeckXkez0xJ51iyivLtXIsxZi4rgA7z3cySlR3kqNyz6nUZXERgRpenVV7fZ6FplS912QYEwuIrrYyCuDZC+VkHmvmEz9Tr5C7tGznFdklvRekaDhHbi8oBZrCKv2FM+tpRun5+Jia3XDfYoLPvMFHMWYKz9AYE8Ae4kWclXioG4NoICgIeISjTxAfQj8Zqy24vC1MNw1Pc1heRn41iEZDnMW1xxhydpERzmJi+rZZzvld/HsbhrOmv9ze6/TSF250rUgMi9+Z7XDSbXmhiIayOQgqgGkQCaO58qAAh0qeU1F9Ba9fMo5JbNCsSsWFwcgcKSWsWTihkSWhL3WtBxPY2XrWd9EREcEQbTeQMMxLUB0l30knYrpLdS2l9n5Y9HR7ni/1Zvp3PE1LZVEJmzwk5gdPdySDuE0HXbuCpY83OgVGJN5IgoPPLxR/HIxx+9Wid1Q2Egrg3w5nterSL+TxVeCIG6tsCapPS9Cc+I4lAAq8hrWYBr6ZYMANRAFTCmAEygtiEKzbpBUayR5jnS2r2lrmv03bIZjRRmyTwOybVwLsC5aOC05EWbJTXmA/7zz4UQoj4s9f2a3U6hkDh/UQisVJPSYHFtgIG4NoSI/IEE6SYSciYIgND13mLEqTFL0obzy2M+E7mAzu3sB5sJEkCSyXF+pyfAfTy0p/2hr0vzHldvceSxlwX++4+v6u2lqmDDsNbApwG8AE0I5I486IAWQz+uDWEMf5XIeCKQiCojJ4eitdN+oYkgQTaI9hL1Oj/PIPXU7CwKUgQBOX946+aTYnGti/zZZOHucWDp9V113XNaOgSoxmHBTAJxMiVQs/iCAaswWFybYzcE30RWmOOlHD6Pbgv5EFqLaM7wWhKcn9W0ziNmnxAlZEmGKqIzLW1OKklt9r7jsIzjlJHMlx0RMOMittvkwodeRjHJmSeqOkyx3gCDxbUhmNkRwUFV553AJKxv4y3SeZQA4q9dHnLmldR7lOa2IEL/Dp7KWFRpZuz7icX6pTqZ5GNW8fiIq2/ZqipEdSlxzZ5nlGUwM9jy9Cgd8oBZDBbXhgghBOe9IyxMKtZWbaUCYlIRVaJUWZhAS39t/87WWe+x+EdaCIS0/iQIJCzGt+Ldv1ukNzqxLffUVminEK9tlkMcJ/rH7Hrar5jCnf40xsAHr975WkWH8WQbYCCuDUHMathOm6ZR1RlOQtRaEYg4yRVC7ifRShjShkceZuYXAkUXkcCGARBCEHLB3disdAU4jLD7AtQrPcb8zSGOjAtHu68KuKaBDz4w83fj5zpgXQxXa0Mkk2di2Aj67QfST865P+0Gts6bBb18FDBrkHXbdLuZcRzTNDKSEOAbD1Utu9ccJqq4sbFgZWE2Xth/vt8d4kqhc0FOUV078M+GUdjCWWsfX3eU3ICIgbg2hZKS4rtQWnbLJkBhrQVA8Ao4BSTG8VvBaPyyzxdntz8XXEgFwRgTzTlEvZiKQIOQ4c7D6CvEjVnUMd1o6L/fxQxqV26TB7zGUWQGTdOgcQ2WyR6yq30Usk6s32UidoMmuODb54+AEtOEmM4TdYK8G929Pw4MxLUhVKAi+LKqLI3uZhckLyY2aSqyLggcN2SUHNmPv4ikDgc3Li9dEaJlHH9fIO82S5I71y42IzwKOfjeJyhjGCEEeOfWvWEoEe0R0QWgK/kZiOtoDMS1Id7y5tepqn5RZxWMXUyKGFVVQURQGIumboCupnHZt3nZ4/MSivS3Ul4Q3qc4TV9QMbNYhi8/sPyaELL33kpMcrHz2gmNbOFlay7HzeLx1k6KCIDHAewCaC145iFOfxQG4roMEOFbqhr65j1aciEURYm6qrG7u0vee2KmvgvYJ6plRDar8erWQI8cCSJCPsTRZH3E8qOBtJZ73ekZIoQgvTIhtFbxuujLH/J+Ysschaisa3E5AF8HsIcecZkhUH8khit0GWDip0Col3w5s2gH460xRltjTCYTVFU9+/zRaOX30SZoia8lR5EACbIwu5FSifeJdzd612D+c2qvXxpRlnEU1fSziPOWWfyb4bzbRJVfAfgqgOnscYZleRSGK3QZYGMOmMxuFGwtPJ0K/hnWWBjDxMZQCIF6nW7WYZU2RdjKLmYyYwEigbpBEIOb2Ec/xrXwXCtCDbNX6rDaxxU765NjVU3hnINdLzDvARwA+BKi5TVgAwzEdRkgooqYnpB4y+7azmTbCArVKA6NRdhL4xZrEJh2OwaobxJIEPgQsMxOOOnGVofVF8L32jcfVTK1jvWaaw/t+vEpAXAR0VWcTTEPn9+RGIjrMhCCuBDC50IIQZVAhgBWEHfBXgAgIhIRBC/JfVu6u6O+pjQTElOAiRFEYIyhZTs9iW7ioZ0illwPCaEtueltuXL7o8DGwFgLUQGnlkJHIAB4DMD3Fs7tkML5AREDcV0GHrj/NRpCeERVaqIYi6XUBwuYTZEzM+qmgS7XmWYcYn3lMfJRYESI1puIovaOkLJaQJfpWu4+3ng4nBx6WY12u66QPYtQiWIPegmLQzaOPkaHHNv3Il3R6Yp4WIIH8MeIgfm5fd3Yn9txYCCuywQxvsZMtaro4b2dQBKEcpoch8gfsJzAci+BpCGLWawggWKHg2XFeifT6lqFRSLoJv30y386IelsgfQ6IAKcdyuOtxQNgK8gBuhncFyq/hsZA3FdJgzzrrHmQKGyYEzN/S2aB7fSkmcBrCay3vPRFc1F29FKWN4dYqAsHEreRNTOVmTm9h/Q03Rhtl3NUVAAdVVvkhGcIGYUFyowRIYON0dhaGtzmSCmKRTfEQ3PtaawvW6CQP5Nc294QQiBAKvJnZvVaq3Wc2n3kzSEQC1ZxfbA1HeJutFoN35XiKNwWFYR6A/MoNbyWkX5y6b1zD8XRNE4B8O8zmTXCsBTAP5EFZJ3/fM/+7fxrT/95iZyshOLweK6TNx/z2td4/37vPd1/5u6PFhFcK5BCLHx35Iv5qoY14ILKQpC0iHFYbDLX3TSietoAWpUufsg8C5cMVmErAs7RArRu8E4Vf2mqj5GvVzxxQtPwa2ooRwwi4G4rgBE+DCgB6mmZ2X4nYngnEds/GewYScAiq1ShAwzjInGXAiBQuh0XBm9WNrJRsriLnP3oo4rZRUBbDooY35fIELTpM7Lh1hneXsRqUX0i6raBuZ/6if+M+zt7cH7NVriDBiI60rAzAfGmD3RQ6OpSswIIbcLlllB1qxpsNRMiBaCx2RaUVU1lBp/YVVwflDOZ8xeg37LZO9da3WprNcRIu5jttQnvl5Q1007JTsfY/U+cCCiv99v13zx4oVEfgNprYMhxnUFYOJaRb+qQb8fDCZiqCopYr1gkBj8BQHEjGk9xc7pUxC/dKZfjmnl39H9TWRtAdfUODjYw87pbfLOk0hAWY7ihjNdPvlE3LWXTdBpJSG9xztZRP8xhgjQNA7ez7bYXnWs+aB9LrB2QdpxZEQEUYBSj/lZMjNQkRrQx4nosz/5N96uzjlMpxM0TdN2TR1wNAaL6wogQbxI+KCIVjFWoYC2d1zNQzMUAJip8Z5ir6alcS5g0eKKacgYgoe1FkVhiYgwmU7IOU9lWRylFzqxWLwWna4reIFrPJxr8gylI/e3jFQUQOP6FTuULLjuWJRLTWOTwRCC/Imqfu/ixQvY399DXdcDaW2IgbiuAG958+sVRP8W0Auq0BBEFQROrZuBWZfCMGNyMKF8p6ZeZHZukdHcv3ZE2Wi0haIoUFcNqqpOi2T+C79Zb6kbFgtvv7tO2cIh4oVC9Y2girquYYzp3EgAxH1Lj8AclcqNbyYi8tkQpGka18Y+B9LaDANxXSkUF0T12yF4SUJU7bc6SdukL69BVVWzSneZCebOkFX/MSaGMUTeO0ADifj2Tr2IGDA+2YuBWitqaXcIFVhrYCxnk3YjZN2Xcz4NdUU6Vv48YrhAgkIl714bAr4Lok8SkTt6MO2AVRiI6wrx4ANv8CGEjzvnRQHNMY2ZmAjaeAgFCWicI2tjMW5ndC20TOmsLQU0JS1FlaKgNaysacvrcBMB5Q2HIxIU3oe2J9flCj6JKLWATkXWyerKcTZQ1+tLlaDQIKpfZKKv3H/Pq0/oB3M8GIjrGEDEHwJQE0gAaBpS1soeKCIGdImoTv25VJcV+XYGV5/EVCmXKkKCkHOeVLVcLuQ6mbGumf5YR2znnYNz7rKaCLb78B6uaWYJC1mGIakrSJzKpKrwzlWAfhrA/uW8vwEdBuI6BlhrvsNsnlTVkMhpVuAAQEQoN5urmzpZTNprd3PYwiEYQ8RMkbR8gKhEi2vZgmtd04XZjycIywZodG5jEIH3HsaY2HGUAFCWOqx3hOyq5+ucB/Tmf5nMkmtaq+JJw/wpoqWDVgZsgIG4jgGqOhEJv+u9dyKizDHgm0t7iKmtiwNAIQRybrHt8goQEZL7GcmIKXWzWdk/KkZ4TnTQlw5TOMRaxel0GqclqaZYU+7EcfTnQkSo67olKGNMqmSg2HqIOXWgiO588N4B+DIRf/m+uwc38UoxENcx4ME3v0Gg+s8IWknwEImiUwkBCEIQnck6gRhV4+CCAty5GYBqHCY6G7vqux6qgATAu1iusgzMJmqJktV1OGYTmEQc1f20aK0prk7Llb6oc/58+5bLZscm5K93fl20jBAzfEGRh4PH+CMBaqBKyPN702fSWlJ5X9Za7B1MAGNBJkoh221UoRAEDbCGU4BSICFMNcijKrJ7+VdqQMZAXMcEZvoagCcBEhVAU38nNtSqIVVjrSKIqfEOzvvWLFg9Hqv/eLydK3pDTZdYB3Hxrxj/voBuzmNql4MQ3NJpzFerBrKvP5s/ZiaETUhLJM6Qi7q3AsaY9vEM1zi4xnVWaSqVSomQmbHj/eNnl7BKbmK/oDqTLvVmOiJ//IrHmc3H7r/3tYObeAwYiOuYoKoHEuRRVWlEgsTOA8ijyebu+gTvPDnnyAefYiCdhGJWoZ0IDx1HSQgxsCyyEBojAthE3dC6yO4RMyHWQ5prHh/L12c+UzoTcF/zfOINg6EqLQHn8p7cwsZ5B+ddSzyU3W9EDstZ2WXxqtwSpxtHNku+eR+SdiTip6L6KQBfvszLM2AOA3EdEx584K85EL1HRC8hBUsIpJl8MB+uJ8AHj2paETMrJ4E9EencHT77LUBqbxMtroAgsmBwETGgm7f/jbIAaa20p0NKcZzKfyaCLQyYoyuYra58+UMIUNFWfNpTnyTmmrWyMkQEVVUtPd/uugFt+aoCjfN7AD5KREM28ZgwENcxgoj+SFS+rghiDKuxnESmEmmMYg96IkJhC1JVOjg4yK89xF3SBcsjBK8SZr0OIoKxBna9nufLzv9YyePpBBvTdjf1vlOnh+ABJUiQVl9HbRZ4tp9ZJnMi6uoQE3HNW6TzJBeHpBCC+DqE8PvM9Dv33vPqpdPPB2yOgbiOEQ8+8GNORB8OQZyoSGqN2lpYPSuG8kLw3qNpHIUgbdGu5o3mg+Oah26QAouF1G1x8RXM5btxRKs5Q8gLVmTO8nYCVGlLp1qXsXcN8uMioVcqtJzco5tqQBSzjHVVTxT6UYU+eY3e+InAQFzHDCb+kKo+4b0PEmYV9AmtGFVViZlpOp1Q7GaaYiPRwlLq6rMRn0vlwD1rYF5qGYKgaZq2j/r1gKth5cUbgV3IUub4lYtzKbvA4cJNQhcsqqZxqOu6vcEsex/t9qJQQa3QbxLzR+6/93XXzwdyHWAgrmOGqDzmffhtFXVJSK09S2pGywVV2MJiOq2SK5M6TKBbBDLbK0qRmqYQqS4bqiAicM5BwvVhNXUB8eM9XxVJzQKXFKATxamG6SaQtXFtReGSREm+rlm0uvSY6XXZLRWVCsSfIOKvHuubGzAQ13HjoQff6Jnx6yG4p4IEaUBRTp8wV4Adu5gWBfYmU5o2jrgolZljjyjETsCMrPZW+KAQBbgoYOzyO3/wAUH8deHytSU3xwxRSSU9y5IUgtHYRsW8RsJSCBQOIAGTAbNpe9IbY1A1DpO6AYyFsunISQS2KGJJj/ex/1aQLIC9wEQfJ6bm2N/gCcdAXFcBqvoN5/wjdd3UxhhdHQ5pW6pQCAGNa5BjXTEAL+3UDSJSay1MFDzqqBypNaZZVioUF5Queyof+IYJwq/GXM1VD6qKoigxHo+TZCKSGzODkCYoJR0YM8O5qLnrW19EhLIsoaqoplMAaDVj1hqQalM37rMg/nf33/2aoQXEMWMgrquAt7/17oqY/neRcAkS2trBbGXl7YiIiLpuUK5xcK5p41vR3VCEeHenaJ0IggYaj0cYb42XHl9joGxGRtnH9U5a65x7JJ/l/dtVFdvbY5Rlkeo9Y5mOaq4eoNatV439trx3M1nfGbJLVpmItMH7xjV7BLyXgAvH+uYHABiI66qBgO9C9UveNwEQbVPubY4R1KXQmaw1FEKgaVVRdlHafRGnWFdqbRMCtre3ce7sOV21MIMoVrXCz0r5Gx3Br67VPHfuXEtcMWbFbWaxP2fROYeqqltJSo6JEcUBGX3LK8suAK2Dd58zhn/z/nteMwTlrwIG4rpKeMfb7hUAvxR82BPRAED7KuxefV7SdjGBQE1VU9M0BADGGCUQjLHItYPMBlDVM6dP67NvvkndTNvgDmkBrbQ4rlZs6ZmAaHGufm8hBJx71jmcPXtmRnSbJybljg8hBFRVjRB8NwQgxSeLomiV81krVpYliqJA0zS7AD6gvSk+A44XA3FdRRDRZ4L3n/PeBeml+bLMoU9e/ULjpqnhfQquE0FEIb2iuCCKU2dO66233CKrJsOEnFFbQU43KmkBSUtlzdL3mC2lm2++Cc+66Wx0vUPuvGGSdRvJzKWeXRn9rGHu9pGL5/Pvzrmpc/5REP3Gm+993Y17kZ9mDMR1FaGiu6r4Fe/DXgiiq1y3dHcnAshaQ3XdzGi7kJhJNXaPCN7h3Jmz+uybny3ZqprXHGUr4Abmp5XIRdPLMooigp2dHdx2222w1sz00EqvBnNsWRNlKmFGt5V/z1N98vGstXDOoa6bp0D868xmEJxeRQzEdRXxjofuVSL6vRDkiyEEh5R8B8XRijmO0g3PYDAbUlWq64bruiEfQqq3SxktjdjZ2cZtz3++bm9vNcvU7lF35CE3YE/zo6xFZo7lPCa2sSF0QXdRxW0veD5uueWWdvhqjlnlDKNqHKbhnEMqYVggLk6xruwuJkuuFpF/Z435yBDburoYiOuqg3ZV5f/03u3FIiCjpLH/ExuGpNHt6e5NQQRkC4IxmDQ17e7v0bSeksKDDdLgBUZwjb7gtlv15S+7Q70PS11C7x2CXyzEvtEhElKgPZN27DNmiTCtprj9jufjBS+4BcHHSoQQPHItu3MOly5dQu08YAzABgIC8Wz8y3KsWwQZEBdoXID3/kki/JpIuPg0vfUTg4G4rjLe8dA9ysSfkhD+wHvnRUSDCGxh4J3vtW5ukXKP0XkJXmh/b58mBxUFryiKrnHdC17wfHnxHXeIOr8Q5VJVBB/gXLNQpH2jolPBM7wLUMmXUqDq4UPAudE27vpzL8Utz74lCVRjk8cQAvb29rC3t7dWZ42Q4lxlUYIJqKrpgSr+NRF97IH7Xn8CHfRri4G4rgHe8dA9FYB/5J17yvlGmEhFg2al9QpQUnpBhKiuK9rf36PptKIQAoUQsLOzrd//ojvk7Llz+QUzCEFQTSu4poEu7zl4wyCT1nQywcHBBFV29bQrmapCwC0vvA0vu/MHAIlWGIgwrSrs7u2hrpvYGWKdXmRsYIxB09RRFgF8jZn/0QP3vf7g6r/bAQNxXTs86r3/QPCuDuKkLOIE6jx2bAUIRGRMQQrGtG5wMJlgWtWo6inVTY1X/Ccvl7t+6K66cc3S2I/3AZODKUISUN6IlleShQKq2N3dw+RggnpazVwPBYFR4K677sQL77gdVVOj9h6TqsakquGCQJlBbKHER6rc2u4ergZBLgbv38fE//Eqvs0BPQzEdY3wzrff1zDzL7rGfdo75+u60e3tLXjvkWaXLmcUBQmUiC0RMwmUpnVFB9OKDqZTes7NN+lrX/2XZfvsNhrxS62qpnGYTiqIIE21MTcEibVBdShc41BXsQIqBFnojqFe8KLn34ZX/+h/irBFuNRMsL+/j4ODg1QCVEQXM/i1Omtk8al42W2a5neKwv4/99/7muWiugHHjoG4riHe8dC93yKm/9E599WmqYP3HtbYTlC/SF6U4spQlVgeRFHl7VzAZFLh0oVdfekdd8jr/upfqZmxVC2fm9/V0ymYgPGoxNZ4jKIoYO21b9N8uZhvpliWJWJA3WM6meLg4KDVWM1bW+XY4i/+6IvxAy+5DReeuoC9i7vtVJ4sbwB1ltQaZ4O6rvdd8J80xv7PAB4/9jc8YCUG4rrGIPAfMpn/1vvw+clk6mIrG+r1s5mbChsFj9kiI40DN4jA5JpA+/u7wBbrG1/5I/Lyc7eunMocgmAymeLgYIIQBEVZ4tT2NnZ2tjEelSgKu8GivfaIxcsW/T7ygMI1DZq6wXRaYTKZts0BMxSAV8Gdzz6Fe0MD//kvwjeA5RGITUqO5InTSN1Pj14WwYd91/hHi6L474noP9x3z2uHgPw1xEBc1xgPve1NAsKnCPgvVcK/nh7s73kvknmKidshC6Bc/ytgQyDDUWUPkDFM1jBgLD3lazxPGn3nzimcA6FeEonPjfD2Lu3j/JPn8dT589jdv4RqWqXhtAJAwAQUhUVRWMRuFNxaZNeS1NpOpACIFGVZoKqmCD5gf/8AFy5cxLe/9RiefOI89nb3omZtbo5krA9l3F5avK0JeMmjn0PxpT9GgIXYMrqV6Voxm9iHIwjyjMUsp4gfR1fLWFX1d4KE97DhvwfCF+67Z+j+cK1hn+4TOIl46K1vUgB/9P4PfvRdouGdVTX9e+Wo/D5jrFFSIpASMWnUDFGQaDEp0A2DhRIgkMAADNnvfZvfdPA9XDy9g1+qD9A4wBJmCExV0TgHHzym0ymIEXtPGUZZlhiNRhiNytinCnHEGZFprRHV2P2gn78kyhIynXn8ShFJSECqaELAxYu7aOoGElLZTcjdX2eHifXhVPHswuAnTpV47cjA1RV4fx+jC5fgz5yJQlKTrqcIOO2LmKEaWsuLQKkEyE+ChK8q8L8a5ofvu/vVQwbxacJAXE8j3vbgGy+87wMf+WVV/VJT1z9PVP/lsixPjcdb5FwAkSFVgKmnjI+dOklUCWAYY2ErBzq/S+Ib3Hd6hKll/NO9gF0pMNJcstIdN3dVJSEEeMABTe1wsD+NI8qMgU1WV46DGWvBHLVnUeEfpQTWmhhXSz3ZO4tnNh6VY0n5734P+P5jIQRIEBATvHMI3mP/YNJOnGa2gEZV/KoguiGGkOLmcYG/uVXgjTsFCCWkKUHnn0Kx+2eon70DExL1aWw8GGX2BAVB07XNA0mmk8njCnyUmf4vZv7MfXcPgy+eTgzE9TTjobe+SQD87nvf/+GviOqb66r6L0T0BcaUpigNqQgp2q5dsa2NCAEAM5MUhHL3IhUXL5GWFpYY79w6gzNa4df3PR4LBmMwiGeJYrFMSJH6GQNwoCq24WHilsyMte3YeSaGtQaj8RgSAoi7yTn5eU2Ea9igKIq29XFZlMjzJL33CMHDmNi0r64qNM6hsAW8hLgfSb3248SROINyiUiUAJBl2NEWbuESP3nTabyWpgiFh/IYUk0x/t7jwIXz8C95MRgKUgBJZa/GZiMORAYiQN00B+LD10D4P5j4N5npe/fe/eohnvU0YyCuZwje/ra7n3jv+z/8HgCfb5rmp63R1wN6MzETQdqSk35TQoWiMQ2N9y6xuTgpKwBGDLbU4N4dxmnb4Dd2gS/5GgEOI7Yx7qOrnKsOsVAZCAgIAXDOg2hWK1bXwHQ67fWqAlr3TTs3kqir8cvbinSumKT5kMxt3zFIqHvbav/Elp4vEUCmgDUGP1ga3Ht6jB/ZUuztVwC2MWIHSzWK3V2M/2wXhUsKOonuN8gAiXwZgIrWVV3/map8iIl+iYAv3DfIHZ4xGIjrGYS3v+3uAOAz7/vAR/5IRP5CXU3/O2PNXUVRbFFsiQpRoRwkJyLYcoe2dmvYi0/CCMGooGICq+LHti1ewIQPTRUfagK+5zxOwcAwtZOaN8Gy7TOpdAr1XkZPu5/9bGfcD/VKaxQhKEJYtKKOOkciQoDCEeHPjcb4sVGBe0bAc22Fi0IIUmDLEUYc4EqLUIxx+nsXMbm0h+npHcAFaAiQ5NkGDWimzRNK9AUQ/W/M/In773nNEMt6hmEgrmcgHnrrmw4AfPz9H3zkM8GHtwYvP2Ws+4GiKLasjXEviR0K1MgY4yf3aGfyPTi7jYoJlgWjJqAWwUuLAi+kEV7JwAfNFJ/1gsoTRgAM09PaCLVrhZytszVPJmc4RRCYcGY8wo+MC7weHneNCGeKEpPGgx2jVIsxe9QiODDATVtbME/+GeTJJxDO3AHSgIItFB77e/vnIfJ5JvPLhvmD99372oGwnqEYiOsZjLc9+Nf23v/w//f/quhvORce8N7/TFmWLynKoiRQjM80+0b3zqNwiumI4MnAiEHNFRxZNKQYlYLX0wivGI3wqAT8m2mNL9QNdkNASTGYfZwZwauBFHqKwXMiCAGnC8Wd1uANp0Z4pQV2GgYHgxqEAEYIgIWiNg1qZRTeI5CHPf8URpf2URUW1ARMLl266DV8AUzvZeZ/zkRP3HfvoMt6JoM2dRc22vkzVMx4PeK97/8og3C7irwVTA/ZsnhZubVdjC6eL2/9yCeKc5//fOl2CBAG6xgOHiUBrArPimAI20rYCow/VcHnnMNXG8GnK8F3XY19DTDGogCDs47sGqHNMLYaXMQgPCtYGCoGNRgVeZy1HndZix+0JV66M8LLCLiZgQNVODXYMgZAQK0NAjEsChgCAIuxa8AI0Jrx5EMPVo/fcXvj9w++JNb8C9oa/4YafuzNb3njoMk6RlwtfhmI6zrD+z7wUSMqtwnws5bpvjPnn/i+2z/6yZ1T3/h2yTsMJwzQCEwKHyawaqBs4IxA1KEUxRZHaUOtiq9Jg282Hl/yiv/oBH/iAi66AIKFZUYUBxCYslp5w+/LskRAFpYiTddJ/wmy0p0gCkwU2CHgNgO8uDR4aUF4sSHcXljcXFqc0hJOGgQbzxFeQGoBLiBQQD1IGY1VkDhsO0EgQlUU1e5tt39y7847P+V2tv9JOHXq63f/5EMDYV0FDMQ1YAb/8n0f2i5cc9fW44/9zLO+9OUfv/UPvrwzHpdoyKA2FkY8PNcAl7BiQFkRbhSmYEACxDXYZgMpSkyIsT8NeNwB56H4Oib4Yz/B4/U5XJQG50ODXQ1QYlgwCpgoJyCPdnSRzhdbRvm/tNQUSSz+UwQYOMTsKENxShXPMxbfZ0o8xxZ48ajBzQZ4HgO3MnDGGIxoDPIWISiaYgKngJAFqwFJJDCvAUqAIQMVB1MoJFTgKqCZhD137tQn5ezZf7D/ijv/5C/8L//DMKz1KmIgrgELePifvp9Of/s7N21994l/fOsjj77F3GxKGTEmxNhpDAwbeFZYZbASvCgcBTADhQYUIaAmg2AZygHsBSyEkgs0TNhXwS4qNEFx4BWXhHFRCBdEcEkUu6K4JDGrV6uiUUEDRQNFgETyYEKhhDEBWzDYhsE2GCURbjaKMwY4ZwlnoThNgm0wto3FyJY4Jw6FMRBm1CJoNEBUYZXAIihgEQhQE1sya4hKeGGGaIDVANMIpPGgfTdpWL6spf1VGhX/7I7f/fD5p/vzOwkYiGvASnzqv/6fvn/0xPl3b33zW3+lbC6coVNbMChAVABwAATKBkFNrBRiQAjwFDD241ifhxpsCGCFggC1IBCs9WAwSLMbp6hV0KjCU0BgbYepavpJIEhSn8ewv4JUEYuT4j8iwkgCCgAFlWAYEPI+PZQFZXUKHoKGfBTDQsHZuiMGKaMKNWACbEHRigzRfiPnUe5WTVXLk760nzaEfwLDH4OhvTv+/ceGwPs1wtXilyGreANAxuU3wlb5083znnMv9rb/Oj3x1B2gyc3jUXGKxgxvAQ8PA8FIDUzgRGeA1wmsYRTMQJDYh51MaqZnYCYMZUVjAOFYdlMA2FaC1QLWxyiYECGWacefSoAlgkkyh0AKgSCWiKeaQDoFAWFCDkIeCgEBsN7CCKHSCsIGsAXGWqBoPEKocTBycCWhqAVqDYhGkEphG4L1DZpmdz8A363K0W8r668w6CsgNHd8diCsGwWDxXUD4Q/+xs9auz+5iXZ3nwtt/mpR+QdwcfJiErmpPHfqFI2plDiTAxYWBZeYaAUYjipyH2CEYNlAQXASwNYjUCQmVQLDxG00FmEHuChR4ChTUO6Iq2yAwqU6xtztgpFk7oCRKs41JAOBBdSAycIogZXQlBWcBjgVkBIKYlgYQKL2KzBhqxbQpQkm1fQgFPZJe277y5DmX4rIJ8HmG3f83iP10/iRnHgMruKAtfGHb3onkTS2qP1p7Fe3ksgrSeRBqaZ/HupuwqnRaT51aodsgWK0DQSBBIUwIDa6iyZ4mCBouACLxn8AmAlKQFCBACCykYc0NxJTQCURi0JSqVJ8PtUGZkU9aSz1iUZYzECmVj6BGKKCEgQjhFo9PAMGgJ00wMG08T4cuHH5hN3e+iIa/0H18hmzXT4BDRe+/99+dMgSPgMwENeAy8ZXX/kGQ9ARgjujJM+hkX0VqHhnc/HgRWSqc+PxaGtc7mzp9haa0sJDUDYepQ+YjrfACrDGeBWnJmGqgJDCm9heRqnrtJCKgCAcs4csCiMKo6n7AsUYm6bvB6vCCsGoQknhjMIxYFwD4xRlrdDKNxeqg8kU/omt55x7bEz0iDT6IU/8OI/KS6ZyBy/69wNZPdMwENeAY8PX/tIbCDBWJvUIUj+XRH5EPR4KVf1SNfQcPrVzanT69MjY0YjKVAiNgEACIUFiMpAYlE3R7pdyi5r8N3F0HUlT9zAkrVbczobUL54ClAQ2CIz30MZDvZv6S9XulOUpnNr+zojMR6QJDwfGd4oz200h8C/63YGonukYiGvAVcNXXvGjDCULQamWz+lW+Qqx9Jbg3J+333j8LAq7zabcofF4qzh3dsRFsUXGQC1DRnEuJKdWyln1TlB4KAI4ZgABcACsVxivsAI0tI/mYLofDqYHbur3m0b2cObUPj/nWU8acZ82PvymF/ctHZXVSLl6yac/PhDVdYaBuAZcM3z5L72BxNIIjdsqz++WCoyF7XMAfjmM+VERvEir5iymk9LWVUHWFFqaApaNFmzUkBEmNgGenXjyIvDqA2uQsnQ6LmoUdk9t8W0j8jmG/qE04VsQuqBb44Z2thwhNC/7vUcGorrOMRDXgKcVf/TCVxKMZS7KQkQLAAaNMwWEYbjQwhQxJQgrlORZQM2CRgUOol6BoNZ4YuNh2IO4YVX/4t//nYGgblAMxDVgwIDrDleLX4YpPwMGDLjuMBDXgAEDrjsMxDVgwIDrDgNxDRgw4LrDQFwDBgy47jAQ14ABA647DMQ1YMCA6w4DcQ0YMOC6w0BcAwYMuO4wENeAAQOuOwzENWDAgOsOA3ENGHDM+PC7f5U+8ovvGQp1ryL+f6AMRHahO7duAAAAAElFTkSuQmCC' + } +} diff --git a/src/utils/constants.js b/src/utils/constants.js new file mode 100644 index 0000000..a1e6d59 --- /dev/null +++ b/src/utils/constants.js @@ -0,0 +1,118 @@ +// 视图 +import { getLastDate, getLastDateTimeEnd, getLastDateTimeStart, getLastMonth, getLastMonthTimeEnd } from '@/utils/index'; + +export const views = { +} + +export const UserType = { + ADMIN: "1", // 管理员 + APP: "2" // 普通用户 +} + +// 日期选择器快捷选项 +export const DatePickerOptions = { + // 默认 + DEFAULT: { + shortcuts: [{ + text: '最近一周', + onClick(picker) { + const end = getLastDate(0); + const start = getLastDate(6); + picker.$emit('pick', [start, end]); + } + }, { + text: '最近一个月', + onClick(picker) { + const end = getLastDate(0); + const start = getLastMonth(1); + picker.$emit('pick', [start, end]); + } + }, { + text: '最近三个月', + onClick(picker) { + const end = getLastDate(0); + const start = getLastMonth(3); + picker.$emit('pick', [start, end]); + } + }] + }, + // 未来 + FUTURE: { + shortcuts: [{ + text: '明天', + onClick(picker) { + const end = getLastDateTimeEnd(-1); + const start = getLastDateTimeStart(-1); + picker.$emit('pick', [start, end]); + } + },{ + text: '未来一周', + onClick(picker) { + const end = getLastDateTimeEnd(-6); + const start = getLastDateTimeStart(0); + picker.$emit('pick', [start, end]); + } + }, { + text: '未来一个月', + onClick(picker) { + const end = getLastMonthTimeEnd(-1); + const start = getLastDateTimeStart(0); + picker.$emit('pick', [start, end]); + } + }, { + text: '未来三个月', + onClick(picker) { + const end = getLastMonthTimeEnd(-3); + const start = getLastDateTimeStart(0); + picker.$emit('pick', [start, end]); + } + }] + }, + // 禁用未来 + DISABLE_FUTURE: { + disabledDate(date) { + return parseInt(date.getTime() / 86400000) > parseInt(Date.now() / 86400000) - 1; + } + }, + // 禁用过去 + DISABLE_PAST: { + disabledDate(date) { + return parseInt(date.getTime() / 86400000) < parseInt(Date.now() / 86400000) - 1; + } + } +} + +// 进度颜色 +export const ProgressColors = [ + {color: '#5cb87a', percentage: 90}, + {color: '#f3ab40', percentage: 60}, + {color: '#f56c6c', percentage: 30}, +] + +// 进度条格式化 +export function ProgressFormat(val) { + if (val == null) { + return '0%'; + } + return val.toFixed(0) + '%'; +} + +// 文件类型 +export const FileType = { + // 图片 + IMAGE: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'ico'], + // 办公 + OFFICE: ['doc', 'docx', 'xls', 'xlsx', 'pdf', 'ppt', 'pptx'], + // 音频 + AUDIO: ['mp3', 'wav', 'm4a', 'ogg', 'flac', 'aac'], + // 视频 + VIDEO: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mpeg', 'mpg', 'm4v', 'webm', 'mkv'], + // 压缩文件 + ZIP: ['zip', 'rar', '7z'], + // 其他 + OTHER: ['exe'], + // 全部 + all() { + return [...this.IMAGE, ...this.OFFICE, ...this.AUDIO, ...this.VIDEO, ...this.OTHER, ...this.ZIP]; + } +} diff --git a/src/utils/date.js b/src/utils/date.js new file mode 100644 index 0000000..1a45c4f --- /dev/null +++ b/src/utils/date.js @@ -0,0 +1,144 @@ +import { parseTime } from '@/utils/ruoyi'; + +/** + * 计算周年 + */ +export function calcFullYear(date) { + if (date == null ) { + return 0; + } + let start = toDate(date); + let end = new Date(); + + return Math.floor((end.getTime() - start.getTime()) / 31536000000) +} + +/** + * 计算年龄 + */ +export function calcBirthDay(birthday) { + if (birthday === null) { + return 0; + } + let now = new Date(); + let start = toDate(birthday); + + return now.getFullYear() - start.getFullYear() + 1; +} + +/** + * 转为日期 + */ +export function toDate(data) { + let date = data; + if (typeof date === 'string') { + date = new Date(data); + } + return date; +} + +/** + * 计算两个时间相差多少秒 + */ +export function calcSecond(start, end) { + if (start == null || end == null) { + return 0; + } + let startDate = toDate(start); + let endDate = toDate(end); + return Math.floor((endDate.getTime() - startDate.getTime()) / 1000); +} + + + +// 获取前n天的日期 +export function getLastDate(n) { + let now = new Date(); + return new Date(now.getTime() - n * 24 * 3600 * 1000) +} + +// 获取前n月的日期 +export function getLastMonth(n) { + const date = new Date(); + const year = date.getFullYear(); + const month = date.getMonth(); + const day = date.getDate(); + + // 减去 n 个月 + date.setMonth(month - n); + + // 确保日期不超过当月的最大天数 + const maxDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + date.setDate(Math.min(day, maxDay)); + + return date; +} + +// 获取前n月的日期字符串 +export function getLastMonthDateStr(n) { + let date = getLastMonth(n); + return parseTime(date, "{y}-{m}-{d}") +} + +// 获取前n天的日期字符串 +export function getLastDateStr(n) { + let date = getLastDate(n); + return parseTime(date, "{y}-{m}-{d}"); +} + +// 获取前n天的日期时间字符串00:00:00 +export function getLastDateTimeStartStr(n) { + let date = getLastDate(n); + return parseTime(date, "{y}-{m}-{d} 00:00:00"); +} + +// 获取前n天的日期时间字符串23:59:59 +export function getLastDateTimeEndStr(n) { + let date = getLastDate(n); + return parseTime(date, "{y}-{m}-{d} 23:59:59"); +} + +// 获取前n天的日期时间00:00:00 +export function getLastDateTimeStart(n) { + return new Date(getLastDateTimeStartStr(n)); +} + +// 获取前n天的日期时间23:59:59 +export function getLastDateTimeEnd(n) { + return new Date(getLastDateTimeEndStr(n)); +} + +/** + * 将秒钟转为描述 + */ +export function toDescriptionFromSecond(seconds) { + if (seconds instanceof String) { + seconds = parseInt(seconds) + } + let positive = true; + if (seconds < 0) { + positive =false; + seconds = -seconds; + } + + let desc = { + day: parseInt(seconds / 86400), + hour: parseInt((seconds % 86400) / 3600), + minute: parseInt((seconds % 3600) / 60), + second: parseInt(seconds % 60), + positive: positive, + } + + desc.text = `${positive ? '' : '-'}`; + if (desc.day > 0) { + desc.text += desc.day + ' 天 '; + } + if (desc.day > 0 || desc.hour > 0) { + desc.text += desc.hour + ' 时 '; + } + if (desc.day > 0 || desc.hour > 0 || desc.minute > 0) { + desc.text += desc.minute + ' 分 '; + } + desc.text += desc.second + ' 秒'; + return desc; +} diff --git a/src/utils/dict/Dict.js b/src/utils/dict/Dict.js new file mode 100644 index 0000000..66473d5 --- /dev/null +++ b/src/utils/dict/Dict.js @@ -0,0 +1,82 @@ +import Vue from 'vue' +import { mergeRecursive } from '@/utils/ruoyi' +import DictMeta from './DictMeta' +import DictData from './DictData' + +const DEFAULT_DICT_OPTIONS = { + types: [], +} + +/** + * @classdesc 字典 + * @property {Object} label 标签对象,内部属性名为字典类型名称 + * @property {Object} dict 字段数组,内部属性名为字典类型名称 + * @property {Array.} _dictMetas 字典元数据数组 + */ +export default class Dict { + constructor() { + this.owner = null + this.label = {} + this.type = {} + } + + init(options) { + if (options instanceof Array) { + options = { types: options } + } + const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options) + if (opts.types === undefined) { + throw new Error('need dict types') + } + const ps = [] + this._dictMetas = opts.types.map(t => DictMeta.parse(t)) + this._dictMetas.forEach(dictMeta => { + const type = dictMeta.type + Vue.set(this.label, type, {}) + Vue.set(this.type, type, []) + if (dictMeta.lazy) { + return + } + ps.push(loadDict(this, dictMeta)) + }) + return Promise.all(ps) + } + + /** + * 重新加载字典 + * @param {String} type 字典类型 + */ + reloadDict(type) { + const dictMeta = this._dictMetas.find(e => e.type === type) + if (dictMeta === undefined) { + return Promise.reject(`the dict meta of ${type} was not found`) + } + return loadDict(this, dictMeta) + } +} + +/** + * 加载字典 + * @param {Dict} dict 字典 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {Promise} + */ +function loadDict(dict, dictMeta) { + return dictMeta.request(dictMeta) + .then(response => { + const type = dictMeta.type + let dicts = dictMeta.responseConverter(response, dictMeta) + if (!(dicts instanceof Array)) { + console.error('the return of responseConverter must be Array.') + dicts = [] + } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) { + console.error('the type of elements in dicts must be DictData') + dicts = [] + } + dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts) + dicts.forEach(d => { + Vue.set(dict.label[type], d.value, d.label) + }) + return dicts + }) +} diff --git a/src/utils/dict/DictConverter.js b/src/utils/dict/DictConverter.js new file mode 100644 index 0000000..0cf5df8 --- /dev/null +++ b/src/utils/dict/DictConverter.js @@ -0,0 +1,17 @@ +import DictOptions from './DictOptions' +import DictData from './DictData' + +export default function(dict, dictMeta) { + const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS) + const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS) + return new DictData(dict[label], dict[value], dict) +} + +/** + * 确定字典字段 + * @param {DictData} dict + * @param {...String} fields + */ +function determineDictField(dict, ...fields) { + return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f)) +} diff --git a/src/utils/dict/DictData.js b/src/utils/dict/DictData.js new file mode 100644 index 0000000..afc763e --- /dev/null +++ b/src/utils/dict/DictData.js @@ -0,0 +1,13 @@ +/** + * @classdesc 字典数据 + * @property {String} label 标签 + * @property {*} value 标签 + * @property {Object} raw 原始数据 + */ +export default class DictData { + constructor(label, value, raw) { + this.label = label + this.value = value + this.raw = raw + } +} diff --git a/src/utils/dict/DictMeta.js b/src/utils/dict/DictMeta.js new file mode 100644 index 0000000..f6ca554 --- /dev/null +++ b/src/utils/dict/DictMeta.js @@ -0,0 +1,38 @@ +import { mergeRecursive } from '@/utils/ruoyi' +import DictOptions from './DictOptions' + +/** + * @classdesc 字典元数据 + * @property {String} type 类型 + * @property {Function} request 请求 + * @property {String} label 标签字段 + * @property {String} value 值字段 + */ +export default class DictMeta { + constructor(options) { + this.type = options.type + this.request = options.request + this.responseConverter = options.responseConverter + this.labelField = options.labelField + this.valueField = options.valueField + this.lazy = options.lazy === true + } +} + + +/** + * 解析字典元数据 + * @param {Object} options + * @returns {DictMeta} + */ +DictMeta.parse= function(options) { + let opts = null + if (typeof options === 'string') { + opts = DictOptions.metas[options] || {} + opts.type = options + } else if (typeof options === 'object') { + opts = options + } + opts = mergeRecursive(DictOptions.metas['*'], opts) + return new DictMeta(opts) +} diff --git a/src/utils/dict/DictOptions.js b/src/utils/dict/DictOptions.js new file mode 100644 index 0000000..68735d4 --- /dev/null +++ b/src/utils/dict/DictOptions.js @@ -0,0 +1,51 @@ +import { mergeRecursive } from '@/utils/ruoyi' +import dictConverter from './DictConverter' + +export const options = { + metas: { + '*': { + /** + * 字典请求,方法签名为function(dictMeta: DictMeta): Promise + */ + request: (dictMeta) => { + console.log(`load dict ${dictMeta.type}`) + return Promise.resolve([]) + }, + /** + * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData + */ + responseConverter, + labelField: 'label', + valueField: 'value', + }, + }, + /** + * 默认标签字段 + */ + DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'], + /** + * 默认值字段 + */ + DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'], +} + +/** + * 映射字典 + * @param {Object} response 字典数据 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {DictData} + */ +function responseConverter(response, dictMeta) { + const dicts = response.content instanceof Array ? response.content : response + if (dicts === undefined) { + console.warn(`no dict data of "${dictMeta.type}" found in the response`) + return [] + } + return dicts.map(d => dictConverter(d, dictMeta)) +} + +export function mergeOptions(src) { + mergeRecursive(options, src) +} + +export default options diff --git a/src/utils/dict/index.js b/src/utils/dict/index.js new file mode 100644 index 0000000..215eb9e --- /dev/null +++ b/src/utils/dict/index.js @@ -0,0 +1,33 @@ +import Dict from './Dict' +import { mergeOptions } from './DictOptions' + +export default function(Vue, options) { + mergeOptions(options) + Vue.mixin({ + data() { + if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) { + return {} + } + const dict = new Dict() + dict.owner = this + return { + dict + } + }, + created() { + if (!(this.dict instanceof Dict)) { + return + } + options.onCreated && options.onCreated(this.dict) + this.dict.init(this.$options.dicts).then(() => { + options.onReady && options.onReady(this.dict) + this.$nextTick(() => { + this.$emit('dictReady', this.dict) + if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) { + this.$options.methods.onDictReady.call(this, this.dict) + } + }) + }) + }, + }) +} diff --git a/src/utils/enums.js b/src/utils/enums.js new file mode 100644 index 0000000..c452872 --- /dev/null +++ b/src/utils/enums.js @@ -0,0 +1,324 @@ +// 用户类型 +export const UserType = { + ADMIN: '1', // 系统用户 + USER: '2', // 普通用户 + + // 判断是否是管理员 + isAdminType(userType) { + return userType === UserType.ADMIN; + } +} + +// 角色 +export const RoleKeys = { + ADMIN: "admin", // 超级管理员 + AGENT: "agent", // 代理商 + MCH: "mch", // 运营商 +} + +// 应用类型 +export const AppType = { + WECHAT: "1", // 微信 + ALI_PAY: "2", // 支付宝 +} + +// 渠道类型 +export const ChannelType = { + SYSTEM: "1", // 官方收款 + CUSTOM: "2", // 用户收款 +} + +// 渠道API类型 +export const ChannelApiType = { + WECHAT: "WX", // 微信 + ALI_PAY: "ALI", // 支付宝 + BANK: "BANK", // 银行卡 + BALANCE: "BALANCE", // 余额 + TM_WX: "TM_WX", // 太米微信 +} + +// 运营区状态 +export const AreaStatus = { + OPEN: "0", // 运营 + CLOSE: "1", // 停运 +} + +// 子区域状态 +export const AreaSubStatus = { + NORMAL: "0", // 正常 + DISABLED: "1", // 禁用 +} + +// 子区域类型 +export const AreaSubType = { + PARKING: "1", // 停车区 + NO_PARKING: "2", // 禁停区 + NO_RIDE: "3", // 禁行区 +} + +// 套餐类型 +export const SuitType = { + SHARE: "1", // 共享 + RENTAL: "2", // 租赁 +} + +// 套餐状态 +export const SuitStatus = { + NORMAL: "0", // 正常 + DISABLED: "1", // 禁用 +} + +// 套餐租赁单位 +export const SuitRentalUnit = { + MINUTE: "minutes", // 分钟 + HOUR: "hours", // 小时 + DAY: "day", // 天 +} + +// 计费方式 +export const SuitRidingRule = { + START: "1", // 起步价 + INTERVAL: "2", // 区间计费 +} + +// 设备状态 +export const DeviceStatus = { + STORAGE: "0", // 仓库中 + AVAILABLE: "1", // 待骑行 + RESERVED: "2", // 预约中 + IN_USE: "3", // 骑行中 + TEMP_LOCKED: "4", // 临时锁车 + DISPATCHING: "6", // 调度中 + DISABLED: "8", // 禁用 + Q_LOCKED: "9", // 强制锁车 + + // 允许入仓的设备状态 + canIn() { + return [this.AVAILABLE, this.DISPATCHING, this.DISABLED]; + }, + // 允许出仓的设备状态 + canOut() { + return [this.STORAGE]; + }, + // 允许禁用的设备状态 + canDisable() { + return [this.AVAILABLE, this.DISPATCHING, this.STORAGE]; + }, + // 允许启用的设备状态 + canEnable() { + return [this.DISABLED]; + }, + // 允许管理员开锁的设备状态 + canAdminUnlock() { + return [this.DISPATCHING, this.STORAGE, this.AVAILABLE, this.TEMP_LOCKED, this.Q_LOCKED]; + }, + // 允许用户开锁的设备状态 + canUserUnlock() { + return [this.IN_USE, this.AVAILABLE, this.TEMP_LOCKED]; + }, + // 允许用户锁车的设备状态 + canUserLock() { + return [this.AVAILABLE, this.TEMP_LOCKED, this.DISPATCHING, this.IN_USE]; + }, + // 允许管理员锁车的状态 + canAdminLock() { + return [this.AVAILABLE, this.TEMP_LOCKED, this.DISPATCHING, this.IN_USE, this.Q_LOCKED]; + }, + // 允许用户使用的设备状态 + canUse() { + return [this.AVAILABLE]; + }, + // 允许返回运营区上电 + canBackUnlock() { + return [this.Q_LOCKED]; + }, + // 允许强制锁车的状态 + canQLock() { + return this.canAdminLock(); + } +} + +// 设备锁状态 +export const DeviceLockStatus = { + LOCKED: "0", // 锁车 + UNLOCKED: "1", // 开锁 +} + +// 加盟类型 +export const AreaJoinType = { + JOIN: "1", // 加盟 + COOPERATE: "2", // 合伙 + OPERATION: "3", // 运维 +} + +// 订单状态 +export const OrderStatus = { + WAIT_PAY: "WAIT_PAY", // 待支付 + PROCESSING: "PROCESSING", // 进行中 + FINISHED: "FINISHED", // 已结束 + CANCELED: "CANCELED", // 已取消 + WAIT_VERIFY: "WAIT_VERIFY", // 待审核 + REJECTED: "REJECTED", // 已驳回 + REFUNDED: "REFUNDED", // 已退款 + + // 允许支付的订单状态 + canPay() { + return [this.WAIT_PAY]; + }, + // 正在使用中的订单状态 + inUse() { + return [this.PROCESSING]; + }, + // 可以支付成功的订单状态 + canPaySuccess() { + return [this.WAIT_PAY]; + }, + // 可以结束的订单状态 + canEnd() { + return [this.PROCESSING]; + }, + // 可以退款的订单状态 + canRefund() { + return [this.FINISHED, this.REFUNDED, this.REJECTED, this.WAIT_VERIFY]; + }, + // 未支付的订单状态 + unPayList() { + return [this.WAIT_PAY]; + }, + // 可以取消支付的订单状态 + canCancelPay() { + return [this.WAIT_PAY]; + }, + // 可以审核的订单状态 + canVerify() { + return [this.WAIT_VERIFY]; + }, + // 有效的订单状态:已支付的订单 + valid() { + return [this.PROCESSING, this.FINISHED, this.WAIT_VERIFY, this.REJECTED, this.REFUNDED]; + } +} + +// 支付业务类型 +export const PayBstType = { + ORDER: "1", // 订单 +} + +// 分成业务类型 +export const BonusBstType = { + ORDER: "1", // 订单 +} + +// 分成到账类型 +export const BonusArrivalType = { + PLATFORM: "PLATFORM", // 平台 + AGENT: "AGENT", // 代理商 + MCH: "MCH", // 运营商 + JOIN: "JOIN", // 加盟商 + PARTNER: "PARTNER", // 合伙人 + + // 用户列表 + userList() { + return [this.AGENT, this.MCH, this.JOIN, this.PARTNER]; + } +} + +// 统计数据键值 +export const StatKeys = { + ORDER_USER_COUNT: "order_user_count", // 累计订单用户 + ORDER_COUNT: "order_count", // 订单数量 + ORDER_PAY_AMOUNT: "order_pay_amount", // 订单支付金额 + ORDER_REFUND_AMOUNT: "order_refund_amount", // 订单退款金额 + ORDER_WAIT_VERIFY_COUNT: "order_wait_verify_count", // 待审核的订单 + ORDER_STATUS_COUNT: "order_status_count", // 订单状态数量 + + BONUS_COUNT: "bonus_count", // 分成数量 + BONUS_AMOUNT: "bonus_amount", // 分成总金额 + BONUS_REFUND_AMOUNT: "bonus_refund_amount", // 分成总退款 + BONUS_WAIT_DIVIDE_AMOUNT: "bonus_wait_divide_amount", // 分成未入账金额 + + USER_COUNT: "user_count", // 用户数量 + USER_TODAY_COUNT: "user_today_count", // 今日新增用户数量 + USER_BALANCE: "user_balance", // 用户余额 + USER_MCH_COUNT: "user_mch_count", // 运营商数量 + USER_AGENT_COUNT: "user_agent_count", // 代理商数量 + USER_AGENT_MCH_COUNT: "user_agent_mch_count", // 下属运营商数量 + + DEVICE_COUNT: "device_count", // 设备数量 + DEVICE_STATUS_COUNT: "device_status_count", // 设备状态数量 + DEVICE_ONLINE_STATUS_COUNT: "device_online_status_count", // 设备在线状态数量 + + AREA_COUNT: "area_count", // 运营区数量 + MODEL_COUNT: "model_count", // 型号数量 + + AREA_JOIN_COUNT: "area_join_count", // 加盟商数量 + AREA_JOIN_PARTNER_COUNT: "area_join_partner_count", // 合伙人数量 + + WITHDRAW_SUCCESS_AMOUNT: "withdraw_success_amount", // 成功提现金额 + WITHDRAW_WAIT_VERIFY_COUNT: "withdraw_wait_verify_count", // 待审核的提现数量 + + MCH_APPLY_APPROVING_COUNT: "mch_apply_approving_count", // 待审核的加盟商申请数量 + + FAULT_PENDING_COUNT: "fault_pending_count" // 待处理的故障数量 +} + +// 收款账户类型 +export const AccountType = { + WX: "WX", // 线下微信 + ALI: "ALI", // 线下支付宝 + BANK: "BANK", // 线下银行卡 + QR: "QR", // 线下收款二维码 +} + +// 提现状态 +// 提现状态 +export const WithdrawStatus = { + WAIT_VERIFY: "WAIT_VERIFY", // 审核中 + PAYING: "PAYING", // 打款中 + SUCCESS: "SUCCESS", // 已打款 + REJECTED: "REJECTED", // 驳回 + FAILED: "FAILED", // 打款失败 + + // 可审核的状态列表 + canVerify() { + return [this.WAIT_VERIFY]; + } +} + +// 分成状态 +export const BonusStatus = { + INVALID: "INVALID", // 未出账 + WAIT_DIVIDE: "WAIT_DIVIDE", // 待分成 + DIVIDEND: "DIVIDEND", // 已分成 + + // 有效的分成状态列表 + valid() { + return [this.WAIT_DIVIDE, this.DIVIDEND]; + }, + + // 可打款的分成状态列表 + canPay() { + return [this.WAIT_DIVIDE]; + } +} + +// 加盟商申请状态 +export const MchApplyStatus = { + APPROVING: "1", // 审核中 + PASSED: "2", // 审核通过 + REJECTED: "3", // 审核驳回 +} + +// 故障状态 +export const FaultStatus = { + REJECTED: "0", // 已驳回 + PENGDING: "1", // 待处理 + REPAIRING: "2", // 维修中 + COMPLETED: "3", // 已完成 +} + +// 提现手续费类型 +export const WithdrawServiceType = { + POINT: "1", // 按比例 + FIXED: "2", // 固定金额 +} diff --git a/src/utils/errorCode.js b/src/utils/errorCode.js new file mode 100644 index 0000000..d2111ee --- /dev/null +++ b/src/utils/errorCode.js @@ -0,0 +1,6 @@ +export default { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + 'default': '系统未知错误,请反馈给管理员' +} diff --git a/src/utils/filter.js b/src/utils/filter.js new file mode 100644 index 0000000..2468049 --- /dev/null +++ b/src/utils/filter.js @@ -0,0 +1,32 @@ +import { isEmpty } from '@/utils/index' + +const filters = { + // 金钱显示,保留两位小数 + money(num) { + return filters.fix2(num); + }, + // 缺省值 + defaultValue(data) { + return isEmpty(data) ? '--' : data; + }, + fix2(num) { + if (num == null) { + return num; + } + return num.toFixed(2); + }, + fix10(num) { + if (num == null) { + return num; + } + return num.toFixed(10); + }, + dv(data) { + return filters.defaultValue(data); + } +} +export default (vm) => { + Object.keys(filters).forEach(key => { + vm.filter(key, filters[key]) + }) +} diff --git a/src/utils/generator/config.js b/src/utils/generator/config.js new file mode 100644 index 0000000..7abf227 --- /dev/null +++ b/src/utils/generator/config.js @@ -0,0 +1,438 @@ +export const formConf = { + formRef: 'elForm', + formModel: 'formData', + size: 'medium', + labelPosition: 'right', + labelWidth: 100, + formRules: 'rules', + gutter: 15, + disabled: false, + span: 24, + formBtns: true +} + +export const inputComponents = [ + { + label: '单行文本', + tag: 'el-input', + tagIcon: 'input', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '多行文本', + tag: 'el-input', + tagIcon: 'textarea', + type: 'textarea', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + labelWidth: null, + autosize: { + minRows: 4, + maxRows: 4 + }, + style: { width: '100%' }, + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '密码', + tag: 'el-input', + tagIcon: 'password', + placeholder: '请输入', + defaultValue: undefined, + span: 24, + 'show-password': true, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': '', + 'suffix-icon': '', + maxlength: null, + 'show-word-limit': false, + readonly: false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input' + }, + { + label: '计数器', + tag: 'el-input-number', + tagIcon: 'number', + placeholder: '', + defaultValue: undefined, + span: 24, + labelWidth: null, + min: undefined, + max: undefined, + step: undefined, + 'step-strictly': false, + precision: undefined, + 'controls-position': '', + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/input-number' + } +] + +export const selectComponents = [ + { + label: '下拉选择', + tag: 'el-select', + tagIcon: 'select', + placeholder: '请选择', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: { width: '100%' }, + clearable: true, + disabled: false, + required: true, + filterable: false, + multiple: false, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/select' + }, + { + label: '级联选择', + tag: 'el-cascader', + tagIcon: 'cascader', + placeholder: '请选择', + defaultValue: [], + span: 24, + labelWidth: null, + style: { width: '100%' }, + props: { + props: { + multiple: false + } + }, + 'show-all-levels': true, + disabled: false, + clearable: true, + filterable: false, + required: true, + options: [{ + id: 1, + value: 1, + label: '选项1', + children: [{ + id: 2, + value: 2, + label: '选项1-1' + }] + }], + dataType: 'dynamic', + labelKey: 'label', + valueKey: 'value', + childrenKey: 'children', + separator: '/', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/cascader' + }, + { + label: '单选框组', + tag: 'el-radio-group', + tagIcon: 'radio', + defaultValue: undefined, + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'medium', + disabled: false, + required: true, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/radio' + }, + { + label: '多选框组', + tag: 'el-checkbox-group', + tagIcon: 'checkbox', + defaultValue: [], + span: 24, + labelWidth: null, + style: {}, + optionType: 'default', + border: false, + size: 'medium', + disabled: false, + required: true, + options: [{ + label: '选项一', + value: 1 + }, { + label: '选项二', + value: 2 + }], + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/checkbox' + }, + { + label: '开关', + tag: 'el-switch', + tagIcon: 'switch', + defaultValue: false, + span: 24, + labelWidth: null, + style: {}, + disabled: false, + required: true, + 'active-text': '', + 'inactive-text': '', + 'active-color': null, + 'inactive-color': null, + 'active-value': true, + 'inactive-value': false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/switch' + }, + { + label: '滑块', + tag: 'el-slider', + tagIcon: 'slider', + defaultValue: null, + span: 24, + labelWidth: null, + disabled: false, + required: true, + min: 0, + max: 100, + step: 1, + 'show-stops': false, + range: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/slider' + }, + { + label: '时间选择', + tag: 'el-time-picker', + tagIcon: 'time', + placeholder: '请选择', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + 'picker-options': { + selectableRange: '00:00:00-23:59:59' + }, + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' + }, + { + label: '时间范围', + tag: 'el-time-picker', + tagIcon: 'time-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + 'is-range': true, + 'range-separator': '至', + 'start-placeholder': '开始时间', + 'end-placeholder': '结束时间', + format: 'HH:mm:ss', + 'value-format': 'HH:mm:ss', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' + }, + { + label: '日期选择', + tag: 'el-date-picker', + tagIcon: 'date', + placeholder: '请选择', + defaultValue: null, + type: 'date', + span: 24, + labelWidth: null, + style: { width: '100%' }, + disabled: false, + clearable: true, + required: true, + format: 'yyyy-MM-dd', + 'value-format': 'yyyy-MM-dd', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' + }, + { + label: '日期范围', + tag: 'el-date-picker', + tagIcon: 'date-range', + defaultValue: null, + span: 24, + labelWidth: null, + style: { width: '100%' }, + type: 'daterange', + 'range-separator': '至', + 'start-placeholder': '开始日期', + 'end-placeholder': '结束日期', + disabled: false, + clearable: true, + required: true, + format: 'yyyy-MM-dd', + 'value-format': 'yyyy-MM-dd', + readonly: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' + }, + { + label: '评分', + tag: 'el-rate', + tagIcon: 'rate', + defaultValue: 0, + span: 24, + labelWidth: null, + style: {}, + max: 5, + 'allow-half': false, + 'show-text': false, + 'show-score': false, + disabled: false, + required: true, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/rate' + }, + { + label: '颜色选择', + tag: 'el-color-picker', + tagIcon: 'color', + defaultValue: null, + labelWidth: null, + 'show-alpha': false, + 'color-format': '', + disabled: false, + required: true, + size: 'medium', + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/color-picker' + }, + { + label: '上传', + tag: 'el-upload', + tagIcon: 'upload', + action: 'https://jsonplaceholder.typicode.com/posts/', + defaultValue: null, + labelWidth: null, + disabled: false, + required: true, + accept: '', + name: 'file', + 'auto-upload': true, + showTip: false, + buttonText: '点击上传', + fileSize: 2, + sizeUnit: 'MB', + 'list-type': 'text', + multiple: false, + regList: [], + changeTag: true, + document: 'https://element.eleme.cn/#/zh-CN/component/upload' + } +] + +export const layoutComponents = [ + { + layout: 'rowFormItem', + tagIcon: 'row', + type: 'default', + justify: 'start', + align: 'top', + label: '行容器', + layoutTree: true, + children: [], + document: 'https://element.eleme.cn/#/zh-CN/component/layout' + }, + { + layout: 'colFormItem', + label: '按钮', + changeTag: true, + labelWidth: null, + tag: 'el-button', + tagIcon: 'button', + span: 24, + default: '主要按钮', + type: 'primary', + icon: 'el-icon-search', + size: 'medium', + disabled: false, + document: 'https://element.eleme.cn/#/zh-CN/component/button' + } +] + +// 组件rule的触发方式,无触发方式的组件不生成rule +export const trigger = { + 'el-input': 'blur', + 'el-input-number': 'blur', + 'el-select': 'change', + 'el-radio-group': 'change', + 'el-checkbox-group': 'change', + 'el-cascader': 'change', + 'el-time-picker': 'change', + 'el-date-picker': 'change', + 'el-rate': 'change' +} diff --git a/src/utils/generator/css.js b/src/utils/generator/css.js new file mode 100644 index 0000000..c1c62e6 --- /dev/null +++ b/src/utils/generator/css.js @@ -0,0 +1,18 @@ +const styles = { + 'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}', + 'el-upload': '.el-upload__tip{line-height: 1.2;}' +} + +function addCss(cssList, el) { + const css = styles[el.tag] + css && cssList.indexOf(css) === -1 && cssList.push(css) + if (el.children) { + el.children.forEach(el2 => addCss(cssList, el2)) + } +} + +export function makeUpCss(conf) { + const cssList = [] + conf.fields.forEach(el => addCss(cssList, el)) + return cssList.join('\n') +} diff --git a/src/utils/generator/drawingDefault.js b/src/utils/generator/drawingDefault.js new file mode 100644 index 0000000..09f133c --- /dev/null +++ b/src/utils/generator/drawingDefault.js @@ -0,0 +1,29 @@ +export default [ + { + layout: 'colFormItem', + tagIcon: 'input', + label: '手机号', + vModel: 'mobile', + formId: 6, + tag: 'el-input', + placeholder: '请输入手机号', + defaultValue: '', + span: 24, + style: { width: '100%' }, + clearable: true, + prepend: '', + append: '', + 'prefix-icon': 'el-icon-mobile', + 'suffix-icon': '', + maxlength: 11, + 'show-word-limit': true, + readonly: false, + disabled: false, + required: true, + changeTag: true, + regList: [{ + pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', + message: '手机号格式错误' + }] + } +] diff --git a/src/utils/generator/html.js b/src/utils/generator/html.js new file mode 100644 index 0000000..9bcc536 --- /dev/null +++ b/src/utils/generator/html.js @@ -0,0 +1,359 @@ +/* eslint-disable max-len */ +import { trigger } from './config' + +let confGlobal +let someSpanIsNot24 + +export function dialogWrapper(str) { + return ` + ${str} +
+ 取消 + 确定 +
+
` +} + +export function vueTemplate(str) { + return `` +} + +export function vueScript(str) { + return `` +} + +export function cssStyle(cssStr) { + return `` +} + +function buildFormTemplate(conf, child, type) { + let labelPosition = '' + if (conf.labelPosition !== 'right') { + labelPosition = `label-position="${conf.labelPosition}"` + } + const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : '' + let str = ` + ${child} + ${buildFromBtns(conf, type)} + ` + if (someSpanIsNot24) { + str = ` + ${str} + ` + } + return str +} + +function buildFromBtns(conf, type) { + let str = '' + if (conf.formBtns && type === 'file') { + str = ` + 提交 + 重置 + ` + if (someSpanIsNot24) { + str = ` + ${str} + ` + } + } + return str +} + +// span不为24的用el-col包裹 +function colWrapper(element, str) { + if (someSpanIsNot24 || element.span !== 24) { + return ` + ${str} + ` + } + return str +} + +const layouts = { + colFormItem(element) { + let labelWidth = '' + if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) { + labelWidth = `label-width="${element.labelWidth}px"` + } + const required = !trigger[element.tag] && element.required ? 'required' : '' + const tagDom = tags[element.tag] ? tags[element.tag](element) : null + let str = ` + ${tagDom} + ` + str = colWrapper(element, str) + return str + }, + rowFormItem(element) { + const type = element.type === 'default' ? '' : `type="${element.type}"` + const justify = element.type === 'default' ? '' : `justify="${element.justify}"` + const align = element.type === 'default' ? '' : `align="${element.align}"` + const gutter = element.gutter ? `gutter="${element.gutter}"` : '' + const children = element.children.map(el => layouts[el.layout](el)) + let str = ` + ${children.join('\n')} + ` + str = colWrapper(element, str) + return str + } +} + +const tags = { + 'el-button': el => { + const { + tag, disabled + } = attrBuilder(el) + const type = el.type ? `type="${el.type}"` : '' + const icon = el.icon ? `icon="${el.icon}"` : '' + const size = el.size ? `size="${el.size}"` : '' + let child = buildElButtonChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}` + }, + 'el-input': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : '' + const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : '' + const readonly = el.readonly ? 'readonly' : '' + const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : '' + const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : '' + const showPassword = el['show-password'] ? 'show-password' : '' + const type = el.type ? `type="${el.type}"` : '' + const autosize = el.autosize && el.autosize.minRows + ? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"` + : '' + let child = buildElInputChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}` + }, + 'el-input-number': el => { + const { disabled, vModel, placeholder } = attrBuilder(el) + const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : '' + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const stepStrictly = el['step-strictly'] ? 'step-strictly' : '' + const precision = el.precision ? `:precision='${el.precision}'` : '' + + return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}>` + }, + 'el-select': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const filterable = el.filterable ? 'filterable' : '' + const multiple = el.multiple ? 'multiple' : '' + let child = buildElSelectChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}` + }, + 'el-radio-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + let child = buildElRadioGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${size} ${disabled}>${child}` + }, + 'el-checkbox-group': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const min = el.min ? `:min="${el.min}"` : '' + const max = el.max ? `:max="${el.max}"` : '' + let child = buildElCheckboxGroupChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}` + }, + 'el-switch': el => { + const { disabled, vModel } = attrBuilder(el) + const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : '' + const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : '' + const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : '' + const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : '' + const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : '' + const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : '' + + return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}>` + }, + 'el-cascader': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const options = el.options ? `:options="${el.vModel}Options"` : '' + const props = el.props ? `:props="${el.vModel}Props"` : '' + const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"' + const filterable = el.filterable ? 'filterable' : '' + const separator = el.separator === '/' ? '' : `separator="${el.separator}"` + + return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}>` + }, + 'el-slider': el => { + const { disabled, vModel } = attrBuilder(el) + const min = el.min ? `:min='${el.min}'` : '' + const max = el.max ? `:max='${el.max}'` : '' + const step = el.step ? `:step='${el.step}'` : '' + const range = el.range ? 'range' : '' + const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : '' + + return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}>` + }, + 'el-time-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const isRange = el['is-range'] ? 'is-range' : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : '' + + return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}>` + }, + 'el-date-picker': el => { + const { + disabled, vModel, clearable, placeholder, width + } = attrBuilder(el) + const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' + const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' + const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' + const format = el.format ? `format="${el.format}"` : '' + const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' + const type = el.type === 'date' ? '' : `type="${el.type}"` + const readonly = el.readonly ? 'readonly' : '' + + return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}>` + }, + 'el-rate': el => { + const { disabled, vModel } = attrBuilder(el) + const max = el.max ? `:max='${el.max}'` : '' + const allowHalf = el['allow-half'] ? 'allow-half' : '' + const showText = el['show-text'] ? 'show-text' : '' + const showScore = el['show-score'] ? 'show-score' : '' + + return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}>` + }, + 'el-color-picker': el => { + const { disabled, vModel } = attrBuilder(el) + const size = `size="${el.size}"` + const showAlpha = el['show-alpha'] ? 'show-alpha' : '' + const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : '' + + return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}>` + }, + 'el-upload': el => { + const disabled = el.disabled ? ':disabled=\'true\'' : '' + const action = el.action ? `:action="${el.vModel}Action"` : '' + const multiple = el.multiple ? 'multiple' : '' + const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : '' + const accept = el.accept ? `accept="${el.accept}"` : '' + const name = el.name !== 'file' ? `name="${el.name}"` : '' + const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : '' + const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"` + const fileList = `:file-list="${el.vModel}fileList"` + const ref = `ref="${el.vModel}"` + let child = buildElUploadChild(el) + + if (child) child = `\n${child}\n` // 换行 + return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}` + } +} + +function attrBuilder(el) { + return { + vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`, + clearable: el.clearable ? 'clearable' : '', + placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '', + width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '', + disabled: el.disabled ? ':disabled=\'true\'' : '' + } +} + +// el-buttin 子级 +function buildElButtonChild(conf) { + const children = [] + if (conf.default) { + children.push(conf.default) + } + return children.join('\n') +} + +// el-input innerHTML +function buildElInputChild(conf) { + const children = [] + if (conf.prepend) { + children.push(``) + } + if (conf.append) { + children.push(``) + } + return children.join('\n') +} + +function buildElSelectChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + children.push(``) + } + return children.join('\n') +} + +function buildElRadioGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) + } + return children.join('\n') +} + +function buildElCheckboxGroupChild(conf) { + const children = [] + if (conf.options && conf.options.length) { + const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox' + const border = conf.border ? 'border' : '' + children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) + } + return children.join('\n') +} + +function buildElUploadChild(conf) { + const list = [] + if (conf['list-type'] === 'picture-card') list.push('') + else list.push(`${conf.buttonText}`) + if (conf.showTip) list.push(`
只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件
`) + return list.join('\n') +} + +export function makeUpHtml(conf, type) { + const htmlList = [] + confGlobal = conf + someSpanIsNot24 = conf.fields.some(item => item.span !== 24) + conf.fields.forEach(el => { + htmlList.push(layouts[el.layout](el)) + }) + const htmlStr = htmlList.join('\n') + + let temp = buildFormTemplate(conf, htmlStr, type) + if (type === 'dialog') { + temp = dialogWrapper(temp) + } + confGlobal = null + return temp +} diff --git a/src/utils/generator/icon.json b/src/utils/generator/icon.json new file mode 100644 index 0000000..2d9999a --- /dev/null +++ b/src/utils/generator/icon.json @@ -0,0 +1 @@ +["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"] \ No newline at end of file diff --git a/src/utils/generator/js.js b/src/utils/generator/js.js new file mode 100644 index 0000000..ee8668d --- /dev/null +++ b/src/utils/generator/js.js @@ -0,0 +1,235 @@ +import { exportDefault, titleCase } from '@/utils/index' +import { trigger } from './config' + +const units = { + KB: '1024', + MB: '1024 / 1024', + GB: '1024 / 1024 / 1024' +} +let confGlobal +const inheritAttrs = { + file: '', + dialog: 'inheritAttrs: false,' +} + + +export function makeUpJs(conf, type) { + confGlobal = conf = JSON.parse(JSON.stringify(conf)) + const dataList = [] + const ruleList = [] + const optionsList = [] + const propsList = [] + const methodList = mixinMethod(type) + const uploadVarList = [] + + conf.fields.forEach(el => { + buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) + }) + + const script = buildexport( + conf, + type, + dataList.join('\n'), + ruleList.join('\n'), + optionsList.join('\n'), + uploadVarList.join('\n'), + propsList.join('\n'), + methodList.join('\n') + ) + confGlobal = null + return script +} + +function buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) { + buildData(el, dataList) + buildRules(el, ruleList) + + if (el.options && el.options.length) { + buildOptions(el, optionsList) + if (el.dataType === 'dynamic') { + const model = `${el.vModel}Options` + const options = titleCase(model) + buildOptionMethod(`get${options}`, model, methodList) + } + } + + if (el.props && el.props.props) { + buildProps(el, propsList) + } + + if (el.action && el.tag === 'el-upload') { + uploadVarList.push( + `${el.vModel}Action: '${el.action}', + ${el.vModel}fileList: [],` + ) + methodList.push(buildBeforeUpload(el)) + if (!el['auto-upload']) { + methodList.push(buildSubmitUpload(el)) + } + } + + if (el.children) { + el.children.forEach(el2 => { + buildAttributes(el2, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) + }) + } +} + +function mixinMethod(type) { + const list = []; const + minxins = { + file: confGlobal.formBtns ? { + submitForm: `submitForm() { + this.$refs['${confGlobal.formRef}'].validate(valid => { + if(!valid) return + // TODO 提交表单 + }) + },`, + resetForm: `resetForm() { + this.$refs['${confGlobal.formRef}'].resetFields() + },` + } : null, + dialog: { + onOpen: 'onOpen() {},', + onClose: `onClose() { + this.$refs['${confGlobal.formRef}'].resetFields() + },`, + close: `close() { + this.$emit('update:visible', false) + },`, + handleConfirm: `handleConfirm() { + this.$refs['${confGlobal.formRef}'].validate(valid => { + if(!valid) return + this.close() + }) + },` + } + } + + const methods = minxins[type] + if (methods) { + Object.keys(methods).forEach(key => { + list.push(methods[key]) + }) + } + + return list +} + +function buildData(conf, dataList) { + if (conf.vModel === undefined) return + let defaultValue + if (typeof (conf.defaultValue) === 'string' && !conf.multiple) { + defaultValue = `'${conf.defaultValue}'` + } else { + defaultValue = `${JSON.stringify(conf.defaultValue)}` + } + dataList.push(`${conf.vModel}: ${defaultValue},`) +} + +function buildRules(conf, ruleList) { + if (conf.vModel === undefined) return + const rules = [] + if (trigger[conf.tag]) { + if (conf.required) { + const type = Array.isArray(conf.defaultValue) ? 'type: \'array\',' : '' + let message = Array.isArray(conf.defaultValue) ? `请至少选择一个${conf.vModel}` : conf.placeholder + if (message === undefined) message = `${conf.label}不能为空` + rules.push(`{ required: true, ${type} message: '${message}', trigger: '${trigger[conf.tag]}' }`) + } + if (conf.regList && Array.isArray(conf.regList)) { + conf.regList.forEach(item => { + if (item.pattern) { + rules.push(`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${trigger[conf.tag]}' }`) + } + }) + } + ruleList.push(`${conf.vModel}: [${rules.join(',')}],`) + } +} + +function buildOptions(conf, optionsList) { + if (conf.vModel === undefined) return + if (conf.dataType === 'dynamic') { conf.options = [] } + const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},` + optionsList.push(str) +} + +function buildProps(conf, propsList) { + if (conf.dataType === 'dynamic') { + conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey) + conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey) + conf.childrenKey !== 'children' && (conf.props.props.children = conf.childrenKey) + } + const str = `${conf.vModel}Props: ${JSON.stringify(conf.props.props)},` + propsList.push(str) +} + +function buildBeforeUpload(conf) { + const unitNum = units[conf.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const + returnList = [] + if (conf.fileSize) { + rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize} + if(!isRightSize){ + this.$message.error('文件大小超过 ${conf.fileSize}${conf.sizeUnit}') + }` + returnList.push('isRightSize') + } + if (conf.accept) { + acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type) + if(!isAccept){ + this.$message.error('应该选择${conf.accept}类型的文件') + }` + returnList.push('isAccept') + } + const str = `${conf.vModel}BeforeUpload(file) { + ${rightSizeCode} + ${acceptCode} + return ${returnList.join('&&')} + },` + return returnList.length ? str : '' +} + +function buildSubmitUpload(conf) { + const str = `submitUpload() { + this.$refs['${conf.vModel}'].submit() + },` + return str +} + +function buildOptionMethod(methodName, model, methodList) { + const str = `${methodName}() { + // TODO 发起请求获取数据 + this.${model} + },` + methodList.push(str) +} + +function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods) { + const str = `${exportDefault}{ + ${inheritAttrs[type]} + components: {}, + props: [], + data () { + return { + ${conf.formModel}: { + ${data} + }, + ${conf.formRules}: { + ${rules} + }, + ${uploadVar} + ${selectOptions} + ${props} + } + }, + computed: {}, + watch: {}, + created () {}, + mounted () {}, + methods: { + ${methods} + } +}` + return str +} diff --git a/src/utils/generator/render.js b/src/utils/generator/render.js new file mode 100644 index 0000000..e8640f0 --- /dev/null +++ b/src/utils/generator/render.js @@ -0,0 +1,126 @@ +import { makeMap } from '@/utils/index' + +// 参考https://github.com/vuejs/vue/blob/v2.6.10/src/platforms/web/server/util.js +const isAttr = makeMap( + 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + + 'target,title,type,usemap,value,width,wrap' +) + +function vModel(self, dataObject, defaultValue) { + dataObject.props.value = defaultValue + + dataObject.on.input = val => { + self.$emit('input', val) + } +} + +const componentChild = { + 'el-button': { + default(h, conf, key) { + return conf[key] + }, + }, + 'el-input': { + prepend(h, conf, key) { + return + }, + append(h, conf, key) { + return + } + }, + 'el-select': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + list.push() + }) + return list + } + }, + 'el-radio-group': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + if (conf.optionType === 'button') list.push({item.label}) + else list.push({item.label}) + }) + return list + } + }, + 'el-checkbox-group': { + options(h, conf, key) { + const list = [] + conf.options.forEach(item => { + if (conf.optionType === 'button') { + list.push({item.label}) + } else { + list.push({item.label}) + } + }) + return list + } + }, + 'el-upload': { + 'list-type': (h, conf, key) => { + const list = [] + if (conf['list-type'] === 'picture-card') { + list.push() + } else { + list.push({conf.buttonText}) + } + if (conf.showTip) { + list.push(
只能上传不超过 {conf.fileSize}{conf.sizeUnit} 的{conf.accept}文件
) + } + return list + } + } +} + +export default { + render(h) { + const dataObject = { + attrs: {}, + props: {}, + on: {}, + style: {} + } + const confClone = JSON.parse(JSON.stringify(this.conf)) + const children = [] + + const childObjs = componentChild[confClone.tag] + if (childObjs) { + Object.keys(childObjs).forEach(key => { + const childFunc = childObjs[key] + if (confClone[key]) { + children.push(childFunc(h, confClone, key)) + } + }) + } + + Object.keys(confClone).forEach(key => { + const val = confClone[key] + if (key === 'vModel') { + vModel(this, dataObject, confClone.defaultValue) + } else if (dataObject[key]) { + dataObject[key] = val + } else if (!isAttr(key)) { + dataObject.props[key] = val + } else { + dataObject.attrs[key] = val + } + }) + return h(this.conf.tag, dataObject, children) + }, + props: ['conf'] +} diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..a17ad90 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,627 @@ +import { FileType } from '@/utils/constants'; +import { parseTime } from '@/utils/ruoyi'; +import Decimal from 'decimal.js'; +/** + * 表格时间格式化 + */ +export function formatDate(cellValue) { + if (cellValue == null || cellValue == "") return ""; + var date = new Date(cellValue) + var year = date.getFullYear() + var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 + var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() + var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds +} + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export function formatTime(time, option) { + if (('' + time).length === 10) { + time = parseInt(time) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param {string} url + * @returns {Object} + */ +export function getQueryObject(url) { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param {string} input value + * @returns {number} output value + */ +export function byteLength(str) { + // returns the byte length of an utf8 string + let s = str.length + for (var i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param {Array} actual + * @returns {Array} + */ +export function cleanArray(actual) { + const newArray = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param {Object} json + * @returns {Array} + */ +export function param(json) { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj(url) { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') + if (!search) { + return {} + } + const obj = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj +} + +/** + * @param {string} val + * @returns {string} + */ +export function html2Text(val) { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText +} + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export function objectMerge(target, source) { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export function toggleClass(element, className) { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += '' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param {string} type + * @returns {Date} + */ +export function getTime(type) { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()) + } +} + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +/** + * @param {Array} arr + * @returns {Array} + */ +export function uniqueArr(arr) { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString() { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536) + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function makeMap(str, expectsLowerCase) { + const map = Object.create(null) + const list = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? val => map[val.toLowerCase()] + : val => map[val] +} + +export const exportDefault = 'export default ' + +export const beautifierConf = { + html: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'separate', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: false, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + }, + js: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'normal', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: true, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + } +} + +// 首字母大小 +export function titleCase(str) { + return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) +} + +// 下划转驼峰 +export function camelCase(str) { + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) +} + +export function isNumberStr(str) { + return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) +} + +// 深度比较两个对象是否相等 +export function isDeepEqual(obj1, obj2) { + if (obj1 === obj2) return true; + + if (typeof obj1 !== typeof obj2) return false; + + if (Array.isArray(obj1)) { + if (!Array.isArray(obj2) || obj1.length !== obj2.length) return false; + for (let i = 0; i < obj1.length; i++) { + if (!isDeepEqual(obj1[i], obj2[i])) return false; + } + } else if (typeof obj1 === 'object' && obj1 !== null) { + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + if (keys1.length !== keys2.length) return false; + for (let key of keys1) { + if (!isDeepEqual(obj1[key], obj2[key])) return false; + } + } else { + // 如果两者都不是数组也不是对象,直接比较值 + return obj1 === obj2; + } + + return true; +} + +export function notNullNum(val) { + if (isEmpty(val)) { + return 0; + } + return val; +} + +/** + * 不为空的数值 + * @param val + * @returns {Decimal} + */ +export function notNullDecimal(val) { + if (isEmpty(val)) { + return new Decimal(0); + } + return new Decimal(val); +} + +/** + * 是否为空 + * @param val + * @returns {boolean} + */ +export function isEmpty(val) { + return val == null || val.length === 0; +} + +// 获取前n天的日期 +export function getLastDate(n) { + let now = new Date(); + return new Date(now.getTime() - n * 24 * 3600 * 1000) +} + +// 获取前n月的日期 +export function getLastMonth(n) { + const date = new Date(); + const year = date.getFullYear(); + const month = date.getMonth(); + const day = date.getDate(); + + // 减去 n 个月 + date.setMonth(month - n); + + // 确保日期不超过当月的最大天数 + const maxDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + date.setDate(Math.min(day, maxDay)); + + return date; +} + +export function getLastMonthTimeEndStr(n) { + let date = getLastMonth(n) + return parseTime(date, "{y}-{m}-{d} 23:59:59"); +} +export function getLastMonthTimeEnd(n) { + return new Date(getLastMonthTimeEndStr(n)); +} + +export function getLastMonthTimeStartStr(n) { + let date = getLastMonth(n) + return parseTime(date, "{y}-{m}-{d} 00:00:00"); +} +export function getLastMonthTimeStart(n) { + return new Date(getLastMonthTimeStartStr(n)) +} + +// 获取前n月的日期字符串 +export function getLastMonthDateStr(n) { + let date = getLastMonth(n); + return parseTime(date, "{y}-{m}-{d}") +} + +// 获取前n天的日期字符串 +export function getLastDateStr(n) { + let date = getLastDate(n); + return parseTime(date, "{y}-{m}-{d}"); +} + +// 获取前n天的日期时间字符串00:00:00 +export function getLastDateTimeStartStr(n) { + let date = getLastDate(n); + return parseTime(date, "{y}-{m}-{d} 00:00:00"); +} + +// 获取前n天的日期时间字符串23:59:59 +export function getLastDateTimeEndStr(n) { + let date = getLastDate(n); + return parseTime(date, "{y}-{m}-{d} 23:59:59"); +} + +// 获取前n天的日期时间00:00:00 +export function getLastDateTimeStart(n) { + return new Date(getLastDateTimeStartStr(n)); +} + +// 获取前n天的日期时间23:59:59 +export function getLastDateTimeEnd(n) { + return new Date(getLastDateTimeEndStr(n)); +} + +// 展示分数 +export function formatFraction(numerator, denominator) { + if (denominator === 1) { + return numerator; + } + return `${numerator}/${denominator}`; +} + +// 获取真实url +export function getRealUrl(url) { + if (url == null ) { + return url; + } + if (url.startsWith('http')) { + return url; + } + return `${process.env.VUE_APP_BASE_API}${url}`; +} + +// 获取文件图标 +export function getFileIcon(fileName) { + const ext = getExt(fileName); + if(['doc', 'docx'].includes(ext)) { + return require('@/assets/fileicons/word.png'); + } else if(['xls', 'xlsx'].includes(ext)) { + return require('@/assets/fileicons/excel.png'); + } else if(['ppt', 'pptx'].includes(ext)) { + return require('@/assets/fileicons/ppt.png'); + } else if(ext === 'pdf') { + return require('@/assets/fileicons/pdf.png'); + } else if(FileType.VIDEO.includes(ext)) { + return require('@/assets/fileicons/video.png'); + } else if(FileType.ZIP.includes(ext)) { + return require('@/assets/fileicons/zip.png'); + } + return require('@/assets/fileicons/unknown.png'); +} + +// 获取文件扩展名 +export function getExt(fileName) { + let fileExtension = ''; + if (fileName.lastIndexOf(".") > -1) { + fileExtension = fileName.slice(fileName.lastIndexOf(".") + 1).toLowerCase(); + } + return fileExtension; +} + +// 是否为办公文件 +export function isOfficeFile(fileName) { + const ext = getExt(fileName); + return FileType.OFFICE.includes(ext); +} +// 是否为音频文件 +export function isAudioFile(fileName) { + const ext = getExt(fileName); + return FileType.AUDIO.includes(ext); +} + +// 是否为图片 +export function isImage(url) { + const ext = getExt(url); + return FileType.IMAGE.includes(ext); +} +// 是否为视频 +export function isVideo(url) { + const ext = getExt(url); + return FileType.VIDEO.includes(ext); +} + +// 为文件名在后缀和名称之间拼接时间戳 +export function getFileNameWithTime(fileName) { + const ext = getExt(fileName); + const name = fileName.slice(0, fileName.lastIndexOf(".")); + return `${name}-${Date.now()}.${ext}`; +} + + +// 获取文件名 +export function getFileName(url) { + if (!url) { + return ''; + } + return url.substring(url.lastIndexOf('/') + 1); +} + +// 获取文件名(不包含扩展名) +export function getOriginName(name) { + if (!name) { + return ''; + } + let lastIndex = name.lastIndexOf('.'); + if (lastIndex >= 0) { + return name.substring(0, lastIndex); + } else { + return name; + } +} + +// 获取字典的label +export function dictLabel(dict, value) { + if (!dict) { + return ''; + } + const dictItem = dict.find(item => item.value === value); + if (!dictItem) { + return ''; + } + return dictItem.label; +} \ No newline at end of file diff --git a/src/utils/jsencrypt.js b/src/utils/jsencrypt.js new file mode 100644 index 0000000..78d9523 --- /dev/null +++ b/src/utils/jsencrypt.js @@ -0,0 +1,30 @@ +import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' + +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' + +const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + + 'UP8iWi1Qw0Y=' + +// 加密 +export function encrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对数据进行加密 +} + +// 解密 +export function decrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) // 设置私钥 + return encryptor.decrypt(txt) // 对数据进行解密 +} + diff --git a/src/utils/mixins.js b/src/utils/mixins.js new file mode 100644 index 0000000..c7dc9b1 --- /dev/null +++ b/src/utils/mixins.js @@ -0,0 +1,210 @@ +import { views } from '@/utils/constants' + +// 视图 +export const $view = { + props: { + view: { + type: String, + default: null + } + }, + data() { + return { + views + } + }, + computed: { + hasView() { + return (views) => { + if (views == null || views.length === 0 || this.view == null) { + return false; + } + let list = views; + if (views instanceof String) { + list = views.split(','); + } + if (!(list instanceof Array)) { + list = [list] + } + return list.includes(this.view); + } + }, + notHasView() { + return (views) => { + return !this.hasView(views); + } + } + } +} + + +/** + * 显隐列 + **/ +export const $showColumns = { + props: { + hideColumns: { + type: Array, + default: () => { + return [] + } + } + }, + data() { + return { + columns: [] + } + }, + computed: { + showColumns() { + if (this.columns == null) { + return []; + } + return this.columns.filter(item => item.visible); + }, + isShow() { + return (key) => { + if (this.columns == null) { + return false; + } + let column = this.columns.find(item => item.key === key); + return column != null && column.visible; + } + } + }, + methods: { + // 初始化列 + initColumns() { + this.hideColumn(this.hideColumns); + }, + // 隐藏列 + hideColumn(columns) { + if (this.columns != null) { + this.columns.filter(item => columns.includes(item.key)) + .forEach(item => { + item.visible = false; + }) + } + }, + // 删除列 + removeColumn(columns) { + if (columns != null) { + columns.forEach(column => { + let index = this.columns.findIndex(item => column === item.key); + if (index != null && index > -1) { + this.columns.splice(index, 1); + } + }) + } + } + } +} + +export const $showSearch = { + props: { + // 删除的查询列 + removeSearchColumns: { + type: Array, + default: () => { + return [] + } + } + }, + data() { + return { + searchColumns: [], + } + }, + methods: { + // 初始化查询列 + initSearchColumns() { + this.removeSearchColumn(this.removeSearchColumns); + }, + // 删除查询列 + removeSearchColumn(columns) { + if (columns != null) { + columns.forEach(column => { + let index = this.searchColumns.findIndex(item => column === item.key); + if (index != null && index > -1) { + this.searchColumns.splice(index, 1); + } + }) + } + }, + } +} + +/** + * 列表选择 + */ +export const $listSelect = { + props: { + // 开启选择模式 + enableSelect: { + type: Boolean, + default: false, + }, + // 开启多选 + enableMultiple: { + type: Boolean, + default: true + } + }, + methods: { + // 切换一行的选中状态 + toggleRowSelection(row, flag) { + this.$refs.table.toggleRowSelection(row, flag); + }, + // 刷新表格的选中状态 + refreshTableSelection() { + if(this.enableMultiple){ + this.tableData.forEach(item => { + if (this.selected.map(j => j[this.prop]).includes(item[this.prop])) { + this.toggleRowSelection(item, true); + } else { + this.toggleRowSelection(item, false); + } + }); + } + }, + } +} + +// 列表页配置项 +export const $listConfig = { + props: { + // 用户自定义配置 + customConfig: { + type: Object, + default: () => { + return {} + } + } + }, + data() { + return { + globalDefaultConfig: { + containerClass: 'app-container', // 容器class + showQuery: true, // 搜索栏 + showRowOpera: true, // 行操作 + showTableOpera: true, // 列操作 + showRefresh: true, // 刷新按钮 + showSearch: true, // 搜索按钮 + showTools: true, // 展示工具栏 + showPage: true, // 分页 + showSelection: true, // 选择栏 + }, + defaultConfig: {} + } + }, + computed: { + // 最终的配置 + listConfig() { + return { + ...this.globalDefaultConfig, + ...this.defaultConfig, + ...this.customConfig + } + } + } +} diff --git a/src/utils/money.js b/src/utils/money.js new file mode 100644 index 0000000..a799952 --- /dev/null +++ b/src/utils/money.js @@ -0,0 +1,9 @@ +import { notNullDecimal } from '@/utils/index' + +// 计算总价 +export function calcMulDecimal(a, b) { + if (a == null || b == null) { + return 0; + } + return notNullDecimal(a).mul(notNullDecimal(b)).toNumber(); +} diff --git a/src/utils/permission.js b/src/utils/permission.js new file mode 100644 index 0000000..44d0549 --- /dev/null +++ b/src/utils/permission.js @@ -0,0 +1,61 @@ +import store from '@/store'; + +/** + * 字符权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkPermi(value) { + if (value && value instanceof Array && value.length > 0) { + const permissions = store.getters && store.getters.permissions + const permissionDatas = value + const all_permission = "*:*:*"; + + const hasPermission = permissions.some(permission => { + return all_permission === permission || permissionDatas.includes(permission) + }) + + return hasPermission; + + } else { + console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) + return false + } +} + +/** + * 角色权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export function checkRole(value) { + if (value && value instanceof Array && value.length > 0) { + const roles = store.getters && store.getters.roles + const permissionRoles = value + const super_admin = "admin"; + + const hasRole = roles.some(role => { + return super_admin === role || permissionRoles.includes(role) + }) + + return hasRole; + + } else { + console.error(`need roles! Like checkRole="['admin','editor']"`) + return false + } +} + +/** + * 是否系统管理员 + */ +export function isSysAdmin() { + return checkRole(['sysAdmin']) +} + +/** + * 是否代理商 + */ +export function isAgent() { + return checkRole(['agent']) +} diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..0152527 --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,152 @@ +import cache from '@/plugins/cache' +import store from '@/store' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate, tansParams } from '@/utils/ruoyi' +import axios from 'axios' +import { Loading, Message, MessageBox } from 'element-ui' +import { saveAs } from 'file-saver' + +let downloadLoadingInstance; +// 是否显示重新登录 +export let isRelogin = { show: false }; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: process.env.VUE_APP_BASE_API, + // 超时 + timeout: 10000 +}) + +// request拦截器 +service.interceptors.request.use(config => { + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 + const limitSize = 5 * 1024 * 1024; // 限制存放数据5M + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') + return config; + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } + } + } + return config +}, error => { + console.log(error) + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use(res => { + // 未设置状态码则默认成功状态 + const code = res.data.code || 200; + // 获取错误信息 + const msg = errorCode[code] || res.data.msg || errorCode['default'] + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + if (code === 401) { + if (!isRelogin.show) { + isRelogin.show = true; + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + isRelogin.show = false; + store.dispatch('LogOut').then(() => { + location.href = '/index'; + }) + }).catch(() => { + isRelogin.show = false; + }); + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + } else if (code === 500) { + Message({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code === 601) { + Message({ message: msg, type: 'warning' }) + return Promise.reject('error') + } else if (code !== 200) { + Message({ message: msg, type: 'error' }) + return Promise.reject('error') + } else { + return res.data + } + }, + error => { + console.log('err' + error) + let { message } = error; + if (message == "Network Error") { + message = "后端接口连接异常"; + } else if (message.includes("timeout")) { + message = "系统接口请求超时"; + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.substr(message.length - 3) + "异常"; + } + Message({ message: message, type: 'error', duration: 5 * 1000 }) + return Promise.reject(error) + } +) + +// 通用下载方法 +export function download(url, params, filename, config) { + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + return service.post(url, params, { + transformRequest: [(params) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob', + ...config + }).then(async (data) => { + const isBlob = blobValidate(data); + if (isBlob) { + const blob = new Blob([data]) + saveAs(blob, filename) + } else { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) +} + +export default service diff --git a/src/utils/ruoyi.js b/src/utils/ruoyi.js new file mode 100644 index 0000000..44bf9c4 --- /dev/null +++ b/src/utils/ruoyi.js @@ -0,0 +1,233 @@ + + +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +// 表单重置 +export function resetForm(refName) { + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } +} + +// 添加日期范围 +export function addDateRange(params, dateRange, propName) { + let search = params; + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; +} + +// 回显数据字典 +export function selectDictLabel(datas, value) { + if (value === undefined) { + return ""; + } + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label); + return true; + } + }) + if (actions.length === 0) { + actions.push(value); + } + return actions.join(''); +} + +// 回显数据字典(字符串、数组) +export function selectDictLabels(datas, value, separator) { + if (value === undefined || value.length ===0) { + return ""; + } + if (Array.isArray(value)) { + value = value.join(","); + } + var actions = []; + var currentSeparator = undefined === separator ? "," : separator; + var temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + var match = false; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator); + match = true; + } + }) + if (!match) { + actions.push(temp[val] + currentSeparator); + } + }) + return actions.join('').substring(0, actions.join('').length - 1); +} + +// 字符串格式化(%s ) +export function sprintf(str) { + var args = arguments, flag = true, i = 1; + str = str.replace(/%s/g, function () { + var arg = args[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; +} + +// 转换字符串,undefined,null等转化为"" +export function parseStrEmpty(str) { + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; +} + +// 数据合并 +export function mergeRecursive(source, target) { + for (var p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; + } + } + return source; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export function handleTree(data, id, parentId, children) { + let config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; + + var childrenListMap = {}; + var nodeIds = {}; + var tree = []; + + for (let d of data) { + let parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (let d of data) { + let parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + + for (let t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (let c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + return tree; +} + +/** +* 参数处理 +* @param {*} params 参数 +*/ +export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result +} + +// 验证是否为blob格式 +export function blobValidate(data) { + return data.type !== 'application/json' +} diff --git a/src/utils/scroll-to.js b/src/utils/scroll-to.js new file mode 100644 index 0000000..c5d8e04 --- /dev/null +++ b/src/utils/scroll-to.js @@ -0,0 +1,58 @@ +Math.easeInOutQuad = function(t, b, c, d) { + t /= d / 2 + if (t < 1) { + return c / 2 * t * t + b + } + t-- + return -c / 2 * (t * (t - 2) - 1) + b +} + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +var requestAnimFrame = (function() { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } +})() + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +function move(amount) { + document.documentElement.scrollTop = amount + document.body.parentNode.scrollTop = amount + document.body.scrollTop = amount +} + +function position() { + return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop +} + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export function scrollTo(to, duration, callback) { + const start = position() + const change = to - start + const increment = 20 + let currentTime = 0 + duration = (typeof (duration) === 'undefined') ? 500 : duration + var animateScroll = function() { + // increment the time + currentTime += increment + // find the value with the quadratic in-out easing function + var val = Math.easeInOutQuad(currentTime, start, change, duration) + // move the document.body + move(val) + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll) + } else { + if (callback && typeof (callback) === 'function') { + // the animation is done so lets callback + callback() + } + } + } + animateScroll() +} diff --git a/src/utils/validate.js b/src/utils/validate.js new file mode 100644 index 0000000..57a568e --- /dev/null +++ b/src/utils/validate.js @@ -0,0 +1,80 @@ +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUsername(str) { + const valid_map = ['admin', 'editor'] + return valid_map.indexOf(str.trim()) >= 0 +} + +/** + * @param {string} url + * @returns {Boolean} + */ +export function validURL(url) { + const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ + return reg.test(url) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validLowerCase(str) { + const reg = /^[a-z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validUpperCase(str) { + const reg = /^[A-Z]+$/ + return reg.test(str) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function validAlphabets(str) { + const reg = /^[A-Za-z]+$/ + return reg.test(str) +} + +/** + * @param {string} email + * @returns {Boolean} + */ +export function validEmail(email) { + const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return reg.test(email) +} + +/** + * @param {string} str + * @returns {Boolean} + */ +export function isString(str) { + return typeof str === 'string' || str instanceof String; +} + +/** + * @param {Array} arg + * @returns {Boolean} + */ +export function isArray(arg) { + if (typeof Array.isArray === 'undefined') { + return Object.prototype.toString.call(arg) === '[object Array]' + } + return Array.isArray(arg) +} diff --git a/src/views/bst/commandLog/index.vue b/src/views/bst/commandLog/index.vue new file mode 100644 index 0000000..094a228 --- /dev/null +++ b/src/views/bst/commandLog/index.vue @@ -0,0 +1,314 @@ + + + diff --git a/src/views/dashboard/mixins/resize.js b/src/views/dashboard/mixins/resize.js new file mode 100644 index 0000000..b1e76e9 --- /dev/null +++ b/src/views/dashboard/mixins/resize.js @@ -0,0 +1,56 @@ +import { debounce } from '@/utils' + +export default { + data() { + return { + $_sidebarElm: null, + $_resizeHandler: null + } + }, + mounted() { + this.initListener() + }, + activated() { + if (!this.$_resizeHandler) { + // avoid duplication init + this.initListener() + } + + // when keep-alive chart activated, auto resize + this.resize() + }, + beforeDestroy() { + this.destroyListener() + }, + deactivated() { + this.destroyListener() + }, + methods: { + // use $_ for mixins properties + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential + $_sidebarResizeHandler(e) { + if (e.propertyName === 'width') { + this.$_resizeHandler() + } + }, + initListener() { + this.$_resizeHandler = debounce(() => { + this.resize() + }, 100) + window.addEventListener('resize', this.$_resizeHandler) + + this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0] + this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler) + }, + destroyListener() { + window.removeEventListener('resize', this.$_resizeHandler) + this.$_resizeHandler = null + + this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler) + }, + resize() { + const { chart } = this + chart && chart.resize() + } + } +} diff --git a/src/views/error/401.vue b/src/views/error/401.vue new file mode 100644 index 0000000..448b6ec --- /dev/null +++ b/src/views/error/401.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/views/error/404.vue b/src/views/error/404.vue new file mode 100644 index 0000000..96f075c --- /dev/null +++ b/src/views/error/404.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/src/views/login.vue b/src/views/login.vue new file mode 100644 index 0000000..6a1ce8e --- /dev/null +++ b/src/views/login.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/src/views/mobile/liveness/index.vue b/src/views/mobile/liveness/index.vue new file mode 100644 index 0000000..e126bcc --- /dev/null +++ b/src/views/mobile/liveness/index.vue @@ -0,0 +1,63 @@ + + + + diff --git a/src/views/monitor/cache/index.vue b/src/views/monitor/cache/index.vue new file mode 100644 index 0000000..e5fb542 --- /dev/null +++ b/src/views/monitor/cache/index.vue @@ -0,0 +1,148 @@ + + + diff --git a/src/views/monitor/cache/list.vue b/src/views/monitor/cache/list.vue new file mode 100644 index 0000000..1404614 --- /dev/null +++ b/src/views/monitor/cache/list.vue @@ -0,0 +1,248 @@ + + + diff --git a/src/views/monitor/druid/index.vue b/src/views/monitor/druid/index.vue new file mode 100644 index 0000000..83b32d1 --- /dev/null +++ b/src/views/monitor/druid/index.vue @@ -0,0 +1,16 @@ + + diff --git a/src/views/monitor/job/index.vue b/src/views/monitor/job/index.vue new file mode 100644 index 0000000..3018bff --- /dev/null +++ b/src/views/monitor/job/index.vue @@ -0,0 +1,513 @@ + + + diff --git a/src/views/monitor/job/log.vue b/src/views/monitor/job/log.vue new file mode 100644 index 0000000..c8ae929 --- /dev/null +++ b/src/views/monitor/job/log.vue @@ -0,0 +1,295 @@ + + + diff --git a/src/views/monitor/logininfor/index.vue b/src/views/monitor/logininfor/index.vue new file mode 100644 index 0000000..074db5a --- /dev/null +++ b/src/views/monitor/logininfor/index.vue @@ -0,0 +1,246 @@ + + + + diff --git a/src/views/monitor/online/index.vue b/src/views/monitor/online/index.vue new file mode 100644 index 0000000..94f7fd3 --- /dev/null +++ b/src/views/monitor/online/index.vue @@ -0,0 +1,122 @@ + + + + diff --git a/src/views/monitor/operlog/index.vue b/src/views/monitor/operlog/index.vue new file mode 100644 index 0000000..3c1af97 --- /dev/null +++ b/src/views/monitor/operlog/index.vue @@ -0,0 +1,337 @@ + + + + diff --git a/src/views/monitor/server/index.vue b/src/views/monitor/server/index.vue new file mode 100644 index 0000000..dc5c232 --- /dev/null +++ b/src/views/monitor/server/index.vue @@ -0,0 +1,207 @@ + + + diff --git a/src/views/redirect.vue b/src/views/redirect.vue new file mode 100644 index 0000000..db4c1d6 --- /dev/null +++ b/src/views/redirect.vue @@ -0,0 +1,12 @@ + diff --git a/src/views/register.vue b/src/views/register.vue new file mode 100644 index 0000000..848869e --- /dev/null +++ b/src/views/register.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue new file mode 100644 index 0000000..de9f564 --- /dev/null +++ b/src/views/system/config/index.vue @@ -0,0 +1,343 @@ + + + diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue new file mode 100644 index 0000000..1283c90 --- /dev/null +++ b/src/views/system/dept/index.vue @@ -0,0 +1,318 @@ + + + diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue new file mode 100644 index 0000000..b04c4a3 --- /dev/null +++ b/src/views/system/dict/data.vue @@ -0,0 +1,402 @@ + + + diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue new file mode 100644 index 0000000..4621623 --- /dev/null +++ b/src/views/system/dict/index.vue @@ -0,0 +1,347 @@ + + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..ac27534 --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,453 @@ + + + diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue new file mode 100644 index 0000000..6fc85f5 --- /dev/null +++ b/src/views/system/notice/index.vue @@ -0,0 +1,313 @@ + + + diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue new file mode 100644 index 0000000..6b3e826 --- /dev/null +++ b/src/views/system/post/index.vue @@ -0,0 +1,309 @@ + + + diff --git a/src/views/system/role/authUser.vue b/src/views/system/role/authUser.vue new file mode 100644 index 0000000..5078471 --- /dev/null +++ b/src/views/system/role/authUser.vue @@ -0,0 +1,199 @@ + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..d2fe69f --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,625 @@ + + + diff --git a/src/views/system/role/selectUser.vue b/src/views/system/role/selectUser.vue new file mode 100644 index 0000000..d11f9ac --- /dev/null +++ b/src/views/system/role/selectUser.vue @@ -0,0 +1,137 @@ + + + diff --git a/src/views/system/user/UserLink.vue b/src/views/system/user/UserLink.vue new file mode 100644 index 0000000..ef8bc48 --- /dev/null +++ b/src/views/system/user/UserLink.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/views/system/user/authRole.vue b/src/views/system/user/authRole.vue new file mode 100644 index 0000000..c00b379 --- /dev/null +++ b/src/views/system/user/authRole.vue @@ -0,0 +1,117 @@ + + + diff --git a/src/views/system/user/components/UserFormDialog.vue b/src/views/system/user/components/UserFormDialog.vue new file mode 100644 index 0000000..5095023 --- /dev/null +++ b/src/views/system/user/components/UserFormDialog.vue @@ -0,0 +1,240 @@ + + + \ No newline at end of file diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..d04a544 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,428 @@ + + + diff --git a/src/views/system/user/profile/index.vue b/src/views/system/user/profile/index.vue new file mode 100644 index 0000000..4d2622e --- /dev/null +++ b/src/views/system/user/profile/index.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/views/system/user/profile/resetPwd.vue b/src/views/system/user/profile/resetPwd.vue new file mode 100644 index 0000000..6dcb6a0 --- /dev/null +++ b/src/views/system/user/profile/resetPwd.vue @@ -0,0 +1,69 @@ + + + diff --git a/src/views/system/user/profile/userAvatar.vue b/src/views/system/user/profile/userAvatar.vue new file mode 100644 index 0000000..8b5c71c --- /dev/null +++ b/src/views/system/user/profile/userAvatar.vue @@ -0,0 +1,184 @@ + + + + diff --git a/src/views/system/user/profile/userInfo.vue b/src/views/system/user/profile/userInfo.vue new file mode 100644 index 0000000..7254b93 --- /dev/null +++ b/src/views/system/user/profile/userInfo.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/views/system/user/view/components/UserStatistics.vue b/src/views/system/user/view/components/UserStatistics.vue new file mode 100644 index 0000000..3479784 --- /dev/null +++ b/src/views/system/user/view/components/UserStatistics.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/views/system/user/view/view.vue b/src/views/system/user/view/view.vue new file mode 100644 index 0000000..92e04c0 --- /dev/null +++ b/src/views/system/user/view/view.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/src/views/tool/build/CodeTypeDialog.vue b/src/views/tool/build/CodeTypeDialog.vue new file mode 100644 index 0000000..b5c2e2e --- /dev/null +++ b/src/views/tool/build/CodeTypeDialog.vue @@ -0,0 +1,106 @@ + + diff --git a/src/views/tool/build/DraggableItem.vue b/src/views/tool/build/DraggableItem.vue new file mode 100644 index 0000000..e881778 --- /dev/null +++ b/src/views/tool/build/DraggableItem.vue @@ -0,0 +1,100 @@ + diff --git a/src/views/tool/build/IconsDialog.vue b/src/views/tool/build/IconsDialog.vue new file mode 100644 index 0000000..958be50 --- /dev/null +++ b/src/views/tool/build/IconsDialog.vue @@ -0,0 +1,123 @@ + + + diff --git a/src/views/tool/build/RightPanel.vue b/src/views/tool/build/RightPanel.vue new file mode 100644 index 0000000..bdfd3e4 --- /dev/null +++ b/src/views/tool/build/RightPanel.vue @@ -0,0 +1,942 @@ + + + + + diff --git a/src/views/tool/build/TreeNodeDialog.vue b/src/views/tool/build/TreeNodeDialog.vue new file mode 100644 index 0000000..fa7f0b2 --- /dev/null +++ b/src/views/tool/build/TreeNodeDialog.vue @@ -0,0 +1,149 @@ + + diff --git a/src/views/tool/build/index.vue b/src/views/tool/build/index.vue new file mode 100644 index 0000000..6d88c51 --- /dev/null +++ b/src/views/tool/build/index.vue @@ -0,0 +1,768 @@ + + + + + diff --git a/src/views/tool/gen/basicInfoForm.vue b/src/views/tool/gen/basicInfoForm.vue new file mode 100644 index 0000000..7029529 --- /dev/null +++ b/src/views/tool/gen/basicInfoForm.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/views/tool/gen/createTable.vue b/src/views/tool/gen/createTable.vue new file mode 100644 index 0000000..695cf88 --- /dev/null +++ b/src/views/tool/gen/createTable.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/views/tool/gen/editTable.vue b/src/views/tool/gen/editTable.vue new file mode 100644 index 0000000..88103b0 --- /dev/null +++ b/src/views/tool/gen/editTable.vue @@ -0,0 +1,234 @@ + + + diff --git a/src/views/tool/gen/genInfoForm.vue b/src/views/tool/gen/genInfoForm.vue new file mode 100644 index 0000000..5135502 --- /dev/null +++ b/src/views/tool/gen/genInfoForm.vue @@ -0,0 +1,312 @@ + + + diff --git a/src/views/tool/gen/importTable.vue b/src/views/tool/gen/importTable.vue new file mode 100644 index 0000000..5e7aba0 --- /dev/null +++ b/src/views/tool/gen/importTable.vue @@ -0,0 +1,121 @@ + + + diff --git a/src/views/tool/gen/index.vue b/src/views/tool/gen/index.vue new file mode 100644 index 0000000..e502fd7 --- /dev/null +++ b/src/views/tool/gen/index.vue @@ -0,0 +1,355 @@ + + + diff --git a/src/views/tool/k3/FieldConverter.vue b/src/views/tool/k3/FieldConverter.vue new file mode 100644 index 0000000..cdb1113 --- /dev/null +++ b/src/views/tool/k3/FieldConverter.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/views/tool/k3/index.vue b/src/views/tool/k3/index.vue new file mode 100644 index 0000000..692fc2c --- /dev/null +++ b/src/views/tool/k3/index.vue @@ -0,0 +1,18 @@ + + diff --git a/src/views/tool/swagger/index.vue b/src/views/tool/swagger/index.vue new file mode 100644 index 0000000..16ca702 --- /dev/null +++ b/src/views/tool/swagger/index.vue @@ -0,0 +1,16 @@ + + diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..6be1a41 --- /dev/null +++ b/vue.config.js @@ -0,0 +1,137 @@ +'use strict' +const path = require('path') + +function resolve(dir) { + return path.join(__dirname, dir) +} + +const CompressionPlugin = require('compression-webpack-plugin') + +const name = process.env.VUE_APP_TITLE || '小鹿骑行管理系统' // 网页标题 + +const port = process.env.port || process.env.npm_config_port || 4100 // 端口 + +// vue.config.js 配置说明 +//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions +// 这里只列一部分,具体配置参考文档 +module.exports = { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + publicPath: process.env.NODE_ENV === "production" ? "/" : "/", + // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) + outputDir: 'dist', + // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) + assetsDir: 'static', + // 是否开启eslint保存检测,有效值:ture | false | 'error' + lintOnSave: process.env.NODE_ENV === 'development', + // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 + productionSourceMap: false, + transpileDependencies: ['quill'], + // webpack-dev-server 相关配置 + devServer: { + host: '0.0.0.0', + port: port, + open: true, + proxy: { + // detail: https://cli.vuejs.org/config/#devserver-proxy + [process.env.VUE_APP_BASE_API]: { + target: `http://localhost:4101`, + // target: `https://ele.ccttiot.com/prod-api`, + changeOrigin: true, + pathRewrite: { + ['^' + process.env.VUE_APP_BASE_API]: '' + } + } + }, + disableHostCheck: true + }, + css: { + loaderOptions: { + sass: { + sassOptions: { outputStyle: "expanded" } + } + } + }, + configureWebpack: { + name: name, + resolve: { + alias: { + '@': resolve('src') + } + }, + plugins: [ + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + new CompressionPlugin({ + cache: false, // 不启用文件缓存 + test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式 + filename: '[path][base].gz[query]', // 压缩后的文件名 + algorithm: 'gzip', // 使用gzip压缩 + minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩 + deleteOriginalAssets: false // 压缩后删除原文件 + }) + ], + }, + chainWebpack(config) { + config.plugins.delete('preload') // TODO: need test + config.plugins.delete('prefetch') // TODO: need test + + // set svg-sprite-loader + config.module + .rule('svg') + .exclude.add(resolve('src/assets/icons')) + .end() + config.module + .rule('icons') + .test(/\.svg$/) + .include.add(resolve('src/assets/icons')) + .end() + .use('svg-sprite-loader') + .loader('svg-sprite-loader') + .options({ + symbolId: 'icon-[name]' + }) + .end() + + config.when(process.env.NODE_ENV !== 'development', config => { + config + .plugin('ScriptExtHtmlWebpackPlugin') + .after('html') + .use('script-ext-html-webpack-plugin', [{ + // `runtime` must same as runtimeChunk name. default is `runtime` + inline: /runtime\..*\.js$/ + }]) + .end() + + config.optimization.splitChunks({ + chunks: 'all', + cacheGroups: { + libs: { + name: 'chunk-libs', + test: /[\\/]node_modules[\\/]/, + priority: 10, + chunks: 'initial' // only package third parties that are initially dependent + }, + elementUI: { + name: 'chunk-elementUI', // split elementUI into a single package + test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm + priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app + }, + commons: { + name: 'chunk-commons', + test: resolve('src/components'), // can customize your rules + minChunks: 3, // minimum common number + priority: 5, + reuseExistingChunk: true + } + } + }) + config.optimization.runtimeChunk('single') + }) + + config.plugin('html').tap(args => { + args[0].version = new Date().getTime() + return args + }) + } +}