@ -0,0 +1,5 @@ |
|||
{ |
|||
"presets": [ |
|||
"@vue/app" |
|||
] |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
root = true |
|||
|
|||
[*] |
|||
charset = utf-8 |
|||
indent_style = space |
|||
indent_size = 2 |
|||
end_of_line = lf |
|||
insert_final_newline = true |
|||
trim_trailing_whitespace = true |
|||
@ -0,0 +1,21 @@ |
|||
module.exports = { |
|||
root: true, |
|||
'extends': [ |
|||
'plugin:vue/essential', |
|||
'@vue/standard' |
|||
], |
|||
rules: { |
|||
// allow async-await
|
|||
'generator-star-spacing': 'off', |
|||
// allow debugger during development
|
|||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', |
|||
'vue/no-parsing-error': [2, { |
|||
'x-invalid-end-tag': false |
|||
}], |
|||
'no-undef': 'off', |
|||
'camelcase': 'off' |
|||
}, |
|||
parserOptions: { |
|||
parser: 'babel-eslint' |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
.DS_Store |
|||
node_modules |
|||
/dist |
|||
|
|||
/tests/e2e/videos/ |
|||
/tests/e2e/screenshots/ |
|||
|
|||
# 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* |
|||
|
|||
build/env.js |
|||
@ -0,0 +1,5 @@ |
|||
module.exports = { |
|||
plugins: { |
|||
autoprefixer: {} |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
language: node_js |
|||
node_js: stable |
|||
script: npm run lint |
|||
notifications: |
|||
email: false |
|||
@ -0,0 +1,50 @@ |
|||
<div align="center"><h3>ApiAdmin 开源框架法律声明与授权条款</h3></div> |
|||
|
|||
``` |
|||
任何用户在使用由 ApiAdmin 技术开发团队(以下简称「本团队」)研发的系列框架(以下简称「ApiAdmin 通用权限开发框架」)前,请您仔细阅读并透彻理解本声明。若您一旦使用 ApiAdmin 通用权限开发框架,您的使用行为即被视为对本声明全部内容的认可和接受。 |
|||
``` |
|||
|
|||
**免责声明** |
|||
|
|||
1. ApiAdmin 通用权限开发框架仅属于快速开发框架并不涉及具体业务应用场景,其尊重并保护所有用户的个人隐私权,不窃取任何用户计算机中的信息,更不具备用户数据存储等网络传输功能。 |
|||
|
|||
2. 任何单位或个人因下载使用 ApiAdmin 通用权限开发框架而产生的任何意外、疏忽、合约毁坏、诽谤、数据丢失、系统故障、服务中断、经济损失、商誉损害、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),本团队不承担任何法律责任。 |
|||
|
|||
3. 任何单位或个人在阅读本免责声明后,应在开源许可证所允许的范围内进行合法的发布、传播和使用 ApiAdmin 通用权限开发框架等行为,若违反本免责声明条款或违反法律法规所造成的法律责任(包括但不限于民事赔偿和刑事责任),由违约者自行承担,本团队不承担任何法律责任。 |
|||
|
|||
4. 本团队对 ApiAdmin 通用权限开发框架拥有知识产权(包括但不限于商标权、专利权、著作权、商业秘密等),上述产品均受到相关法律法规的保护。任何单位或个人不得在未经本团队书面授权的情况下对 ApiAdmin 通用权限开发框架本身申请相关的知识产权。 |
|||
|
|||
5. 使用者必须在适用法律和法规允许的范围内正确使用 ApiAdmin,严禁将其用于非法、欺诈、恶意或侵犯他人合法权益的目的,亦不将运用于任何违反我国法律法规的平台。若发现任何未经授权或违法使用本框架的情况,我们将依据相关法律追究责任,并有权采取必要的措施予以制止。 |
|||
|
|||
6. 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动。任何基于本项目二次开发而产生的一切法律纠纷和责任,均与作者无关。 |
|||
|
|||
7. 用户明确并同意本声明条款列举的全部内容,对使用 ApiAdmin 通用权限开发框架可能存在的风险和相关后果将完全由用户自行承担,本团队不承担任何法律责任。 |
|||
|
|||
8. 如果本声明的任何部分被认为无效或不可执行,则该部分将被解释为反映本团队的初衷,其余部分仍具有完全效力。不可执行的部分声明,并不构成我们放弃执行该声明的权利。 |
|||
|
|||
|
|||
**授权条款** |
|||
|
|||
ApiAdmin 是一个基于 .NET 构建的开源通用权限开发框架,您可以在 MIT 许可证的条款下自由地使用、复制、分发、修改和贡献此项目。这意味着您可以根据自身需求和法律要求选择更适合您的许可条款: |
|||
|
|||
1. **MIT 许可证**:另一种选择是遵循 MIT 许可协议,详情参见 [LICENSE-MIT](https://gitee.com/apiadmin/ApiAdmin/blob/master/LICENSE)。 |
|||
|
|||
|
|||
**责任限制** |
|||
|
|||
ApiAdmin 团队及社区成员尽力提供完善的文档和技术支持,但并不对因使用框架过程中产生的问题提供绝对的解决方案保障。所有用户提供或推荐的解决方案、代码片段、最佳实践等均“按原样”提供,使用者须自行判断并承担使用后的一切风险。 |
|||
|
|||
|
|||
**法律义务与合规性** |
|||
|
|||
使用者在利用 ApiAdmin 开发应用程序时,负有确保其应用程序符合所有适用法律、行业标准以及信息安全规范的全部责任。使用者应自行评估并确保其产品不会滥用框架功能,尤其是防止被用于潜在有害或不道德的目的。 |
|||
|
|||
|
|||
**技术交流** |
|||
|
|||
ApiAdmin 交流群提供的支持和资源旨在辅助开发过程,但不应视为全面的技术指导或保证。我们鼓励用户积极参与开源过程,同时也提醒用户充分测试其开发成果,确保其安全性和稳定性。 |
|||
|
|||
|
|||
**变更说明** |
|||
|
|||
本团队有权随时对声明条款及附件内容进行单方面的变更或更新,变更或更新后立即自动生效,无需另行单独通知您或任何第三方;若您在声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。 |
|||
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2017 iView |
|||
|
|||
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,94 @@ |
|||
<p align="center"> |
|||
<a href="https://cn.vuejs.org"> |
|||
<img width="200" src="https://cn.vuejs.org/images/logo.png"> |
|||
</a> |
|||
</p> |
|||
|
|||
# ApiAdmin-WEB |
|||
[]() |
|||
[](http://www.apiadmin.org/) |
|||
[](https://gitee.com/apiadmin/ApiAdmin) |
|||
[](https://github.com/vuejs/vue) |
|||
[](https://github.com/iview/iview) |
|||
|
|||
# 特别提示 |
|||
本项目依赖于[ApiAdmin](https://gitee.com/apiadmin/ApiAdmin),请确保您同时具备PHP和Vue的技能,否则使用本项目存在技术性障碍! |
|||
|
|||
## 线上体验 |
|||
[https://admin.apiadmin.org](https://admin.apiadmin.org)。账号请加群获取! |
|||
|
|||
## Install |
|||
```bush |
|||
// install dependencies |
|||
npm install |
|||
``` |
|||
|
|||
## Prepare |
|||
```bush |
|||
/src/config/index.js中的baseUrl需要换成你自己搭建的后台接口域名 |
|||
vue.config.js中的BASE_URL按照代码注释换成你的地址 |
|||
``` |
|||
|
|||
## Run |
|||
### Development |
|||
```bush |
|||
npm run dev |
|||
``` |
|||
### Production(Build) |
|||
```bush |
|||
npm run build |
|||
``` |
|||
|
|||
## 愿景 |
|||
|
|||
> 希望有人用它,希望更多的人用它。 |
|||
> 希望它能帮助到你,希望它能帮助到更多的你。 |
|||
|
|||
## 简介 |
|||
|
|||
1. 接口文档自动生成 |
|||
2. 接口输入参数自动检查 |
|||
3. 接口输出参数数据类型自动规整 |
|||
4. 灵活的参数规则设定 |
|||
5. 支持三方Api无缝融合 |
|||
6. 本地二次开发友好 |
|||
7. ... |
|||
|
|||
``` |
|||
ApiAdmin(PHP部分) |
|||
├─ 系统维护 |
|||
| ├─ 菜单管理 - 编辑访客权限,处理菜单父子关系,被权限系统依赖(极为重要) |
|||
| ├─ 用户管理 - 添加新用户,封号,删号以及给账号分配权限组 |
|||
| ├─ 权限管理 - 权限组管理,给权限组添加权限,将用户提出权限组 |
|||
| └─ 操作日志 - 记录管理员的操作,用于追责,回溯和备案 |
|||
| ... |
|||
``` |
|||
|
|||
## 鸣谢 |
|||
|
|||
- [iView-Admin](https://github.com/iview/iview-admin) |
|||
- [iView](https://github.com/iview/iview) |
|||
- [Vue](https://github.com/vuejs/vue) |
|||
- [Webpack](https://github.com/webpack/webpack) |
|||
|
|||
## 效果展示 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
## 联系我们 |
|||
官方唯一QQ群:221522638 |
|||
|
|||
## License |
|||
[Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) |
|||
|
|||
Copyright (c) 2017-present, ApiAdmin |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"pluginsFile": "tests/e2e/plugins/index.js" |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
{ |
|||
"name": "apiadmin", |
|||
"version": "4.1.0", |
|||
"author": "zhaoxiang <zhaoxiang051405@gmail.com>", |
|||
"private": false, |
|||
"scripts": { |
|||
"dev": "vue-cli-service serve --open", |
|||
"build": "vue-cli-service build", |
|||
"lint": "vue-cli-service lint" |
|||
}, |
|||
"dependencies": { |
|||
"axios": "^0.18.1", |
|||
"dayjs": "^1.8.28", |
|||
"echarts": "^4.8.0", |
|||
"html2canvas": "^1.0.0-rc.5", |
|||
"view-design": "^4.2.0", |
|||
"vue": "^2.6.11", |
|||
"vue-i18n": "^7.8.0", |
|||
"vue-router": "^3.1.6", |
|||
"vuex": "^3.1.3" |
|||
}, |
|||
"devDependencies": { |
|||
"@vue/cli-plugin-babel": "^3.11.0", |
|||
"@vue/cli-plugin-eslint": "^3.11.0", |
|||
"@vue/cli-plugin-unit-mocha": "^3.11.0", |
|||
"@vue/cli-service": "^3.11.0", |
|||
"@vue/eslint-config-standard": "^3.0.0-beta.10", |
|||
"babel-eslint": "7.2.3", |
|||
"chai": "^4.1.2", |
|||
"eslint-plugin-cypress": "^2.10.3", |
|||
"less": "^2.7.3", |
|||
"less-loader": "^4.0.5", |
|||
"lint-staged": "^6.0.0", |
|||
"vue-template-compiler": "^2.6.11" |
|||
}, |
|||
"browserslist": [ |
|||
"> 1%", |
|||
"last 2 versions", |
|||
"not ie <= 8" |
|||
], |
|||
"gitHooks": { |
|||
"pre-commit": "lint-staged" |
|||
}, |
|||
"lint-staged": { |
|||
"*.js": [ |
|||
"vue-cli-service lint", |
|||
"git add" |
|||
], |
|||
"*.vue": [ |
|||
"vue-cli-service lint", |
|||
"git add" |
|||
] |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,23 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<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"> |
|||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
|||
<!-- import Vue.js --> |
|||
<script src="//cdn.staticfile.org/vue/2.6.11/vue.min.js"></script> |
|||
<!-- import stylesheet --> |
|||
<link rel="stylesheet" href="//cdn.staticfile.org/view-design/4.3.2/styles/iview.css"> |
|||
<!-- import iView --> |
|||
<script src="//cdn.staticfile.org/view-design/4.3.2/iview.min.js"></script> |
|||
<title></title> |
|||
</head> |
|||
<body> |
|||
<noscript> |
|||
<strong>We're sorry but iview-admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |
|||
</noscript> |
|||
<div id="app"></div> |
|||
<!-- built files will be auto injected --> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,27 @@ |
|||
<template> |
|||
<div id="app"> |
|||
<router-view/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'App' |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less"> |
|||
.size{ |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
html,body{ |
|||
.size; |
|||
overflow: hidden; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
#app { |
|||
.size; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,73 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 获取应用组数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getList = (params) => { |
|||
return axios.request({ |
|||
url: 'AppGroup/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const del = (hash) => { |
|||
return axios.request({ |
|||
url: 'AppGroup/del', |
|||
method: 'get', |
|||
params: { |
|||
hash: hash |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 显示/隐藏应用组 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const changeStatus = (status, id) => { |
|||
return axios.request({ |
|||
url: 'AppGroup/changeStatus', |
|||
method: 'get', |
|||
params: { |
|||
status: status, |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增应用组 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'AppGroup/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑应用组 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'AppGroup/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
export const getAll = () => { |
|||
return axios.request({ |
|||
url: 'AppGroup/getAll', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 获取应用数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getList = (params) => { |
|||
return axios.request({ |
|||
url: 'App/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 刷新秘钥 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const refreshAppSecretApi = () => { |
|||
return axios.request({ |
|||
url: 'App/refreshAppSecret', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取应用权限信息 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getAppInfo = (id) => { |
|||
return axios.request({ |
|||
url: 'App/getAppInfo', |
|||
method: 'get', |
|||
params: { |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
export const del = (id) => { |
|||
return axios.request({ |
|||
url: 'App/del', |
|||
method: 'get', |
|||
params: { |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 显示/隐藏应用 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const changeStatus = (status, id) => { |
|||
return axios.request({ |
|||
url: 'App/changeStatus', |
|||
method: 'get', |
|||
params: { |
|||
status: status, |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增应用 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'App/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑应用 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'App/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
export const getGroups = () => { |
|||
return axios.request({ |
|||
url: 'Auth/getGroups', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取权限列表数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getList = (params) => { |
|||
return axios.request({ |
|||
url: 'Auth/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const editRule = (data) => { |
|||
return axios.request({ |
|||
url: 'Auth/editRule', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取权限细节列表数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getRuleList = (params) => { |
|||
return axios.request({ |
|||
url: 'Auth/getRuleList', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 修改权限组状态 |
|||
* @param status |
|||
* @param id |
|||
* @returns {never} |
|||
*/ |
|||
export const changeStatus = (status, id) => { |
|||
return axios.request({ |
|||
url: 'Auth/changeStatus', |
|||
method: 'get', |
|||
params: { |
|||
status: status, |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增权限组 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'Auth/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑权限组 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'Auth/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 删除权限组 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const del = (id) => { |
|||
return axios.request({ |
|||
url: 'Auth/del', |
|||
method: 'get', |
|||
params: { |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 删除用户组成员 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const delMember = (params) => { |
|||
return axios.request({ |
|||
url: 'Auth/delMember', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 获取字段数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getDataType = (params) => { |
|||
return axios.request({ |
|||
url: 'Fields/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const upJson = (data) => { |
|||
return axios.request({ |
|||
url: 'Fields/upload', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
export const del = (id) => { |
|||
return axios.request({ |
|||
url: 'Fields/del', |
|||
method: 'get', |
|||
params: { |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增字段 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'Fields/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑字段 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'Fields/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
export const getResponse = (params) => { |
|||
return axios.request({ |
|||
url: 'Fields/response', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const getRequest = (params) => { |
|||
return axios.request({ |
|||
url: 'Fields/request', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 获取应用组数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getList = (params) => { |
|||
return axios.request({ |
|||
url: 'InterfaceGroup/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const del = (hash) => { |
|||
return axios.request({ |
|||
url: 'InterfaceGroup/del', |
|||
method: 'get', |
|||
params: { |
|||
hash: hash |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 显示/隐藏应用组 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const changeStatus = (status, id) => { |
|||
return axios.request({ |
|||
url: 'InterfaceGroup/changeStatus', |
|||
method: 'get', |
|||
params: { |
|||
status: status, |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取列表【筛选项】 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getAll = () => { |
|||
return axios.request({ |
|||
url: 'InterfaceGroup/getAll', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增应用组 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'InterfaceGroup/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑应用组 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'InterfaceGroup/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 获取接口组数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getHash = () => { |
|||
return axios.request({ |
|||
url: 'InterfaceList/getHash', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 刷新路由 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const refresh = () => { |
|||
return axios.request({ |
|||
url: 'InterfaceList/refresh', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取接口数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getList = (params) => { |
|||
return axios.request({ |
|||
url: 'InterfaceList/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const del = (hash) => { |
|||
return axios.request({ |
|||
url: 'InterfaceList/del', |
|||
method: 'get', |
|||
params: { |
|||
hash: hash |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 启用/禁用接口 |
|||
* @param status |
|||
* @param hash |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const changeStatus = (status, hash) => { |
|||
return axios.request({ |
|||
url: 'InterfaceList/changeStatus', |
|||
method: 'get', |
|||
params: { |
|||
status: status, |
|||
hash: hash |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增接口 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'InterfaceList/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑接口 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'InterfaceList/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 获取菜单列表数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getList = (params) => { |
|||
return axios.request({ |
|||
url: 'Log/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const del = (id) => { |
|||
return axios.request({ |
|||
url: 'Log/del', |
|||
method: 'get', |
|||
params: { |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 获取菜单列表数据 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const getList = (keywords) => { |
|||
keywords = keywords || '' |
|||
return axios.request({ |
|||
url: 'Menu/index', |
|||
method: 'get', |
|||
params: { |
|||
keywords: keywords |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 显示/隐藏菜单 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const changeStatus = (status, id) => { |
|||
return axios.request({ |
|||
url: 'Menu/changeStatus', |
|||
method: 'get', |
|||
params: { |
|||
status: status, |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增菜单 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'Menu/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑菜单 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'Menu/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 删除菜单 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const del = (id) => { |
|||
return axios.request({ |
|||
url: 'Menu/del', |
|||
method: 'get', |
|||
params: { |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* 使用QQ数据登录 |
|||
* @param params |
|||
* @returns {wx.RequestTask | never | *} |
|||
*/ |
|||
export const loginByQQ = (params) => { |
|||
return axios.request({ |
|||
url: 'ThirdLogin/loginByQQ', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const wx = (params) => { |
|||
return axios.request({ |
|||
url: 'ThirdLogin/wx', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const getQQCode = () => { |
|||
return axios.request({ |
|||
url: 'ThirdLogin/getQQCode', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
export const getQr = () => { |
|||
return axios.request({ |
|||
url: 'ThirdLogin/getQr', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
export const checkWxLogin = (params) => { |
|||
return axios.request({ |
|||
url: 'ThirdLogin/checkWxLogin', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
export const getWxCode = () => { |
|||
return axios.request({ |
|||
url: 'ThirdLogin/getWxCode', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
import axios from '@/libs/api.request' |
|||
|
|||
/** |
|||
* |
|||
* @param username |
|||
* @param password |
|||
* @returns {never} |
|||
*/ |
|||
export const login = ({ username, password }) => { |
|||
const data = { |
|||
username, |
|||
password |
|||
} |
|||
return axios.request({ |
|||
url: 'Login/index', |
|||
data, |
|||
method: 'post' |
|||
}) |
|||
} |
|||
|
|||
export const getUsers = (params) => { |
|||
return axios.request({ |
|||
url: 'User/getUsers', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取当前用户信息 |
|||
* @returns {never} |
|||
*/ |
|||
export const getUserInfo = () => { |
|||
return axios.request({ |
|||
url: 'Login/getUserInfo', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 用户登出 |
|||
* @returns {never} |
|||
*/ |
|||
export const logout = () => { |
|||
return axios.request({ |
|||
url: 'Login/logout', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取用户列表 |
|||
* @param params |
|||
* @returns {never} |
|||
*/ |
|||
export const getUserIndex = (params) => { |
|||
return axios.request({ |
|||
url: 'User/index', |
|||
method: 'get', |
|||
params: params |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 修改用户状态 |
|||
* @param status |
|||
* @param id |
|||
* @returns {never} |
|||
*/ |
|||
export const changeStatus = (status, id) => { |
|||
return axios.request({ |
|||
url: 'User/changeStatus', |
|||
method: 'get', |
|||
params: { |
|||
status: status, |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 新增用户 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const add = (data) => { |
|||
return axios.request({ |
|||
url: 'User/add', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 编辑用户 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const edit = (data) => { |
|||
return axios.request({ |
|||
url: 'User/edit', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 删除用户 |
|||
* @param status |
|||
* @param id |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const del = (id) => { |
|||
return axios.request({ |
|||
url: 'User/del', |
|||
method: 'get', |
|||
params: { |
|||
id: id |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取权限菜单 |
|||
* @returns {*} |
|||
*/ |
|||
export const getAccessMenu = () => { |
|||
return axios.request({ |
|||
url: 'Login/getAccessMenu', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
export const own = (data) => { |
|||
return axios.request({ |
|||
url: 'User/own', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
import axios from '@/libs/wiki.request' |
|||
|
|||
/** |
|||
* |
|||
* @param username |
|||
* @param password |
|||
* @returns {never} |
|||
*/ |
|||
export const errorCode = () => { |
|||
return axios.request({ |
|||
url: 'Api/errorCode', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
export const apiGroup = () => { |
|||
return axios.request({ |
|||
url: 'Api/groupList', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 获取接口详情 |
|||
* @param params |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const detail = (params) => { |
|||
return axios.request({ |
|||
url: 'Api/detail', |
|||
method: 'get', |
|||
params |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 用户登录 |
|||
* @param data |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const login = (data) => { |
|||
return axios.request({ |
|||
url: 'Api/login', |
|||
method: 'post', |
|||
data |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 用户登出 |
|||
* @returns {wx.RequestTask | never} |
|||
*/ |
|||
export const logout = () => { |
|||
return axios.request({ |
|||
url: 'Api/logout', |
|||
method: 'get' |
|||
}) |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
|
|||
@font-face {font-family: "iconfont"; |
|||
src: url('iconfont.eot?t=1541579316141'); /* IE9*/ |
|||
src: url('iconfont.eot?t=1541579316141#iefix') format('embedded-opentype'), /* IE6-IE8 */ |
|||
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAiEAAsAAAAADmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8eUnXY21hcAAAAYAAAACjAAACLi+YJuBnbHlmAAACJAAABAgAAAcg4dRWHmhlYWQAAAYsAAAAMQAAADYTL8piaGhlYQAABmAAAAAgAAAAJAfdA4xobXR4AAAGgAAAABQAAAAsLAD//2xvY2EAAAaUAAAAGAAAABgImgpGbWF4cAAABqwAAAAfAAAAIAEcAG5uYW1lAAAGzAAAAUUAAAJtPlT+fXBvc3QAAAgUAAAAbgAAAI54roygeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMTx/ytzwv4EhhrmBoRkozAiSAwDuUwzMeJzlkUEKwkAMRd/YabXFhQvxFF6qPYPrUujGY7jyIr1JoZNjtMnEhag3MOEN5MMk8D9QAoVyVSKEJwGrh6oh6wVN1iM3nc+cVImJVKdOehlklElmWdYVstp+ql8VdIv15a1NLW0zFXsO7Kjz3erH/3+rY37vr6kxnx1LKNWOJZlaxxJNnWOpSu+ot8jgqMvI6KjfyOSo88jsaAbI4tBsig89rQB4nLVUTWwbRRSeNzO767i2g7N/FP9s7MRrE5ON4/V6rSZyU0PiINSSNImES4IUoapWz6hEiqiMBDQqEojkAkiFStyKRC+9VSoFCeUEyqESVUAqEkcu3OAQb3hrJxAXwSGI3X0/szPz5vvm2x0i7O/vf8IJe5VkSJnUyUtklRBQJE1VIjRtUafkmk6pSu2ipleh4+xikkKxSksWTUeo8m8NoagpYtoslTmxrLl37z64e33esuJjU8P5Wd262LxoPVnPZ06Pxfe+C0YjkhSJygPhQCA8ABPOykwuN7NyuRvgUnAgLEnhATkaCQQiUe/7XKUyV6nQz+t2o7l66+rs7NVbq82GXTdrdjxjRGU5amTids2bUDMFtzCsqsMYMqr3IDY6OT05GjsI8Exv/6CSkOWEQigh+y3clxY5QVTcEZFIGtHLxDUJs6WsHR1y9SFKdr1HggCp3V1ICYL36OOpVmvKN9bC1u6R3vZ0qwWtVovgJfqOfUvfIYxIWL+fyETHNVJqSkIT1JTjW8ZWh3yDJDz0ctvsyt51etvrg9/QHhqGlzMM+vbmizPnDWPLMNbW19e7tffvsBzL99aWEfBRY46t+tbe3PypXv/IMDYN43WsQBe9HL2NC33RuxABrPsG+xH3o4bVRE2KgCRqulbWNf8W/UYVHM129aKra24VshZkq+CWD/Oy6Xt8cGYEthgHVlVliCfynAlqjo6oysTKlYUAD4docMI5/1ZioN+GwZNBcTwWUmTdBUqhTwX29QebXzF4An4JJMzwfMl+WQ01+IlQZVR4yhie53ycA16pOI/ODiYNGK4MChdCgXNnX5gIJXPCSYnf2OF850aQ+zJIyOs+u8+mMO8jQdwtg1TIWVRjKAnFcslMi8KfGUPoSUCergUyUk77dMyS69Ms6tijKZKYwUGKbpfdzu+iYeZYAHMFiOVi+MD7h9mb99qC0L7X8c+XatMfTj97KZ5IxJt/pd43tYYQKEjAnXMOB6kQEBrwg+LPjindAPOHNdC3q3ait0I3/ZIunZEARLNYNEUA6czSP3N/7j9wz6ZESdX0VNl1zGNS/szbQaQSIGk4DtVPcZf8AgXpf9A2OyTit5s2syZmand46bhEe2WtodLHkvaoqtTXuXN2/c42WADP9HGfbUcUW7JgqHss4xHtlMys679FqUomdP9VJBQBdnlPABBubpuNwqnmQj6/0HwNQzKxDUJFgKiXurBG6dqFjmeBzsvtRPJgGIZThYa5fdOvsReOticPh6JHHXxsv7ItJpOniYPYsmZ/x0QD/o5P105DeQwF6MH33ogoLi+KQp7zpY3HQV5bFMURzheXeds7gpP+jKNXljjHuYvXHke7cdCxLLZf6YX7B63UcCV4nGNgZGBgAOKAN2ZR8fw2Xxm4WRhA4AbHYRMY/f///1oWBuYGIJeDgQkkCgAvWgs2AAAAeJxjYGRgYG7438AQw8Lw/z8DAwsDA1AEBXADAHXiBHJ4nGNhYGBgYfj/nwVM48cATwECKwAAAAAAjAC6AOgBFAGAAf4CbgLqAzgDkHicY2BkYGDgZkhiYGcAASYg5gJCBob/YD4DABOmAYsAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicbYhdDoIwEAb3a6k/YIIX8VArWewmdJFWJOnpJTG+OQ+TzJCjLy39p4ODR4OAA4444YwWHS7U3IVzn6Voldtb8ksHnvohrlqjjmw1rmzXsvdT7fEbblnCmOfNfJIYStJJfGIL27yb6AOCGR89AAA=') format('woff'), |
|||
url('iconfont.ttf?t=1541579316141') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ |
|||
url('iconfont.svg?t=1541579316141#iconfont') format('svg'); /* iOS 4.1- */ |
|||
} |
|||
|
|||
.iconfont { |
|||
font-family:"iconfont" !important; |
|||
font-size:16px; |
|||
font-style:normal; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
} |
|||
|
|||
.icon-bear:before { content: "\e600"; } |
|||
|
|||
.icon-resize-vertical:before { content: "\e7c3"; } |
|||
|
|||
.icon-chuizhifanzhuan:before { content: "\e661"; } |
|||
|
|||
.icon-shuipingfanzhuan:before { content: "\e662"; } |
|||
|
|||
.icon-qq:before { content: "\e609"; } |
|||
|
|||
.icon-frown:before { content: "\e77e"; } |
|||
|
|||
.icon-meh:before { content: "\e780"; } |
|||
|
|||
.icon-smile:before { content: "\e783"; } |
|||
|
|||
.icon-man:before { content: "\e7e2"; } |
|||
|
|||
.icon-woman:before { content: "\e7e5"; } |
|||
|
|||
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@ -0,0 +1,58 @@ |
|||
<template> |
|||
<div ref="dom" class="charts chart-bar"></div> |
|||
</template> |
|||
|
|||
<script> |
|||
import echarts from 'echarts' |
|||
import tdTheme from './theme.json' |
|||
import { on, off } from '@/libs/tools' |
|||
echarts.registerTheme('tdTheme', tdTheme) |
|||
export default { |
|||
name: 'ChartBar', |
|||
props: { |
|||
value: Object, |
|||
text: String, |
|||
subtext: String |
|||
}, |
|||
data () { |
|||
return { |
|||
dom: null |
|||
} |
|||
}, |
|||
methods: { |
|||
resize () { |
|||
this.dom.resize() |
|||
} |
|||
}, |
|||
mounted () { |
|||
this.$nextTick(() => { |
|||
let xAxisData = Object.keys(this.value) |
|||
let seriesData = Object.values(this.value) |
|||
let option = { |
|||
title: { |
|||
text: this.text, |
|||
subtext: this.subtext, |
|||
x: 'center' |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
data: xAxisData |
|||
}, |
|||
yAxis: { |
|||
type: 'value' |
|||
}, |
|||
series: [{ |
|||
data: seriesData, |
|||
type: 'bar' |
|||
}] |
|||
} |
|||
this.dom = echarts.init(this.$refs.dom, 'tdTheme') |
|||
this.dom.setOption(option) |
|||
on(window, 'resize', this.resize) |
|||
}) |
|||
}, |
|||
beforeDestroy () { |
|||
off(window, 'resize', this.resize) |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,3 @@ |
|||
import ChartPie from './pie.vue' |
|||
import ChartBar from './bar.vue' |
|||
export { ChartPie, ChartBar } |
|||
@ -0,0 +1,70 @@ |
|||
<template> |
|||
<div ref="dom" class="charts chart-pie"></div> |
|||
</template> |
|||
|
|||
<script> |
|||
import echarts from 'echarts' |
|||
import tdTheme from './theme.json' |
|||
import { on, off } from '@/libs/tools' |
|||
echarts.registerTheme('tdTheme', tdTheme) |
|||
export default { |
|||
name: 'ChartPie', |
|||
props: { |
|||
value: Array, |
|||
text: String, |
|||
subtext: String |
|||
}, |
|||
data () { |
|||
return { |
|||
dom: null |
|||
} |
|||
}, |
|||
methods: { |
|||
resize () { |
|||
this.dom.resize() |
|||
} |
|||
}, |
|||
mounted () { |
|||
this.$nextTick(() => { |
|||
let legend = this.value.map(_ => _.name) |
|||
let option = { |
|||
title: { |
|||
text: this.text, |
|||
subtext: this.subtext, |
|||
x: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item', |
|||
formatter: '{a} <br/>{b} : {c} ({d}%)' |
|||
}, |
|||
legend: { |
|||
orient: 'vertical', |
|||
left: 'left', |
|||
data: legend |
|||
}, |
|||
series: [ |
|||
{ |
|||
type: 'pie', |
|||
radius: '55%', |
|||
center: ['50%', '60%'], |
|||
data: this.value, |
|||
itemStyle: { |
|||
emphasis: { |
|||
shadowBlur: 10, |
|||
shadowOffsetX: 0, |
|||
shadowColor: 'rgba(0, 0, 0, 0.5)' |
|||
} |
|||
} |
|||
} |
|||
] |
|||
} |
|||
this.dom = echarts.init(this.$refs.dom, 'tdTheme') |
|||
this.dom.setOption(option) |
|||
on(window, 'resize', this.resize) |
|||
}) |
|||
}, |
|||
beforeDestroy () { |
|||
off(window, 'resize', this.resize) |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,491 @@ |
|||
|
|||
{ |
|||
"color": [ |
|||
"#2d8cf0", |
|||
"#19be6b", |
|||
"#ff9900", |
|||
"#E46CBB", |
|||
"#9A66E4", |
|||
"#ed3f14" |
|||
], |
|||
"backgroundColor": "rgba(0,0,0,0)", |
|||
"textStyle": {}, |
|||
"title": { |
|||
"textStyle": { |
|||
"color": "#516b91" |
|||
}, |
|||
"subtextStyle": { |
|||
"color": "#93b7e3" |
|||
} |
|||
}, |
|||
"line": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": "2" |
|||
} |
|||
}, |
|||
"lineStyle": { |
|||
"normal": { |
|||
"width": "2" |
|||
} |
|||
}, |
|||
"symbolSize": "6", |
|||
"symbol": "emptyCircle", |
|||
"smooth": true |
|||
}, |
|||
"radar": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": "2" |
|||
} |
|||
}, |
|||
"lineStyle": { |
|||
"normal": { |
|||
"width": "2" |
|||
} |
|||
}, |
|||
"symbolSize": "6", |
|||
"symbol": "emptyCircle", |
|||
"smooth": true |
|||
}, |
|||
"bar": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"barBorderWidth": 0, |
|||
"barBorderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"barBorderWidth": 0, |
|||
"barBorderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"pie": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"scatter": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"boxplot": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"parallel": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"sankey": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"funnel": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"gauge": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
}, |
|||
"emphasis": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
} |
|||
}, |
|||
"candlestick": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"color": "#edafda", |
|||
"color0": "transparent", |
|||
"borderColor": "#d680bc", |
|||
"borderColor0": "#8fd3e8", |
|||
"borderWidth": "2" |
|||
} |
|||
} |
|||
}, |
|||
"graph": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"borderWidth": 0, |
|||
"borderColor": "#ccc" |
|||
} |
|||
}, |
|||
"lineStyle": { |
|||
"normal": { |
|||
"width": 1, |
|||
"color": "#aaa" |
|||
} |
|||
}, |
|||
"symbolSize": "6", |
|||
"symbol": "emptyCircle", |
|||
"smooth": true, |
|||
"color": [ |
|||
"#2d8cf0", |
|||
"#19be6b", |
|||
"#f5ae4a", |
|||
"#9189d5", |
|||
"#56cae2", |
|||
"#cbb0e3" |
|||
], |
|||
"label": { |
|||
"normal": { |
|||
"textStyle": { |
|||
"color": "#eee" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"map": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"areaColor": "#f3f3f3", |
|||
"borderColor": "#516b91", |
|||
"borderWidth": 0.5 |
|||
}, |
|||
"emphasis": { |
|||
"areaColor": "rgba(165,231,240,1)", |
|||
"borderColor": "#516b91", |
|||
"borderWidth": 1 |
|||
} |
|||
}, |
|||
"label": { |
|||
"normal": { |
|||
"textStyle": { |
|||
"color": "#000" |
|||
} |
|||
}, |
|||
"emphasis": { |
|||
"textStyle": { |
|||
"color": "rgb(81,107,145)" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"geo": { |
|||
"itemStyle": { |
|||
"normal": { |
|||
"areaColor": "#f3f3f3", |
|||
"borderColor": "#516b91", |
|||
"borderWidth": 0.5 |
|||
}, |
|||
"emphasis": { |
|||
"areaColor": "rgba(165,231,240,1)", |
|||
"borderColor": "#516b91", |
|||
"borderWidth": 1 |
|||
} |
|||
}, |
|||
"label": { |
|||
"normal": { |
|||
"textStyle": { |
|||
"color": "#000" |
|||
} |
|||
}, |
|||
"emphasis": { |
|||
"textStyle": { |
|||
"color": "rgb(81,107,145)" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"categoryAxis": { |
|||
"axisLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": "#cccccc" |
|||
} |
|||
}, |
|||
"axisTick": { |
|||
"show": false, |
|||
"lineStyle": { |
|||
"color": "#333" |
|||
} |
|||
}, |
|||
"axisLabel": { |
|||
"show": true, |
|||
"textStyle": { |
|||
"color": "#999999" |
|||
} |
|||
}, |
|||
"splitLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": [ |
|||
"#eeeeee" |
|||
] |
|||
} |
|||
}, |
|||
"splitArea": { |
|||
"show": false, |
|||
"areaStyle": { |
|||
"color": [ |
|||
"rgba(250,250,250,0.05)", |
|||
"rgba(200,200,200,0.02)" |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
"valueAxis": { |
|||
"axisLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": "#cccccc" |
|||
} |
|||
}, |
|||
"axisTick": { |
|||
"show": false, |
|||
"lineStyle": { |
|||
"color": "#333" |
|||
} |
|||
}, |
|||
"axisLabel": { |
|||
"show": true, |
|||
"textStyle": { |
|||
"color": "#999999" |
|||
} |
|||
}, |
|||
"splitLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": [ |
|||
"#eeeeee" |
|||
] |
|||
} |
|||
}, |
|||
"splitArea": { |
|||
"show": false, |
|||
"areaStyle": { |
|||
"color": [ |
|||
"rgba(250,250,250,0.05)", |
|||
"rgba(200,200,200,0.02)" |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
"logAxis": { |
|||
"axisLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": "#cccccc" |
|||
} |
|||
}, |
|||
"axisTick": { |
|||
"show": false, |
|||
"lineStyle": { |
|||
"color": "#333" |
|||
} |
|||
}, |
|||
"axisLabel": { |
|||
"show": true, |
|||
"textStyle": { |
|||
"color": "#999999" |
|||
} |
|||
}, |
|||
"splitLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": [ |
|||
"#eeeeee" |
|||
] |
|||
} |
|||
}, |
|||
"splitArea": { |
|||
"show": false, |
|||
"areaStyle": { |
|||
"color": [ |
|||
"rgba(250,250,250,0.05)", |
|||
"rgba(200,200,200,0.02)" |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
"timeAxis": { |
|||
"axisLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": "#cccccc" |
|||
} |
|||
}, |
|||
"axisTick": { |
|||
"show": false, |
|||
"lineStyle": { |
|||
"color": "#333" |
|||
} |
|||
}, |
|||
"axisLabel": { |
|||
"show": true, |
|||
"textStyle": { |
|||
"color": "#999999" |
|||
} |
|||
}, |
|||
"splitLine": { |
|||
"show": true, |
|||
"lineStyle": { |
|||
"color": [ |
|||
"#eeeeee" |
|||
] |
|||
} |
|||
}, |
|||
"splitArea": { |
|||
"show": false, |
|||
"areaStyle": { |
|||
"color": [ |
|||
"rgba(250,250,250,0.05)", |
|||
"rgba(200,200,200,0.02)" |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
"toolbox": { |
|||
"iconStyle": { |
|||
"normal": { |
|||
"borderColor": "#999" |
|||
}, |
|||
"emphasis": { |
|||
"borderColor": "#666" |
|||
} |
|||
} |
|||
}, |
|||
"legend": { |
|||
"textStyle": { |
|||
"color": "#999999" |
|||
} |
|||
}, |
|||
"tooltip": { |
|||
"axisPointer": { |
|||
"lineStyle": { |
|||
"color": "#ccc", |
|||
"width": 1 |
|||
}, |
|||
"crossStyle": { |
|||
"color": "#ccc", |
|||
"width": 1 |
|||
} |
|||
} |
|||
}, |
|||
"timeline": { |
|||
"lineStyle": { |
|||
"color": "#8fd3e8", |
|||
"width": 1 |
|||
}, |
|||
"itemStyle": { |
|||
"normal": { |
|||
"color": "#8fd3e8", |
|||
"borderWidth": 1 |
|||
}, |
|||
"emphasis": { |
|||
"color": "#8fd3e8" |
|||
} |
|||
}, |
|||
"controlStyle": { |
|||
"normal": { |
|||
"color": "#8fd3e8", |
|||
"borderColor": "#8fd3e8", |
|||
"borderWidth": 0.5 |
|||
}, |
|||
"emphasis": { |
|||
"color": "#8fd3e8", |
|||
"borderColor": "#8fd3e8", |
|||
"borderWidth": 0.5 |
|||
} |
|||
}, |
|||
"checkpointStyle": { |
|||
"color": "#8fd3e8", |
|||
"borderColor": "rgba(138,124,168,0.37)" |
|||
}, |
|||
"label": { |
|||
"normal": { |
|||
"textStyle": { |
|||
"color": "#8fd3e8" |
|||
} |
|||
}, |
|||
"emphasis": { |
|||
"textStyle": { |
|||
"color": "#8fd3e8" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"visualMap": { |
|||
"color": [ |
|||
"#516b91", |
|||
"#59c4e6", |
|||
"#a5e7f0" |
|||
] |
|||
}, |
|||
"dataZoom": { |
|||
"backgroundColor": "rgba(0,0,0,0)", |
|||
"dataBackgroundColor": "rgba(255,255,255,0.3)", |
|||
"fillerColor": "rgba(167,183,204,0.4)", |
|||
"handleColor": "#a7b7cc", |
|||
"handleSize": "100%", |
|||
"textStyle": { |
|||
"color": "#333" |
|||
} |
|||
}, |
|||
"markPoint": { |
|||
"label": { |
|||
"normal": { |
|||
"textStyle": { |
|||
"color": "#eee" |
|||
} |
|||
}, |
|||
"emphasis": { |
|||
"textStyle": { |
|||
"color": "#eee" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
<template> |
|||
<component :is="iconType" :type="iconName" :color="iconColor" :size="iconSize"/> |
|||
</template> |
|||
|
|||
<script> |
|||
import Icons from '_c/icons' |
|||
export default { |
|||
name: 'CommonIcon', |
|||
components: { Icons }, |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
color: String, |
|||
size: Number |
|||
}, |
|||
computed: { |
|||
iconType () { |
|||
return this.type.indexOf('_') === 0 ? 'Icons' : 'Icon' |
|||
}, |
|||
iconName () { |
|||
return this.iconType === 'Icons' ? this.getCustomIconName(this.type) : this.type |
|||
}, |
|||
iconSize () { |
|||
return this.size || (this.iconType === 'Icons' ? 12 : undefined) |
|||
}, |
|||
iconColor () { |
|||
return this.color || '' |
|||
} |
|||
}, |
|||
methods: { |
|||
getCustomIconName (iconName) { |
|||
return iconName.slice(1) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
import CommonIcon from './common-icon.vue' |
|||
export default CommonIcon |
|||
@ -0,0 +1,8 @@ |
|||
.no-select{ |
|||
-webkit-touch-callout: none; |
|||
-webkit-user-select: none; |
|||
-khtml-user-select: none; |
|||
-moz-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
export const showTitle = (item, vm) => { |
|||
return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name) |
|||
} |
|||
@ -0,0 +1,220 @@ |
|||
<style lang="less"> |
|||
.icon-search { |
|||
position: relative; |
|||
margin: 20px auto 30px; |
|||
text-align: center; |
|||
input { |
|||
width: 500px; |
|||
box-sizing: border-box; |
|||
border: 0; |
|||
border-radius: 4px; |
|||
background: #f5f5f5; |
|||
text-align: center; |
|||
font-size: 14px; |
|||
outline: none; |
|||
margin: 0 auto; |
|||
padding: 8px 0; |
|||
} |
|||
} |
|||
.icon-block { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
max-height: 500px; |
|||
overflow: auto; |
|||
} |
|||
.icon-bar { |
|||
overflow: auto; |
|||
overflow-x: hidden; |
|||
} |
|||
.icon-bar::-webkit-scrollbar { |
|||
width: 6px; |
|||
height: 6px; |
|||
} |
|||
|
|||
.icon-bar::-webkit-scrollbar-thumb { |
|||
border-radius: 3px; |
|||
background: #c3c3c3; |
|||
} |
|||
|
|||
.icon-bar::-webkit-scrollbar-track { |
|||
background: #fff; |
|||
} |
|||
.icon-wrap { |
|||
:hover { |
|||
color: #1890ff; |
|||
transition: color 0.3s; |
|||
} |
|||
} |
|||
.icons-item { |
|||
margin: 6px 6px 6px 0; |
|||
width: 145px; |
|||
text-align: center; |
|||
list-style: none; |
|||
cursor: pointer; |
|||
height: 100px; |
|||
color: #5c6b77; |
|||
transition: all 0.2s ease; |
|||
position: relative; |
|||
padding-top: 10px; |
|||
p { |
|||
padding-top: 15px; |
|||
margin: 5px; |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
</style> |
|||
|
|||
<template> |
|||
<div> |
|||
<div style="display:flex"> |
|||
<Input |
|||
v-model="currentValue" |
|||
@on-change="handleChange" |
|||
:placeholder="placeholder" |
|||
:size="size" |
|||
:disabled="disabled" |
|||
:readonly="readonly" |
|||
:maxlength="maxlength" |
|||
:icon="currentValue" |
|||
/> |
|||
<Button |
|||
@click="iconModalVisible=true" |
|||
:size="size" |
|||
:disabled="disabled" |
|||
:icon="icon" |
|||
class="margin-left-10" |
|||
>选择图标</Button> |
|||
</div> |
|||
|
|||
<Modal |
|||
title="选择图标" |
|||
v-model="iconModalVisible" |
|||
:width="950" |
|||
:styles="{top: '30px'}" |
|||
footer-hide |
|||
:z-index="1060" |
|||
> |
|||
<div class="icon-search"> |
|||
<input |
|||
type="text" |
|||
v-model="key" |
|||
:placeholder="tip" |
|||
@input="handleInput" |
|||
@focus="handleFocus" |
|||
@blur="handleBlur" |
|||
> |
|||
</div> |
|||
<div class="icon-block icon-bar"> |
|||
<div class="icon-wrap" v-for="(item, i) in iconData" :key="i" @click="hanleChoose(item)"> |
|||
<div class="icons-item"> |
|||
<Icon :type="item" style="font-size: 32px;"/> |
|||
<p>{{item}}</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</Modal> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { icons } from '@/libs/icons' |
|||
export default { |
|||
name: 'IconChoose', |
|||
props: { |
|||
value: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
size: String, |
|||
placeholder: { |
|||
type: String, |
|||
default: '输入图标名或选择图标' |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
readonly: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
maxlength: Number, |
|||
icon: { |
|||
type: String, |
|||
default: 'md-ionic' |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
iconModalVisible: false, |
|||
currentValue: this.value, |
|||
iconData: [], |
|||
key: '', |
|||
tip: '输入英文关键词搜索,比如 success' |
|||
} |
|||
}, |
|||
methods: { |
|||
init () { |
|||
let re = [] |
|||
icons.forEach(e => { |
|||
e.icons.forEach(item => { |
|||
re.push(item) |
|||
}) |
|||
}) |
|||
this.iconData = re |
|||
}, |
|||
handleInput () { |
|||
if (this.key) { |
|||
// 搜索 |
|||
let re = [] |
|||
icons.forEach(e => { |
|||
e.tags.forEach(item => { |
|||
if (item.indexOf(this.key) >= 0) { |
|||
e.icons.forEach(r => { |
|||
re.push(r) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
this.iconData = re |
|||
} else { |
|||
this.init() |
|||
} |
|||
}, |
|||
handleFocus () { |
|||
if (!this.key) { |
|||
this.tip = '' |
|||
} |
|||
}, |
|||
handleBlur () { |
|||
if (!this.key) { |
|||
this.tip = '输入英文关键词搜索,比如 success' |
|||
} |
|||
}, |
|||
handleChange (v) { |
|||
this.$emit('input', this.currentValue) |
|||
this.$emit('on-change', this.currentValue) |
|||
}, |
|||
setCurrentValue (value) { |
|||
if (value === this.currentValue) { |
|||
return |
|||
} |
|||
this.currentValue = value |
|||
}, |
|||
hanleChoose (v) { |
|||
this.currentValue = v |
|||
this.$emit('input', this.currentValue) |
|||
this.$emit('on-change', this.currentValue) |
|||
this.iconModalVisible = false |
|||
} |
|||
}, |
|||
watch: { |
|||
value (val) { |
|||
this.setCurrentValue(val) |
|||
} |
|||
}, |
|||
created () { |
|||
this.init() |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,35 @@ |
|||
<template> |
|||
<i :class="`iconfont icon-${type}`" :style="styles"></i> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Icons', |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
color: { |
|||
type: String, |
|||
default: '#5c6b77' |
|||
}, |
|||
size: { |
|||
type: Number, |
|||
default: 16 |
|||
} |
|||
}, |
|||
computed: { |
|||
styles () { |
|||
return { |
|||
fontSize: `${this.size}px`, |
|||
color: this.color |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
import Icons from './icons.vue' |
|||
export default Icons |
|||
@ -0,0 +1,2 @@ |
|||
import InforCard from './infor-card.vue' |
|||
export default InforCard |
|||
@ -0,0 +1,94 @@ |
|||
<template> |
|||
<Card :shadow="shadow" class="info-card-wrapper" :padding="0"> |
|||
<div class="content-con"> |
|||
<div class="left-area" :style="{background: color, width: leftWidth}"> |
|||
<common-icon class="icon" :type="icon" :size="iconSize" color="#fff"/> |
|||
</div> |
|||
<div class="right-area" :style="{width: rightWidth}"> |
|||
<div> |
|||
<slot></slot> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</Card> |
|||
</template> |
|||
|
|||
<script> |
|||
import CommonIcon from '_c/common-icon' |
|||
export default { |
|||
name: 'InforCard', |
|||
components: { |
|||
CommonIcon |
|||
}, |
|||
props: { |
|||
left: { |
|||
type: Number, |
|||
default: 36 |
|||
}, |
|||
color: { |
|||
type: String, |
|||
default: '#2d8cf0' |
|||
}, |
|||
icon: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
iconSize: { |
|||
type: Number, |
|||
default: 20 |
|||
}, |
|||
shadow: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
computed: { |
|||
leftWidth () { |
|||
return `${this.left}%` |
|||
}, |
|||
rightWidth () { |
|||
return `${100 - this.left}%` |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less"> |
|||
.common{ |
|||
float: left; |
|||
height: 100%; |
|||
display: table; |
|||
text-align: center; |
|||
} |
|||
.size{ |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
.middle-center{ |
|||
display: table-cell; |
|||
vertical-align: middle; |
|||
} |
|||
.info-card-wrapper{ |
|||
.size; |
|||
overflow: hidden; |
|||
.ivu-card-body{ |
|||
.size; |
|||
} |
|||
.content-con{ |
|||
.size; |
|||
position: relative; |
|||
.left-area{ |
|||
.common; |
|||
& > .icon{ |
|||
.middle-center; |
|||
} |
|||
} |
|||
.right-area{ |
|||
.common; |
|||
& > div{ |
|||
.middle-center; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
import ABackTop from './index.vue' |
|||
export default ABackTop |
|||
@ -0,0 +1,90 @@ |
|||
<template> |
|||
<div :class="classes" :style="styles" @click="back"> |
|||
<slot> |
|||
<div :class="innerClasses"> |
|||
<i class="ivu-icon ivu-icon-ios-arrow-up"></i> |
|||
</div> |
|||
</slot> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import { scrollTop } from '@/libs/util' |
|||
import { on, off } from '@/libs/tools' |
|||
const prefixCls = 'ivu-back-top' |
|||
|
|||
export default { |
|||
name: 'ABackTop', |
|||
props: { |
|||
height: { |
|||
type: Number, |
|||
default: 400 |
|||
}, |
|||
bottom: { |
|||
type: Number, |
|||
default: 30 |
|||
}, |
|||
right: { |
|||
type: Number, |
|||
default: 30 |
|||
}, |
|||
duration: { |
|||
type: Number, |
|||
default: 1000 |
|||
}, |
|||
container: { |
|||
type: null, |
|||
default: window |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
backTop: false |
|||
} |
|||
}, |
|||
mounted () { |
|||
// window.addEventListener('scroll', this.handleScroll, false) |
|||
// window.addEventListener('resize', this.handleScroll, false) |
|||
on(this.containerEle, 'scroll', this.handleScroll) |
|||
on(this.containerEle, 'resize', this.handleScroll) |
|||
}, |
|||
beforeDestroy () { |
|||
// window.removeEventListener('scroll', this.handleScroll, false) |
|||
// window.removeEventListener('resize', this.handleScroll, false) |
|||
off(this.containerEle, 'scroll', this.handleScroll) |
|||
off(this.containerEle, 'resize', this.handleScroll) |
|||
}, |
|||
computed: { |
|||
classes () { |
|||
return [ |
|||
`${prefixCls}`, |
|||
{ |
|||
[`${prefixCls}-show`]: this.backTop |
|||
} |
|||
] |
|||
}, |
|||
styles () { |
|||
return { |
|||
bottom: `${this.bottom}px`, |
|||
right: `${this.right}px` |
|||
} |
|||
}, |
|||
innerClasses () { |
|||
return `${prefixCls}-inner` |
|||
}, |
|||
containerEle () { |
|||
return this.container === window ? window : document.querySelector(this.container) |
|||
} |
|||
}, |
|||
methods: { |
|||
handleScroll () { |
|||
this.backTop = this.containerEle.scrollTop >= this.height |
|||
}, |
|||
back () { |
|||
let target = typeof this.container === 'string' ? this.containerEle : (document.documentElement || document.body) |
|||
const sTop = target.scrollTop |
|||
scrollTop(this.containerEle, sTop, 0, this.duration) |
|||
this.$emit('on-click') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,84 @@ |
|||
<template> |
|||
<div v-if="showFullScreenBtn" class="full-screen-btn-con"> |
|||
<Tooltip :content="value ? '退出全屏' : '全屏'" placement="bottom"> |
|||
<Icon @click.native="handleChange" :type="value ? 'md-contract' : 'md-expand'" :size="23"></Icon> |
|||
</Tooltip> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Fullscreen', |
|||
computed: { |
|||
showFullScreenBtn () { |
|||
return window.navigator.userAgent.indexOf('MSIE') < 0 |
|||
} |
|||
}, |
|||
props: { |
|||
value: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
methods: { |
|||
handleFullscreen () { |
|||
let main = document.body |
|||
if (this.value) { |
|||
if (document.exitFullscreen) { |
|||
document.exitFullscreen() |
|||
} else if (document.mozCancelFullScreen) { |
|||
document.mozCancelFullScreen() |
|||
} else if (document.webkitCancelFullScreen) { |
|||
document.webkitCancelFullScreen() |
|||
} else if (document.msExitFullscreen) { |
|||
document.msExitFullscreen() |
|||
} |
|||
} else { |
|||
if (main.requestFullscreen) { |
|||
main.requestFullscreen() |
|||
} else if (main.mozRequestFullScreen) { |
|||
main.mozRequestFullScreen() |
|||
} else if (main.webkitRequestFullScreen) { |
|||
main.webkitRequestFullScreen() |
|||
} else if (main.msRequestFullscreen) { |
|||
main.msRequestFullscreen() |
|||
} |
|||
} |
|||
}, |
|||
handleChange () { |
|||
this.handleFullscreen() |
|||
} |
|||
}, |
|||
mounted () { |
|||
let isFullscreen = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen |
|||
isFullscreen = !!isFullscreen |
|||
document.addEventListener('fullscreenchange', () => { |
|||
this.$emit('input', !this.value) |
|||
this.$emit('on-change', !this.value) |
|||
}) |
|||
document.addEventListener('mozfullscreenchange', () => { |
|||
this.$emit('input', !this.value) |
|||
this.$emit('on-change', !this.value) |
|||
}) |
|||
document.addEventListener('webkitfullscreenchange', () => { |
|||
this.$emit('input', !this.value) |
|||
this.$emit('on-change', !this.value) |
|||
}) |
|||
document.addEventListener('msfullscreenchange', () => { |
|||
this.$emit('input', !this.value) |
|||
this.$emit('on-change', !this.value) |
|||
}) |
|||
this.$emit('input', isFullscreen) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less"> |
|||
.full-screen-btn-con .ivu-tooltip-rel{ |
|||
height: 64px; |
|||
line-height: 56px; |
|||
i{ |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
import Fullscreen from './fullscreen.vue' |
|||
export default Fullscreen |
|||
@ -0,0 +1,4 @@ |
|||
.custom-bread-crumb{ |
|||
display: inline-block; |
|||
vertical-align: top; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
<template> |
|||
<div class="custom-bread-crumb"> |
|||
<Breadcrumb :style="{fontSize: `${fontSize}px`}"> |
|||
<BreadcrumbItem v-for="item in list" :to="item.to" :key="`bread-crumb-${item.name}`"> |
|||
<common-icon style="margin-right: 4px;" :type="item.icon || ''"/> |
|||
{{ showTitle(item) }} |
|||
</BreadcrumbItem> |
|||
</Breadcrumb> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import { showTitle } from '@/libs/util' |
|||
import CommonIcon from '_c/common-icon' |
|||
import './custom-bread-crumb.less' |
|||
export default { |
|||
name: 'customBreadCrumb', |
|||
components: { |
|||
CommonIcon |
|||
}, |
|||
props: { |
|||
list: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
fontSize: { |
|||
type: Number, |
|||
default: 14 |
|||
}, |
|||
showIcon: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
methods: { |
|||
showTitle (item) { |
|||
return showTitle(item, this) |
|||
}, |
|||
isCustomIcon (iconName) { |
|||
return iconName.indexOf('_') === 0 |
|||
}, |
|||
getCustomIconName (iconName) { |
|||
return iconName.slice(1) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,2 @@ |
|||
import customBreadCrumb from './custom-bread-crumb.vue' |
|||
export default customBreadCrumb |
|||
@ -0,0 +1,14 @@ |
|||
.header-bar{ |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
.custom-content-con{ |
|||
float: right; |
|||
height: auto; |
|||
padding-right: 20px; |
|||
line-height: 64px; |
|||
& > *{ |
|||
float: right; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
<template> |
|||
<div class="header-bar"> |
|||
<sider-trigger :collapsed="collapsed" icon="md-menu" @on-change="handleCollpasedChange"></sider-trigger> |
|||
<custom-bread-crumb show-icon style="margin-left: 30px;" :list="breadCrumbList"></custom-bread-crumb> |
|||
<div class="custom-content-con"> |
|||
<slot></slot> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import siderTrigger from './sider-trigger' |
|||
import customBreadCrumb from './custom-bread-crumb' |
|||
import './header-bar.less' |
|||
export default { |
|||
name: 'HeaderBar', |
|||
components: { |
|||
siderTrigger, |
|||
customBreadCrumb |
|||
}, |
|||
props: { |
|||
collapsed: Boolean |
|||
}, |
|||
computed: { |
|||
breadCrumbList () { |
|||
return this.$store.state.app.breadCrumbList |
|||
} |
|||
}, |
|||
methods: { |
|||
handleCollpasedChange (state) { |
|||
this.$emit('on-coll-change', state) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,2 @@ |
|||
import HeaderBar from './header-bar' |
|||
export default HeaderBar |
|||
@ -0,0 +1,2 @@ |
|||
import siderTrigger from './sider-trigger.vue' |
|||
export default siderTrigger |
|||
@ -0,0 +1,21 @@ |
|||
.trans{ |
|||
transition: transform .2s ease; |
|||
} |
|||
@size: 40px; |
|||
.sider-trigger-a{ |
|||
padding: 6px; |
|||
width: @size; |
|||
height: @size; |
|||
display: inline-block; |
|||
text-align: center; |
|||
color: #5c6b77; |
|||
margin-top: 12px; |
|||
i{ |
|||
.trans; |
|||
vertical-align: top; |
|||
} |
|||
&.collapsed i{ |
|||
transform: rotateZ(90deg); |
|||
.trans; |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<template> |
|||
<a @click="handleChange" type="text" :class="['sider-trigger-a', collapsed ? 'collapsed' : '']"><Icon :type="icon" :size="size" /></a> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
name: 'siderTrigger', |
|||
props: { |
|||
collapsed: Boolean, |
|||
icon: { |
|||
type: String, |
|||
default: 'navicon-round' |
|||
}, |
|||
size: { |
|||
type: Number, |
|||
default: 26 |
|||
} |
|||
}, |
|||
methods: { |
|||
handleChange () { |
|||
this.$emit('on-change', !this.collapsed) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="less"> |
|||
@import './sider-trigger.less'; |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
import Language from './language.vue' |
|||
export default Language |
|||
@ -0,0 +1,51 @@ |
|||
<template> |
|||
<div> |
|||
<Dropdown trigger="click" @on-click="selectLang"> |
|||
<a href="javascript:void(0)"> |
|||
{{ title }} |
|||
<Icon :size="18" type="md-arrow-dropdown" /> |
|||
</a> |
|||
<DropdownMenu slot="list"> |
|||
<DropdownItem v-for="(value, key) in localList" :name="key" :key="`lang-${key}`">{{ value }}</DropdownItem> |
|||
</DropdownMenu> |
|||
</Dropdown> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Language', |
|||
props: { |
|||
lang: String |
|||
}, |
|||
data () { |
|||
return { |
|||
langList: { |
|||
'zh-CN': '语言', |
|||
'zh-TW': '語言', |
|||
'en-US': 'Lang' |
|||
}, |
|||
localList: { |
|||
'zh-CN': '中文简体', |
|||
'zh-TW': '中文繁体', |
|||
'en-US': 'English' |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
lang (lang) { |
|||
this.$i18n.locale = lang |
|||
} |
|||
}, |
|||
computed: { |
|||
title () { |
|||
return this.langList[this.lang] |
|||
} |
|||
}, |
|||
methods: { |
|||
selectLang (name) { |
|||
this.$emit('on-lang-change', name) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,51 @@ |
|||
<template> |
|||
<Dropdown ref="dropdown" @on-click="handleClick" :class="hideTitle ? '' : 'collased-menu-dropdown'" :transfer="hideTitle" :placement="placement"> |
|||
<a class="drop-menu-a" type="text" @mouseover="handleMousemove($event, children)" :style="{textAlign: !hideTitle ? 'left' : ''}"><common-icon :size="rootIconSize" :color="textColor" :type="parentItem.icon"/><span class="menu-title" v-if="!hideTitle">{{ showTitle(parentItem) }}</span><Icon style="float: right;" v-if="!hideTitle" type="ios-arrow-forward" :size="16"/></a> |
|||
<DropdownMenu ref="dropdown" slot="list"> |
|||
<template v-for="child in children"> |
|||
<collapsed-menu v-if="showChildren(child)" :icon-size="iconSize" :parent-item="child" :key="`drop-${child.name}`"></collapsed-menu> |
|||
<DropdownItem v-else :key="`drop-${child.name}`" :name="child.name"><common-icon :size="iconSize" :type="child.icon"/><span class="menu-title">{{ showTitle(child) }}</span></DropdownItem> |
|||
</template> |
|||
</DropdownMenu> |
|||
</Dropdown> |
|||
</template> |
|||
<script> |
|||
import mixin from './mixin' |
|||
import itemMixin from './item-mixin' |
|||
import { findNodeUpperByClasses } from '@/libs/util' |
|||
|
|||
export default { |
|||
name: 'CollapsedMenu', |
|||
mixins: [ mixin, itemMixin ], |
|||
props: { |
|||
hideTitle: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
rootIconSize: { |
|||
type: Number, |
|||
default: 16 |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
placement: 'right-end' |
|||
} |
|||
}, |
|||
methods: { |
|||
handleClick (name) { |
|||
this.$emit('on-click', name) |
|||
}, |
|||
handleMousemove (event, children) { |
|||
const { pageY } = event |
|||
const height = children.length * 38 |
|||
const isOverflow = pageY + height < window.innerHeight |
|||
this.placement = isOverflow ? 'right-start' : 'right-end' |
|||
} |
|||
}, |
|||
mounted () { |
|||
let dropdown = findNodeUpperByClasses(this.$refs.dropdown.$el, ['ivu-select-dropdown', 'ivu-dropdown-transfer']) |
|||
if (dropdown) dropdown.style.overflow = 'visible' |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,2 @@ |
|||
import SideMenu from './side-menu.vue' |
|||
export default SideMenu |
|||
@ -0,0 +1,21 @@ |
|||
export default { |
|||
props: { |
|||
parentItem: { |
|||
type: Object, |
|||
default: () => {} |
|||
}, |
|||
theme: String, |
|||
iconSize: Number |
|||
}, |
|||
computed: { |
|||
parentName () { |
|||
return this.parentItem.name |
|||
}, |
|||
children () { |
|||
return this.parentItem.children |
|||
}, |
|||
textColor () { |
|||
return this.theme === 'dark' ? '#fff' : '#495060' |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
import CommonIcon from '_c/common-icon' |
|||
import { showTitle } from '@/libs/util' |
|||
export default { |
|||
components: { |
|||
CommonIcon |
|||
}, |
|||
methods: { |
|||
showTitle (item) { |
|||
return showTitle(item, this) |
|||
}, |
|||
showChildren (item) { |
|||
return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways)) |
|||
}, |
|||
getNameOrHref (item, children0) { |
|||
return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
<template> |
|||
<Submenu :name="`${parentName}`"> |
|||
<template slot="title"> |
|||
<common-icon :type="parentItem.icon || ''"/> |
|||
<span>{{ showTitle(parentItem) }}</span> |
|||
</template> |
|||
<template v-for="item in children"> |
|||
<template v-if="item.children && item.children.length === 1"> |
|||
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item> |
|||
<menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`"><common-icon :type="item.children[0].icon || ''"/><span>{{ showTitle(item.children[0]) }}</span></menu-item> |
|||
</template> |
|||
<template v-else> |
|||
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item> |
|||
<menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`"><common-icon :type="item.icon || ''"/><span>{{ showTitle(item) }}</span></menu-item> |
|||
</template> |
|||
</template> |
|||
</Submenu> |
|||
</template> |
|||
<script> |
|||
import mixin from './mixin' |
|||
import itemMixin from './item-mixin' |
|||
export default { |
|||
name: 'SideMenuItem', |
|||
mixins: [ mixin, itemMixin ] |
|||
} |
|||
</script> |
|||
@ -0,0 +1,40 @@ |
|||
.side-menu-wrapper{ |
|||
user-select: none; |
|||
.menu-collapsed{ |
|||
padding-top: 10px; |
|||
|
|||
.ivu-dropdown{ |
|||
width: 100%; |
|||
.ivu-dropdown-rel a{ |
|||
width: 100%; |
|||
} |
|||
} |
|||
.ivu-tooltip{ |
|||
width: 100%; |
|||
.ivu-tooltip-rel{ |
|||
width: 100%; |
|||
} |
|||
.ivu-tooltip-popper .ivu-tooltip-content{ |
|||
.ivu-tooltip-arrow{ |
|||
border-right-color: #fff; |
|||
} |
|||
.ivu-tooltip-inner{ |
|||
background: #fff; |
|||
color: #495060; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
a.drop-menu-a{ |
|||
display: inline-block; |
|||
padding: 6px 15px; |
|||
width: 100%; |
|||
text-align: center; |
|||
color: #495060; |
|||
} |
|||
} |
|||
.menu-title{ |
|||
padding-left: 6px; |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
<template> |
|||
<div class="side-menu-wrapper"> |
|||
<slot></slot> |
|||
<Menu ref="menu" v-show="!collapsed" :active-name="activeName" :open-names="openedNames" :accordion="accordion" :theme="theme" width="auto" @on-select="handleSelect"> |
|||
<template v-for="item in menuList"> |
|||
<template v-if="item.children && item.children.length === 1"> |
|||
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item> |
|||
<menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`"><common-icon :type="item.children[0].icon || ''"/><span>{{ showTitle(item.children[0]) }}</span></menu-item> |
|||
</template> |
|||
<template v-else> |
|||
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item> |
|||
<menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`"><common-icon :type="item.icon || ''"/><span>{{ showTitle(item) }}</span></menu-item> |
|||
</template> |
|||
</template> |
|||
</Menu> |
|||
<div class="menu-collapsed" v-show="collapsed" :list="menuList"> |
|||
<template v-for="item in menuList"> |
|||
<collapsed-menu v-if="item.children && item.children.length > 1" @on-click="handleSelect" hide-title :root-icon-size="rootIconSize" :icon-size="iconSize" :theme="theme" :parent-item="item" :key="`drop-menu-${item.name}`"></collapsed-menu> |
|||
<Tooltip transfer v-else :content="showTitle(item.children && item.children[0] ? item.children[0] : item)" placement="right" :key="`drop-menu-${item.name}`"> |
|||
<a @click="handleSelect(getNameOrHref(item, true))" class="drop-menu-a" :style="{textAlign: 'center'}"><common-icon :size="rootIconSize" :color="textColor" :type="item.icon || (item.children && item.children[0].icon)"/></a> |
|||
</Tooltip> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import SideMenuItem from './side-menu-item.vue' |
|||
import CollapsedMenu from './collapsed-menu.vue' |
|||
import { getUnion } from '@/libs/tools' |
|||
import mixin from './mixin' |
|||
|
|||
export default { |
|||
name: 'SideMenu', |
|||
mixins: [ mixin ], |
|||
components: { |
|||
SideMenuItem, |
|||
CollapsedMenu |
|||
}, |
|||
props: { |
|||
menuList: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
collapsed: { |
|||
type: Boolean |
|||
}, |
|||
theme: { |
|||
type: String, |
|||
default: 'dark' |
|||
}, |
|||
rootIconSize: { |
|||
type: Number, |
|||
default: 20 |
|||
}, |
|||
iconSize: { |
|||
type: Number, |
|||
default: 16 |
|||
}, |
|||
accordion: Boolean, |
|||
activeName: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
openNames: { |
|||
type: Array, |
|||
default: () => [] |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
openedNames: [] |
|||
} |
|||
}, |
|||
methods: { |
|||
handleSelect (name) { |
|||
this.$emit('on-select', name) |
|||
}, |
|||
getOpenedNamesByActiveName (name) { |
|||
return this.$route.matched.map(item => item.name).filter(item => item !== name) |
|||
}, |
|||
updateOpenName (name) { |
|||
if (name === 'home') this.openedNames = [] |
|||
else this.openedNames = this.getOpenedNamesByActiveName(name) |
|||
} |
|||
}, |
|||
computed: { |
|||
textColor () { |
|||
return this.theme === 'dark' ? '#fff' : '#495060' |
|||
} |
|||
}, |
|||
watch: { |
|||
activeName (name) { |
|||
if (this.accordion) this.openedNames = this.getOpenedNamesByActiveName(name) |
|||
else this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name)) |
|||
}, |
|||
openNames (newNames) { |
|||
this.openedNames = newNames |
|||
}, |
|||
openedNames () { |
|||
this.$nextTick(() => { |
|||
this.$refs.menu.updateOpened() |
|||
}) |
|||
} |
|||
}, |
|||
mounted () { |
|||
this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name)) |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="less"> |
|||
@import './side-menu.less'; |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
import TagsNav from './tags-nav.vue' |
|||
export default TagsNav |
|||
@ -0,0 +1,87 @@ |
|||
.no-select{ |
|||
-webkit-touch-callout: none; |
|||
-webkit-user-select: none; |
|||
-khtml-user-select: none; |
|||
-moz-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
} |
|||
.size{ |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
.tags-nav{ |
|||
position: relative; |
|||
border-top: 1px solid #F0F0F0; |
|||
border-bottom: 1px solid #F0F0F0; |
|||
.no-select; |
|||
.size; |
|||
.close-con{ |
|||
position: absolute; |
|||
right: 0; |
|||
top: 0; |
|||
height: 100%; |
|||
width: 32px; |
|||
background: #fff; |
|||
text-align: center; |
|||
z-index: 10; |
|||
} |
|||
.btn-con{ |
|||
position: absolute; |
|||
top: 0px; |
|||
height: 100%; |
|||
background: #fff; |
|||
padding-top: 3px; |
|||
z-index: 10; |
|||
button{ |
|||
padding: 6px 4px; |
|||
line-height: 14px; |
|||
text-align: center; |
|||
} |
|||
&.left-btn{ |
|||
left: 0px; |
|||
} |
|||
&.right-btn{ |
|||
right: 32px; |
|||
border-right: 1px solid #F0F0F0; |
|||
} |
|||
} |
|||
.scroll-outer{ |
|||
position: absolute; |
|||
left: 28px; |
|||
right: 61px; |
|||
top: 0; |
|||
bottom: 0; |
|||
box-shadow: 0px 0 3px 2px rgba(100,100,100,.1) inset; |
|||
.scroll-body{ |
|||
height: ~"calc(100% - 1px)"; |
|||
display: inline-block; |
|||
padding: 1px 4px 0; |
|||
position: absolute; |
|||
overflow: visible; |
|||
white-space: nowrap; |
|||
transition: left .3s ease; |
|||
.ivu-tag-dot-inner{ |
|||
transition: background .2s ease; |
|||
} |
|||
} |
|||
} |
|||
.contextmenu { |
|||
position: absolute; |
|||
margin: 0; |
|||
padding: 5px 0; |
|||
background: #fff; |
|||
z-index: 1000; |
|||
list-style-type: none; |
|||
border-radius: 4px; |
|||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .1); |
|||
li { |
|||
margin: 0; |
|||
padding: 5px 15px; |
|||
cursor: pointer; |
|||
&:hover { |
|||
background: #eee; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,209 @@ |
|||
<template> |
|||
<div class="tags-nav"> |
|||
<div class="close-con"> |
|||
<Dropdown transfer @on-click="handleTagsOption" style="margin-top:7px;"> |
|||
<Button size="small" type="text"> |
|||
<Icon :size="18" type="ios-close-circle-outline" /> |
|||
</Button> |
|||
<DropdownMenu slot="list"> |
|||
<DropdownItem name="close-all">关闭所有</DropdownItem> |
|||
<DropdownItem name="close-others">关闭其他</DropdownItem> |
|||
</DropdownMenu> |
|||
</Dropdown> |
|||
</div> |
|||
<ul v-show="visible" :style="{left: contextMenuLeft + 'px', top: contextMenuTop + 'px'}" class="contextmenu"> |
|||
<li v-for="(item, key) of menuList" @click="handleTagsOption(key)" :key="key">{{item}}</li> |
|||
</ul> |
|||
<div class="btn-con left-btn"> |
|||
<Button type="text" @click="handleScroll(240)"> |
|||
<Icon :size="18" type="ios-arrow-back" /> |
|||
</Button> |
|||
</div> |
|||
<div class="btn-con right-btn"> |
|||
<Button type="text" @click="handleScroll(-240)"> |
|||
<Icon :size="18" type="ios-arrow-forward" /> |
|||
</Button> |
|||
</div> |
|||
<div class="scroll-outer" ref="scrollOuter" @DOMMouseScroll="handlescroll" @mousewheel="handlescroll"> |
|||
<div ref="scrollBody" class="scroll-body" :style="{left: tagBodyLeft + 'px'}"> |
|||
<transition-group name="taglist-moving-animation"> |
|||
<Tag |
|||
type="dot" |
|||
v-for="(item, index) in list" |
|||
ref="tagsPageOpened" |
|||
:key="`tag-nav-${index}`" |
|||
:name="item.name" |
|||
:data-route-item="item" |
|||
@on-close="handleClose(item)" |
|||
@click.native="handleClick(item)" |
|||
:closable="item.name !== 'home'" |
|||
:color="isCurrentTag(item) ? 'primary' : 'default'" |
|||
@contextmenu.prevent.native="contextMenu(item, $event)" |
|||
>{{ showTitleInside(item) }}</Tag> |
|||
</transition-group> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { showTitle, routeEqual } from '@/libs/util' |
|||
import beforeClose from '@/router/before-close' |
|||
export default { |
|||
name: 'TagsNav', |
|||
props: { |
|||
value: Object, |
|||
list: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
} |
|||
}, |
|||
data () { |
|||
return { |
|||
tagBodyLeft: 0, |
|||
rightOffset: 40, |
|||
outerPadding: 4, |
|||
contextMenuLeft: 0, |
|||
contextMenuTop: 0, |
|||
visible: false, |
|||
menuList: { |
|||
others: '关闭其他', |
|||
all: '关闭所有' |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
currentRouteObj () { |
|||
const { name, params, query } = this.value |
|||
return { name, params, query } |
|||
} |
|||
}, |
|||
methods: { |
|||
handlescroll (e) { |
|||
var type = e.type |
|||
let delta = 0 |
|||
if (type === 'DOMMouseScroll' || type === 'mousewheel') { |
|||
delta = (e.wheelDelta) ? e.wheelDelta : -(e.detail || 0) * 40 |
|||
} |
|||
this.handleScroll(delta) |
|||
}, |
|||
handleScroll (offset) { |
|||
const outerWidth = this.$refs.scrollOuter.offsetWidth |
|||
const bodyWidth = this.$refs.scrollBody.offsetWidth |
|||
if (offset > 0) { |
|||
this.tagBodyLeft = Math.min(0, this.tagBodyLeft + offset) |
|||
} else { |
|||
if (outerWidth < bodyWidth) { |
|||
if (this.tagBodyLeft < -(bodyWidth - outerWidth)) { |
|||
this.tagBodyLeft = this.tagBodyLeft |
|||
} else { |
|||
this.tagBodyLeft = Math.max(this.tagBodyLeft + offset, outerWidth - bodyWidth) |
|||
} |
|||
} else { |
|||
this.tagBodyLeft = 0 |
|||
} |
|||
} |
|||
}, |
|||
handleTagsOption (type) { |
|||
if (type.includes('all')) { |
|||
// 关闭所有,除了home |
|||
let res = this.list.filter(item => item.name === 'home') |
|||
this.$emit('on-close', res, 'all') |
|||
} else if (type.includes('others')) { |
|||
// 关闭除当前页和home页的其他页 |
|||
let res = this.list.filter(item => routeEqual(this.currentRouteObj, item) || item.name === 'home') |
|||
this.$emit('on-close', res, 'others', this.currentRouteObj) |
|||
setTimeout(() => { |
|||
this.getTagElementByRoute(this.currentRouteObj) |
|||
}, 100) |
|||
} |
|||
}, |
|||
handleClose (current) { |
|||
if (current.meta && current.meta.beforeCloseName && current.meta.beforeCloseName in beforeClose) { |
|||
new Promise(beforeClose[current.meta.beforeCloseName]).then(close => { |
|||
if (close) { |
|||
this.close(current) |
|||
} |
|||
}) |
|||
} else { |
|||
this.close(current) |
|||
} |
|||
}, |
|||
close (route) { |
|||
let res = this.list.filter(item => !routeEqual(route, item)) |
|||
this.$emit('on-close', res, undefined, route) |
|||
}, |
|||
handleClick (item) { |
|||
this.$emit('input', item) |
|||
}, |
|||
showTitleInside (item) { |
|||
return showTitle(item, this) |
|||
}, |
|||
isCurrentTag (item) { |
|||
return routeEqual(this.currentRouteObj, item) |
|||
}, |
|||
moveToView (tag) { |
|||
const outerWidth = this.$refs.scrollOuter.offsetWidth |
|||
const bodyWidth = this.$refs.scrollBody.offsetWidth |
|||
if (bodyWidth < outerWidth) { |
|||
this.tagBodyLeft = 0 |
|||
} else if (tag.offsetLeft < -this.tagBodyLeft) { |
|||
// 标签在可视区域左侧 |
|||
this.tagBodyLeft = -tag.offsetLeft + this.outerPadding |
|||
} else if (tag.offsetLeft > -this.tagBodyLeft && tag.offsetLeft + tag.offsetWidth < -this.tagBodyLeft + outerWidth) { |
|||
// 标签在可视区域 |
|||
this.tagBodyLeft = Math.min(0, outerWidth - tag.offsetWidth - tag.offsetLeft - this.outerPadding) |
|||
} else { |
|||
// 标签在可视区域右侧 |
|||
this.tagBodyLeft = -(tag.offsetLeft - (outerWidth - this.outerPadding - tag.offsetWidth)) |
|||
} |
|||
}, |
|||
getTagElementByRoute (route) { |
|||
this.$nextTick(() => { |
|||
this.refsTag = this.$refs.tagsPageOpened |
|||
this.refsTag.forEach((item, index) => { |
|||
if (routeEqual(route, item.$attrs['data-route-item'])) { |
|||
let tag = this.refsTag[index].$el |
|||
this.moveToView(tag) |
|||
} |
|||
}) |
|||
}) |
|||
}, |
|||
contextMenu (item, e) { |
|||
if (item.name === 'home') { |
|||
return |
|||
} |
|||
this.visible = true |
|||
const offsetLeft = this.$el.getBoundingClientRect().left |
|||
this.contextMenuLeft = e.clientX - offsetLeft + 10 |
|||
this.contextMenuTop = e.clientY - 64 |
|||
}, |
|||
closeMenu () { |
|||
this.visible = false |
|||
} |
|||
}, |
|||
watch: { |
|||
'$route' (to) { |
|||
this.getTagElementByRoute(to) |
|||
}, |
|||
visible (value) { |
|||
if (value) { |
|||
document.body.addEventListener('click', this.closeMenu) |
|||
} else { |
|||
document.body.removeEventListener('click', this.closeMenu) |
|||
} |
|||
} |
|||
}, |
|||
mounted () { |
|||
setTimeout(() => { |
|||
this.getTagElementByRoute(this.$route) |
|||
}, 200) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less"> |
|||
@import './tags-nav.less'; |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
import User from './user.vue' |
|||
export default User |
|||
@ -0,0 +1,12 @@ |
|||
.user{ |
|||
&-avator-dropdown{ |
|||
cursor: pointer; |
|||
display: inline-block; |
|||
// height: 64px; |
|||
vertical-align: middle; |
|||
// line-height: 64px; |
|||
.ivu-badge-dot{ |
|||
top: 16px; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
<template> |
|||
<div class="user-avator-dropdown"> |
|||
<Dropdown @on-click="handleClick"> |
|||
<Badge> |
|||
<Avatar :src="userAvator"/> |
|||
</Badge> |
|||
<Icon :size="18" type="md-arrow-dropdown"></Icon> |
|||
<DropdownMenu slot="list"> |
|||
<DropdownItem name="user_center">个人中心</DropdownItem> |
|||
<DropdownItem name="logout">退出登录</DropdownItem> |
|||
</DropdownMenu> |
|||
</Dropdown> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import './user.less' |
|||
import { mapActions } from 'vuex' |
|||
export default { |
|||
name: 'User', |
|||
props: { |
|||
userAvator: { |
|||
type: String, |
|||
default: '' |
|||
} |
|||
}, |
|||
methods: { |
|||
...mapActions([ |
|||
'handleLogOut' |
|||
]), |
|||
logout () { |
|||
this.handleLogOut().then(() => { |
|||
this.$router.push({ |
|||
name: 'login' |
|||
}) |
|||
}) |
|||
}, |
|||
handleClick (name) { |
|||
switch (name) { |
|||
case 'logout': |
|||
this.logout() |
|||
break |
|||
case 'user_center': |
|||
this.$router.push({ |
|||
name: 'user_center' |
|||
}) |
|||
break |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,2 @@ |
|||
import Main from './main.vue' |
|||
export default Main |
|||
@ -0,0 +1,75 @@ |
|||
.main{ |
|||
.logo-con{ |
|||
height: 64px; |
|||
padding: 10px; |
|||
img{ |
|||
height: 44px; |
|||
width: auto; |
|||
display: block; |
|||
margin: 0 auto; |
|||
} |
|||
} |
|||
.header-con{ |
|||
background: #fff; |
|||
padding: 0 20px; |
|||
width: 100%; |
|||
} |
|||
.main-layout-con{ |
|||
height: 100%; |
|||
overflow: hidden; |
|||
} |
|||
.main-content-con{ |
|||
height: ~"calc(100% - 60px)"; |
|||
overflow: hidden; |
|||
} |
|||
.tag-nav-wrapper{ |
|||
padding: 0; |
|||
height:40px; |
|||
background:#F0F0F0; |
|||
} |
|||
.content-wrapper{ |
|||
padding: 18px; |
|||
height: ~"calc(100% - 80px)"; |
|||
overflow: auto; |
|||
} |
|||
.left-sider{ |
|||
.ivu-layout-sider-children{ |
|||
overflow-y: scroll; |
|||
margin-right: -18px; |
|||
} |
|||
} |
|||
} |
|||
.ivu-menu-item > i{ |
|||
margin-right: 12px !important; |
|||
} |
|||
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { |
|||
margin-right: 8px !important; |
|||
} |
|||
.collased-menu-dropdown{ |
|||
width: 100%; |
|||
margin: 0; |
|||
line-height: normal; |
|||
padding: 7px 0 6px 16px; |
|||
clear: both; |
|||
font-size: 12px !important; |
|||
white-space: nowrap; |
|||
list-style: none; |
|||
cursor: pointer; |
|||
transition: background 0.2s ease-in-out; |
|||
&:hover{ |
|||
background: rgba(100, 100, 100, 0.1); |
|||
} |
|||
& * { |
|||
color: #515a6e; |
|||
} |
|||
.ivu-menu-item > i{ |
|||
margin-right: 12px !important; |
|||
} |
|||
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { |
|||
margin-right: 8px !important; |
|||
} |
|||
} |
|||
|
|||
.ivu-select-dropdown.ivu-dropdown-transfer{ |
|||
max-height: 400px; |
|||
} |
|||
@ -0,0 +1,180 @@ |
|||
<template> |
|||
<Layout style="height: 100%" class="main"> |
|||
<Sider hide-trigger collapsible :width="256" :collapsed-width="64" v-model="collapsed" class="left-sider" :style="{overflow: 'hidden'}"> |
|||
<side-menu accordion ref="sideMenu" :active-name="$route.name" :collapsed="collapsed" @on-select="turnToPage" :menu-list="menuList"> |
|||
<!-- 需要放在菜单上面的内容,如Logo,写在side-menu标签内部,如下 --> |
|||
<div class="logo-con"> |
|||
<img v-show="!collapsed" :src="maxLogo" key="max-logo" /> |
|||
<img v-show="collapsed" :src="minLogo" key="min-logo" /> |
|||
</div> |
|||
</side-menu> |
|||
</Sider> |
|||
<Layout> |
|||
<Header class="header-con"> |
|||
<header-bar :collapsed="collapsed" @on-coll-change="handleCollapsedChange"> |
|||
<user :user-avator="userAvatar"/> |
|||
<language v-if="$config.useI18n" @on-lang-change="setLocal" style="margin-right: 10px;" :lang="local"/> |
|||
<fullscreen v-model="isFullscreen" style="margin-right: 10px;"/> |
|||
</header-bar> |
|||
</Header> |
|||
<Content class="main-content-con"> |
|||
<Layout class="main-layout-con"> |
|||
<div class="tag-nav-wrapper"> |
|||
<tags-nav :value="$route" @input="handleClick" :list="tagNavList" @on-close="handleCloseTag"/> |
|||
</div> |
|||
<Content class="content-wrapper"> |
|||
<keep-alive :include="cacheList"> |
|||
<router-view/> |
|||
</keep-alive> |
|||
<ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop> |
|||
</Content> |
|||
</Layout> |
|||
</Content> |
|||
</Layout> |
|||
</Layout> |
|||
</template> |
|||
<script> |
|||
import SideMenu from './components/side-menu' |
|||
import HeaderBar from './components/header-bar' |
|||
import TagsNav from './components/tags-nav' |
|||
import User from './components/user' |
|||
import ABackTop from './components/a-back-top' |
|||
import Fullscreen from './components/fullscreen' |
|||
import Language from './components/language' |
|||
import { mapMutations, mapActions } from 'vuex' |
|||
import { getNewTagList, routeEqual } from '@/libs/util' |
|||
import routers from '@/router/routers' |
|||
import minLogo from '@/assets/images/logo-min.jpg' |
|||
import maxLogo from '@/assets/images/logo.jpg' |
|||
import defaultImg from '@/assets/images/default-img.jpg' |
|||
import './main.less' |
|||
export default { |
|||
name: 'Main', |
|||
components: { |
|||
SideMenu, |
|||
HeaderBar, |
|||
Language, |
|||
TagsNav, |
|||
Fullscreen, |
|||
User, |
|||
ABackTop |
|||
}, |
|||
data () { |
|||
return { |
|||
collapsed: false, |
|||
minLogo, |
|||
maxLogo, |
|||
isFullscreen: false |
|||
} |
|||
}, |
|||
computed: { |
|||
tagNavList () { |
|||
return this.$store.state.app.tagNavList |
|||
}, |
|||
userAvatar () { |
|||
if (JSON.stringify(this.$store.state.user.userInfo) !== '{}') { |
|||
let userData = {} |
|||
if (this.$store.state.user.userInfo.hasOwnProperty('user_data') && !this.$store.state.user.userInfo.hasOwnProperty('userData')) { |
|||
userData = this.$store.state.user.userInfo.user_data |
|||
} else { |
|||
userData = this.$store.state.user.userInfo.userData |
|||
} |
|||
if (userData.head_img) { |
|||
return userData.head_img |
|||
} |
|||
} |
|||
return defaultImg |
|||
}, |
|||
cacheList () { |
|||
return ['ParentView', ...this.tagNavList.length ? this.tagNavList.filter(item => !(item.meta && item.meta.notCache)).map(item => item.name) : []] |
|||
}, |
|||
menuList () { |
|||
return this.$store.getters.menuList |
|||
}, |
|||
local () { |
|||
return this.$store.state.app.local |
|||
} |
|||
}, |
|||
methods: { |
|||
...mapMutations([ |
|||
'setBreadCrumb', |
|||
'setTagNavList', |
|||
'addTag', |
|||
'setLocal', |
|||
'setHomeRoute', |
|||
'closeTag' |
|||
]), |
|||
...mapActions([ |
|||
'handleLogin' |
|||
]), |
|||
turnToPage (route) { |
|||
let { name, params, query } = {} |
|||
if (typeof route === 'string') name = route |
|||
else { |
|||
name = route.name |
|||
params = route.params |
|||
query = route.query |
|||
} |
|||
if (name.indexOf('isTurnByHref_') > -1) { |
|||
window.open(name.split('_')[1]) |
|||
return |
|||
} |
|||
this.$router.push({ |
|||
name, |
|||
params, |
|||
query |
|||
}) |
|||
}, |
|||
handleCollapsedChange (state) { |
|||
this.collapsed = state |
|||
}, |
|||
handleCloseTag (res, type, route) { |
|||
if (type !== 'others') { |
|||
if (type === 'all') { |
|||
this.turnToPage('home') |
|||
} else { |
|||
if (routeEqual(this.$route, route)) { |
|||
this.closeTag(route) |
|||
} |
|||
} |
|||
} |
|||
this.setTagNavList(res) |
|||
}, |
|||
handleClick (item) { |
|||
this.turnToPage(item) |
|||
} |
|||
}, |
|||
watch: { |
|||
'$route' (newRoute) { |
|||
const { name, query, params, meta } = newRoute |
|||
this.addTag({ |
|||
route: { name, query, params, meta }, |
|||
type: 'push' |
|||
}) |
|||
this.setBreadCrumb(newRoute) |
|||
this.setTagNavList(getNewTagList(this.tagNavList, newRoute)) |
|||
this.$refs.sideMenu.updateOpenName(newRoute.name) |
|||
} |
|||
}, |
|||
mounted () { |
|||
/** |
|||
* @description 初始化设置面包屑导航和标签导航 |
|||
*/ |
|||
this.setTagNavList() |
|||
this.setHomeRoute(routers) |
|||
const { name, params, query, meta } = this.$route |
|||
this.addTag({ |
|||
route: { name, params, query, meta } |
|||
}) |
|||
this.setBreadCrumb(this.$route) |
|||
// 设置初始语言 |
|||
this.setLocal(this.$i18n.locale) |
|||
// 如果当前打开页面不在标签栏中,跳到homeName页 |
|||
if (!this.tagNavList.find(item => item.name === this.$route.name)) { |
|||
this.$router.push({ |
|||
name: 'home' |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,25 @@ |
|||
export default { |
|||
/** |
|||
* @description 配置显示在浏览器标签的title |
|||
*/ |
|||
title: 'ApiAdmin身边的接口管理专家', |
|||
/** |
|||
* @description 是否使用国际化,默认为false |
|||
* 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'} |
|||
* 用来在菜单中显示文字 |
|||
*/ |
|||
useI18n: false, |
|||
/** |
|||
* @description api请求基础路径 |
|||
*/ |
|||
baseUrl: { |
|||
dev: '', |
|||
pro: '' |
|||
}, |
|||
/** |
|||
* @description 需要加载的插件 |
|||
*/ |
|||
plugin: { |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
import draggable from './module/draggable' |
|||
import has from './module/has-permission' |
|||
|
|||
const directives = { |
|||
draggable, |
|||
has |
|||
} |
|||
|
|||
export default directives |
|||
@ -0,0 +1,34 @@ |
|||
import directive from './directives' |
|||
|
|||
const importDirective = Vue => { |
|||
/** |
|||
* 拖拽指令 v-draggable="options" |
|||
* options = { |
|||
* trigger: /这里传入作为拖拽触发器的CSS选择器/, |
|||
* body: /这里传入需要移动容器的CSS选择器/, |
|||
* recover: /拖动结束之后是否恢复到原来的位置/ |
|||
* } |
|||
*/ |
|||
Vue.directive('draggable', directive.draggable) |
|||
/** |
|||
* clipboard指令 v-draggable="options" |
|||
* options = { |
|||
* value: /在输入框中使用v-model绑定的值/, |
|||
* success: /复制成功后的回调/, |
|||
* error: /复制失败后的回调/ |
|||
* } |
|||
*/ |
|||
Vue.directive('clipboard', directive.clipboard) |
|||
|
|||
/** |
|||
* 代码高亮指令 |
|||
*/ |
|||
Vue.directive('highlight', directive.highlight) |
|||
|
|||
/** |
|||
* 判定是否有权限 |
|||
*/ |
|||
Vue.directive('has', directive.has) |
|||
} |
|||
|
|||
export default importDirective |
|||
@ -0,0 +1,41 @@ |
|||
import { on } from '@/libs/tools' |
|||
export default { |
|||
inserted: (el, binding, vnode) => { |
|||
let triggerDom = document.querySelector(binding.value.trigger) |
|||
triggerDom.style.cursor = 'move' |
|||
let bodyDom = document.querySelector(binding.value.body) |
|||
let pageX = 0 |
|||
let pageY = 0 |
|||
let transformX = 0 |
|||
let transformY = 0 |
|||
let canMove = false |
|||
const handleMousedown = e => { |
|||
let transform = /\(.*\)/.exec(bodyDom.style.transform) |
|||
if (transform) { |
|||
transform = transform[0].slice(1, transform[0].length - 1) |
|||
let splitxy = transform.split('px, ') |
|||
transformX = parseFloat(splitxy[0]) |
|||
transformY = parseFloat(splitxy[1].split('px')[0]) |
|||
} |
|||
pageX = e.pageX |
|||
pageY = e.pageY |
|||
canMove = true |
|||
} |
|||
const handleMousemove = e => { |
|||
let xOffset = e.pageX - pageX + transformX |
|||
let yOffset = e.pageY - pageY + transformY |
|||
if (canMove) bodyDom.style.transform = `translate(${xOffset}px, ${yOffset}px)` |
|||
} |
|||
const handleMouseup = e => { |
|||
canMove = false |
|||
} |
|||
on(triggerDom, 'mousedown', handleMousedown) |
|||
on(document, 'mousemove', handleMousemove) |
|||
on(document, 'mouseup', handleMouseup) |
|||
}, |
|||
update: (el, binding, vnode) => { |
|||
if (!binding.value.recover) return |
|||
let bodyDom = document.querySelector(binding.value.body) |
|||
bodyDom.style.transform = '' |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
import store from '@/store' |
|||
|
|||
export default { |
|||
inserted: (el, binding, vnode) => { |
|||
store.dispatch('getUserInfo').then(user => { |
|||
let myParent = el.parentNode |
|||
if (user.access && !user.access.includes('admin/' + binding.value)) { |
|||
myParent.removeChild(el) |
|||
} |
|||
if (myParent.childNodes) { |
|||
if (myParent.childNodes.length === 0) { |
|||
myParent.parentNode.removeChild(myParent) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
@import '~view-design/src/styles/index.less'; |
|||
|
|||
@menu-dark-title: #001529; |
|||
@menu-dark-active-bg: #000c17; |
|||
@layout-sider-background: #001529; |
|||
|
|||
.margin-loop (@i) when (@i > 0) { |
|||
.margin-top-@{i} { |
|||
margin-top: ~"@{i}px"; |
|||
} |
|||
.margin-bottom-@{i} { |
|||
margin-bottom: ~"@{i}px"; |
|||
} |
|||
.margin-left-@{i} { |
|||
margin-left: ~"@{i}px"; |
|||
} |
|||
.margin-right-@{i} { |
|||
margin-right: ~"@{i}px"; |
|||
} |
|||
.margin-loop(@i - 5); |
|||
} |
|||
.margin-loop (25); |
|||
|
|||
.margin-bottom-0 { |
|||
margin-bottom: 0 |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
import config from '@/config' |
|||
import axios from 'axios' |
|||
import iView from 'view-design' |
|||
import { setToken, getToken } from '@/libs/util' |
|||
import router from '@/router' |
|||
|
|||
export const baseUrl = (process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro) + 'admin/' |
|||
|
|||
class HttpRequest { |
|||
constructor (baseUrl) { |
|||
this.baseUrl = baseUrl |
|||
} |
|||
|
|||
interceptors (instance) { |
|||
// 请求拦截
|
|||
instance.interceptors.request.use(config => { |
|||
return config |
|||
}, error => { |
|||
return Promise.reject(error) |
|||
}) |
|||
// 响应拦截
|
|||
instance.interceptors.response.use(res => { |
|||
const { data, status } = res |
|||
if (data.code < 0) { |
|||
if (data.code === -14) { |
|||
setToken('') |
|||
router.push({ name: 'login' }) |
|||
} else { |
|||
iView.Message.error(data.msg) |
|||
} |
|||
throw new Error(data.msg) |
|||
} else { |
|||
return { data, status } |
|||
} |
|||
}, error => { |
|||
return Promise.reject(error) |
|||
}) |
|||
} |
|||
|
|||
request (options) { |
|||
const instance = axios.create() |
|||
let apiAuth = getToken() |
|||
if (apiAuth === false) { |
|||
options = Object.assign({ |
|||
baseURL: this.baseUrl, |
|||
headers: {} |
|||
}, options) |
|||
} else { |
|||
options = Object.assign({ |
|||
baseURL: this.baseUrl, |
|||
headers: { |
|||
'Api-Auth': apiAuth |
|||
} |
|||
}, options) |
|||
} |
|||
this.interceptors(instance) |
|||
return instance(options) |
|||
} |
|||
} |
|||
|
|||
const api_axios = new HttpRequest(baseUrl) |
|||
export default api_axios |
|||
@ -0,0 +1 @@ |
|||
export default (url) => () => import(`@/view/${url}.vue`) |
|||
@ -0,0 +1,47 @@ |
|||
/** |
|||
* ①添 |
|||
* @@新增 定义初始化菜单 |
|||
*/ |
|||
import lazyLoading from './lazy-loading' |
|||
import Main from '@/components/main' // Main 是架构组件,不在后台返回,在文件里单独引入
|
|||
|
|||
// 加载路由菜单,从localStorage拿到路由,在创建路由时使用
|
|||
export const dynamicRouterAdd = () => { |
|||
let data = sessionStorage.getItem('dynamicRouter') |
|||
if (!data) { |
|||
return [] |
|||
} |
|||
return filterAsyncRouter(JSON.parse(data)) |
|||
} |
|||
|
|||
// @函数: 遍历后台传来的路由字符串,转换为组件对象
|
|||
export const filterAsyncRouter = (asyncRouterMap) => { |
|||
let accessedRouters = [] |
|||
if (asyncRouterMap) { |
|||
asyncRouterMap.filter(route => { |
|||
if (route.show === 1) { |
|||
let accessedRouter = {} |
|||
accessedRouter.path = route.router |
|||
accessedRouter.name = route.title |
|||
accessedRouter.meta = { |
|||
icon: route.icon, |
|||
title: route.title, |
|||
hideInMenu: false |
|||
} |
|||
if (route.component === '') { |
|||
accessedRouter.component = Main |
|||
} else { |
|||
if (route.component === 'interface/request' || route.component === 'interface/response') { |
|||
accessedRouter.meta.hideInMenu = true |
|||
} |
|||
accessedRouter.component = lazyLoading(route.component) |
|||
} |
|||
if (route.children && route.children.length) { |
|||
accessedRouter.children = filterAsyncRouter(route.children) |
|||
} |
|||
accessedRouters.push(accessedRouter) |
|||
} |
|||
}) |
|||
} |
|||
return accessedRouters |
|||
} |
|||
@ -0,0 +1,307 @@ |
|||
export const forEach = (arr, fn) => { |
|||
if (!arr.length || !fn) return |
|||
let i = -1 |
|||
let len = arr.length |
|||
while (++i < len) { |
|||
let item = arr[i] |
|||
fn(item, i, arr) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} arr1 |
|||
* @param {Array} arr2 |
|||
* @description 得到两个数组的交集, 两个数组的元素为数值或字符串 |
|||
*/ |
|||
export const getIntersection = (arr1, arr2) => { |
|||
let len = Math.min(arr1.length, arr2.length) |
|||
let i = -1 |
|||
let res = [] |
|||
while (++i < len) { |
|||
const item = arr2[i] |
|||
if (arr1.indexOf(item) > -1) res.push(item) |
|||
} |
|||
return res |
|||
} |
|||
|
|||
/** |
|||
* 判断当前变量是不是数组 |
|||
* @param param |
|||
* @returns {arg is Array<any>|boolean} |
|||
*/ |
|||
export const isArr = (param) => { |
|||
if (typeof Array.isArray === 'function') { |
|||
return Array.isArray(param) |
|||
} else { |
|||
return Object.prototype.toString.call(param) === '[object Array]' |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} arr1 |
|||
* @param {Array} arr2 |
|||
* @description 得到两个数组的并集, 两个数组的元素为数值或字符串 |
|||
*/ |
|||
export const getUnion = (arr1, arr2) => { |
|||
return Array.from(new Set([...arr1, ...arr2])) |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} target 目标数组 |
|||
* @param {Array} arr 需要查询的数组 |
|||
* @description 判断要查询的数组是否至少有一个元素包含在目标数组中 |
|||
*/ |
|||
export const hasOneOf = (targetarr, arr) => { |
|||
return targetarr.some(_ => arr.indexOf(_) > -1) |
|||
} |
|||
|
|||
/** |
|||
* @param {String|Number} value 要验证的字符串或数值 |
|||
* @param {*} validList 用来验证的列表 |
|||
*/ |
|||
export const oneOf = (value, validList) => { |
|||
for (let i = 0; i < validList.length; i++) { |
|||
if (value === validList[i]) { |
|||
return true |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
/** |
|||
* 和PHP一样的时间戳格式化函数 |
|||
* @param {string} format 格式 |
|||
* @param {int} timestamp 要格式化的时间 默认为当前时间 |
|||
* @return {string} 格式化的时间字符串 |
|||
*/ |
|||
export const formatDate = (format, timestamp) => { |
|||
let jsDate = ((timestamp) ? new Date(timestamp * 1000) : new Date()) |
|||
let pad = function (n, c) { |
|||
if ((n = n + '').length < c) { |
|||
return new Array(++c - n.length).join('0') + n |
|||
} else { |
|||
return n |
|||
} |
|||
} |
|||
let txt_weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] |
|||
let txt_ord_in = { 1: 'st', 2: 'nd', 3: 'rd', 21: 'st', 22: 'nd', 23: 'rd', 31: 'st' } |
|||
let txt_months = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] |
|||
let f = { |
|||
d: function () { |
|||
return pad(f.j(), 2) |
|||
}, |
|||
D: function () { |
|||
return f.l().substr(0, 3) |
|||
}, |
|||
j: function () { |
|||
return jsDate.getDate() |
|||
}, |
|||
l: function () { |
|||
return txt_weekdays[f.w()] |
|||
}, |
|||
N: function () { |
|||
return f.w() + 1 |
|||
}, |
|||
S: function () { |
|||
return txt_ord_in[f.j()] ? txt_ord_in[f.j()] : 'th' |
|||
}, |
|||
w: function () { |
|||
return jsDate.getDay() |
|||
}, |
|||
z: function () { |
|||
return (jsDate - new Date(jsDate.getFullYear() + '/1/1')) / 864e5 >> 0 |
|||
}, |
|||
W: function () { |
|||
let a = f.z() |
|||
let b = 364 + f.L() - a |
|||
let nd2 |
|||
let nd = (new Date(jsDate.getFullYear() + '/1/1').getDay() || 7) - 1 |
|||
if (b <= 2 && ((jsDate.getDay() || 7) - 1) <= 2 - b) { |
|||
return 1 |
|||
} else { |
|||
if (a <= 2 && nd >= 4 && a >= (6 - nd)) { |
|||
nd2 = new Date(jsDate.getFullYear() - 1 + '/12/31') |
|||
return date('W', Math.round(nd2.getTime() / 1000)) |
|||
} else { |
|||
return (1 + (nd <= 3 ? ((a + nd) / 7) : (a - (7 - nd)) / 7) >> 0) |
|||
} |
|||
} |
|||
}, |
|||
F: function () { |
|||
return txt_months[f.n()] |
|||
}, |
|||
m: function () { |
|||
return pad(f.n(), 2) |
|||
}, |
|||
M: function () { |
|||
return f.F().substr(0, 3) |
|||
}, |
|||
n: function () { |
|||
return jsDate.getMonth() + 1 |
|||
}, |
|||
t: function () { |
|||
let n |
|||
if ((n = jsDate.getMonth() + 1) === 2) { |
|||
return 28 + f.L() |
|||
} else { |
|||
// eslint-disable-next-line no-mixed-operators
|
|||
if (n & 1 && n < 8 || !(n & 1) && n > 7) { |
|||
return 31 |
|||
} else { |
|||
return 30 |
|||
} |
|||
} |
|||
}, |
|||
L: function () { |
|||
let y = f.Y() |
|||
return (!(y & 3) && (y % 1e2 || !(y % 4e2))) ? 1 : 0 |
|||
}, |
|||
Y: function () { |
|||
return jsDate.getFullYear() |
|||
}, |
|||
y: function () { |
|||
return (jsDate.getFullYear() + '').slice(2) |
|||
}, |
|||
a: function () { |
|||
return jsDate.getHours() > 11 ? 'pm' : 'am' |
|||
}, |
|||
A: function () { |
|||
return f.a().toUpperCase() |
|||
}, |
|||
B: function () { |
|||
let off = (jsDate.getTimezoneOffset() + 60) * 60 |
|||
let theSeconds = (jsDate.getHours() * 3600) + (jsDate.getMinutes() * 60) + jsDate.getSeconds() + off |
|||
let beat = Math.floor(theSeconds / 86.4) |
|||
if (beat > 1000) beat -= 1000 |
|||
if (beat < 0) beat += 1000 |
|||
if ((String(beat)).length === 1) beat = '00' + beat |
|||
if ((String(beat)).length === 2) beat = '0' + beat |
|||
return beat |
|||
}, |
|||
g: function () { |
|||
return jsDate.getHours() % 12 || 12 |
|||
}, |
|||
G: function () { |
|||
return jsDate.getHours() |
|||
}, |
|||
h: function () { |
|||
return pad(f.g(), 2) |
|||
}, |
|||
H: function () { |
|||
return pad(jsDate.getHours(), 2) |
|||
}, |
|||
i: function () { |
|||
return pad(jsDate.getMinutes(), 2) |
|||
}, |
|||
s: function () { |
|||
return pad(jsDate.getSeconds(), 2) |
|||
}, |
|||
O: function () { |
|||
let t = pad(Math.abs(jsDate.getTimezoneOffset() / 60 * 100), 4) |
|||
if (jsDate.getTimezoneOffset() > 0) t = '-' + t; else t = '+' + t |
|||
return t |
|||
}, |
|||
P: function () { |
|||
let O = f.O() |
|||
return (O.substr(0, 3) + ':' + O.substr(3, 2)) |
|||
}, |
|||
c: function () { |
|||
return f.Y() + '-' + f.m() + '-' + f.d() + 'T' + f.h() + ':' + f.i() + ':' + f.s() + f.P() |
|||
}, |
|||
U: function () { |
|||
return Math.round(jsDate.getTime() / 1000) |
|||
} |
|||
} |
|||
|
|||
let exp = /[\\]?([a-zA-Z])/g |
|||
return format.replace(exp, function (t, s) { |
|||
let ret = '' |
|||
if (t !== s) { |
|||
ret = s |
|||
} else if (f[s]) { |
|||
ret = f[s]() |
|||
} else { |
|||
ret = s |
|||
} |
|||
return ret |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* @returns {String} 当前浏览器名称 |
|||
*/ |
|||
export const getExplorer = () => { |
|||
const ua = window.navigator.userAgent |
|||
const isExplorer = (exp) => { |
|||
return ua.indexOf(exp) > -1 |
|||
} |
|||
if (isExplorer('MSIE')) return 'IE' |
|||
else if (isExplorer('Firefox')) return 'Firefox' |
|||
else if (isExplorer('Chrome')) return 'Chrome' |
|||
else if (isExplorer('Opera')) return 'Opera' |
|||
else if (isExplorer('Safari')) return 'Safari' |
|||
} |
|||
|
|||
/** |
|||
* @description 绑定事件 on(element, event, handler) |
|||
*/ |
|||
export const on = (function () { |
|||
if (document.addEventListener) { |
|||
return function (element, event, handler) { |
|||
if (element && event && handler) { |
|||
element.addEventListener(event, handler, false) |
|||
} |
|||
} |
|||
} else { |
|||
return function (element, event, handler) { |
|||
if (element && event && handler) { |
|||
element.attachEvent('on' + event, handler) |
|||
} |
|||
} |
|||
} |
|||
})() |
|||
|
|||
/** |
|||
* @description 解绑事件 off(element, event, handler) |
|||
*/ |
|||
export const off = (function () { |
|||
if (document.removeEventListener) { |
|||
return function (element, event, handler) { |
|||
if (element && event) { |
|||
element.removeEventListener(event, handler, false) |
|||
} |
|||
} |
|||
} else { |
|||
return function (element, event, handler) { |
|||
if (element && event) { |
|||
element.detachEvent('on' + event, handler) |
|||
} |
|||
} |
|||
} |
|||
})() |
|||
|
|||
/** |
|||
* 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性 |
|||
* 如果没有传入key这个参数,则判断obj对象是否有键值对 |
|||
*/ |
|||
export const hasKey = (obj, key) => { |
|||
if (key) return key in obj |
|||
else { |
|||
let keysArr = Object.keys(obj) |
|||
return keysArr.length |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {*} obj1 对象 |
|||
* @param {*} obj2 对象 |
|||
* @description 判断两个对象是否相等,这两个对象的值只能是数字或字符串 |
|||
*/ |
|||
export const objEqual = (obj1, obj2) => { |
|||
const keysArr1 = Object.keys(obj1) |
|||
const keysArr2 = Object.keys(obj2) |
|||
if (keysArr1.length !== keysArr2.length) return false |
|||
else if (keysArr1.length === 0 && keysArr2.length === 0) return true |
|||
/* eslint-disable-next-line */ |
|||
else return !keysArr1.some(key => obj1[key] != obj2[key]) |
|||
} |
|||
@ -0,0 +1,384 @@ |
|||
import config from '@/config' |
|||
import { forEach, hasOneOf, oneOf, objEqual, isArr } from '@/libs/tools' |
|||
|
|||
const { title, useI18n } = config |
|||
|
|||
export const TOKEN_KEY = 'ApiAdmin_' |
|||
|
|||
export const setToken = (token) => { |
|||
sessionStorage.setItem(TOKEN_KEY + 'ApiAuth', token) |
|||
} |
|||
|
|||
export const getToken = () => { |
|||
const token = sessionStorage.getItem(TOKEN_KEY + 'ApiAuth') |
|||
if (token) { |
|||
return token |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
export const hasChild = (item) => { |
|||
return item.children && item.children.length !== 0 |
|||
} |
|||
|
|||
const showThisMenuEle = (item, access) => { |
|||
if (item.meta && item.meta.access && access) { |
|||
if (isArr(item.meta.access)) { |
|||
if (hasOneOf(item.meta.access, access)) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
} else { |
|||
if (oneOf(item.meta.access, access)) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
} else { |
|||
return true |
|||
} |
|||
} |
|||
/** |
|||
* @param {Array} list 通过路由列表得到菜单列表 |
|||
* @returns {Array} |
|||
*/ |
|||
export const getMenuByRouter = (list, access) => { |
|||
let res = [] |
|||
forEach(list, item => { |
|||
if (!item.meta || (item.meta && !item.meta.hideInMenu)) { |
|||
let obj = { |
|||
icon: (item.meta && item.meta.icon) || '', |
|||
name: item.name, |
|||
meta: item.meta |
|||
} |
|||
if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) { |
|||
obj.children = getMenuByRouter(item.children, access) |
|||
} |
|||
if (item.meta && item.meta.href) obj.href = item.meta.href |
|||
if (showThisMenuEle(item, access)) res.push(obj) |
|||
} |
|||
}) |
|||
return res |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} routeMetched 当前路由metched |
|||
* @returns {Array} |
|||
*/ |
|||
export const getBreadCrumbList = (route, homeRoute) => { |
|||
let homeItem = { ...homeRoute, icon: homeRoute.meta.icon } |
|||
let routeMetched = route.matched |
|||
if (routeMetched.some(item => item.name === homeRoute.name)) return [homeItem] |
|||
let res = routeMetched.filter(item => { |
|||
return item.meta === undefined || !item.meta.hideInBread |
|||
}).map(item => { |
|||
let meta = { ...item.meta } |
|||
if (meta.title && typeof meta.title === 'function') { |
|||
meta.__titleIsFunction__ = true |
|||
meta.title = meta.title(route) |
|||
} |
|||
let obj = { |
|||
icon: (item.meta && item.meta.icon) || '', |
|||
name: item.name, |
|||
meta: meta |
|||
} |
|||
return obj |
|||
}) |
|||
res = res.filter(item => { |
|||
return !item.meta.hideInMenu |
|||
}) |
|||
return [{ ...homeItem, to: homeRoute.path }, ...res] |
|||
} |
|||
|
|||
export const getRouteTitleHandled = (route) => { |
|||
let router = { ...route } |
|||
let meta = { ...route.meta } |
|||
let title = '' |
|||
if (meta.title) { |
|||
if (typeof meta.title === 'function') { |
|||
meta.__titleIsFunction__ = true |
|||
title = meta.title(router) |
|||
} else title = meta.title |
|||
} |
|||
meta.title = title |
|||
router.meta = meta |
|||
return router |
|||
} |
|||
|
|||
export const showTitle = (item, vm) => { |
|||
let { title, __titleIsFunction__ } = item.meta |
|||
if (!title) return |
|||
if (useI18n) { |
|||
if (title.includes('{{') && title.includes('}}') && useI18n) title = title.replace(/({{[\s\S]+?}})/, (m, str) => str.replace(/{{([\s\S]*)}}/, (m, _) => vm.$t(_.trim()))) |
|||
else if (__titleIsFunction__) title = item.meta.title |
|||
else title = vm.$t(item.name) |
|||
} else title = (item.meta && item.meta.title) || item.name |
|||
return title |
|||
} |
|||
|
|||
/** |
|||
* @description 本地存储和获取标签导航列表 |
|||
*/ |
|||
export const setTagNavListInLocalstorage = list => { |
|||
localStorage.tagNaveList = JSON.stringify(list) |
|||
} |
|||
/** |
|||
* @returns {Array} 其中的每个元素只包含路由原信息中的name, path, meta三项 |
|||
*/ |
|||
export const getTagNavListFromLocalstorage = () => { |
|||
const list = localStorage.tagNaveList |
|||
return list ? JSON.parse(list) : [] |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} routers 路由列表数组 |
|||
* @description 用于找到路由列表中name为home的对象 |
|||
*/ |
|||
export const getHomeRoute = (routers) => { |
|||
let i = -1 |
|||
let len = routers.length |
|||
let homeRoute = {} |
|||
while (++i < len) { |
|||
let item = routers[i] |
|||
if (item.children && item.children.length) { |
|||
let res = getHomeRoute(item.children, 'home') |
|||
if (res.name) return res |
|||
} else { |
|||
if (item.name === 'home') homeRoute = item |
|||
} |
|||
} |
|||
return homeRoute |
|||
} |
|||
|
|||
/** |
|||
* @param {*} list 现有标签导航列表 |
|||
* @param {*} newRoute 新添加的路由原信息对象 |
|||
* @description 如果该newRoute已经存在则不再添加 |
|||
*/ |
|||
export const getNewTagList = (list, newRoute) => { |
|||
const { name, path, meta } = newRoute |
|||
let newList = [...list] |
|||
if (newList.findIndex(item => item.name === name) >= 0) return newList |
|||
else newList.push({ name, path, meta }) |
|||
return newList |
|||
} |
|||
|
|||
/** |
|||
* @param {String} url |
|||
* @description 从URL中解析参数 |
|||
*/ |
|||
export const getParams = url => { |
|||
const keyValueArr = url.split('?')[1].split('&') |
|||
let paramObj = {} |
|||
keyValueArr.forEach(item => { |
|||
const keyValue = item.split('=') |
|||
paramObj[keyValue[0]] = keyValue[1] |
|||
}) |
|||
return paramObj |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} list 标签列表 |
|||
* @param {String} name 当前关闭的标签的name |
|||
*/ |
|||
export const getNextRoute = (list, route) => { |
|||
let res = {} |
|||
if (list.length === 2) { |
|||
res = getHomeRoute(list) |
|||
} else { |
|||
const index = list.findIndex(item => routeEqual(item, route)) |
|||
if (index === list.length - 1) res = list[list.length - 2] |
|||
else res = list[index + 1] |
|||
} |
|||
return res |
|||
} |
|||
|
|||
/** |
|||
* @param {Number} times 回调函数需要执行的次数 |
|||
* @param {Function} callback 回调函数 |
|||
*/ |
|||
export const doCustomTimes = (times, callback) => { |
|||
let i = -1 |
|||
while (++i < times) { |
|||
callback(i) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {Object} file 从上传组件得到的文件对象 |
|||
* @returns {Promise} resolve参数是解析后的二维数组 |
|||
* @description 从Csv文件中解析出表格,解析成二维数组 |
|||
*/ |
|||
export const getArrayFromFile = (file) => { |
|||
let nameSplit = file.name.split('.') |
|||
let format = nameSplit[nameSplit.length - 1] |
|||
return new Promise((resolve, reject) => { |
|||
let reader = new FileReader() |
|||
reader.readAsText(file) // 以文本格式读取
|
|||
let arr = [] |
|||
reader.onload = function (evt) { |
|||
let data = evt.target.result // 读到的数据
|
|||
let pasteData = data.trim() |
|||
arr = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => { |
|||
return row.split('\t') |
|||
}).map(item => { |
|||
return item[0].split(',') |
|||
}) |
|||
if (format === 'csv') resolve(arr) |
|||
else reject(new Error('[Format Error]:你上传的不是Csv文件')) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* @param {Array} array 表格数据二维数组 |
|||
* @returns {Object} { columns, tableData } |
|||
* @description 从二维数组中获取表头和表格数据,将第一行作为表头,用于在iView的表格中展示数据 |
|||
*/ |
|||
export const getTableDataFromArray = (array) => { |
|||
let columns = [] |
|||
let tableData = [] |
|||
if (array.length > 1) { |
|||
let titles = array.shift() |
|||
columns = titles.map(item => { |
|||
return { |
|||
title: item, |
|||
key: item |
|||
} |
|||
}) |
|||
tableData = array.map(item => { |
|||
let res = {} |
|||
item.forEach((col, i) => { |
|||
res[titles[i]] = col |
|||
}) |
|||
return res |
|||
}) |
|||
} |
|||
return { |
|||
columns, |
|||
tableData |
|||
} |
|||
} |
|||
|
|||
export const findNodeUpper = (ele, tag) => { |
|||
if (ele.parentNode) { |
|||
if (ele.parentNode.tagName === tag.toUpperCase()) { |
|||
return ele.parentNode |
|||
} else { |
|||
return findNodeUpper(ele.parentNode, tag) |
|||
} |
|||
} |
|||
} |
|||
|
|||
export const findNodeUpperByClasses = (ele, classes) => { |
|||
let parentNode = ele.parentNode |
|||
if (parentNode) { |
|||
let classList = parentNode.classList |
|||
if (classList && classes.every(className => classList.contains(className))) { |
|||
return parentNode |
|||
} else { |
|||
return findNodeUpperByClasses(parentNode, classes) |
|||
} |
|||
} |
|||
} |
|||
|
|||
export const findNodeDownward = (ele, tag) => { |
|||
const tagName = tag.toUpperCase() |
|||
if (ele.childNodes.length) { |
|||
let i = -1 |
|||
let len = ele.childNodes.length |
|||
while (++i < len) { |
|||
let child = ele.childNodes[i] |
|||
if (child.tagName === tagName) return child |
|||
else return findNodeDownward(child, tag) |
|||
} |
|||
} |
|||
} |
|||
|
|||
export const showByAccess = (access, canViewAccess) => { |
|||
return hasOneOf(canViewAccess, access) |
|||
} |
|||
|
|||
/** |
|||
* @description 根据name/params/query判断两个路由对象是否相等 |
|||
* @param {*} route1 路由对象 |
|||
* @param {*} route2 路由对象 |
|||
*/ |
|||
export const routeEqual = (route1, route2) => { |
|||
const params1 = route1.params || {} |
|||
const params2 = route2.params || {} |
|||
const query1 = route1.query || {} |
|||
const query2 = route2.query || {} |
|||
return (route1.name === route2.name) && objEqual(params1, params2) && objEqual(query1, query2) |
|||
} |
|||
|
|||
/** |
|||
* 判断打开的标签列表里是否已存在这个新添加的路由对象 |
|||
*/ |
|||
export const routeHasExist = (tagNavList, routeItem) => { |
|||
let len = tagNavList.length |
|||
let res = false |
|||
doCustomTimes(len, (index) => { |
|||
if (routeEqual(tagNavList[index], routeItem)) res = true |
|||
}) |
|||
return res |
|||
} |
|||
|
|||
export const localSave = (key, value) => { |
|||
localStorage.setItem(key, value) |
|||
} |
|||
|
|||
export const localRead = (key) => { |
|||
return localStorage.getItem(key) || '' |
|||
} |
|||
|
|||
// scrollTop animation
|
|||
export const scrollTop = (el, from = 0, to, duration = 500, endCallback) => { |
|||
if (!window.requestAnimationFrame) { |
|||
window.requestAnimationFrame = ( |
|||
window.webkitRequestAnimationFrame || |
|||
window.mozRequestAnimationFrame || |
|||
window.msRequestAnimationFrame || |
|||
function (callback) { |
|||
return window.setTimeout(callback, 1000 / 60) |
|||
} |
|||
) |
|||
} |
|||
const difference = Math.abs(from - to) |
|||
const step = Math.ceil(difference / duration * 50) |
|||
|
|||
const scroll = (start, end, step) => { |
|||
if (start === end) { |
|||
endCallback && endCallback() |
|||
return |
|||
} |
|||
|
|||
let d = (start + step > end) ? end : start + step |
|||
if (start > end) { |
|||
d = (start - step < end) ? end : start - step |
|||
} |
|||
|
|||
if (el === window) { |
|||
window.scrollTo(d, d) |
|||
} else { |
|||
el.scrollTop = d |
|||
} |
|||
window.requestAnimationFrame(() => scroll(d, end, step)) |
|||
} |
|||
scroll(from, to, step) |
|||
} |
|||
|
|||
/** |
|||
* @description 根据当前跳转的路由设置显示在浏览器标签的title |
|||
* @param {Object} routeItem 路由对象 |
|||
* @param {Object} vm Vue实例 |
|||
*/ |
|||
export const setTitle = (routeItem, vm) => { |
|||
const handledRoute = getRouteTitleHandled(routeItem) |
|||
const pageTitle = showTitle(handledRoute, vm) |
|||
const resTitle = pageTitle ? `${title} - ${pageTitle}` : title |
|||
window.document.title = resTitle |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
import config from '@/config' |
|||
import axios from 'axios' |
|||
import iView from 'view-design' |
|||
import { setToken, getToken } from '@/libs/util' |
|||
import router from '@/router' |
|||
|
|||
export const baseUrl = (process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro) + 'wiki/' |
|||
|
|||
class HttpRequest { |
|||
constructor (baseUrl) { |
|||
this.baseUrl = baseUrl |
|||
} |
|||
|
|||
interceptors (instance) { |
|||
// 请求拦截
|
|||
instance.interceptors.request.use(config => { |
|||
return config |
|||
}, error => { |
|||
return Promise.reject(error) |
|||
}) |
|||
// 响应拦截
|
|||
instance.interceptors.response.use(res => { |
|||
const { data, status } = res |
|||
if (data.code !== 1) { |
|||
if (data.code === -14) { |
|||
setToken('') |
|||
router.push({ name: 'wiki_login' }) |
|||
} else { |
|||
iView.Message.error(data.msg) |
|||
} |
|||
throw new Error(data.msg) |
|||
} else { |
|||
return { data, status } |
|||
} |
|||
}, error => { |
|||
return Promise.reject(error) |
|||
}) |
|||
} |
|||
|
|||
request (options) { |
|||
const instance = axios.create() |
|||
let apiAuth = getToken() |
|||
if (apiAuth === false) { |
|||
options = Object.assign({ |
|||
baseURL: this.baseUrl, |
|||
headers: {} |
|||
}, options) |
|||
} else { |
|||
options = Object.assign({ |
|||
baseURL: this.baseUrl, |
|||
headers: { |
|||
'Api-Auth': apiAuth |
|||
} |
|||
}, options) |
|||
} |
|||
this.interceptors(instance) |
|||
return instance(options) |
|||
} |
|||
} |
|||
|
|||
const api_axios = new HttpRequest(baseUrl) |
|||
export default api_axios |
|||
@ -0,0 +1,37 @@ |
|||
import Vue from 'vue' |
|||
import VueI18n from 'vue-i18n' |
|||
import { localRead } from '@/libs/util' |
|||
import customZhCn from './lang/zh-CN' |
|||
import customZhTw from './lang/zh-TW' |
|||
import customEnUs from './lang/en-US' |
|||
import zhCnLocale from 'view-design/src/locale/lang/zh-CN' |
|||
import enUsLocale from 'view-design/src/locale/lang/en-US' |
|||
import zhTwLocale from 'view-design/src/locale/lang/zh-TW' |
|||
|
|||
Vue.use(VueI18n) |
|||
|
|||
// 自动根据浏览器系统语言设置语言
|
|||
const navLang = navigator.language |
|||
const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false |
|||
let lang = localLang || localRead('local') || 'zh-CN' |
|||
|
|||
Vue.config.lang = lang |
|||
|
|||
// vue-i18n 6.x+写法
|
|||
Vue.locale = () => {} |
|||
const messages = { |
|||
'zh-CN': Object.assign(zhCnLocale, customZhCn), |
|||
'zh-TW': Object.assign(zhTwLocale, customZhTw), |
|||
'en-US': Object.assign(enUsLocale, customEnUs) |
|||
} |
|||
const i18n = new VueI18n({ |
|||
locale: lang, |
|||
messages |
|||
}) |
|||
|
|||
export default i18n |
|||
|
|||
// vue-i18n 5.x写法
|
|||
// Vue.locale('zh-CN', Object.assign(zhCnLocale, customZhCn))
|
|||
// Vue.locale('en-US', Object.assign(zhTwLocale, customZhTw))
|
|||
// Vue.locale('zh-TW', Object.assign(enUsLocale, customEnUs))
|
|||
@ -0,0 +1,31 @@ |
|||
export default { |
|||
home: 'Home', |
|||
login: 'Login', |
|||
system_setting: 'System Setting', |
|||
menu_setting: 'Menu Setting', |
|||
user_setting: 'User Setting', |
|||
auth_setting: 'Auth Setting', |
|||
logs: 'Logs', |
|||
apps_setting: 'Apps Setting', |
|||
apps_group: 'Apps Group', |
|||
apps_list: 'Apps List', |
|||
interface_setting: 'Interface Setting', |
|||
interface_group: 'Interface Group', |
|||
interface_list: 'Interface List', |
|||
add_button: 'Add', |
|||
find_button: 'Find', |
|||
refresh_button: 'Refresh', |
|||
delete_button: 'Delete', |
|||
edit_button: 'Edit', |
|||
open_choose: 'Open', |
|||
close_choose: 'Close', |
|||
show_choose: 'Show', |
|||
hide_choose: 'Hide', |
|||
interface_request: 'Request Params', |
|||
interface_response: 'Response Params', |
|||
user_center: 'User Center', |
|||
wiki_list: 'Wiki Center', |
|||
wiki_error: 'Wiki Error', |
|||
wiki_calculation: 'Wiki Calculation', |
|||
wiki_login: 'Wiki Login' |
|||
} |
|||