Compare commits

...

32 Commits

Author SHA1 Message Date
chenghongxing
10a7613e6e Modify: Update the README preview address and document address;
修改:更新 README 预览地址及文档地址;
2024-05-08 11:35:51 +08:00
chenghongxing
3c79f89416 fix: Modify the preview site and documentation deployment configuration; 🐛
修改:预览网站及文档部署配置;
2024-05-08 11:20:13 +08:00
chenghongxing
35f0e431b8 fix: Fixed an issue where the role array configuration did not work; 🐛
修复:角色数组配置不生效的问题;
2024-05-08 11:18:56 +08:00
chenghongxing
60be8cf4ec fix: Fixed page refresh loading exception issue; 🐛
修复:修复页面刷新加载时异常问题;
2024-05-08 11:17:35 +08:00
chenghongxing
17e41ce1fc fix: Fixed the display problem of breadcrumbs on dynamic route page; 🐛
修复:修复动态路由页面的面包屑显示问题;
2023-04-11 10:28:01 +08:00
chenghongxing
baae56f715 add: support parameterized route multi-tab title setting; #339 🐛
新增:支持带参路由多页签标题设置;
2023-04-10 23:27:37 +08:00
iczer
9a7493c99c
Merge pull request #250 from matrix-zyh/patch-1
Fix: document errors;
修复:文档错误;
2023-03-29 10:53:27 +08:00
iczer
cc60ff1a27
Merge pull request #186 from fqucuo/patch-1
Optimization: Add beforeEnter parsing to asynchronous routing;
优化:异步路由中增加对beforeEnter的解析;
2023-03-29 10:51:14 +08:00
iczer
c0e5f2ccec
Update README.md 2023-03-27 12:19:23 +08:00
iczer
3fa8995690
Update README.md 2023-03-27 12:14:59 +08:00
chenghongxing
a4d4b28ce3 fix: fixed the issue that the router permission configuration does not support arrays
; 🐛
修复:修复路由权限配置不支持数组的问题;
2023-03-15 14:50:41 +08:00
chenghongxing
d7a530db46 fix: fixed the color problem in night mode; 🐛
修复:修复黑夜模式下颜色问题;
2023-03-12 17:24:48 +08:00
iczer
7f5fbb7426 fix: fixed border color consistent with vue3 version; 🐛
修复:修正边框颜色与 vue3 版本一致;
2023-03-12 14:40:07 +08:00
iczer
6000da4220 fix: the problem of disable cache of dynamic route; 🐛
修复:动态路由配置禁用缓存无效问题;
2023-01-12 18:14:59 +08:00
iczer
c0aec854af Merge branch 'master' of https://github.com/iczer/vue-antd-admin
 Conflicts:
	yarn.lock
2022-09-21 16:26:00 +08:00
iczer
8062905b17 fix: the check problem of standard table with pagination; 🐛
修复:标准表格分页勾选问题;
2022-09-21 16:24:30 +08:00
iczer
56635a948b
Merge pull request #265 from xcchcaptain/patch-1
更新文本错误
2021-11-23 11:11:34 +08:00
iczer
2d0c6c2a2b
Merge pull request #275 from Ten-K/fix/notice
fix(HeaderNotice.vue): change loadding to loading
2021-11-23 11:10:49 +08:00
iczer
5d4bd75b22
Merge pull request #274 from ggymm/patch-1
修复混合导航,头部菜单没有选中状态问题
2021-11-23 11:10:08 +08:00
lrl
6d43e9af42 fix(HeaderNotice.vue): change loadding to loading 2021-11-13 22:08:05 +08:00
ggymm
c2d2c2c686
修复混合导航,头部菜单没有选中状态问题 2021-11-02 15:20:16 +08:00
xcchcaptain
a9b3da4d19
更新文本错误
依据上下文,此处应为“permission”
2021-09-26 11:15:12 +08:00
Matrix
baf063f9ea
onFulfilled -> onRejected
响应拦截器  - onRejected 那部分,单词写错了,大大你把写成了 onRejected 写成了 onFulfilled
2021-08-06 14:31:23 +08:00
chenghongxing
816d19f7da 修复:带查询参数的路由导致多页签出现页面多开问题; 2021-05-08 20:41:57 +08:00
chenghongxing
ce83564335 修复:异步路由部分配置不生效问题; 2021-05-08 20:41:02 +08:00
chenghongxing
1345a02cd0 update version 2021-05-06 12:19:30 +08:00
chenghongxing
449fd99f9d 新增:增加列表详情页demo、动态路由菜单demo、带参路由菜单demo; 2021-05-06 12:11:55 +08:00
chenghongxing
97a1417112 新增:路由配置增加动态路由参数支持 和 高亮菜单配置; 2021-05-06 11:13:35 +08:00
chenghongxing
ba89880736 修复:混合模式下菜单权限过滤无效问题; 2021-05-05 22:07:25 +08:00
iczer
8d42b936e5 Merge remote-tracking branch 'origin/master' 2021-04-20 12:07:27 +08:00
iczer
da1dafda54 修复:npm 安装 webpack-theme-color-replacer 插件启动报错问题;🐛 2021-04-20 12:07:06 +08:00
fqucuo
f0d60a8242
异步路由中增加对beforeEnter的解析
异步路由中增加对beforeEnter的解析
2021-01-24 00:19:22 +08:00
25 changed files with 351 additions and 121 deletions

View File

@ -16,9 +16,9 @@ Multiple theme modes available
![image](./src/assets/img/preview-nine.png)
</div>
- Previewhttps://iczer.gitee.io/vue-antd-admin
- Documentationhttps://iczer.gitee.io/vue-antd-admin-docs
- FAQhttps://iczer.gitee.io/vue-antd-admin-docs/start/faq.html
- Previewhttps://vue-antd-admin.pages.dev
- Documentationhttps://doc.vue-antd-admin.pages.dev
- FAQhttps://doc.vue-antd-admin.pages.dev/start/faq.html
- Mirror Repo in Chinahttps://gitee.com/iczer/vue-antd-admin
## Browsers support

View File

@ -1,3 +1,5 @@
简体中文 | [English](./README.en-US.md)
<h1 align="center">Vue Antd Admin</h1>
@ -6,6 +8,12 @@
[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)
--
[![MIT](https://img.shields.io/github/license/iczer/vue-antd-admin)](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE)
[![Dependence](https://img.shields.io/david/iczer/vue-antd-admin)](https://david-dm.org/iczer/vue-antd-admin)
[![DevDependencies](https://img.shields.io/david/dev/iczer/vue-antd-admin)](https://david-dm.org/iczer/vue-antd-admin?type=dev)
@ -16,9 +24,9 @@
![image](./src/assets/img/preview-nine.png)
</div>
- 预览地址https://iczer.gitee.io/vue-antd-admin
- 使用文档https://iczer.gitee.io/vue-antd-admin-docs
- 常见问题https://iczer.gitee.io/vue-antd-admin-docs/start/faq.html
- 预览地址https://vue-antd-admin.pages.dev
- 使用文档https://doc.vue-antd-admin.pages.dev
- 常见问题https://doc.vue-antd-admin.pages.dev/start/faq.html
- 国内镜像https://gitee.com/iczer/vue-antd-admin
## 浏览器支持

View File

@ -1,7 +1,7 @@
module.exports = {
title: 'Vue Antd Admin',
description: 'Vue Antd Admin',
base: '/vue-antd-admin-docs/',
base: '/',
head: [
['link', { rel: 'icon', href: '/favicon.ico' }]
],

View File

@ -31,7 +31,7 @@ permission = {
operation: ['add', 'delete', 'edit', 'close'] //权限下的操作权限
}
```
你也可以设置 role 的值为字符串,比如 permission = 'form', 它等同于:
你也可以设置 permission 的值为字符串,比如 permission = 'form', 它等同于:
```js
permission = {
id: 'form'

View File

@ -44,7 +44,7 @@ const tokenCheck = {
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
### onRejected
我们会为 onFulfilled 钩子函数注入 error 和 options 两个参数:
我们会为 onRejected 钩子函数注入 error 和 options 两个参数:
* `error: Error`: axios 请求错误对象
* `options: Object`: 应用配置,包含: {router, i18n, store, message},可根据需要扩展。
@ -128,4 +128,4 @@ export default {
response: [resp401, resp403] // 响应拦截
}
```
:::
:::

View File

@ -1,17 +1,18 @@
{
"name": "vue-antd-admin",
"version": "0.7.2",
"version": "0.7.4",
"homepage": "https://iczer.github.io/vue-antd-admin",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
"lint": "vue-cli-service lint",
"predeploy": "yarn build",
"deploy": "gh-pages -d dist -b pages -r https://gitee.com/iczer/vue-antd-admin.git",
"docs:dev": "vuepress dev docs",
"docs:build": "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"
"deploy": "gh-pages -d dist -b pages -r https://github.com/iczer/vue-antd-admin.git",
"docs:dev": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress dev docs",
"docs:build": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress build docs",
"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": {
"@antv/data-set": "^0.11.4",

View File

@ -38,6 +38,26 @@ import {getI18nKey} from '@/utils/routerUtil'
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 {
name: 'IMenu',
props: {
@ -73,6 +93,9 @@ export default {
computed: {
menuTheme() {
return this.theme == 'light' ? this.theme : 'dark'
},
routesMap() {
return toRoutesMap(this.options)
}
},
created () {
@ -132,7 +155,8 @@ export default {
},
renderMenuItem: function (h, menu) {
let tag = 'router-link'
let config = {props: {to: menu.fullPath}, attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;'}}
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'}}
@ -200,16 +224,23 @@ export default {
})
},
updateMenu () {
const matchedRoutes = this.$route.matched.filter(item => item.path !== '')
this.selectedKeys = this.getSelectedKey(this.$route)
let openKeys = matchedRoutes.map(item => item.path)
this.selectedKeys = this.getSelectedKeys()
let openKeys = this.selectedKeys.filter(item => item !== '')
openKeys = openKeys.slice(0, openKeys.length -1)
if (!fastEqual(openKeys, this.sOpenKeys)) {
this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys
}
},
getSelectedKey (route) {
return route.matched.map(item => item.path)
getSelectedKeys() {
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) {

View File

@ -23,7 +23,7 @@
:expandedRowKeys="expandedRowKeys"
:expandedRowRender="expandedRowRender"
@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') ">
<slot :name="slot" v-bind="{text, record, index}"></slot>
@ -64,22 +64,70 @@ export default {
}
},
methods: {
updateSelect (selectedRowKeys, selectedRows) {
this.$emit('update:selectedRows', selectedRows)
this.$emit('selectedRowChange', selectedRowKeys, selectedRows)
equals(record1, record2) {
if (record1 === record2) {
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) {
const totalList = columns.filter(item => item.needTotal)
return columns.filter(item => item.needTotal)
.map(item => {
return {
...item,
total: 0
}
})
return totalList
},
onClear() {
this.updateSelect([], [])
this.$emit('update:selectedRows', [])
this.$emit('selectedRowChange', [], [])
this.$emit('clear')
},
onChange(pagination, filters, sorter, {currentDataSource}) {
@ -110,10 +158,8 @@ export default {
},
computed: {
selectedRowKeys() {
return this.selectedRows.map(record => {
return (typeof this.rowKey === 'function') ? this.rowKey(record) : record[this.rowKey]
})
}
return this.selectedRows.map(record => this.getKey(record))
},
}
}
</script>

View File

@ -22,10 +22,11 @@ const ANTD = {
'component-background': '#fff',
'heading-color': 'rgba(0, 0, 0, 0.85)',
'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)',
'shadow-color': 'rgba(0, 0, 0, 0.15)',
'border-color-split': '#f0f0f0',
'border-color-base': '#d9d9d9',
'background-color-light': '#fafafa',
'background-color-base': '#f5f5f5',
'table-selected-row-bg': '#fafafa',
@ -34,8 +35,9 @@ const ANTD = {
'disabled-color': 'rgba(0, 0, 0, 0.25)',
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
'menu-dark-highlight-color': '#fefefe',
'menu-dark-selected-item-icon-color': '#fefefe',
'menu-dark-arrow-color': '#fefefe',
'btn-primary-color': '#fff',
'btn-primary-color': '#fefefe',
},
light: {
'layout-body-background': '#f0f2f5',
@ -43,10 +45,11 @@ const ANTD = {
'component-background': '#fff',
'heading-color': 'rgba(0, 0, 0, 0.85)',
'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)',
'shadow-color': 'rgba(0, 0, 0, 0.15)',
'border-color-split': '#f0f0f0',
'border-color-base': '#d9d9d9',
'background-color-light': '#fafafa',
'background-color-base': '#f5f5f5',
'table-selected-row-bg': '#fafafa',
@ -55,8 +58,9 @@ const ANTD = {
'disabled-color': 'rgba(0, 0, 0, 0.25)',
'menu-dark-color': 'rgba(1, 1, 1, 0.65)',
'menu-dark-highlight-color': '#fefefe',
'menu-dark-selected-item-icon-color': '#fefefe',
'menu-dark-arrow-color': '#fefefe',
'btn-primary-color': '#fff',
'btn-primary-color': '#fefefe',
},
night: {
'layout-body-background': '#000',
@ -64,10 +68,11 @@ const ANTD = {
'component-background': '#141414',
'heading-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)',
'shadow-color': 'rgba(255, 255, 255, 0.15)',
'border-color-split': '#303030',
'border-color-base': '#282828',
'background-color-light': '#ffffff0a',
'background-color-base': '#2a2a2a',
'table-selected-row-bg': '#ffffff0a',
@ -76,8 +81,9 @@ const ANTD = {
'disabled-color': 'rgba(255, 255, 255, 0.25)',
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
'menu-dark-highlight-color': '#fefefe',
'menu-dark-selected-item-icon-color': '#fefefe',
'menu-dark-arrow-color': '#fefefe',
'btn-primary-color': '#141414',
'btn-primary-color': '#fefefe',
}
}
}

View File

@ -92,7 +92,7 @@ export default {
let routes = this.$route.matched
const path = this.$route.path
let breadcrumb = []
routes.filter(item => path.includes(item.path))
routes.filter(item => path.includes(item.path) || item.regex.test(path))
.forEach(route => {
const path = route.path.length === 0 ? '/home' : route.path
breadcrumb.push(this.$t(getI18nKey(path)))

View File

@ -25,13 +25,13 @@ export default {
computed: {
...mapState('setting', ['isMobile', 'multiPage', 'animate']),
desc() {
return this.page.desc
return this.page?.desc
},
linkList() {
return this.page.linkList
return this.page?.linkList
},
extraImage() {
return this.page.extraImage
return this.page?.extraImage
}
},
mounted () {

View File

@ -56,9 +56,10 @@ export default {
this.loading = false
return
}
this.loadding = true
if (this.show) return
this.loading = true
setTimeout(() => {
this.loadding = false
this.loading = false
}, 1000)
}
}

View File

@ -14,11 +14,11 @@
:type="fixedTabs ? 'lock' : 'unlock'"
/>
</a-tooltip>
<a-tab-pane v-for="page in pageList" :key="page.fullPath">
<div slot="tab" class="tab" @contextmenu="e => onContextmenu(page.fullPath, e)">
<a-icon @click="onRefresh(page)" :class="['icon-sync', {'hide': page.fullPath !== active && !page.loading}]" :type="page.loading ? 'loading' : 'sync'" />
<div class="title" @click="onTabClick(page.fullPath)" >{{pageName(page)}}</div>
<a-icon v-if="!page.unclose" @click="onClose(page.fullPath)" class="icon-close" type="close"/>
<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>
@ -89,14 +89,13 @@
this.$emit('close', key)
},
onRefresh(page) {
this.$emit('refresh', page.fullPath, page)
this.$emit('refresh', page.path, page)
},
onContextmenu(pageKey, e) {
this.$emit('contextmenu', pageKey, e)
},
pageName(page) {
const pagePath = page.fullPath.split('?')[0]
const custom = this.customTitles.find(item => item.path === pagePath)
const custom = this.customTitles.find(item => item.path === page.path)
return (custom && custom.title) || page.title || this.$t(getI18nKey(page.keyPath))
}
}
@ -185,4 +184,4 @@
.virtual-tabs{
height: 48px;
}
</style>
</style>

View File

@ -62,7 +62,7 @@ export default {
this.loadCacheConfig(this.$router?.options?.routes)
this.loadCachedTabs()
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.activePage = route.fullPath
@ -87,9 +87,12 @@ export default {
},
'$route': function (newRoute) {
this.activePage = newRoute.fullPath
const page = this.pageList.find(item => item.path === newRoute.fullPath)
if (!this.multiPage) {
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))
}
if (this.multiPage) {
@ -113,25 +116,26 @@ export default {
methods: {
changePage (key) {
this.activePage = key
this.$router.push(key)
const page = this.pageList.find(item => item.path === key)
this.$router.push(page.fullPath)
},
remove (key, next) {
if (this.pageList.length === 1) {
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)
if (next) {
this.$router.push(next)
} else if (key === this.activePage) {
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)
}
},
refresh (key, page) {
page = page || this.pageList.find(item => item.fullPath === key)
page = page || this.pageList.find(item => item.path === key)
page.loading = true
this.clearCache(page)
if (key === this.activePage) {
@ -159,7 +163,7 @@ export default {
},
closeOthers (pageKey) {
//
const clearPages = this.pageList.filter(item => item.fullPath !== pageKey && !item.unclose)
const clearPages = this.pageList.filter(item => item.path !== pageKey && !item.unclose)
this.clearCaches = clearPages.map(item => item.cachedKey)
this.pageList = this.pageList.filter(item => !clearPages.includes(item))
//
@ -169,25 +173,25 @@ export default {
}
},
closeLeft (pageKey) {
const index = this.pageList.findIndex(item => item.fullPath === pageKey)
const index = this.pageList.findIndex(item => item.path === pageKey)
//
const clearPages = this.pageList.filter((item, i) => i < index && !item.unclose)
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.$router.push(this.activePage)
}
},
closeRight (pageKey) {
//
const index = this.pageList.findIndex(item => item.fullPath === pageKey)
const index = this.pageList.findIndex(item => item.path === pageKey)
const clearPages = this.pageList.filter((item, i) => i > index && !item.unclose)
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.$router.push(this.activePage)
}
@ -234,7 +238,8 @@ export default {
closePageListener(event) {
const {closeRoute, nextRoute} = event.detail
const closePath = typeof closeRoute === 'string' ? closeRoute : closeRoute.path
this.remove(closePath, nextRoute)
const path = closePath && closePath.split('?')[0]
this.remove(path, nextRoute)
},
/**
* 页面刷新事件监听
@ -242,7 +247,8 @@ export default {
*/
refreshPageListener(event) {
const {pageKey} = event.detail
this.refresh(pageKey)
const path = pageKey && pageKey.split('?')[0]
this.refresh(path)
},
/**
* 页面 unload 事件监听器添加页签到 session 缓存用于刷新时保留页签
@ -255,6 +261,7 @@ export default {
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),
}
@ -264,7 +271,7 @@ export default {
* @param 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_) {
const vnode = this.$refs.tabContent.$vnode
@ -294,7 +301,7 @@ export default {
routes.forEach(item => {
const cacheAble = item.meta?.page?.cacheAble ?? pCache ?? true
if (!cacheAble) {
this.excludeKeys.push(new RegExp(`${item.fullPath}\\d+$`))
this.excludeKeys.push(new RegExp(`${item.path.replace(/:[^/]*/g, '[^/]*')}(\\?.*)?\\d*$`))
}
if (item.children) {
this.loadCacheConfig(item.children, cacheAble)

View File

@ -5,6 +5,7 @@ import '@/mock/user/login'
import '@/mock/workplace'
import '@/mock/user/routes'
import '@/mock/goods'
import '@/mock/list'
// 设置全局延时
Mock.setup({

52
src/mock/list/index.js Normal file
View 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
}
}
})

16
src/pages/Demo.vue Normal file
View 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>

View File

@ -98,6 +98,7 @@
:selectedRows.sync="selectedRows"
@clear="onClear"
@change="onChange"
:pagination="{...pagination, onChange: onPageChange}"
@selectedRowChange="onSelectChange"
>
<div slot="description" slot-scope="{text}">
@ -116,6 +117,7 @@
<a @click="deleteRecord(record.key)" v-auth="`delete`">
<a-icon type="delete" />删除2
</a>
<router-link :to="`/list/query/detail/${record.key}`" >详情</router-link>
</div>
<template slot="statusTitle">
<a-icon @click.native="onStatusTitleClick" type="info-circle" />
@ -127,6 +129,7 @@
<script>
import StandardTable from '@/components/table/StandardTable'
import {request} from '@/utils/request'
const columns = [
{
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 {
name: 'QueryList',
components: {StandardTable},
@ -180,14 +170,37 @@ export default {
return {
advanced: true,
columns: columns,
dataSource: dataSource,
selectedRows: []
dataSource: [],
selectedRows: [],
pagination: {
current: 1,
pageSize: 10,
total: 0
}
}
},
authorize: {
deleteRecord: 'delete'
},
mounted() {
this.getData()
},
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) {
this.dataSource = this.dataSource.filter(item => item.key !== key)
this.selectedRows = this.selectedRows.filter(item => item.key !== key)

View File

@ -39,9 +39,9 @@ const auth = function(authConfig, permission, role, permissions, roles) {
if (type === 'permission') {
return checkFromPermission(check, permission)
} else if (type === 'role') {
return checkFromRoles(check, role)
return checkFromRoles(check, roles)
} else {
return checkFromPermission(check, permission) || checkFromRoles(check, role)
return checkFromPermission(check, permission) || checkFromRoles(check, roles)
}
}

View File

@ -17,8 +17,9 @@ const TabsPagePlugin = {
},
$setPageTitle(route, title) {
if (title) {
let path = typeof route === 'object' ? route.path : route
path = path && path.split('?')[0]
// 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})
}
}
@ -26,8 +27,8 @@ const TabsPagePlugin = {
computed: {
customTitle() {
const customTitles = this.$store.state.setting.customTitles
const path = this.$route.path.split('?')[0]
const custom = customTitles.find(item => item.path === path)
// const path = this.$route.path.split('?')[0]
const custom = customTitles.find(item => item.path === this.$route.fullPath)
return custom && custom.title
}
}

View File

@ -95,6 +95,15 @@ const options = {
},
component: () => import('@/pages/list/QueryList'),
},
{
path: 'query/detail/:id',
name: '查询详情',
meta: {
highlight: '/list/query',
invisible: true
},
component: () => import('@/pages/Demo')
},
{
path: 'primary',
name: '标准列表',
@ -230,6 +239,28 @@ const options = {
},
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',

View File

@ -30,8 +30,8 @@ export default {
}
return state.menuData
},
firstMenu(state) {
const {menuData} = state
firstMenu(state, getters) {
const {menuData} = getters
if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData)
}

View File

@ -8,10 +8,12 @@ function hasPermission(authority, permissions) {
let required = '*'
if (typeof authority === 'string') {
required = authority
} else if (Array.isArray(authority)) {
required = authority
} else if (typeof authority === 'object') {
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') {
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 roles 拥有的角色
* 判断目标数组是否有所需元素
* @param {String | String[]}required 所需元素数组或单个元素
* @param {String[]|Object[]} source 目标数组
* @param {Function} filter 匹配条件
* (r: String, s: String|Object) => boolean
* @returns {boolean}
*/
function hasAnyRole(required, roles) {
function hasAnyItem(required, source, filter) {
if (!required) {
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)))
}
/**

View File

@ -44,17 +44,39 @@ function parseRoutes(routesConfig, routerMap) {
router = typeof item === 'string' ? {path: item, name: item} : item
}
// 从 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: {
authority: routeCfg.authority || router.authority || routeCfg.meta?.authority || router.meta?.authority || '*',
icon: routeCfg.icon || router.icon || routeCfg.meta?.icon || router.meta?.icon,
page: routeCfg.page || router.page || routeCfg.meta?.page || router.meta?.page,
link: routeCfg.link || router.link || routeCfg.meta?.link || router.meta?.link
}
meta: {...meta, authority: meta.authority || '*'}
}
if (router.beforeEnter) {
route.beforeEnter = router.beforeEnter
}
if (routeCfg.invisible || router.invisible) {
route.meta.invisible = true
@ -193,7 +215,7 @@ function formatAuthority(routes, pAuthorities = []) {
let authority = {}
if (!meta.authority) {
authority = defaultAuthority
}else if (typeof meta.authority === 'string') {
}else if (typeof meta.authority === 'string' || Array.isArray(meta.authority)) {
authority.permission = meta.authority
} else if (typeof meta.authority === 'object') {
authority = meta.authority

View File

@ -2735,15 +2735,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061:
version "1.0.30001083"
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"
integrity sha1-UkEMIMbwKfYE8NReygQ5qC5xJEI=
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=
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061, caniuse-lite@^1.0.30001087:
version "1.0.30001616"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001616.tgz"
integrity sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==
case-sensitive-paths-webpack-plugin@^2.3.0:
version "2.3.0"