mirror of https://github.com/jeecgboot/jeecg-boot
zhangdaihao
6 years ago
385 changed files with 60058 additions and 1 deletions
@ -0,0 +1,147 @@
|
||||
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 |
@ -0,0 +1,39 @@
|
||||
[*] |
||||
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 |
||||
|
@ -0,0 +1 @@
|
||||
public/* linguist-vendored |
@ -0,0 +1,21 @@
|
||||
.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* |
@ -0,0 +1,5 @@
|
||||
{ |
||||
"printWidth": 120, |
||||
"semi": false, |
||||
"singleQuote": true |
||||
} |
@ -0,0 +1,21 @@
|
||||
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. |
@ -0,0 +1,147 @@
|
||||
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 |
@ -0,0 +1,5 @@
|
||||
module.exports = { |
||||
presets: [ |
||||
'@vue/app' |
||||
] |
||||
} |
@ -0,0 +1,24 @@
|
||||
'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') |
||||
} |
||||
}, |
||||
} |
@ -0,0 +1,98 @@
|
||||
{ |
||||
"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" |
||||
] |
||||
} |
After Width: | Height: | Size: 78 KiB |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,238 @@
|
||||
<!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> |
After Width: | Height: | Size: 20 KiB |
File diff suppressed because one or more lines are too long
@ -0,0 +1,44 @@
|
||||
<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> |
@ -0,0 +1,129 @@
|
||||
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 |
||||
} |
||||
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
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 |
@ -0,0 +1,49 @@
|
||||
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' |
||||
} |
||||
}) |
||||
} |
@ -0,0 +1,99 @@
|
||||
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 |
||||
}) |
||||
} |
||||
|
After Width: | Height: | Size: 8.7 KiB |
@ -0,0 +1,21 @@
|
||||
.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; |
||||
} |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 4.6 KiB |
@ -0,0 +1,46 @@
|
||||
<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> |
@ -0,0 +1,100 @@
|
||||
<!-- |
||||
<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> |
@ -0,0 +1,4 @@
|
||||
import AvatarList from './List' |
||||
import "./index.less" |
||||
|
||||
export default AvatarList |
@ -0,0 +1,60 @@
|
||||
@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; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,111 @@
|
||||
<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> |
@ -0,0 +1,103 @@
|
||||
<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> |
@ -0,0 +1,3 @@
|
||||
import CountDown from './CountDown' |
||||
|
||||
export default CountDown |
@ -0,0 +1,63 @@
|
||||
<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> |
@ -0,0 +1,3 @@
|
||||
import Ellipsis from './Ellipsis' |
||||
|
||||
export default Ellipsis |
@ -0,0 +1,54 @@
|
||||
<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> |
@ -0,0 +1,3 @@
|
||||
import NumberInfo from './NumberInfo' |
||||
|
||||
export default NumberInfo |
@ -0,0 +1,55 @@
|
||||
@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; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,41 @@
|
||||
<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> |
@ -0,0 +1,3 @@
|
||||
import Trend from './Trend.vue' |
||||
|
||||
export default Trend |
@ -0,0 +1,42 @@
|
||||
@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; |
||||
} |
||||
} |
@ -0,0 +1,25 @@
|
||||
|
||||
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 |
||||
}, '') |
||||
} |
@ -0,0 +1,12 @@
|
||||
/** |
||||
* components util |
||||
*/ |
||||
|
||||
/** |
||||
* 清理空值,对象 |
||||
* @param children |
||||
* @returns {*[]} |
||||
*/ |
||||
export function filterEmpty (children = []) { |
||||
return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) |
||||
} |
@ -0,0 +1,57 @@
|
||||
<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> |
@ -0,0 +1,67 @@
|
||||
<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> |
@ -0,0 +1,67 @@
|
||||
<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> |
@ -0,0 +1,68 @@
|
||||
<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> |
@ -0,0 +1,75 @@
|
||||
<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> |
@ -0,0 +1,68 @@
|
||||
<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> |
@ -0,0 +1,77 @@
|
||||
<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> |
@ -0,0 +1,64 @@
|
||||
<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> |
@ -0,0 +1,82 @@
|
||||
<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> |
@ -0,0 +1,13 @@
|
||||
.antv-chart-mini { |
||||
position: relative; |
||||
width: 100%; |
||||
|
||||
.chart-wrapper { |
||||
position: absolute; |
||||
bottom: -28px; |
||||
width: 100%; |
||||
|
||||
/* margin: 0 -5px; |
||||
overflow: hidden;*/ |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
<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> |
@ -0,0 +1,61 @@
|
||||
/** |
||||
* 字典 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 ''; |
||||
} |
||||
} |
@ -0,0 +1,36 @@
|
||||
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); |
||||
} |
@ -0,0 +1,22 @@
|
||||
<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> |
@ -0,0 +1,7 @@
|
||||
import T from './DictSelectTag.vue' |
||||
const DictSelectTag = { |
||||
install: function (Vue) { |
||||
Vue.component('DictSelectTag',T); |
||||
} |
||||
} |
||||
export default DictSelectTag; |
@ -0,0 +1,4 @@
|
||||
@import "~ant-design-vue/lib/style/index"; |
||||
|
||||
// The prefix to use on all css classes from ant-pro. |
||||
@ant-pro-prefix : ant-pro; |
@ -0,0 +1,74 @@
|
||||
<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> |
@ -0,0 +1,43 @@
|
||||
日期组件 |
||||
说明: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> |
@ -0,0 +1,60 @@
|
||||
<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> |
@ -0,0 +1,16 @@
|
||||
<template> |
||||
<div> |
||||
<router-view /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
|
||||
export default { |
||||
name: "BlankLayout", |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,47 @@
|
||||
<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> |
@ -0,0 +1,85 @@
|
||||
<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> |
@ -0,0 +1,17 @@
|
||||
<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> |
@ -0,0 +1,213 @@
|
||||
<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> |
@ -0,0 +1,150 @@
|
||||
<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 © 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> |
@ -0,0 +1,8 @@
|
||||
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 } |
@ -0,0 +1,71 @@
|
||||
<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> |
@ -0,0 +1,62 @@
|
||||
<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> |
@ -0,0 +1,162 @@
|
||||
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) |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,51 @@
|
||||
<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> |
@ -0,0 +1,127 @@
|
||||
<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> |
@ -0,0 +1,596 @@
|
||||
<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> |
@ -0,0 +1,238 @@
|
||||
<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> |
@ -0,0 +1,127 @@
|
||||
<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> |
@ -0,0 +1,58 @@
|
||||
<template> |
||||
<a-popover trigger="click" placement="bottomRight" :overlayStyle="{ width: '300px' }"> |
||||
<template slot="content"> |
||||
<a-spin :spinning="loadding"> |
||||
<a-tabs> |
||||
<a-tab-pane v-for="(tab, k) in tabs" :tab="tab.title" :key="k"> |
||||
|
||||
</a-tab-pane> |
||||
</a-tabs> |
||||
</a-spin> |
||||
</template> |
||||
<span @click="fetchNotice" class="header-notice"> |
||||
<a-badge count="12"> |
||||
<a-icon style="font-size: 16px; padding: 4px" type="bell" /> |
||||
</a-badge> |
||||
</span> |
||||
</a-popover> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "HeaderNotice", |
||||
props: { |
||||
tabs: { |
||||
type: Array, |
||||
default: null, |
||||
required: true |
||||
} |
||||
}, |
||||
data () { |
||||
return { |
||||
loadding: false |
||||
} |
||||
}, |
||||
methods: { |
||||
fetchNotice () { |
||||
if (this.loadding) { |
||||
this.loadding = false |
||||
return |
||||
} |
||||
this.loadding = true |
||||
setTimeout(() => { |
||||
this.loadding = false |
||||
}, 2000) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.header-notice{ |
||||
display: inline-block; |
||||
transition: all 0.3s; |
||||
span { |
||||
vertical-align: initial; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,314 @@
|
||||
<template> |
||||
<div class="setting-drawer"> |
||||
<a-drawer |
||||
width="300" |
||||
placement="right" |
||||
:closable="false" |
||||
@close="onClose" |
||||
:visible="visible" |
||||
:style="{}" |
||||
> |
||||
<div class="setting-drawer-index-content"> |
||||
|
||||
<div :style="{ marginBottom: '24px' }"> |
||||
<h3 class="setting-drawer-index-title">整体风格设置</h3> |
||||
|
||||
<div class="setting-drawer-index-blockChecbox"> |
||||
<a-tooltip> |
||||
<template slot="title"> |
||||
暗色菜单风格 |
||||
</template> |
||||
<div class="setting-drawer-index-item" @click="handleMenuTheme('dark')"> |
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" alt="dark"> |
||||
<div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'"> |
||||
<a-icon type="check"/> |
||||
</div> |
||||
</div> |
||||
</a-tooltip> |
||||
|
||||
<a-tooltip> |
||||
<template slot="title"> |
||||
亮色菜单风格 |
||||
</template> |
||||
<div class="setting-drawer-index-item" @click="handleMenuTheme('light')"> |
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" alt="light"> |
||||
<div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'"> |
||||
<a-icon type="check"/> |
||||
</div> |
||||
</div> |
||||
</a-tooltip> |
||||
</div> |
||||
</div> |
||||
|
||||
<div :style="{ marginBottom: '24px' }"> |
||||
<h3 class="setting-drawer-index-title">主题色</h3> |
||||
|
||||
<div style="height: 20px"> |
||||
<a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index"> |
||||
<template slot="title"> |
||||
{{ item.key }} |
||||
</template> |
||||
<a-tag :color="item.color" @click="changeColor(item.color)"> |
||||
<a-icon type="check" v-if="item.color === primaryColor"></a-icon> |
||||
</a-tag> |
||||
</a-tooltip> |
||||
|
||||
</div> |
||||
</div> |
||||
<a-divider /> |
||||
|
||||
<div :style="{ marginBottom: '24px' }"> |
||||
<h3 class="setting-drawer-index-title">导航模式</h3> |
||||
|
||||
<div class="setting-drawer-index-blockChecbox"> |
||||
<a-tooltip> |
||||
<template slot="title"> |
||||
侧边栏导航 |
||||
</template> |
||||
<div class="setting-drawer-index-item" @click="handleLayout('sidemenu')"> |
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" alt="sidemenu"> |
||||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'"> |
||||
<a-icon type="check"/> |
||||
</div> |
||||
</div> |
||||
</a-tooltip> |
||||
|
||||
<a-tooltip> |
||||
<template slot="title"> |
||||
顶部栏导航 |
||||
</template> |
||||
<div class="setting-drawer-index-item" @click="handleLayout('topmenu')"> |
||||
<img src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" alt="topmenu"> |
||||
<div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'"> |
||||
<a-icon type="check"/> |
||||
</div> |
||||
</div> |
||||
</a-tooltip> |
||||
</div> |
||||
<div :style="{ marginTop: '24px' }"> |
||||
<a-list :split="false"> |
||||
<a-list-item> |
||||
<a-tooltip slot="actions"> |
||||
<template slot="title"> |
||||
该设定仅 [顶部栏导航] 时有效 |
||||
</template> |
||||
<a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange"> |
||||
<a-select-option value="Fixed">固定</a-select-option> |
||||
<a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option> |
||||
</a-select> |
||||
</a-tooltip> |
||||
<a-list-item-meta> |
||||
<div slot="title">内容区域宽度</div> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
<a-list-item> |
||||
<a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" /> |
||||
<a-list-item-meta> |
||||
<div slot="title">固定 Header</div> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
<a-list-item> |
||||
<a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" /> |
||||
<a-list-item-meta> |
||||
<div slot="title" :style="{ textDecoration: !fixedHeader ? 'line-through' : 'unset' }">下滑时隐藏 Header</div> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
<a-list-item > |
||||
<a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :defaultChecked="fixSiderbar" @change="handleFixSiderbar" /> |
||||
<a-list-item-meta> |
||||
<div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
</a-list> |
||||
</div> |
||||
</div> |
||||
<a-divider /> |
||||
|
||||
<div :style="{ marginBottom: '24px' }"> |
||||
<h3 class="setting-drawer-index-title">其他设置</h3> |
||||
<div> |
||||
<a-list :split="false"> |
||||
<a-list-item> |
||||
<a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" /> |
||||
<a-list-item-meta> |
||||
<div slot="title">色弱模式</div> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
</a-list> |
||||
</div> |
||||
</div> |
||||
<a-divider /> |
||||
<div :style="{ marginBottom: '24px' }"> |
||||
<a-alert type="warning"> |
||||
<span slot="message"> |
||||
配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件 |
||||
<a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/defaultSettings.js" target="_blank">src/defaultSettings.js</a> |
||||
</span> |
||||
</a-alert> |
||||
</div> |
||||
</div> |
||||
<div class="setting-drawer-index-handle" @click="toggle"> |
||||
<a-icon type="setting" v-if="!visible"/> |
||||
<a-icon type="close" v-else/> |
||||
</div> |
||||
</a-drawer> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import DetailList from '@/components/tools/DetailList' |
||||
import SettingItem from '@/components/setting/SettingItem' |
||||
import config from '@/defaultSettings' |
||||
import { updateTheme, updateColorWeak, colorList } from '@/components/tools/setting' |
||||
import { mixin, mixinDevice } from '@/utils/mixin.js' |
||||
|
||||
export default { |
||||
components: { |
||||
DetailList, |
||||
SettingItem |
||||
}, |
||||
mixins: [mixin, mixinDevice], |
||||
data() { |
||||
return { |
||||
visible: true, |
||||
colorList, |
||||
} |
||||
}, |
||||
watch: { |
||||
|
||||
}, |
||||
mounted () { |
||||
const vm = this |
||||
setTimeout(() => { |
||||
vm.visible = false |
||||
}, 16) |
||||
// 当主题色不是默认色时,才进行主题编译 |
||||
if (this.primaryColor !== config.primaryColor) { |
||||
updateTheme(this.primaryColor) |
||||
} |
||||
if (this.colorWeak !== config.colorWeak) { |
||||
updateColorWeak(this.colorWeak) |
||||
} |
||||
}, |
||||
methods: { |
||||
showDrawer() { |
||||
this.visible = true |
||||
}, |
||||
onClose() { |
||||
this.visible = false |
||||
}, |
||||
toggle() { |
||||
this.visible = !this.visible |
||||
}, |
||||
onColorWeak (checked) { |
||||
this.$store.dispatch('ToggleWeak', checked) |
||||
updateColorWeak(checked) |
||||
}, |
||||
handleMenuTheme (theme) { |
||||
this.$store.dispatch('ToggleTheme', theme) |
||||
}, |
||||
handleLayout (mode) { |
||||
this.$store.dispatch('ToggleLayoutMode', mode) |
||||
// 因为顶部菜单不能固定左侧菜单栏,所以强制关闭 |
||||
// |
||||
this.handleFixSiderbar(false); |
||||
}, |
||||
handleContentWidthChange (type) { |
||||
this.$store.dispatch('ToggleContentWidth', type) |
||||
}, |
||||
changeColor (color) { |
||||
if (this.primaryColor !== color) { |
||||
this.$store.dispatch('ToggleColor', color) |
||||
updateTheme(color) |
||||
} |
||||
}, |
||||
handleFixedHeader (fixed) { |
||||
this.$store.dispatch('ToggleFixedHeader', fixed) |
||||
}, |
||||
handleFixedHeaderHidden (autoHidden) { |
||||
this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden) |
||||
}, |
||||
handleFixSiderbar (fixed) { |
||||
if (this.layoutMode === 'topmenu') { |
||||
this.$store.dispatch('ToggleFixSiderbar', false) |
||||
return; |
||||
} |
||||
this.$store.dispatch('ToggleFixSiderbar', fixed) |
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
|
||||
.setting-drawer-index-content { |
||||
|
||||
.setting-drawer-index-blockChecbox { |
||||
display: flex; |
||||
|
||||
.setting-drawer-index-item { |
||||
margin-right: 16px; |
||||
position: relative; |
||||
border-radius: 4px; |
||||
cursor: pointer; |
||||
|
||||
img { |
||||
width: 48px; |
||||
} |
||||
|
||||
.setting-drawer-index-selectIcon { |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
width: 100%; |
||||
padding-top: 15px; |
||||
padding-left: 24px; |
||||
height: 100%; |
||||
color: #1890ff; |
||||
font-size: 14px; |
||||
font-weight: 700; |
||||
} |
||||
} |
||||
} |
||||
.setting-drawer-theme-color-colorBlock { |
||||
width: 20px; |
||||
height: 20px; |
||||
border-radius: 2px; |
||||
float: left; |
||||
cursor: pointer; |
||||
margin-right: 8px; |
||||
padding-left: 0px; |
||||
padding-right: 0px; |
||||
text-align: center; |
||||
color: #fff; |
||||
font-weight: 700; |
||||
|
||||
i { |
||||
font-size: 14px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.setting-drawer-index-handle { |
||||
position: absolute; |
||||
top: 240px; |
||||
background: #1890ff; |
||||
width: 48px; |
||||
height: 48px; |
||||
right: 300px; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
cursor: pointer; |
||||
pointer-events: auto; |
||||
z-index: 1001; |
||||
text-align: center; |
||||
font-size: 16px; |
||||
border-radius: 4px 0 0 4px; |
||||
|
||||
i { |
||||
color: rgb(255, 255, 255); |
||||
font-size: 20px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,38 @@
|
||||
<template> |
||||
<div class="setting-drawer-index-item"> |
||||
<h3 class="setting-drawer-index-title">{{ title }}</h3> |
||||
<slot></slot> |
||||
<a-divider v-if="divider"/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "SettingItem", |
||||
props: { |
||||
title: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
divider: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
|
||||
.setting-drawer-index-item { |
||||
margin-bottom: 24px; |
||||
|
||||
.setting-drawer-index-title { |
||||
font-size: 14px; |
||||
color: rgba(0, 0, 0, .85); |
||||
line-height: 22px; |
||||
margin-bottom: 12px; |
||||
} |
||||
|
||||
} |
||||
</style> |
@ -0,0 +1,292 @@
|
||||
Table 重封装组件说明 |
||||
==== |
||||
|
||||
|
||||
封装说明 |
||||
---- |
||||
|
||||
> 基础的使用方式与 API 与 [官方版(Table)](https://vuecomponent.github.io/ant-design-vue/components/table-cn/) 本一致,在其基础上,封装了加载数据的方法。 |
||||
> |
||||
> 你无需在你是用表格的页面进行分页逻辑处理,仅需向 Table 组件传递绑定 `:data="Promise"` 对象即可 |
||||
|
||||
|
||||
|
||||
例子1 |
||||
---- |
||||
(基础使用) |
||||
|
||||
```vue |
||||
|
||||
<template> |
||||
<s-table |
||||
ref="table" |
||||
:rowKey="(record) => record.data.id" |
||||
size="default" |
||||
:columns="columns" |
||||
:data="loadData" |
||||
> |
||||
</s-table> |
||||
</template> |
||||
|
||||
<script> |
||||
import STable from '@/components/table/' |
||||
|
||||
export default { |
||||
components: { |
||||
STable |
||||
}, |
||||
data() { |
||||
return { |
||||
columns: [ |
||||
{ |
||||
title: '规则编号', |
||||
dataIndex: 'no' |
||||
}, |
||||
{ |
||||
title: '描述', |
||||
dataIndex: 'description' |
||||
}, |
||||
{ |
||||
title: '服务调用次数', |
||||
dataIndex: 'callNo', |
||||
sorter: true, |
||||
needTotal: true, |
||||
customRender: (text) => text + ' 次' |
||||
}, |
||||
{ |
||||
title: '状态', |
||||
dataIndex: 'status', |
||||
needTotal: true |
||||
}, |
||||
{ |
||||
title: '更新时间', |
||||
dataIndex: 'updatedAt', |
||||
sorter: true |
||||
} |
||||
], |
||||
// 查询条件参数 |
||||
queryParam: {}, |
||||
// 加载数据方法 必须为 Promise 对象 |
||||
loadData: parameter => { |
||||
return this.$http.get('/service', { |
||||
params: Object.assign(parameter, this.queryParam) |
||||
}).then(res => { |
||||
return res.result |
||||
}) |
||||
}, |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
``` |
||||
|
||||
|
||||
|
||||
例子2 |
||||
---- |
||||
|
||||
(简单的表格,最后一列是各种操作) |
||||
|
||||
```vue |
||||
<template> |
||||
<s-table |
||||
ref="table" |
||||
size="default" |
||||
:columns="columns" |
||||
:data="loadData" |
||||
> |
||||
<span slot="action" slot-scope="text, record"> |
||||
<a>编辑</a> |
||||
<a-divider type="vertical"/> |
||||
<a-dropdown> |
||||
<a class="ant-dropdown-link"> |
||||
更多 <a-icon type="down"/> |
||||
</a> |
||||
<a-menu slot="overlay"> |
||||
<a-menu-item> |
||||
<a href="javascript:;">1st menu item</a> |
||||
</a-menu-item> |
||||
<a-menu-item> |
||||
<a href="javascript:;">2nd menu item</a> |
||||
</a-menu-item> |
||||
<a-menu-item> |
||||
<a href="javascript:;">3rd menu item</a> |
||||
</a-menu-item> |
||||
</a-menu> |
||||
</a-dropdown> |
||||
</span> |
||||
</s-table> |
||||
</template> |
||||
|
||||
<script> |
||||
import STable from '@/components/table/' |
||||
|
||||
export default { |
||||
components: { |
||||
STable |
||||
}, |
||||
data() { |
||||
return { |
||||
columns: [ |
||||
{ |
||||
title: '规则编号', |
||||
dataIndex: 'no' |
||||
}, |
||||
{ |
||||
title: '描述', |
||||
dataIndex: 'description' |
||||
}, |
||||
{ |
||||
title: '服务调用次数', |
||||
dataIndex: 'callNo', |
||||
}, |
||||
{ |
||||
title: '状态', |
||||
dataIndex: 'status', |
||||
}, |
||||
{ |
||||
title: '更新时间', |
||||
dataIndex: 'updatedAt', |
||||
}, |
||||
{ |
||||
table: '操作', |
||||
dataIndex: 'action', |
||||
scopedSlots: {customRender: 'action'}, |
||||
} |
||||
], |
||||
// 查询条件参数 |
||||
queryParam: {}, |
||||
// 加载数据方法 必须为 Promise 对象 |
||||
loadData: parameter => { |
||||
return this.$http.get('/service', { |
||||
params: Object.assign(parameter, this.queryParam) |
||||
}).then(res => { |
||||
return res.result |
||||
}) |
||||
}, |
||||
} |
||||
}, |
||||
methods: { |
||||
edit(row) { |
||||
// axios 发送数据到后端 修改数据成功后 |
||||
// 调用 refresh() 重新加载列表数据 |
||||
// 这里 setTimeout 模拟发起请求的网络延迟.. |
||||
setTimeout(() => { |
||||
this.$refs.table.refresh() |
||||
}, 1500) |
||||
|
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
``` |
||||
|
||||
|
||||
|
||||
内置方法 |
||||
---- |
||||
|
||||
通过 `this.$refs.table` 调用 |
||||
|
||||
`this.$refs.table.refresh()` 刷新列表 (用户新增/修改数据后,重载列表数据) |
||||
|
||||
> 注意:要调用 `refresh()` 需要给表格组件设定 `ref` 值 |
||||
|
||||
|
||||
|
||||
注意事项 |
||||
---- |
||||
|
||||
> 你可能需要为了与后端提供的接口返回结果一致而去修改以下代码: |
||||
(需要注意的是,这里的修改是全局性的,意味着整个项目所有使用该 table 组件都需要遵守这个返回结果定义的字段。) |
||||
|
||||
修改 `@/components/table/index.js` 第 106 行起 |
||||
|
||||
|
||||
|
||||
```javascript |
||||
result.then(r => { |
||||
this.localPagination = Object.assign({}, this.localPagination, { |
||||
current: r.pageNo, // 返回结果中的当前分页数 |
||||
total: r.totalCount, // 返回结果中的总记录数 |
||||
showSizeChanger: this.showSizeChanger, |
||||
pageSize: (pagination && pagination.pageSize) || |
||||
this.localPagination.pageSize |
||||
}); |
||||
|
||||
!r.totalCount && ['auto', false].includes(this.showPagination) && (this.localPagination = false) |
||||
this.localDataSource = r.data; // 返回结果中的数组数据 |
||||
this.localLoading = false |
||||
}); |
||||
``` |
||||
返回 JSON 例子: |
||||
```json |
||||
{ |
||||
"message": "", |
||||
"result": { |
||||
"data": [{ |
||||
id: 1, |
||||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', |
||||
title: 'Alipay', |
||||
description: '那是一种内在的东西, 他们到达不了,也无法触及的', |
||||
status: 1, |
||||
updatedAt: '2018-07-26 00:00:00' |
||||
}, |
||||
{ |
||||
id: 2, |
||||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', |
||||
title: 'Angular', |
||||
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的', |
||||
status: 1, |
||||
updatedAt: '2018-07-26 00:00:00' |
||||
}, |
||||
{ |
||||
id: 3, |
||||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', |
||||
title: 'Ant Design', |
||||
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', |
||||
status: 1, |
||||
updatedAt: '2018-07-26 00:00:00' |
||||
}, |
||||
{ |
||||
id: 4, |
||||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', |
||||
title: 'Ant Design Pro', |
||||
description: '那时候我只会想自己想要什么,从不想自己拥有什么', |
||||
status: 1, |
||||
updatedAt: '2018-07-26 00:00:00' |
||||
}, |
||||
{ |
||||
id: 5, |
||||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', |
||||
title: 'Bootstrap', |
||||
description: '凛冬将至', |
||||
status: 1, |
||||
updatedAt: '2018-07-26 00:00:00' |
||||
}, |
||||
{ |
||||
id: 6, |
||||
cover: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', |
||||
title: 'Vue', |
||||
description: '生命就像一盒巧克力,结果往往出人意料', |
||||
status: 1, |
||||
updatedAt: '2018-07-26 00:00:00' |
||||
} |
||||
], |
||||
"pageSize": 10, |
||||
"pageNo": 0, |
||||
"totalPage": 6, |
||||
"totalCount": 57 |
||||
}, |
||||
"status": 200, |
||||
"timestamp": 1534955098193 |
||||
} |
||||
``` |
||||
|
||||
|
||||
|
||||
更新时间 |
||||
---- |
||||
|
||||
该文档最后更新于: 2018-10-31 PM 08:15 |
@ -0,0 +1,252 @@
|
||||
<template> |
||||
<div class="standard-table"> |
||||
<div class="alert"> |
||||
<a-alert type="info" :show-icon="true"> |
||||
<div slot="message"> |
||||
已选择 <a style="font-weight: 600">{{ selectedRows.length }}</a> |
||||
<template v-for="(item, index) in needTotalList" v-if="item.needTotal"> |
||||
{{ item.title }} 总计 |
||||
<a :key="index" style="font-weight: 600"> |
||||
{{ item.customRender ? item.customRender(item.total) : item.total }} |
||||
</a> |
||||
</template> |
||||
<a style="margin-left: 24px" @click="onClearSelected">清空</a> |
||||
</div> |
||||
</a-alert> |
||||
</div> |
||||
<a-table |
||||
:size="size" |
||||
:bordered="bordered" |
||||
:loading="loading" |
||||
:columns="columns" |
||||
:dataSource="current" |
||||
:rowKey="rowKey" |
||||
:pagination="pagination" |
||||
:rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: updateSelect }" |
||||
> |
||||
</a-table> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "StandardTable", |
||||
// props: ['bordered', 'loading', 'columns', 'data', 'rowKey', 'pagination', 'selectedRows'], |
||||
props: { |
||||
|
||||
/** |
||||
* 数据加载函数,返回值必须是 Promise |
||||
* 默认情况下必须传递 data 参数; |
||||
* 如果使用本地数据渲染表格,业务代码中将获取本地数据包装为 Promise 即可。 |
||||
* |
||||
* currentData 用于向外暴露表格当前渲染的数据, |
||||
* 业务开发中也可以直接修改 currentData,从而重新渲染表格(仅推荐用于客户端排序、数据过滤等场景) |
||||
*/ |
||||
data: { |
||||
type: Function, |
||||
required: true |
||||
}, |
||||
dataSource: { |
||||
type: Array, |
||||
default () { |
||||
return [] |
||||
} |
||||
}, |
||||
columns: { |
||||
type: Array, |
||||
required: true |
||||
}, |
||||
/* pagination: { |
||||
type: Object, |
||||
default () { |
||||
return {} |
||||
} |
||||
},*/ |
||||
pageSize: { |
||||
type: Number, |
||||
default: 10 |
||||
}, |
||||
pageNum: { |
||||
type: Number, |
||||
default: 1 |
||||
}, |
||||
pageSizeOptions: { |
||||
type: Array, |
||||
default () { |
||||
return ['10', '20', '30', '40', '50'] |
||||
} |
||||
}, |
||||
responseParamsName: { |
||||
type: Object, |
||||
default () { |
||||
return {} |
||||
} |
||||
}, |
||||
bordered: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
/** |
||||
* 表格大小风格,default, middle, small |
||||
*/ |
||||
size: { |
||||
type: String, |
||||
default: 'default' |
||||
}, |
||||
rowKey: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
selectedRows: { |
||||
type: Array, |
||||
default: null |
||||
} |
||||
}, |
||||
data () { |
||||
return { |
||||
needTotalList: [], |
||||
selectedRowKeys: [], |
||||
|
||||
loading: true, |
||||
|
||||
total: 0, |
||||
pageNumber: this.pageNum, |
||||
currentPageSize: this.pageSize, |
||||
defaultCurrent: 1, |
||||
sortParams: {}, |
||||
|
||||
current: [], |
||||
pagination: {}, |
||||
paramsName: {}, |
||||
} |
||||
}, |
||||
created () { |
||||
//数据请求参数配置 |
||||
this.paramsName = Object.assign( |
||||
{}, |
||||
{ |
||||
pageNumber: "pageNo", |
||||
pageSize: "pageSize", |
||||
total: "totalCount", |
||||
results: "data", |
||||
sortColumns: "sortColumns" |
||||
}, |
||||
this.responseParamsName |
||||
); |
||||
|
||||
this.needTotalList = this.initTotalList(this.columns) |
||||
|
||||
// load data |
||||
this.loadData( { pageNum: this.pageNumber } ) |
||||
}, |
||||
methods: { |
||||
updateSelect (selectedRowKeys, selectedRows) { |
||||
this.selectedRowKeys = selectedRowKeys |
||||
let list = this.needTotalList |
||||
this.needTotalList = list.map(item => { |
||||
return { |
||||
...item, |
||||
total: selectedRows.reduce((sum, val) => { |
||||
return sum + val[item.dataIndex] |
||||
}, 0) |
||||
} |
||||
}) |
||||
this.$emit('change', selectedRowKeys, selectedRows) |
||||
}, |
||||
initTotalList (columns) { |
||||
const totalList = [] |
||||
columns.forEach(column => { |
||||
if (column.needTotal) { |
||||
totalList.push({ ...column, total: 0 }) |
||||
} |
||||
}) |
||||
return totalList |
||||
}, |
||||
|
||||
loadData (params) { |
||||
let that = this |
||||
that.loading = true |
||||
params = Object.assign({}, params) |
||||
const remoteParams = Object.assign({}, that.sortParams) |
||||
remoteParams[that.paramsName.pageNumber] = params.pageNum || that.pageNumber |
||||
remoteParams[that.paramsName.pageSize] = params.pageSize || that.currentPageSize |
||||
|
||||
if (params.pageNum) { |
||||
that.pageNumber = params.pageNum |
||||
} |
||||
if (params.pageSize) { |
||||
that.currentPageSize = params.pageSize |
||||
} |
||||
|
||||
let dataPromise = that.data(remoteParams) |
||||
|
||||
dataPromise.then( response => { |
||||
if (!response) { |
||||
that.loading = false |
||||
return |
||||
} |
||||
let results = response[that.paramsName.results] |
||||
results = (results instanceof Array && results) || [] |
||||
|
||||
that.current = results |
||||
|
||||
that.$emit("update:currentData", that.current.slice()) |
||||
that.$emit("dataloaded", that.current.slice()) |
||||
|
||||
that.total = response[that.paramsName.total] * 1 |
||||
that.pagination = that.pager() |
||||
that.loading = false |
||||
}, () => { |
||||
// error callback |
||||
that.loading = false |
||||
}) |
||||
}, |
||||
// eslint-disable-next-line |
||||
onPagerChange (page, pageSize) { |
||||
this.pageNumber = page |
||||
this.loadData({ pageNum: page }) |
||||
}, |
||||
onPagerSizeChange (current, size) { |
||||
this.currentPageSize = size |
||||
/* |
||||
if (current === this.pageNumber) this.loadData() |
||||
console.log('page-size-change', current, size) |
||||
*/ |
||||
}, |
||||
onClearSelected () { |
||||
this.selectedRowKeys = [] |
||||
this.updateSelect([], []) |
||||
}, |
||||
pager () { |
||||
return { |
||||
total: this.total, |
||||
showTotal: total => `共有 ${total} 条`, |
||||
showSizeChanger: true, |
||||
pageSizeOptions: this.pageSizeOptions, |
||||
pageSize: this.pageSize, |
||||
defaultCurrent: this.defaultCurrent, |
||||
onChange: this.onPagerChange, |
||||
onShowSizeChange: this.onPagerSizeChange |
||||
} |
||||
} |
||||
}, |
||||
watch: { |
||||
'selectedRows': function (selectedRows) { |
||||
this.needTotalList = this.needTotalList.map(item => { |
||||
return { |
||||
...item, |
||||
total: selectedRows.reduce( (sum, val) => { |
||||
return sum + val[item.dataIndex] |
||||
}, 0) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.alert { |
||||
margin-bottom: 16px; |
||||
} |
||||
</style> |
@ -0,0 +1,265 @@
|
||||
import T from "ant-design-vue/es/table/Table"; |
||||
import get from "lodash.get" |
||||
export default { |
||||
data() { |
||||
return { |
||||
needTotalList: [], |
||||
|
||||
selectedRows: [], |
||||
selectedRowKeys: [], |
||||
|
||||
localLoading: false, |
||||
localDataSource: [], |
||||
localPagination: Object.assign({}, T.props.pagination) |
||||
}; |
||||
}, |
||||
props: Object.assign({}, T.props, { |
||||
rowKey: { |
||||
type: [String, Function], |
||||
default: 'id' |
||||
}, |
||||
data: { |
||||
type: Function, |
||||
required: true |
||||
}, |
||||
pageNum: { |
||||
type: Number, |
||||
default: 1 |
||||
}, |
||||
pageSize: { |
||||
type: Number, |
||||
default: 10 |
||||
}, |
||||
showSizeChanger: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
showAlertInfo: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
showPagination: { |
||||
default: 'auto' |
||||
} |
||||
}), |
||||
watch: { |
||||
'localPagination.current'(val) { |
||||
this.$router.push({ |
||||
name: this.$route.name, |
||||
params: Object.assign({}, this.$route.params, { |
||||
pageNo: val |
||||
}), |
||||
}); |
||||
}, |
||||
pageNum(val) { |
||||
Object.assign(this.localPagination, { |
||||
current: val |
||||
}); |
||||
}, |
||||
pageSize(val) { |
||||
console.log('pageSize:', val) |
||||
Object.assign(this.localPagination, { |
||||
pageSize: val |
||||
}); |
||||
}, |
||||
showSizeChanger(val) { |
||||
console.log('showSizeChanger', val) |
||||
Object.assign(this.localPagination, { |
||||
showSizeChanger: val |
||||
}); |
||||
} |
||||
}, |
||||
created() { |
||||
this.localPagination = ['auto', true].includes(this.showPagination) && Object.assign({}, this.localPagination, { |
||||
current: this.pageNum, |
||||
pageSize: this.pageSize, |
||||
showSizeChanger: this.showSizeChanger |
||||
}); |
||||
this.needTotalList = this.initTotalList(this.columns) |
||||
this.loadData(); |
||||
}, |
||||
methods: { |
||||
refresh() { |
||||
this.loadData(); |
||||
}, |
||||
loadData(pagination, filters, sorter) { |
||||
|
||||
this.localLoading = true |
||||
var result = this.data( |
||||
Object.assign({ |
||||
pageNo: (pagination && pagination.current) || |
||||
this.localPagination.current, |
||||
pageSize: (pagination && pagination.pageSize) || |
||||
this.localPagination.pageSize |
||||
}, |
||||
(sorter && sorter.field && { |
||||
sortField: sorter.field |
||||
}) || {}, |
||||
(sorter && sorter.order && { |
||||
sortOrder: sorter.order |
||||
}) || {}, { |
||||
...filters |
||||
} |
||||
) |
||||
); |
||||
|
||||
if (result instanceof Promise) { |
||||
result.then(r => { |
||||
this.localPagination = Object.assign({}, this.localPagination, { |
||||
current: r.pageNo, // 返回结果中的当前分页数
|
||||
total: r.totalCount, // 返回结果中的总记录数
|
||||
showSizeChanger: this.showSizeChanger, |
||||
pageSize: (pagination && pagination.pageSize) || |
||||
this.localPagination.pageSize |
||||
}); |
||||
|
||||
!r.totalCount && ['auto', false].includes(this.showPagination) && (this.localPagination = false) |
||||
this.localDataSource = r.data; // 返回结果中的数组数据
|
||||
this.localLoading = false |
||||
}); |
||||
} |
||||
}, |
||||
initTotalList(columns) { |
||||
const totalList = [] |
||||
columns && columns instanceof Array && columns.forEach(column => { |
||||
if (column.needTotal) { |
||||
totalList.push({ ...column, |
||||
total: 0 |
||||
}) |
||||
} |
||||
}) |
||||
return totalList |
||||
}, |
||||
updateSelect(selectedRowKeys, selectedRows) { |
||||
this.selectedRowKeys = selectedRowKeys |
||||
this.selectedRows = selectedRows |
||||
let list = this.needTotalList |
||||
this.needTotalList = list.map(item => { |
||||
return { |
||||
...item, |
||||
total: selectedRows.reduce((sum, val) => { |
||||
let total = sum + get(val, item.dataIndex) |
||||
return isNaN(total) ? 0 : total |
||||
}, 0) |
||||
} |
||||
}) |
||||
// this.$emit('change', selectedRowKeys, selectedRows)
|
||||
}, |
||||
updateEdit() { |
||||
this.selectedRows = [] |
||||
}, |
||||
onClearSelected() { |
||||
this.selectedRowKeys = [] |
||||
this.updateSelect([], []) |
||||
}, |
||||
renderMsg(h) { |
||||
const _vm = this |
||||
let d = [] |
||||
// 构建 已选择
|
||||
d.push( |
||||
h('span', { |
||||
style: { |
||||
marginRight: '12px' |
||||
} |
||||
}, ['已选择 ', h('a', { |
||||
style: { |
||||
fontWeight: 600 |
||||
} |
||||
}, this.selectedRows.length)]) |
||||
); |
||||
|
||||
// 构建 列统计
|
||||
this.needTotalList.map(item => { |
||||
d.push(h('span', { |
||||
style: { |
||||
marginRight: '12px' |
||||
} |
||||
}, |
||||
[ |
||||
`${ item.title }总计 `, |
||||
h('a', { |
||||
style: { |
||||
fontWeight: 600 |
||||
} |
||||
}, `${ !item.customRender ? item.total : item.customRender(item.total) }`) |
||||
])) |
||||
}); |
||||
|
||||
// 构建 清空选择
|
||||
d.push(h('a', { |
||||
style: { |
||||
marginLeft: '24px' |
||||
}, |
||||
on: { |
||||
click: _vm.onClearSelected |
||||
} |
||||
}, '清空')) |
||||
|
||||
return d |
||||
}, |
||||
renderAlert(h) { |
||||
return h('span', { |
||||
slot: 'message' |
||||
}, this.renderMsg(h)) |
||||
}, |
||||
}, |
||||
|
||||
render(h) { |
||||
const _vm = this |
||||
|
||||
let props = {}, |
||||
localKeys = Object.keys(this.$data); |
||||
|
||||
Object.keys(T.props).forEach(k => { |
||||
let localKey = `local${k.substring(0,1).toUpperCase()}${k.substring(1)}`; |
||||
if (localKeys.includes(localKey)) { |
||||
return props[k] = _vm[localKey]; |
||||
} |
||||
return props[k] = _vm[k]; |
||||
}) |
||||
|
||||
|
||||
// 显示信息提示
|
||||
if (this.showAlertInfo) { |
||||
|
||||
props.rowSelection = { |
||||
selectedRowKeys: this.selectedRowKeys, |
||||
onChange: (selectedRowKeys, selectedRows) => { |
||||
_vm.updateSelect(selectedRowKeys, selectedRows) |
||||
_vm.$emit('onSelect', { selectedRowKeys: selectedRowKeys, selectedRows: selectedRows }) |
||||
} |
||||
}; |
||||
|
||||
return h('div', {}, [ |
||||
h("a-alert", { |
||||
style: { |
||||
marginBottom: '16px' |
||||
}, |
||||
props: { |
||||
type: 'info', |
||||
showIcon: true |
||||
} |
||||
}, [_vm.renderAlert(h)]), |
||||
h("a-table", { |
||||
tag: "component", |
||||
attrs: props, |
||||
on: { |
||||
change: _vm.loadData |
||||
}, |
||||
scopedSlots: this.$scopedSlots |
||||
}, this.$slots.default) |
||||
]); |
||||
|
||||
} |
||||
|
||||
return h("a-table", { |
||||
tag: "component", |
||||
attrs: props, |
||||
on: { |
||||
change: _vm.loadData |
||||
}, |
||||
scopedSlots: this.$scopedSlots |
||||
}, this.$slots.default); |
||||
|
||||
} |
||||
}; |
@ -0,0 +1,48 @@
|
||||
<template> |
||||
<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> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
name: '', |
||||
breadList: [], |
||||
} |
||||
}, |
||||
created () { |
||||
this.getBreadcrumb() |
||||
}, |
||||
methods: { |
||||
getBreadcrumb() { |
||||
|
||||
console.log('this.$route.matched', this.$route.matched) |
||||
|
||||
this.breadList = [] |
||||
this.breadList.push({ name: 'dashboard', path: '/dashboard/', meta: { title: '首页' } }) |
||||
|
||||
this.name = this.$route.name |
||||
this.$route.matched.forEach((item) => { |
||||
// item.meta.name === 'dashboard' ? item.path = '/dashboard' : this.$route.path === item.path |
||||
this.breadList.push(item) |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
$route() { |
||||
this.getBreadcrumb() |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,147 @@
|
||||
<template> |
||||
<div :class="['detail-list', size, layout === 'vertical' ? 'vertical': 'horizontal']"> |
||||
<div v-if="title" class="title">{{ title }}</div> |
||||
<a-row> |
||||
<slot></slot> |
||||
</a-row> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { Col } from 'ant-design-vue/es/grid/' |
||||
|
||||
const Item = { |
||||
name: 'DetailListItem', |
||||
props: { |
||||
term: { |
||||
type: String, |
||||
default: '', |
||||
required: false |
||||
}, |
||||
}, |
||||
inject: { |
||||
col: { |
||||
type: Number |
||||
} |
||||
}, |
||||
render () { |
||||
return ( |
||||
<Col {...{props: responsive[this.col]}}> |
||||
<div class="term">{this.$props.term}</div> |
||||
<div class="content">{this.$slots.default}</div> |
||||
</Col> |
||||
) |
||||
} |
||||
} |
||||
|
||||
const responsive = { |
||||
1: { xs: 24 }, |
||||
2: { xs: 24, sm: 12 }, |
||||
3: { xs: 24, sm: 12, md: 8 }, |
||||
4: { xs: 24, sm: 12, md: 6 } |
||||
} |
||||
|
||||
export default { |
||||
name: "DetailList", |
||||
Item: Item, |
||||
components: { |
||||
Col |
||||
}, |
||||
props: { |
||||
title: { |
||||
type: String, |
||||
default: '', |
||||
required: false |
||||
}, |
||||
col: { |
||||
type: Number, |
||||
required: false, |
||||
default: 3 |
||||
}, |
||||
size: { |
||||
type: String, |
||||
required: false, |
||||
default: 'large' |
||||
}, |
||||
layout: { |
||||
type: String, |
||||
required: false, |
||||
default: 'horizontal' |
||||
} |
||||
}, |
||||
provide () { |
||||
return { |
||||
col: this.col > 4 ? 4 : this.col |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
|
||||
.detail-list { |
||||
|
||||
.title { |
||||
color: rgba(0,0,0,.85); |
||||
font-size: 14px; |
||||
font-weight: 500; |
||||
margin-bottom: 16px; |
||||
} |
||||
|
||||
.term { |
||||
color: rgba(0,0,0,.85); |
||||
display: table-cell; |
||||
line-height: 20px; |
||||
margin-right: 8px; |
||||
padding-bottom: 16px; |
||||
white-space: nowrap; |
||||
|
||||
&:after { |
||||
content: ":"; |
||||
margin: 0 8px 0 2px; |
||||
position: relative; |
||||
top: -.5px; |
||||
} |
||||
} |
||||
|
||||
.content { |
||||
color: rgba(0,0,0,.65); |
||||
display: table-cell; |
||||
line-height: 22px; |
||||
padding-bottom: 16px; |
||||
width: 100%; |
||||
} |
||||
|
||||
&.small { |
||||
|
||||
.title { |
||||
font-size: 14px; |
||||
color: rgba(0, 0, 0, .65); |
||||
font-weight: normal; |
||||
margin-bottom: 12px; |
||||
} |
||||
.term, .content { |
||||
padding-bottom: 8px; |
||||
} |
||||
} |
||||
|
||||
&.large { |
||||
.term, .content { |
||||
padding-bottom: 16px; |
||||
} |
||||
|
||||
.title { |
||||
font-size: 16px; |
||||
} |
||||
} |
||||
|
||||
&.vertical { |
||||
.term { |
||||
padding-bottom: 8px; |
||||
} |
||||
.term, .content { |
||||
display: block; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,32 @@
|
||||
<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> |
@ -0,0 +1,67 @@
|
||||
<template> |
||||
<div class="head-info" :class="center && 'center'"> |
||||
<span>{{ title }}</span> |
||||
<p>{{ content }}</p> |
||||
<em v-if="bordered"/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "HeadInfo", |
||||
props: { |
||||
title: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
content: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
bordered: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
center: { |
||||
type: Boolean, |
||||
default: true |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.head-info { |
||||
position: relative; |
||||
text-align: left; |
||||
padding: 0 32px 0 0; |
||||
min-width: 125px; |
||||
|
||||
&.center { |
||||
text-align: center; |
||||
padding: 0 32px; |
||||
} |
||||
|
||||
span { |
||||
color: rgba(0, 0, 0, .45); |
||||
display: inline-block; |
||||
font-size: 14px; |
||||
line-height: 22px; |
||||
margin-bottom: 4px; |
||||
} |
||||
p { |
||||
color: rgba(0, 0, 0, .85); |
||||
font-size: 24px; |
||||
line-height: 32px; |
||||
margin: 0; |
||||
} |
||||
em { |
||||
background-color: #e8e8e8; |
||||
position: absolute; |
||||
height: 56px; |
||||
width: 1px; |
||||
top: 0; |
||||
right: 0; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,85 @@
|
||||
<template> |
||||
<a-popover |
||||
trigger="click" |
||||
placement="bottomRight" |
||||
:autoAdjustOverflow="true" |
||||
:arrowPointAtCenter="true" |
||||
overlayClassName="header-notice-wrapper" |
||||
:overlayStyle="{ width: '300px', top: '50px' }"> |
||||
<template slot="content"> |
||||
<a-spin :spinning="loadding"> |
||||
<a-tabs> |
||||
<a-tab-pane tab="通知" key="1"> |
||||
<a-list> |
||||
<a-list-item> |
||||
<a-list-item-meta title="你收到了 14 份新周报" description="一年前"> |
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
<a-list-item> |
||||
<a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前"> |
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
<a-list-item> |
||||
<a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前"> |
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/> |
||||
</a-list-item-meta> |
||||
</a-list-item> |
||||
</a-list> |
||||
</a-tab-pane> |
||||
<a-tab-pane tab="消息" key="2"> |
||||
123 |
||||
</a-tab-pane> |
||||
<a-tab-pane tab="待办" key="3"> |
||||
123 |
||||
</a-tab-pane> |
||||
</a-tabs> |
||||
</a-spin> |
||||
</template> |
||||
<span @click="fetchNotice" class="header-notice"> |
||||
<a-badge count="12"> |
||||
<a-icon style="font-size: 16px; padding: 4px" type="bell" /> |
||||
</a-badge> |
||||
</span> |
||||
</a-popover> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "HeaderNotice", |
||||
data () { |
||||
return { |
||||
loadding: false |
||||
} |
||||
}, |
||||
methods: { |
||||
fetchNotice () { |
||||
if (this.loadding) { |
||||
this.loadding = false |
||||
return |
||||
} |
||||
this.loadding = true |
||||
setTimeout(() => { |
||||
this.loadding = false |
||||
}, 2000) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="css"> |
||||
.header-notice-wrapper { |
||||
top: 50px !important; |
||||
} |
||||
</style> |
||||
<style lang="scss" scoped> |
||||
.header-notice{ |
||||
display: inline-block; |
||||
transition: all 0.3s; |
||||
|
||||
span { |
||||
vertical-align: initial; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,33 @@
|
||||
<template> |
||||
<div class="logo"> |
||||
<router-link :to="{name:'dashboard'}"> |
||||
<img src="~@/assets/logo.svg" alt="logo"> |
||||
<h1 v-if="showTitle">{{ title }}</h1> |
||||
</router-link> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "Logo", |
||||
props: { |
||||
title: { |
||||
type: String, |
||||
default: 'Jeecg-Boot Pro', |
||||
required: false |
||||
}, |
||||
showTitle: { |
||||
type: Boolean, |
||||
default: true, |
||||
required: false |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
/*缩小首页布 局顶部的高度*/ |
||||
.sider .logo { |
||||
height: 59px!important; |
||||
line-height: 59px!important; |
||||
} |
||||
</style> |
@ -0,0 +1,89 @@
|
||||
<template> |
||||
<!-- 两步验证 --> |
||||
<a-modal |
||||
centered |
||||
v-model="visible" |
||||
@cancel="handleCancel" |
||||
:maskClosable="false" |
||||
> |
||||
<div slot="title" :style="{ textAlign: 'center' }">两步验证</div> |
||||
<template slot="footer"> |
||||
<div :style="{ textAlign: 'center' }"> |
||||
<a-button key="back" @click="handleCancel">返回</a-button> |
||||
<a-button key="submit" type="primary" :loading="stepLoading" @click="handleStepOk"> |
||||
继续 |
||||
</a-button> |
||||
</div> |
||||
</template> |
||||
|
||||
<a-spin :spinning="stepLoading"> |
||||
<a-form layout="vertical" :auto-form-create="(form)=>{this.form = form}"> |
||||
<div class="step-form-wrapper"> |
||||
<p style="text-align: center" v-if="!stepLoading">请在手机中打开 Google Authenticator 或两步验证 APP<br />输入 6 位动态码</p> |
||||
<p style="text-align: center" v-else>正在验证..<br/>请稍后</p> |
||||
<a-form-item |
||||
:style="{ textAlign: 'center' }" |
||||
hasFeedback |
||||
fieldDecoratorId="stepCode" |
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入 6 位动态码!', pattern: /^\d{6}$/, len: 6 }]}" |
||||
> |
||||
<a-input :style="{ textAlign: 'center' }" @keyup.enter.native="handleStepOk" placeholder="000000" /> |
||||
</a-form-item> |
||||
<p style="text-align: center"> |
||||
<a @click="onForgeStepCode">遗失手机?</a> |
||||
</p> |
||||
</div> |
||||
</a-form> |
||||
</a-spin> |
||||
</a-modal> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
props: { |
||||
visible: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
stepLoading: false, |
||||
|
||||
form: null |
||||
}; |
||||
}, |
||||
methods: { |
||||
handleStepOk() { |
||||
const vm = this |
||||
this.stepLoading = true |
||||
this.form.validateFields((err, values) => { |
||||
if (!err) { |
||||
console.log('values', values) |
||||
setTimeout( () => { |
||||
vm.stepLoading = false |
||||
vm.$emit('success', { values }) |
||||
}, 2000) |
||||
return; |
||||
} |
||||
this.stepLoading = false |
||||
this.$emit('error', { err }) |
||||
}) |
||||
}, |
||||
handleCancel () { |
||||
this.visible = false |
||||
this.$emit('cancel') |
||||
}, |
||||
onForgeStepCode() { |
||||
|
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.step-form-wrapper { |
||||
margin: 0 auto; |
||||
width: 80%; |
||||
max-width: 400px; |
||||
} |
||||
</style> |
@ -0,0 +1,93 @@
|
||||
<template> |
||||
<div class="user-wrapper"> |
||||
<span class="action"> |
||||
<a-icon type="question-circle-o"></a-icon> |
||||
</span> |
||||
<header-notice class="action"/> |
||||
<a-dropdown> |
||||
<span class="action ant-dropdown-link user-dropdown-menu"> |
||||
<a-avatar class="avatar" size="small" :src="getAvatar()"/> |
||||
<span>欢迎您,{{ nickname() }}</span> |
||||
</span> |
||||
<a-menu slot="overlay" class="user-dropdown-menu-wrapper"> |
||||
<a-menu-item key="0"> |
||||
<router-link :to="{ name: 'account-center' }"> |
||||
<a-icon type="user"/> |
||||
<span>个人中心</span> |
||||
</router-link> |
||||
</a-menu-item> |
||||
<a-menu-item key="1"> |
||||
<router-link :to="{ name: 'account-settings' }"> |
||||
<a-icon type="setting"/> |
||||
<span>账户设置</span> |
||||
</router-link> |
||||
</a-menu-item> |
||||
<!-- <a-menu-item key="2" disabled> |
||||
<a-icon type="setting"/> |
||||
<span>测试</span> |
||||
</a-menu-item> |
||||
<a-menu-divider/> |
||||
<a-menu-item key="3"> |
||||
<a href="javascript:;" @click="handleLogout"> |
||||
<a-icon type="logout"/> |
||||
<span>退出登录</span> |
||||
</a> |
||||
</a-menu-item>--> |
||||
</a-menu> |
||||
</a-dropdown> |
||||
<span class="action"> |
||||
<a class="logout_title" href="javascript:;" @click="handleLogout"> |
||||
<a-icon type="logout"/> |
||||
<span> 退出登录</span> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import HeaderNotice from './HeaderNotice' |
||||
import { mapActions, mapGetters } from 'vuex' |
||||
import {imgView} from '@/api/api' |
||||
|
||||
export default { |
||||
name: "UserMenu", |
||||
components: { |
||||
HeaderNotice |
||||
}, |
||||
methods: { |
||||
...mapActions(["Logout"]), |
||||
...mapGetters(["nickname", "avatar"]), |
||||
getAvatar(){ |
||||
console.log('url = '+ imgView+this.avatar()) |
||||
return imgView+this.avatar() |
||||
}, |
||||
handleLogout() { |
||||
const that = this |
||||
|
||||
this.$confirm({ |
||||
title: '提示', |
||||
content: '真的要注销登录吗 ?', |
||||
onOk() { |
||||
return that.Logout({}).then(() => { |
||||
window.location.reload() |
||||
}).catch(err => { |
||||
that.$message.error({ |
||||
title: '错误', |
||||
description: err.message |
||||
}) |
||||
}) |
||||
}, |
||||
onCancel() { |
||||
}, |
||||
}); |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.logout_title{ |
||||
color: rgba(0, 0, 0, 0.65); |
||||
text-decoration:none; |
||||
} |
||||
</style> |
@ -0,0 +1,95 @@
|
||||
import { message } from 'ant-design-vue/es'; |
||||
// import defaultSettings from '../defaultSettings';
|
||||
|
||||
let lessNodesAppended; |
||||
|
||||
const colorList = [ |
||||
{ |
||||
key: '薄暮', color: '#F5222D', |
||||
}, |
||||
{ |
||||
key: '火山', color: '#FA541C', |
||||
}, |
||||
{ |
||||
key: '日暮', color: '#FAAD14', |
||||
}, |
||||
{ |
||||
key: '明青', color: '#13C2C2', |
||||
}, |
||||
{ |
||||
key: '极光绿', color: '#52C41A', |
||||
}, |
||||
{ |
||||
key: '拂晓蓝(默认)', color: '#1890FF', |
||||
}, |
||||
{ |
||||
key: '极客蓝', color: '#2F54EB', |
||||
}, |
||||
{ |
||||
key: '酱紫', color: '#722ED1', |
||||
}, |
||||
]; |
||||
|
||||
const updateTheme = primaryColor => { |
||||
// Don't compile less in production!
|
||||
/* if (process.env.NODE_ENV === 'production') { |
||||
return; |
||||
} */ |
||||
// Determine if the component is remounted
|
||||
if (!primaryColor) { |
||||
return; |
||||
} |
||||
const hideMessage = message.loading('正在编译主题!', 0); |
||||
function buildIt() { |
||||
if (!window.less) { |
||||
return; |
||||
} |
||||
setTimeout(() => { |
||||
window.less |
||||
.modifyVars({ |
||||
'@primary-color': primaryColor, |
||||
}) |
||||
.then(() => { |
||||
hideMessage(); |
||||
}) |
||||
.catch(() => { |
||||
message.error('Failed to update theme'); |
||||
hideMessage(); |
||||
}); |
||||
}, 200); |
||||
} |
||||
if (!lessNodesAppended) { |
||||
// insert less.js and color.less
|
||||
const lessStyleNode = document.createElement('link'); |
||||
const lessConfigNode = document.createElement('script'); |
||||
const lessScriptNode = document.createElement('script'); |
||||
lessStyleNode.setAttribute('rel', 'stylesheet/less'); |
||||
lessStyleNode.setAttribute('href', '/color.less'); |
||||
lessConfigNode.innerHTML = ` |
||||
window.less = { |
||||
async: true, |
||||
env: 'production', |
||||
javascriptEnabled: true |
||||
}; |
||||
`;
|
||||
lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js'; |
||||
lessScriptNode.async = true; |
||||
lessScriptNode.onload = () => { |
||||
buildIt(); |
||||
lessScriptNode.onload = null; |
||||
}; |
||||
document.body.appendChild(lessStyleNode); |
||||
document.body.appendChild(lessConfigNode); |
||||
document.body.appendChild(lessScriptNode); |
||||
lessNodesAppended = true; |
||||
} else { |
||||
buildIt(); |
||||
} |
||||
}; |
||||
|
||||
const updateColorWeak = colorWeak => { |
||||
// document.body.className = colorWeak ? 'colorWeak' : '';
|
||||
colorWeak ? document.body.classList.add('colorWeak') : document.body.classList.remove('colorWeak') |
||||
}; |
||||
|
||||
export { updateTheme, colorList, updateColorWeak } |
@ -0,0 +1,394 @@
|
||||
import { UserLayout, TabLayout, RouteView, BlankLayout, PageView } from '@/components/layouts' |
||||
|
||||
/** |
||||
* 走菜单,走权限控制 |
||||
* @type {[null,null]} |
||||
*/ |
||||
export const asyncRouterMap = [ |
||||
|
||||
{ |
||||
path: '/', |
||||
name: 'index', |
||||
component: TabLayout, |
||||
meta: { title: '首页' }, |
||||
redirect: '/dashboard/workplace', |
||||
children: [ |
||||
|
||||
//系统管理
|
||||
{ |
||||
path: '/system', |
||||
name: 'system', |
||||
redirect: '/isystem/user', |
||||
component: RouteView, |
||||
meta: { title: '系统管理', icon: 'dashboard', permission: [ 'dashboard' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/system/user', |
||||
name: 'user', |
||||
component: () => import('@/views/system/UserList'), |
||||
meta: { title: '用户管理', permission: [ 'dashboard' ] } |
||||
}, |
||||
|
||||
{ |
||||
path: '/system/role', |
||||
name: 'role', |
||||
component: () => import('@/views/system/RoleList'), |
||||
meta: { title: '角色管理', permission: [ 'dashboard' ] } |
||||
}, |
||||
{ |
||||
path: '/system/log', |
||||
name: 'log', |
||||
component: () => import('@/views/system/LogList'), |
||||
meta: { title: '日志管理', permission: [ 'dashboard' ] } |
||||
}, |
||||
|
||||
|
||||
] |
||||
}, |
||||
|
||||
// dashboard
|
||||
{ |
||||
path: '/dashboard', |
||||
name: 'dashboard', |
||||
redirect: '/dashboard/workplace', |
||||
component: RouteView, |
||||
meta: { title: '仪表盘', icon: 'dashboard', permission: [ 'dashboard' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/dashboard/analysis', |
||||
name: 'Analysis', |
||||
component: () => import('@/views/dashboard/Analysis'), |
||||
meta: { title: '分析页', permission: [ 'dashboard' ] } |
||||
}, |
||||
{ |
||||
path: '/dashboard/monitor', |
||||
name: 'Monitor', |
||||
hidden: true, |
||||
component: () => import('@/views/dashboard/Monitor'), |
||||
meta: { title: '监控页', permission: [ 'dashboard' ] } |
||||
}, |
||||
{ |
||||
path: '/dashboard/workplace', |
||||
name: 'Workplace', |
||||
component: () => import('@/views/dashboard/Workplace'), |
||||
meta: { title: '工作台', permission: [ 'dashboard' ] } |
||||
} |
||||
] |
||||
}, |
||||
|
||||
//jeecg
|
||||
{ |
||||
path: '/jeecg', |
||||
name: 'jeecg', |
||||
redirect: '/jeecg', |
||||
component: RouteView, |
||||
meta: { title: 'JEECG案例', icon: 'dashboard', permission: [ 'dashboard' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/jeecg/JeecgDemoList', |
||||
name: 'DemoList', |
||||
component: () => import('@/views/jeecg/JeecgDemoList'), |
||||
meta: { title: '列表例子', permission: [ 'dashboard' ] } |
||||
}, |
||||
{ |
||||
path: '/jeecg/helloworld', |
||||
name: 'helloworld', |
||||
hidden : true, |
||||
component: () => import('@/views/jeecg/helloworld'), |
||||
meta: { title: 'helloworld', permission: [ 'dashboard' ] } |
||||
} |
||||
] |
||||
}, |
||||
// forms
|
||||
{ |
||||
path: '/form', |
||||
redirect: '/form/basic-form', |
||||
component: PageView, |
||||
meta: { title: '表单页', icon: 'form', permission: [ 'form' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/form/base-form', |
||||
name: 'BaseForm', |
||||
component: () => import('@/views/form/BasicForm'), |
||||
meta: { title: '基础表单', permission: [ 'form' ] } |
||||
}, |
||||
{ |
||||
path: '/form/step-form', |
||||
name: 'StepForm', |
||||
component: () => import('@/views/form/stepForm/StepForm'), |
||||
meta: { title: '分步表单', permission: [ 'form' ] } |
||||
}, |
||||
{ |
||||
path: '/form/advanced-form', |
||||
name: 'AdvanceForm', |
||||
component: () => import('@/views/form/advancedForm/AdvancedForm'), |
||||
meta: { title: '高级表单', permission: [ 'form' ] } |
||||
} |
||||
] |
||||
}, |
||||
|
||||
// list
|
||||
{ |
||||
path: '/list', |
||||
name: 'list', |
||||
component: PageView, |
||||
redirect: '/list/query-list', |
||||
meta: { title: '列表页', icon: 'table', permission: [ 'table' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/list/query-list', |
||||
name: 'QueryList', |
||||
component: () => import('@/views/list/TableList'), |
||||
meta: { title: '查询表格', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/edit-table', |
||||
name: 'EditList', |
||||
component: () => import('@/views/list/TableInnerEditList'), |
||||
meta: { title: '内联编辑表格', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/user-list', |
||||
name: 'UserList', |
||||
component: () => import('@/views/list/UserList'), |
||||
meta: { title: '用户列表', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/role-list', |
||||
name: 'RoleList', |
||||
component: () => import('@/views/list/RoleList'), |
||||
meta: { title: '角色列表', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/permission-list', |
||||
name: 'PermissionList', |
||||
component: () => import('@/views/list/PermissionList'), |
||||
meta: { title: '权限列表', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/basic-list', |
||||
name: 'BasicList', |
||||
component: () => import('@/views/list/StandardList'), |
||||
meta: { title: '标准列表', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/card', |
||||
name: 'CardList', |
||||
component: () => import('@/views/list/CardList'), |
||||
meta: { title: '卡片列表', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/search', |
||||
name: 'SearchList', |
||||
component: () => import('@/views/list/search/SearchLayout'), |
||||
redirect: '/list/search/article', |
||||
meta: { title: '搜索列表', permission: [ 'table' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/list/search/article', |
||||
name: 'SearchArticles', |
||||
component: () => import('../views/list/TableList'), |
||||
meta: { title: '搜索列表(文章)', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/search/project', |
||||
name: 'SearchProjects', |
||||
component: () => import('../views/list/TableList'), |
||||
meta: { title: '搜索列表(项目)', permission: [ 'table' ] } |
||||
}, |
||||
{ |
||||
path: '/list/search/application', |
||||
name: 'SearchApplications', |
||||
component: () => import('../views/list/TableList'), |
||||
meta: { title: '搜索列表(应用)', permission: [ 'table' ] } |
||||
}, |
||||
] |
||||
}, |
||||
] |
||||
}, |
||||
|
||||
// profile
|
||||
{ |
||||
path: '/profile', |
||||
name: 'profile', |
||||
component: RouteView, |
||||
redirect: '/profile/basic', |
||||
meta: { title: '详情页', icon: 'profile', permission: [ 'profile' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/profile/basic', |
||||
name: 'ProfileBasic', |
||||
component: () => import('@/views/profile/basic/Index'), |
||||
meta: { title: '基础详情页', permission: [ 'profile' ] } |
||||
}, |
||||
{ |
||||
path: '/profile/advanced', |
||||
name: 'ProfileAdvanced', |
||||
component: () => import('@/views/profile/advanced/Advanced'), |
||||
meta: { title: '高级详情页', permission: [ 'profile' ] } |
||||
} |
||||
] |
||||
}, |
||||
|
||||
// result
|
||||
{ |
||||
path: '/result', |
||||
name: 'result', |
||||
component: PageView, |
||||
redirect: '/result/success', |
||||
meta: { title: '结果页', icon: 'check-circle-o', permission: [ 'result' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/result/success', |
||||
name: 'ResultSuccess', |
||||
component: () => import(/* webpackChunkName: "result" */ '@/views/result/Success'), |
||||
meta: { title: '成功', hiddenHeaderContent: true, permission: [ 'result' ] } |
||||
}, |
||||
{ |
||||
path: '/result/fail', |
||||
name: 'ResultFail', |
||||
component: () => import(/* webpackChunkName: "result" */ '@/views/result/Error'), |
||||
meta: { title: '失败', hiddenHeaderContent: true, permission: [ 'result' ] } |
||||
} |
||||
] |
||||
}, |
||||
|
||||
// Exception
|
||||
{ |
||||
path: '/exception', |
||||
name: 'exception', |
||||
component: RouteView, |
||||
redirect: '/exception/403', |
||||
meta: { title: '异常页', icon: 'warning', permission: [ 'exception' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/exception/403', |
||||
name: 'Exception403', |
||||
component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/403'), |
||||
meta: { title: '403', permission: [ 'exception' ] } |
||||
}, |
||||
{ |
||||
path: '/exception/404', |
||||
name: 'Exception404', |
||||
component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404'), |
||||
meta: { title: '404', permission: [ 'exception' ] } |
||||
}, |
||||
{ |
||||
path: '/exception/500', |
||||
name: 'Exception500', |
||||
component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/500'), |
||||
meta: { title: '500', permission: [ 'exception' ] } |
||||
} |
||||
] |
||||
}, |
||||
|
||||
// account
|
||||
{ |
||||
path: '/account', |
||||
component: RouteView, |
||||
name: 'account', |
||||
meta: { title: '个人页', icon: 'user', keepAlive: true, permission: [ 'user' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/account/center', |
||||
name: 'center', |
||||
component: () => import('@/views/account/center/Index'), |
||||
meta: { title: '个人中心', keepAlive: true, permission: [ 'user' ] } |
||||
}, |
||||
{ |
||||
path: '/account/settings', |
||||
name: 'settings', |
||||
component: () => import('@/views/account/settings/Index'), |
||||
meta: { title: '个人设置', hideHeader: true, keepAlive: true, permission: [ 'user' ] }, |
||||
redirect: '/account/settings/base', |
||||
alwaysShow: true, |
||||
children: [ |
||||
{ |
||||
path: '/account/settings/base', |
||||
name: 'BaseSettings', |
||||
component: () => import('@/views/account/settings/BaseSetting'), |
||||
meta: { title: '基本设置', hidden: true, keepAlive: true, permission: [ 'user' ] } |
||||
}, |
||||
{ |
||||
path: '/account/settings/security', |
||||
name: 'SecuritySettings', |
||||
component: () => import('@/views/account/settings/Security'), |
||||
meta: { title: '安全设置', hidden: true, keepAlive: true, permission: [ 'user' ] } |
||||
}, |
||||
{ |
||||
path: '/account/settings/custom', |
||||
name: 'CustomSettings', |
||||
component: () => import('@/views/account/settings/Custom'), |
||||
meta: { title: '个性化设置', hidden: true, keepAlive: true, permission: [ 'user' ] } |
||||
}, |
||||
{ |
||||
path: '/account/settings/binding', |
||||
name: 'BindingSettings', |
||||
component: () => import('@/views/account/settings/Binding'), |
||||
meta: { title: '账户绑定', hidden: true, keepAlive: true, permission: [ 'user' ] } |
||||
}, |
||||
{ |
||||
path: '/account/settings/notification', |
||||
name: 'NotificationSettings', |
||||
component: () => import('@/views/account/settings/Notification'), |
||||
meta: { title: '新消息通知', hidden: true, keepAlive: true, permission: [ 'user' ] } |
||||
}, |
||||
] |
||||
}, |
||||
] |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
path: '*', redirect: '/404', hidden: true |
||||
} |
||||
] |
||||
|
||||
/** |
||||
* 基础路由 |
||||
* @type { *[] } |
||||
*/ |
||||
export const constantRouterMap = [ |
||||
{ |
||||
path: '/user', |
||||
component: UserLayout, |
||||
redirect: '/user/login', |
||||
hidden: true, |
||||
children: [ |
||||
{ |
||||
path: 'login', |
||||
name: 'login', |
||||
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Login') |
||||
}, |
||||
{ |
||||
path: 'register', |
||||
name: 'register', |
||||
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Register') |
||||
}, |
||||
{ |
||||
path: 'register-result', |
||||
name: 'registerResult', |
||||
component: () => import(/* webpackChunkName: "user" */ '@/views/user/RegisterResult') |
||||
} |
||||
] |
||||
}, |
||||
|
||||
{ |
||||
path: '/test', |
||||
component: BlankLayout, |
||||
redirect: '/test/home', |
||||
children: [ |
||||
{ |
||||
path: 'home', |
||||
name: 'TestHome', |
||||
component: () => import('@/views/Home') |
||||
} |
||||
] |
||||
}, |
||||
|
||||
{ |
||||
path: '/404', |
||||
component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404') |
||||
}, |
||||
|
||||
] |
@ -0,0 +1,31 @@
|
||||
/** |
||||
* 项目默认配置项 |
||||
* primaryColor - 默认主题色 |
||||
* navTheme - sidebar theme ['dark', 'light'] 两种主题 |
||||
* colorWeak - 色盲模式 |
||||
* layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 |
||||
* fixedHeader - 固定 Header : boolean |
||||
* fixSiderbar - 固定左侧菜单栏 : boolean |
||||
* autoHideHeader - 向下滚动时,隐藏 Header : boolean |
||||
* contentWidth - 内容区布局: 流式 | 固定 |
||||
* |
||||
* storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) |
||||
* |
||||
*/ |
||||
|
||||
export default { |
||||
primaryColor: '#1890FF', // primary color of ant design
|
||||
navTheme: 'light', // theme for nav menu
|
||||
layout: 'sidemenu', // nav menu position: sidemenu or topmenu
|
||||
contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu
|
||||
fixedHeader: false, // sticky header
|
||||
fixSiderbar: false, // sticky siderbar
|
||||
autoHideHeader: false, // auto hide header
|
||||
colorWeak: false, |
||||
// vue-ls options
|
||||
storageOptions: { |
||||
namespace: 'pro__', // key prefix
|
||||
name: 'ls', // name variable Vue.[ls] or this.[$ls],
|
||||
storage: 'local', // storage name session, local, memory
|
||||
} |
||||
} |
@ -0,0 +1,61 @@
|
||||
import Vue from 'vue' |
||||
import App from './App.vue' |
||||
import Storage from 'vue-ls' |
||||
import router from './router' |
||||
import store from './store/' |
||||
|
||||
import { VueAxios } from "@/utils/request" |
||||
|
||||
import Antd from 'ant-design-vue' |
||||
import Viser from 'viser-vue' |
||||
import 'ant-design-vue/dist/antd.less'; // or 'ant-design-vue/dist/antd.less'
|
||||
|
||||
import '@/permission' // permission control
|
||||
import '@/utils/filter' // base filter
|
||||
import DictSelectTag from './components/dict/index.js' |
||||
import Print from 'vue-print-nb' |
||||
/*import '@babel/polyfill'*/ |
||||
|
||||
import { |
||||
ACCESS_TOKEN, |
||||
DEFAULT_COLOR, |
||||
DEFAULT_THEME, |
||||
DEFAULT_LAYOUT_MODE, |
||||
DEFAULT_COLOR_WEAK, |
||||
SIDEBAR_TYPE, |
||||
DEFAULT_FIXED_HEADER, |
||||
DEFAULT_FIXED_HEADER_HIDDEN, |
||||
DEFAULT_FIXED_SIDEMENU, |
||||
DEFAULT_CONTENT_WIDTH_TYPE |
||||
} from "@/store/mutation-types" |
||||
import config from '@/defaultSettings' |
||||
|
||||
import hasPermission from '@/utils/hasPermission' |
||||
|
||||
Vue.config.productionTip = false |
||||
|
||||
Vue.use(Storage, config.storageOptions) |
||||
Vue.use(Antd) |
||||
Vue.use(VueAxios, router) |
||||
Vue.use(Viser) |
||||
Vue.use(hasPermission) |
||||
Vue.use(DictSelectTag) |
||||
Vue.use(Print) |
||||
|
||||
new Vue({ |
||||
router, |
||||
store, |
||||
mounted () { |
||||
store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true)) |
||||
store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme)) |
||||
store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout)) |
||||
store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader)) |
||||
store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar)) |
||||
store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth)) |
||||
store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader)) |
||||
store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak)) |
||||
store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor)) |
||||
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN)) |
||||
}, |
||||
render: h => h(App) |
||||
}).$mount('#app') |
@ -0,0 +1,75 @@
|
||||
import Vue from 'vue' |
||||
import router from './router' |
||||
import store from './store' |
||||
import NProgress from 'nprogress' // progress bar
|
||||
import 'nprogress/nprogress.css' // progress bar style
|
||||
import notification from 'ant-design-vue/es/notification' |
||||
import { ACCESS_TOKEN } from '@/store/mutation-types' |
||||
import { generateIndexRouter } from "@/utils/util" |
||||
|
||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
|
||||
const whiteList = ['/user/login', '/user/register', '/user/register-result'] // no redirect whitelist
|
||||
|
||||
router.beforeEach((to, from, next) => { |
||||
NProgress.start() // start progress bar
|
||||
|
||||
if (Vue.ls.get(ACCESS_TOKEN)) { |
||||
/* has token */ |
||||
if (to.path === '/user/login') { |
||||
next({ path: '/dashboard/workplace' }) |
||||
NProgress.done() |
||||
} else { |
||||
if (store.getters.permissionList.length === 0) { |
||||
store.dispatch('GetPermissionList').then(res => { |
||||
const menuData = res.result; |
||||
console.log(res.message) |
||||
if (menuData === null || menuData === "" || menuData === undefined) { |
||||
return; |
||||
} |
||||
let constRoutes = []; |
||||
constRoutes = generateIndexRouter(menuData); |
||||
// 添加主界面路由
|
||||
store.dispatch('UpdateAppRouter', { constRoutes }).then(() => { |
||||
// 根据roles权限生成可访问的路由表
|
||||
// 动态添加可访问路由表
|
||||
router.addRoutes(store.getters.addRouters) |
||||
const redirect = decodeURIComponent(from.query.redirect || to.path) |
||||
if (to.path === redirect) { |
||||
// hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
|
||||
next({ ...to, replace: true }) |
||||
} else { |
||||
// 跳转到目的路由
|
||||
next({ path: redirect }) |
||||
} |
||||
}) |
||||
}) |
||||
.catch(() => { |
||||
notification.error({ |
||||
message: '系统提示', |
||||
description: '请求用户信息失败,请重试!' |
||||
}) |
||||
// update-begin- --- author:scott ------ date:20190225 ---- for:Token失效跳转登录逻辑修改----
|
||||
// store.dispatch('Logout').then(() => {
|
||||
// next({ path: '/user/login', query: { redirect: to.fullPath } })
|
||||
// })
|
||||
// update-end- --- author:scott ------ date:20190225 ---- for:Token失效跳转登录逻辑修改----
|
||||
}) |
||||
} else { |
||||
next() |
||||
} |
||||
} |
||||
} else { |
||||
if (whiteList.indexOf(to.path) !== -1) { |
||||
// 在免登录白名单,直接进入
|
||||
next() |
||||
} else { |
||||
next({ path: '/user/login', query: { redirect: to.fullPath } }) |
||||
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
|
||||
} |
||||
} |
||||
}) |
||||
|
||||
router.afterEach(() => { |
||||
NProgress.done() // finish progress bar
|
||||
}) |
@ -0,0 +1,139 @@
|
||||
路由/菜单说明 |
||||
==== |
||||
|
||||
|
||||
|
||||
配置文件路径 |
||||
---- |
||||
|
||||
`@/config/router.config.js` |
||||
|
||||
|
||||
|
||||
格式和说明 |
||||
---- |
||||
|
||||
```javascript |
||||
/** |
||||
* 路由配置说明: |
||||
* 建议:sider menu 请不要超过三级菜单,若超过三级菜单,则应该设计为顶部主菜单 配合左侧次级菜单 |
||||
* |
||||
**/ |
||||
{ |
||||
redirect: noredirect, |
||||
name: 'router-name', |
||||
hidden: true, |
||||
meta: { |
||||
title: 'title', |
||||
icon: 'a-icon', |
||||
keepAlive: true, |
||||
hiddenHeaderContent: true, |
||||
} |
||||
} |
||||
``` |
||||
|
||||
|
||||
|
||||
`{ Route }` 对象 |
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | |
||||
| -------- | ----------------------------------------- | ------- | ------ | |
||||
| hidden | 控制路由是否显示在 sidebar | boolean | falase | |
||||
| redirect | 重定向地址, 访问这个路由时,自定进行重定向 | string | - | |
||||
| name | 路由名称, 建议设置,且不能重名 | string | - | |
||||
| meta | 路由元信息(路由附带扩展信息) | object | {} | |
||||
|
||||
|
||||
|
||||
`{ Meta }` 路由元信息对象 |
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | |
||||
| ------------------- | ------------------------------------------------------------ | ------- | ------ | |
||||
| title | 路由标题, 用于显示面包屑, 页面标题 *推荐设置 | string | - | |
||||
| icon | 路由在 menu 上显示的图标 | string | - | |
||||
| keepAlive | 缓存该路由 | boolean | false | |
||||
| hiddenHeaderContent | *特殊 隐藏 [PageHeader](https://github.com/sendya/ant-design-pro-vue/blob/master/src/components/layout/PageHeader.vue#L14) 组件中的页面带的 面包屑和页面标题栏 | boolean | false | |
||||
| permission | 与项目提供的权限拦截匹配的权限,如果不匹配,则会被禁止访问该路由页面 | array | [] | |
||||
|
||||
|
||||
|
||||
路由例子 |
||||
---- |
||||
|
||||
```ecmascript 6 |
||||
const asyncRouterMap = [ |
||||
{ |
||||
path: '/', |
||||
name: 'index', |
||||
component: BasicLayout, |
||||
meta: { title: '首页' }, |
||||
redirect: '/dashboard/analysis', |
||||
children: [ |
||||
{ |
||||
path: '/dashboard', |
||||
component: Layout, |
||||
name: 'dashboard', |
||||
redirect: '/dashboard/workplace', |
||||
meta: {title: '仪表盘', icon: 'dashboard', permission: ['dashboard']}, |
||||
children: [ |
||||
{ |
||||
path: '/dashboard/analysis', |
||||
name: 'Analysis', |
||||
component: () => import('@/views/dashboard/Analysis'), |
||||
meta: {title: '分析页', permission: ['dashboard']} |
||||
}, |
||||
{ |
||||
path: '/dashboard/monitor', |
||||
name: 'Monitor', |
||||
hidden: true, |
||||
component: () => import('@/views/dashboard/Monitor'), |
||||
meta: {title: '监控页', permission: ['dashboard']} |
||||
}, |
||||
{ |
||||
path: '/dashboard/workplace', |
||||
name: 'Workplace', |
||||
component: () => import('@/views/dashboard/Workplace'), |
||||
meta: {title: '工作台', permission: ['dashboard']} |
||||
} |
||||
] |
||||
}, |
||||
|
||||
// result |
||||
{ |
||||
path: '/result', |
||||
name: 'result', |
||||
component: PageView, |
||||
redirect: '/result/success', |
||||
meta: { title: '结果页', icon: 'check-circle-o', permission: [ 'result' ] }, |
||||
children: [ |
||||
{ |
||||
path: '/result/success', |
||||
name: 'ResultSuccess', |
||||
component: () => import(/* webpackChunkName: "result" */ '@/views/result/Success'), |
||||
// 该页面隐藏面包屑和页面标题栏 |
||||
meta: { title: '成功', hiddenHeaderContent: true, permission: [ 'result' ] } |
||||
}, |
||||
{ |
||||
path: '/result/fail', |
||||
name: 'ResultFail', |
||||
component: () => import(/* webpackChunkName: "result" */ '@/views/result/Error'), |
||||
// 该页面隐藏面包屑和页面标题栏 |
||||
meta: { title: '失败', hiddenHeaderContent: true, permission: [ 'result' ] } |
||||
} |
||||
] |
||||
}, |
||||
... |
||||
] |
||||
}, |
||||
] |
||||
``` |
||||
|
||||
> 1. 请注意 `component: () => import('..') ` 方式引入路由的页面组件为 懒加载模式。具体可以看 [Vue 官方文档](https://router.vuejs.org/zh/guide/advanced/lazy-loading.html) |
||||
> 2. 增加新的路由应该增加在 '/' (index) 路由的 `children` 内 |
||||
> 3. `permission` 可以进行自定义修改,只需要对这个模块进行自定义修改即可 [src/store/modules/permission.js#L10](https://github.com/sendya/ant-design-pro-vue/blob/master/src/store/modules/permission.js#L10) |
||||
|
||||
|
||||
|
||||
附权限路由结构: |
||||
|
||||
![权限结构](https://static-2.loacg.com/open/static/github/permissions.png) |
@ -0,0 +1,12 @@
|
||||
import Vue from 'vue' |
||||
import Router from 'vue-router' |
||||
import { constantRouterMap } from '@/config/router.config' |
||||
|
||||
Vue.use(Router) |
||||
|
||||
export default new Router({ |
||||
mode: 'history', |
||||
base: process.env.BASE_URL, |
||||
scrollBehavior: () => ({ y: 0 }), |
||||
routes: constantRouterMap |
||||
}) |
@ -0,0 +1,17 @@
|
||||
import Vue from 'vue' |
||||
import { USER_INFO} from "@/store/mutation-types" |
||||
const getters = { |
||||
device: state => state.app.device, |
||||
theme: state => state.app.theme, |
||||
color: state => state.app.color, |
||||
token: state => state.user.token, |
||||
avatar: state => {state.user.avatar = Vue.ls.get(USER_INFO).avatar; return state.user.avatar}, |
||||
username: state => state.user.username, |
||||
nickname: state => {state.user.realname = Vue.ls.get(USER_INFO).realname; return state.user.realname}, |
||||
welcome: state => state.user.welcome, |
||||
permissionList: state => state.user.permissionList, |
||||
userInfo: state => {state.user.info = Vue.ls.get(USER_INFO); return state.user.info}, |
||||
addRouters: state => state.permission.addRouters |
||||
} |
||||
|
||||
export default getters |
@ -0,0 +1,27 @@
|
||||
import Vue from 'vue' |
||||
import Vuex from 'vuex' |
||||
|
||||
import app from './modules/app' |
||||
import user from './modules/user' |
||||
import permission from './modules/permission' |
||||
import getters from './getters' |
||||
|
||||
Vue.use(Vuex) |
||||
|
||||
export default new Vuex.Store({ |
||||
modules: { |
||||
app, |
||||
user, |
||||
permission |
||||
}, |
||||
state: { |
||||
|
||||
}, |
||||
mutations: { |
||||
|
||||
}, |
||||
actions: { |
||||
|
||||
}, |
||||
getters |
||||
}) |
@ -0,0 +1,121 @@
|
||||
import Vue from 'vue' |
||||
import { |
||||
SIDEBAR_TYPE, |
||||
DEFAULT_THEME, |
||||
DEFAULT_LAYOUT_MODE, |
||||
DEFAULT_COLOR, |
||||
DEFAULT_COLOR_WEAK, |
||||
DEFAULT_FIXED_HEADER, |
||||
DEFAULT_FIXED_SIDEMENU, |
||||
DEFAULT_FIXED_HEADER_HIDDEN, |
||||
DEFAULT_CONTENT_WIDTH_TYPE |
||||
} from "@/store/mutation-types" |
||||
|
||||
const app = { |
||||
state: { |
||||
sidebar: { |
||||
opened: true, |
||||
withoutAnimation: false |
||||
}, |
||||
device: 'desktop', |
||||
theme: '', |
||||
layout: '', |
||||
contentWidth: '', |
||||
fixedHeader: false, |
||||
fixSiderbar: false, |
||||
autoHideHeader: false, |
||||
color: null, |
||||
weak: false, |
||||
multipage: true |
||||
}, |
||||
mutations: { |
||||
SET_SIDEBAR_TYPE: (state, type) => { |
||||
state.sidebar.opened = type |
||||
Vue.ls.set(SIDEBAR_TYPE, type) |
||||
}, |
||||
CLOSE_SIDEBAR: (state, withoutAnimation) => { |
||||
Vue.ls.set(SIDEBAR_TYPE, true) |
||||
state.sidebar.opened = false |
||||
state.sidebar.withoutAnimation = withoutAnimation |
||||
}, |
||||
TOGGLE_DEVICE: (state, device) => { |
||||
state.device = device |
||||
}, |
||||
TOGGLE_THEME: (state, theme) => { |
||||
// setStore('_DEFAULT_THEME', theme)
|
||||
Vue.ls.set(DEFAULT_THEME, theme) |
||||
state.theme = theme |
||||
}, |
||||
TOGGLE_LAYOUT_MODE: (state, layout) => { |
||||
Vue.ls.set(DEFAULT_LAYOUT_MODE, layout) |
||||
state.layout = layout |
||||
}, |
||||
TOGGLE_FIXED_HEADER: (state, fixed) => { |
||||
Vue.ls.set(DEFAULT_FIXED_HEADER, fixed) |
||||
state.fixedHeader = fixed |
||||
}, |
||||
TOGGLE_FIXED_SIDERBAR: (state, fixed) => { |
||||
Vue.ls.set(DEFAULT_FIXED_SIDEMENU, fixed) |
||||
state.fixSiderbar = fixed |
||||
}, |
||||
TOGGLE_FIXED_HEADER_HIDDEN: (state, show) => { |
||||
Vue.ls.set(DEFAULT_FIXED_HEADER_HIDDEN, show) |
||||
state.autoHideHeader = show |
||||
}, |
||||
TOGGLE_CONTENT_WIDTH: (state, type) => { |
||||
Vue.ls.set(DEFAULT_CONTENT_WIDTH_TYPE, type) |
||||
state.contentWidth = type |
||||
}, |
||||
TOGGLE_COLOR: (state, color) => { |
||||
Vue.ls.set(DEFAULT_COLOR, color) |
||||
state.color = color |
||||
}, |
||||
TOGGLE_WEAK: (state, flag) => { |
||||
Vue.ls.set(DEFAULT_COLOR_WEAK, flag) |
||||
state.weak = flag |
||||
}, |
||||
setMultipage (state, multipage) { |
||||
state.multipage = multipage |
||||
} |
||||
}, |
||||
actions: { |
||||
setSidebar: ({ commit }, type) => { |
||||
commit('SET_SIDEBAR_TYPE', type) |
||||
}, |
||||
CloseSidebar({ commit }, { withoutAnimation }) { |
||||
commit('CLOSE_SIDEBAR', withoutAnimation) |
||||
}, |
||||
ToggleDevice({ commit }, device) { |
||||
commit('TOGGLE_DEVICE', device) |
||||
}, |
||||
ToggleTheme({ commit }, theme) { |
||||
commit('TOGGLE_THEME', theme) |
||||
}, |
||||
ToggleLayoutMode({ commit }, mode) { |
||||
commit('TOGGLE_LAYOUT_MODE', mode) |
||||
}, |
||||
ToggleFixedHeader({ commit }, fixedHeader) { |
||||
if (!fixedHeader) { |
||||
commit('TOGGLE_FIXED_HEADER_HIDDEN', false) |
||||
} |
||||
commit('TOGGLE_FIXED_HEADER', fixedHeader) |
||||
}, |
||||
ToggleFixSiderbar({ commit }, fixSiderbar) { |
||||
commit( 'TOGGLE_FIXED_SIDERBAR', fixSiderbar) |
||||
}, |
||||
ToggleFixedHeaderHidden({ commit }, show) { |
||||
commit('TOGGLE_FIXED_HEADER_HIDDEN', show) |
||||
}, |
||||
ToggleContentWidth({ commit }, type) { |
||||
commit('TOGGLE_CONTENT_WIDTH', type) |
||||
}, |
||||
ToggleColor({ commit }, color) { |
||||
commit('TOGGLE_COLOR', color) |
||||
}, |
||||
ToggleWeak({ commit }, weakFlag) { |
||||
commit('TOGGLE_WEAK', weakFlag) |
||||
} |
||||
} |
||||
} |
||||
|
||||
export default app |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue