Browse Source

项目初始化

master
453530270@qq.com 1 year ago
commit
286e76c130
  1. 5
      .babelrc
  2. 9
      .editorconfig
  3. 0
      .eslintignore
  4. 21
      .eslintrc.js
  5. 26
      .gitignore
  6. 5
      .postcssrc.js
  7. 5
      .travis.yml
  8. 50
      DISCLAIMER.md
  9. 21
      LICENSE
  10. 94
      README.md
  11. 3
      cypress.json
  12. 54
      package.json
  13. BIN
      public/favicon.ico
  14. 23
      public/index.html
  15. 27
      src/App.vue
  16. 73
      src/api/app-group.js
  17. 91
      src/api/app.js
  18. 113
      src/api/auth.js
  19. 73
      src/api/fields.js
  20. 78
      src/api/interface-group.js
  21. 88
      src/api/interface.js
  22. 23
      src/api/log.js
  23. 75
      src/api/menu.js
  24. 51
      src/api/third-login.js
  25. 140
      src/api/user.js
  26. 58
      src/api/wiki.js
  27. 37
      src/assets/icons/iconfont.css
  28. BIN
      src/assets/icons/iconfont.eot
  29. 56
      src/assets/icons/iconfont.svg
  30. BIN
      src/assets/icons/iconfont.ttf
  31. BIN
      src/assets/icons/iconfont.woff
  32. BIN
      src/assets/images/default-img.jpg
  33. 1
      src/assets/images/error-page/error-401.svg
  34. 1
      src/assets/images/error-page/error-404.svg
  35. 1
      src/assets/images/error-page/error-500.svg
  36. BIN
      src/assets/images/login-bg.jpg
  37. 17
      src/assets/images/login-wiki.svg
  38. BIN
      src/assets/images/logo-min.jpg
  39. BIN
      src/assets/images/logo.jpg
  40. BIN
      src/assets/images/qq-login.png
  41. BIN
      src/assets/images/wx-login.png
  42. 58
      src/components/charts/bar.vue
  43. 3
      src/components/charts/index.js
  44. 70
      src/components/charts/pie.vue
  45. 491
      src/components/charts/theme.json
  46. 42
      src/components/common-icon/common-icon.vue
  47. 2
      src/components/common-icon/index.js
  48. 8
      src/components/common/common.less
  49. 3
      src/components/common/util.js
  50. 220
      src/components/icon-choose/index.vue
  51. 35
      src/components/icons/icons.vue
  52. 2
      src/components/icons/index.js
  53. 2
      src/components/info-card/index.js
  54. 94
      src/components/info-card/infor-card.vue
  55. 2
      src/components/main/components/a-back-top/index.js
  56. 90
      src/components/main/components/a-back-top/index.vue
  57. 84
      src/components/main/components/fullscreen/fullscreen.vue
  58. 2
      src/components/main/components/fullscreen/index.js
  59. 4
      src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less
  60. 46
      src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue
  61. 2
      src/components/main/components/header-bar/custom-bread-crumb/index.js
  62. 14
      src/components/main/components/header-bar/header-bar.less
  63. 34
      src/components/main/components/header-bar/header-bar.vue
  64. 2
      src/components/main/components/header-bar/index.js
  65. 2
      src/components/main/components/header-bar/sider-trigger/index.js
  66. 21
      src/components/main/components/header-bar/sider-trigger/sider-trigger.less
  67. 27
      src/components/main/components/header-bar/sider-trigger/sider-trigger.vue
  68. 2
      src/components/main/components/language/index.js
  69. 51
      src/components/main/components/language/language.vue
  70. 51
      src/components/main/components/side-menu/collapsed-menu.vue
  71. 2
      src/components/main/components/side-menu/index.js
  72. 21
      src/components/main/components/side-menu/item-mixin.js
  73. 18
      src/components/main/components/side-menu/mixin.js
  74. 26
      src/components/main/components/side-menu/side-menu-item.vue
  75. 40
      src/components/main/components/side-menu/side-menu.less
  76. 114
      src/components/main/components/side-menu/side-menu.vue
  77. 2
      src/components/main/components/tags-nav/index.js
  78. 87
      src/components/main/components/tags-nav/tags-nav.less
  79. 209
      src/components/main/components/tags-nav/tags-nav.vue
  80. 2
      src/components/main/components/user/index.js
  81. 12
      src/components/main/components/user/user.less
  82. 52
      src/components/main/components/user/user.vue
  83. 2
      src/components/main/index.js
  84. 75
      src/components/main/main.less
  85. 180
      src/components/main/main.vue
  86. 25
      src/config/index.js
  87. 9
      src/directive/directives.js
  88. 34
      src/directive/index.js
  89. 41
      src/directive/module/draggable.js
  90. 17
      src/directive/module/has-permission.js
  91. 26
      src/index.less
  92. 62
      src/libs/api.request.js
  93. 3508
      src/libs/icons.js
  94. 1
      src/libs/lazy-loading.js
  95. 47
      src/libs/router-utils.js
  96. 307
      src/libs/tools.js
  97. 384
      src/libs/util.js
  98. 62
      src/libs/wiki.request.js
  99. 37
      src/locale/index.js
  100. 31
      src/locale/lang/en-US.js

5
.babelrc

@ -0,0 +1,5 @@
{
"presets": [
"@vue/app"
]
}

9
.editorconfig

@ -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
.eslintignore

21
.eslintrc.js

@ -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'
}
}

26
.gitignore

@ -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

5
.postcssrc.js

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

5
.travis.yml

@ -0,0 +1,5 @@
language: node_js
node_js: stable
script: npm run lint
notifications:
email: false

50
DISCLAIMER.md

@ -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 交流群提供的支持和资源旨在辅助开发过程,但不应视为全面的技术指导或保证。我们鼓励用户积极参与开源过程,同时也提醒用户充分测试其开发成果,确保其安全性和稳定性。
**变更说明**
本团队有权随时对声明条款及附件内容进行单方面的变更或更新,变更或更新后立即自动生效,无需另行单独通知您或任何第三方;若您在声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。

21
LICENSE

@ -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.

94
README.md

@ -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
[![](https://img.shields.io/badge/build-passing-brightgreen.svg)]()
[![ApiAdmin](https://img.shields.io/hexpm/l/plug.svg)](http://www.apiadmin.org/)
[![ApiAdmin](https://img.shields.io/badge/ApiAdmin-4.1.2-brightgreen.svg)](https://gitee.com/apiadmin/ApiAdmin)
[![vue](https://img.shields.io/badge/vue-2.6.10-brightgreen.svg?style=flat-square)](https://github.com/vuejs/vue)
[![iview ui](https://img.shields.io/badge/view_design-4.2.0-brightgreen.svg?style=flat-square)](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)
## 效果展示
![输入图片说明](https://gitee.com/uploads/images/2018/0224/095358_19cb42d0_110856.png "api.png")
![输入图片说明](https://gitee.com/uploads/images/2018/0224/095410_55dc23e1_110856.png "app.png")
![输入图片说明](https://gitee.com/uploads/images/2018/0224/095420_bddff990_110856.png "auth1.png")
![输入图片说明](https://gitee.com/uploads/images/2018/0224/095427_fa86e42d_110856.png "auth2.png")
![输入图片说明](https://gitee.com/uploads/images/2018/0224/095436_3600de17_110856.png "lock.png")
![输入图片说明](https://gitee.com/uploads/images/2018/0224/095444_d2a88da0_110856.png "user.png")
## 联系我们
官方唯一QQ群:221522638
## License
[Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0)
Copyright (c) 2017-present, ApiAdmin

3
cypress.json

@ -0,0 +1,3 @@
{
"pluginsFile": "tests/e2e/plugins/index.js"
}

54
package.json

@ -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"
]
}
}

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

23
public/index.html

@ -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>

27
src/App.vue

@ -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>

73
src/api/app-group.js

@ -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'
})
}

91
src/api/app.js

@ -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
})
}

113
src/api/auth.js

@ -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
})
}

73
src/api/fields.js

@ -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
})
}

78
src/api/interface-group.js

@ -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
})
}

88
src/api/interface.js

@ -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
})
}

23
src/api/log.js

@ -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
}
})
}

75
src/api/menu.js

@ -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
}
})
}

51
src/api/third-login.js

@ -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'
})
}

140
src/api/user.js

@ -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
})
}

58
src/api/wiki.js

@ -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'
})
}

37
src/assets/icons/iconfont.css

@ -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"; }

BIN
src/assets/icons/iconfont.eot

Binary file not shown.

56
src/assets/icons/iconfont.svg

@ -0,0 +1,56 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="bear" unicode="&#58880;" d="M1024 683.008q0-70.656-46.08-121.856 46.08-89.088 46.08-193.536 0-96.256-39.936-181.248t-109.568-147.968-162.816-99.328-199.68-36.352-199.68 36.352-162.304 99.328-109.568 147.968-40.448 181.248q0 104.448 46.08 193.536-46.08 51.2-46.08 121.856 0 37.888 13.824 71.168t37.376 58.368 55.808 39.424 68.096 14.336q43.008 0 78.848-18.432t59.392-50.176q46.08 17.408 96.256 26.624t102.4 9.216 102.4-9.216 96.256-26.624q24.576 31.744 59.904 50.176t78.336 18.432q36.864 0 68.608-14.336t55.296-39.424 37.376-58.368 13.824-71.168zM205.824 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512-31.744000000000028q53.248 0 99.84 13.312t81.408 35.84 54.784 52.736 19.968 65.024q0 33.792-19.968 64t-54.784 52.736-81.408 35.84-99.84 13.312-99.84-13.312-81.408-35.84-54.784-52.736-19.968-64q0-34.816 19.968-65.024t54.784-52.736 81.408-35.84 99.84-13.312zM818.176 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512 235.51999999999998q39.936 0 68.096-9.728t28.16-24.064-28.16-24.064-68.096-9.728-68.096 9.728-28.16 24.064 28.16 24.064 68.096 9.728z" horiz-adv-x="1024" />
<glyph glyph-name="resize-vertical" unicode="&#59331;" d="M512 896C229.248 896 0 666.752 0 384s229.248-512 512-512 512 229.248 512 512S794.752 896 512 896zM576 192l64 0-128-128-128 128 64 0L448 576l-64 0 128 128 128-128-64 0L576 192z" horiz-adv-x="1024" />
<glyph glyph-name="chuizhifanzhuan" unicode="&#58977;" d="M286.01856 645.08416l472.4224 0 0-146.2784-472.4224 0 0 146.2784ZM87.19872 420.37248l885.80096 0 0-70.87104-885.80096 0 0 70.87104ZM773.55008 268.05248l0-31.0016L270.6688 237.05088l0 31.0016L773.55008 268.05248zM773.55008 121.4208l0-31.0016L270.6688 90.4192l0 31.0016L773.55008 121.4208zM742.54848 240.75776l31.0016 0 0-123.04896-31.0016 0L742.54848 240.75776zM270.70464 240.57856l31.0016 0 0-123.04896-31.0016 0L270.70464 240.57856z" horiz-adv-x="1024" />
<glyph glyph-name="shuipingfanzhuan" unicode="&#58978;" d="M252.76928 596.096l146.2784 0 0-472.42752-146.2784 0 0 472.42752ZM477.48096 810.65472l70.87104 0 0-885.80608-70.87104 0 0 885.80608ZM629.80096 611.2l31.0016 0 0-502.88128-31.0016 0L629.80096 611.2zM776.42752 611.2l31.0016 0 0-502.88128-31.0016 0L776.42752 611.2zM657.09056 580.1984l0 31.0016 123.04896 0 0-31.0016L657.09056 580.1984zM657.27488 108.35456l0 31.0016 123.04896 0 0-31.0016L657.27488 108.35456z" horiz-adv-x="1024" />
<glyph glyph-name="qq" unicode="&#58889;" d="M147.372058 491.394284c-5.28997-13.909921 2.431986-22.698872 0-75.732573-0.682996-14.25092-62.165649-78.762555-86.569511-145.791177-24.192863-66.517625-27.519845-135.978232 9.811944-163.285078 37.419789-27.305846 72.191593 90.879487 76.757567 73.685584 1.961989-7.509958 4.436975-15.317914 7.423958-23.338868a331.945126 331.945126 0 0 1 61.140655-101.162429c5.929967-6.783962-36.009797-19.199892-61.140655-61.99365-25.173858-42.751759 7.209959-120.49032 132.223254-120.49032 161.27909 0 197.288886 56.70368 200.574868 56.447681 12.031932-0.895995 12.841928 0 25.599855 0 15.572912 0 9.129948-1.279993 23.593867 0 7.807956 0.682996 86.186514-67.839617 194.686901-56.447681 184.873956 19.45589 156.586116 81.40754 142.079198 120.48932-15.103915 40.83277-68.692612 59.946662-66.303626 62.549647 44.28775 48.938724 51.285711 79.018554 66.346626 123.9463 6.143965 18.473896 49.066723-101.674426 82.089537-73.685584 13.781922 11.690934 41.301767 60.24566 13.781922 163.285078-27.519845 102.996419-80.767544 126.505286-79.615551 145.791177 2.389987 40.191773 1.023994 68.436614-1.023994 75.732573-9.812945 35.4128-30.378829 27.604844-30.378829 35.4128C858.450044 730.752933 705.10691 896 515.966978 896s-342.398067-165.289067-342.398068-369.192916c0-16.169909-14.378919-4.223976-26.154852-35.4128z" horiz-adv-x="1024" />
<glyph glyph-name="frown" unicode="&#59262;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM512 363c-85.5 0-155.6-67.3-160-151.6-0.2-4.6 3.4-8.4 8-8.4h48.1c4.2 0 7.8 3.2 8.1 7.4C420 259.9 461.5 299 512 299s92.1-39.1 95.8-88.6c0.3-4.2 3.9-7.4 8.1-7.4H664c4.6 0 8.2 3.8 8 8.4-4.4 84.3-74.5 151.6-160 151.6z" horiz-adv-x="1024" />
<glyph glyph-name="meh" unicode="&#59264;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 331H360c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h304c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8z" horiz-adv-x="1024" />
<glyph glyph-name="smile" unicode="&#59267;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 363h-48.1c-4.2 0-7.8-3.2-8.1-7.4C604 306.1 562.5 267 512 267s-92.1 39.1-95.8 88.6c-0.3 4.2-3.9 7.4-8.1 7.4H360c-4.6 0-8.2-3.8-8-8.4 4.4-84.3 74.5-151.6 160-151.6s155.6 67.3 160 151.6c0.2 4.6-3.4 8.4-8 8.4z" horiz-adv-x="1024" />
<glyph glyph-name="man" unicode="&#59362;" d="M874 776H622c-3.3 0-6-2.7-6-6v-56c0-3.3 2.7-6 6-6h160.4L583.1 508.7c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S120 356.9 120 280s30-149.3 84.4-203.6C258.7 22 331.1-8 408-8s149.3 30 203.6 84.4C666 130.7 696 203.1 696 280c0 64.1-20.8 124.9-59.2 174.9L836 654.1V494c0-3.3 2.7-6 6-6h56c3.3 0 6 2.7 6 6V746c0 16.5-13.5 30-30 30zM408 68c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z" horiz-adv-x="1024" />
<glyph glyph-name="woman" unicode="&#59365;" d="M909.7 739.4l-42.2 42.2c-3.1 3.1-8.2 3.1-11.3 0L764 689.4l-84.2 84.2c-3.1 3.1-8.2 3.1-11.3 0l-42.1-42.1c-3.1-3.1-3.1-8.1 0-11.3l84.2-84.2-135.5-135.3c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S112 348.9 112 272s30-149.3 84.4-203.6C250.7 14 323.1-16 400-16s149.3 30 203.6 84.4C658 122.7 688 195.1 688 272c0 64.2-20.9 125.1-59.3 175.1l135.4 135.4 84.2-84.2c3.1-3.1 8.2-3.1 11.3 0l42.1 42.1c3.1 3.1 3.1 8.1 0 11.3l-84.2 84.2 92.2 92.2c3.1 3.1 3.1 8.2 0 11.3zM400 60c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z" horiz-adv-x="1024" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
src/assets/icons/iconfont.ttf

Binary file not shown.

BIN
src/assets/icons/iconfont.woff

Binary file not shown.

BIN
src/assets/images/default-img.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1
src/assets/images/error-page/error-401.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

1
src/assets/images/error-page/error-404.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

1
src/assets/images/error-page/error-500.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/assets/images/login-bg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

17
src/assets/images/login-wiki.svg

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1457px" height="651px" viewBox="0 0 1457 651" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 54 (76480) - https://sketchapp.com -->
<title>bg</title>
<desc>Created with Sketch.</desc>
<g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="body">
<rect id="矩形" fill="#E0E3F5" transform="translate(1111.405592, 620.405592) rotate(45.000000) translate(-1111.405592, -620.405592) " x="1089.90559" y="598.905592" width="43" height="43"></rect>
<rect id="矩形" fill="#DEEFF6" transform="translate(127.254834, 230.254834) rotate(40.000000) translate(-127.254834, -230.254834) " x="95.254834" y="198.254834" width="64" height="64"></rect>
<circle id="Oval-7" fill="#C9CFED" fill-rule="nonzero" opacity="0.45" cx="1286.5" cy="27.5" r="23.5"></circle>
<circle id="Oval-7" fill="#E7E6E6" fill-rule="nonzero" opacity="0.45" cx="191" cy="51" r="18"></circle>
<circle id="Oval-7" fill="#EFEFEF" fill-rule="nonzero" opacity="0.45" cx="888" cy="499" r="18"></circle>
<ellipse id="Oval-7" stroke="#E4E4F2" stroke-width="10" fill-opacity="0" fill="#FFFFFF" fill-rule="nonzero" opacity="0.45" cx="1373.5" cy="331.5" rx="78.5" ry="79.5"></ellipse>
<ellipse id="Oval-7" stroke="#CEECF8" stroke-width="10" fill-opacity="0" fill="#FFFFFF" fill-rule="nonzero" opacity="0.45" cx="235" cy="481.5" rx="44" ry="44.5"></ellipse>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/assets/images/logo-min.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

BIN
src/assets/images/logo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/assets/images/qq-login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
src/assets/images/wx-login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

58
src/components/charts/bar.vue

@ -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>

3
src/components/charts/index.js

@ -0,0 +1,3 @@
import ChartPie from './pie.vue'
import ChartBar from './bar.vue'
export { ChartPie, ChartBar }

70
src/components/charts/pie.vue

@ -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>

491
src/components/charts/theme.json

@ -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"
}
}
}
}
}

42
src/components/common-icon/common-icon.vue

@ -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>

2
src/components/common-icon/index.js

@ -0,0 +1,2 @@
import CommonIcon from './common-icon.vue'
export default CommonIcon

8
src/components/common/common.less

@ -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;
}

3
src/components/common/util.js

@ -0,0 +1,3 @@
export const showTitle = (item, vm) => {
return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name)
}

220
src/components/icon-choose/index.vue

@ -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>

35
src/components/icons/icons.vue

@ -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>

2
src/components/icons/index.js

@ -0,0 +1,2 @@
import Icons from './icons.vue'
export default Icons

2
src/components/info-card/index.js

@ -0,0 +1,2 @@
import InforCard from './infor-card.vue'
export default InforCard

94
src/components/info-card/infor-card.vue

@ -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>

2
src/components/main/components/a-back-top/index.js

@ -0,0 +1,2 @@
import ABackTop from './index.vue'
export default ABackTop

90
src/components/main/components/a-back-top/index.vue

@ -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>

84
src/components/main/components/fullscreen/fullscreen.vue

@ -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>

2
src/components/main/components/fullscreen/index.js

@ -0,0 +1,2 @@
import Fullscreen from './fullscreen.vue'
export default Fullscreen

4
src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less

@ -0,0 +1,4 @@
.custom-bread-crumb{
display: inline-block;
vertical-align: top;
}

46
src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue

@ -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>

2
src/components/main/components/header-bar/custom-bread-crumb/index.js

@ -0,0 +1,2 @@
import customBreadCrumb from './custom-bread-crumb.vue'
export default customBreadCrumb

14
src/components/main/components/header-bar/header-bar.less

@ -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;
}
}
}

34
src/components/main/components/header-bar/header-bar.vue

@ -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>

2
src/components/main/components/header-bar/index.js

@ -0,0 +1,2 @@
import HeaderBar from './header-bar'
export default HeaderBar

2
src/components/main/components/header-bar/sider-trigger/index.js

@ -0,0 +1,2 @@
import siderTrigger from './sider-trigger.vue'
export default siderTrigger

21
src/components/main/components/header-bar/sider-trigger/sider-trigger.less

@ -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;
}
}

27
src/components/main/components/header-bar/sider-trigger/sider-trigger.vue

@ -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>

2
src/components/main/components/language/index.js

@ -0,0 +1,2 @@
import Language from './language.vue'
export default Language

51
src/components/main/components/language/language.vue

@ -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>

51
src/components/main/components/side-menu/collapsed-menu.vue

@ -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>

2
src/components/main/components/side-menu/index.js

@ -0,0 +1,2 @@
import SideMenu from './side-menu.vue'
export default SideMenu

21
src/components/main/components/side-menu/item-mixin.js

@ -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'
}
}
}

18
src/components/main/components/side-menu/mixin.js

@ -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)
}
}
}

26
src/components/main/components/side-menu/side-menu-item.vue

@ -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>

40
src/components/main/components/side-menu/side-menu.less

@ -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;
}

114
src/components/main/components/side-menu/side-menu.vue

@ -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>

2
src/components/main/components/tags-nav/index.js

@ -0,0 +1,2 @@
import TagsNav from './tags-nav.vue'
export default TagsNav

87
src/components/main/components/tags-nav/tags-nav.less

@ -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;
}
}
}
}

209
src/components/main/components/tags-nav/tags-nav.vue

@ -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>

2
src/components/main/components/user/index.js

@ -0,0 +1,2 @@
import User from './user.vue'
export default User

12
src/components/main/components/user/user.less

@ -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;
}
}
}

52
src/components/main/components/user/user.vue

@ -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>

2
src/components/main/index.js

@ -0,0 +1,2 @@
import Main from './main.vue'
export default Main

75
src/components/main/main.less

@ -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;
}

180
src/components/main/main.vue

@ -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>

25
src/config/index.js

@ -0,0 +1,25 @@
export default {
/**
* @description 配置显示在浏览器标签的title
*/
title: 'ApiAdmin身边的接口管理专家',
/**
* @description 是否使用国际化默认为false
* 如果不使用则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'}
* 用来在菜单中显示文字
*/
useI18n: false,
/**
* @description api请求基础路径
*/
baseUrl: {
dev: '',
pro: ''
},
/**
* @description 需要加载的插件
*/
plugin: {
}
}

9
src/directive/directives.js

@ -0,0 +1,9 @@
import draggable from './module/draggable'
import has from './module/has-permission'
const directives = {
draggable,
has
}
export default directives

34
src/directive/index.js

@ -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

41
src/directive/module/draggable.js

@ -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 = ''
}
}

17
src/directive/module/has-permission.js

@ -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)
}
}
})
}
}

26
src/index.less

@ -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
}

62
src/libs/api.request.js

@ -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

3508
src/libs/icons.js

File diff suppressed because it is too large

1
src/libs/lazy-loading.js

@ -0,0 +1 @@
export default (url) => () => import(`@/view/${url}.vue`)

47
src/libs/router-utils.js

@ -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
}

307
src/libs/tools.js

@ -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])
}

384
src/libs/util.js

@ -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
}

62
src/libs/wiki.request.js

@ -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

37
src/locale/index.js

@ -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))

31
src/locale/lang/en-US.js

@ -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'
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save