提交 52c8b478 编写于 作者: JEECG低代码平台's avatar JEECG低代码平台

jeecg-boot 1.0版本发布

上级 e7fa2b23
Jeecg-Boot 快速开发平台(前后端分离版本)
===============
当前最新版本: 1.0(发布日期:20190225)
项目介绍:
-----------------------------------
Jeecg-boot 一个全新的版本,采用前后端分离方案,提供强大代码生成器的快速开发平台
前端页面代码和后端功能代码一键生成,不需要写任何代码,保持jeecg一贯的强大!!
技术架构:
-----------------------------------
后端技术: SpringBoot + Mybatis-plus + Shiro + Jwt + Swagger-ui + Redis
前端技术: Ant-design-vue + Vue + Webpack
其他技术: Druid(数据库连接池)、Logback(日志工具) 、poi(Excel工具)、
Quartz(定时任务)、lombok(简化代码)
项目构建: Maven、Jdk8
前端开发必读文档:
前端UI组件: Ant Design of Vue
https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn
报表UI组件:viser-vue
https://viserjs.github.io/demo.html#/viser/bar/basic-bar
VUE基础知识:
https://cn.vuejs.org/v2/guide
Overview
----
基于 [Ant Design of Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/) 实现的 Vue 版
[预览地址](http://boot.jeecg.org) **附带一些后台基础用到的列表展示例子**
效果抢先看:
1. 系统效果
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154007_icdX.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25153956_Q752.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201901/07154149_555Q.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154209_qlCg.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154251_XoW9.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154331_0ndT.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154414_ckFS.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155155_Hm6H.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155213_T04n.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155224_MRLU.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155234_7zCP.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155242_K7Sw.png "在这里输入图片标题")
技术文档
-----------------------------------
* [在线演示](http://boot.jeecg.org)
* [官方文档](http://jeecg-boot.mydoc.io)
* QQ交流群:284271917
前端开发环境和依赖
----
- node
- yarn
- webpack
- eslint
- @vue/cli 3.2.1
- [ant-design-vue](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation) - antv/g2 封装实现
项目下载和运行
----
- 拉取项目代码
```bash
git clone https://github.com/zhangdaiscott/jeecg-boot.git
cd jeecg-boot/ant-design-jeecg-vue
```
- 安装依赖
```
yarn install
```
- 开发模式运行
```
yarn run serve
```
- 编译项目
```
yarn run build
```
- Lints and fixes files
```
yarn run lint
```
其他说明
----
- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请更新您的 cli
- 关闭 Eslint (不推荐) 移除 `package.json``eslintConfig` 整个节点代码
- 修改 Ant Design 配色,在文件 `vue.config.js` 中,其他 less 变量覆盖参考 [ant design](https://ant.design/docs/react/customize-theme-cn) 官方说明
```ecmascript 6
css: {
loaderOptions: {
less: {
modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */
'primary-color': '#F5222D',
'link-color': '#F5222D',
'border-radius-base': '4px',
},
javascriptEnabled: true,
}
}
}
```
附属文档
----
- [路由/菜单说明](https://github.com/sendya/ant-design-pro-vue/blob/master/src/router/README.md)
- [ANTD 默认配置项](https://github.com/sendya/ant-design-pro-vue/blob/master/src/defaultSettings.js)
- 其他待补充...
备注
----
> @vue/cli 升级后,eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules
\ No newline at end of file
[*]
charset=utf-8
end_of_line=crlf
insert_final_newline=false
indent_style=space
indent_size=2
[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}]
indent_style=space
indent_size=2
[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}]
indent_style=space
indent_size=2
[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
indent_style=space
indent_size=2
[*.svg]
indent_style=space
indent_size=2
[*.js.map]
indent_style=space
indent_size=2
[*.less]
indent_style=space
indent_size=2
[*.vue]
indent_style=space
indent_size=2
[{.analysis_options,*.yml,*.yaml}]
indent_style=space
indent_size=2
public/* linguist-vendored
\ No newline at end of file
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
{
"printWidth": 120,
"semi": false,
"singleQuote": true
}
MIT License
Copyright (c) 2018 Anan Yang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
\ No newline at end of file
Jeecg-Boot 快速开发平台(前后端分离版本)
===============
当前最新版本: 1.0(发布日期:20190225)
项目介绍:
-----------------------------------
Jeecg-boot 一个全新的版本,采用前后端分离方案,提供强大代码生成器的快速开发平台
前端页面代码和后端功能代码一键生成,不需要写任何代码,保持jeecg一贯的强大!!
技术架构:
-----------------------------------
后端技术: SpringBoot + Mybatis-plus + Shiro + Jwt + Swagger-ui + Redis
前端技术: Ant-design-vue + Vue + Webpack
其他技术: Druid(数据库连接池)、Logback(日志工具) 、poi(Excel工具)、
Quartz(定时任务)、lombok(简化代码)
项目构建: Maven、Jdk8
前端开发必读文档:
前端UI组件: Ant Design of Vue
https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn
报表UI组件:viser-vue
https://viserjs.github.io/demo.html#/viser/bar/basic-bar
VUE基础知识:
https://cn.vuejs.org/v2/guide
Overview
----
基于 [Ant Design of Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/) 实现的 Vue 版
[预览地址](http://boot.jeecg.org) **附带一些后台基础用到的列表展示例子**
效果抢先看:
1. 系统效果
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154007_icdX.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25153956_Q752.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201901/07154149_555Q.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154209_qlCg.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154251_XoW9.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154331_0ndT.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25154414_ckFS.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155155_Hm6H.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155213_T04n.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155224_MRLU.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155234_7zCP.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201902/25155242_K7Sw.png "在这里输入图片标题")
技术文档
-----------------------------------
* [在线演示](http://boot.jeecg.org)
* [官方文档](http://jeecg-boot.mydoc.io)
* QQ交流群:284271917
前端开发环境和依赖
----
- node
- yarn
- webpack
- eslint
- @vue/cli 3.2.1
- [ant-design-vue](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation) - antv/g2 封装实现
项目下载和运行
----
- 拉取项目代码
```bash
git clone https://github.com/zhangdaiscott/jeecg-boot.git
cd jeecg-boot/ant-design-jeecg-vue
```
- 安装依赖
```
yarn install
```
- 开发模式运行
```
yarn run serve
```
- 编译项目
```
yarn run build
```
- Lints and fixes files
```
yarn run lint
```
其他说明
----
- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请更新您的 cli
- 关闭 Eslint (不推荐) 移除 `package.json``eslintConfig` 整个节点代码
- 修改 Ant Design 配色,在文件 `vue.config.js` 中,其他 less 变量覆盖参考 [ant design](https://ant.design/docs/react/customize-theme-cn) 官方说明
```ecmascript 6
css: {
loaderOptions: {
less: {
modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */
'primary-color': '#F5222D',
'link-color': '#F5222D',
'border-radius-base': '4px',
},
javascriptEnabled: true,
}
}
}
```
附属文档
----
- [路由/菜单说明](https://github.com/sendya/ant-design-pro-vue/blob/master/src/router/README.md)
- [ANTD 默认配置项](https://github.com/sendya/ant-design-pro-vue/blob/master/src/defaultSettings.js)
- 其他待补充...
备注
----
> @vue/cli 升级后,eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules
\ No newline at end of file
module.exports = {
presets: [
'@vue/app'
]
}
'use strict'
const path = require('path')
function resolve (dir) {
return path.join(__dirname, '.', dir)
}
module.exports = {
context: path.resolve(__dirname, './'),
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'config': resolve('config'),
'@': resolve('src'),
'@views': resolve('src/views'),
'@comp': resolve('src/components'),
'@core': resolve('src/core'),
'@utils': resolve('src/utils'),
'@entry': resolve('src/entry'),
'@router': resolve('src/router'),
'@store': resolve('src/store')
}
},
}
\ No newline at end of file
{
"name": "vue-antd-pro",
"version": "1.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e"
},
"dependencies": {
"@antv/data-set": "^0.10.1",
"ant-design-vue": "^1.3.1",
"axios": "^0.18.0",
"dayjs": "^1.8.0",
"enquire.js": "^2.1.6",
"js-cookie": "^2.2.0",
"lodash.get": "^4.4.2",
"lodash.pick": "^4.4.0",
"md5": "^2.2.1",
"nprogress": "^0.2.0",
"viser-vue": "^2.4.4",
"vue": "^2.5.22",
"vue-class-component": "^6.0.0",
"vue-cropper": "^0.4.8",
"vue-i18n": "^8.7.0",
"vue-ls": "^3.2.0",
"vue-print-nb": "^1.0.3",
"vue-property-decorator": "^7.3.0",
"vue-router": "^3.0.1",
"vuex": "^3.0.1",
"vuex-class": "^0.3.1"
},
"devDependencies": {
"@babel/polyfill": "^7.2.5",
"@vue/cli-plugin-babel": "^3.3.0",
"@vue/cli-plugin-eslint": "^3.3.0",
"@vue/cli-service": "^3.3.0",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.12.0",
"eslint-plugin-vue": "^5.1.0",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"node-sass": "^4.11.0",
"sass-loader": "^7.0.1",
"vue-template-compiler": "^2.5.22"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/strongly-recommended",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"generator-star-spacing": "off",
"no-mixed-operators": 0,
"vue/max-attributes-per-line": [
2,
{
"singleline": 5,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}
],
"vue/attribute-hyphenation": 0,
"vue/html-self-closing": 0,
"vue/component-name-in-template-casing": 0,
"vue/html-closing-bracket-spacing": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/no-unused-components": 0,
"vue/multiline-html-element-content-newline": 0,
"vue/no-use-v-if-with-v-for": 0,
"vue/html-closing-bracket-newline": 0,
"vue/no-parsing-error": 0,
"no-console": 0
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
此差异已折叠。
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Jeecg-Boot 快速开发平台(Ant Design Vue)</title>
<link rel="icon" href="<%= BASE_URL %>logo.png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.js"></script>
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 120px;
height: 120px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
/* COLOR 1 */
border-top-color: #FFF;
-webkit-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-moz-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 2s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
z-index: 1001;
}
#loader:before {
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
/* COLOR 2 */
border-top-color: #FFF;
-webkit-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-moz-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 3s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
}
#loader:after {
content: "";
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
/* COLOR 3 */
-moz-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-webkit-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 1.5s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(0deg);
/* IE 9 */
transform: rotate(0deg);
/* Firefox 16+, IE 10+, Opera */
}
100% {
-webkit-transform: rotate(360deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(360deg);
/* IE 9 */
transform: rotate(360deg);
/* Firefox 16+, IE 10+, Opera */
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(0deg);
/* IE 9 */
transform: rotate(0deg);
/* Firefox 16+, IE 10+, Opera */
}
100% {
-webkit-transform: rotate(360deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(360deg);
/* IE 9 */
transform: rotate(360deg);
/* Firefox 16+, IE 10+, Opera */
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #49a9ee;
/* Old browsers */
z-index: 1000;
-webkit-transform: translateX(0);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(0);
/* IE 9 */
transform: translateX(0);
/* Firefox 16+, IE 10+, Opera */
}
#loader-wrapper .loader-section.section-left {
left: 0;
}
#loader-wrapper .loader-section.section-right {
right: 0;
}
/* Loaded */
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(-100%);
/* IE 9 */
transform: translateX(-100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(100%);
/* IE 9 */
transform: translateX(100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader {
opacity: 0;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.loaded #loader-wrapper {
visibility: hidden;
-webkit-transform: translateY(-100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateY(-100%);
/* IE 9 */
transform: translateY(-100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out;
}
/* JavaScript Turned Off */
.no-js #loader-wrapper {
display: none;
}
.no-js h1 {
color: #222222;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-size: 14px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 60%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 14px;
color: #FFF;
opacity: 0.5;
}
</style>
</head>
<body>
<!-- built files will be auto injected -->
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载 Jeecg-Boot 快速开发平台(Ant Design Vue),请耐心等待
</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
!function(){"use strict";function e(e){return e===undefined||null===e}function t(e){return e!==undefined&&null!==e}function n(e){return null!==e&&"object"===(void 0===e?"undefined":d(e))}function r(e){return"object"===(void 0===e?"undefined":d(e))&&e instanceof HTMLElement}function o(e){var t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function i(){return new RegExp("MSIE (\\d+\\.\\d+);").test(navigator.userAgent),parseFloat(RegExp.$1)||Infinity}function a(e,t){for(var n in t)e[n]=t[n];return e}function c(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}function u(e){return p.call(e).slice(8,-1)}function s(e){throw new Error("Vaptcha error: "+e)}function l(e){var t=e.mode,n=e.outage;return!!t&&"downtime"===(t=t.toLowerCase())&&!!n}function Promise(e){var t=this;this.state="pending",this.value=undefined,this.reason=undefined,this.onResolveAsyncCallbacks=[],this.onRejectAsyncCallbacks=[];var n=function(e){"pending"===t.state&&(t.state="fulfilled",t.value=e,t.onResolveAsyncCallbacks.map(function(e){return e()}))},r=function(e){"pending"===t.state&&(t.state="rejected",t.reason=e,t.onRejectAsyncCallbacks.map(function(t){return t(e)}))};try{e(n,r)}catch(o){r(o)}}window.HTMLElement=window.HTMLElement||Element,Array.prototype.map||(Array.prototype.map=function(e,t){var n,r,o;if(null==this)throw new TypeError(" this is null or not defined");var i=Object(this),a=i.length>>>0;if("[object Function]"!=Object.prototype.toString.call(e))throw new TypeError(e+" is not a function");for(t&&(n=t),r=new Array(a),o=0;o<a;){var c,u;o in i&&(c=i[o],u=e.call(n,c,o,i),r[o]=u),o++}return r}),Array.prototype.includes||(Array.prototype.includes=function(e,t){if(null==this)throw new TypeError('"this" is null or not defined');var n=Object(this),r=n.length>>>0;if(0===r)return!1;for(var o=0|t,i=Math.max(o>=0?o:r-Math.abs(o),0);i<r;){if(n[i]===e)return!0;i++}return!1}),Array.prototype.findIndex||(Array.prototype.findIndex=function(e){if(null==this)throw new TypeError('"this" is null or not defined');var t=Object(this),n=t.length>>>0;if("function"!=typeof e)throw new TypeError("predicate must be a function");for(var r=arguments[1],o=0;o<n;){if(e.call(r,t[o],o,t))return o;o++}return-1}),Object.create||(Object.create=function(e){var t=function(){};return t.prototype=e,new t});var f={vid:null,scene:"",container:null,type:"float",style:"dark",lang:"zh-CN",ai:!0,https:!0,guide:!0,aiAnimation:!0,protocol:"https://",css_version:"downtime",cdn_servers:["cdn.vaptcha.com"],api_server:"api.vaptcha.com/v2",canvas_path:"/canvas.min.js"},d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p=Object.prototype.toString,h=(c(function(e){for(var t={},n=e&&-1!==e.indexOf("?")&&e.split("?")[1]||window.location.search.substring(1),r=n.split("&"),o=0;o<r.length;o++){var i=r[o].split("=");t[decodeURIComponent(i[0])]=decodeURIComponent(i[1])}return t}),c(function(e){return e.charAt(0).toUpperCase()+e.slice(1)})),v=function(t){this.data=t,this.valiudateFuns=[],this.ruleFuns={required:function(t,n){return e(t)||0===t.length?n:null}}};v.prototype={constructor:v,addValidateRules:function(e){a(this.ruleFuns,e)},add:function(e,t,n){var r=this,o=t.split(":"),i=o.shift(),a=this.ruleFuns[i];o.unshift(this.data[e]),o.push(n),a&&this.valiudateFuns.push(function(){return a.apply(r,o)})},validate:function(){for(var e,t=0;e=this.valiudateFuns[t++];){var n=e();if(n)return s(n),!1}return!0}};var m={AccessDenied:"0101",RefreshAgain:"0102",Success:"0103",Fail:"0104",RefreshTooFast:"0105",RefreshTanto:"0106",DrawTanto:"0107",Attack:"0108",jsonpTimeOut:"0703",challengeExpire:"1002"};Promise.prototype.then=function(e){var t=this;if("fulfilled"===this.state){var r=e(this.value);if(n(r)&&"Promise"===o(r.constructor))return r}return"pending"===this.state?new Promise(function(r){t.onResolveAsyncCallbacks.push(function(){var i=e(t.value);if(n(i)&&"Promise"===o(i.constructor))return i.then(r);r(i)})}):this},Promise.prototype["catch"]=function(e){return"rejected"===this.state&&e(this.reason),"pending"===this.state&&this.onRejectAsyncCallbacks.push(e),this},Promise.resolve=function(e){return new Promise(function(t){t(e)})},Promise.reject=function(e){return new Promise(function(t,n){n(e)})};var y=function(){var e=f.protocol,t=f.api_server,n=function(e){var t="";for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t+="&"+n+"="+encodeURIComponent(e[n]));return t},r=function(r,o){var i=n(o),a=r.indexOf("http://")>-1||r.indexOf("https://")>-1;return r.indexOf("?")<0&&(i="?"+i.slice(1)),a?""+r+i:""+e+t+r+i},o=function(e){var t=document.getElementsByTagName("head")[0],n=document.createElement("script");return n.charset="UTF-8",n.src=e,t.appendChild(n),{remove:function(){t.removeChild(n)}}},i=function(e,t){return t=t||{},new Promise(function(n){var i="VaptchaJsonp"+(new Date).valueOf();window[i]&&(i+="1"),a(t,{callback:i}),e=r(e,t);var c=o(e),u=setTimeout(function(){clearTimeout(u),window[i]=null,c.remove(),n({code:"0703",msg:"Time out,Refresh Again!"})},1e4);window[i]=function(){clearTimeout(u),n.apply(this,arguments),c.remove(),window[i]=null}})};return i.setConfig=function(n){e=n.protocol||e,t=n.api_server||t},i}(),g={getConfig:function(e){return y("/config",{id:e.vid,type:e.type,scene:e.scene||""})},refresh:function(e){return y("/refresh",e)},click:function(e){return y("/click",e)},get:function(e){return y("/get",e)},verify:function(e){return y("/verify",e)},userbehavior:function(e){return y("/userbehavior",e)}},w={"0201":"id empty","0202":"id error","0208":"scene error","0209":"request used up","0906":"params error","0702":"domain not match"},b=function(){var o=!1,c=function(e){var t=new v(e);return t.add("outage","required","please configure outage"),t.validate(),a(e,{js_path:"vaptcha-sdk-downtime.2.0.2.js",api_server:window.location.host,protocol:window.location.protocol+"//",mode:"DownTime"}),y.setConfig(e),y(e.outage,{action:"get"}).then(function(t){return t.code!==m.Success?(s(w[t.msg]||t.msg),Promise.reject(t.code)):(a(e,t),Promise.resolve())})},f=function(e){return l(e)?c(e):g.getConfig(e).then(function(t){return t.code!==m.Success?(s(w[t.msg]||t.msg),Promise.reject(t.code)):(t.type!==e.type&&(t.mode=t.type,t.type=e.type),a(e,t),l(e)?c(e):Promise.resolve())})},d=function(e,t){return""+e.protocol+e.cdn_servers[0]+"/"+t},p=function(t){var n=document.getElementsByTagName("head")[0],r=document.getElementById("vaptcha_style");return new Promise(function(o){e(r)?(r=document.createElement("link"),a(r,{rel:"stylesheet",type:"text/css",href:t,id:"vaptcha_style",onload:o}),n&&n.appendChild(r)):o()})},b=function C(e){var n=document.getElementsByTagName("head")[0],r=document.querySelector("script[src='"+e+"']");return new Promise(function(o){if(t(r))return void(r.loaded?o():setTimeout(function(){return C(e).then(o)}));r=document.createElement("script");var i=function(){r.readyState&&"loaded"!==r.readyState&&"complete"!==r.readyState||(o(),r.loaded=!0,r.onload=null,r.onreadystatechange=null)};a(r,{async:!0,charset:"utf-8",src:e,onerror:function(){return s("load sdk timeout")},onload:i,onreadystatechange:i}),n.appendChild(r)})},j=function T(e){var t=e.sdkName,n=e.config,r=d(n,n.js_path);return b(r).then(function(){var e=window["_"+h(t)+"Vaptcha"],r=new e(n);return r.vaptcha.resetCaptcha=function(e,t){a(n,t),T({sdkName:e,config:n}).then(function(e){r.destroy(),r.options=e.options,r.vaptcha=e.vaptcha,e.render(),"character"===n.mode&&["click","float","popup"].includes(n.type)&&e.vaptcha.dtClickCb({target:e.vaptcha.btnDiv})})},Promise.resolve(r)})},A=function(e){o=!0,e.https=!0,e.protocol="https://",y.setConfig(e),!["embed","popup","invisible"].includes(e.type)&&(e.type="popup"),i()<9&&b(d(e,e.canvas_path));var t=new v(e);if(t.addValidateRules({elementOrSelector:function(t,o){if("String"===u(e.container)&&(e.container=document.querySelector(e.container)),n(e.container)&&r(e.container[0])&&(e.container=e.container[0]),!r(e.container))return o}}),t.add("vid","required","please configure vid"),"invisible"!==e.type&&t.add("container","elementOrSelector","please configure container with element or selector"),t.validate())return f(e).then(function(){var t="theme_https."+e.css_version+".css",n=d(e,t);return p(n)}).then(function(){var t=e.mode||e.type;return o=!1,j({sdkName:t,config:e})})};return function S(e){return new Promise(function(t){o?setTimeout(function(){S(e).then(t)},1e3):A(e).then(t)})["catch"](function(e){return o=!1,s(e),Promise.reject(e)})}}(),j=function(){var e=function(e){var n=e.getAttribute("data-config"),r={};if(t(n))try{r=JSON.parse(n)}catch(o){s("dom config format error")}return r},n=function(e){var n=e.getAttribute("data-vid");return t(n)?{vid:n}:{}},r=function(e,n){var r=Object.create(f);r.container=e,a(r,n),t(r.vid)&&b(r).then(function(e){e.renderTokenInput(),e.render()})};return function(){for(var t=document.querySelectorAll("[data-vid]"),o=document.querySelectorAll("[data-config]"),i=0;i<o.length;i++){var a=e(o[i]);r(o[i],a)}for(var c=0;c<t.length;c++)if(!Array.prototype.includes.call(o,t[c])){var u=n(t[c]);r(t[c],u)}}}();window.onload=j,window.vaptcha=function(e){var t=Object.create(f);return a(t,e),b(t)}}();
\ No newline at end of file
<template>
<a-locale-provider :locale="locale">
<div id="app">
<router-view/>
</div>
</a-locale-provider>
</template>
<script>
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
import enquireScreen from '@/utils/device'
export default {
data () {
return {
locale: zhCN,
}
},
created () {
let that = this
enquireScreen(deviceType => {
// tablet
if (deviceType === 0) {
that.$store.commit('TOGGLE_DEVICE', 'mobile')
that.$store.dispatch('setSidebar', false)
}
// mobile
else if (deviceType === 1) {
that.$store.commit('TOGGLE_DEVICE', 'mobile')
that.$store.dispatch('setSidebar', false)
}
else {
that.$store.commit('TOGGLE_DEVICE', 'desktop')
that.$store.dispatch('setSidebar', true)
}
})
}
}
</script>
<style>
#app {
height: 100%;
}
</style>
\ No newline at end of file
import { getAction,deleteAction,putAction,postAction} from '@/api/manage'
//根路径
const doMian = "/jeecg-boot/";
//图片预览请求地址
const imgView = "http://127.0.0.1:8080/jeecg-boot/sys/common/view/";
//角色管理
const addRole = (params)=>postAction("/sys/role/add",params);
const editRole = (params)=>putAction("/sys/role/edit",params);
const getRoleList = (params)=>getAction("/sys/role/list",params);
const deleteRole = (params)=>deleteAction("/sys/role/delete",params);
const deleteRoleList = (params)=>deleteAction("/sys/role/deleteBatch",params);
const checkRoleCode = (params)=>getAction("/sys/role/checkRoleCode",params);
const queryall = (params)=>getAction("/sys/role/queryall",params);
//用户管理
const addUser = (params)=>postAction("/sys/user/add",params);
const editUser = (params)=>putAction("/sys/user/edit",params);
const queryUserRole = (params)=>getAction("/sys/user/queryUserRole",params);
const getUserList = (params)=>getAction("/sys/user/list",params);
const deleteUser = (params)=>deleteAction("/sys/user/delete",params);
const deleteUserList = (params)=>deleteAction("/sys/user/deleteBatch",params);
const frozenBatch = (params)=>putAction("/sys/user/frozenBatch",params);
//验证用户账号是否唯一
const checkUsername = (params)=>getAction("/sys/user/checkOnlyUser",params);
//改变密码
const changPassword = (params)=>putAction("/sys/user/changPassword",params);
//权限管理
const addPermission= (params)=>postAction("/sys/permission/add",params);
const editPermission= (params)=>putAction("/sys/permission/edit",params);
const getPermissionList = (params)=>getAction("/sys/permission/list",params);
const deletePermission = (params)=>deleteAction("/sys/permission/delete",params);
const deletePermissionList = (params)=>deleteAction("/sys/permission/deleteBatch",params);
const queryTreeList = (params)=>getAction("/sys/permission/queryTreeList",params);
const queryListAsync = (params)=>getAction("/sys/permission/queryListAsync",params);
const queryRolePermission = (params)=>getAction("/sys/permission/queryRolePermission",params);
const saveRolePermission = (params)=>postAction("/sys/permission/saveRolePermission",params);
const queryPermissionsByUser = (params)=>getAction("/sys/permission/queryByUser",params);
const loadAllRoleIds = (params)=>getAction("/sys/permission/loadAllRoleIds",params);
// 部门管理
const queryDepartTreeList = (params)=>getAction("/sysdepart/sysDepart/queryTreeList",params);
const queryIdTree = (params)=>getAction("/sysdepart/sysDepart/queryIdTree",params);
const queryParentName = (params)=>getAction("/sysdepart/sysDepart/queryParentName",params);
const searchByKeywords = (params)=>getAction("/sysdepart/sysDepart/searchBy",params);
//日志管理
const getLogList = (params)=>getAction("/sys/log/list",params);
const deleteLog = (params)=>deleteAction("/sys/log/delete",params);
const deleteLogList = (params)=>deleteAction("/sys/log/deleteBatch",params);
//数据字典
const addDict = (params)=>postAction("/sys/dict/add",params);
const editDict = (params)=>putAction("/sys/dict/edit",params);
const getDictList = (params)=>getAction("/sys/dict/list",params);
const treeList = (params)=>getAction("/sys/dict/treeList",params);
const delDict = (params)=>deleteAction("/sys/dict/delete",params);
const getDictItemList = (params)=>getAction("/sys/dictItem/list",params);
const addDictItem = (params)=>postAction("/sys/dictItem/add",params);
const editDictItem = (params)=>putAction("/sys/dictItem/edit",params);
const delDictItem = (params)=>deleteAction("/sys/dictItem/delete",params);
const delDictItemList = (params)=>deleteAction("/sys/dictItem/deleteBatch",params);
//字典标签专用(通过code获取字典数组)
export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItems/${code}`,params);
//系统通告
const doReleaseData = (params)=>getAction("/sys/annountCement/doReleaseData",params);
const doReovkeData = (params)=>getAction("/sys/annountCement/doReovkeData",params);
//获取系统访问量
const getLoginfo = (params)=>getAction("/sys/loginfo",params);
export {
imgView,
doMian,
addRole,
editRole,
getRoleList,
deleteRole,
deleteRoleList,
checkRoleCode,
addUser,
editUser,
queryUserRole,
queryall,
getUserList,
deleteUser,
deleteUserList,
frozenBatch,
checkUsername,
changPassword,
getPermissionList,
deletePermission,
deletePermissionList,
addPermission,
editPermission,
queryTreeList,
queryListAsync,
queryRolePermission,
saveRolePermission,
queryPermissionsByUser,
loadAllRoleIds,
queryDepartTreeList,
queryIdTree,
queryParentName,
searchByKeywords,
getLogList,
deleteLog,
deleteLogList,
getDictList,
addDict,
editDict,
delDict,
treeList,
getDictItemList,
addDictItem,
editDictItem,
delDictItem,
delDictItemList,
doReleaseData,
doReovkeData,
getLoginfo
}
const api = {
Login: '/sys/login',
Logout: '/auth/logout',
ForgePassword: '/auth/forge-password',
Register: '/auth/register',
SendSms: '/account/sms',
// get my info
UserInfo: '/user/info'
}
export default api
\ No newline at end of file
import api from './index'
import { axios } from '@/utils/request'
/**
* login func
* parameter: {
* username: '',
* password: '',
* remember_me: true,
* captcha: '12345'
* }
* @param parameter
* @returns {*}
*/
export function login(parameter) {
return axios({
url: '/sys/login',
method: 'post',
data: parameter
})
}
export function getSmsCaptcha(parameter) {
return axios({
url: api.SendSms,
method: 'post',
data: parameter
})
}
export function getInfo() {
return axios({
url: '/api/user/info',
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
export function logout() {
return axios({
url: '/api/auth/logout',
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
\ No newline at end of file
import { axios } from '@/utils/request'
const api = {
user: '/api/user',
role: '/api/role',
service: '/api/service',
permission: '/api/permission',
permissionNoPager: '/api/permission/no-pager'
}
export default api
//post
export function postAction(url,parameter) {
return axios({
url: url,
method:'post' ,
data: parameter
})
}
//post method= {post | put}
export function httpAction(url,parameter,method) {
return axios({
url: url,
method:method ,
data: parameter
})
}
//put
export function putAction(url,parameter) {
return axios({
url: url,
method:'put',
data: parameter
})
}
//get
export function getAction(url,parameter) {
return axios({
url: url,
method: 'get',
params: parameter
})
}
//deleteAction
export function deleteAction(url,parameter) {
return axios({
url: url,
method: 'delete',
params: parameter
})
}
export function getUserList(parameter) {
return axios({
url: api.user,
method: 'get',
params: parameter
})
}
export function getRoleList(parameter) {
return axios({
url: api.role,
method: 'get',
params: parameter
})
}
export function getServiceList(parameter) {
return axios({
url: api.service,
method: 'get',
params: parameter
})
}
export function getPermissions(parameter) {
return axios({
url: api.permissionNoPager,
method: 'get',
params: parameter
})
}
// id == 0 add post
// id != 0 update put
export function saveService(parameter) {
return axios({
url: api.service,
method: parameter.id == 0 ? 'post' : 'put',
data: parameter
})
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
.search{
margin-bottom: 54px;
}
.fold{
width: calc(100% - 216px);
display: inline-block
}
.operator{
margin-bottom: 18px;
}
@media screen and (max-width: 900px) {
.fold {
width: 100%;
}
}
.operator button {
margin-right: 5px;
}
i {
cursor: pointer;
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
<title>Group 28 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="62.1023273%" y1="0%" x2="108.19718%" y2="37.8635764%" id="linearGradient-1">
<stop stop-color="#4285EB" offset="0%"></stop>
<stop stop-color="#2EC7FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="69.644116%" y1="0%" x2="54.0428975%" y2="108.456714%" id="linearGradient-2">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#148EFF" offset="37.8600687%"></stop>
<stop stop-color="#0A60FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="69.6908165%" y1="-12.9743587%" x2="16.7228981%" y2="117.391248%" id="linearGradient-3">
<stop stop-color="#FA816E" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="41.472606%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
<linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-4">
<stop stop-color="#FA8E7D" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="51.2635191%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo" transform="translate(-20.000000, -20.000000)">
<g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)">
<g id="Group-27-Copy-3">
<g id="Group-25" fill-rule="nonzero">
<g id="2">
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-1)"></path>
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-2)"></path>
</g>
<path d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z" id="Shape" fill="url(#linearGradient-3)"></path>
</g>
<ellipse id="Combined-Shape" fill="url(#linearGradient-4)" cx="100.519339" cy="100.436681" rx="23.6001926" ry="23.580786"></ellipse>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<template>
<tooltip v-if="tips !== ''">
<template slot="title">{{ tips }}</template>
<avatar :size="avatarSize" :src="src" />
</tooltip>
<avatar v-else :size="avatarSize" :src="src" />
</template>
<script>
import Avatar from 'ant-design-vue/es/avatar'
import Tooltip from 'ant-design-vue/es/tooltip'
export default {
name: "AvatarItem",
components: {
Avatar,
Tooltip
},
props: {
tips: {
type: String,
default: '',
required: false
},
src: {
type: String,
default: ''
}
},
data () {
return {
size: this.$parent.size
}
},
computed: {
avatarSize () {
return this.size !== 'mini' && this.size || 20
}
},
watch: {
'$parent.size' (val) {
this.size = val
}
}
}
</script>
\ No newline at end of file
<!--
<template>
<div :class="[prefixCls]">
<ul>
<slot></slot>
<template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template>
<template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength">
<avatar-item :size="size">
<avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar>
</avatar-item>
</template>
</ul>
</div>
</template>
-->
<script>
import Avatar from 'ant-design-vue/es/avatar'
import AvatarItem from './Item'
import { filterEmpty } from '@/components/_util/util'
export default {
AvatarItem,
name: "AvatarList",
components: {
Avatar,
AvatarItem
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-avatar-list'
},
/**
* 头像大小 类型: large、small 、mini, default
* 默认值: default
*/
size: {
type: [String, Number],
default: 'default'
},
/**
* 要显示的最大项目
*/
maxLength: {
type: Number,
default: 0
},
/**
* 多余的项目风格
*/
excessItemsStyle: {
type: Object,
default: () => {
return {
color: '#f56a00',
backgroundColor: '#fde3cf'
}
}
}
},
data () {
return {}
},
methods: {
getItems(items) {
const classString = {
[`${this.prefixCls}-item`]: true,
[`${this.size}`]: true
}
if (this.maxLength > 0) {
items = items.slice(0, this.maxLength)
items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>))
}
const itemList = items.map((item) => (
<li class={ classString }>{ item }</li>
))
return itemList
}
},
render () {
const { prefixCls, size } = this.$props
const classString = {
[`${prefixCls}`]: true,
[`${size}`]: true,
}
const items = filterEmpty(this.$slots.default)
const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{ this.getItems(items) }</ul> : null
return (
<div class={ classString }>
{ itemsDom }
</div>
)
}
}
</script>
\ No newline at end of file
import AvatarList from './List'
import "./index.less"
export default AvatarList
\ No newline at end of file
@import "../index";
@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
.@{avatar-list-prefix-cls} {
display: inline-block;
ul {
list-style: none;
display: inline-block;
padding: 0;
margin: 0 0 0 8px;
font-size: 0;
}
}
.@{avatar-list-item-prefix-cls} {
display: inline-block;
font-size: @font-size-base;
margin-left: -8px;
width: @avatar-size-base;
height: @avatar-size-base;
:global {
.ant-avatar {
border: 1px solid #fff;
cursor: pointer;
}
}
&.large {
width: @avatar-size-lg;
height: @avatar-size-lg;
}
&.small {
width: @avatar-size-sm;
height: @avatar-size-sm;
}
&.mini {
width: 20px;
height: 20px;
:global {
.ant-avatar {
width: 20px;
height: 20px;
line-height: 20px;
.ant-avatar-string {
font-size: 12px;
line-height: 18px;
}
}
}
}
}
<template>
<a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
<div class="chart-card-header">
<div class="meta">
<span class="chart-card-title">{{ title }}</span>
<span class="chart-card-action">
<slot name="action"></slot>
</span>
</div>
<div class="total"><span>{{ total }}</span></div>
</div>
<div class="chart-card-content">
<div class="content-fix">
<slot></slot>
</div>
</div>
<div class="chart-card-footer">
<div class="field">
<slot name="footer"></slot>
</div>
</div>
</a-card>
</template>
<script>
export default {
name: "ChartCard",
props: {
title: {
type: String,
default: ''
},
total: {
type: String,
default: ''
},
loading: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="scss" scoped>
.chart-card-header {
position: relative;
overflow: hidden;
width: 100%;
.meta {
position: relative;
overflow: hidden;
width: 100%;
color: rgba(0, 0, 0, .45);
font-size: 14px;
line-height: 22px;
}
}
.chart-card-action {
cursor: pointer;
position: absolute;
top: 0;
right: 0;
}
.chart-card-footer {
border-top: 1px solid #e8e8e8;
padding-top: 9px;
margin-top: 8px;
> * {
position: relative;
}
.field {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
}
}
.chart-card-content {
margin-bottom: 12px;
position: relative;
height: 46px;
width: 100%;
.content-fix {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
}
.total {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
color: #000;
margin-top: 4px;
margin-bottom: 0;
font-size: 30px;
line-height: 38px;
height: 38px;
}
</style>
\ No newline at end of file
<template>
<span>
{{ lastTime | format }}
</span>
</template>
<script>
function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
}
export default {
name: "CountDown",
props: {
format: {
type: Function,
default: undefined
},
target: {
type: [Date, Number],
required: true,
},
onEnd: {
type: Function,
default: () => {
}
}
},
data() {
return {
dateTime: '0',
originTargetTime: 0,
lastTime: 0,
timer: 0,
interval: 1000
}
},
filters: {
format(time) {
const hours = 60 * 60 * 1000;
const minutes = 60 * 1000;
const h = Math.floor(time / hours);
const m = Math.floor((time - h * hours) / minutes);
const s = Math.floor((time - h * hours - m * minutes) / 1000);
return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
}
},
created() {
this.initTime()
this.tick()
},
methods: {
initTime() {
let lastTime = 0;
let targetTime = 0;
this.originTargetTime = this.target
try {
if (Object.prototype.toString.call(this.target) === '[object Date]') {
targetTime = this.target
} else {
targetTime = new Date(this.target).getTime()
}
} catch (e) {
throw new Error('invalid target prop')
}
lastTime = targetTime - new Date().getTime();
this.lastTime = lastTime < 0 ? 0 : lastTime
},
tick() {
const {onEnd} = this
this.timer = setTimeout(() => {
if (this.lastTime < this.interval) {
clearTimeout(this.timer)
this.lastTime = 0
if (typeof onEnd === 'function') {
onEnd();
}
} else {
this.lastTime -= this.interval
this.tick()
}
}, this.interval)
}
},
beforeUpdate () {
if (this.originTargetTime !== this.target) {
this.initTime()
}
},
beforeDestroy() {
clearTimeout(this.timer)
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
import CountDown from './CountDown'
export default CountDown
\ No newline at end of file
<script>
import Tooltip from 'ant-design-vue/es/tooltip'
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/StringUtil'
/*
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
const TooltipOverlayStyle = {
overflowWrap: 'break-word',
wordWrap: 'break-word',
};
*/
export default {
name: 'Ellipsis',
components: {
Tooltip
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-ellipsis'
},
tooltip: {
type: Boolean
},
length: {
type: Number,
required: true
},
lines: {
type: Number,
default: 1
},
fullWidthRecognition: {
type: Boolean,
default: false
}
},
methods: {
getStrDom (str) {
return (
<span>{ cutStrByFullLength(str, this.length) + '...' }</span>
)
},
getTooltip ( fullStr) {
return (
<Tooltip>
<template slot="title">{ fullStr }</template>
{ this.getStrDom(fullStr) }
</Tooltip>
)
}
},
render () {
const { tooltip, length } = this.$props
let str = this.$slots.default.map(vNode => vNode.text).join("")
const strDom = tooltip && getStrFullLength(str) > length ? this.getTooltip(str) : this.getStrDom(str);
return (
strDom
)
}
}
</script>
\ No newline at end of file
import Ellipsis from './Ellipsis'
export default Ellipsis
\ No newline at end of file
<template>
<div :class="[prefixCls]">
<slot name="subtitle">
<div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div>
</slot>
<div class="number-info-value">
<span>{{ total }}</span>
<span class="sub-total">
{{ subTotal }}
<icon :type="`caret-${status}`" />
</span>
</div>
</div>
</template>
<script>
import Icon from 'ant-design-vue/es/icon'
export default {
name: 'NumberInfo',
props: {
prefixCls: {
type: String,
default: 'ant-pro-number-info'
},
total: {
type: Number,
required: true
},
subTotal: {
type: Number,
required: true
},
subTitle: {
type: [String, Function],
default: ''
},
status: {
type: String,
default: 'up'
}
},
components: {
Icon
},
data () {
return {}
}
}
</script>
<style lang="less" scoped>
@import "index";
</style>
\ No newline at end of file
import NumberInfo from './NumberInfo'
export default NumberInfo
\ No newline at end of file
@import "../index";
@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info";
.@{numberInfo-prefix-cls} {
.ant-pro-number-info-subtitle {
color: @text-color-secondary;
font-size: @font-size-base;
height: 22px;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
}
.number-info-value {
margin-top: 4px;
font-size: 0;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
& > span {
color: @heading-color;
display: inline-block;
line-height: 32px;
height: 32px;
font-size: 24px;
margin-right: 32px;
}
.sub-total {
color: @text-color-secondary;
font-size: @font-size-lg;
vertical-align: top;
margin-right: 0;
i {
font-size: 12px;
transform: scale(0.82);
margin-left: 4px;
}
:global {
.anticon-caret-up {
color: @red-6;
}
.anticon-caret-down {
color: @green-6;
}
}
}
}
}
\ No newline at end of file
<template>
<div :class="[prefixCls, reverseColor && 'reverse-color' ]">
<span>
<slot name="term"></slot>
<span class="item-text">
<slot></slot>
</span>
</span>
<span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span>
</div>
</template>
<script>
export default {
name: "Trend",
props: {
prefixCls: {
type: String,
default: 'ant-pro-trend'
},
/**
* 上升下降标识:up|down
*/
flag: {
type: String,
required: true
},
/**
* 颜色反转
*/
reverseColor: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
@import "index";
</style>
\ No newline at end of file
import Trend from './Trend.vue'
export default Trend
\ No newline at end of file
@import "../index";
@trend-prefix-cls: ~"@{ant-pro-prefix}-trend";
.@{trend-prefix-cls} {
display: inline-block;
font-size: @font-size-base;
line-height: 22px;
.up,
.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(0.83);
}
}
.item-text {
display: inline-block;
margin-left: 8px;
color: rgba(0,0,0,.85);
}
.up {
color: @red-6;
}
.down {
color: @green-6;
top: -1px;
}
&.reverse-color .up {
color: @green-6;
}
&.reverse-color .down {
color: @red-6;
}
}
\ No newline at end of file
export const getStrFullLength = (str = '') =>
str.split('').reduce((pre, cur) => {
const charCode = cur.charCodeAt(0)
if (charCode >= 0 && charCode <= 128) {
return pre + 1
}
return pre + 2
}, 0)
export const cutStrByFullLength = (str = '', maxLength) => {
let showLength = 0
return str.split('').reduce((pre, cur) => {
const charCode = cur.charCodeAt(0)
if (charCode >= 0 && charCode <= 128) {
showLength += 1
} else {
showLength += 2
}
if (showLength <= maxLength) {
return pre + cur
}
return pre
}, '')
}
\ No newline at end of file
/**
* components util
*/
/**
* 清理空值,对象
* @param children
* @returns {*[]}
*/
export function filterEmpty (children = []) {
return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
}
\ No newline at end of file
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="data"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
const data = []
for (let i = 0; i < 12; i += 1) {
data.push({
x: `${i + 1}月`,
y: Math.floor(Math.random() * 1000) + 200
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
export default {
name: "Bar",
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
data,
scale,
tooltip
}
}
}
</script>
\ No newline at end of file
<template>
<div>
<v-chart
:forceFit="true"
:height="height"
:width="width"
:data="data"
:scale="scale"
:padding="0">
<v-tooltip />
<v-interval
:shape="['liquid-fill-gauge']"
position="transfer*value"
color=""
:v-style="{
lineWidth: 10,
opacity: 0.75
}"
:tooltip="[
'transfer*value',
(transfer, value) => {
return {
name: transfer,
value,
};
},
]"
></v-interval>
<v-guide
v-for="(row, index) in data"
:key="index"
type="text"
:top="true"
:position="{
gender: row.transfer,
value: 45
}"
:content="row.value + '%'"
:v-style="{
fontSize: 100,
textAlign: 'center',
opacity: 0.75,
}"
/>
</v-chart>
</div>
</template>
<script>
export default {
name: "Liquid",
props: {
height: {
type: Number,
default: 0
},
width: {
type: Number,
default: 0
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="height" :data="datasource" :padding="[36, 0, 18, 0]">
<v-tooltip />
<v-smooth-area position="x*y" />
</v-chart>
</div>
</div>
</template>
<script>
import moment from 'dayjs'
const data = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
data.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
console.log("123321",data)
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
export default {
name: "MiniArea",
props:{
datasource:{
type: Array,
default:()=>[]
}
},
created(){
if(this.datasource.length==0){
this.datasource = data;
}
},
data () {
return {
tooltip,
scale,
height: 100
}
}
}
</script>
<style lang="scss" scoped>
@import "chart";
</style>
\ No newline at end of file
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="height" :data="datasource" :padding="[36, 5, 18, 5]">
<v-tooltip />
<v-bar position="x*y" />
</v-chart>
</div>
</div>
</template>
<script>
import moment from 'dayjs'
const data = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
data.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 30
}]
export default {
name: "MiniBar",
props:{
datasource:{
type: Array,
default:()=>[]
}
},
created(){
if(this.datasource.length==0){
this.datasource = data;
}
},
data () {
return {
data,
tooltip,
scale,
height: 100
}
}
}
</script>
<style lang="scss" scoped>
@import "chart";
</style>
\ No newline at end of file
<template>
<div class="chart-mini-progress">
<div class="target" :style="{ left: target + '%'}">
<span :style="{ backgroundColor: color }" />
<span :style="{ backgroundColor: color }"/>
</div>
<div class="progress-wrapper">
<div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height }"></div>
</div>
</div>
</template>
<script>
export default {
name: "MiniProgress",
props: {
target: {
type: Number,
default: 0
},
height: {
type: String,
default: '10px'
},
color: {
type: String,
default: '#13C2C2'
},
percentage: {
type: Number,
default: 0
}
}
}
</script>
<style lang="scss" scoped>
.chart-mini-progress {
padding: 5px 0;
position: relative;
width: 100%;
.target {
position: absolute;
top: 0;
bottom: 0;
span {
border-radius: 100px;
position: absolute;
top: 0;
left: 0;
height: 4px;
width: 2px;
&:last-child {
top: auto;
bottom: 0;
}
}
}
.progress-wrapper {
background-color: #f5f5f5;
position: relative;
.progress {
transition: all .4s cubic-bezier(.08,.82,.17,1) 0s;
border-radius: 1px 0 0 1px;
background-color: #1890ff;
width: 0;
height: 100%;
}
}
}
</style>
\ No newline at end of file
<template>
<v-chart :forceFit="true" height="400" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
<v-tooltip></v-tooltip>
<v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid" />
<v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid" />
<v-legend dataKey="user" marker="circle" :offset="30" />
<v-coord type="polar" radius="0.8" />
<v-line position="item*score" color="user" :size="2" />
<v-point position="item*score" color="user" :size="4" shape="circle" />
</v-chart>
</template>
<script>
const axis1Opts = {
dataKey: 'item',
line: null,
tickLine: null,
grid: {
lineStyle: {
lineDash: null
},
hideFirstLine: false
}
}
const axis2Opts = {
dataKey: 'score',
line: null,
tickLine: null,
grid: {
type: 'polygon',
lineStyle: {
lineDash: null
}
}
}
const scale = [
{
dataKey: 'score',
min: 0,
max: 80
}, {
dataKey: 'user',
alias: '类型'
}
]
export default {
name: 'Radar',
props: {
data: {
type: Array,
default: null,
}
},
data () {
return {
axis1Opts,
axis2Opts,
scale
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div class="rank">
<h4 class="title">{{ title }}</h4>
<ul class="list">
<li :key="index" v-for="(item, index) in list">
<span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
<span>{{ item.name }}</span>
<span>{{ item.total }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "RankList",
// ['title', 'list']
props: {
title: {
type: String,
default: ''
},
list: {
type: Array,
default: null
}
}
}
</script>
<style lang="scss" scoped>
.rank {
padding: 0 32px 32px 72px;
.list {
margin: 25px 0 0;
padding: 0;
list-style: none;
li {
margin-top: 16px;
span {
color: rgba(0, 0, 0, .65);
font-size: 14px;
line-height: 22px;
&:first-child {
background-color: #f5f5f5;
border-radius: 20px;
display: inline-block;
font-size: 12px;
font-weight: 600;
margin-right: 24px;
height: 20px;
line-height: 20px;
width: 20px;
text-align: center;
}
&.active {
background-color: #314659;
color: #fff;
}
&:last-child {
float: right;
}
}
}
}
}
.mobile .rank {
padding: 0 32px 32px 32px;
}
</style>
\ No newline at end of file
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="data"
:scale="scale"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
title: '日期(天)',
alias: '日期(天)',
min: 2
}, {
dataKey: 'y',
title: '流量(Gb)',
alias: '流量(Gb)',
min: 1
}]
export default {
name: "Bar",
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
data: [],
scale,
tooltip
}
},
created () {
this.getMonthBar()
},
methods: {
getMonthBar() {
this.$http.get('/analysis/month-bar')
.then(res => {
this.data = res.result
})
}
}
}
</script>
\ No newline at end of file
<template>
<div class="chart-trend">
{{ term }}
<span>{{ rate }}%</span>
<span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
</div>
</template>
<script>
export default {
name: "Trend",
props: {
term: {
type: String,
default: '',
required: true
},
percentage: {
type: Number,
default: null
},
type: {
type: Boolean,
default: null
},
target: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
},
fixed: {
type: Number,
default: 2
}
},
data () {
return {
trend: this.type && 'up' || 'down',
rate: this.percentage
}
},
created () {
let type = this.type === null ? this.value >= this.target : this.type
this.trend = type ? 'up' : 'down';
this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
}
}
</script>
<style lang="scss" scoped>
.chart-trend {
display: inline-block;
font-size: 14px;
line-height: 22px;
.trend-icon {
font-size: 12px;
&.up, &.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(.83);
}
}
&.up {
color: #f5222d;
}
&.down {
color: #52c41a;
top: -1px;
}
}
}
</style>
\ No newline at end of file
.antv-chart-mini {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
/* margin: 0 -5px;
overflow: hidden;*/
}
}
\ No newline at end of file
<template>
<a-select :placeholder="placeholder" :value="value" @change="handleInput">
<a-select-option value="">请选择</a-select-option>
<a-select-option v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
</template>
<script>
import {ajaxGetDictItems} from '@/api/api'
export default {
name: "DictSelectTag",
props: {
dictCode: String,
placeholder: String,
value: String,// 1.接收一个 value prop
},
data() {
return {
dictOptions: [],
}
},
created() {
console.log(this.dictCode);
//获取字典数据
this.initDictData();
},
methods: {
initDictData() {
//根据字典Code, 初始化字典数组
ajaxGetDictItems(this.dictCode, null).then((res) => {
if (res.success) {
// console.log(res.result);
this.dictOptions = res.result;
}
})
},
handleInput(val) {
console.log(val);
this.$emit('input', val); // 2.触发 input 事件,并传入新值
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
/**
* 字典 util
* author: scott
* date: 20190109
*/
import {ajaxGetDictItems} from '@/api/api'
import {getAction} from '@/api/manage'
/**
* 获取字典数组
* @param dictCode 字典Code
* @return List<Map>
*/
export async function initDictOptions(dictCode) {
if (!dictCode) {
return '字典Code不能为空!';
}
//获取字典数组
let res = await ajaxGetDictItems(dictCode);
return res;
}
/**
* 字典值替换文本通用方法
* @param dictOptions 字典数组
* @param text 字典值
* @return String
*/
export function filterDictText(dictOptions, text) {
let re = "";
dictOptions.forEach(function (option) {
if (text === option.value) {
re = option.text;
}
});
return re;
}
/**
* 翻译字段值对应的文本
* @param children
* @returns string
*/
export async function ajaxFilterDictText(dictCode, key) {
if (!dictCode) {
return '字典Code不能为空!';
}
//console.log(`key : ${key}`);
if (!key) {
return '';
}
//通过请求读取字典文本
let res = await getAction(`/sys/dict/getDictText/${dictCode}/${key}`);
if (res.success) {
// console.log('restult: '+ res.result);
return res.result;
} else {
return '';
}
}
\ No newline at end of file
DictSelectTag 组件说明
===
例子
----
<DictSelectTag v-model="queryParam.sex" placeholder="请输入用户性别" dictCode="sex"/>
DictSelectUtil.js 列表字典函数用法说明
===
例子
----
第一步: 引入依赖方法
import {initDictOptions, filterDictText} from '@/components/dict/DictSelectUtil'
第二步: 在created()初始化方法执行字典配置方法
//初始化字典配置
this.initDictConfig();
第三步: 实现initDictConfig方法,加载列表所需要的字典(列表上有多个字典项,就执行多次initDictOptions方法)
//sexDictOptions 自行定义
initDictConfig() {
//初始化字典 - 性别
initDictOptions('sex').then((res) => {
if (res.success) {
this.sexDictOptions = res.result;
}
});
},
第四步:实现字段的customRender方法
customRender: (text, record, index) => {
//字典值替换通用方法
return filterDictText(this.sexDictOptions, text);
}
\ No newline at end of file
<template>
<span>
<!--// 1、当有数据输入时触发了该组件的input事件-->
<!--<input type="text" :value="value" @input="updateVal($event.target.value)">-->
<a-input :placeholder="placeholder" :value="value" @input="updateVal($event.target.value)"></a-input>
</span>
</template>
<script>
export default {
props: {
value: String,
placeholder: String
},
methods: {
updateVal: function(val) {
// 2、手动触发父组件的input事件并将值传给父组件
this.$emit('input', val);
}
}
}
</script>
\ No newline at end of file
import T from './DictSelectTag.vue'
const DictSelectTag = {
install: function (Vue) {
Vue.component('DictSelectTag',T);
}
}
export default DictSelectTag;
\ No newline at end of file
@import "~ant-design-vue/lib/style/index";
// The prefix to use on all css classes from ant-pro.
@ant-pro-prefix : ant-pro;
\ No newline at end of file
<template>
<a-date-picker
:disabled="readOnly"
:placeholder="placeholder"
@change="handleDateChange"
:value="momVal"
:showTime="showTime"
:format="dateFormat"
/>
</template>
<script>
import moment from 'moment'
export default {
name: 'JDate',
props: {
placeholder:{
type: String,
default: '',
required: false
},
value:{
type: String,
default: '',
required: false
},
dateFormat:{
type: String,
default: 'YYYY-MM-DD',
required: false
},
triggerChange:{
type: Boolean,
required: false,
default: false
},
readOnly:{
type: Boolean,
required: false,
default: false
},
showTime:{
type: Boolean,
required: false,
default: false
}
},
data () {
let dateStr = this.value;
return {
decorator:"",
momVal:!dateStr?null:moment(dateStr,this.dateFormat)
}
},
watch: {
value (val) {
if(!val){
this.momVal = null
}else{
this.momVal = moment(val,this.dateFormat)
}
}
},
methods: {
moment,
handleDateChange(mom,dateStr){
if(this.triggerChange){
this.$emit('change', dateStr);
}else{
this.$emit('input', dateStr);
}
}
}
}
</script>
日期组件
说明:antd-vue日期组件需要用moment中转一下,用起来不是很方便,特二次封装,使用时只需要传字符串即可
====
参数说明
----
placeholder:placeholder
readOnly:true/false
value:绑定v-model或是v-decorator后不需要设置
showTime:是否展示时间true/false
dateFormat:日期格式 默认'YYYY-MM-DD' 若showTime设置为true则需要将其设置成对应的时间格式(如:YYYY-MM-DD HH:mm:ss)
triggerChange:触发组件值改变的事件是否是change,当使用v-decorator时且没有设置decorator的option.trigger为input需要设置该值为true
使用示例
----
1.组件带有v-model的使用方法
<j-date v-model="dateStr"></j-date>
2.组件带有v-decorator的使用方法
a).设置trigger-change属性为true
<j-date :trigger-change="true" v-decorator="['dateStr',{}]"></j-date>
b).设置decorator的option.trigger为input
<j-date v-decorator="['dateStr',{trigger:'input'}]"></j-date>
3.其他使用
添加style
<j-date v-model="dateStr" style="width:100%"></j-date>
添加placeholder
<j-date v-model="dateStr" placeholder="请输入dateStr"></j-date>
添加readOnly
<j-date v-model="dateStr" :read-only="true"></j-date>
备注:
script内需引入jdate
<script>
import JDate from '@/components/jeecg/JDate'
export default {
name: "demo",
components: {
JDate
},
....
</script>
\ No newline at end of file
<template>
<global-layout>
<transition name="page-transition">
<keep-alive v-if="keepAlive">
<router-view />
</keep-alive>
<router-view v-else />
</transition>
</global-layout>
</template>
<script>
import GlobalLayout from '@/components/page/GlobalLayout'
export default {
name: "BasicLayout",
components: {
GlobalLayout
},
data () {
return {
}
},
computed: {
keepAlive () {
return this.$route.meta.keepAlive
}
},
methods: {
},
}
</script>
<style lang="scss">
/*
* The following styles are auto-applied to elements with
* transition="page-transition" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the page transition by editing
* these styles.
*/
.page-transition-enter {
opacity: 0;
}
.page-transition-leave-active {
opacity: 0;
}
.page-transition-enter .page-transition-container,
.page-transition-leave-active .page-transition-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
</style>
\ No newline at end of file
<template>
<div>
<router-view />
</div>
</template>
<script>
export default {
name: "BlankLayout",
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<iframe :id="id" :src="url" frameborder="0" width="100%" height="800px" scrolling="auto"></iframe>
</template>
<script>
import PageLayout from '../page/PageLayout'
import RouteView from './RouteView'
export default {
name: "IframePageContent",
data () {
return {
url: "",
id:""
}
},
created () {
this.goUrl()
},
updated () {
this.goUrl()
},
watch: {
$route(to, from) {
this.goUrl();
}
},
methods: {
goUrl () {
let url = this.$route.meta.url
let id = this.$route.path
this.id = id
//url = "http://www.baidu.com"
console.log("------url------"+url)
if (url !== null && url !== undefined) {
this.url = url;
//window.open(this.url);
}
}
}
}
</script>
<style>
</style>
\ No newline at end of file
<template>
<page-layout :desc="description" :title="getTitle" :link-list="linkList" :search="search" :tabs="tabs">
<div slot="extra" class="extra-img">
<img :src="extraImage"/>
</div>
<!-- keep-alive -->
<route-view ref="content"></route-view>
</page-layout>
</template>
<script>
import PageLayout from '../page/PageLayout'
import RouteView from './RouteView'
export default {
name: "PageContent",
components: {
RouteView,
PageLayout
},
data () {
return {
title: '',
description: '',
linkList: [],
extraImage: '',
search: false,
tabs: {}
}
},
mounted () {
this.getPageHeaderInfo()
},
updated () {
this.getPageHeaderInfo()
},
computed: {
getTitle () {
return this.$route.meta.title
}
},
methods: {
getPageHeaderInfo () {
// eslint-disable-next-line
this.title = this.$route.meta.title
// 因为套用了一层 route-view 所以要取 ref 对象下的子节点的第一个对象
const content = this.$refs.content && this.$refs.content.$children[0]
if (content) {
this.description = content.description
this.linkList = content.linkList
this.extraImage = content.extraImage
this.search = content.search == true ? true : false
this.tabs = content.tabs
}
}
}
}
</script>
<style lang="scss" scoped>
.extra-img {
margin-top: -60px;
text-align: center;
width: 195px;
img {
width: 100%;
}
}
.mobile {
.extra-img{
margin-top: 0;
text-align: center;
width: 96px;
img{
width: 100%;
}
}
}
</style>
\ No newline at end of file
<template>
<keep-alive v-if="keepAlive">
<router-view />
</keep-alive>
<router-view v-else />
</template>
<script>
export default {
name: "RouteView",
computed: {
keepAlive () {
return this.$route.meta.keepAlive
}
},
}
</script>
\ No newline at end of file
<template>
<global-layout>
<contextmenu :itemList="menuItemList" :visible.sync="menuVisible" @select="onMenuSelect" />
<a-tabs
@contextmenu.native="e => onContextmenu(e)"
v-if="multipage"
:active-key="activePage"
style="margin-top: -8px; margin-bottom: -10px"
:hide-add="true"
type="editable-card"
@change="changePage"
@edit="editPage">
<a-tab-pane :id="page.fullPath" :key="page.fullPath" v-for="page in pageList">
<span slot="tab" :pagekey="page.fullPath">{{ page.meta.title }}</span>
</a-tab-pane>
</a-tabs>
<transition name="page-toggle">
<keep-alive v-if="multipage">
<router-view />
</keep-alive>
<router-view v-else />
</transition>
</global-layout>
</template>
<script>
import GlobalLayout from '@/components/page/GlobalLayout'
import Contextmenu from '@/components/menu/Contextmenu'
const indexKey="/dashboard/analysis"
export default {
name: "TabLayout",
components: {
GlobalLayout,
Contextmenu
},
data () {
return {
pageList: [],
linkList: [],
activePage: '',
menuVisible: false,
menuItemList: [
{ key: '1', icon: 'arrow-left', text: '关闭左侧' },
{ key: '2', icon: 'arrow-right', text: '关闭右侧' },
{ key: '3', icon: 'close', text: '关闭其它' }
]
}
},
computed: {
multipage () {
return this.$store.state.app.multipage
}
},
created () {
this.pageList.push(this.$route)
this.linkList.push(this.$route.fullPath)
this.activePage = this.$route.fullPath
},
watch: {
'$route': function (newRoute) {
this.activePage = newRoute.fullPath
if (!this.multipage) {
this.linkList = [newRoute.fullPath]
this.pageList = [newRoute]
} else if (this.linkList.indexOf(newRoute.fullPath) < 0) {
this.linkList.push(newRoute.fullPath)
this.pageList.push(newRoute)
}else if(this.linkList.indexOf(newRoute.fullPath) >= 0){
let oldIndex = this.linkList.indexOf(newRoute.fullPath);
this.pageList.splice(oldIndex,1,newRoute);
}
},
'activePage': function (key) {
let index = this.linkList.lastIndexOf(key);
var waitRouter = this.pageList[index];
this.$router.push({
path: waitRouter.path,
name: waitRouter.name,
params: waitRouter.params
});
},
'multipage': function (newVal) {
if (!newVal) {
this.linkList = [this.$route.fullPath]
this.pageList = [this.$route]
}
}
},
methods: {
changePage (key) {
this.activePage = key
},
editPage (key, action) {
this[action](key)
},
remove (key) {
if(key==indexKey){
this.$message.warning('首页不能关闭!')
return
}
if (this.pageList.length === 1) {
this.$message.warning('这是最后一页,不能再关闭了啦')
return
}
this.pageList = this.pageList.filter(item => item.fullPath !== key)
let index = this.linkList.indexOf(key)
this.linkList = this.linkList.filter(item => item !== key)
index = index >= this.linkList.length ? this.linkList.length - 1 : index
this.activePage = this.linkList[index]
},
onContextmenu (e) {
const pagekey = this.getPageKey(e.target)
if (pagekey !== null) {
e.preventDefault()
this.menuVisible = true
}
},
getPageKey (target, depth) {
depth = depth || 0
if (depth > 2) {
return null
}
let pageKey = target.getAttribute('pagekey')
pageKey = pageKey || (target.previousElementSibling ? target.previousElementSibling.getAttribute('pagekey') : null)
return pageKey || (target.firstElementChild ? this.getPageKey(target.firstElementChild, ++depth) : null)
},
onMenuSelect (key, target) {
let pageKey = this.getPageKey(target)
switch (key) {
case '1':
this.closeLeft(pageKey)
break
case '2':
this.closeRight(pageKey)
break
case '3':
this.closeOthers(pageKey)
break
default:
break
}
},
closeOthers (pageKey) {
let index = this.linkList.indexOf(pageKey)
if(pageKey==indexKey){
this.linkList = this.linkList.slice(index, index + 1)
this.pageList = this.pageList.slice(index, index + 1)
this.activePage = this.linkList[0]
}else{
let indexContent = this.pageList.slice(0,1)[0]
this.linkList = this.linkList.slice(index, index + 1)
this.pageList = this.pageList.slice(index, index + 1)
this.linkList.unshift(indexKey)
this.pageList.unshift(indexContent)
this.activePage = this.linkList[1]
}
},
closeLeft (pageKey) {
if(pageKey==indexKey){
return
}
let tempList = [...this.pageList];
let indexContent = tempList.slice(0,1)[0]
let index = this.linkList.indexOf(pageKey)
this.linkList = this.linkList.slice(index)
this.pageList = this.pageList.slice(index)
this.linkList.unshift(indexKey)
this.pageList.unshift(indexContent)
if (this.linkList.indexOf(this.activePage) < 0) {
this.activePage = this.linkList[0]
}
},
closeRight (pageKey) {
let index = this.linkList.indexOf(pageKey)
this.linkList = this.linkList.slice(0, index + 1)
this.pageList = this.pageList.slice(0, index + 1)
if (this.linkList.indexOf(this.activePage < 0)) {
this.activePage = this.linkList[this.linkList.length - 1]
}
}
},
}
</script>
<style lang="scss">
/*
* The following styles are auto-applied to elements with
* transition="page-transition" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the page transition by editing
* these styles.
*/
.page-transition-enter {
opacity: 0;
}
.page-transition-leave-active {
opacity: 0;
}
.page-transition-enter .page-transition-container,
.page-transition-leave-active .page-transition-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
/*美化弹出Tab样式*/
.ant-tabs-nav-container {
margin-top: 4px;
}
</style>
\ No newline at end of file
<template>
<div id="userLayout" :class="['user-layout-wrapper', device]">
<div class="container">
<div class="top">
<div class="header">
<a href="/">
<img src="~@/assets/logo.svg" class="logo" alt="logo">
<span class="title">Jeecg Boot</span>
</a>
</div>
<div class="desc">
Jeecg Boot 是中国最具影响力的 企业级 快速开发平台
</div>
</div>
<route-view></route-view>
<div class="footer">
<div class="links">
<a href="_self">帮助</a>
<a href="_self">隐私</a>
<a href="_self">条款</a>
</div>
<div class="copyright">
Copyright &copy; 2019 <a href="http://www.jeecg.org" target="_blank">JEECG开源社区</a> 出品
</div>
</div>
</div>
</div>
</template>
<script>
import RouteView from "@/components/layouts/RouteView"
import { mixinDevice } from '@/utils/mixin.js'
export default {
name: "UserLayout",
components: { RouteView },
mixins: [mixinDevice],
data () {
return {}
},
mounted () {
document.body.classList.add('userLayout')
},
beforeDestroy () {
document.body.classList.remove('userLayout')
},
}
</script>
<style lang="scss" scoped>
#userLayout.user-layout-wrapper {
height: 100%;
&.mobile {
.container {
.main {
max-width: 368px;
width: 98%;
}
}
}
.container {
width: 100%;
min-height: 100%;
background: #f0f2f5 url(~@/assets/background.svg) no-repeat 50%;
background-size: 100%;
padding: 110px 0 144px;
position: relative;
a {
text-decoration: none;
}
.top {
text-align: center;
.header {
height: 44px;
line-height: 44px;
.badge {
position: absolute;
display: inline-block;
line-height: 1;
vertical-align: middle;
margin-left: -12px;
margin-top: -10px;
opacity: 0.8;
}
.logo {
height: 44px;
vertical-align: top;
margin-right: 16px;
border-style: none;
}
.title {
font-size: 33px;
color: rgba(0, 0, 0, .85);
font-family: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-weight: 600;
position: relative;
top: 2px;
}
}
.desc {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
margin-top: 12px;
margin-bottom: 40px;
}
}
.main {
min-width: 260px;
width: 368px;
margin: 0 auto;
}
.footer {
position: absolute;
width: 100%;
bottom: 0;
padding: 0 16px;
margin: 48px 0 24px;
text-align: center;
.links {
margin-bottom: 8px;
font-size: 14px;
a {
color: rgba(0, 0, 0, 0.45);
transition: all 0.3s;
&:not(:last-child) {
margin-right: 40px;
}
}
}
.copyright {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
}
}
}
</style>
\ No newline at end of file
import UserLayout from '@/components/layouts/UserLayout'
import BlankLayout from '@/components/layouts/BlankLayout'
import BasicLayout from '@/components/layouts/BasicLayout'
import RouteView from '@/components/layouts/RouteView'
import PageView from '@/components/layouts/PageView'
import TabLayout from '@/components/layouts/TabLayout'
export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView, TabLayout }
\ No newline at end of file
<template>
<a-menu :style="style" class="contextmenu" v-show="visible" @click="handleClick" :selectedKeys="selectedKeys">
<a-menu-item :key="item.key" v-for="item in itemList">
<a-icon role="menuitemicon" v-if="item.icon" :type="item.icon" />{{ item.text }}
</a-menu-item>
</a-menu>
</template>
<script>
export default {
name: 'Contextmenu',
props: {
visible: {
type: Boolean,
required: false,
default: false
},
itemList: {
type: Array,
required: true,
default: () => []
}
},
data () {
return {
left: 0,
top: 0,
target: null,
selectedKeys: []
}
},
computed: {
style () {
return {
left: this.left + 'px',
top: this.top + 'px'
}
}
},
created () {
window.addEventListener('mousedown', e => this.closeMenu(e))
window.addEventListener('contextmenu', e => this.setPosition(e))
},
methods: {
closeMenu (e) {
if (['menuitemicon', 'menuitem'].indexOf(e.target.getAttribute('role')) < 0) {
this.$emit('update:visible', false)
}
},
setPosition (e) {
this.left = e.clientX
this.top = e.clientY
this.target = e.target
},
handleClick ({key}) {
this.$emit('select', key, this.target)
this.$emit('update:visible', false)
}
}
}
</script>
<style lang="less" scoped>
.contextmenu{
position: fixed;
z-index: 1;
border: 1px solid #9e9e9e;
border-radius: 4px;
box-shadow: 2px 2px 10px #aaaaaa !important;
}
</style>
<template>
<a-layout-sider
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
width="248px"
:collapsible="collapsible"
v-model="collapsed"
:trigger="null">
<logo />
<s-menu
:collapsed="collapsed"
:menu="menus"
:theme="theme"
@select="onSelect"
:mode="mode"
style="padding: 16px 0px;"></s-menu>
</a-layout-sider>
</template>
<script>
import ALayoutSider from "ant-design-vue/es/layout/Sider"
import Logo from '../tools/Logo'
import SMenu from './index'
import { mixin, mixinDevice } from '@/utils/mixin.js'
export default {
name: "SideMenu",
components: { ALayoutSider, Logo, SMenu },
mixins: [mixin, mixinDevice],
props: {
mode: {
type: String,
required: false,
default: 'inline'
},
theme: {
type: String,
required: false,
default: 'dark'
},
collapsible: {
type: Boolean,
required: false,
default: false
},
collapsed: {
type: Boolean,
required: false,
default: false
},
menus: {
type: Array,
required: true
}
},
methods: {
onSelect (obj) {
this.$emit('menuSelect', obj)
}
}
}
</script>
\ No newline at end of file
import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'
const { Item, SubMenu } = Menu
export default {
name: 'SMenu',
props: {
menu: {
type: Array,
required: true
},
theme: {
type: String,
required: false,
default: 'dark'
},
mode: {
type: String,
required: false,
default: 'inline'
},
collapsed: {
type: Boolean,
required: false,
default: false
}
},
data () {
return {
openKeys: [],
selectedKeys: [],
cachedOpenKeys: []
}
},
computed: {
rootSubmenuKeys: (vm) => {
let keys = []
vm.menu.forEach(item => keys.push(item.path))
return keys
}
},
created () {
this.updateMenu()
},
watch: {
collapsed (val) {
if (val) {
this.cachedOpenKeys = this.openKeys
this.openKeys = []
} else {
this.openKeys = this.cachedOpenKeys
}
},
'$route': function () {
this.updateMenu()
}
},
methods: {
renderIcon: function (h, icon) {
return icon === 'none' || icon === undefined ? null
: h(Icon, { props: { type: icon !== undefined ? icon : '' } })
},
renderMenuItem: function (h, menu, pIndex, index) {
return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index },
[
h(
'router-link',
{ attrs: { to: { name: menu.name } } },
[
this.renderIcon(h, menu.meta.icon),
h('span', [ menu.meta.title ])
]
)
]
)
},
renderSubMenu: function (h, menu, pIndex, index) {
const this2_ = this;
let subItem = [ h('span',
{ slot: 'title' },
[
this.renderIcon(h, menu.meta.icon),
h('span', [ menu.meta.title ])
]
) ]
let itemArr = []
let pIndex_ = pIndex + '_' + index
if (!menu.alwaysShow) {
menu.children.forEach(function (item, i) {
itemArr.push(this2_.renderItem(h, item, pIndex_, i))
})
}
return h(
SubMenu,
{ key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index },
subItem.concat(itemArr)
)
},
renderItem: function (h, menu, pIndex, index) {
if (!menu.hidden) {
return menu.children && !menu.alwaysShow ? this.renderSubMenu(h, menu, pIndex, index) : this.renderMenuItem(h, menu, pIndex, index)
}
},
renderMenu: function (h, menuTree) {
const this2_ = this
let menuArr = []
menuTree.forEach(function (menu, i) {
if (!menu.hidden) {
menuArr.push(this2_.renderItem(h, menu, '0', i))
}
})
return menuArr
},
onOpenChange (openKeys) {
const latestOpenKey = openKeys.find(key => this.openKeys.indexOf(key) === -1)
if (this.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [ latestOpenKey ] : []
}
},
updateMenu () {
let routes = this.$route.matched.concat()
if (routes.length >= 4 && this.$route.meta.hidden) {
routes.pop()
this.selectedKeys = [ routes[2].path ]
} else {
this.selectedKeys = [ routes.pop().path ]
}
let openKeys = []
if (this.mode === 'inline') {
routes.forEach((item) => {
openKeys.push(item.path)
})
}
this.collapsed ? this.cachedOpenKeys = openKeys : this.openKeys = openKeys
}
},
render (h) {
return h(
Menu,
{
props: {
theme: this.$props.theme,
mode: this.$props.mode,
openKeys: this.openKeys,
selectedKeys: this.selectedKeys
},
on: {
openChange: this.onOpenChange,
select: (obj) => {
this.selectedKeys = obj.selectedKeys
this.$emit('select', obj)
}
}
}, this.renderMenu(h, this.menu)
)
}
}
\ No newline at end of file
<template>
<div class="footer">
<div class="links">
<a href="http://www.jeecg.org" target="_blank">JEECG 首页</a>
<a href="https://github.com/zhangdaiscott/jeecg-boot" target="_blank">
<a-icon type="github"/>
</a>
<a href="https://ant.design/">Ant Design</a>
<a href="https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/">Vue Antd</a>
</div>
<div class="copyright">
Copyright
<a-icon type="copyright"/>
2019 <span>JEECG开源社区 出品</span>
</div>
</div>
</template>
<script>
export default {
name: "LayoutFooter"
}
</script>
<style lang="scss" scoped>
.footer {
padding: 0 16px;
margin: 48px 0 24px;
text-align: center;
.links {
margin-bottom: 8px;
a {
color: rgba(0, 0, 0, .45);
&:hover {
color: rgba(0, 0, 0, .65);
}
&:not(:last-child) {
margin-right: 40px;
}
}
}
.copyright {
color: rgba(0, 0, 0, .45);
font-size: 14px;
}
}
</style>
\ No newline at end of file
<template>
<!-- , width: fixedHeader ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%' -->
<a-layout-header v-if="!headerBarFixed" :class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]" :style="{ padding: '0' }">
<div v-if="mode === 'sidemenu'" class="header">
<a-icon
v-if="device==='mobile'"
class="trigger"
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
@click.native="toggle"></a-icon>
<a-icon
v-else
class="trigger"
:type="collapsed ? 'menu-unfold' : 'menu-fold'"
@click.native="toggle"/>
<span>欢迎进入 Jeecg-Boot 企业级快速开发平台</span>
<user-menu></user-menu>
</div>
<div v-else :class="['top-nav-header-index', theme]">
<div class="header-index-wide">
<div class="header-index-left">
<logo class="top-nav-header" :show-title="device !== 'mobile'" />
<s-menu
v-if="device !== 'mobile'"
mode="horizontal"
:menu="menus"
:theme="theme"
></s-menu>
<a-icon
v-else
class="trigger"
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
@click.native="toggle"></a-icon>
</div>
<user-menu class="header-index-right"></user-menu>
</div>
</div>
</a-layout-header>
</template>
<script>
import UserMenu from '../tools/UserMenu'
import SMenu from '../menu/'
import Logo from '../tools/Logo'
import { mixin } from '@/utils/mixin.js'
export default {
name: "GlobalHeader",
components: {
UserMenu,
SMenu,
Logo
},
mixins: [mixin],
props: {
mode: {
type: String,
// sidemenu, topmenu
default: 'sidemenu'
},
menus: {
type: Array,
required: true
},
theme: {
type: String,
required: false,
default: 'dark'
},
collapsed: {
type: Boolean,
required: false,
default: false
},
device: {
type: String,
required: false,
default: 'desktop'
}
},
data() {
return {
headerBarFixed: false,
}
},
mounted () {
window.addEventListener('scroll', this.handleScroll)
},
methods: {
handleScroll () {
if (this.autoHideHeader) {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
if (scrollTop > 100) {
this.headerBarFixed = true
} else {
this.headerBarFixed = false
}
} else {
this.headerBarFixed = false
}
},
toggle() {
this.$emit('toggle')
}
}
}
</script>
<style lang="scss" scoped>
/* update_begin author:scott date:20190220 for: 缩小首页布局顶部的高度*/
.layout .top-nav-header-index .header-index-wide {
margin-left: 20px
}
.layout .header {
height: 59px;
}
.ant-layout-header {
height: 59px;
line-height: 59px;
}
.layout .top-nav-header-index .header-index-wide .ant-menu.ant-menu-horizontal {
height: 59px;
line-height: 59px;
}
/* update_end author:scott date:20190220 for: 缩小首页布局顶部的高度*/
</style>
\ No newline at end of file
<template>
<a-layout class="layout" :class="[device]">
<template v-if="layoutMode === 'sidemenu'">
<a-drawer
v-if="device === 'mobile'"
:wrapClassName="'drawer-sider ' + navTheme"
placement="left"
@close="() => this.collapsed = false"
:closable="false"
:visible="collapsed"
>
<side-menu
mode="inline"
:menus="menus"
@menuSelect="menuSelect"
:theme="navTheme"
:collapsed="false"
:collapsible="true"></side-menu>
</a-drawer>
<side-menu
v-else
mode="inline"
:menus="menus"
:theme="navTheme"
:collapsed="collapsed"
:collapsible="true"></side-menu>
</template>
<!-- 下次优化这些代码 -->
<template v-else>
<a-drawer
v-if="device === 'mobile'"
:wrapClassName="'drawer-sider ' + navTheme"
placement="left"
@close="() => this.collapsed = false"
:closable="false"
:visible="collapsed"
>
<side-menu
mode="inline"
:menus="menus"
@menuSelect="menuSelect"
:theme="navTheme"
:collapsed="false"
:collapsible="true"></side-menu>
</a-drawer>
</template>
<a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: fixSiderbar && isDesktop() ? `${sidebarOpened ? 256 : 80}px` : '0' }">
<!-- layout header -->
<global-header
:mode="layoutMode"
:menus="menus"
:theme="navTheme"
:collapsed="collapsed"
:device="device"
@toggle="toggle"
/>
<!-- layout content -->
<a-layout-content :style="{ margin: '10px 24px 0', height: '100%', paddingTop: fixedHeader ? '64px' : '0' }">
<slot></slot>
</a-layout-content>
<!-- layout footer -->
<a-layout-footer style="padding: 0px">
<global-footer />
</a-layout-footer>
</a-layout>
<setting-drawer></setting-drawer>
</a-layout>
</template>
<script>
import SideMenu from '@/components/menu/SideMenu'
import GlobalHeader from '@/components/page/GlobalHeader'
import GlobalFooter from '@/components/page/GlobalFooter'
import SettingDrawer from '@/components/setting/SettingDrawer'
import { triggerWindowResizeEvent } from '@/utils/util'
import { mapState, mapActions } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin.js'
export default {
name: "GlobalLayout",
components: {
SideMenu,
GlobalHeader,
GlobalFooter,
SettingDrawer
},
mixins: [mixin, mixinDevice],
data () {
return {
collapsed: false,
menus: []
}
},
computed: {
...mapState({
// 主路由
mainMenu: state => state.permission.addRouters,
})
},
watch: {
sidebarOpened(val) {
this.collapsed = !val
},
},
created() {
this.menus = this.mainMenu.find((item) => item.path === '/').children
},
methods: {
...mapActions(['setSidebar']),
toggle() {
this.collapsed = !this.collapsed
this.setSidebar(!this.collapsed)
triggerWindowResizeEvent()
},
menuSelect() {
if (!this.isDesktop()) {
this.collapsed = false
}
}
},
}
</script>
<style lang="scss">
body {
// 打开滚动条固定显示
overflow-y: scroll;
&.colorWeak {
filter: invert(80%);
}
}
.layout {
min-height: 100vh;
overflow-x: hidden;
&.mobile {
.ant-layout-content {
.content {
margin: 24px 0 0;
}
}
/**
* ant-table-wrapper
* 覆盖的表格手机模式样式,如果想修改在手机上表格最低宽度,可以在这里改动
*/
.ant-table-wrapper {
.ant-table-content {
overflow-y: auto;
}
.ant-table-body {
min-width: 800px;
}
}
.sidemenu {
.ant-header-fixedHeader {
&.ant-header-side-opened, &.ant-header-side-closed {
width: 100%
}
}
}
.topmenu {
/* 必须为 topmenu 才能启用流式布局 */
&.content-width-Fluid {
.header-index-wide {
margin-left: 0;
}
}
}
}
&.ant-layout-has-sider {
flex-direction: row;
}
.trigger {
font-size: 20px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color .3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.topmenu {
.ant-header-fixedHeader {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: 100%;
transition: width .2s;
&.ant-header-side-opened {
width: 100%;
}
&.ant-header-side-closed {
width: 100%;
}
}
/* 必须为 topmenu 才能启用流式布局 */
&.content-width-Fluid {
.header-index-wide {
max-width: unset;
margin-left: 24px;
}
.page-header-index-wide {
max-width: unset;
}
}
}
.sidemenu {
.ant-header-fixedHeader {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: 100%;
transition: width .2s;
&.ant-header-side-opened {
width: calc(100% - 256px)
}
&.ant-header-side-closed {
width: calc(100% - 80px)
}
}
}
.header {
height: 64px;
padding: 0 12px 0 0;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
position: relative;
}
.header, .top-nav-header-index {
.user-wrapper {
float: right;
height: 100%;
.action {
cursor: pointer;
padding: 0 12px;
display: inline-block;
transition: all .3s;
height: 100%;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
.avatar {
margin: 20px 8px 20px 0;
color: #1890ff;
background: hsla(0, 0%, 100%, .85);
vertical-align: middle;
}
.icon {
font-size: 16px;
padding: 4px;
}
}
}
&.dark {
.user-wrapper {
.action {
color: rgba(255, 255, 255, 0.85);
&:hover {
background: rgba(255, 255, 255, 0.16);
}
}
}
}
}
&.mobile {
.top-nav-header-index {
.header-index-wide {
.header-index-left {
.trigger {
color: rgba(255, 255, 255, 0.85);
padding: 0 12px;
}
.logo.top-nav-header {
text-align: center;
width: 56px;
line-height: 58px;
}
}
}
&.light {
.header-index-wide {
.header-index-left {
.trigger {
color: rgba(0, 0, 0, 0.65);
}
}
}
//
}
}
}
&.tablet {
// overflow: hidden; text-overflow:ellipsis; white-space: nowrap;
.top-nav-header-index {
.header-index-wide {
.header-index-left {
.logo > a {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
}
}
}
}
.top-nav-header-index {
box-shadow: 0 1px 4px rgba(0,21,41,.08);
position: relative;
transition: background .3s,width .2s;
.header-index-wide {
max-width: 1200px;
margin: auto;
padding-left: 0;
display: flex;
height: 64px;
.ant-menu.ant-menu-horizontal {
border: none;
height: 64px;
line-height: 64px;
}
.header-index-left {
flex: 1 1;
display: flex;
.logo.top-nav-header {
width: 165px;
height: 64px;
position: relative;
line-height: 64px;
transition: all .3s;
overflow: hidden;
img {
display: inline-block;
vertical-align: middle;
height: 32px;
}
h1 {
color: #fff;
display: inline-block;
vertical-align: top;
font-size: 16px;
margin: 0 0 0 12px;
font-weight: 400;
}
}
}
.header-index-right {
float: right;
height: 64px;
overflow: hidden;
}
}
&.light {
background-color: #fff;
.header-index-wide {
.header-index-left {
.logo {
h1 {
color: #002140;
}
}
}
}
}
}
// 内容区
.layout-content {
margin: 24px 24px 0px;
height: 100%;
height: 64px;
padding: 0 12px 0 0;
}
}
.topmenu {
.page-header-index-wide {
max-width: 1200px;
margin: 0 auto;
}
}
// drawer-sider 自定义
.ant-drawer.drawer-sider {
.sider {
box-shadow: none;
}
&.dark {
.ant-drawer-content {
background-color: rgb(0, 21, 41);
}
}
&.light {
box-shadow: none;
.ant-drawer-content {
background-color: #fff;
}
}
.ant-drawer-body {
padding: 0
}
}
// 菜单样式
.sider {
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
position: relative;
z-index: 10;
&.ant-fixed-sidemenu {
position: fixed;
height: 100%;
}
.logo {
height: 64px;
position: relative;
line-height: 64px;
padding-left: 24px;
-webkit-transition: all .3s;
transition: all .3s;
background: #002140;
overflow: hidden;
img, h1 {
display: inline-block;
vertical-align: middle;
}
img {
height: 32px;
}
h1 {
color: #fff;
font-size: 20px;
margin: 0 0 0 12px;
font-family: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-weight: 600;
}
}
&.light {
background-color: #fff;
box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05);
.logo {
background: #fff;
box-shadow: 1px 1px 0px 0px #e8e8e8;
h1 {
color: unset;
}
}
.ant-menu-light {
border-right-color: transparent;
}
}
}
// 外置的样式控制
.user-dropdown-menu-wrapper.ant-dropdown-menu {
padding: 4px 0;
.ant-dropdown-menu-item {
width: 160px;
}
.ant-dropdown-menu-item > .anticon:first-child,
.ant-dropdown-menu-item > a > .anticon:first-child,
.ant-dropdown-menu-submenu-title > .anticon:first-child
.ant-dropdown-menu-submenu-title > a > .anticon:first-child {
min-width: 12px;
margin-right: 8px;
}
}
// 数据列表 样式
.table-alert {
margin-bottom: 16px;
}
.table-page-search-wrapper {
.ant-form-inline {
.ant-form-item {
display: flex;
margin-bottom: 24px;
margin-right: 0;
.ant-form-item-control-wrapper {
flex: 1 1;
display: inline-block;
vertical-align: middle;
}
>.ant-form-item-label {
line-height: 32px;
padding-right: 8px;
width: auto;
}
.ant-form-item-control {
height: 32px;
line-height: 32px;
}
}
}
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
}
.content {
.table-operator {
margin-bottom: 18px;
button {
margin-right: 8px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="page-header">
<div class="page-header-index-wide">
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item v-for="(item, index) in breadList" :key="index">
<router-link v-if="item.name != name" :to="{ path: item.path }">
{{ item.meta.title }}
</router-link>
<span v-else>{{ item.meta.title }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
<div class="detail">
<div class="main" v-if="!$route.meta.hiddenHeaderContent">
<div class="row">
<img v-if="logo" :src="logo" class="logo"/>
<h1 v-if="title" class="title">{{ title }}</h1>
<div class="action">
<slot name="action"></slot>
</div>
</div>
<div class="row">
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar"/>
</div>
<div v-if="this.$slots.content" class="headerContent">
<slot name="content"></slot>
</div>
<div v-if="this.$slots.extra" class="extra">
<slot name="extra"></slot>
</div>
</div>
<div>
<slot name="pageMenu"></slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Breadcrumb from '@/components/tools/Breadcrumb'
export default {
name: "PageHeader",
components: {
"s-breadcrumb": Breadcrumb
},
props: {
title: {
type: String,
default: '',
required: false
},
breadcrumb: {
type: Array,
default: null,
required: false
},
logo: {
type: String,
default: '',
required: false
},
avatar: {
type: String,
default: '',
required: false
}
},
data() {
return {
name: '',
breadList: [],
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
this.breadList = []
// this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: '首页'}})
this.name = this.$route.name
this.$route.matched.forEach((item) => {
// item.name !== 'index' && this.breadList.push(item)
this.breadList.push(item)
})
}
},
watch: {
$route() {
this.getBreadcrumb()
}
}
}
</script>
<style lang="scss" scoped>
.page-header {
background: #fff;
padding: 16px 32px 0;
border-bottom: 1px solid #e8e8e8;
.breadcrumb {
margin-bottom: 16px;
}
.detail {
display: flex;
/*margin-bottom: 16px;*/
.avatar {
flex: 0 1 72px;
margin: 0 24px 8px 0;
& > span {
border-radius: 72px;
display: block;
width: 72px;
height: 72px;
}
}
.main {
width: 100%;
flex: 0 1 auto;
.row {
display: flex;
width: 100%;
.avatar {
margin-bottom: 16px;
}
}
.title {
font-size: 20px;
font-weight: 500;
font-size: 20px;
line-height: 28px;
font-weight: 500;
color: rgba(0,0,0,.85);
margin-bottom: 16px;
flex: auto;
}
.logo {
width: 28px;
height: 28px;
border-radius: 4px;
margin-right: 16px;
}
.content, .headerContent {
flex: auto;
color: rgba(0,0,0,.45);
line-height: 22px;
.link {
margin-top: 16px;
line-height: 24px;
a {
font-size: 14px;
margin-right: 32px;
}
}
}
.extra {
flex: 0 1 auto;
margin-left: 88px;
min-width: 242px;
text-align: right;
}
.action {
margin-left: 56px;
min-width: 266px;
flex: 0 1 auto;
text-align: right;
&:empty {
display: none;
}
}
}
}
}
.mobile .page-header {
.main {
.row {
flex-wrap: wrap;
.avatar {
flex: 0 1 25%;
margin: 0 2% 8px 0;
}
.content, .headerContent {
flex: 0 1 70%;
.link {
margin-top: 16px;
line-height: 24px;
a {
font-size: 14px;
margin-right: 10px;
}
}
}
.extra {
flex: 1 1 auto;
margin-left: 0;
min-width: 0;
text-align: right;
}
.action {
margin-left: unset;
min-width: 266px;
flex: 0 1 auto;
text-align: left;
margin-bottom: 12px;
&:empty {
display: none;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div :style="!$route.meta.pageHeader ? 'margin: -16px -24px 0px;' : null">
<!-- pageHeader , route meta hideHeader:true on hide -->
<page-header v-if="!$route.meta.pageHeader" :title="title" :logo="logo" :avatar="avatar">
<slot slot="action" name="action"></slot>
<slot slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && desc">
<p style="font-size: 14px;color: rgba(0,0,0,.65)">{{ desc }}</p>
<div class="link">
<template v-for="(link, index) in linkList">
<a :key="index" :href="link.href">
<a-icon :type="link.icon"/>
<span>{{ link.title }}</span>
</a>
</template>
</div>
</div>
<slot slot="extra" name="extra"></slot>
<div slot="pageMenu">
<div class="page-menu-search" v-if="search">
<a-input-search style="width: 80%; max-width: 522px;" placeholder="请输入..." size="large" enterButton="搜索" />
</div>
<div class="page-menu-tabs" v-if="tabs && tabs.items">
<!-- @change="callback" :activeKey="activeKey" -->
<a-tabs :tabBarStyle="{margin: 0}" @change="tabs.callback" :activeKey="tabs.active()">
<a-tab-pane v-for="item in tabs.items" :tab="item.title" :key="item.key"></a-tab-pane>
</a-tabs>
</div>
</div>
</page-header>
<div class="content">
<div :class="['page-header-index-wide']">
<slot></slot>
</div>
</div>
</div>
</template>
<script>
import PageHeader from './PageHeader'
export default {
name: "LayoutContent",
components: {
PageHeader
},
// ['desc', 'logo', 'title', 'avatar', 'linkList', 'extraImage']
props: {
desc: {
type: String,
default: null
},
logo: {
type: String,
default: null
},
title: {
type: String,
default: null
},
avatar: {
type: String,
default: null
},
linkList: {
type: Array,
default: null
},
extraImage: {
type: String,
default: null
},
search: {
type: Boolean,
default: false
},
tabs: {
type: Object,
default: () => {}
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.content {
margin: 24px 24px 0;
.link {
margin-top: 16px;
&:not(:empty) {
margin-bottom: 16px;
}
a {
margin-right: 32px;
height: 24px;
line-height: 24px;
display: inline-block;
i {
font-size: 24px;
margin-right: 8px;
vertical-align: middle;
}
span {
height: 24px;
line-height: 24px;
display: inline-block;
vertical-align: middle;
}
}
}
}
.page-menu-search {
text-align: center;
margin-bottom: 16px;
}
.page-menu-tabs {
margin-top: 48px;
}
.page-header[data-v-6740ec88] {
margin: 0px 24px 0;
}
</style>
\ No newline at end of file
此差异已折叠。
此差异已折叠。
<template>
<div class="toolbar">
<div style="float: left">
<slot name="extra"></slot>
</div>
<div style="float: right">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "FooterToolBar"
}
</script>
<style lang="scss" scoped>
.toolbar {
position: fixed;
width: 100%;
bottom: 0;
right: 0;
height: 56px;
line-height: 56px;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
background: #fff;
border-top: 1px solid #e8e8e8;
padding: 0 24px;
z-index: 9;
}
</style>
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册