Compare commits

..

No commits in common. "master" and "0.7.0" have entirely different histories.

50 changed files with 218 additions and 842 deletions

2
.env
View File

@ -1,4 +1,3 @@
VUE_APP_PUBLIC_PATH=/
VUE_APP_NAME=Admin
VUE_APP_ROUTES_KEY=admin.routes
VUE_APP_PERMISSIONS_KEY=admin.permissions
@ -6,5 +5,4 @@ VUE_APP_ROLES_KEY=admin.roles
VUE_APP_USER_KEY=admin.user
VUE_APP_SETTING_KEY=admin.setting
VUE_APP_TBAS_KEY=admin.tabs
VUE_APP_TBAS_TITLES_KEY=admin.tabs.titles
VUE_APP_API_BASE_URL=http://api.iczer.com

1
.gitignore vendored
View File

@ -17,4 +17,3 @@ selenium-debug.log
*.njsproj
*.sln
package-lock.json
.env.production.local

View File

@ -16,9 +16,9 @@ Multiple theme modes available
![image](./src/assets/img/preview-nine.png)
</div>
- Previewhttps://vue-antd-admin.pages.dev
- Documentationhttps://doc.vue-antd-admin.pages.dev
- FAQhttps://doc.vue-antd-admin.pages.dev/start/faq.html
- Previewhttps://iczer.gitee.io/vue-antd-admin
- Documentationhttps://iczer.gitee.io/vue-antd-admin-docs
- FAQhttps://iczer.github.io/vue-antd-admin/start/faq.html
- Mirror Repo in Chinahttps://gitee.com/iczer/vue-antd-admin
## Browsers support
@ -43,11 +43,11 @@ $ yarn serve
$ npm install
$ npm run serve
```
More instructions at [documentation](https://iczer.gitee.io/vue-antd-admin-docs).
More instructions at [documentation](https://iczer.github.io/vue-antd-admin).
## Contributing
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.
- 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.
- Join the community and share your experiences with us. QQ Group:942083829、812277510(full)、610090280(full)
- Join the community and share your experiences with us. QQ Group: 610090280

View File

@ -1,5 +1,3 @@
简体中文 | [English](./README.en-US.md)
<h1 align="center">Vue Antd Admin</h1>
@ -8,12 +6,6 @@
[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)
@ -24,9 +16,9 @@ vue3 版本现已推出,更名为
![image](./src/assets/img/preview-nine.png)
</div>
- 预览地址https://vue-antd-admin.pages.dev
- 使用文档https://doc.vue-antd-admin.pages.dev
- 常见问题https://doc.vue-antd-admin.pages.dev/start/faq.html
- 预览地址https://iczer.gitee.io/vue-antd-admin
- 使用文档https://iczer.gitee.io/vue-antd-admin-docs
- 常见问题https://iczer.github.io/vue-antd-admin/start/faq.html
- 国内镜像https://gitee.com/iczer/vue-antd-admin
## 浏览器支持
@ -51,18 +43,11 @@ $ yarn serve
$ npm install
$ npm run serve
```
更多信息参考 [使用文档](https://iczer.gitee.io/vue-antd-admin-docs)
更多信息参考 [使用文档](https://iczer.github.io/vue-antd-admin)
## 参与贡献
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :star2:
- 在你的公司或个人项目中使用 Vue Antd Admin。
- 通过 [Issue](https://github.com/iczer/vue-antd-admin/issues) 报告:bug:或进行咨询。
- 提交 [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) 改进 Admin 的代码。
- 加入社群与小伙伴们一同交流心得。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>
- 加入社群与小伙伴们一同交流心得。QQ群610090280

View File

@ -1,7 +1,7 @@
module.exports = {
title: 'Vue Antd Admin',
description: 'Vue Antd Admin',
base: '/',
base: '/vue-antd-admin-docs/',
head: [
['link', { rel: 'icon', href: '/favicon.ico' }]
],
@ -36,8 +36,7 @@ module.exports = {
title: '进阶',
collapsable: false,
children: [
'/advance/i18n', '/advance/async', '/advance/authority', '/advance/login', '/advance/guard', '/advance/interceptors',
'/advance/api'
'/advance/i18n', '/advance/async', '/advance/authority', '/advance/login', '/advance/guard', '/advance/interceptors'
]
},
{

View File

@ -1,42 +0,0 @@
---
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`,即通过权限校验还是角色进行校验,可不传(不传的话,会对两种类型都进行匹配,任意一种匹配成功即校验通过)。

View File

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

View File

@ -3,127 +3,5 @@ title: 国际化
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/) 。
### 作者还没来得及编辑该页面,如果你感兴趣,可以点击下方链接,帮助作者完善此页

View File

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

View File

@ -5,4 +5,4 @@ lang: zh-CN
# 社区
## 交流学习
### QQ群812277510、610090280(已满)
### QQ群610090280

View File

@ -1,23 +1,22 @@
{
"name": "vue-antd-admin",
"version": "0.7.4",
"version": "0.7.0",
"homepage": "https://iczer.github.io/vue-antd-admin",
"private": true,
"scripts": {
"serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"predeploy": "yarn build",
"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"
"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"
},
"dependencies": {
"@antv/data-set": "^0.11.4",
"animate.css": "^4.1.0",
"ant-design-vue": "1.7.2",
"ant-design-vue": "^1.6.2",
"axios": "^0.19.2",
"clipboard": "^2.0.6",
"core-js": "^3.6.5",
@ -54,7 +53,7 @@
"vue-cli-plugin-style-resources-loader": "^0.1.4",
"vue-template-compiler": "^2.6.11",
"vuepress": "^1.5.2",
"webpack-theme-color-replacer": "1.3.18",
"webpack-theme-color-replacer": "^1.3.12",
"whatwg-fetch": "^3.0.0"
},
"eslintConfig": {

View File

@ -11,13 +11,11 @@
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
</head>
<body>
<body class="beauty-scroll">
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="popContainer" class="beauty-scroll" style="height: 100vh; overflow-y: scroll">
<div id="app"></div>
</div>
<div id="app"></div>
<!-- require cdn assets js -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>

View File

@ -1,5 +1,5 @@
<template>
<a-config-provider :locale="locale" :get-popup-container="popContainer">
<a-config-provider :locale="locale">
<router-view/>
</a-config-provider>
</template>
@ -31,7 +31,6 @@ export default {
},
lang(val) {
this.setLanguage(val)
this.setHtmlTitle()
},
$route() {
this.setHtmlTitle()
@ -43,13 +42,10 @@ export default {
'theme.color': function(val) {
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`)
themeUtil.changeThemeColor(val, this.theme.mode).then(closeMessage)
},
'layout': function() {
window.dispatchEvent(new Event('resize'))
}
},
computed: {
...mapState('setting', ['layout', 'theme', 'weekMode', 'lang'])
...mapState('setting', ['theme', 'weekMode', 'lang'])
},
methods: {
...mapMutations('setting', ['setDevice']),
@ -80,9 +76,6 @@ export default {
const key = route.path === '/' ? 'home.name' : getI18nKey(route.matched[route.matched.length - 1].path)
document.title = process.env.VUE_APP_NAME + ' | ' + this.$t(key)
},
popContainer() {
return document.getElementById("popContainer")
}
}
}
</script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

View File

@ -4,16 +4,7 @@ const patternTypes = [String, RegExp, Array]
function matches (pattern, name) {
if (Array.isArray(pattern)) {
if (pattern.indexOf(name) > -1) {
return true
} else {
for (let item of pattern) {
if (isRegExp(item) && item.test(name)) {
return true
}
}
return false
}
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
@ -27,13 +18,6 @@ function getComponentName (opts) {
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) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
@ -51,8 +35,7 @@ function pruneCache (keepAliveInstance, filter) {
const cachedNode = cache[key]
if (cachedNode) {
const name = getComponentName(cachedNode.componentOptions)
const componentKey = getComponentKey(cachedNode)
if (name && !filter(name, componentKey)) {
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
@ -87,7 +70,6 @@ export default {
props: {
include: patternTypes,
exclude: patternTypes,
excludeKeys: patternTypes,
max: [String, Number],
clearCaches: Array
},
@ -116,13 +98,10 @@ export default {
mounted () {
this.$watch('include', val => {
pruneCache(this, (name) => matches(val, name))
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, (name) => !matches(val, name))
})
this.$watch('excludeKeys', val => {
pruneCache(this, (name, key) => !matches(val, key))
pruneCache(this, name => !matches(val, name))
})
},
@ -133,14 +112,12 @@ export default {
if (componentOptions) {
// check pattern
const name = getComponentName(componentOptions)
const componentKey = getComponentKey(vnode)
const { include, exclude, excludeKeys } = this
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name)) ||
(excludeKeys && componentKey && matches(excludeKeys, componentKey))
(exclude && name && matches(exclude, name))
) {
return vnode
}

View File

@ -38,26 +38,6 @@ 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: {
@ -93,9 +73,6 @@ export default {
computed: {
menuTheme() {
return this.theme == 'light' ? this.theme : 'dark'
},
routesMap() {
return toRoutesMap(this.options)
}
},
created () {
@ -155,8 +132,7 @@ export default {
},
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;'}}
let config = {props: {to: menu.fullPath}, 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'}}
@ -224,23 +200,15 @@ export default {
})
},
updateMenu () {
this.selectedKeys = this.getSelectedKeys()
let openKeys = this.selectedKeys.filter(item => item !== '')
openKeys = openKeys.slice(0, openKeys.length -1)
const menuRoutes = this.$route.matched.filter(item => item.path !== '')
this.selectedKeys = this.getSelectedKey(this.$route)
let openKeys = menuRoutes.map(item => item.path)
if (!fastEqual(openKeys, this.sOpenKeys)) {
this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys
}
},
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)
getSelectedKey (route) {
return route.matched.map(item => item.path)
}
},
render (h) {

View File

@ -23,7 +23,7 @@
:expandedRowKeys="expandedRowKeys"
:expandedRowRender="expandedRowRender"
@change="onChange"
:rowSelection="selectedRows ? {selectedRowKeys, onSelect, onSelectAll} : undefined"
:rowSelection="selectedRows ? {selectedRowKeys: selectedRowKeys, onChange: updateSelect} : 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,70 +64,22 @@ export default {
}
},
methods: {
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)
updateSelect (selectedRowKeys, selectedRows) {
this.$emit('update:selectedRows', selectedRows)
this.$emit('selectedRowChange', selectedRowKeys, selectedRows)
},
initTotalList (columns) {
return columns.filter(item => item.needTotal)
const totalList = columns.filter(item => item.needTotal)
.map(item => {
return {
...item,
total: 0
}
})
return totalList
},
onClear() {
this.$emit('update:selectedRows', [])
this.$emit('selectedRowChange', [], [])
this.updateSelect([], [])
this.$emit('clear')
},
onChange(pagination, filters, sorter, {currentDataSource}) {
@ -143,14 +95,7 @@ export default {
return {
...item,
total: selectedRows.reduce((sum, val) => {
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
return sum + val[item.dataIndex]
}, 0)
}
})
@ -158,8 +103,10 @@ export default {
},
computed: {
selectedRowKeys() {
return this.selectedRows.map(record => this.getKey(record))
},
return this.selectedRows.map(record => {
return (typeof this.rowKey === 'function') ? this.rowKey(record) : record[this.rowKey]
})
}
}
}
</script>

View File

@ -49,16 +49,18 @@
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)
this.$emit('update:visibleColumns', [...this.columns])
for (let col of this.columns) {
if (col.visible === undefined) {
this.$set(col, 'visible', true)
}
if (!col.visible) {
this.checkedCounts -= 1
}
}
},
methods: {
onCheckChange(e, col) {
@ -124,16 +126,6 @@
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
}
}
}
}
}

View File

@ -32,7 +32,7 @@
</div>
</div>
<a-table
v-bind="{...$props, columns: visibleColumns, title: undefined, loading: false}"
v-bind="{...$options.propsData, columns: visibleColumns, title: undefined, loading: false}"
:size="sSize"
@expandedRowsChange="onExpandedRowsChange"
@change="onChange"
@ -62,7 +62,7 @@
props: {
tableLayout: String,
bordered: Boolean,
childrenColumnName: {type: String, default: 'children'},
childrenColumnName: Array[String],
columns: Array,
components: Object,
dataSource: Array,
@ -76,12 +76,12 @@
indentSize: Number,
loading: Boolean,
locale: Object,
pagination: [Object, Boolean],
pagination: Object,
rowClassName: Function,
rowKey: [String, Function],
rowSelection: Object,
scroll: Object,
showHeader: {type: Boolean, default: true},
showHeader: Boolean,
size: String,
title: String,
customHeaderRow: Function,

View File

@ -7,10 +7,7 @@
{{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-switch @change="onSwitchChange(col)" class="switch" v-model="col.search.value" size="small" checked-children="是" un-checked-children="否" />
<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}]">
@ -73,14 +70,12 @@
props: ['columns', 'formatConditions'],
inject: ['table'],
created() {
this.formatColumns(this.columns)
this.columns.forEach(item => {
this.$set(item, 'search', {...item.search, visible: false, value: undefined, format: this.getFormat(item)})
})
console.log(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)
@ -223,11 +218,6 @@
return true
}
return false
},
formatColumns(columns) {
columns.forEach(item => {
this.$set(item, 'search', {...item.search, visible: false, value: undefined, format: this.getFormat(item)})
})
}
}
}

View File

@ -35,7 +35,7 @@ const Item = {
return h(
'li',
{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]
)
}
}

View File

@ -22,11 +22,10 @@ const ANTD = {
'component-background': '#fff',
'heading-color': 'rgba(0, 0, 0, 0.85)',
'text-color': 'rgba(0, 0, 0, 0.65)',
'text-color-inverse': '#fefefe',
'text-color-inverse': '#fff',
'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',
@ -35,9 +34,8 @@ 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': '#fefefe',
'btn-primary-color': '#fff',
},
light: {
'layout-body-background': '#f0f2f5',
@ -45,11 +43,10 @@ const ANTD = {
'component-background': '#fff',
'heading-color': 'rgba(0, 0, 0, 0.85)',
'text-color': 'rgba(0, 0, 0, 0.65)',
'text-color-inverse': '#fefefe',
'text-color-inverse': '#fff',
'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',
@ -58,9 +55,8 @@ 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': '#fefefe',
'btn-primary-color': '#fff',
},
night: {
'layout-body-background': '#000',
@ -68,11 +64,10 @@ const ANTD = {
'component-background': '#141414',
'heading-color': 'rgba(255, 255, 255, 0.85)',
'text-color': 'rgba(255, 255, 255, 0.85)',
'text-color-inverse': '#fefefe',
'text-color-inverse': '#141414',
'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',
@ -81,9 +76,8 @@ 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': '#fefefe',
'btn-primary-color': '#141414',
}
}
}

View File

@ -15,7 +15,6 @@ module.exports = {
pageWidth: 'fixed', //内容区域宽度fixed:固定宽度fluid:流式宽度
weekMode: false, //色弱模式true:开启false:不开启
multiPage: false, //多页签模式true:开启false:不开启
cachePage: true, //是否缓存页面数据仅多页签模式下生效true 缓存, false 不缓存
hideSetting: false, //隐藏设置抽屉true:隐藏false:不隐藏
systemName: 'Vue Antd Admin', //系统名称
copyright: '2018 ICZER 工作室出品', //copyright

View File

@ -60,10 +60,10 @@ export default {
this.updatePageHeight(0)
},
computed: {
...mapState('setting', ['layout', 'multiPage', 'pageMinHeight', 'pageWidth', 'customTitles']),
...mapState('setting', ['layout', 'multiPage', 'pageMinHeight', 'pageWidth']),
pageTitle() {
let pageTitle = this.page && this.page.title
return this.customTitle || (pageTitle && this.$t(pageTitle)) || this.title || this.routeName
return pageTitle === undefined ? (this.title || this.routeName) : this.$t(pageTitle)
},
routeName() {
const route = this.$route
@ -90,16 +90,14 @@ export default {
...mapMutations('setting', ['correctPageMinHeight']),
getRouteBreadcrumb() {
let routes = this.$route.matched
const path = this.$route.path
let breadcrumb = []
routes.filter(item => path.includes(item.path) || item.regex.test(path))
.forEach(route => {
routes.forEach(route => {
const path = route.path.length === 0 ? '/home' : route.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
if (pageTitle) {
breadcrumb[breadcrumb.length - 1] = pageTitle
}
return breadcrumb
},

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

@ -26,9 +26,6 @@ export default {
.copyright{
color: @text-color-second;
font-size: 14px;
i {
margin: 0 4px;
}
}
.links{
margin-bottom: 8px;

View File

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

View File

@ -14,11 +14,11 @@
: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"/>
<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"/>
</div>
</a-tab-pane>
</a-tabs>
@ -63,7 +63,7 @@
this.affixed = this.fixedTabs
},
computed: {
...mapState('setting', ['layout', 'pageWidth', 'fixedHeader', 'fixedTabs', 'customTitles']),
...mapState('setting', ['layout', 'pageWidth', 'fixedHeader', 'fixedTabs']),
lockTitle() {
return this.$t(this.fixedTabs ? 'unlock' : 'lock')
}
@ -89,14 +89,13 @@
this.$emit('close', key)
},
onRefresh(page) {
this.$emit('refresh', page.path, page)
this.$emit('refresh', page.fullPath, 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))
return page.title || this.$t(getI18nKey(page.keyPath))
}
}
}
@ -184,4 +183,4 @@
.virtual-tabs{
height: 48px;
}
</style>
</style>

View File

@ -12,10 +12,10 @@
/>
<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">
<a-keep-alive :exclude-keys="excludeKeys" v-if="multiPage && cachePage" v-model="clearCaches">
<a-keep-alive v-if="multiPage" v-model="clearCaches">
<router-view v-if="!refreshing" ref="tabContent" :key="$route.fullPath" />
</a-keep-alive>
<router-view ref="tabContent" v-else-if="!refreshing" />
<router-view v-else />
</page-toggle-transition>
</div>
</admin-layout>
@ -40,12 +40,11 @@ export default {
pageList: [],
activePage: '',
menuVisible: false,
refreshing: false,
excludeKeys: []
refreshing: false
}
},
computed: {
...mapState('setting', ['multiPage', 'cachePage', 'animate', 'layout', 'pageWidth']),
...mapState('setting', ['multiPage', 'animate', 'layout', 'pageWidth']),
menuItemList() {
return [
{ key: '1', icon: 'vertical-right', text: this.$t('closeLeft') },
@ -59,10 +58,9 @@ export default {
}
},
created () {
this.loadCacheConfig(this.$router?.options?.routes)
this.loadCachedTabs()
const route = this.$route
if (this.pageList.findIndex(item => item.path === route.fullPath) === -1) {
if (this.pageList.findIndex(item => item.fullPath === route.fullPath) === -1) {
this.pageList.push(this.createPage(route))
}
this.activePage = route.fullPath
@ -81,18 +79,11 @@ export default {
this.correctPageMinHeight(this.tabsOffset)
},
watch: {
'$router.options.routes': function (val) {
this.excludeKeys = []
this.loadCacheConfig(val)
},
'$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 (page) {
page.fullPath = newRoute.fullPath
} else if (!page) {
} else if (this.pageList.findIndex(item => item.fullPath === newRoute.fullPath) === -1) {
this.pageList.push(this.createPage(newRoute))
}
if (this.multiPage) {
@ -116,26 +107,25 @@ export default {
methods: {
changePage (key) {
this.activePage = key
const page = this.pageList.find(item => item.path === key)
this.$router.push(page.fullPath)
this.$router.push(key)
},
remove (key, next) {
if (this.pageList.length === 1) {
return this.$message.warning(this.$t('warn'))
}
//
let index = this.pageList.findIndex(item => item.path === key)
let index = this.pageList.findIndex(item => item.fullPath === 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].path
this.activePage = this.pageList[index].fullPath
this.$router.push(this.activePage)
}
},
refresh (key, page) {
page = page || this.pageList.find(item => item.path === key)
page = page || this.pageList.find(item => item.fullPath === key)
page.loading = true
this.clearCache(page)
if (key === this.activePage) {
@ -163,7 +153,7 @@ export default {
},
closeOthers (pageKey) {
//
const clearPages = this.pageList.filter(item => item.path !== pageKey && !item.unclose)
const clearPages = this.pageList.filter(item => item.fullPath !== pageKey && !item.unclose)
this.clearCaches = clearPages.map(item => item.cachedKey)
this.pageList = this.pageList.filter(item => !clearPages.includes(item))
//
@ -173,25 +163,25 @@ export default {
}
},
closeLeft (pageKey) {
const index = this.pageList.findIndex(item => item.path === pageKey)
const index = this.pageList.findIndex(item => item.fullPath === 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.path === this.activePage)) {
if (!this.pageList.find(item => item.fullPath === this.activePage)) {
this.activePage = pageKey
this.$router.push(this.activePage)
}
},
closeRight (pageKey) {
//
const index = this.pageList.findIndex(item => item.path === pageKey)
const index = this.pageList.findIndex(item => item.fullPath === 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.path === this.activePage)) {
if (!this.pageList.find(item => item.fullPath === this.activePage)) {
this.activePage = pageKey
this.$router.push(this.activePage)
}
@ -238,8 +228,7 @@ export default {
closePageListener(event) {
const {closeRoute, nextRoute} = event.detail
const closePath = typeof closeRoute === 'string' ? closeRoute : closeRoute.path
const path = closePath && closePath.split('?')[0]
this.remove(path, nextRoute)
this.remove(closePath, nextRoute)
},
/**
* 页面刷新事件监听
@ -247,8 +236,7 @@ export default {
*/
refreshPageListener(event) {
const {pageKey} = event.detail
const path = pageKey && pageKey.split('?')[0]
this.refresh(path)
this.refresh(pageKey)
},
/**
* 页面 unload 事件监听器添加页签到 session 缓存用于刷新时保留页签
@ -261,7 +249,6 @@ 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),
}
@ -271,7 +258,7 @@ export default {
* @param route 页面对应的路由
*/
setCachedKey(route) {
const page = this.pageList.find(item => item.path === route.fullPath)
const page = this.pageList.find(item => item.fullPath === route.fullPath)
page.unclose = route.meta && route.meta.page && (route.meta.page.closable === false)
if (!page._init_) {
const vnode = this.$refs.tabContent.$vnode
@ -297,17 +284,6 @@ 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'])
}
}

View File

@ -10,7 +10,6 @@ import 'animate.css/source/animate.css'
import Plugins from '@/plugins'
import {initI18n} from '@/utils/i18n'
import bootstrap from '@/bootstrap'
import 'moment/locale/zh-cn'
const router = initRouter(store.state.setting.asyncRoutes)
const i18n = initI18n('CN', 'US')

View File

@ -48,59 +48,4 @@ Mock.mock(RegExp(`${process.env.VUE_APP_API_BASE_URL}/goods` + '.*'),'get', ({ur
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
})

View File

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

View File

@ -1,52 +0,0 @@
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
}
}
})

View File

@ -8,32 +8,21 @@ const user = Mock.mock({
position: '@POSITION'
})
Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/login`, 'post', ({body}) => {
let result = {data: {}}
let result = {}
const {name, password} = JSON.parse(body)
let success = false
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']}]
if (name !== 'admin' || password !== '888888') {
result.code = -1
result.message = '账户名或密码错误admin/888888'
} else {
success = false
}
if (success) {
result.code = 0
result.message = Mock.mock('@TIMEFIX').CN + ',欢迎回来'
result.data = {}
result.data.user = user
result.data.token = 'Authorization:' + Math.random()
result.data.expireAt = new Date(new Date().getTime() + 30 * 60 * 1000)
} else {
result.code = -1
result.message = '账户名或密码错误admin/888888 or test/888888'
result.data.permissions = [{id: 'queryForm', operation: ['add', 'edit']}]
result.data.roles = [{id: 'admin', operation: ['add', 'edit', 'delete']}]
}
return result
})

View File

@ -1,16 +0,0 @@
<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

@ -90,19 +90,24 @@
searchAble: true,
dataIndex: 'send',
dataType: 'boolean',
scopedSlots: {customRender: 'send'},
search: {
switchOptions: {
checkedText: '开',
uncheckedText: '关'
}
}
scopedSlots: {customRender: 'send'}
},
{
title: '发货时间',
dataIndex: 'sendTime',
dataType: 'datetime'
},
{
title: '下单日期',
searchAble: true,
dataIndex: 'orderDate',
dataType: 'date'
},
{
title: '审核时间',
dataIndex: 'auditTime',
dataType: 'time',
}
},
],
dataSource: [],
conditions: {}
@ -110,7 +115,6 @@
},
created() {
this.getGoodList()
this.getColumns()
},
methods: {
getGoodList() {
@ -125,12 +129,8 @@
this.loading = false
})
},
getColumns() {
ds.goodsColumns().then(res => {
this.columns = res.data
})
},
onSearch(conditions, searchOptions) {
console.log(conditions)
console.log(searchOptions)
this.page = 1
this.conditions = conditions

View File

@ -79,7 +79,7 @@
</a-form>
</div>
<div>
<a-space class="operator">
<div class="operator">
<a-button @click="addNew" type="primary">新建</a-button>
<a-button >批量操作</a-button>
<a-dropdown>
@ -91,14 +91,13 @@
更多操作 <a-icon type="down" />
</a-button>
</a-dropdown>
</a-space>
</div>
<standard-table
:columns="columns"
:dataSource="dataSource"
:selectedRows.sync="selectedRows"
@clear="onClear"
@change="onChange"
:pagination="{...pagination, onChange: onPageChange}"
@selectedRowChange="onSelectChange"
>
<div slot="description" slot-scope="{text}">
@ -117,7 +116,6 @@
<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" />
@ -129,7 +127,6 @@
<script>
import StandardTable from '@/components/table/StandardTable'
import {request} from '@/utils/request'
const columns = [
{
title: '规则编号',
@ -163,6 +160,19 @@ 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},
@ -170,37 +180,14 @@ export default {
return {
advanced: true,
columns: columns,
dataSource: [],
selectedRows: [],
pagination: {
current: 1,
pageSize: 10,
total: 0
}
dataSource: dataSource,
selectedRows: []
}
},
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, roles)
return checkFromRoles(check, role)
} else {
return checkFromPermission(check, permission) || checkFromRoles(check, roles)
return checkFromPermission(check, permission) || checkFromRoles(check, role)
}
}

View File

@ -6,30 +6,9 @@ const TabsPagePlugin = {
const event = new CustomEvent('page:close', {detail:{closeRoute, nextRoute}})
window.dispatchEvent(event)
},
$refreshPage(route) {
const path = typeof route === 'object' ? route.path : route
const event = new CustomEvent('page:refresh', {detail:{pageKey: path}})
$refreshPage(pageKey) {
const event = new CustomEvent('page:refresh', {detail:{pageKey}})
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
}
}
})

View File

@ -56,9 +56,6 @@ const options = {
name: '表单页',
meta: {
icon: 'form',
page: {
cacheAble: false
}
},
component: PageView,
children: [
@ -95,15 +92,6 @@ 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: '标准列表',
@ -239,28 +227,6 @@ 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

@ -6,5 +6,4 @@ module.exports = {
LOGIN: `${BASE_URL}/login`,
ROUTES: `${BASE_URL}/routes`,
GOODS: `${BASE_URL}/goods`,
GOODS_COLUMNS: `${BASE_URL}/columns`,
}

View File

@ -1,12 +1,8 @@
import {GOODS, GOODS_COLUMNS} from './api'
import {GOODS} 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}
export default {goodsList}

View File

@ -3,11 +3,8 @@ import {ADMIN} from '@/config/default'
import {formatFullPath} from '@/utils/i18n'
import {filterMenu} from '@/utils/authority-utils'
import {getLocalSetting} from '@/utils/themeUtil'
import deepClone from 'lodash.clonedeep'
const localSetting = getLocalSetting(true)
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY)
const customTitles = (customTitlesStr && JSON.parse(customTitlesStr)) || []
export default {
namespaced: true,
@ -18,7 +15,6 @@ export default {
pageMinHeight: 0,
menuData: [],
activatedFirst: undefined,
customTitles,
...config,
...localSetting
},
@ -26,12 +22,12 @@ export default {
menuData(state, getters, rootState) {
if (state.filterMenu) {
const {permissions, roles} = rootState.account
return filterMenu(deepClone(state.menuData), permissions, roles)
filterMenu(state.menuData, permissions, roles)
}
return state.menuData
},
firstMenu(state, getters) {
const {menuData} = getters
firstMenu(state) {
const {menuData} = state
if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData)
}
@ -43,11 +39,11 @@ export default {
},
subMenu(state) {
const {menuData, activatedFirst} = state
if (menuData.length > 0 && !menuData[0].fullPath) {
if (!menuData[0].fullPath) {
formatFullPath(menuData)
}
const current = menuData.find(menu => menu.fullPath === activatedFirst)
return current && current.children || []
return current && current.children ? current.children : []
}
},
mutations: {
@ -98,17 +94,6 @@ export default {
},
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))
}
}
}
}

View File

@ -8,12 +8,10 @@ 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 === '*' || hasAnyItem(required, permissions, (r, t) => !!(r === t || r === t.id))
return required === '*' || (permissions && permissions.findIndex(item => item === required || item.id === required) !== -1)
}
/**
@ -26,23 +24,25 @@ function hasRole(authority, roles) {
if (typeof authority === 'object') {
required = authority.role
}
return authority === '*' || hasAnyItem(required, roles, (r, t) => !!(r === t || r === t.id))
return authority === '*' || hasAnyRole(required, roles)
}
/**
* 判断目标数组是否有所需元素
* @param {String | String[]}required 所需元素数组或单个元素
* @param {String[]|Object[]} source 目标数组
* @param {Function} filter 匹配条件
* (r: String, s: String|Object) => boolean
* 判断是否有需要的任意一个角色
* @param required {String | Array[String]} 需要的角色可以是单个角色或者一个角色数组
* @param roles 拥有的角色
* @returns {boolean}
*/
function hasAnyItem(required, source, filter) {
function hasAnyRole(required, roles) {
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)))
}
/**
@ -69,16 +69,15 @@ function hasAuthority(route, permissions, roles) {
* @param roles
*/
function filterMenu(menuData, permissions, roles) {
return menuData.filter(menu => {
menuData.forEach(menu => {
if (menu.meta && menu.meta.invisible === undefined) {
if (!hasAuthority(menu, permissions, roles)) {
return false
menu.meta.invisible = true
}
if (menu.children && menu.children.length > 0) {
filterMenu(menu.children, permissions, roles)
}
}
if (menu.children && menu.children.length > 0) {
menu.children = filterMenu(menu.children, permissions, roles)
}
return true
})
}

View File

@ -9,8 +9,8 @@ const resp401 = {
*/
onFulfilled(response, options) {
const {message} = options
if (response.code === 401) {
message.error('无此权限')
if (response.status === 401) {
message.error('无此接口权限')
}
return response
},
@ -22,10 +22,7 @@ const resp401 = {
*/
onRejected(error, options) {
const {message} = options
const {response} = error
if (response.status === 401) {
message.error('无此权限')
}
message.error(error.message)
return Promise.reject(error)
}
}
@ -33,18 +30,10 @@ const resp401 = {
const resp403 = {
onFulfilled(response, options) {
const {message} = options
if (response.code === 403) {
message.error('请求被拒绝')
if (response.status === 403) {
message.error(`请求被拒绝`)
}
return response
},
onRejected(error, options) {
const {message} = options
const {response} = error
if (response.status === 403) {
message.error('请求被拒绝')
}
return Promise.reject(error)
}
}

View File

@ -30,14 +30,14 @@ const METHOD = {
* @param params 请求参数
* @returns {Promise<AxiosResponse<T>>}
*/
async function request(url, method, params, config) {
async function request(url, method, params) {
switch (method) {
case METHOD.GET:
return axios.get(url, {params, ...config})
return axios.get(url, {params})
case METHOD.POST:
return axios.post(url, params, config)
return axios.post(url, params)
default:
return axios.get(url, {params, ...config})
return axios.get(url, {params})
}
}

View File

@ -32,9 +32,9 @@ function parseRoutes(routesConfig, routerMap) {
routesConfig.forEach(item => {
// 获取注册在 routerMap 中的 router初始化 routeCfg
let router = undefined, routeCfg = {}
if (typeof item === 'string') {
if (typeof item === 'string' && routerMap[item]) {
router = routerMap[item]
routeCfg = {path: (router && router.path) || item, router: item}
routeCfg = {path: router.path || item, router: item}
} else if (typeof item === 'object') {
router = routerMap[item.router]
routeCfg = item
@ -44,39 +44,17 @@ 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: {...meta, authority: meta.authority || '*'}
}
if (router.beforeEnter) {
route.beforeEnter = router.beforeEnter
meta: {
authority: routeCfg.authority || router.authority || '*',
icon: routeCfg.icon || router.icon,
page: routeCfg.page || router.page,
link: routeCfg.link || router.link
}
}
if (routeCfg.invisible || router.invisible) {
route.meta.invisible = true
@ -215,7 +193,7 @@ function formatAuthority(routes, pAuthorities = []) {
let authority = {}
if (!meta.authority) {
authority = defaultAuthority
}else if (typeof meta.authority === 'string' || Array.isArray(meta.authority)) {
}else if (typeof meta.authority === 'string') {
authority.permission = meta.authority
} else if (typeof meta.authority === 'object') {
authority = meta.authority

View File

@ -110,7 +110,7 @@ module.exports = {
}
}
},
publicPath: process.env.VUE_APP_PUBLIC_PATH,
publicPath: isProd ? '/vue-antd-admin/' : '/',
outputDir: 'dist',
assetsDir: 'static',
productionSourceMap: false

View File

@ -1146,14 +1146,6 @@
resolved "https://registry.npm.taobao.org/@nodelib/fs.stat/download/@nodelib/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
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":
version "0.14.0"
resolved "https://registry.npm.taobao.org/@sindresorhus/is/download/@sindresorhus/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@ -1967,14 +1959,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
ant-design-vue@1.7.2:
version "1.7.2"
resolved "https://registry.npm.taobao.org/ant-design-vue/download/ant-design-vue-1.7.2.tgz#aac7ff802205711631c8698e2a0c7b4e61dfd73e"
integrity sha1-qsf/gCIFcRYxyGmOKgx7TmHf1z4=
ant-design-vue@^1.6.2:
version "1.6.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"
integrity sha1-mDY0yqyc2soLOglbVAEFtPdtop8=
dependencies:
"@ant-design/icons" "^2.1.1"
"@ant-design/icons-vue" "^2.0.0"
"@simonwep/pickr" "~1.7.0"
add-dom-event-listener "^1.0.2"
array-tree-filter "^2.1.0"
async-validator "^3.0.3"
@ -2735,10 +2726,15 @@ 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, 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==
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=
case-sensitive-paths-webpack-plugin@^2.3.0:
version "2.3.0"
@ -6951,11 +6947,6 @@ nanomatch@^1.2.9:
snapdragon "^0.8.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:
version "1.4.0"
resolved "https://registry.npm.taobao.org/natural-compare/download/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@ -10418,10 +10409,10 @@ webpack-sources@*, webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sourc
source-list-map "^2.0.0"
source-map "~0.6.1"
webpack-theme-color-replacer@1.3.18:
version "1.3.18"
resolved "https://registry.npmjs.org/webpack-theme-color-replacer/-/webpack-theme-color-replacer-1.3.18.tgz#98b70eab698e40b06ea3c56a3db8590f7ccef847"
integrity sha512-z7qM3opvuSjAyJd0eLMOpZhH56r+fFctczWG6xnhUSeRsvbCg/EnFdsYoGL3xYJZNANvwLlggpJxnAcuFV5a6Q==
webpack-theme-color-replacer@^1.3.12:
version "1.3.12"
resolved "https://registry.npm.taobao.org/webpack-theme-color-replacer/download/webpack-theme-color-replacer-1.3.12.tgz#0593a3149310c0e5b6b85afeccd61925b1b8e86b"
integrity sha1-BZOjFJMQwOW2uFr+zNYZJbG46Gs=
dependencies:
webpack-sources "*"