mirror of
https://github.com/iczer/vue-antd-admin
synced 2025-04-06 04:00:06 +08:00
Compare commits
116 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
10a7613e6e | ||
|
3c79f89416 | ||
|
35f0e431b8 | ||
|
60be8cf4ec | ||
|
17e41ce1fc | ||
|
baae56f715 | ||
|
9a7493c99c | ||
|
cc60ff1a27 | ||
|
c0e5f2ccec | ||
|
3fa8995690 | ||
|
a4d4b28ce3 | ||
|
d7a530db46 | ||
|
7f5fbb7426 | ||
|
6000da4220 | ||
|
c0aec854af | ||
|
8062905b17 | ||
|
56635a948b | ||
|
2d0c6c2a2b | ||
|
5d4bd75b22 | ||
|
6d43e9af42 | ||
|
c2d2c2c686 | ||
|
a9b3da4d19 | ||
|
baf063f9ea | ||
|
816d19f7da | ||
|
ce83564335 | ||
|
1345a02cd0 | ||
|
449fd99f9d | ||
|
97a1417112 | ||
|
ba89880736 | ||
|
aa4e3d93d5 | ||
|
d730f6d783 | ||
|
8d42b936e5 | ||
|
da1dafda54 | ||
|
8127121ab6 | ||
|
d29a14936a | ||
|
80e3ad42bc | ||
|
bb7fa9abb6 | ||
|
990daf2d27 | ||
|
939f8640d3 | ||
|
a1ae7d1e3f | ||
|
8ddc7c167c | ||
|
ce536b95c2 | ||
|
4dbbb852a9 | ||
|
f0d60a8242 | ||
|
9ddd117d5e | ||
|
2fc5b9d594 | ||
|
83c6381a4b | ||
|
0c41878174 | ||
|
a5c34a8514 | ||
|
df076bda24 | ||
|
62b57a97cb | ||
|
867377a6d2 | ||
|
a2e5370ae8 | ||
|
39b64d0704 | ||
|
d2b2631fb1 | ||
|
345b46bf6f | ||
|
b0fc3a943e | ||
|
37f66c8786 | ||
|
5a333faa2b | ||
|
f2d3823069 | ||
|
33179d96b7 | ||
|
935cd77d4f | ||
|
010ffdaacb | ||
|
5c6b2a2048 | ||
|
34a76d5894 | ||
|
0f1a845189 | ||
|
5de611523c | ||
|
c2915c93d3 | ||
|
e661ae0813 | ||
|
915c2078cb | ||
|
407c2719cb | ||
|
d9b5e4b766 | ||
|
75a510edbd | ||
|
31e22aaf0e | ||
|
63ea2a9459 | ||
|
ae2b7a86ef | ||
|
23b7dfe2a4 | ||
|
10296fd022 | ||
|
9e7a03fcd8 | ||
|
f74d08248e | ||
|
d638eaa6bf | ||
|
344ad1d1f6 | ||
|
c82103d64d | ||
|
74c54cbf73 | ||
|
a8dab1687a | ||
|
19ab79c88c | ||
|
2262cb98ef | ||
|
7cea44c216 | ||
|
6c79a54a53 | ||
|
97979c5059 | ||
|
8130f9f250 | ||
|
57dc7fe2e8 | ||
|
e841ac77fd | ||
|
ec2c70d181 | ||
|
3a7c51cd8a | ||
|
a4b0785f0f | ||
|
c1e956a5a0 | ||
|
acd66d7d6c | ||
|
533890a376 | ||
|
10fc9c11b8 | ||
|
c4e81a1a61 | ||
|
b021ce4f0b | ||
|
59bb834da8 | ||
|
1a434dbd2a | ||
|
501bd23c20 | ||
|
3619242076 | ||
|
a4281b62dc | ||
|
6b41fa7f31 | ||
|
463fc93af9 | ||
|
4d69602595 | ||
|
517c1959d8 | ||
|
3d3e56de12 | ||
|
313af63f33 | ||
|
83576d88d7 | ||
|
9df2666304 | ||
|
a03b37ff30 |
2
.env
2
.env
@ -1,3 +1,4 @@
|
|||||||
|
VUE_APP_PUBLIC_PATH=/
|
||||||
VUE_APP_NAME=Admin
|
VUE_APP_NAME=Admin
|
||||||
VUE_APP_ROUTES_KEY=admin.routes
|
VUE_APP_ROUTES_KEY=admin.routes
|
||||||
VUE_APP_PERMISSIONS_KEY=admin.permissions
|
VUE_APP_PERMISSIONS_KEY=admin.permissions
|
||||||
@ -5,4 +6,5 @@ VUE_APP_ROLES_KEY=admin.roles
|
|||||||
VUE_APP_USER_KEY=admin.user
|
VUE_APP_USER_KEY=admin.user
|
||||||
VUE_APP_SETTING_KEY=admin.setting
|
VUE_APP_SETTING_KEY=admin.setting
|
||||||
VUE_APP_TBAS_KEY=admin.tabs
|
VUE_APP_TBAS_KEY=admin.tabs
|
||||||
|
VUE_APP_TBAS_TITLES_KEY=admin.tabs.titles
|
||||||
VUE_APP_API_BASE_URL=http://api.iczer.com
|
VUE_APP_API_BASE_URL=http://api.iczer.com
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ selenium-debug.log
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
.env.production.local
|
||||||
|
@ -16,9 +16,9 @@ Multiple theme modes available:
|
|||||||

|

|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
- Preview:https://iczer.gitee.io/vue-antd-admin
|
- Preview:https://vue-antd-admin.pages.dev
|
||||||
- Documentation:https://iczer.gitee.io/vue-antd-admin-docs
|
- Documentation:https://doc.vue-antd-admin.pages.dev
|
||||||
- FAQ:https://iczer.github.io/vue-antd-admin/start/faq.html
|
- FAQ:https://doc.vue-antd-admin.pages.dev/start/faq.html
|
||||||
- Mirror Repo in China:https://gitee.com/iczer/vue-antd-admin
|
- Mirror Repo in China:https://gitee.com/iczer/vue-antd-admin
|
||||||
|
|
||||||
## Browsers support
|
## Browsers support
|
||||||
@ -43,11 +43,11 @@ $ yarn serve
|
|||||||
$ npm install
|
$ npm install
|
||||||
$ npm run serve
|
$ npm run serve
|
||||||
```
|
```
|
||||||
More instructions at [documentation](https://iczer.github.io/vue-antd-admin).
|
More instructions at [documentation](https://iczer.gitee.io/vue-antd-admin-docs).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Any type of contribution is welcome, here are some examples of how you may contribute to this project: :star2::
|
Any type of contribution is welcome, here are some examples of how you may contribute to this project: :star2::
|
||||||
- Use Vue Antd Admin in your daily work.
|
- Use Vue Antd Admin in your daily work.
|
||||||
- Submit [Issue](https://github.com/iczer/vue-antd-admin/issues) to report :bug: or ask questions.
|
- Submit [Issue](https://github.com/iczer/vue-antd-admin/issues) to report :bug: or ask questions.
|
||||||
- Propose [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) to improve our code.
|
- Propose [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) to improve our code.
|
||||||
- Join the community and share your experiences with us. QQ Group: 610090280
|
- Join the community and share your experiences with us. QQ Group:942083829、812277510(full)、610090280(full)
|
||||||
|
25
README.md
25
README.md
@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
简体中文 | [English](./README.en-US.md)
|
简体中文 | [English](./README.en-US.md)
|
||||||
<h1 align="center">Vue Antd Admin</h1>
|
<h1 align="center">Vue Antd Admin</h1>
|
||||||
|
|
||||||
@ -6,6 +8,12 @@
|
|||||||
[Ant Design Pro](https://github.com/ant-design/ant-design-pro) 的 Vue 实现版本
|
[Ant Design Pro](https://github.com/ant-design/ant-design-pro) 的 Vue 实现版本
|
||||||
开箱即用的中后台前端/设计解决方案
|
开箱即用的中后台前端/设计解决方案
|
||||||
|
|
||||||
|
:star::star::star:
|
||||||
|
vue3 版本现已推出,更名为
|
||||||
|
[stepin-template](https://github.com/stepui/stepin-template),欢迎体验,
|
||||||
|
[立即前往](https://github.com/stepui/stepin-template)
|
||||||
|
--
|
||||||
|
|
||||||
[](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE)
|
[](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE)
|
||||||
[](https://david-dm.org/iczer/vue-antd-admin)
|
[](https://david-dm.org/iczer/vue-antd-admin)
|
||||||
[](https://david-dm.org/iczer/vue-antd-admin?type=dev)
|
[](https://david-dm.org/iczer/vue-antd-admin?type=dev)
|
||||||
@ -16,9 +24,9 @@
|
|||||||

|

|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
- 预览地址:https://iczer.gitee.io/vue-antd-admin
|
- 预览地址:https://vue-antd-admin.pages.dev
|
||||||
- 使用文档:https://iczer.gitee.io/vue-antd-admin-docs
|
- 使用文档:https://doc.vue-antd-admin.pages.dev
|
||||||
- 常见问题:https://iczer.github.io/vue-antd-admin/start/faq.html
|
- 常见问题:https://doc.vue-antd-admin.pages.dev/start/faq.html
|
||||||
- 国内镜像:https://gitee.com/iczer/vue-antd-admin
|
- 国内镜像:https://gitee.com/iczer/vue-antd-admin
|
||||||
|
|
||||||
## 浏览器支持
|
## 浏览器支持
|
||||||
@ -43,11 +51,18 @@ $ yarn serve
|
|||||||
$ npm install
|
$ npm install
|
||||||
$ npm run serve
|
$ npm run serve
|
||||||
```
|
```
|
||||||
更多信息参考 [使用文档](https://iczer.github.io/vue-antd-admin)
|
更多信息参考 [使用文档](https://iczer.gitee.io/vue-antd-admin-docs)
|
||||||
|
|
||||||
## 参与贡献
|
## 参与贡献
|
||||||
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :star2::
|
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :star2::
|
||||||
- 在你的公司或个人项目中使用 Vue Antd Admin。
|
- 在你的公司或个人项目中使用 Vue Antd Admin。
|
||||||
- 通过 [Issue](https://github.com/iczer/vue-antd-admin/issues) 报告:bug:或进行咨询。
|
- 通过 [Issue](https://github.com/iczer/vue-antd-admin/issues) 报告:bug:或进行咨询。
|
||||||
- 提交 [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) 改进 Admin 的代码。
|
- 提交 [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) 改进 Admin 的代码。
|
||||||
- 加入社群,与小伙伴们一同交流心得。QQ群:610090280
|
- 加入社群,与小伙伴们一同交流心得。QQ群:942083829、 812277510(已满)、610090280(已满)
|
||||||
|
|
||||||
|
## 打赏
|
||||||
|
如果该项目对您有所帮助,可以请作者喝一杯咖啡。
|
||||||
|
<p>
|
||||||
|
<img src="./src/assets/img/alipay.png" width="320px" style="display: inline-block;" />
|
||||||
|
<img src="./src/assets/img/wechatpay.png" width="320px" style="display: inline-block; margin-left: 24px;" />
|
||||||
|
</p>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
title: 'Vue Antd Admin',
|
title: 'Vue Antd Admin',
|
||||||
description: 'Vue Antd Admin',
|
description: 'Vue Antd Admin',
|
||||||
base: '/vue-antd-admin-docs/',
|
base: '/',
|
||||||
head: [
|
head: [
|
||||||
['link', { rel: 'icon', href: '/favicon.ico' }]
|
['link', { rel: 'icon', href: '/favicon.ico' }]
|
||||||
],
|
],
|
||||||
@ -36,7 +36,8 @@ module.exports = {
|
|||||||
title: '进阶',
|
title: '进阶',
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
children: [
|
children: [
|
||||||
'/advance/i18n', '/advance/async', '/advance/authority', '/advance/login', '/advance/guard', '/advance/interceptors'
|
'/advance/i18n', '/advance/async', '/advance/authority', '/advance/login', '/advance/guard', '/advance/interceptors',
|
||||||
|
'/advance/api'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
42
docs/advance/api.md
Normal file
42
docs/advance/api.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: 全局API
|
||||||
|
lang: zn-CN
|
||||||
|
---
|
||||||
|
# 全局API
|
||||||
|
我们提供了一些全局Api,在日常功能开发中或许会有帮助,它们均被绑定到了页面组件或子组件实例上。
|
||||||
|
在组件内可以直接通过`this.$[apiName]`的方式调用。如下:
|
||||||
|
|
||||||
|
## 多页签
|
||||||
|
### $closePage(closeRoute, nextRoute)
|
||||||
|
该api用于关闭当前已打开的页签,接收两个参数:
|
||||||
|
* **closeRoute**
|
||||||
|
要关闭的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||||
|
* **nextRoute**
|
||||||
|
关闭页签要后跳转的 route 对象,可不传,不传则会自动选择打开页签(临近原则)。
|
||||||
|
|
||||||
|
### $refreshPage(route)
|
||||||
|
该api用于刷新路由对应的页签,接收一个参数:
|
||||||
|
* **route**
|
||||||
|
要刷新的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||||
|
|
||||||
|
### $openPage(route, title)
|
||||||
|
该api用于打开一个新页签,接收两个参数:
|
||||||
|
* **route**
|
||||||
|
要打开的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||||
|
* **title**
|
||||||
|
设置打开页签的标题,可不传。
|
||||||
|
|
||||||
|
### $setPageTitle(route, title)
|
||||||
|
该api用于设置页签的标题,接收两个参数:
|
||||||
|
* **route**
|
||||||
|
要设置的页签对应的 route 对象,可简写为路由的 fullPath 字符串值。
|
||||||
|
* **title**
|
||||||
|
页签的标题。
|
||||||
|
|
||||||
|
## 权限
|
||||||
|
### $auth(check, type)
|
||||||
|
该api可以用于操作权限校验,接收两个参数:
|
||||||
|
* **check**
|
||||||
|
需要要校验的操作权限
|
||||||
|
* **type**
|
||||||
|
操作权限校验类别,可选 `permission` 和 `role`,即通过权限校验还是角色进行校验,可不传(不传的话,会对两种类型都进行匹配,任意一种匹配成功即校验通过)。
|
@ -197,7 +197,7 @@ export default options
|
|||||||
```js {3}
|
```js {3}
|
||||||
getRoutesConfig().then(result => {
|
getRoutesConfig().then(result => {
|
||||||
const routesConfig = result.data.data
|
const routesConfig = result.data.data
|
||||||
loadRoutes({router: this.$router, store: this.$store, i18n: this.$i18n}, routesConfig)
|
loadRoutes(routesConfig)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
至此,异步路由的加载就完成了,你可以访问异步加载的路由了。
|
至此,异步路由的加载就完成了,你可以访问异步加载的路由了。
|
||||||
@ -209,12 +209,9 @@ loadRoutes 方法会合并 /router/async/config.async.js 文件中配置的基
|
|||||||
```js
|
```js
|
||||||
/**
|
/**
|
||||||
* 加载路由
|
* 加载路由
|
||||||
* @param router 应用路由实例
|
|
||||||
* @param store 应用的 vuex.store 实例
|
|
||||||
* @param i18n 应用的 vue-i18n 实例
|
|
||||||
* @param routesConfig 路由配置
|
* @param routesConfig 路由配置
|
||||||
*/
|
*/
|
||||||
function loadRoutes({router, store, i18n}, routesConfig) {
|
function loadRoutes(routesConfig) {
|
||||||
// 如果 routesConfig 有值,则更新到本地,否则从本地获取
|
// 如果 routesConfig 有值,则更新到本地,否则从本地获取
|
||||||
if (routesConfig) {
|
if (routesConfig) {
|
||||||
store.commit('account/setRoutesConfig', routesConfig)
|
store.commit('account/setRoutesConfig', routesConfig)
|
||||||
|
@ -31,7 +31,7 @@ permission = {
|
|||||||
operation: ['add', 'delete', 'edit', 'close'] //权限下的操作权限
|
operation: ['add', 'delete', 'edit', 'close'] //权限下的操作权限
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
你也可以设置 role 的值为字符串,比如 permission = 'form', 它等同于:
|
你也可以设置 permission 的值为字符串,比如 permission = 'form', 它等同于:
|
||||||
```js
|
```js
|
||||||
permission = {
|
permission = {
|
||||||
id: 'form'
|
id: 'form'
|
||||||
|
@ -3,5 +3,127 @@ title: 国际化
|
|||||||
lang: zn-CN
|
lang: zn-CN
|
||||||
---
|
---
|
||||||
# 国际化
|
# 国际化
|
||||||
|
vue-antd-admin 采用 [vue-i18n](https://kazupon.github.io/vue-i18n/) 插件来实现国际化,该项目已经内置并且加载好了基础配置。可以直接上手使用。
|
||||||
|
|
||||||
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页
|
> 如果你还没有看快速入门,请先移步查看: [页面 -> i18n国际化配置](../develop/page.html#i18n国际化配置)
|
||||||
|
|
||||||
|
|
||||||
|
## 菜单和路由
|
||||||
|
|
||||||
|
### 默认情况
|
||||||
|
如果你没有对菜单进行国际化配置,admin 默认会从路由数据中提取数据作为国际化配置。route.name 作为中文语言,route.path 作为英文语言。
|
||||||
|
国际化提取函数定义在 `@/utils/i18n.js` 文件中,会在路由加载时调用,如下:
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* 从路由提取国际化数据
|
||||||
|
* @param i18n
|
||||||
|
* @param routes
|
||||||
|
*/
|
||||||
|
function mergeI18nFromRoutes(i18n, routes) {
|
||||||
|
formatFullPath(routes)
|
||||||
|
const CN = generateI18n(new Object(), routes, 'name')
|
||||||
|
const US = generateI18n(new Object(), routes, 'path')
|
||||||
|
i18n.mergeLocaleMessage('CN', CN)
|
||||||
|
i18n.mergeLocaleMessage('US', US)
|
||||||
|
const messages = routesI18n.messages
|
||||||
|
Object.keys(messages).forEach(lang => {
|
||||||
|
i18n.mergeLocaleMessage(lang, messages[lang])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 自定义
|
||||||
|
如果你想自定义菜单国际化数据,可在 `@/router/i18n.js` 文件中配置。我们以路由的 path 作为 key(嵌套path 的写法也会被解析),name 作为 国际化语言的值。
|
||||||
|
假设你有一个路由的配置如下:
|
||||||
|
```js
|
||||||
|
[{
|
||||||
|
path: 'parent',
|
||||||
|
...
|
||||||
|
children: [{
|
||||||
|
path: 'self',
|
||||||
|
...
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
[{
|
||||||
|
path: 'other',
|
||||||
|
...
|
||||||
|
children: [{
|
||||||
|
path: '/parent/self', // 在国际化配置中 key 会解析为 parent.self
|
||||||
|
...
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
那么你需要在 `@/router/i18n.js` 中这样配置:
|
||||||
|
```jsx
|
||||||
|
messages: {
|
||||||
|
CN: {
|
||||||
|
parent: {
|
||||||
|
name: '父級菜單',
|
||||||
|
self: {name: '菜單名'},
|
||||||
|
},
|
||||||
|
US: {
|
||||||
|
parent: {
|
||||||
|
name: 'parent menu',
|
||||||
|
self: {name: 'menu name'},
|
||||||
|
},
|
||||||
|
HK: {
|
||||||
|
parent: {
|
||||||
|
name: '父級菜單',
|
||||||
|
self: {name: '菜單名'},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## 添加语言
|
||||||
|
|
||||||
|
首先在 `@/layouts/header/AdminHeader.vue` ,新增一门语言 (多个同理)。
|
||||||
|
|
||||||
|
```vue {15}
|
||||||
|
<template>
|
||||||
|
...
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
...
|
||||||
|
export default {
|
||||||
|
...
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
langList: [
|
||||||
|
{key: 'CN', name: '简体中文', alias: '简体'},
|
||||||
|
{key: 'HK', name: '繁體中文', alias: '繁體'},
|
||||||
|
{key: 'US', name: 'English', alias: 'English'},
|
||||||
|
// 新增一个语言选项, key是i18n的索引,name是菜单显示名称
|
||||||
|
{key: 'JP', name: 'Japanese', alias: 'Japanese'}
|
||||||
|
],
|
||||||
|
searchActive: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
> TIP: 后续开发建议把这里改成动态配置的方式!
|
||||||
|
|
||||||
|
然后开始往 `@/router/i18n.js` 和 `@/pages/你的页面/i18n.js` 里面分别添加上语言的翻译。
|
||||||
|
|
||||||
|
```vue {12,13,14}
|
||||||
|
module.exports = {
|
||||||
|
messages: {
|
||||||
|
CN: {
|
||||||
|
home: {name: '首页'},
|
||||||
|
},
|
||||||
|
US: {
|
||||||
|
home: {name: 'home'},
|
||||||
|
},
|
||||||
|
HK: {
|
||||||
|
home: {name: '首頁'},
|
||||||
|
},
|
||||||
|
JP: {
|
||||||
|
home: {name: '最初のページ'},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Notice: 更多用法请移步到 [vue-i18n](https://kazupon.github.io/vue-i18n/) 。
|
@ -44,7 +44,7 @@ const tokenCheck = {
|
|||||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||||
|
|
||||||
### onRejected
|
### onRejected
|
||||||
我们会为 onFulfilled 钩子函数注入 error 和 options 两个参数:
|
我们会为 onRejected 钩子函数注入 error 和 options 两个参数:
|
||||||
* `error: Error`: axios 请求错误对象
|
* `error: Error`: axios 请求错误对象
|
||||||
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
|
||||||
|
|
||||||
@ -128,4 +128,4 @@ export default {
|
|||||||
response: [resp401, resp403] // 响应拦截
|
response: [resp401, resp403] // 响应拦截
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
@ -5,4 +5,4 @@ lang: zh-CN
|
|||||||
# 社区
|
# 社区
|
||||||
|
|
||||||
## 交流学习
|
## 交流学习
|
||||||
### QQ群:610090280
|
### QQ群:812277510、610090280(已满)
|
||||||
|
30
package.json
30
package.json
@ -1,36 +1,38 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-antd-admin",
|
"name": "vue-antd-admin",
|
||||||
"version": "0.5.0",
|
"version": "0.7.4",
|
||||||
"homepage": "https://iczer.github.io/vue-antd-admin",
|
"homepage": "https://iczer.github.io/vue-antd-admin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"predeploy": "yarn build",
|
"predeploy": "yarn build",
|
||||||
"deploy": "gh-pages -d dist -b pages -r https://gitee.com/iczer/vue-antd-admin.git",
|
"deploy": "gh-pages -d dist -b pages -r https://github.com/iczer/vue-antd-admin.git",
|
||||||
"docs:dev": "vuepress dev docs",
|
"docs:dev": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress dev docs",
|
||||||
"docs:build": "vuepress build docs",
|
"docs:build": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress build docs",
|
||||||
"docs:deploy": "vuepress build docs && gh-pages -d docs/.vuepress/dist -b master -r https://gitee.com/iczer/vue-antd-admin-docs.git"
|
"predocs:deploy": "yarn docs:build",
|
||||||
|
"docs:deploy": "gh-pages -d docs/.vuepress/dist -b doc -r https://github.com/iczer/vue-antd-admin.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antv/data-set": "^0.11.4",
|
"@antv/data-set": "^0.11.4",
|
||||||
"animate.css": "^4.1.0",
|
"animate.css": "^4.1.0",
|
||||||
"ant-design-vue": "^1.6.2",
|
"ant-design-vue": "1.7.2",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"clipboard": "^2.0.6",
|
"clipboard": "^2.0.6",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"date-fns": "^2.14.0",
|
"date-fns": "^2.14.0",
|
||||||
"enquire.js": "^2.1.6",
|
"enquire.js": "^2.1.6",
|
||||||
|
"highlight.js": "^10.2.1",
|
||||||
"js-cookie": "^2.2.1",
|
"js-cookie": "^2.2.1",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
"viser-vue": "^2.4.8",
|
"viser-vue": "^2.4.8",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-i18n": "^8.18.2",
|
"vue-i18n": "^8.18.2",
|
||||||
"vue-router": "^3.3.4",
|
"vue-router": "^3.3.4",
|
||||||
"vuedraggable": "^2.23.2",
|
"vuedraggable": "^2.23.2",
|
||||||
"vuex": "^3.4.0",
|
"vuex": "^3.4.0"
|
||||||
"nprogress": "^0.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/colors": "^4.0.1",
|
"@ant-design/colors": "^4.0.1",
|
||||||
@ -39,7 +41,9 @@
|
|||||||
"@vue/cli-service": "^4.4.0",
|
"@vue/cli-service": "^4.4.0",
|
||||||
"@vuepress/plugin-back-to-top": "^1.5.2",
|
"@vuepress/plugin-back-to-top": "^1.5.2",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
|
"compression-webpack-plugin": "^2.0.0",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
@ -50,10 +54,8 @@
|
|||||||
"vue-cli-plugin-style-resources-loader": "^0.1.4",
|
"vue-cli-plugin-style-resources-loader": "^0.1.4",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.11",
|
||||||
"vuepress": "^1.5.2",
|
"vuepress": "^1.5.2",
|
||||||
"webpack-theme-color-replacer": "^1.3.12",
|
"webpack-theme-color-replacer": "1.3.18",
|
||||||
"whatwg-fetch": "^3.0.0",
|
"whatwg-fetch": "^3.0.0"
|
||||||
"compression-webpack-plugin": "^2.0.0",
|
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
@ -5,17 +5,19 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= process.env.VUE_APP_NAME %></title>
|
||||||
<!-- require cdn assets css -->
|
<!-- require cdn assets css -->
|
||||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
||||||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
|
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
|
||||||
<% } %>
|
<% } %>
|
||||||
</head>
|
</head>
|
||||||
<body class="beauty-scroll">
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="popContainer" class="beauty-scroll" style="height: 100vh; overflow-y: scroll">
|
||||||
|
<div id="app"></div>
|
||||||
|
</div>
|
||||||
<!-- require cdn assets js -->
|
<!-- require cdn assets js -->
|
||||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||||
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
||||||
|
11
src/App.vue
11
src/App.vue
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-config-provider :locale="locale">
|
<a-config-provider :locale="locale" :get-popup-container="popContainer">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</a-config-provider>
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
@ -31,6 +31,7 @@ export default {
|
|||||||
},
|
},
|
||||||
lang(val) {
|
lang(val) {
|
||||||
this.setLanguage(val)
|
this.setLanguage(val)
|
||||||
|
this.setHtmlTitle()
|
||||||
},
|
},
|
||||||
$route() {
|
$route() {
|
||||||
this.setHtmlTitle()
|
this.setHtmlTitle()
|
||||||
@ -42,10 +43,13 @@ export default {
|
|||||||
'theme.color': function(val) {
|
'theme.color': function(val) {
|
||||||
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`)
|
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`)
|
||||||
themeUtil.changeThemeColor(val, this.theme.mode).then(closeMessage)
|
themeUtil.changeThemeColor(val, this.theme.mode).then(closeMessage)
|
||||||
|
},
|
||||||
|
'layout': function() {
|
||||||
|
window.dispatchEvent(new Event('resize'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('setting', ['theme', 'weekMode', 'lang'])
|
...mapState('setting', ['layout', 'theme', 'weekMode', 'lang'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations('setting', ['setDevice']),
|
...mapMutations('setting', ['setDevice']),
|
||||||
@ -76,6 +80,9 @@ export default {
|
|||||||
const key = route.path === '/' ? 'home.name' : getI18nKey(route.matched[route.matched.length - 1].path)
|
const key = route.path === '/' ? 'home.name' : getI18nKey(route.matched[route.matched.length - 1].path)
|
||||||
document.title = process.env.VUE_APP_NAME + ' | ' + this.$t(key)
|
document.title = process.env.VUE_APP_NAME + ' | ' + this.$t(key)
|
||||||
},
|
},
|
||||||
|
popContainer() {
|
||||||
|
return document.getElementById("popContainer")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
BIN
src/assets/img/alipay.png
Normal file
BIN
src/assets/img/alipay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
BIN
src/assets/img/wechatpay.png
Normal file
BIN
src/assets/img/wechatpay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 119 KiB |
7
src/bootstrap.js
vendored
7
src/bootstrap.js
vendored
@ -1,4 +1,4 @@
|
|||||||
import {loadRoutes, loadGuards} from '@/utils/routerUtil'
|
import {loadRoutes, loadGuards, setAppOptions} from '@/utils/routerUtil'
|
||||||
import {loadInterceptors} from '@/utils/request'
|
import {loadInterceptors} from '@/utils/request'
|
||||||
import guards from '@/router/guards'
|
import guards from '@/router/guards'
|
||||||
import interceptors from '@/utils/axios-interceptors'
|
import interceptors from '@/utils/axios-interceptors'
|
||||||
@ -9,12 +9,15 @@ import interceptors from '@/utils/axios-interceptors'
|
|||||||
* @param router 应用的路由实例
|
* @param router 应用的路由实例
|
||||||
* @param store 应用的 vuex.store 实例
|
* @param store 应用的 vuex.store 实例
|
||||||
* @param i18n 应用的 vue-i18n 实例
|
* @param i18n 应用的 vue-i18n 实例
|
||||||
|
* @param i18n 应用的 message 实例
|
||||||
*/
|
*/
|
||||||
function bootstrap({router, store, i18n, message}) {
|
function bootstrap({router, store, i18n, message}) {
|
||||||
|
// 设置应用配置
|
||||||
|
setAppOptions({router, store, i18n})
|
||||||
// 加载 axios 拦截器
|
// 加载 axios 拦截器
|
||||||
loadInterceptors(interceptors, {router, store, i18n, message})
|
loadInterceptors(interceptors, {router, store, i18n, message})
|
||||||
// 加载路由
|
// 加载路由
|
||||||
loadRoutes({router, store, i18n})
|
loadRoutes()
|
||||||
// 加载路由守卫
|
// 加载路由守卫
|
||||||
loadGuards(guards, {router, store, i18n, message})
|
loadGuards(guards, {router, store, i18n, message})
|
||||||
}
|
}
|
||||||
|
37
src/components/cache/AKeepAlive.js
vendored
37
src/components/cache/AKeepAlive.js
vendored
@ -4,7 +4,16 @@ const patternTypes = [String, RegExp, Array]
|
|||||||
|
|
||||||
function matches (pattern, name) {
|
function matches (pattern, name) {
|
||||||
if (Array.isArray(pattern)) {
|
if (Array.isArray(pattern)) {
|
||||||
return pattern.indexOf(name) > -1
|
if (pattern.indexOf(name) > -1) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
for (let item of pattern) {
|
||||||
|
if (isRegExp(item) && item.test(name)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
} else if (typeof pattern === 'string') {
|
} else if (typeof pattern === 'string') {
|
||||||
return pattern.split(',').indexOf(name) > -1
|
return pattern.split(',').indexOf(name) > -1
|
||||||
} else if (isRegExp(pattern)) {
|
} else if (isRegExp(pattern)) {
|
||||||
@ -18,6 +27,13 @@ function getComponentName (opts) {
|
|||||||
return opts && (opts.Ctor.options.name || opts.tag)
|
return opts && (opts.Ctor.options.name || opts.tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getComponentKey (vnode) {
|
||||||
|
const {componentOptions, key} = vnode
|
||||||
|
return key == null
|
||||||
|
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
|
||||||
|
: key + componentOptions.Ctor.cid
|
||||||
|
}
|
||||||
|
|
||||||
function getFirstComponentChild (children) {
|
function getFirstComponentChild (children) {
|
||||||
if (Array.isArray(children)) {
|
if (Array.isArray(children)) {
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
@ -35,7 +51,8 @@ function pruneCache (keepAliveInstance, filter) {
|
|||||||
const cachedNode = cache[key]
|
const cachedNode = cache[key]
|
||||||
if (cachedNode) {
|
if (cachedNode) {
|
||||||
const name = getComponentName(cachedNode.componentOptions)
|
const name = getComponentName(cachedNode.componentOptions)
|
||||||
if (name && !filter(name)) {
|
const componentKey = getComponentKey(cachedNode)
|
||||||
|
if (name && !filter(name, componentKey)) {
|
||||||
pruneCacheEntry(cache, key, keys, _vnode)
|
pruneCacheEntry(cache, key, keys, _vnode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,6 +87,7 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
include: patternTypes,
|
include: patternTypes,
|
||||||
exclude: patternTypes,
|
exclude: patternTypes,
|
||||||
|
excludeKeys: patternTypes,
|
||||||
max: [String, Number],
|
max: [String, Number],
|
||||||
clearCaches: Array
|
clearCaches: Array
|
||||||
},
|
},
|
||||||
@ -98,10 +116,13 @@ export default {
|
|||||||
|
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$watch('include', val => {
|
this.$watch('include', val => {
|
||||||
pruneCache(this, name => matches(val, name))
|
pruneCache(this, (name) => matches(val, name))
|
||||||
})
|
})
|
||||||
this.$watch('exclude', val => {
|
this.$watch('exclude', val => {
|
||||||
pruneCache(this, name => !matches(val, name))
|
pruneCache(this, (name) => !matches(val, name))
|
||||||
|
})
|
||||||
|
this.$watch('excludeKeys', val => {
|
||||||
|
pruneCache(this, (name, key) => !matches(val, key))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -112,12 +133,14 @@ export default {
|
|||||||
if (componentOptions) {
|
if (componentOptions) {
|
||||||
// check pattern
|
// check pattern
|
||||||
const name = getComponentName(componentOptions)
|
const name = getComponentName(componentOptions)
|
||||||
const { include, exclude } = this
|
const componentKey = getComponentKey(vnode)
|
||||||
|
const { include, exclude, excludeKeys } = this
|
||||||
if (
|
if (
|
||||||
// not included
|
// not included
|
||||||
(include && (!name || !matches(include, name))) ||
|
(include && (!name || !matches(include, name))) ||
|
||||||
// excluded
|
// excluded
|
||||||
(exclude && name && matches(exclude, name))
|
(exclude && name && matches(exclude, name)) ||
|
||||||
|
(excludeKeys && componentKey && matches(excludeKeys, componentKey))
|
||||||
) {
|
) {
|
||||||
return vnode
|
return vnode
|
||||||
}
|
}
|
||||||
@ -127,7 +150,7 @@ export default {
|
|||||||
// same constructor may get registered as different local components
|
// same constructor may get registered as different local components
|
||||||
// so cid alone is not enough (#3269)
|
// so cid alone is not enough (#3269)
|
||||||
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
|
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
|
||||||
: vnode.key
|
: vnode.key + componentOptions.Ctor.cid
|
||||||
if (cache[key]) {
|
if (cache[key]) {
|
||||||
vnode.componentInstance = cache[key].componentInstance
|
vnode.componentInstance = cache[key].componentInstance
|
||||||
// make current key freshest
|
// make current key freshest
|
||||||
|
@ -33,6 +33,7 @@ export default {
|
|||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
target: null,
|
target: null,
|
||||||
|
meta: null,
|
||||||
selectedKeys: []
|
selectedKeys: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -45,14 +46,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
const clickHandler = () => this.closeMenu()
|
window.addEventListener('click', this.closeMenu)
|
||||||
const contextMenuHandler = e => this.setPosition(e)
|
window.addEventListener('contextmenu', this.setPosition)
|
||||||
window.addEventListener('click', clickHandler)
|
},
|
||||||
window.addEventListener('contextmenu', contextMenuHandler)
|
beforeDestroy() {
|
||||||
this.$emit('hook:beforeDestroy', () => {
|
window.removeEventListener('click', this.closeMenu)
|
||||||
window.removeEventListener('click', clickHandler)
|
window.removeEventListener('contextmenu', this.setPosition)
|
||||||
window.removeEventListener('contextmenu', contextMenuHandler)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeMenu () {
|
closeMenu () {
|
||||||
@ -62,9 +61,10 @@ export default {
|
|||||||
this.left = e.clientX
|
this.left = e.clientX
|
||||||
this.top = e.clientY
|
this.top = e.clientY
|
||||||
this.target = e.target
|
this.target = e.target
|
||||||
|
this.meta = e.meta
|
||||||
},
|
},
|
||||||
handleClick ({ key }) {
|
handleClick ({ key }) {
|
||||||
this.$emit('select', key, this.target)
|
this.$emit('select', key, this.target, this.meta)
|
||||||
this.closeMenu()
|
this.closeMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,26 @@ import {getI18nKey} from '@/utils/routerUtil'
|
|||||||
|
|
||||||
const {Item, SubMenu} = Menu
|
const {Item, SubMenu} = Menu
|
||||||
|
|
||||||
|
const resolvePath = (path, params = {}) => {
|
||||||
|
let _path = path
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
_path = _path.replace(new RegExp(`:${key}`, 'g'), value)
|
||||||
|
})
|
||||||
|
return _path
|
||||||
|
}
|
||||||
|
|
||||||
|
const toRoutesMap = (routes) => {
|
||||||
|
const map = {}
|
||||||
|
routes.forEach(route => {
|
||||||
|
map[route.fullPath] = route
|
||||||
|
if (route.children && route.children.length > 0) {
|
||||||
|
const childrenMap = toRoutesMap(route.children)
|
||||||
|
Object.assign(map, childrenMap)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'IMenu',
|
name: 'IMenu',
|
||||||
props: {
|
props: {
|
||||||
@ -73,6 +93,9 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
menuTheme() {
|
menuTheme() {
|
||||||
return this.theme == 'light' ? this.theme : 'dark'
|
return this.theme == 'light' ? this.theme : 'dark'
|
||||||
|
},
|
||||||
|
routesMap() {
|
||||||
|
return toRoutesMap(this.options)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
@ -131,10 +154,17 @@ export default {
|
|||||||
return !icon || icon == 'none' ? null : h(Icon, {props: {type: icon}})
|
return !icon || icon == 'none' ? null : h(Icon, {props: {type: icon}})
|
||||||
},
|
},
|
||||||
renderMenuItem: function (h, menu) {
|
renderMenuItem: function (h, menu) {
|
||||||
|
let tag = 'router-link'
|
||||||
|
const path = resolvePath(menu.fullPath, menu.meta.params)
|
||||||
|
let config = {props: {to: {path, query: menu.meta.query}, }, attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;'}}
|
||||||
|
if (menu.meta && menu.meta.link) {
|
||||||
|
tag = 'a'
|
||||||
|
config = {attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;', href: menu.meta.link, target: '_blank'}}
|
||||||
|
}
|
||||||
return h(
|
return h(
|
||||||
Item, {key: menu.fullPath},
|
Item, {key: menu.fullPath},
|
||||||
[
|
[
|
||||||
h('router-link', {props: {to: menu.fullPath}, attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;'}},
|
h(tag, config,
|
||||||
[
|
[
|
||||||
this.renderIcon(h, menu.meta ? menu.meta.icon : 'none', menu.fullPath),
|
this.renderIcon(h, menu.meta ? menu.meta.icon : 'none', menu.fullPath),
|
||||||
this.$t(getI18nKey(menu.fullPath))
|
this.$t(getI18nKey(menu.fullPath))
|
||||||
@ -194,15 +224,23 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateMenu () {
|
updateMenu () {
|
||||||
const menuRoutes = this.$route.matched.filter(item => item.path !== '')
|
this.selectedKeys = this.getSelectedKeys()
|
||||||
this.selectedKeys = this.getSelectedKey(this.$route)
|
let openKeys = this.selectedKeys.filter(item => item !== '')
|
||||||
let openKeys = menuRoutes.map(item => item.path)
|
openKeys = openKeys.slice(0, openKeys.length -1)
|
||||||
if (!fastEqual(openKeys, this.sOpenKeys)) {
|
if (!fastEqual(openKeys, this.sOpenKeys)) {
|
||||||
this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys
|
this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getSelectedKey (route) {
|
getSelectedKeys() {
|
||||||
return route.matched.map(item => item.path)
|
let matches = this.$route.matched
|
||||||
|
const route = matches[matches.length - 1]
|
||||||
|
let chose = this.routesMap[route.path]
|
||||||
|
if (chose && chose.meta && chose.meta.highlight) {
|
||||||
|
chose = this.routesMap[chose.meta.highlight]
|
||||||
|
const resolve = this.$router.resolve({path: chose.fullPath})
|
||||||
|
matches = (resolve.resolved && resolve.resolved.matched) || matches
|
||||||
|
}
|
||||||
|
return matches.map(item => item.path)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render (h) {
|
render (h) {
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
:expandedRowKeys="expandedRowKeys"
|
:expandedRowKeys="expandedRowKeys"
|
||||||
:expandedRowRender="expandedRowRender"
|
:expandedRowRender="expandedRowRender"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
:rowSelection="selectedRows ? {selectedRowKeys: selectedRowKeys, onChange: updateSelect} : undefined"
|
:rowSelection="selectedRows ? {selectedRowKeys, onSelect, onSelectAll} : undefined"
|
||||||
>
|
>
|
||||||
<template slot-scope="text, record, index" :slot="slot" v-for="slot in Object.keys($scopedSlots).filter(key => key !== 'expandedRowRender') ">
|
<template slot-scope="text, record, index" :slot="slot" v-for="slot in Object.keys($scopedSlots).filter(key => key !== 'expandedRowRender') ">
|
||||||
<slot :name="slot" v-bind="{text, record, index}"></slot>
|
<slot :name="slot" v-bind="{text, record, index}"></slot>
|
||||||
@ -64,22 +64,70 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSelect (selectedRowKeys, selectedRows) {
|
equals(record1, record2) {
|
||||||
this.$emit('update:selectedRows', selectedRows)
|
if (record1 === record2) {
|
||||||
this.$emit('selectedRowChange', selectedRowKeys, selectedRows)
|
return true
|
||||||
|
}
|
||||||
|
const {rowKey} = this
|
||||||
|
if (rowKey && typeof rowKey === 'string') {
|
||||||
|
return record1[rowKey] === record2[rowKey]
|
||||||
|
} else if (rowKey && typeof rowKey === 'function') {
|
||||||
|
return rowKey(record1) === rowKey(record2)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
contains(arr, item) {
|
||||||
|
if (!arr || arr.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const {equals} = this
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (equals(arr[i], item)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
onSelectAll(selected, rows) {
|
||||||
|
const {getKey, contains} = this
|
||||||
|
const unselected = this.dataSource.filter(item => !contains(rows, item, this.rowKey))
|
||||||
|
const _selectedRows = this.selectedRows.filter(item => !contains(unselected, item, this.rowKey))
|
||||||
|
const set = {}
|
||||||
|
_selectedRows.forEach(item => set[getKey(item)] = item)
|
||||||
|
rows.forEach(item => set[getKey(item)] = item)
|
||||||
|
const _rows = Object.values(set)
|
||||||
|
this.$emit('update:selectedRows', _rows)
|
||||||
|
this.$emit('selectedRowChange', _rows.map(item => getKey(item)), _rows)
|
||||||
|
},
|
||||||
|
getKey(record) {
|
||||||
|
const {rowKey} = this
|
||||||
|
if (!rowKey || !record) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (typeof rowKey === 'string') {
|
||||||
|
return record[rowKey]
|
||||||
|
} else {
|
||||||
|
return rowKey(record)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelect(record, selected) {
|
||||||
|
const {equals, selectedRows, getKey} = this
|
||||||
|
const _selectedRows = selected ? [...selectedRows, record] : selectedRows.filter(row => !equals(row, record))
|
||||||
|
this.$emit('update:selectedRows', _selectedRows)
|
||||||
|
this.$emit('selectedRowChange', _selectedRows.map(item => getKey(item)), _selectedRows)
|
||||||
},
|
},
|
||||||
initTotalList (columns) {
|
initTotalList (columns) {
|
||||||
const totalList = columns.filter(item => item.needTotal)
|
return columns.filter(item => item.needTotal)
|
||||||
.map(item => {
|
.map(item => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
total: 0
|
total: 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return totalList
|
|
||||||
},
|
},
|
||||||
onClear() {
|
onClear() {
|
||||||
this.updateSelect([], [])
|
this.$emit('update:selectedRows', [])
|
||||||
|
this.$emit('selectedRowChange', [], [])
|
||||||
this.$emit('clear')
|
this.$emit('clear')
|
||||||
},
|
},
|
||||||
onChange(pagination, filters, sorter, {currentDataSource}) {
|
onChange(pagination, filters, sorter, {currentDataSource}) {
|
||||||
@ -95,7 +143,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
total: selectedRows.reduce((sum, val) => {
|
total: selectedRows.reduce((sum, val) => {
|
||||||
return sum + val[item.dataIndex]
|
let v
|
||||||
|
try{
|
||||||
|
v = val[item.dataIndex] ? val[item.dataIndex] : eval(`val.${item.dataIndex}`);
|
||||||
|
}catch(_){
|
||||||
|
v = val[item.dataIndex];
|
||||||
|
}
|
||||||
|
v = !isNaN(parseFloat(v)) ? parseFloat(v) : 0;
|
||||||
|
return sum + v
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -103,10 +158,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
selectedRowKeys() {
|
selectedRowKeys() {
|
||||||
return this.selectedRows.map(record => {
|
return this.selectedRows.map(record => this.getKey(record))
|
||||||
return (typeof this.rowKey === 'function') ? this.rowKey(record) : record[this.rowKey]
|
},
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
155
src/components/table/advance/ActionColumns.vue
Normal file
155
src/components/table/advance/ActionColumns.vue
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<div class="action-columns" ref="root">
|
||||||
|
<a-popover v-model="visible" placement="bottomRight" trigger="click" :get-popup-container="() => $refs.root">
|
||||||
|
<div slot="title">
|
||||||
|
<a-checkbox :indeterminate="indeterminate" :checked="checkAll" @change="onCheckAllChange" class="check-all" />列展示
|
||||||
|
<a-button @click="resetColumns" style="float: right" type="link" size="small">重置</a-button>
|
||||||
|
</div>
|
||||||
|
<a-list style="width: 100%" size="small" :key="i" v-for="(col, i) in columns" slot="content">
|
||||||
|
<a-list-item>
|
||||||
|
<a-checkbox v-model="col.visible" @change="e => onCheckChange(e, col)"/>
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<template slot="actions">
|
||||||
|
<a-tooltip title="固定在列头" :mouseEnterDelay="0.5" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon :class="['left', {active: col.fixed === 'left'}]" @click="fixColumn('left', col)" type="vertical-align-top" />
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="固定在列尾" :mouseEnterDelay="0.5" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon :class="['right', {active: col.fixed === 'right'}]" @click="fixColumn('right', col)" type="vertical-align-bottom" />
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="添加搜索" :mouseEnterDelay="0.5" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon :class="{active: col.searchAble}" @click="setSearch(col)" type="search" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-list-item>
|
||||||
|
</a-list>
|
||||||
|
<a-icon class="action" type="setting" />
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ActionColumns',
|
||||||
|
props: ['columns', 'visibleColumns'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
indeterminate: false,
|
||||||
|
checkAll: true,
|
||||||
|
checkedCounts: this.columns.length,
|
||||||
|
backColumns: cloneDeep(this.columns)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
checkedCounts(val) {
|
||||||
|
this.checkAll = val === this.columns.length
|
||||||
|
this.indeterminate = val > 0 && val < this.columns.length
|
||||||
|
},
|
||||||
|
columns(newVal, oldVal) {
|
||||||
|
if (newVal != oldVal) {
|
||||||
|
this.checkedCounts = newVal.length
|
||||||
|
this.formatColumns(newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.formatColumns(this.columns)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCheckChange(e, col) {
|
||||||
|
if (!col.visible) {
|
||||||
|
this.checkedCounts -= 1
|
||||||
|
} else {
|
||||||
|
this.checkedCounts += 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fixColumn(fixed, col) {
|
||||||
|
if (fixed !== col.fixed) {
|
||||||
|
this.$set(col, 'fixed', fixed)
|
||||||
|
} else {
|
||||||
|
this.$set(col, 'fixed', undefined)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSearch(col) {
|
||||||
|
this.$set(col, 'searchAble', !col.searchAble)
|
||||||
|
if (!col.searchAble && col.search) {
|
||||||
|
this.resetSearch(col)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetSearch(col) {
|
||||||
|
// col.search.value = col.dataType === 'boolean' ? false : undefined
|
||||||
|
col.search.value = undefined
|
||||||
|
col.search.backup = undefined
|
||||||
|
},
|
||||||
|
resetColumns() {
|
||||||
|
const {columns, backColumns} = this
|
||||||
|
let counts = columns.length
|
||||||
|
backColumns.forEach((back, index) => {
|
||||||
|
const column = columns[index]
|
||||||
|
column.visible = back.visible === undefined || back.visible
|
||||||
|
if (!column.visible) {
|
||||||
|
counts -= 1
|
||||||
|
}
|
||||||
|
if (back.fixed !== undefined) {
|
||||||
|
column.fixed = back.fixed
|
||||||
|
} else {
|
||||||
|
this.$set(column, 'fixed', undefined)
|
||||||
|
}
|
||||||
|
this.$set(column, 'searchAble', back.searchAble)
|
||||||
|
// column.searchAble = back.searchAble
|
||||||
|
this.resetSearch(column)
|
||||||
|
})
|
||||||
|
this.checkedCounts = counts
|
||||||
|
this.visible = false
|
||||||
|
this.$emit('reset', this.getConditions(columns))
|
||||||
|
},
|
||||||
|
onCheckAllChange(e) {
|
||||||
|
if (e.target.checked) {
|
||||||
|
this.checkedCounts = this.columns.length
|
||||||
|
this.columns.forEach(col => col.visible = true)
|
||||||
|
} else {
|
||||||
|
this.checkedCounts = 0
|
||||||
|
this.columns.forEach(col => col.visible = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getConditions(columns) {
|
||||||
|
const conditions = {}
|
||||||
|
columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||||
|
.forEach(col => {
|
||||||
|
conditions[col.dataIndex] = col.search.value
|
||||||
|
})
|
||||||
|
return conditions
|
||||||
|
},
|
||||||
|
formatColumns(columns) {
|
||||||
|
for (let col of columns) {
|
||||||
|
if (col.visible === undefined) {
|
||||||
|
this.$set(col, 'visible', true)
|
||||||
|
}
|
||||||
|
if (!col.visible) {
|
||||||
|
this.checkedCounts -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.action-columns{
|
||||||
|
display: inline-block;
|
||||||
|
.check-all{
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.left,.right{
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
.active{
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
44
src/components/table/advance/ActionSize.vue
Normal file
44
src/components/table/advance/ActionSize.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="action-size" ref="root">
|
||||||
|
<a-tooltip title="密度">
|
||||||
|
<a-dropdown placement="bottomCenter" :trigger="['click']" :get-popup-container="() => $refs.root">
|
||||||
|
<a-icon class="action" type="column-height" />
|
||||||
|
<a-menu :selected-keys="[value]" slot="overlay" @click="onClick">
|
||||||
|
<a-menu-item key="default">
|
||||||
|
默认
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
中等
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
紧密
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ActionSize',
|
||||||
|
props: ['value'],
|
||||||
|
inject: ['table'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedKeys: ['middle']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick({key}) {
|
||||||
|
this.$emit('input', key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.action-size{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
249
src/components/table/advance/AdvanceTable.vue
Normal file
249
src/components/table/advance/AdvanceTable.vue
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="table" :id="id" class="advanced-table">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div :class="['header-bar', size]">
|
||||||
|
<div class="title">
|
||||||
|
<template v-if="title">{{title}}</template>
|
||||||
|
<slot v-else-if="$slots.title" name="title"></slot>
|
||||||
|
<template v-else>高级表格</template>
|
||||||
|
</div>
|
||||||
|
<div class="search">
|
||||||
|
<search-area :format-conditions="formatConditions" @change="onSearchChange" :columns="columns" >
|
||||||
|
<template :slot="slot" v-for="slot in slots">
|
||||||
|
<slot :name="slot"></slot>
|
||||||
|
</template>
|
||||||
|
</search-area>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<a-tooltip title="刷新">
|
||||||
|
<a-icon @click="refresh" class="action" :type="loading ? 'loading' : 'reload'" />
|
||||||
|
</a-tooltip>
|
||||||
|
<action-size v-model="sSize" class="action" />
|
||||||
|
<a-tooltip title="列配置">
|
||||||
|
<action-columns :columns="columns" @reset="onColumnsReset" class="action">
|
||||||
|
<template :slot="slot" v-for="slot in slots">
|
||||||
|
<slot :name="slot"></slot>
|
||||||
|
</template>
|
||||||
|
</action-columns>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="全屏">
|
||||||
|
<a-icon @click="toggleScreen" class="action" :type="fullScreen ? 'fullscreen-exit' : 'fullscreen'" />
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-table
|
||||||
|
v-bind="{...$props, columns: visibleColumns, title: undefined, loading: false}"
|
||||||
|
:size="sSize"
|
||||||
|
@expandedRowsChange="onExpandedRowsChange"
|
||||||
|
@change="onChange"
|
||||||
|
@expand="onExpand"
|
||||||
|
>
|
||||||
|
<template slot-scope="text, record, index" :slot="slot" v-for="slot in scopedSlots ">
|
||||||
|
<slot :name="slot" v-bind="{text, record, index}"></slot>
|
||||||
|
</template>
|
||||||
|
<template :slot="slot" v-for="slot in slots">
|
||||||
|
<slot :name="slot"></slot>
|
||||||
|
</template>
|
||||||
|
<template slot-scope="record, index, indent, expanded" :slot="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''">
|
||||||
|
<slot v-bind="{record, index, indent, expanded}" :name="$scopedSlots.expandedRowRender ? 'expandedRowRender' : ''"></slot>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ActionSize from '@/components/table/advance/ActionSize'
|
||||||
|
import ActionColumns from '@/components/table/advance/ActionColumns'
|
||||||
|
import SearchArea from '@/components/table/advance/SearchArea'
|
||||||
|
export default {
|
||||||
|
name: 'AdvanceTable',
|
||||||
|
components: {SearchArea, ActionColumns, ActionSize},
|
||||||
|
props: {
|
||||||
|
tableLayout: String,
|
||||||
|
bordered: Boolean,
|
||||||
|
childrenColumnName: {type: String, default: 'children'},
|
||||||
|
columns: Array,
|
||||||
|
components: Object,
|
||||||
|
dataSource: Array,
|
||||||
|
defaultExpandAllRows: Array[String],
|
||||||
|
expandedRowKeys: Array[String],
|
||||||
|
expandedRowRender: Function,
|
||||||
|
expandIcon: Function,
|
||||||
|
expandRowByClick: Boolean,
|
||||||
|
expandIconColumnIndex: Number,
|
||||||
|
footer: Function,
|
||||||
|
indentSize: Number,
|
||||||
|
loading: Boolean,
|
||||||
|
locale: Object,
|
||||||
|
pagination: [Object, Boolean],
|
||||||
|
rowClassName: Function,
|
||||||
|
rowKey: [String, Function],
|
||||||
|
rowSelection: Object,
|
||||||
|
scroll: Object,
|
||||||
|
showHeader: {type: Boolean, default: true},
|
||||||
|
size: String,
|
||||||
|
title: String,
|
||||||
|
customHeaderRow: Function,
|
||||||
|
customRow: Function,
|
||||||
|
getPopupContainer: Function,
|
||||||
|
transformCellText: Function,
|
||||||
|
formatConditions: Boolean
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
table: this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: `${new Date().getTime()}-${Math.floor(Math.random() * 10)}`,
|
||||||
|
sSize: this.size || 'default',
|
||||||
|
fullScreen: false,
|
||||||
|
conditions: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
slots() {
|
||||||
|
return Object.keys(this.$slots).filter(slot => slot !== 'title')
|
||||||
|
},
|
||||||
|
scopedSlots() {
|
||||||
|
return Object.keys(this.$scopedSlots).filter(slot => slot !== 'expandedRowRender' && slot !== 'title')
|
||||||
|
},
|
||||||
|
visibleColumns(){
|
||||||
|
return this.columns.filter(col => col.visible)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.addListener()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.removeListener()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refresh() {
|
||||||
|
this.$emit('refresh', this.conditions)
|
||||||
|
},
|
||||||
|
onSearchChange(conditions, searchOptions) {
|
||||||
|
this.conditions = conditions
|
||||||
|
this.$emit('search', conditions, searchOptions)
|
||||||
|
},
|
||||||
|
toggleScreen() {
|
||||||
|
if (this.fullScreen) {
|
||||||
|
this.outFullScreen()
|
||||||
|
} else {
|
||||||
|
this.inFullScreen()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inFullScreen() {
|
||||||
|
const el = this.$refs.table
|
||||||
|
el.classList.add('beauty-scroll')
|
||||||
|
if (el.requestFullscreen) {
|
||||||
|
el.requestFullscreen()
|
||||||
|
return true
|
||||||
|
} else if (el.webkitRequestFullScreen) {
|
||||||
|
el.webkitRequestFullScreen()
|
||||||
|
return true
|
||||||
|
} else if (el.mozRequestFullScreen) {
|
||||||
|
el.mozRequestFullScreen()
|
||||||
|
return true
|
||||||
|
} else if (el.msRequestFullscreen) {
|
||||||
|
el.msRequestFullscreen()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
this.$message.warn('对不起,您的浏览器不支持全屏模式')
|
||||||
|
el.classList.remove('beauty-scroll')
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
outFullScreen() {
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen()
|
||||||
|
} else if (document.webkitCancelFullScreen) {
|
||||||
|
document.webkitCancelFullScreen();
|
||||||
|
} else if (document.mozCancelFullScreen) {
|
||||||
|
document.mozCancelFullScreen()
|
||||||
|
} else if (document.msExitFullscreen) {
|
||||||
|
document.msExitFullscreen()
|
||||||
|
}
|
||||||
|
this.$refs.table.classList.remove('beauty-scroll')
|
||||||
|
},
|
||||||
|
onColumnsReset(conditions) {
|
||||||
|
this.$emit('reset', conditions)
|
||||||
|
},
|
||||||
|
onExpandedRowsChange(expandedRows) {
|
||||||
|
this.$emit('expandedRowsChange', expandedRows)
|
||||||
|
},
|
||||||
|
onChange(pagination, filters, sorter, options) {
|
||||||
|
this.$emit('change', pagination, filters, sorter, options)
|
||||||
|
},
|
||||||
|
onExpand(expanded, record) {
|
||||||
|
this.$emit('expand', expanded, record)
|
||||||
|
},
|
||||||
|
addListener() {
|
||||||
|
document.addEventListener('fullscreenchange', this.fullScreenListener)
|
||||||
|
document.addEventListener('webkitfullscreenchange', this.fullScreenListener)
|
||||||
|
document.addEventListener('mozfullscreenchange', this.fullScreenListener)
|
||||||
|
document.addEventListener('msfullscreenchange', this.fullScreenListener)
|
||||||
|
},
|
||||||
|
removeListener() {
|
||||||
|
document.removeEventListener('fullscreenchange', this.fullScreenListener)
|
||||||
|
document.removeEventListener('webkitfullscreenchange', this.fullScreenListener)
|
||||||
|
document.removeEventListener('mozfullscreenchange', this.fullScreenListener)
|
||||||
|
document.removeEventListener('msfullscreenchange', this.fullScreenListener)
|
||||||
|
},
|
||||||
|
fullScreenListener(e) {
|
||||||
|
if (e.target.id === this.id) {
|
||||||
|
this.fullScreen = !this.fullScreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.advanced-table{
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: @component-background;
|
||||||
|
.header-bar{
|
||||||
|
padding: 16px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
&.middle{
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
&.small{
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-bottom: 0;
|
||||||
|
.title{
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
transition: all 0.3s;
|
||||||
|
font-size: 18px;
|
||||||
|
color: @title-color;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.search{
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
margin: 0 24px;
|
||||||
|
}
|
||||||
|
.actions{
|
||||||
|
text-align: right;
|
||||||
|
font-size: 17px;
|
||||||
|
color: @text-color;
|
||||||
|
.action{
|
||||||
|
margin: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover{
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
313
src/components/table/advance/SearchArea.vue
Normal file
313
src/components/table/advance/SearchArea.vue
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-area" ref="root">
|
||||||
|
<div class="select-root" ref="selectRoot"></div>
|
||||||
|
<div class="search-item" :key="index" v-for="(col, index) in searchCols">
|
||||||
|
<div v-if="col.dataType === 'boolean'" :class="['title', {active: col.search.value !== undefined}]">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-switch @change="onSwitchChange(col)" class="switch" v-model="col.search.value" size="small"
|
||||||
|
:checked-children="(col.search.switchOptions && col.search.switchOptions.checkedText) || '是'"
|
||||||
|
:un-checked-children="(col.search.switchOptions && col.search.switchOptions.uncheckedText) || '否'"
|
||||||
|
/>
|
||||||
|
<a-icon v-if="col.search.value !== undefined" class="close" @click="e => onCloseClick(e, col)" type="close-circle" theme="filled" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'time'" :class="['title', {active: col.search.value}]">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-time-picker :format="col.search.format" v-model="col.search.value" placeholder="选择时间" @change="(time, timeStr) => onCalendarChange(time, timeStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="time-picker" size="small" :get-popup-container="() => $refs.root"/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'date'" :class="['title', {active: col.search.value}]">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-date-picker :format="col.search.format" v-model="col.search.value" @change="onDateChange(col)" class="date-picker" size="small" :getCalendarContainer="() => $refs.root"/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'datetime'" class="title datetime active">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-date-picker :format="col.search.format" v-model="col.search.value" @change="(date, dateStr) => onCalendarChange(date, dateStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="datetime-picker" size="small" show-time :getCalendarContainer="() => $refs.root"/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="col.dataType === 'select'" :class="['title', {active: col.search.value !== undefined}]">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}:
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<a-select :allowClear="true" :options="col.search.selectOptions" v-model="col.search.value" placeholder="请选择..." @change="onSelectChange(col)" class="select" slot="content" size="small" :get-popup-container="() => $refs.selectRoot">
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
<div v-else :class="['title', {active: col.search.value}]">
|
||||||
|
<a-popover @visibleChange="onVisibleChange(col, index)" v-model="col.search.visible" placement="bottom" :trigger="['click']" :get-popup-container="() => $refs.root">
|
||||||
|
<template v-if="col.title">
|
||||||
|
{{col.title}}
|
||||||
|
</template>
|
||||||
|
<slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
|
||||||
|
<div class="value " v-if="col.search.value">: {{col.search.format && typeof col.search.format === 'function' ? col.search.format(col.search.value) : col.search.value}}</div>
|
||||||
|
<a-icon v-if="!col.search.value" class="icon-down" type="down"/>
|
||||||
|
<div class="operations" slot="content">
|
||||||
|
<a-button @click="onCancel(col)" class="btn" size="small" type="link">取消</a-button>
|
||||||
|
<a-button @click="onConfirm(col)" class="btn" size="small" type="primary">确认</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="search-overlay" slot="title">
|
||||||
|
<a-input :id="`${searchIdPrefix}${index}`" :allow-clear="true" @keyup.esc="onCancel(col)" @keyup.enter="onConfirm(col)" v-model="col.search.value" size="small" />
|
||||||
|
</div>
|
||||||
|
</a-popover>
|
||||||
|
<a-icon v-if="col.search.value" @click="e => onCloseClick(e, col)" class="close" type="close-circle" theme="filled"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import fastEqual from 'fast-deep-equal'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SearchArea',
|
||||||
|
props: ['columns', 'formatConditions'],
|
||||||
|
inject: ['table'],
|
||||||
|
created() {
|
||||||
|
this.formatColumns(this.columns)
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
columns(newVal, oldVal) {
|
||||||
|
if (newVal != oldVal) {
|
||||||
|
this.formatColumns(newVal)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchCols(newVal, oldVal) {
|
||||||
|
if (newVal.length != oldVal.length) {
|
||||||
|
const newConditions = this.getConditions(newVal)
|
||||||
|
const newSearchOptions = this.getSearchOptions(newVal)
|
||||||
|
if (!fastEqual(newConditions, this.conditions)) {
|
||||||
|
this.conditions = newConditions
|
||||||
|
this.searchOptions = newSearchOptions
|
||||||
|
this.$emit('change', this.conditions, this.searchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
conditions: {},
|
||||||
|
searchOptions: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
searchCols() {
|
||||||
|
return this.columns.filter(item => item.searchAble)
|
||||||
|
},
|
||||||
|
searchIdPrefix() {
|
||||||
|
return this.table.id + '-ipt-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCloseClick(e, col) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
col.search.value = undefined
|
||||||
|
const {backup, value} = col.search
|
||||||
|
if (backup !== value) {
|
||||||
|
this.backupAndEmitChange(col)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel(col) {
|
||||||
|
col.search.value = col.search.backup
|
||||||
|
col.search.visible = false
|
||||||
|
},
|
||||||
|
onConfirm(col) {
|
||||||
|
const {backup, value} = col.search
|
||||||
|
col.search.visible = false
|
||||||
|
if (backup !== value) {
|
||||||
|
this.backupAndEmitChange(col)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSwitchChange(col) {
|
||||||
|
const {backup, value} = col.search
|
||||||
|
if (backup !== value) {
|
||||||
|
this.backupAndEmitChange(col)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelectChange(col) {
|
||||||
|
this.backupAndEmitChange(col)
|
||||||
|
},
|
||||||
|
onCalendarOpenChange(open, col) {
|
||||||
|
col.search.visible = open
|
||||||
|
const {momentEqual, backupAndEmitChange} = this
|
||||||
|
const {value, backup, format} = col.search
|
||||||
|
if (!open && !momentEqual(value, backup, format)) {
|
||||||
|
backupAndEmitChange(col, moment(value))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCalendarChange(date, dateStr, col) {
|
||||||
|
const {momentEqual, backupAndEmitChange} = this
|
||||||
|
const {value, backup, format} = col.search
|
||||||
|
if (!col.search.visible && !momentEqual(value, backup, format)) {
|
||||||
|
backupAndEmitChange(col, moment(value))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDateChange(col) {
|
||||||
|
const {momentEqual, backupAndEmitChange} = this
|
||||||
|
const {value, backup, format} = col.search
|
||||||
|
if (!momentEqual(value, backup, format)) {
|
||||||
|
backupAndEmitChange(col, moment(value))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFormat(col) {
|
||||||
|
if (col.search && col.search.format) {
|
||||||
|
return col.search.format
|
||||||
|
}
|
||||||
|
const dataType = col.dataType
|
||||||
|
switch(dataType) {
|
||||||
|
case 'time': return 'HH:mm:ss'
|
||||||
|
case 'date': return 'YYYY-MM-DD'
|
||||||
|
case 'datetime': return 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
default: return undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backupAndEmitChange(col, backValue = col.search.value) {
|
||||||
|
const {getConditions, getSearchOptions} = this
|
||||||
|
col.search.backup = backValue
|
||||||
|
this.conditions = getConditions(this.searchCols)
|
||||||
|
this.searchOptions = getSearchOptions(this.searchCols)
|
||||||
|
this.$emit('change', this.conditions, this.searchOptions)
|
||||||
|
},
|
||||||
|
getConditions(columns) {
|
||||||
|
const conditions = {}
|
||||||
|
columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||||
|
.forEach(col => {
|
||||||
|
const {value, format} = col.search
|
||||||
|
if (this.formatConditions && format) {
|
||||||
|
if (typeof format === 'function') {
|
||||||
|
conditions[col.dataIndex] = format(col.search.value)
|
||||||
|
} else if (typeof format === 'string' && value.constructor.name === 'Moment') {
|
||||||
|
conditions[col.dataIndex] = value.format(format)
|
||||||
|
} else {
|
||||||
|
conditions[col.dataIndex] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conditions[col.dataIndex] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return conditions
|
||||||
|
},
|
||||||
|
getSearchOptions(columns) {
|
||||||
|
return columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
|
||||||
|
.map(({dataIndex, search}) => ({field: dataIndex, value: search.value, format: search.format}))
|
||||||
|
},
|
||||||
|
onVisibleChange(col, index) {
|
||||||
|
if (!col.search.visible) {
|
||||||
|
col.search.value = col.search.backup
|
||||||
|
} else {
|
||||||
|
let input = document.getElementById(`${this.searchIdPrefix}${index}`)
|
||||||
|
if (input) {
|
||||||
|
setTimeout(() => {input.focus()}, 0)
|
||||||
|
} else {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
input = document.getElementById(`${this.searchIdPrefix}${index}`)
|
||||||
|
input.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
momentEqual(target, source, format) {
|
||||||
|
if (target === source) {
|
||||||
|
return true
|
||||||
|
} else if (target && source && target.format(format) === source.format(format)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
formatColumns(columns) {
|
||||||
|
columns.forEach(item => {
|
||||||
|
this.$set(item, 'search', {...item.search, visible: false, value: undefined, format: this.getFormat(item)})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.search-area{
|
||||||
|
.select-root{
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
margin: -4px 0;
|
||||||
|
.search-item{
|
||||||
|
margin: 4px 4px;
|
||||||
|
display: inline-block;
|
||||||
|
.title{
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
user-select: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
.close{
|
||||||
|
color: @text-color-second;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
:hover{
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.switch{
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.time-picker{
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 96px;
|
||||||
|
}
|
||||||
|
.date-picker{
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
.datetime-picker{
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 195px;
|
||||||
|
}
|
||||||
|
.value{
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
flex:1;
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 144px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
&.active{
|
||||||
|
background-color: @layout-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-down{
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search-overlay{
|
||||||
|
padding: 8px 0px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.select{
|
||||||
|
margin-left: 4px;
|
||||||
|
max-width: 144px;
|
||||||
|
min-width: 96px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.operations{
|
||||||
|
display: flex;
|
||||||
|
margin: -6px 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
.btn{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
2
src/components/table/advance/index.js
Normal file
2
src/components/table/advance/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import AdvanceTable from './AdvanceTable'
|
||||||
|
export default AdvanceTable
|
49
src/components/table/api/ApiTable.vue
Normal file
49
src/components/table/api/ApiTable.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<a-table :data-source="apiSource" :pagination="false">
|
||||||
|
<h2 v-if="title" style="margin: 0 16px 0" slot="title">{{title}}</h2>
|
||||||
|
<a-table-column width="20%" data-index="param" title="参数">
|
||||||
|
<div slot-scope="text" v-html="text"></div>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column width="50%" data-index="desc" title="说明">
|
||||||
|
<div slot-scope="text" v-html="text"></div>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column v-if="isApi" width="15%" data-index="type" title="类型">
|
||||||
|
<div slot-scope="text" v-html="text"></div>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column v-if="isApi" width="15%" data-index="default" title="默认值">
|
||||||
|
<div slot-scope="text" v-html="text"></div>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column v-if="!isApi" width="30%" data-index="callback" title="回调函数">
|
||||||
|
<div slot-scope="text" v-html="text"></div>
|
||||||
|
</a-table-column>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ApiTable',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: 'API'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'api',
|
||||||
|
validator(value) {
|
||||||
|
return ['api', 'event'].includes(value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
apiSource: Array
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isApi() {
|
||||||
|
return this.type === 'api'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -35,7 +35,7 @@ const Item = {
|
|||||||
return h(
|
return h(
|
||||||
'li',
|
'li',
|
||||||
{class: 'avatar-item'},
|
{class: 'avatar-item'},
|
||||||
[!this.$props.tips ? h(ATooltip, {props: {title: this.$props.tips}}, [avatar]) : avatar]
|
[this.$props.tips ? h(ATooltip, {props: {title: this.$props.tips}}, [avatar]) : avatar]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,11 @@ const ANTD = {
|
|||||||
'component-background': '#fff',
|
'component-background': '#fff',
|
||||||
'heading-color': 'rgba(0, 0, 0, 0.85)',
|
'heading-color': 'rgba(0, 0, 0, 0.85)',
|
||||||
'text-color': 'rgba(0, 0, 0, 0.65)',
|
'text-color': 'rgba(0, 0, 0, 0.65)',
|
||||||
'text-color-inverse': '#fff',
|
'text-color-inverse': '#fefefe',
|
||||||
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
|
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
|
||||||
'shadow-color': 'rgba(0, 0, 0, 0.15)',
|
'shadow-color': 'rgba(0, 0, 0, 0.15)',
|
||||||
'border-color-split': '#f0f0f0',
|
'border-color-split': '#f0f0f0',
|
||||||
|
'border-color-base': '#d9d9d9',
|
||||||
'background-color-light': '#fafafa',
|
'background-color-light': '#fafafa',
|
||||||
'background-color-base': '#f5f5f5',
|
'background-color-base': '#f5f5f5',
|
||||||
'table-selected-row-bg': '#fafafa',
|
'table-selected-row-bg': '#fafafa',
|
||||||
@ -34,8 +35,9 @@ const ANTD = {
|
|||||||
'disabled-color': 'rgba(0, 0, 0, 0.25)',
|
'disabled-color': 'rgba(0, 0, 0, 0.25)',
|
||||||
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
|
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
|
||||||
'menu-dark-highlight-color': '#fefefe',
|
'menu-dark-highlight-color': '#fefefe',
|
||||||
|
'menu-dark-selected-item-icon-color': '#fefefe',
|
||||||
'menu-dark-arrow-color': '#fefefe',
|
'menu-dark-arrow-color': '#fefefe',
|
||||||
'btn-primary-color': '#fff',
|
'btn-primary-color': '#fefefe',
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
'layout-body-background': '#f0f2f5',
|
'layout-body-background': '#f0f2f5',
|
||||||
@ -43,10 +45,11 @@ const ANTD = {
|
|||||||
'component-background': '#fff',
|
'component-background': '#fff',
|
||||||
'heading-color': 'rgba(0, 0, 0, 0.85)',
|
'heading-color': 'rgba(0, 0, 0, 0.85)',
|
||||||
'text-color': 'rgba(0, 0, 0, 0.65)',
|
'text-color': 'rgba(0, 0, 0, 0.65)',
|
||||||
'text-color-inverse': '#fff',
|
'text-color-inverse': '#fefefe',
|
||||||
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
|
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
|
||||||
'shadow-color': 'rgba(0, 0, 0, 0.15)',
|
'shadow-color': 'rgba(0, 0, 0, 0.15)',
|
||||||
'border-color-split': '#f0f0f0',
|
'border-color-split': '#f0f0f0',
|
||||||
|
'border-color-base': '#d9d9d9',
|
||||||
'background-color-light': '#fafafa',
|
'background-color-light': '#fafafa',
|
||||||
'background-color-base': '#f5f5f5',
|
'background-color-base': '#f5f5f5',
|
||||||
'table-selected-row-bg': '#fafafa',
|
'table-selected-row-bg': '#fafafa',
|
||||||
@ -55,8 +58,9 @@ const ANTD = {
|
|||||||
'disabled-color': 'rgba(0, 0, 0, 0.25)',
|
'disabled-color': 'rgba(0, 0, 0, 0.25)',
|
||||||
'menu-dark-color': 'rgba(1, 1, 1, 0.65)',
|
'menu-dark-color': 'rgba(1, 1, 1, 0.65)',
|
||||||
'menu-dark-highlight-color': '#fefefe',
|
'menu-dark-highlight-color': '#fefefe',
|
||||||
|
'menu-dark-selected-item-icon-color': '#fefefe',
|
||||||
'menu-dark-arrow-color': '#fefefe',
|
'menu-dark-arrow-color': '#fefefe',
|
||||||
'btn-primary-color': '#fff',
|
'btn-primary-color': '#fefefe',
|
||||||
},
|
},
|
||||||
night: {
|
night: {
|
||||||
'layout-body-background': '#000',
|
'layout-body-background': '#000',
|
||||||
@ -64,10 +68,11 @@ const ANTD = {
|
|||||||
'component-background': '#141414',
|
'component-background': '#141414',
|
||||||
'heading-color': 'rgba(255, 255, 255, 0.85)',
|
'heading-color': 'rgba(255, 255, 255, 0.85)',
|
||||||
'text-color': 'rgba(255, 255, 255, 0.85)',
|
'text-color': 'rgba(255, 255, 255, 0.85)',
|
||||||
'text-color-inverse': '#141414',
|
'text-color-inverse': '#fefefe',
|
||||||
'text-color-secondary': 'rgba(255, 255, 255, 0.45)',
|
'text-color-secondary': 'rgba(255, 255, 255, 0.45)',
|
||||||
'shadow-color': 'rgba(255, 255, 255, 0.15)',
|
'shadow-color': 'rgba(255, 255, 255, 0.15)',
|
||||||
'border-color-split': '#303030',
|
'border-color-split': '#303030',
|
||||||
|
'border-color-base': '#282828',
|
||||||
'background-color-light': '#ffffff0a',
|
'background-color-light': '#ffffff0a',
|
||||||
'background-color-base': '#2a2a2a',
|
'background-color-base': '#2a2a2a',
|
||||||
'table-selected-row-bg': '#ffffff0a',
|
'table-selected-row-bg': '#ffffff0a',
|
||||||
@ -76,8 +81,9 @@ const ANTD = {
|
|||||||
'disabled-color': 'rgba(255, 255, 255, 0.25)',
|
'disabled-color': 'rgba(255, 255, 255, 0.25)',
|
||||||
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
|
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
|
||||||
'menu-dark-highlight-color': '#fefefe',
|
'menu-dark-highlight-color': '#fefefe',
|
||||||
|
'menu-dark-selected-item-icon-color': '#fefefe',
|
||||||
'menu-dark-arrow-color': '#fefefe',
|
'menu-dark-arrow-color': '#fefefe',
|
||||||
'btn-primary-color': '#141414',
|
'btn-primary-color': '#fefefe',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,16 @@ module.exports = {
|
|||||||
mode: 'dark', //主题模式 可选 dark、 light 和 night
|
mode: 'dark', //主题模式 可选 dark、 light 和 night
|
||||||
success: '#52c41a', //成功色
|
success: '#52c41a', //成功色
|
||||||
warning: '#faad14', //警告色
|
warning: '#faad14', //警告色
|
||||||
error: '#f5222d', //错误色
|
error: '#f5222f', //错误色
|
||||||
},
|
},
|
||||||
layout: 'side', //导航布局,可选 side 和 head,分别为侧边导航和顶部导航
|
layout: 'side', //导航布局,可选 side 和 head,分别为侧边导航和顶部导航
|
||||||
fixedHeader: false, //固定头部状态栏,true:固定,false:不固定
|
fixedHeader: false, //固定头部状态栏,true:固定,false:不固定
|
||||||
fixedSideBar: true, //固定侧边栏,true:固定,false:不固定
|
fixedSideBar: true, //固定侧边栏,true:固定,false:不固定
|
||||||
|
fixedTabs: false, //固定页签头,true:固定,false:不固定
|
||||||
pageWidth: 'fixed', //内容区域宽度,fixed:固定宽度,fluid:流式宽度
|
pageWidth: 'fixed', //内容区域宽度,fixed:固定宽度,fluid:流式宽度
|
||||||
weekMode: false, //色弱模式,true:开启,false:不开启
|
weekMode: false, //色弱模式,true:开启,false:不开启
|
||||||
multiPage: false, //多页签模式,true:开启,false:不开启
|
multiPage: false, //多页签模式,true:开启,false:不开启
|
||||||
|
cachePage: true, //是否缓存页面数据,仅多页签模式下生效,true 缓存, false 不缓存
|
||||||
hideSetting: false, //隐藏设置抽屉,true:隐藏,false:不隐藏
|
hideSetting: false, //隐藏设置抽屉,true:隐藏,false:不隐藏
|
||||||
systemName: 'Vue Antd Admin', //系统名称
|
systemName: 'Vue Antd Admin', //系统名称
|
||||||
copyright: '2018 ICZER 工作室出品', //copyright
|
copyright: '2018 ICZER 工作室出品', //copyright
|
||||||
|
@ -19,12 +19,24 @@ const cssResolve = {
|
|||||||
return cssObj.toText()
|
return cssObj.toText()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after': {
|
||||||
|
resolve(cssText, cssObj) {
|
||||||
|
cssObj.rules.push('border-top:0', 'border-left:0')
|
||||||
|
return cssObj.toText()
|
||||||
|
}
|
||||||
|
},
|
||||||
'.ant-checkbox-checked .ant-checkbox-inner:after': {
|
'.ant-checkbox-checked .ant-checkbox-inner:after': {
|
||||||
resolve(cssText, cssObj) {
|
resolve(cssText, cssObj) {
|
||||||
cssObj.rules.push('border-top:0', 'border-left:0')
|
cssObj.rules.push('border-top:0', 'border-left:0')
|
||||||
return cssObj.toText()
|
return cssObj.toText()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after': {
|
||||||
|
resolve(cssText, cssObj) {
|
||||||
|
cssObj.rules.push('border-top:0', 'border-left:0')
|
||||||
|
return cssObj.toText()
|
||||||
|
}
|
||||||
|
},
|
||||||
'.ant-menu-dark .ant-menu-inline.ant-menu-sub': {
|
'.ant-menu-dark .ant-menu-inline.ant-menu-sub': {
|
||||||
resolve(cssText, cssObj) {
|
resolve(cssText, cssObj) {
|
||||||
cssObj.rules = cssObj.rules.filter(rule => rule.indexOf('box-shadow') == -1)
|
cssObj.rules = cssObj.rules.filter(rule => rule.indexOf('box-shadow') == -1)
|
||||||
|
@ -12,10 +12,10 @@
|
|||||||
<setting />
|
<setting />
|
||||||
</drawer>
|
</drawer>
|
||||||
<a-layout class="admin-layout-main beauty-scroll">
|
<a-layout class="admin-layout-main beauty-scroll">
|
||||||
<admin-header :style="headerStyle" :menuData="headMenuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
|
<admin-header :class="[{'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage}]" :style="headerStyle" :menuData="headMenuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
|
||||||
<a-layout-header v-if="fixedHeader"></a-layout-header>
|
<a-layout-header :class="['virtual-header', {'fixed-tabs' : fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage}]" v-show="fixedHeader"></a-layout-header>
|
||||||
<a-layout-content class="admin-layout-content">
|
<a-layout-content class="admin-layout-content" :style="`min-height: ${minHeight}px;`">
|
||||||
<div :style="`min-height: ${minHeight}px; position: relative`">
|
<div style="position: relative">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
@ -34,19 +34,24 @@ import SideMenu from '../components/menu/SideMenu'
|
|||||||
import Setting from '../components/setting/Setting'
|
import Setting from '../components/setting/Setting'
|
||||||
import {mapState, mapMutations, mapGetters} from 'vuex'
|
import {mapState, mapMutations, mapGetters} from 'vuex'
|
||||||
|
|
||||||
const minHeight = window.innerHeight - 64 - 24 - 122
|
// const minHeight = window.innerHeight - 64 - 122
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AdminLayout',
|
name: 'AdminLayout',
|
||||||
components: {Setting, SideMenu, Drawer, PageFooter, AdminHeader},
|
components: {Setting, SideMenu, Drawer, PageFooter, AdminHeader},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
minHeight: minHeight,
|
minHeight: window.innerHeight - 64 - 122,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
showSetting: false,
|
showSetting: false,
|
||||||
drawerOpen: false
|
drawerOpen: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
adminLayout: this
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route(val) {
|
$route(val) {
|
||||||
this.setActivated(val)
|
this.setActivated(val)
|
||||||
@ -62,7 +67,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar',
|
...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar',
|
||||||
'hideSetting']),
|
'fixedTabs', 'hideSetting', 'multiPage']),
|
||||||
...mapGetters('setting', ['firstMenu', 'subMenu', 'menuData']),
|
...mapGetters('setting', ['firstMenu', 'subMenu', 'menuData']),
|
||||||
sideMenuWidth() {
|
sideMenuWidth() {
|
||||||
return this.collapsed ? '80px' : '256px'
|
return this.collapsed ? '80px' : '256px'
|
||||||
@ -70,8 +75,7 @@ export default {
|
|||||||
headerStyle() {
|
headerStyle() {
|
||||||
let width = (this.fixedHeader && this.layout !== 'head' && !this.isMobile) ? `calc(100% - ${this.sideMenuWidth})` : '100%'
|
let width = (this.fixedHeader && this.layout !== 'head' && !this.isMobile) ? `calc(100% - ${this.sideMenuWidth})` : '100%'
|
||||||
let position = this.fixedHeader ? 'fixed' : 'static'
|
let position = this.fixedHeader ? 'fixed' : 'static'
|
||||||
let transition = this.fixedHeader ? 'transition: width 0.2s' : ''
|
return `width: ${width}; position: ${position};`
|
||||||
return `width: ${width}; position: ${position}; ${transition}`
|
|
||||||
},
|
},
|
||||||
headMenuData() {
|
headMenuData() {
|
||||||
const {layout, menuData, firstMenu} = this
|
const {layout, menuData, firstMenu} = this
|
||||||
@ -105,11 +109,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.correctPageMinHeight(minHeight - 1)
|
this.correctPageMinHeight(this.minHeight - 24)
|
||||||
this.setActivated(this.$route)
|
this.setActivated(this.$route)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.correctPageMinHeight(-minHeight + 1)
|
this.correctPageMinHeight(-this.minHeight + 24)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -127,16 +131,28 @@ export default {
|
|||||||
.virtual-side{
|
.virtual-side{
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
.virtual-header{
|
||||||
|
transition: all 0.2s;
|
||||||
|
opacity: 0;
|
||||||
|
&.fixed-tabs.multi-page:not(.fixed-header){
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
.admin-layout-main{
|
.admin-layout-main{
|
||||||
.admin-header{
|
.admin-header{
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s;
|
||||||
|
&.fixed-tabs.multi-page:not(.fixed-header){
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.admin-layout-content{
|
.admin-layout-content{
|
||||||
padding: 24px 24px 0;
|
padding: 24px 24px 0;
|
||||||
overflow-x: hidden;
|
/*overflow-x: hidden;*/
|
||||||
min-height: calc(100vh - 64px - 122px);
|
/*min-height: calc(100vh - 64px - 122px);*/
|
||||||
}
|
}
|
||||||
.setting{
|
.setting{
|
||||||
background-color: @primary-color;
|
background-color: @primary-color;
|
||||||
|
@ -60,10 +60,10 @@ export default {
|
|||||||
this.updatePageHeight(0)
|
this.updatePageHeight(0)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('setting', ['layout', 'multiPage', 'pageMinHeight', 'pageWidth']),
|
...mapState('setting', ['layout', 'multiPage', 'pageMinHeight', 'pageWidth', 'customTitles']),
|
||||||
pageTitle() {
|
pageTitle() {
|
||||||
let pageTitle = this.page && this.page.title
|
let pageTitle = this.page && this.page.title
|
||||||
return pageTitle === undefined ? (this.title || this.routeName) : this.$t(pageTitle)
|
return this.customTitle || (pageTitle && this.$t(pageTitle)) || this.title || this.routeName
|
||||||
},
|
},
|
||||||
routeName() {
|
routeName() {
|
||||||
const route = this.$route
|
const route = this.$route
|
||||||
@ -90,11 +90,17 @@ export default {
|
|||||||
...mapMutations('setting', ['correctPageMinHeight']),
|
...mapMutations('setting', ['correctPageMinHeight']),
|
||||||
getRouteBreadcrumb() {
|
getRouteBreadcrumb() {
|
||||||
let routes = this.$route.matched
|
let routes = this.$route.matched
|
||||||
|
const path = this.$route.path
|
||||||
let breadcrumb = []
|
let breadcrumb = []
|
||||||
routes.forEach(route => {
|
routes.filter(item => path.includes(item.path) || item.regex.test(path))
|
||||||
|
.forEach(route => {
|
||||||
const path = route.path.length === 0 ? '/home' : route.path
|
const path = route.path.length === 0 ? '/home' : route.path
|
||||||
breadcrumb.push(this.$t(getI18nKey(path)))
|
breadcrumb.push(this.$t(getI18nKey(path)))
|
||||||
})
|
})
|
||||||
|
let pageTitle = this.page && this.page.title
|
||||||
|
if (this.customTitle || pageTitle) {
|
||||||
|
breadcrumb[breadcrumb.length - 1] = this.customTitle || pageTitle
|
||||||
|
}
|
||||||
return breadcrumb
|
return breadcrumb
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -25,13 +25,13 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState('setting', ['isMobile', 'multiPage', 'animate']),
|
...mapState('setting', ['isMobile', 'multiPage', 'animate']),
|
||||||
desc() {
|
desc() {
|
||||||
return this.page.desc
|
return this.page?.desc
|
||||||
},
|
},
|
||||||
linkList() {
|
linkList() {
|
||||||
return this.page.linkList
|
return this.page?.linkList
|
||||||
},
|
},
|
||||||
extraImage() {
|
extraImage() {
|
||||||
return this.page.extraImage
|
return this.page?.extraImage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
@ -20,12 +20,15 @@ export default {
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.footer{
|
.footer{
|
||||||
padding: 0 16px;
|
padding: 48px 16px 24px;
|
||||||
margin: 48px 0 24px;
|
/*margin: 48px 0 24px;*/
|
||||||
text-align: center;
|
text-align: center;
|
||||||
.copyright{
|
.copyright{
|
||||||
color: @text-color-second;
|
color: @text-color-second;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
i {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.links{
|
.links{
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
@ -56,9 +56,10 @@ export default {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.loadding = true
|
if (this.show) return
|
||||||
|
this.loading = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loadding = false
|
this.loading = false
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
187
src/layouts/tabs/TabsHead.vue
Normal file
187
src/layouts/tabs/TabsHead.vue
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="['tabs-head', layout, pageWidth]">
|
||||||
|
<a-tabs
|
||||||
|
type="editable-card"
|
||||||
|
:class="['tabs-container', layout, pageWidth, {'affixed' : affixed, 'fixed-header' : fixedHeader, 'collapsed' : adminLayout.collapsed}]"
|
||||||
|
:active-key="active"
|
||||||
|
:hide-add="true"
|
||||||
|
>
|
||||||
|
<a-tooltip placement="left" :title="lockTitle" slot="tabBarExtraContent">
|
||||||
|
<a-icon
|
||||||
|
theme="filled"
|
||||||
|
@click="onLockClick"
|
||||||
|
class="header-lock"
|
||||||
|
:type="fixedTabs ? 'lock' : 'unlock'"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tab-pane v-for="page in pageList" :key="page.path">
|
||||||
|
<div slot="tab" class="tab" @contextmenu="e => onContextmenu(page.path, e)">
|
||||||
|
<a-icon @click="onRefresh(page)" :class="['icon-sync', {'hide': page.path !== active && !page.loading}]" :type="page.loading ? 'loading' : 'sync'" />
|
||||||
|
<div class="title" @click="onTabClick(page.path)" >{{pageName(page)}}</div>
|
||||||
|
<a-icon v-if="!page.unclose" @click="onClose(page.path)" class="icon-close" type="close"/>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<div v-if="affixed" class="virtual-tabs"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapState, mapMutations} from 'vuex'
|
||||||
|
import {getI18nKey} from '@/utils/routerUtil'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TabsHead',
|
||||||
|
i18n: {
|
||||||
|
messages: {
|
||||||
|
CN: {
|
||||||
|
lock: '点击锁定页签头',
|
||||||
|
unlock: '点击解除锁定',
|
||||||
|
},
|
||||||
|
HK: {
|
||||||
|
lock: '點擊鎖定頁簽頭',
|
||||||
|
unlock: '點擊解除鎖定',
|
||||||
|
},
|
||||||
|
US: {
|
||||||
|
lock: 'click to lock the tabs head',
|
||||||
|
unlock: 'click to unlock',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
pageList: Array,
|
||||||
|
active: String,
|
||||||
|
fixed: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
affixed: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject:['adminLayout'],
|
||||||
|
created() {
|
||||||
|
this.affixed = this.fixedTabs
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState('setting', ['layout', 'pageWidth', 'fixedHeader', 'fixedTabs', 'customTitles']),
|
||||||
|
lockTitle() {
|
||||||
|
return this.$t(this.fixedTabs ? 'unlock' : 'lock')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations('setting', ['setFixedTabs']),
|
||||||
|
onLockClick() {
|
||||||
|
this.setFixedTabs(!this.fixedTabs)
|
||||||
|
if (this.fixedTabs) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.affixed = true
|
||||||
|
}, 200)
|
||||||
|
} else {
|
||||||
|
this.affixed = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTabClick(key) {
|
||||||
|
if (this.active !== key) {
|
||||||
|
this.$emit('change', key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClose(key) {
|
||||||
|
this.$emit('close', key)
|
||||||
|
},
|
||||||
|
onRefresh(page) {
|
||||||
|
this.$emit('refresh', page.path, page)
|
||||||
|
},
|
||||||
|
onContextmenu(pageKey, e) {
|
||||||
|
this.$emit('contextmenu', pageKey, e)
|
||||||
|
},
|
||||||
|
pageName(page) {
|
||||||
|
const custom = this.customTitles.find(item => item.path === page.path)
|
||||||
|
return (custom && custom.title) || page.title || this.$t(getI18nKey(page.keyPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.tab{
|
||||||
|
margin: 0 -16px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
.title{
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.icon-close{
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 6px;
|
||||||
|
margin-right: -4px !important;
|
||||||
|
color: @text-color-second;
|
||||||
|
&:hover{
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-sync{
|
||||||
|
margin-left: -4px;
|
||||||
|
color: @primary-4;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
&:hover{
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
font-size: 14px;
|
||||||
|
&.hide{
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tabs-head{
|
||||||
|
margin: 0 auto;
|
||||||
|
&.head.fixed{
|
||||||
|
width: 1400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tabs-container{
|
||||||
|
margin: -16px auto 8px;
|
||||||
|
transition: top,left 0.2s;
|
||||||
|
.header-lock{
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @primary-3;
|
||||||
|
&:hover{
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.affixed{
|
||||||
|
margin: 0 auto;
|
||||||
|
top: 0px;
|
||||||
|
padding: 8px 24px 0;
|
||||||
|
position: fixed;
|
||||||
|
height: 48px;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: @layout-body-background;
|
||||||
|
&.side,&.mix{
|
||||||
|
right: 0;
|
||||||
|
left: 256px;
|
||||||
|
&.collapsed{
|
||||||
|
left: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.head{
|
||||||
|
width: inherit;
|
||||||
|
padding: 8px 0 0;
|
||||||
|
&.fluid{
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 8px 24px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.fixed-header{
|
||||||
|
top: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.virtual-tabs{
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,26 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<admin-layout>
|
<admin-layout>
|
||||||
<contextmenu :itemList="menuItemList" :visible.sync="menuVisible" @select="onMenuSelect" />
|
<contextmenu :itemList="menuItemList" :visible.sync="menuVisible" @select="onMenuSelect" />
|
||||||
<a-tabs
|
<tabs-head
|
||||||
v-if="multiPage"
|
v-if="multiPage"
|
||||||
type="editable-card"
|
:active="activePage"
|
||||||
:active-key="activePage"
|
:page-list="pageList"
|
||||||
:class="['tabs-view', layout, pageWidth]"
|
@change="changePage"
|
||||||
:hide-add="true"
|
@close="remove"
|
||||||
@change="changePage"
|
@refresh="refresh"
|
||||||
@edit="editPage"
|
@contextmenu="onContextmenu"
|
||||||
@contextmenu="onContextmenu"
|
/>
|
||||||
>
|
|
||||||
<a-tab-pane :key="page.fullPath" v-for="page in pageList">
|
|
||||||
<span slot="tab" :pagekey="page.fullPath">{{pageName(page)}}</span>
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
|
||||||
<div :class="['tabs-view-content', layout, pageWidth]" :style="`margin-top: ${multiPage ? -24 : 0}px`">
|
<div :class="['tabs-view-content', layout, pageWidth]" :style="`margin-top: ${multiPage ? -24 : 0}px`">
|
||||||
<page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction">
|
<page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction">
|
||||||
<a-keep-alive v-if="multiPage" v-model="clearCaches">
|
<a-keep-alive :exclude-keys="excludeKeys" v-if="multiPage && cachePage" v-model="clearCaches">
|
||||||
<router-view ref="tabContent" :key="$route.fullPath" />
|
<router-view v-if="!refreshing" ref="tabContent" :key="$route.fullPath" />
|
||||||
</a-keep-alive>
|
</a-keep-alive>
|
||||||
<router-view v-else />
|
<router-view ref="tabContent" v-else-if="!refreshing" />
|
||||||
</page-toggle-transition>
|
</page-toggle-transition>
|
||||||
</div>
|
</div>
|
||||||
</admin-layout>
|
</admin-layout>
|
||||||
@ -33,26 +28,30 @@ import PageToggleTransition from '@/components/transition/PageToggleTransition'
|
|||||||
import {mapState, mapMutations} from 'vuex'
|
import {mapState, mapMutations} from 'vuex'
|
||||||
import {getI18nKey} from '@/utils/routerUtil'
|
import {getI18nKey} from '@/utils/routerUtil'
|
||||||
import AKeepAlive from '@/components/cache/AKeepAlive'
|
import AKeepAlive from '@/components/cache/AKeepAlive'
|
||||||
|
import TabsHead from '@/layouts/tabs/TabsHead'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TabsView',
|
name: 'TabsView',
|
||||||
i18n: require('./i18n'),
|
i18n: require('./i18n'),
|
||||||
components: { PageToggleTransition, Contextmenu, AdminLayout , AKeepAlive },
|
components: {TabsHead, PageToggleTransition, Contextmenu, AdminLayout , AKeepAlive },
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
clearCaches: [],
|
clearCaches: [],
|
||||||
pageList: [],
|
pageList: [],
|
||||||
activePage: '',
|
activePage: '',
|
||||||
menuVisible: false
|
menuVisible: false,
|
||||||
|
refreshing: false,
|
||||||
|
excludeKeys: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState('setting', ['multiPage', 'animate', 'layout', 'pageWidth']),
|
...mapState('setting', ['multiPage', 'cachePage', 'animate', 'layout', 'pageWidth']),
|
||||||
menuItemList() {
|
menuItemList() {
|
||||||
return [
|
return [
|
||||||
{ key: '1', icon: 'vertical-right', text: this.$t('closeLeft') },
|
{ key: '1', icon: 'vertical-right', text: this.$t('closeLeft') },
|
||||||
{ key: '2', icon: 'vertical-left', text: this.$t('closeRight') },
|
{ key: '2', icon: 'vertical-left', text: this.$t('closeRight') },
|
||||||
{ key: '3', icon: 'close', text: this.$t('closeOthers') }
|
{ key: '3', icon: 'close', text: this.$t('closeOthers') },
|
||||||
|
{ key: '4', icon: 'sync', text: this.$t('refresh') },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
tabsOffset() {
|
tabsOffset() {
|
||||||
@ -60,9 +59,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
this.loadCacheConfig(this.$router?.options?.routes)
|
||||||
this.loadCachedTabs()
|
this.loadCachedTabs()
|
||||||
const route = this.$route
|
const route = this.$route
|
||||||
if (this.pageList.findIndex(item => item.fullPath === route.fullPath) === -1) {
|
if (this.pageList.findIndex(item => item.path === route.fullPath) === -1) {
|
||||||
this.pageList.push(this.createPage(route))
|
this.pageList.push(this.createPage(route))
|
||||||
}
|
}
|
||||||
this.activePage = route.fullPath
|
this.activePage = route.fullPath
|
||||||
@ -81,11 +81,18 @@ export default {
|
|||||||
this.correctPageMinHeight(this.tabsOffset)
|
this.correctPageMinHeight(this.tabsOffset)
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
'$router.options.routes': function (val) {
|
||||||
|
this.excludeKeys = []
|
||||||
|
this.loadCacheConfig(val)
|
||||||
|
},
|
||||||
'$route': function (newRoute) {
|
'$route': function (newRoute) {
|
||||||
this.activePage = newRoute.fullPath
|
this.activePage = newRoute.fullPath
|
||||||
|
const page = this.pageList.find(item => item.path === newRoute.fullPath)
|
||||||
if (!this.multiPage) {
|
if (!this.multiPage) {
|
||||||
this.pageList = [this.createPage(newRoute)]
|
this.pageList = [this.createPage(newRoute)]
|
||||||
} else if (this.pageList.findIndex(item => item.fullPath === newRoute.fullPath) === -1) {
|
} else if (page) {
|
||||||
|
page.fullPath = newRoute.fullPath
|
||||||
|
} else if (!page) {
|
||||||
this.pageList.push(this.createPage(newRoute))
|
this.pageList.push(this.createPage(newRoute))
|
||||||
}
|
}
|
||||||
if (this.multiPage) {
|
if (this.multiPage) {
|
||||||
@ -109,46 +116,56 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
changePage (key) {
|
changePage (key) {
|
||||||
this.activePage = key
|
this.activePage = key
|
||||||
this.$router.push(key)
|
const page = this.pageList.find(item => item.path === key)
|
||||||
},
|
this.$router.push(page.fullPath)
|
||||||
editPage (key, action) {
|
|
||||||
this[action](key) // remove
|
|
||||||
},
|
},
|
||||||
remove (key, next) {
|
remove (key, next) {
|
||||||
if (this.pageList.length === 1) {
|
if (this.pageList.length === 1) {
|
||||||
return this.$message.warning(this.$t('warn'))
|
return this.$message.warning(this.$t('warn'))
|
||||||
}
|
}
|
||||||
//清除缓存
|
//清除缓存
|
||||||
let index = this.pageList.findIndex(item => item.fullPath === key)
|
let index = this.pageList.findIndex(item => item.path === key)
|
||||||
this.clearCaches = this.pageList.splice(index, 1).map(page => page.cachedKey)
|
this.clearCaches = this.pageList.splice(index, 1).map(page => page.cachedKey)
|
||||||
if (next) {
|
if (next) {
|
||||||
this.$router.push(next)
|
this.$router.push(next)
|
||||||
} else if (key === this.activePage) {
|
} else if (key === this.activePage) {
|
||||||
index = index >= this.pageList.length ? this.pageList.length - 1 : index
|
index = index >= this.pageList.length ? this.pageList.length - 1 : index
|
||||||
this.activePage = this.pageList[index].fullPath
|
this.activePage = this.pageList[index].path
|
||||||
this.$router.push(this.activePage)
|
this.$router.push(this.activePage)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onContextmenu (e) {
|
refresh (key, page) {
|
||||||
const pageKey = getPageKey(e.target)
|
page = page || this.pageList.find(item => item.path === key)
|
||||||
|
page.loading = true
|
||||||
|
this.clearCache(page)
|
||||||
|
if (key === this.activePage) {
|
||||||
|
this.reloadContent(() => page.loading = false)
|
||||||
|
} else {
|
||||||
|
// 其实刷新很快,加这个延迟纯粹为了 loading 状态多展示一会儿,让用户感知刷新这一过程
|
||||||
|
setTimeout(() => page.loading = false, 500)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onContextmenu(pageKey, e) {
|
||||||
if (pageKey) {
|
if (pageKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
e.meta = pageKey
|
||||||
this.menuVisible = true
|
this.menuVisible = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMenuSelect (key, target) {
|
onMenuSelect (key, target, pageKey) {
|
||||||
let pageKey = getPageKey(target)
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case '1': this.closeLeft(pageKey); break
|
case '1': this.closeLeft(pageKey); break
|
||||||
case '2': this.closeRight(pageKey); break
|
case '2': this.closeRight(pageKey); break
|
||||||
case '3': this.closeOthers(pageKey); break
|
case '3': this.closeOthers(pageKey); break
|
||||||
|
case '4': this.refresh(pageKey); break
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeOthers (pageKey) {
|
closeOthers (pageKey) {
|
||||||
// 清除缓存
|
// 清除缓存
|
||||||
this.clearCaches = this.pageList.filter(item => item.fullPath !== pageKey).map(item => item.cachedKey)
|
const clearPages = this.pageList.filter(item => item.path !== pageKey && !item.unclose)
|
||||||
this.pageList = this.pageList.filter(item => item.fullPath === pageKey)
|
this.clearCaches = clearPages.map(item => item.cachedKey)
|
||||||
|
this.pageList = this.pageList.filter(item => !clearPages.includes(item))
|
||||||
// 判断跳转
|
// 判断跳转
|
||||||
if (this.activePage != pageKey) {
|
if (this.activePage != pageKey) {
|
||||||
this.activePage = pageKey
|
this.activePage = pageKey
|
||||||
@ -156,27 +173,45 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeLeft (pageKey) {
|
closeLeft (pageKey) {
|
||||||
const index = this.pageList.findIndex(item => item.fullPath === pageKey)
|
const index = this.pageList.findIndex(item => item.path === pageKey)
|
||||||
// 清除缓存
|
// 清除缓存
|
||||||
this.clearCaches = this.pageList.filter((item, i) => i < index).map(item => item.cachedKey)
|
const clearPages = this.pageList.filter((item, i) => i < index && !item.unclose)
|
||||||
this.pageList = this.pageList.slice(index)
|
this.clearCaches = clearPages.map(item => item.cachedKey)
|
||||||
|
this.pageList = this.pageList.filter(item => !clearPages.includes(item))
|
||||||
// 判断跳转
|
// 判断跳转
|
||||||
if (!this.pageList.find(item => item.fullPath === this.activePage)) {
|
if (!this.pageList.find(item => item.path === this.activePage)) {
|
||||||
this.activePage = pageKey
|
this.activePage = pageKey
|
||||||
this.$router.push(this.activePage)
|
this.$router.push(this.activePage)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeRight (pageKey) {
|
closeRight (pageKey) {
|
||||||
// 清除缓存
|
// 清除缓存
|
||||||
const index = this.pageList.findIndex(item => item.fullPath === pageKey)
|
const index = this.pageList.findIndex(item => item.path === pageKey)
|
||||||
this.clearCaches = this.pageList.filter((item, i) => i > index).map(item => item.cachedKey)
|
const clearPages = this.pageList.filter((item, i) => i > index && !item.unclose)
|
||||||
this.pageList = this.pageList.slice(0, index + 1)
|
this.clearCaches = clearPages.map(item => item.cachedKey)
|
||||||
|
this.pageList = this.pageList.filter(item => !clearPages.includes(item))
|
||||||
// 判断跳转
|
// 判断跳转
|
||||||
if (!this.pageList.find(item => item.fullPath === this.activePage)) {
|
if (!this.pageList.find(item => item.path === this.activePage)) {
|
||||||
this.activePage = pageKey
|
this.activePage = pageKey
|
||||||
this.$router.push(this.activePage)
|
this.$router.push(this.activePage)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
clearCache(page) {
|
||||||
|
page._init_ = false
|
||||||
|
this.clearCaches = [page.cachedKey]
|
||||||
|
},
|
||||||
|
reloadContent(onLoaded) {
|
||||||
|
this.refreshing = true
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshing = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.setCachedKey(this.$route)
|
||||||
|
if (typeof onLoaded === 'function') {
|
||||||
|
onLoaded.apply(this, [])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
pageName(page) {
|
pageName(page) {
|
||||||
return this.$t(getI18nKey(page.keyPath))
|
return this.$t(getI18nKey(page.keyPath))
|
||||||
},
|
},
|
||||||
@ -185,6 +220,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
addListener() {
|
addListener() {
|
||||||
window.addEventListener('page:close', this.closePageListener)
|
window.addEventListener('page:close', this.closePageListener)
|
||||||
|
window.addEventListener('page:refresh', this.refreshPageListener)
|
||||||
window.addEventListener('unload', this.unloadListener)
|
window.addEventListener('unload', this.unloadListener)
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -192,6 +228,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
removeListener() {
|
removeListener() {
|
||||||
window.removeEventListener('page:close', this.closePageListener)
|
window.removeEventListener('page:close', this.closePageListener)
|
||||||
|
window.removeEventListener('page:refresh', this.refreshPageListener)
|
||||||
window.removeEventListener('unload', this.unloadListener)
|
window.removeEventListener('unload', this.unloadListener)
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -201,7 +238,17 @@ export default {
|
|||||||
closePageListener(event) {
|
closePageListener(event) {
|
||||||
const {closeRoute, nextRoute} = event.detail
|
const {closeRoute, nextRoute} = event.detail
|
||||||
const closePath = typeof closeRoute === 'string' ? closeRoute : closeRoute.path
|
const closePath = typeof closeRoute === 'string' ? closeRoute : closeRoute.path
|
||||||
this.remove(closePath, nextRoute)
|
const path = closePath && closePath.split('?')[0]
|
||||||
|
this.remove(path, nextRoute)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 页面刷新事件监听
|
||||||
|
* @param event 页签关闭事件
|
||||||
|
*/
|
||||||
|
refreshPageListener(event) {
|
||||||
|
const {pageKey} = event.detail
|
||||||
|
const path = pageKey && pageKey.split('?')[0]
|
||||||
|
this.refresh(path)
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 页面 unload 事件监听器,添加页签到 session 缓存,用于刷新时保留页签
|
* 页面 unload 事件监听器,添加页签到 session 缓存,用于刷新时保留页签
|
||||||
@ -211,16 +258,24 @@ export default {
|
|||||||
sessionStorage.setItem(process.env.VUE_APP_TBAS_KEY, JSON.stringify(tabs))
|
sessionStorage.setItem(process.env.VUE_APP_TBAS_KEY, JSON.stringify(tabs))
|
||||||
},
|
},
|
||||||
createPage(route) {
|
createPage(route) {
|
||||||
return {keyPath: route.matched[route.matched.length - 1].path, fullPath: route.fullPath}
|
return {
|
||||||
|
keyPath: route.matched[route.matched.length - 1].path,
|
||||||
|
fullPath: route.fullPath, loading: false,
|
||||||
|
path: route.fullPath,
|
||||||
|
title: route.meta && route.meta.page && route.meta.page.title,
|
||||||
|
unclose: route.meta && route.meta.page && (route.meta.page.closable === false),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 设置页面缓存的key
|
* 设置页面缓存的key
|
||||||
* @param route 页面对应的路由
|
* @param route 页面对应的路由
|
||||||
*/
|
*/
|
||||||
setCachedKey(route) {
|
setCachedKey(route) {
|
||||||
const page = this.pageList.find(item => item.fullPath === route.fullPath)
|
const page = this.pageList.find(item => item.path === route.fullPath)
|
||||||
|
page.unclose = route.meta && route.meta.page && (route.meta.page.closable === false)
|
||||||
if (!page._init_) {
|
if (!page._init_) {
|
||||||
page.cachedKey = this.$refs.tabContent.$vnode.key
|
const vnode = this.$refs.tabContent.$vnode
|
||||||
|
page.cachedKey = vnode.key + vnode.componentOptions.Ctor.cid
|
||||||
page._init_ = true
|
page._init_ = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -242,23 +297,20 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
loadCacheConfig(routes, pCache = true) {
|
||||||
|
routes.forEach(item => {
|
||||||
|
const cacheAble = item.meta?.page?.cacheAble ?? pCache ?? true
|
||||||
|
if (!cacheAble) {
|
||||||
|
this.excludeKeys.push(new RegExp(`${item.path.replace(/:[^/]*/g, '[^/]*')}(\\?.*)?\\d*$`))
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
this.loadCacheConfig(item.children, cacheAble)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
...mapMutations('setting', ['correctPageMinHeight'])
|
...mapMutations('setting', ['correctPageMinHeight'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 由于ant-design-vue组件库的TabPane组件暂不支持自定义监听器,无法直接获取到右键target所在标签页的 pagekey 。故增加此方法用于
|
|
||||||
* 查询右键target所在标签页的标识 pagekey ,以用于自定义右键菜单的事件处理。
|
|
||||||
* 注:TabPane组件支持自定义监听器后可去除该方法并重构 ‘自定义右键菜单的事件处理’
|
|
||||||
* @param target 查询开始目标
|
|
||||||
* @param depth 查询层级深度 (查找层级最多不超过3层,超过3层深度直接返回 null)
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
function getPageKey (target, depth = 0) {
|
|
||||||
if (depth > 2 || !target) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return target.getAttribute('pagekey') || getPageKey(target.firstElementChild, ++depth)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
@ -4,18 +4,21 @@ module.exports = {
|
|||||||
closeLeft: '关闭左侧',
|
closeLeft: '关闭左侧',
|
||||||
closeRight: '关闭右侧',
|
closeRight: '关闭右侧',
|
||||||
closeOthers: '关闭其它',
|
closeOthers: '关闭其它',
|
||||||
|
refresh: '刷新页面',
|
||||||
warn: '这是最后一页,不能再关闭了',
|
warn: '这是最后一页,不能再关闭了',
|
||||||
},
|
},
|
||||||
HK: {
|
HK: {
|
||||||
closeLeft: '關閉左側',
|
closeLeft: '關閉左側',
|
||||||
closeRight: '關閉右側',
|
closeRight: '關閉右側',
|
||||||
closeOthers: '關閉其它',
|
closeOthers: '關閉其它',
|
||||||
|
refresh: '刷新頁面',
|
||||||
warn: '這是最後一頁,不能再關閉了',
|
warn: '這是最後一頁,不能再關閉了',
|
||||||
},
|
},
|
||||||
US: {
|
US: {
|
||||||
closeLeft: 'close left',
|
closeLeft: 'close left',
|
||||||
closeRight: 'close right',
|
closeRight: 'close right',
|
||||||
closeOthers: 'close others',
|
closeOthers: 'close others',
|
||||||
|
refresh: 'refresh the page',
|
||||||
warn: 'This is the last page, you can\'t close it',
|
warn: 'This is the last page, you can\'t close it',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import 'animate.css/source/animate.css'
|
|||||||
import Plugins from '@/plugins'
|
import Plugins from '@/plugins'
|
||||||
import {initI18n} from '@/utils/i18n'
|
import {initI18n} from '@/utils/i18n'
|
||||||
import bootstrap from '@/bootstrap'
|
import bootstrap from '@/bootstrap'
|
||||||
|
import 'moment/locale/zh-cn'
|
||||||
|
|
||||||
const router = initRouter(store.state.setting.asyncRoutes)
|
const router = initRouter(store.state.setting.asyncRoutes)
|
||||||
const i18n = initI18n('CN', 'US')
|
const i18n = initI18n('CN', 'US')
|
||||||
|
@ -50,6 +50,8 @@ const welcomeMessages = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const goods = ['运动鞋', '短裤', 'T恤', '七分裤', '风衣', '寸衫']
|
||||||
|
|
||||||
Random.extend({
|
Random.extend({
|
||||||
admin () {
|
admin () {
|
||||||
return this.pick(admins)
|
return this.pick(admins)
|
||||||
@ -69,6 +71,9 @@ Random.extend({
|
|||||||
position () {
|
position () {
|
||||||
return this.pick(positions)
|
return this.pick(positions)
|
||||||
},
|
},
|
||||||
|
goods () {
|
||||||
|
return this.pick(goods)
|
||||||
|
},
|
||||||
saying () {
|
saying () {
|
||||||
return this.pick(sayings)
|
return this.pick(sayings)
|
||||||
},
|
},
|
||||||
|
106
src/mock/goods/index.js
Normal file
106
src/mock/goods/index.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import Mock from 'mockjs'
|
||||||
|
import '@/mock/extend'
|
||||||
|
import {parseUrlParams} from '@/utils/request'
|
||||||
|
|
||||||
|
const current = new Date().getTime()
|
||||||
|
|
||||||
|
const goodsList = Mock.mock({
|
||||||
|
'list|100': [{
|
||||||
|
'id|+1': 0,
|
||||||
|
'name': '@GOODS',
|
||||||
|
'orderId': `${current}-@integer(1,100)`,
|
||||||
|
'status|1-4': 1,
|
||||||
|
'send': '@BOOLEAN',
|
||||||
|
'sendTime': '@DATETIME',
|
||||||
|
'orderDate': '@DATE',
|
||||||
|
'auditTime': '@TIME'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
Mock.mock(RegExp(`${process.env.VUE_APP_API_BASE_URL}/goods` + '.*'),'get', ({url}) => {
|
||||||
|
const params = parseUrlParams(decodeURI(url))
|
||||||
|
let {page, pageSize} = params
|
||||||
|
page = eval(page) - 1 || 0
|
||||||
|
pageSize = eval(pageSize) || 10
|
||||||
|
delete params.page
|
||||||
|
delete params.pageSize
|
||||||
|
let result = goodsList.list.filter(item => {
|
||||||
|
for (let [key, value] of Object.entries(params)) {
|
||||||
|
if (item[key] != value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
const total = result.length
|
||||||
|
if ((page) * pageSize > total) {
|
||||||
|
result = []
|
||||||
|
} else {
|
||||||
|
result = result.slice(page * pageSize, (page + 1) * pageSize)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
page: page + 1,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
list: result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const columnsConfig = [
|
||||||
|
{
|
||||||
|
title: '商品名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
searchAble: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单号',
|
||||||
|
dataIndex: 'orderId'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'status',
|
||||||
|
dataType: 'select',
|
||||||
|
slots: {title: 'statusTitle'},
|
||||||
|
scopedSlots: {customRender: 'status'},
|
||||||
|
search: {
|
||||||
|
selectOptions: [
|
||||||
|
{title: '已下单', value: 1},
|
||||||
|
{title: '已付款', value: 2},
|
||||||
|
{title: '已审核', value: 3},
|
||||||
|
// {title: '已发货', value: 4}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发货',
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'send',
|
||||||
|
dataType: 'boolean',
|
||||||
|
scopedSlots: {customRender: 'send'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发货时间',
|
||||||
|
dataIndex: 'sendTime',
|
||||||
|
dataType: 'datetime'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '下单日期',
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'orderDate',
|
||||||
|
dataType: 'date',
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审核时间',
|
||||||
|
dataIndex: 'auditTime',
|
||||||
|
dataType: 'time',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/columns`, 'get', () => {
|
||||||
|
return columnsConfig
|
||||||
|
})
|
@ -4,8 +4,10 @@ import '@/mock/project'
|
|||||||
import '@/mock/user/login'
|
import '@/mock/user/login'
|
||||||
import '@/mock/workplace'
|
import '@/mock/workplace'
|
||||||
import '@/mock/user/routes'
|
import '@/mock/user/routes'
|
||||||
|
import '@/mock/goods'
|
||||||
|
import '@/mock/list'
|
||||||
|
|
||||||
// 设置全局延时
|
// 设置全局延时
|
||||||
Mock.setup({
|
Mock.setup({
|
||||||
timeout: '300-600'
|
timeout: '200-400'
|
||||||
})
|
})
|
||||||
|
52
src/mock/list/index.js
Normal file
52
src/mock/list/index.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import Mock from 'mockjs'
|
||||||
|
import '@/mock/extend'
|
||||||
|
import {parseUrlParams} from '@/utils/request'
|
||||||
|
|
||||||
|
const current = new Date().getTime()
|
||||||
|
|
||||||
|
const source = Mock.mock({
|
||||||
|
'list|100': [{
|
||||||
|
'key|+1': 0,
|
||||||
|
'no': `${current}-@integer(1,100)`,
|
||||||
|
'description': '这是一段描述',
|
||||||
|
'callNo|0-50': 5,
|
||||||
|
'status|1-4': 1,
|
||||||
|
'updatedAt': '@DATETIME',
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
Mock.mock(RegExp(`${process.env.VUE_APP_API_BASE_URL}/list` + '.*'),'get', ({url}) => {
|
||||||
|
const params = parseUrlParams(decodeURI(url))
|
||||||
|
let {page, pageSize} = params
|
||||||
|
page = eval(page) - 1 || 0
|
||||||
|
pageSize = eval(pageSize) || 10
|
||||||
|
|
||||||
|
delete params.page
|
||||||
|
delete params.pageSize
|
||||||
|
|
||||||
|
let result = source.list.filter(item => {
|
||||||
|
for (let [key, value] of Object.entries(params)) {
|
||||||
|
if (item[key] !== value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
const total = result.length
|
||||||
|
if ((page) * pageSize > total) {
|
||||||
|
result = []
|
||||||
|
} else {
|
||||||
|
result = result.slice(page * pageSize, (page + 1) * pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
page: page + 1,
|
||||||
|
pageSize,
|
||||||
|
total: 100,
|
||||||
|
list: result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
@ -8,21 +8,32 @@ const user = Mock.mock({
|
|||||||
position: '@POSITION'
|
position: '@POSITION'
|
||||||
})
|
})
|
||||||
Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/login`, 'post', ({body}) => {
|
Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/login`, 'post', ({body}) => {
|
||||||
let result = {}
|
let result = {data: {}}
|
||||||
const {name, password} = JSON.parse(body)
|
const {name, password} = JSON.parse(body)
|
||||||
|
|
||||||
if (name !== 'admin' || password !== '888888') {
|
let success = false
|
||||||
result.code = -1
|
|
||||||
result.message = '账户名或密码错误(admin/888888)'
|
if (name === 'admin' && password === '888888') {
|
||||||
|
success = true
|
||||||
|
result.data.permissions = [{id: 'queryForm', operation: ['add', 'edit']}]
|
||||||
|
result.data.roles = [{id: 'admin', operation: ['add', 'edit', 'delete']}]
|
||||||
|
} else if (name === 'test' || password === '888888') {
|
||||||
|
success = true
|
||||||
|
result.data.permissions = [{id: 'queryForm', operation: ['add', 'edit']}]
|
||||||
|
result.data.roles = [{id: 'test', operation: ['add', 'edit', 'delete']}]
|
||||||
} else {
|
} else {
|
||||||
|
success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
result.code = 0
|
result.code = 0
|
||||||
result.message = Mock.mock('@TIMEFIX').CN + ',欢迎回来'
|
result.message = Mock.mock('@TIMEFIX').CN + ',欢迎回来'
|
||||||
result.data = {}
|
|
||||||
result.data.user = user
|
result.data.user = user
|
||||||
result.data.token = 'Authorization:' + Math.random()
|
result.data.token = 'Authorization:' + Math.random()
|
||||||
result.data.expireAt = new Date(new Date().getTime() + 30 * 60 * 1000)
|
result.data.expireAt = new Date(new Date().getTime() + 30 * 60 * 1000)
|
||||||
result.data.permissions = [{id: 'queryForm', operation: ['add', 'edit']}]
|
} else {
|
||||||
result.data.roles = [{id: 'admin', operation: ['add', 'edit', 'delete']}]
|
result.code = -1
|
||||||
|
result.message = '账户名或密码错误(admin/888888 or test/888888)'
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
@ -18,7 +18,21 @@ Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/routes`, 'get', () => {
|
|||||||
router: 'basicForm',
|
router: 'basicForm',
|
||||||
name: '验权表单',
|
name: '验权表单',
|
||||||
icon: 'file-excel',
|
icon: 'file-excel',
|
||||||
authority: 'form'
|
authority: 'queryForm'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
router: 'antdv',
|
||||||
|
path: 'antdv',
|
||||||
|
name: 'Ant Design Vue',
|
||||||
|
icon: 'ant-design',
|
||||||
|
link: 'https://www.antdv.com/docs/vue/introduce-cn/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
router: 'document',
|
||||||
|
path: 'document',
|
||||||
|
name: '使用文档',
|
||||||
|
icon: 'file-word',
|
||||||
|
link: 'https://iczer.gitee.io/vue-antd-admin-docs/'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
16
src/pages/Demo.vue
Normal file
16
src/pages/Demo.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>query: {{$route.query}}</p>
|
||||||
|
<p>params: {{$route.params}}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Demo'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
152
src/pages/components/table/Api.vue
Normal file
152
src/pages/components/table/Api.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div class="api">
|
||||||
|
<div class="introduce">
|
||||||
|
<h2 class="title">说明</h2>
|
||||||
|
<p class="content">
|
||||||
|
AdvanceTable 是基于 Ant Design Vue Table 组件封装,支持其所有 API。<br/>
|
||||||
|
主要添加了<em>列设置</em>及<em>搜索控件配置</em>的功能,可用于一些需要动态配置表格展示、动态配置搜索条件的场景。<br/>
|
||||||
|
使用方式 与 antd table 基本无异。添加了部分API,如下:
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<api-table :api-source="apiSource" />
|
||||||
|
<api-table type="event" title="事件" :api-source="events" />
|
||||||
|
<api-table title="Column" :api-source="columnApi" />
|
||||||
|
<api-table title="Search" :api-source="searchApi" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ApiTable from '@/components/table/api/ApiTable'
|
||||||
|
export default {
|
||||||
|
name: 'Api',
|
||||||
|
components: {ApiTable},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiSource: [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
param: '<a href="https://www.antdv.com/components/table-cn/#API" target="_blank">Ant Design Vue Table API</a>',
|
||||||
|
desc: '支持 Ant Design Vue Table 组件 所有 api',
|
||||||
|
type: '--',
|
||||||
|
default: '--',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
param: 'title',
|
||||||
|
desc: '表格标题',
|
||||||
|
type: 'string | slot',
|
||||||
|
default: '\'高级表格\''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
param: 'formatConditions',
|
||||||
|
desc: `是否格式化搜索条件的值,格式化规则参考 <a>Search 配置</a>。
|
||||||
|
<br/>false:取搜索输入控件的原值 <br/>true:取搜索输入控件格式化后的值`,
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 3,
|
||||||
|
param: 'columns',
|
||||||
|
desc: `表格列配置,参考 <a>Column 配置</a>`,
|
||||||
|
type: 'array',
|
||||||
|
default: '--',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
param: '<a href="https://www.antdv.com/components/table-cn/#API" target="_blank">Ant Design Vue Table Events API</a>',
|
||||||
|
desc: '支持 Ant Design Vue Table 所有事件',
|
||||||
|
callback: '--',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
param: 'search',
|
||||||
|
desc: '搜索条件变化时触发',
|
||||||
|
callback: 'Function(conditions, searchOptions: [{field, value, format}])',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
param: 'refresh',
|
||||||
|
desc: '表头刷新图标点击时触发',
|
||||||
|
callback: 'Function(conditions, searchOptions: [{field, value, format}])',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 3,
|
||||||
|
param: 'reset',
|
||||||
|
desc: '列配置重置按钮点击时触发',
|
||||||
|
callback: 'Function(conditions, searchOptions: [{field, value, format}])',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columnApi: [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
param: '<a href="https://www.antdv.com/components/table-cn/#API" target="_blank">Ant Design Vue Table Column API</a>',
|
||||||
|
desc: '支持 Ant Design Vue Table 组件 Column 配置所有 api',
|
||||||
|
type: '--',
|
||||||
|
default: '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
param: 'searchAble',
|
||||||
|
desc: '是否启用列搜索',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
param: 'dataType',
|
||||||
|
desc: `数据类型,该配置将决定列搜索输入控件的类型,与列搜索输入控件对应关系如下:<br/>
|
||||||
|
string: 输入框组件<br/>
|
||||||
|
boolean: 开关组件<br/>
|
||||||
|
select: 下拉输入框组件<br/>
|
||||||
|
date: 日期选择器<br/>
|
||||||
|
time: 时间选择器<br/>
|
||||||
|
datetime: 带时间选择器的日期选择器`,
|
||||||
|
type: `'string' | 'boolean' | 'select' | 'date' | 'time' | 'datetime'`,
|
||||||
|
default: `'string'`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 3,
|
||||||
|
param: 'search',
|
||||||
|
desc: '列搜索配置,参考 <a>Search 配置</a>',
|
||||||
|
type: 'object',
|
||||||
|
default: '--'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
searchApi: [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
param: 'format',
|
||||||
|
desc: `列搜索输入控件值的格式化配置。<br/>如果输入控件支持格式化,则可设置该值为字符串,如日期输入组件,可设为为 'YYYY-MM-DD'。
|
||||||
|
<br/>不支持格式化的输入控件,可设置为一个接收控件的输入值作为参数的函数,如 (value) => {return \`prefix\${value}\`}。`,
|
||||||
|
type: 'string | Function(value)',
|
||||||
|
default: '取输入控件默认的格式化配置'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
param: 'selectOptions',
|
||||||
|
desc: `select 数据类型的下拉输入组件的选项配置,可参考 <a href="https://www.antdv.com/components/select-cn/#API" target="_blank">Ant Design Vue Select Option props Api</a>`,
|
||||||
|
type: 'array',
|
||||||
|
default: '--'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.api{
|
||||||
|
.introduce{
|
||||||
|
padding: 16px;
|
||||||
|
.content{
|
||||||
|
em{
|
||||||
|
margin: 0 4px;
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
166
src/pages/components/table/Table.vue
Normal file
166
src/pages/components/table/Table.vue
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<div class="table">
|
||||||
|
<advance-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
title="高级表格-Beta"
|
||||||
|
:loading="loading"
|
||||||
|
rowKey="id"
|
||||||
|
@search="onSearch"
|
||||||
|
@refresh="onRefresh"
|
||||||
|
:format-conditions="true"
|
||||||
|
@reset="onReset"
|
||||||
|
:pagination="{
|
||||||
|
current: page,
|
||||||
|
pageSize: pageSize,
|
||||||
|
total: total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showLessItems: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,总计 ${total} 条`,
|
||||||
|
onChange: onPageChange,
|
||||||
|
onShowSizeChange: onSizeChange,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template slot="statusTitle">
|
||||||
|
状态<a-icon style="margin: 0 4px" type="info-circle" />
|
||||||
|
</template>
|
||||||
|
<template slot="send" slot-scope="{text}">
|
||||||
|
{{text ? '是' : '否'}}
|
||||||
|
</template>
|
||||||
|
<template slot="status" slot-scope="{text}">
|
||||||
|
{{text | statusStr}}
|
||||||
|
</template>
|
||||||
|
</advance-table>
|
||||||
|
<api />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AdvanceTable from '@/components/table/advance/AdvanceTable'
|
||||||
|
import {dataSource as ds} from '@/services'
|
||||||
|
import Api from '@/pages/components/table/Api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Table',
|
||||||
|
components: {Api, AdvanceTable},
|
||||||
|
filters: {
|
||||||
|
statusStr(val) {
|
||||||
|
switch (val) {
|
||||||
|
case 1: return '已下单'
|
||||||
|
case 2: return '已付款'
|
||||||
|
case 3: return '已审核'
|
||||||
|
case 4: return '已发货'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '商品名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
searchAble: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单号',
|
||||||
|
dataIndex: 'orderId'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'status',
|
||||||
|
dataType: 'select',
|
||||||
|
slots: {title: 'statusTitle'},
|
||||||
|
scopedSlots: {customRender: 'status'},
|
||||||
|
search: {
|
||||||
|
selectOptions: [
|
||||||
|
{title: '已下单', value: 1},
|
||||||
|
{title: '已付款', value: 2},
|
||||||
|
{title: '已审核', value: 3},
|
||||||
|
{title: '已发货', value: 4}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发货',
|
||||||
|
searchAble: true,
|
||||||
|
dataIndex: 'send',
|
||||||
|
dataType: 'boolean',
|
||||||
|
scopedSlots: {customRender: 'send'},
|
||||||
|
search: {
|
||||||
|
switchOptions: {
|
||||||
|
checkedText: '开',
|
||||||
|
uncheckedText: '关'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审核时间',
|
||||||
|
dataIndex: 'auditTime',
|
||||||
|
dataType: 'time',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataSource: [],
|
||||||
|
conditions: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getGoodList()
|
||||||
|
this.getColumns()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getGoodList() {
|
||||||
|
this.loading = true
|
||||||
|
const {page, pageSize, conditions} = this
|
||||||
|
ds.goodsList({page, pageSize, ...conditions}).then(result => {
|
||||||
|
const {list, page, pageSize, total} = result.data.data
|
||||||
|
this.dataSource = list
|
||||||
|
this.page = page
|
||||||
|
this.total = total
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getColumns() {
|
||||||
|
ds.goodsColumns().then(res => {
|
||||||
|
this.columns = res.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSearch(conditions, searchOptions) {
|
||||||
|
console.log(searchOptions)
|
||||||
|
this.page = 1
|
||||||
|
this.conditions = conditions
|
||||||
|
this.getGoodList()
|
||||||
|
},
|
||||||
|
onSizeChange(current, size) {
|
||||||
|
this.page = 1
|
||||||
|
this.pageSize = size
|
||||||
|
this.getGoodList()
|
||||||
|
},
|
||||||
|
onRefresh(conditions) {
|
||||||
|
this.conditions = conditions
|
||||||
|
this.getGoodList()
|
||||||
|
},
|
||||||
|
onReset(conditions) {
|
||||||
|
this.conditions = conditions
|
||||||
|
this.getGoodList()
|
||||||
|
},
|
||||||
|
onPageChange(page, pageSize) {
|
||||||
|
this.page = page
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.getGoodList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.table{
|
||||||
|
background-color: @base-bg-color;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
2
src/pages/components/table/index.js
Normal file
2
src/pages/components/table/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import Table from './Table'
|
||||||
|
export default Table
|
@ -79,7 +79,7 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="operator">
|
<a-space class="operator">
|
||||||
<a-button @click="addNew" type="primary">新建</a-button>
|
<a-button @click="addNew" type="primary">新建</a-button>
|
||||||
<a-button >批量操作</a-button>
|
<a-button >批量操作</a-button>
|
||||||
<a-dropdown>
|
<a-dropdown>
|
||||||
@ -91,13 +91,14 @@
|
|||||||
更多操作 <a-icon type="down" />
|
更多操作 <a-icon type="down" />
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</div>
|
</a-space>
|
||||||
<standard-table
|
<standard-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:dataSource="dataSource"
|
:dataSource="dataSource"
|
||||||
:selectedRows.sync="selectedRows"
|
:selectedRows.sync="selectedRows"
|
||||||
@clear="onClear"
|
@clear="onClear"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
|
:pagination="{...pagination, onChange: onPageChange}"
|
||||||
@selectedRowChange="onSelectChange"
|
@selectedRowChange="onSelectChange"
|
||||||
>
|
>
|
||||||
<div slot="description" slot-scope="{text}">
|
<div slot="description" slot-scope="{text}">
|
||||||
@ -116,6 +117,7 @@
|
|||||||
<a @click="deleteRecord(record.key)" v-auth="`delete`">
|
<a @click="deleteRecord(record.key)" v-auth="`delete`">
|
||||||
<a-icon type="delete" />删除2
|
<a-icon type="delete" />删除2
|
||||||
</a>
|
</a>
|
||||||
|
<router-link :to="`/list/query/detail/${record.key}`" >详情</router-link>
|
||||||
</div>
|
</div>
|
||||||
<template slot="statusTitle">
|
<template slot="statusTitle">
|
||||||
<a-icon @click.native="onStatusTitleClick" type="info-circle" />
|
<a-icon @click.native="onStatusTitleClick" type="info-circle" />
|
||||||
@ -127,6 +129,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import StandardTable from '@/components/table/StandardTable'
|
import StandardTable from '@/components/table/StandardTable'
|
||||||
|
import {request} from '@/utils/request'
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '规则编号',
|
title: '规则编号',
|
||||||
@ -160,19 +163,6 @@ const columns = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const dataSource = []
|
|
||||||
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
dataSource.push({
|
|
||||||
key: i,
|
|
||||||
no: 'NO ' + i,
|
|
||||||
description: '这是一段描述',
|
|
||||||
callNo: Math.floor(Math.random() * 1000),
|
|
||||||
status: Math.floor(Math.random() * 10) % 4,
|
|
||||||
updatedAt: '2018-07-26'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QueryList',
|
name: 'QueryList',
|
||||||
components: {StandardTable},
|
components: {StandardTable},
|
||||||
@ -180,14 +170,37 @@ export default {
|
|||||||
return {
|
return {
|
||||||
advanced: true,
|
advanced: true,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
dataSource: dataSource,
|
dataSource: [],
|
||||||
selectedRows: []
|
selectedRows: [],
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
authorize: {
|
authorize: {
|
||||||
deleteRecord: 'delete'
|
deleteRecord: 'delete'
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onPageChange(page, pageSize) {
|
||||||
|
this.pagination.current = page
|
||||||
|
this.pagination.pageSize = pageSize
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
getData() {
|
||||||
|
request(process.env.VUE_APP_API_BASE_URL + '/list', 'get', {page: this.pagination.current,
|
||||||
|
pageSize: this.pagination.pageSize}).then(res => {
|
||||||
|
const {list, page, pageSize, total} = res?.data?.data ?? {}
|
||||||
|
this.dataSource = list
|
||||||
|
this.pagination.current = page
|
||||||
|
this.pagination.pageSize = pageSize
|
||||||
|
this.pagination.total = total
|
||||||
|
})
|
||||||
|
},
|
||||||
deleteRecord(key) {
|
deleteRecord(key) {
|
||||||
this.dataSource = this.dataSource.filter(item => item.key !== key)
|
this.dataSource = this.dataSource.filter(item => item.key !== key)
|
||||||
this.selectedRows = this.selectedRows.filter(item => item.key !== key)
|
this.selectedRows = this.selectedRows.filter(item => item.key !== key)
|
||||||
|
@ -120,7 +120,7 @@ export default {
|
|||||||
// 获取路由配置
|
// 获取路由配置
|
||||||
getRoutesConfig().then(result => {
|
getRoutesConfig().then(result => {
|
||||||
const routesConfig = result.data.data
|
const routesConfig = result.data.data
|
||||||
loadRoutes({router: this.$router, store: this.$store, i18n: this.$i18n}, routesConfig)
|
loadRoutes(routesConfig)
|
||||||
this.$router.push('/dashboard/workplace')
|
this.$router.push('/dashboard/workplace')
|
||||||
this.$message.success(loginRes.message, 3)
|
this.$message.success(loginRes.message, 3)
|
||||||
})
|
})
|
||||||
|
@ -39,9 +39,9 @@ const auth = function(authConfig, permission, role, permissions, roles) {
|
|||||||
if (type === 'permission') {
|
if (type === 'permission') {
|
||||||
return checkFromPermission(check, permission)
|
return checkFromPermission(check, permission)
|
||||||
} else if (type === 'role') {
|
} else if (type === 'role') {
|
||||||
return checkFromRoles(check, role)
|
return checkFromRoles(check, roles)
|
||||||
} else {
|
} else {
|
||||||
return checkFromPermission(check, permission) || checkFromRoles(check, role)
|
return checkFromPermission(check, permission) || checkFromRoles(check, roles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,31 @@ const TabsPagePlugin = {
|
|||||||
$closePage(closeRoute, nextRoute) {
|
$closePage(closeRoute, nextRoute) {
|
||||||
const event = new CustomEvent('page:close', {detail:{closeRoute, nextRoute}})
|
const event = new CustomEvent('page:close', {detail:{closeRoute, nextRoute}})
|
||||||
window.dispatchEvent(event)
|
window.dispatchEvent(event)
|
||||||
|
},
|
||||||
|
$refreshPage(route) {
|
||||||
|
const path = typeof route === 'object' ? route.path : route
|
||||||
|
const event = new CustomEvent('page:refresh', {detail:{pageKey: path}})
|
||||||
|
window.dispatchEvent(event)
|
||||||
|
},
|
||||||
|
$openPage(route, title) {
|
||||||
|
this.$setPageTitle(route, title)
|
||||||
|
this.$router.push(route)
|
||||||
|
},
|
||||||
|
$setPageTitle(route, title) {
|
||||||
|
if (title) {
|
||||||
|
// let path = typeof route === 'object' ? route.path : route
|
||||||
|
// path = path && path.split('?')[0]
|
||||||
|
let path = typeof route === 'object' ? this.$router.resolve(route).route.fullPath : route
|
||||||
|
this.$store.commit('setting/setCustomTitle', {path, title})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
customTitle() {
|
||||||
|
const customTitles = this.$store.state.setting.customTitles
|
||||||
|
// const path = this.$route.path.split('?')[0]
|
||||||
|
const custom = customTitles.find(item => item.path === this.$route.fullPath)
|
||||||
|
return custom && custom.title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -37,6 +37,11 @@ const options = {
|
|||||||
{
|
{
|
||||||
path: 'workplace',
|
path: 'workplace',
|
||||||
name: '工作台',
|
name: '工作台',
|
||||||
|
meta: {
|
||||||
|
page: {
|
||||||
|
closable: false
|
||||||
|
}
|
||||||
|
},
|
||||||
component: () => import('@/pages/dashboard/workplace'),
|
component: () => import('@/pages/dashboard/workplace'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -51,6 +56,9 @@ const options = {
|
|||||||
name: '表单页',
|
name: '表单页',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'form',
|
icon: 'form',
|
||||||
|
page: {
|
||||||
|
cacheAble: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
component: PageView,
|
component: PageView,
|
||||||
children: [
|
children: [
|
||||||
@ -87,6 +95,15 @@ const options = {
|
|||||||
},
|
},
|
||||||
component: () => import('@/pages/list/QueryList'),
|
component: () => import('@/pages/list/QueryList'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'query/detail/:id',
|
||||||
|
name: '查询详情',
|
||||||
|
meta: {
|
||||||
|
highlight: '/list/query',
|
||||||
|
invisible: true
|
||||||
|
},
|
||||||
|
component: () => import('@/pages/Demo')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'primary',
|
path: 'primary',
|
||||||
name: '标准列表',
|
name: '标准列表',
|
||||||
@ -188,7 +205,7 @@ const options = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'components',
|
path: 'components',
|
||||||
name: '小组件',
|
name: '内置组件',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'appstore-o'
|
icon: 'appstore-o'
|
||||||
},
|
},
|
||||||
@ -203,6 +220,11 @@ const options = {
|
|||||||
path: 'palette',
|
path: 'palette',
|
||||||
name: '颜色复选框',
|
name: '颜色复选框',
|
||||||
component: () => import('@/pages/components/Palette')
|
component: () => import('@/pages/components/Palette')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'table',
|
||||||
|
name: '高级表格',
|
||||||
|
component: () => import('@/pages/components/table')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -216,6 +238,44 @@ const options = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
component: () => import('@/pages/form/basic')
|
component: () => import('@/pages/form/basic')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '带参菜单',
|
||||||
|
path: 'router/query',
|
||||||
|
meta: {
|
||||||
|
icon: 'project',
|
||||||
|
query: {
|
||||||
|
name: '菜单默认参数'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
component: () => import('@/pages/Demo')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '动态路由菜单',
|
||||||
|
path: 'router/dynamic/:id',
|
||||||
|
meta: {
|
||||||
|
icon: 'project',
|
||||||
|
params: {
|
||||||
|
id: 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
component: () => import('@/pages/Demo')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ant Design Vue',
|
||||||
|
path: 'antdv',
|
||||||
|
meta: {
|
||||||
|
icon: 'ant-design',
|
||||||
|
link: 'https://www.antdv.com/docs/vue/introduce-cn/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '使用文档',
|
||||||
|
path: 'document',
|
||||||
|
meta: {
|
||||||
|
icon: 'file-word',
|
||||||
|
link: 'https://iczer.gitee.io/vue-antd-admin-docs/'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -66,13 +66,21 @@ const authorityGuard = (to, from, next, options) => {
|
|||||||
*/
|
*/
|
||||||
const redirectGuard = (to, from, next, options) => {
|
const redirectGuard = (to, from, next, options) => {
|
||||||
const {store} = options
|
const {store} = options
|
||||||
|
const getFirstChild = (routes) => {
|
||||||
|
const route = routes[0]
|
||||||
|
if (!route.children || route.children.length === 0) {
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
return getFirstChild(route.children)
|
||||||
|
}
|
||||||
if (store.state.setting.layout === 'mix') {
|
if (store.state.setting.layout === 'mix') {
|
||||||
const firstMenu = store.getters['setting/firstMenu']
|
const firstMenu = store.getters['setting/firstMenu']
|
||||||
if (firstMenu.find(item => item.fullPath === to.fullPath)) {
|
if (firstMenu.find(item => item.fullPath === to.fullPath)) {
|
||||||
store.commit('setting/setActivatedFirst', to.fullPath)
|
store.commit('setting/setActivatedFirst', to.fullPath)
|
||||||
const subMenu = store.getters['setting/subMenu']
|
const subMenu = store.getters['setting/subMenu']
|
||||||
if (subMenu.length > 0) {
|
if (subMenu.length > 0) {
|
||||||
return next({path: subMenu[0].fullPath})
|
const redirect = getFirstChild(subMenu)
|
||||||
|
return next({path: redirect.fullPath})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,7 @@
|
|||||||
const BASE_URL = process.env.VUE_APP_API_BASE_URL
|
const BASE_URL = process.env.VUE_APP_API_BASE_URL
|
||||||
module.exports = {
|
module.exports = {
|
||||||
LOGIN: `${BASE_URL}/login`,
|
LOGIN: `${BASE_URL}/login`,
|
||||||
ROUTES: `${BASE_URL}/routes`
|
ROUTES: `${BASE_URL}/routes`,
|
||||||
|
GOODS: `${BASE_URL}/goods`,
|
||||||
|
GOODS_COLUMNS: `${BASE_URL}/columns`,
|
||||||
}
|
}
|
||||||
|
12
src/services/dataSource.js
Normal file
12
src/services/dataSource.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import {GOODS, GOODS_COLUMNS} from './api'
|
||||||
|
import {METHOD, request} from '@/utils/request'
|
||||||
|
|
||||||
|
export async function goodsList(params) {
|
||||||
|
return request(GOODS, METHOD.GET, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function goodsColumns() {
|
||||||
|
return request(GOODS_COLUMNS, METHOD.GET)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {goodsList, goodsColumns}
|
@ -1,5 +1,7 @@
|
|||||||
import userService from './user'
|
import userService from './user'
|
||||||
|
import dataSource from './dataSource'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
userService
|
userService,
|
||||||
|
dataSource
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,11 @@ import {ADMIN} from '@/config/default'
|
|||||||
import {formatFullPath} from '@/utils/i18n'
|
import {formatFullPath} from '@/utils/i18n'
|
||||||
import {filterMenu} from '@/utils/authority-utils'
|
import {filterMenu} from '@/utils/authority-utils'
|
||||||
import {getLocalSetting} from '@/utils/themeUtil'
|
import {getLocalSetting} from '@/utils/themeUtil'
|
||||||
|
import deepClone from 'lodash.clonedeep'
|
||||||
|
|
||||||
const localSetting = getLocalSetting(true)
|
const localSetting = getLocalSetting(true)
|
||||||
|
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY)
|
||||||
|
const customTitles = (customTitlesStr && JSON.parse(customTitlesStr)) || []
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@ -15,6 +18,7 @@ export default {
|
|||||||
pageMinHeight: 0,
|
pageMinHeight: 0,
|
||||||
menuData: [],
|
menuData: [],
|
||||||
activatedFirst: undefined,
|
activatedFirst: undefined,
|
||||||
|
customTitles,
|
||||||
...config,
|
...config,
|
||||||
...localSetting
|
...localSetting
|
||||||
},
|
},
|
||||||
@ -22,12 +26,12 @@ export default {
|
|||||||
menuData(state, getters, rootState) {
|
menuData(state, getters, rootState) {
|
||||||
if (state.filterMenu) {
|
if (state.filterMenu) {
|
||||||
const {permissions, roles} = rootState.account
|
const {permissions, roles} = rootState.account
|
||||||
filterMenu(state.menuData, permissions, roles)
|
return filterMenu(deepClone(state.menuData), permissions, roles)
|
||||||
}
|
}
|
||||||
return state.menuData
|
return state.menuData
|
||||||
},
|
},
|
||||||
firstMenu(state) {
|
firstMenu(state, getters) {
|
||||||
const {menuData} = state
|
const {menuData} = getters
|
||||||
if (menuData.length > 0 && !menuData[0].fullPath) {
|
if (menuData.length > 0 && !menuData[0].fullPath) {
|
||||||
formatFullPath(menuData)
|
formatFullPath(menuData)
|
||||||
}
|
}
|
||||||
@ -39,11 +43,11 @@ export default {
|
|||||||
},
|
},
|
||||||
subMenu(state) {
|
subMenu(state) {
|
||||||
const {menuData, activatedFirst} = state
|
const {menuData, activatedFirst} = state
|
||||||
if (!menuData[0].fullPath) {
|
if (menuData.length > 0 && !menuData[0].fullPath) {
|
||||||
formatFullPath(menuData)
|
formatFullPath(menuData)
|
||||||
}
|
}
|
||||||
const current = menuData.find(menu => menu.fullPath === activatedFirst)
|
const current = menuData.find(menu => menu.fullPath === activatedFirst)
|
||||||
return current && current.children ? current.children : []
|
return current && current.children || []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
@ -91,6 +95,20 @@ export default {
|
|||||||
},
|
},
|
||||||
setActivatedFirst(state, activatedFirst) {
|
setActivatedFirst(state, activatedFirst) {
|
||||||
state.activatedFirst = activatedFirst
|
state.activatedFirst = activatedFirst
|
||||||
|
},
|
||||||
|
setFixedTabs(state, fixedTabs) {
|
||||||
|
state.fixedTabs = fixedTabs
|
||||||
|
},
|
||||||
|
setCustomTitle(state, {path, title}) {
|
||||||
|
if (title) {
|
||||||
|
const obj = state.customTitles.find(item => item.path === path)
|
||||||
|
if (obj) {
|
||||||
|
obj.title = title
|
||||||
|
} else {
|
||||||
|
state.customTitles.push({path, title})
|
||||||
|
}
|
||||||
|
sessionStorage.setItem(process.env.VUE_APP_TBAS_TITLES_KEY, JSON.stringify(state.customTitles))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@ function hasPermission(authority, permissions) {
|
|||||||
let required = '*'
|
let required = '*'
|
||||||
if (typeof authority === 'string') {
|
if (typeof authority === 'string') {
|
||||||
required = authority
|
required = authority
|
||||||
|
} else if (Array.isArray(authority)) {
|
||||||
|
required = authority
|
||||||
} else if (typeof authority === 'object') {
|
} else if (typeof authority === 'object') {
|
||||||
required = authority.permission
|
required = authority.permission
|
||||||
}
|
}
|
||||||
return required === '*' || (permissions && permissions.findIndex(item => item === required || item.id === required) !== -1)
|
return required === '*' || hasAnyItem(required, permissions, (r, t) => !!(r === t || r === t.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,25 +26,23 @@ function hasRole(authority, roles) {
|
|||||||
if (typeof authority === 'object') {
|
if (typeof authority === 'object') {
|
||||||
required = authority.role
|
required = authority.role
|
||||||
}
|
}
|
||||||
return authority === '*' || hasAnyRole(required, roles)
|
return authority === '*' || hasAnyItem(required, roles, (r, t) => !!(r === t || r === t.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否有需要的任意一个角色
|
* 判断目标数组是否有所需元素
|
||||||
* @param required {String | Array[String]} 需要的角色,可以是单个角色或者一个角色数组
|
* @param {String | String[]}required 所需元素,数组或单个元素
|
||||||
* @param roles 拥有的角色
|
* @param {String[]|Object[]} source 目标数组
|
||||||
|
* @param {Function} filter 匹配条件
|
||||||
|
* (r: String, s: String|Object) => boolean
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function hasAnyRole(required, roles) {
|
function hasAnyItem(required, source, filter) {
|
||||||
if (!required) {
|
if (!required) {
|
||||||
return false
|
return false
|
||||||
} else if(Array.isArray(required)) {
|
|
||||||
return roles.findIndex(role => {
|
|
||||||
return required.findIndex(item => item === role || item === role.id) !== -1
|
|
||||||
}) !== -1
|
|
||||||
} else {
|
|
||||||
return roles.findIndex(role => role === required || role.id === required) !== -1
|
|
||||||
}
|
}
|
||||||
|
let checkedList = Array.isArray(required) ? required : [required]
|
||||||
|
return !!source.find(s => checkedList.find(r => filter(r, s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,13 +69,16 @@ function hasAuthority(route, permissions, roles) {
|
|||||||
* @param roles
|
* @param roles
|
||||||
*/
|
*/
|
||||||
function filterMenu(menuData, permissions, roles) {
|
function filterMenu(menuData, permissions, roles) {
|
||||||
menuData.forEach(menu => {
|
return menuData.filter(menu => {
|
||||||
if (menu.meta && menu.meta.invisible === undefined) {
|
if (menu.meta && menu.meta.invisible === undefined) {
|
||||||
menu.meta.invisible = !hasAuthority(menu, permissions, roles)
|
if (!hasAuthority(menu, permissions, roles)) {
|
||||||
if (menu.children && menu.children.length > 0) {
|
return false
|
||||||
filterMenu(menu.children, permissions, roles)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (menu.children && menu.children.length > 0) {
|
||||||
|
menu.children = filterMenu(menu.children, permissions, roles)
|
||||||
|
}
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ const resp401 = {
|
|||||||
*/
|
*/
|
||||||
onFulfilled(response, options) {
|
onFulfilled(response, options) {
|
||||||
const {message} = options
|
const {message} = options
|
||||||
if (response.status === 401) {
|
if (response.code === 401) {
|
||||||
message.error('无此接口权限')
|
message.error('无此权限')
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
@ -22,7 +22,10 @@ const resp401 = {
|
|||||||
*/
|
*/
|
||||||
onRejected(error, options) {
|
onRejected(error, options) {
|
||||||
const {message} = options
|
const {message} = options
|
||||||
message.error(error.message)
|
const {response} = error
|
||||||
|
if (response.status === 401) {
|
||||||
|
message.error('无此权限')
|
||||||
|
}
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,10 +33,18 @@ const resp401 = {
|
|||||||
const resp403 = {
|
const resp403 = {
|
||||||
onFulfilled(response, options) {
|
onFulfilled(response, options) {
|
||||||
const {message} = options
|
const {message} = options
|
||||||
if (response.status === 403) {
|
if (response.code === 403) {
|
||||||
message.error(`请求被拒绝`)
|
message.error('请求被拒绝')
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
|
},
|
||||||
|
onRejected(error, options) {
|
||||||
|
const {message} = options
|
||||||
|
const {response} = error
|
||||||
|
if (response.status === 403) {
|
||||||
|
message.error('请求被拒绝')
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,14 +30,14 @@ const METHOD = {
|
|||||||
* @param params 请求参数
|
* @param params 请求参数
|
||||||
* @returns {Promise<AxiosResponse<T>>}
|
* @returns {Promise<AxiosResponse<T>>}
|
||||||
*/
|
*/
|
||||||
async function request(url, method, params) {
|
async function request(url, method, params, config) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case METHOD.GET:
|
case METHOD.GET:
|
||||||
return axios.get(url, {params})
|
return axios.get(url, {params, ...config})
|
||||||
case METHOD.POST:
|
case METHOD.POST:
|
||||||
return axios.post(url, params)
|
return axios.post(url, params, config)
|
||||||
default:
|
default:
|
||||||
return axios.get(url, {params})
|
return axios.get(url, {params, ...config})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +134,28 @@ function loadInterceptors(interceptors, options) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 url 中的参数
|
||||||
|
* @param url
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function parseUrlParams(url) {
|
||||||
|
const params = {}
|
||||||
|
if (!url || url === '' || typeof url !== 'string') {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
const paramsStr = url.split('?')[1]
|
||||||
|
if (!paramsStr) {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
const paramsArr = paramsStr.replace(/&|=/g, ' ').split(' ')
|
||||||
|
for (let i = 0; i < paramsArr.length / 2; i++) {
|
||||||
|
const value = paramsArr[i * 2 + 1]
|
||||||
|
params[paramsArr[i * 2]] = value === 'true' ? true : (value === 'false' ? false : value)
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
METHOD,
|
METHOD,
|
||||||
AUTH_TYPE,
|
AUTH_TYPE,
|
||||||
@ -141,5 +163,6 @@ export {
|
|||||||
setAuthorization,
|
setAuthorization,
|
||||||
removeAuthorization,
|
removeAuthorization,
|
||||||
checkAuthorization,
|
checkAuthorization,
|
||||||
loadInterceptors
|
loadInterceptors,
|
||||||
|
parseUrlParams
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,25 @@ import routerMap from '@/router/async/router.map'
|
|||||||
import {mergeI18nFromRoutes} from '@/utils/i18n'
|
import {mergeI18nFromRoutes} from '@/utils/i18n'
|
||||||
import Router from 'vue-router'
|
import Router from 'vue-router'
|
||||||
import deepMerge from 'deepmerge'
|
import deepMerge from 'deepmerge'
|
||||||
|
import basicOptions from '@/router/async/config.async'
|
||||||
|
|
||||||
|
//应用配置
|
||||||
|
let appOptions = {
|
||||||
|
router: undefined,
|
||||||
|
i18n: undefined,
|
||||||
|
store: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置应用配置
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
function setAppOptions(options) {
|
||||||
|
const {router, store, i18n} = options
|
||||||
|
appOptions.router = router
|
||||||
|
appOptions.store = store
|
||||||
|
appOptions.i18n = i18n
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 路由配置 和 路由组件注册 解析路由
|
* 根据 路由配置 和 路由组件注册 解析路由
|
||||||
@ -13,48 +32,83 @@ function parseRoutes(routesConfig, routerMap) {
|
|||||||
routesConfig.forEach(item => {
|
routesConfig.forEach(item => {
|
||||||
// 获取注册在 routerMap 中的 router,初始化 routeCfg
|
// 获取注册在 routerMap 中的 router,初始化 routeCfg
|
||||||
let router = undefined, routeCfg = {}
|
let router = undefined, routeCfg = {}
|
||||||
if (typeof item === 'string' && routerMap[item]) {
|
if (typeof item === 'string') {
|
||||||
router = routerMap[item]
|
router = routerMap[item]
|
||||||
routeCfg = {path: router.path || item, router: item}
|
routeCfg = {path: (router && router.path) || item, router: item}
|
||||||
} else if (typeof item === 'object') {
|
} else if (typeof item === 'object') {
|
||||||
router = routerMap[item.router]
|
router = routerMap[item.router]
|
||||||
routeCfg = item
|
routeCfg = item
|
||||||
}
|
}
|
||||||
// 从 router 和 routeCfg 解析路由
|
|
||||||
if (!router) {
|
if (!router) {
|
||||||
console.warn(`can't find register for router ${routeCfg.router}, please register it in advance.`)
|
console.warn(`can't find register for router ${routeCfg.router}, please register it in advance.`)
|
||||||
} else {
|
router = typeof item === 'string' ? {path: item, name: item} : item
|
||||||
const route = {
|
|
||||||
path: routeCfg.path || router.path || routeCfg.router,
|
|
||||||
name: routeCfg.name || router.name,
|
|
||||||
component: router.component,
|
|
||||||
redirect: routeCfg.redirect || router.redirect,
|
|
||||||
meta: {
|
|
||||||
authority: routeCfg.authority || router.authority || '*',
|
|
||||||
icon: routeCfg.icon || router.icon,
|
|
||||||
page: routeCfg.page || router.page
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (routeCfg.invisible || router.invisible) {
|
|
||||||
route.meta.invisible = true
|
|
||||||
}
|
|
||||||
if (routeCfg.children && routeCfg.children.length > 0) {
|
|
||||||
route.children = parseRoutes(routeCfg.children, routerMap)
|
|
||||||
}
|
|
||||||
routes.push(route)
|
|
||||||
}
|
}
|
||||||
|
// 从 router 和 routeCfg 解析路由
|
||||||
|
const meta = {
|
||||||
|
authority: router.authority,
|
||||||
|
icon: router.icon,
|
||||||
|
page: router.page,
|
||||||
|
link: router.link,
|
||||||
|
params: router.params,
|
||||||
|
query: router.query,
|
||||||
|
...router.meta
|
||||||
|
}
|
||||||
|
const cfgMeta = {
|
||||||
|
authority: routeCfg.authority,
|
||||||
|
icon: routeCfg.icon,
|
||||||
|
page: routeCfg.page,
|
||||||
|
link: routeCfg.link,
|
||||||
|
params: routeCfg.params,
|
||||||
|
query: routeCfg.query,
|
||||||
|
...routeCfg.meta
|
||||||
|
}
|
||||||
|
Object.keys(cfgMeta).forEach(key => {
|
||||||
|
if (cfgMeta[key] === undefined || cfgMeta[key] === null || cfgMeta[key] === '') {
|
||||||
|
delete cfgMeta[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Object.assign(meta, cfgMeta)
|
||||||
|
const route = {
|
||||||
|
path: routeCfg.path || router.path || routeCfg.router,
|
||||||
|
name: routeCfg.name || router.name,
|
||||||
|
component: router.component,
|
||||||
|
redirect: routeCfg.redirect || router.redirect,
|
||||||
|
meta: {...meta, authority: meta.authority || '*'}
|
||||||
|
}
|
||||||
|
if (router.beforeEnter) {
|
||||||
|
route.beforeEnter = router.beforeEnter
|
||||||
|
}
|
||||||
|
if (routeCfg.invisible || router.invisible) {
|
||||||
|
route.meta.invisible = true
|
||||||
|
}
|
||||||
|
if (routeCfg.children && routeCfg.children.length > 0) {
|
||||||
|
route.children = parseRoutes(routeCfg.children, routerMap)
|
||||||
|
}
|
||||||
|
routes.push(route)
|
||||||
})
|
})
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载路由
|
* 加载路由
|
||||||
* @param router 应用路由实例
|
* @param routesConfig {RouteConfig[]} 路由配置
|
||||||
* @param store 应用的 vuex.store 实例
|
|
||||||
* @param i18n 应用的 vue-i18n 实例
|
|
||||||
* @param routesConfig 路由配置
|
|
||||||
*/
|
*/
|
||||||
function loadRoutes({router, store, i18n}, routesConfig) {
|
function loadRoutes(routesConfig) {
|
||||||
|
//兼容 0.6.1 以下版本
|
||||||
|
/*************** 兼容 version < v0.6.1 *****************/
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
const arg0 = arguments[0]
|
||||||
|
if (arg0.router || arg0.i18n || arg0.store) {
|
||||||
|
routesConfig = arguments[1]
|
||||||
|
console.error('the usage of signature loadRoutes({router, store, i18n}, routesConfig) is out of date, please use the new signature: loadRoutes(routesConfig).')
|
||||||
|
console.error('方法签名 loadRoutes({router, store, i18n}, routesConfig) 的用法已过时, 请使用新的方法签名 loadRoutes(routesConfig)。')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*************** 兼容 version < v0.6.1 *****************/
|
||||||
|
|
||||||
|
// 应用配置
|
||||||
|
const {router, store, i18n} = appOptions
|
||||||
|
|
||||||
// 如果 routesConfig 有值,则更新到本地,否则从本地获取
|
// 如果 routesConfig 有值,则更新到本地,否则从本地获取
|
||||||
if (routesConfig) {
|
if (routesConfig) {
|
||||||
store.commit('account/setRoutesConfig', routesConfig)
|
store.commit('account/setRoutesConfig', routesConfig)
|
||||||
@ -66,8 +120,8 @@ function loadRoutes({router, store, i18n}, routesConfig) {
|
|||||||
if (asyncRoutes) {
|
if (asyncRoutes) {
|
||||||
if (routesConfig && routesConfig.length > 0) {
|
if (routesConfig && routesConfig.length > 0) {
|
||||||
const routes = parseRoutes(routesConfig, routerMap)
|
const routes = parseRoutes(routesConfig, routerMap)
|
||||||
formatRoutes(routes)
|
const finalRoutes = mergeRoutes(basicOptions.routes, routes)
|
||||||
const finalRoutes = mergeRoutes(router.options.routes, routes)
|
formatRoutes(finalRoutes)
|
||||||
router.options = {...router.options, routes: finalRoutes}
|
router.options = {...router.options, routes: finalRoutes}
|
||||||
router.matcher = new Router({...router.options, routes:[]}).matcher
|
router.matcher = new Router({...router.options, routes:[]}).matcher
|
||||||
router.addRoutes(finalRoutes)
|
router.addRoutes(finalRoutes)
|
||||||
@ -161,7 +215,7 @@ function formatAuthority(routes, pAuthorities = []) {
|
|||||||
let authority = {}
|
let authority = {}
|
||||||
if (!meta.authority) {
|
if (!meta.authority) {
|
||||||
authority = defaultAuthority
|
authority = defaultAuthority
|
||||||
}else if (typeof meta.authority === 'string') {
|
}else if (typeof meta.authority === 'string' || Array.isArray(meta.authority)) {
|
||||||
authority.permission = meta.authority
|
authority.permission = meta.authority
|
||||||
} else if (typeof meta.authority === 'object') {
|
} else if (typeof meta.authority === 'object') {
|
||||||
authority = meta.authority
|
authority = meta.authority
|
||||||
@ -216,4 +270,4 @@ function loadGuards(guards, options) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export {parseRoutes, loadRoutes, formatAuthority, getI18nKey, loadGuards, deepMergeRoutes, formatRoutes}
|
export {parseRoutes, loadRoutes, formatAuthority, getI18nKey, loadGuards, deepMergeRoutes, formatRoutes, setAppOptions}
|
||||||
|
@ -110,7 +110,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
publicPath: isProd ? '/vue-antd-admin/' : '/',
|
publicPath: process.env.VUE_APP_PUBLIC_PATH,
|
||||||
outputDir: 'dist',
|
outputDir: 'dist',
|
||||||
assetsDir: 'static',
|
assetsDir: 'static',
|
||||||
productionSourceMap: false
|
productionSourceMap: false
|
||||||
|
48
yarn.lock
48
yarn.lock
@ -1146,6 +1146,14 @@
|
|||||||
resolved "https://registry.npm.taobao.org/@nodelib/fs.stat/download/@nodelib/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
resolved "https://registry.npm.taobao.org/@nodelib/fs.stat/download/@nodelib/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
||||||
integrity sha1-K1o6s/kYzKSKjHVMCBaOPwPrphs=
|
integrity sha1-K1o6s/kYzKSKjHVMCBaOPwPrphs=
|
||||||
|
|
||||||
|
"@simonwep/pickr@~1.7.0":
|
||||||
|
version "1.7.4"
|
||||||
|
resolved "https://registry.npm.taobao.org/@simonwep/pickr/download/@simonwep/pickr-1.7.4.tgz#b14fcd945890388b870cd6db4d6c78d531f25141"
|
||||||
|
integrity sha1-sU/NlFiQOIuHDNbbTWx41THyUUE=
|
||||||
|
dependencies:
|
||||||
|
core-js "^3.6.5"
|
||||||
|
nanopop "^2.1.0"
|
||||||
|
|
||||||
"@sindresorhus/is@^0.14.0":
|
"@sindresorhus/is@^0.14.0":
|
||||||
version "0.14.0"
|
version "0.14.0"
|
||||||
resolved "https://registry.npm.taobao.org/@sindresorhus/is/download/@sindresorhus/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
resolved "https://registry.npm.taobao.org/@sindresorhus/is/download/@sindresorhus/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||||
@ -1959,13 +1967,14 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
|||||||
"@types/color-name" "^1.1.1"
|
"@types/color-name" "^1.1.1"
|
||||||
color-convert "^2.0.1"
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
ant-design-vue@^1.6.2:
|
ant-design-vue@1.7.2:
|
||||||
version "1.6.2"
|
version "1.7.2"
|
||||||
resolved "https://registry.npm.taobao.org/ant-design-vue/download/ant-design-vue-1.6.2.tgz?cache=0&sync_timestamp=1591081225900&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fant-design-vue%2Fdownload%2Fant-design-vue-1.6.2.tgz#983634caac9cdaca0b3a095b540105b4f76da29f"
|
resolved "https://registry.npm.taobao.org/ant-design-vue/download/ant-design-vue-1.7.2.tgz#aac7ff802205711631c8698e2a0c7b4e61dfd73e"
|
||||||
integrity sha1-mDY0yqyc2soLOglbVAEFtPdtop8=
|
integrity sha1-qsf/gCIFcRYxyGmOKgx7TmHf1z4=
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ant-design/icons" "^2.1.1"
|
"@ant-design/icons" "^2.1.1"
|
||||||
"@ant-design/icons-vue" "^2.0.0"
|
"@ant-design/icons-vue" "^2.0.0"
|
||||||
|
"@simonwep/pickr" "~1.7.0"
|
||||||
add-dom-event-listener "^1.0.2"
|
add-dom-event-listener "^1.0.2"
|
||||||
array-tree-filter "^2.1.0"
|
array-tree-filter "^2.1.0"
|
||||||
async-validator "^3.0.3"
|
async-validator "^3.0.3"
|
||||||
@ -2726,15 +2735,10 @@ caniuse-api@^3.0.0:
|
|||||||
lodash.memoize "^4.1.2"
|
lodash.memoize "^4.1.2"
|
||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061, caniuse-lite@^1.0.30001087:
|
||||||
version "1.0.30001083"
|
version "1.0.30001616"
|
||||||
resolved "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001083.tgz?cache=0&sync_timestamp=1592075334738&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaniuse-lite%2Fdownload%2Fcaniuse-lite-1.0.30001083.tgz#52410c20c6f029f604f0d45eca0439a82e712442"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001616.tgz"
|
||||||
integrity sha1-UkEMIMbwKfYE8NReygQ5qC5xJEI=
|
integrity sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001087:
|
|
||||||
version "1.0.30001088"
|
|
||||||
resolved "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001088.tgz#23a6b9e192106107458528858f2c0e0dba0d9073"
|
|
||||||
integrity sha1-I6a54ZIQYQdFhSiFjywODboNkHM=
|
|
||||||
|
|
||||||
case-sensitive-paths-webpack-plugin@^2.3.0:
|
case-sensitive-paths-webpack-plugin@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
@ -5302,6 +5306,11 @@ hex-color-regex@^1.1.0:
|
|||||||
resolved "https://registry.npm.taobao.org/hex-color-regex/download/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
resolved "https://registry.npm.taobao.org/hex-color-regex/download/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||||
integrity sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=
|
integrity sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=
|
||||||
|
|
||||||
|
highlight.js@^10.2.1:
|
||||||
|
version "10.2.1"
|
||||||
|
resolved "https://registry.npm.taobao.org/highlight.js/download/highlight.js-10.2.1.tgz#09784fe2e95612abbefd510948945d4fe6fa9668"
|
||||||
|
integrity sha1-CXhP4ulWEqu+/VEJSJRdT+b6lmg=
|
||||||
|
|
||||||
highlight.js@^9.6.0:
|
highlight.js@^9.6.0:
|
||||||
version "9.18.1"
|
version "9.18.1"
|
||||||
resolved "https://registry.npm.taobao.org/highlight.js/download/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c"
|
resolved "https://registry.npm.taobao.org/highlight.js/download/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c"
|
||||||
@ -6942,6 +6951,11 @@ nanomatch@^1.2.9:
|
|||||||
snapdragon "^0.8.1"
|
snapdragon "^0.8.1"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
|
nanopop@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.npm.taobao.org/nanopop/download/nanopop-2.1.0.tgz#23476513cee2405888afd2e8a4b54066b70b9e60"
|
||||||
|
integrity sha1-I0dlE87iQFiIr9LopLVAZrcLnmA=
|
||||||
|
|
||||||
natural-compare@^1.4.0:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npm.taobao.org/natural-compare/download/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.npm.taobao.org/natural-compare/download/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
@ -10404,10 +10418,10 @@ webpack-sources@*, webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sourc
|
|||||||
source-list-map "^2.0.0"
|
source-list-map "^2.0.0"
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
webpack-theme-color-replacer@^1.3.12:
|
webpack-theme-color-replacer@1.3.18:
|
||||||
version "1.3.12"
|
version "1.3.18"
|
||||||
resolved "https://registry.npm.taobao.org/webpack-theme-color-replacer/download/webpack-theme-color-replacer-1.3.12.tgz#0593a3149310c0e5b6b85afeccd61925b1b8e86b"
|
resolved "https://registry.npmjs.org/webpack-theme-color-replacer/-/webpack-theme-color-replacer-1.3.18.tgz#98b70eab698e40b06ea3c56a3db8590f7ccef847"
|
||||||
integrity sha1-BZOjFJMQwOW2uFr+zNYZJbG46Gs=
|
integrity sha512-z7qM3opvuSjAyJd0eLMOpZhH56r+fFctczWG6xnhUSeRsvbCg/EnFdsYoGL3xYJZNANvwLlggpJxnAcuFV5a6Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
webpack-sources "*"
|
webpack-sources "*"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user