分离路由和菜单

增加动态路由功能
增加菜单管理功能
<svg-icon>更名为<v-icon>,功能修改
修复index.html中js执行顺序的问题
文件移动
master
toesbieya 6 years ago
parent 6795de7969
commit 1c8164b7e3
  1. 5
      vue/package-lock.json
  2. 1
      vue/package.json
  3. 8
      vue/public/index.html
  4. 2
      vue/src/api/account/index.js
  5. 2
      vue/src/api/doc/history.js
  6. 2
      vue/src/api/doc/purchase/inbound.js
  7. 2
      vue/src/api/doc/purchase/order.js
  8. 2
      vue/src/api/doc/sell/order.js
  9. 2
      vue/src/api/doc/sell/outbound.js
  10. 2
      vue/src/api/message/manage.js
  11. 2
      vue/src/api/message/user.js
  12. 2
      vue/src/api/record/index.js
  13. 0
      vue/src/api/request.js
  14. 2
      vue/src/api/statistic/index.js
  15. 2
      vue/src/api/stock/current.js
  16. 2
      vue/src/api/system/category.js
  17. 2
      vue/src/api/system/customer.js
  18. 2
      vue/src/api/system/department.js
  19. 16
      vue/src/api/system/resource.js
  20. 12
      vue/src/api/system/role.js
  21. 2
      vue/src/api/system/supplier.js
  22. 2
      vue/src/api/system/user.js
  23. 4
      vue/src/asset/icon/index.js
  24. 12
      vue/src/asset/style/element-ui.scss
  25. 10
      vue/src/asset/style/index.scss
  26. 0
      vue/src/component/AbstractForm/item.vue
  27. 0
      vue/src/component/ContextMenu/item.vue
  28. 0
      vue/src/component/FormDialog/LoadingMask.vue
  29. 2
      vue/src/component/FormDialog/index.vue
  30. 6
      vue/src/component/RegionSelector/Tree/TreeDialog.vue
  31. 0
      vue/src/component/SearchForm/item.vue
  32. 50
      vue/src/component/SvgIcon/index.vue
  33. 34
      vue/src/component/VIcon/index.vue
  34. 46
      vue/src/component/editor/JsonEditor.vue
  35. 2
      vue/src/component/editor/RichTextEditor.vue
  36. 9
      vue/src/config/index.js
  37. 4
      vue/src/globalMethod/Guide/style.scss
  38. 0
      vue/src/globalMethod/Verify/image/btn_move.png
  39. 0
      vue/src/globalMethod/Verify/image/btn_stop.png
  40. 0
      vue/src/globalMethod/Verify/image/icon_close.png
  41. 0
      vue/src/globalMethod/Verify/image/icon_info.png
  42. 0
      vue/src/globalMethod/Verify/image/icon_refresh_bottom.png
  43. 0
      vue/src/globalMethod/Verify/image/icon_refresh_top.png
  44. 0
      vue/src/globalMethod/Verify/image/loading.png
  45. 0
      vue/src/globalMethod/Verify/image/track.png
  46. 0
      vue/src/globalMethod/Verify/index.js
  47. 0
      vue/src/globalMethod/Verify/main.vue
  48. 0
      vue/src/globalMethod/Verify/style.scss
  49. 4
      vue/src/globalMethod/index.js
  50. 8
      vue/src/layout/component/Sidebar/component/SidebarItemContent.vue
  51. 8
      vue/src/layout/component/Sidebar/index.vue
  52. 2
      vue/src/layout/component/Sidebar/style.scss
  53. 12
      vue/src/layout/component/TagsView/index.vue
  54. 2
      vue/src/mixin/docDetailMixin.js
  55. 34
      vue/src/mixin/treePageMixin.js
  56. 6
      vue/src/router/define.js
  57. 85
      vue/src/router/index.js
  58. 15
      vue/src/router/module/app.js
  59. 34
      vue/src/router/module/example.js
  60. 10
      vue/src/router/module/message.js
  61. 18
      vue/src/router/module/purchase.js
  62. 18
      vue/src/router/module/sell.js
  63. 8
      vue/src/router/module/stock.js
  64. 24
      vue/src/router/module/system.js
  65. 165
      vue/src/router/util.js
  66. 258
      vue/src/store/module/resource.js
  67. 22
      vue/src/store/module/user.js
  68. 6
      vue/src/util/auth.js
  69. 2
      vue/src/util/excel/common.js
  70. 2
      vue/src/util/file.js
  71. 18
      vue/src/util/index.js
  72. 35
      vue/src/util/tree.js
  73. 12
      vue/src/view/app/login/LoginForm.vue
  74. 6
      vue/src/view/app/login/RegisterForm.vue
  75. 2
      vue/src/view/app/login/SetAnimation.vue
  76. 2
      vue/src/view/example/component/uploadPage.vue
  77. 2
      vue/src/view/example/developingTest/component/ApiSpeedTest.vue
  78. 2
      vue/src/view/example/icon/indexPage.vue
  79. 4
      vue/src/view/index/component/PanelGroup/PanelGroupItem.vue
  80. 30
      vue/src/view/index/component/PanelGroup/index.vue
  81. 17
      vue/src/view/message/manage/EditDialog.vue
  82. 2
      vue/src/view/message/manage/indexPage.vue
  83. 8
      vue/src/view/purchase/inbound/indexPage.vue
  84. 8
      vue/src/view/purchase/order/indexPage.vue
  85. 2
      vue/src/view/sell/order/indexPage.vue
  86. 2
      vue/src/view/sell/outbound/indexPage.vue
  87. 2
      vue/src/view/stock/current/indexPage.vue
  88. 24
      vue/src/view/system/category/component/Form.vue
  89. 2
      vue/src/view/system/category/component/List.vue
  90. 2
      vue/src/view/system/customer/indexPage.vue
  91. 0
      vue/src/view/system/department/component/ZoomControl.vue
  92. 5
      vue/src/view/system/department/indexPage.vue
  93. 13
      vue/src/view/system/menu/common.js
  94. 358
      vue/src/view/system/menu/indexPage.vue
  95. 39
      vue/src/view/system/role/EditDialog.vue
  96. 13
      vue/src/view/system/role/indexPage.vue
  97. 2
      vue/src/view/system/supplier/indexPage.vue
  98. 4
      vue/src/view/system/user/component/RoleSelector.vue
  99. 2
      vue/src/view/system/user/indexPage.vue

@ -5680,6 +5680,11 @@
"q": "^1.1.2"
}
},
"codemirror": {
"version": "5.56.0",
"resolved": "https://registry.npm.taobao.org/codemirror/download/codemirror-5.56.0.tgz",
"integrity": "sha1-Z1ZA/MeAEFzSLT+qc4tdfqZCb2E="
},
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",

@ -9,6 +9,7 @@
},
"dependencies": {
"axios": "^0.19.2",
"codemirror": "^5.56.0",
"core-js": "^3.6.5",
"decimal.js": "^10.2.0",
"element-ui": "^2.13.2",

@ -39,10 +39,10 @@
<!--图片预览-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lightgallery.js@1.2.0/dist/css/lightgallery.min.css">
<script async src="https://cdn.jsdelivr.net/npm/lightgallery.js@1.2.0/dist/js/lightgallery.min.js"></script>
<script async src="<%= BASE_URL %>static/lightgallery/lg-pager.min.js"></script>
<script async src="<%= BASE_URL %>static/lightgallery/lg-thumbnail.min.js"></script>
<script async src="<%= BASE_URL %>static/lightgallery/lg-zoom.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/lightgallery.js@1.2.0/dist/js/lightgallery.min.js"></script>
<script defer src="<%= BASE_URL %>static/lightgallery/lg-pager.min.js"></script>
<script defer src="<%= BASE_URL %>static/lightgallery/lg-thumbnail.min.js"></script>
<script defer src="<%= BASE_URL %>static/lightgallery/lg-zoom.min.js"></script>
<!--excel-->
<script async src="<%= BASE_URL %>static/js/xlsx.full.min.js"></script>

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import {isEmpty} from "@/util"
export const baseUrl = '/account'

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/history`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from '../baseUrl'
export const baseUrl = `${BASE}/purchase/inbound`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from '../baseUrl'
export const baseUrl = `${BASE}/purchase/order`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from '../baseUrl'
export const baseUrl = `${BASE}/sell/order`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from '../baseUrl'
export const baseUrl = `${BASE}/sell/outbound`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/manage`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/user`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
export const baseUrl = '/record'

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
export const baseUrl = `/statistic`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
export const baseUrl = '/stock/current'

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/category`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/customer`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/department`

@ -1,8 +1,20 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/resource`
export function getAllResources() {
export function getAll() {
return request.get(`${baseUrl}/getAll`).then(({data}) => data.data)
}
export function add(data) {
return request.post(`${baseUrl}/add`, data).then(({data}) => data)
}
export function update(data) {
return request.post(`${baseUrl}/update`, data).then(({data}) => data)
}
export function del(id) {
return request.get(`${baseUrl}/del?id=${id}`).then(({data}) => data)
}

@ -1,24 +1,24 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/role`
export function searchRoles(data) {
export function search(data) {
return request.post(`${baseUrl}/search`, data).then(({data}) => data.data)
}
export function getRoles() {
export function get() {
return request.get(`${baseUrl}/get`).then(({data}) => data.data)
}
export function addRole(data) {
export function add(data) {
return request.post(`${baseUrl}/add`, data).then(({data}) => data)
}
export function updateRole(data) {
export function update(data) {
return request.post(`${baseUrl}/update`, data).then(({data}) => data)
}
export function delRole(data) {
export function del(data) {
return request.post(`${baseUrl}/del`, data).then(({data}) => data)
}

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/supplier`

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import BASE from './baseUrl'
export const baseUrl = `${BASE}/user`

@ -1,7 +1,7 @@
import Vue from 'vue'
import SvgIcon from '@/component/SvgIcon'
import VIcon from '@/component/VIcon'
Vue.component('svg-icon', SvgIcon)
Vue.component('v-icon', VIcon)
const req = require.context('./svg', false, /\.svg$/)
const keys = req.keys()

@ -1,4 +1,16 @@
// cover some element-ui styles
.el-alert {
&--info.is-light {
color: #515a6e;
background-color: #f0faff;
border: 1px solid #abdcff;
.el-icon-info {
color: $--color-primary;
}
}
}
.el-button {
[class*="el-icon-"] + span {
margin-left: 7px;

@ -184,8 +184,14 @@ a:hover {
}
}
.el-form-item__content > .el-tree {
margin-top: 8px;
.el-form-item__content {
> .el-tree {
margin-top: 8px;
}
> .el-switch {
vertical-align: baseline;
}
}

@ -1,5 +1,5 @@
<script type="text/jsx">
import LoadingMask from '@/component/LoadingMask'
import LoadingMask from './LoadingMask'
export default {
name: "FormDialog",

@ -72,9 +72,9 @@ export default {
v-on:node-click={this.nodeClick}
>
{({data}) => (
<span className="el-tree-node__label">
{this.limit ? data.name + `(${data.value})` : data.name}
</span>
<span class="el-tree-node__label">
{this.limit ? data.name + `(${data.value})` : data.name}
</span>
)}
</el-tree>
</form-dialog>

@ -1,50 +0,0 @@
<script type="text/jsx">
import {isExternal} from '@/util/validate'
export default {
name: 'SvgIcon',
functional: true,
props: {
icon: String,
className: String
},
render(h, context) {
const {listeners} = context
const {icon, className} = context.props
const c = 'svg-icon ' + (className || '')
if (isExternal(icon)) {
const style = {
mask: `url(${this.icon}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.icon}) no-repeat 50% 50%`
}
return <div {...{on: listeners}} class={'svg-external-icon ' + c} style={style}/>
}
else {
const iconName = `#icon-${icon}`
return (
<svg {...{on: listeners}} class={c} aria-hidden="true">
<use href={iconName}/>
</svg>
)
}
}
}
</script>
<style>
.svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>

@ -0,0 +1,34 @@
<script type="text/jsx">
export default {
name: 'VIcon',
functional: true,
props: {icon: String},
render(h, context) {
const {data, props: {icon}} = context
const svgPrefix = 'svg-'
if (icon.startsWith(svgPrefix)) {
return (
<svg class="icon" aria-hidden="true" {...data}>
<use href={`#icon-${icon.replace(svgPrefix, '')}`}/>
</svg>
)
}
return <i class={icon} {...data}/>
}
}
</script>
<style>
.icon {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
</style>

@ -0,0 +1,46 @@
<template>
<textarea/>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/rubyblue.css'
import 'codemirror/mode/javascript/javascript'
import {isEmpty} from "@/util"
export default {
name: "JsonEditor",
props: ['value'],
watch: {
value(value) {
const editorValue = this.jsonEditor.getValue()
if (value !== editorValue) {
this.jsonEditor.setValue(this.value)
}
}
},
mounted() {
this.jsonEditor = CodeMirror.fromTextArea(
this.$el,
{
mode: 'application/json',
theme: 'rubyblue'
}
)
!isEmpty(this.value) && this.jsonEditor.setValue(this.value)
this.jsonEditor.on('change', cm => {
this.$emit('input', cm.getValue())
})
},
beforeDestroy() {
const element = this.jsonEditor.doc.cm.getWrapperElement()
element && element.remove && element.remove()
}
}
</script>

@ -20,7 +20,7 @@ const DEFAULT_OPTIONS = {
}
export default {
name: "TinymceEditor",
name: "RichTextEditor",
props: {
value: String,

@ -5,17 +5,21 @@ const contextPath = '/'
module.exports = {
isDev,
title: 'BiuBiuBiu~',
//项目的部署路径,始终以'/'开头,以'/'结束
contextPath,
//页面标题、登陆注册页的标题
title: 'BiuBiuBiu~',
//全局axios的baseUrl、devServer的路由前缀
apiPrefix: '/api',
//路由模式,['hash','history']
routerMode: 'history',
//是否使用由后端返回的数据作为路由
useBackendRoute: false,
//是否在开发时使用mock,为true时不会启动websocket和代理
useMock: false,
@ -25,6 +29,7 @@ module.exports = {
//侧边栏顶上的logo地址
sidebarLogoUrl: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png',
//是否使用错误日志捕捉,暂时没啥用
errorLog: true,
//storage的相关键名

@ -34,10 +34,6 @@
position: absolute;
right: 90px;
}
.next-btn {
}
}
}

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before

Width:  |  Height:  |  Size: 490 B

After

Width:  |  Height:  |  Size: 490 B

Before

Width:  |  Height:  |  Size: 961 B

After

Width:  |  Height:  |  Size: 961 B

@ -1,13 +1,13 @@
import BottomTip from './BottomTip'
import Guide from './Guide'
import ImageViewer from './ImageViewer'
import PuzzleVerify from './PuzzleVerify'
import Verify from './Verify'
import Signature from './SignautreBoard'
export default function (Vue) {
Vue.prototype.$bottomTip = BottomTip
Vue.prototype.$guide = Guide
Vue.prototype.$image = ImageViewer
Vue.prototype.$puzzleVerify = PuzzleVerify
Vue.prototype.$verify = Verify
Vue.prototype.$signature = Signature
}

@ -13,13 +13,7 @@ export default {
const {icon, title} = context.props
const vnodes = []
let iconComponent
if (icon) {
const isElIcon = icon.startsWith('el-icon-')
iconComponent = isElIcon ? <i class={`svg-icon ${icon}`}/> : <svg-icon icon={icon}/>
}
else iconComponent = <span class="svg-icon"/>
const iconComponent = icon ? <v-icon icon={icon}/> : <span class="icon"/>
vnodes.push(iconComponent)

@ -18,7 +18,7 @@ export default {
computed: {
...mapState('app', ['device']),
...mapState('resource', ['sidebarMenus']),
...mapState('resource', ['menus']),
...mapState('setting', ['showLogo', 'sidebarCollapse', 'sidebarUniqueOpen', 'sidebarShowParent', 'sidebarAutoHidden']),
@ -121,12 +121,12 @@ export default {
mode="vertical"
on-select={this.select}
>
{this.sidebarMenus.map(route => (
{this.menus.map(menu => (
<sidebar-item
key={route.path}
key={menu.path}
show-parent={this.sidebarShowParent}
collapse={this.sidebarCollapse}
item={route}
item={menu}
/>
))}
</el-menu>

@ -82,7 +82,7 @@ $iconSize: 17px;
height: 100%;
width: 100%;
.svg-icon {
.icon {
margin-right: 16px;
width: $iconSize;
height: $iconSize;

@ -30,7 +30,7 @@
<script>
import ScrollPane from './ScrollPane'
import ContextMenu from "@/component/ContextMenu"
import ContextMenuItem from "@/component/ContextMenu/ContextMenuItem"
import ContextMenuItem from "@/component/ContextMenu/item"
import shortcuts from './mixin/shortcuts'
import decideRouterTransition from './mixin/decideRouterTransition'
@ -56,8 +56,8 @@ export default {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
routes() {
return this.$store.state.resource.routes
menus() {
return this.$store.state.resource.menus
}
},
@ -79,9 +79,9 @@ export default {
return tag.meta && tag.meta.affix
},
filterAffixTags(routes) {
filterAffixTags(menus) {
const tags = []
routes.forEach(({name, fullPath, children, meta}) => {
menus.forEach(({name, fullPath, children, meta}) => {
if (meta && meta.title && meta.affix) {
tags.push({
//fullPath$route.fullPath
@ -99,7 +99,7 @@ export default {
return tags
},
initTags() {
this.affixTags = this.filterAffixTags(this.routes)
this.affixTags = this.filterAffixTags(this.menus)
for (const tag of this.affixTags) {
this.$store.commit('tagsView/addVisitedView', tag)
}

@ -1,5 +1,5 @@
import AbstractForm from '@/component/AbstractForm'
import AbstractFormItem from "@/component/AbstractForm/AbstractFormItem"
import AbstractFormItem from "@/component/AbstractForm/item"
import AbstractTable from "@/component/AbstractTable"
import DocDetailHeader from "@/component/biz/doc/DocDetailHeader"
import DocDetailFooter from "@/component/biz/doc/DocDetailFooter"

@ -0,0 +1,34 @@
/*
* 树形控件页通用混入
* */
const mixin = {
data() {
return {
curNode: null
}
},
watch: {
curNode(v) {
const ref = this.$refs.tree
!v && ref && ref.setCurrentKey()
}
},
methods: {
nodeClick(data, node, ref) {
if (this.curNode === node) {
this.$refs.tree.setCurrentKey()
this.curNode = null
}
else this.curNode = node
if (this.afterNodeClick) {
this.afterNodeClick(data, node, ref, !this.curNode)
}
}
}
}
export default mixin

@ -3,7 +3,7 @@ import Redirect from "@/view/app/redirect"
import Login from "@/view/app/login/index"
import Page404 from "@/view/app/404"
import Page403 from "@/view/app/403"
import {metaExtend} from "@/router/util"
import {parseRoutes, stringifyRoutes, metaExtend} from "./util"
const modulesFiles = require.context('./module', false, /\.js$/)
const dynamicRoutes = modulesFiles.keys().reduce((modules, modulePath) => {
@ -14,7 +14,9 @@ const dynamicRoutes = modulesFiles.keys().reduce((modules, modulePath) => {
metaExtend(dynamicRoutes)
export {dynamicRoutes}
export function getDynamicRoutes() {
return parseRoutes(stringifyRoutes(dynamicRoutes))
}
//系统自有路由
export default [

@ -13,33 +13,27 @@
* */
import Vue from 'vue'
import Router from 'vue-router'
import {pathToRegexp} from 'path-to-regexp'
import NProgress from 'nprogress'
import {contextPath, routerMode} from '@/config'
import defaultRoutes, {dynamicRoutes} from '@/router/define'
import {contextPath, routerMode, title} from '@/config'
import defaultRoutes from '@/router/define'
import {auth, needAuth} from "@/util/auth"
import {isUserExist} from "@/util/storage"
import {
setPageTitle,
specifyRouteTitle,
transformWhiteList,
needExtraRedirect,
initMenuAndResource,
iframeControl
} from './util'
import {stringifyRoutes, parseRoutes, generateRoutes} from './util'
import store from "@/store"
import {isEmpty} from "@/util"
Vue.use(Router)
NProgress.configure({showSpinner: false})
const endRoute = [{path: '*', redirect: '/404'}]
const whiteList = transformWhiteList(['/login', '/register', '/404', '/403'])
const router = new Router({
base: contextPath,
mode: routerMode,
scrollBehavior: () => ({y: 0}),
routes: defaultRoutes.concat(dynamicRoutes, endRoute)
routes: defaultRoutes
})
router.beforeEach(async (to, from, next) => {
@ -56,15 +50,23 @@ router.beforeEach(async (to, from, next) => {
setPageTitle(to)
//白名单内不需要进行权限控制
if (whiteList.some(reg => reg.test(to.path))) return next()
if (whiteList.some(reg => reg.test(to.path))) {
return next()
}
//未登录时返回登录页
if (!isUserExist()) return next({path: '/login', query: {redirect: to.fullPath}})
if (!isUserExist()) {
return next({path: '/login', query: {redirect: to.fullPath}})
}
await initMenuAndResource()
//初始化路由和菜单权限
if (!store.state.resource.init) {
await store.dispatch('resource/init', {...store.state.user, addRoutes: true})
return next({...to, replace: true})
}
//页面不需要鉴权或有访问权限时通过
if (!needAuth(to) || auth(to.path)) {
if (!needAuth(to) || auth(getAuthorizedPath(to))) {
iframeControl(to, from)
return next()
}
@ -75,4 +77,53 @@ router.beforeEach(async (to, from, next) => {
router.afterEach(() => NProgress.done())
//将给定的白名单url转换为正则
function transformWhiteList(list) {
return list.map(pathToRegexp)
}
//拼接页面标题
function setPageTitle(route) {
const pageTitle = route.meta.title
document.title = pageTitle ? `${pageTitle} - ${title}` : title
}
//确定路由的标题
function specifyRouteTitle(to, from) {
const {meta} = to
if (typeof meta.dynamicTitle === 'function') {
meta.title = meta.dynamicTitle(to, from)
}
}
//判断是否需要一次额外的redirect跳转
function needExtraRedirect(to, from) {
//若是共用组件的路由页面之间的跳转,借助redirect避免组件复用
const a = to.meta.commonModule, b = from.meta.commonModule
return !isEmpty(a) && a === b
}
//获取进行权限验证的路由地址,使用了动态路由的会用到
function getAuthorizedPath(route) {
const {params, path, matched} = route
const isDynamic = Object.values(params).some(v => path.includes(v))
return isDynamic ? matched[matched.length - 1].path : path
}
//判断是否需要打开iframe
function iframeControl(to, from) {
let iframe = to.meta.iframe
const operate = iframe ? 'open' : 'close'
if (to.path === `/redirect${from.path}`) {
iframe = from.meta.iframe
}
return store.dispatch(`iframe/${operate}`, iframe)
}
export default router
export function addDynamicRoutes(json) {
json = parseRoutes(stringifyRoutes(json))
const endRoute = {path: '*', redirect: '/404'}
router.addRoutes([...generateRoutes(json), endRoute])
}

@ -1,30 +1,27 @@
//路由表:系统页面
import Layout from '@/layout'
import {lazyLoadView} from "@/router/util"
const router = [
{
path: '/',
component: Layout,
redirect: '/index',
component: 'Layout',
children: [
{
path: 'index',
component: lazyLoadView('index/'),
component: 'index/',
name: 'index',
meta: {title: '首页', affix: true, icon: 'home', noAuth: true, sort: 0}
meta: {title: '首页', affix: true, icon: 'svg-home', noAuth: true, sort: 0}
},
{
path: 'user',
name: 'userCenter',
component: lazyLoadView('userCenter/'),
meta: {title: '个人中心', noCache: true, icon: 'user', noAuth: true, hidden: true},
component: 'userCenter/',
meta: {title: '个人中心', noCache: true, icon: 'svg-user', noAuth: true, hidden: true},
}
]
},
{
path: 'https://doc.toesbieya.cn',
meta: {title: '文档', icon: 'documentation', noAuth: true, sort: 1}
meta: {title: '文档', icon: 'svg-documentation', noAuth: true, sort: 1}
}
]

@ -1,20 +1,18 @@
/*路由表:演示用例*/
import Layout from '@/layout'
import {lazyLoadView} from "@/router/util"
const router = {
path: '/example',
component: Layout,
meta: {title: '演示用例', icon: 'show', noAuth: true, noCache: true},
component: 'Layout',
meta: {title: '演示用例', icon: 'svg-show', noAuth: true, noCache: true},
children: [
{
path: 'stylePage',
component: lazyLoadView('example/style'),
component: 'example/style',
meta: {title: '样 式'}
},
{
path: 'icon',
component: lazyLoadView('example/icon/'),
component: 'example/icon/',
meta: {title: '图 标'}
},
{
@ -23,66 +21,64 @@ const router = {
},
{
path: 'cool',
component: {render: h => h('router-view')},
meta: {title: '好玩的东东'},
children: [
{
path: 'fluid',
component: lazyLoadView('example/cool/fluid'),
component: 'example/cool/fluid',
meta: {title: '流体动画'}
},
{
path: 'l2d',
component: lazyLoadView('example/cool/l2d'),
component: 'example/cool/l2d',
meta: {title: '看板娘'}
},
]
},
{
path: 'components',
component: {render: h => h('router-view')},
meta: {title: '组件'},
children: [
{
path: 'uploadExample',
component: lazyLoadView('example/component/upload'),
component: 'example/component/upload',
meta: {title: '上传文件'}
},
{
path: 'skeletonExample',
component: lazyLoadView('example/component/skeleton'),
component: 'example/component/skeleton',
meta: {title: '骨架屏'}
},
{
path: 'rippleExample',
component: lazyLoadView('example/component/ripple'),
component: 'example/component/ripple',
meta: {title: '波纹'}
},
{
path: 'signatureExample',
component: lazyLoadView('example/component/signature'),
component: 'example/component/signature',
meta: {title: '手写签名'}
},
{
path: 'regionSelectorExample',
component: lazyLoadView('example/component/regionSelector'),
component: 'example/component/regionSelector',
meta: {title: '行政区划选择'}
},
{
path: 'treeSelectExample',
component: lazyLoadView('example/component/treeSelect'),
component: 'example/component/treeSelect',
meta: {title: '树选择'}
}
]
},
{
path: 'developingTest',
component: lazyLoadView('example/developingTest/'),
component: 'example/developingTest/',
meta: {title: '开发测试'}
},
{
path: 'detailPage1',
component: lazyLoadView('example/detail'),
component: 'example/detail',
meta: {
title: '详情页缓存1',
usePathKey: true,
@ -91,7 +87,7 @@ const router = {
},
{
path: 'detailPage2',
component: lazyLoadView('example/detail'),
component: 'example/detail',
meta: {
title: '详情页缓存2',
usePathKey: true,

@ -1,22 +1,20 @@
/*路由表:消息中心*/
import Layout from '@/layout'
import {lazyLoadView} from "@/router/util"
const router = {
path: '/message',
component: Layout,
meta: {title: '消息中心', icon: 'message', alwaysShow: true},
component: 'Layout',
meta: {title: '消息中心', icon: 'svg-message', alwaysShow: true},
children: [
{
path: 'manage',
name: 'messageManagement',
component: lazyLoadView('message/manage/'),
component: 'message/manage/',
meta: {title: '消息管理'}
},
{
path: 'user',
name: 'userMessage',
component: lazyLoadView('message/user/'),
component: 'message/user/',
meta: {title: '个人消息', noAuth: true, noCache: true}
}
]

@ -1,23 +1,23 @@
/*路由表:采购管理*/
import Layout from '@/layout'
import {lazyLoadView} from "@/router/util"
const router = {
path: '/doc/purchase',
component: Layout,
meta: {title: '采购管理', icon: 'shopping', alwaysShow: true},
component: 'Layout',
meta: {title: '采购管理', icon: 'svg-shopping', alwaysShow: true},
children: [
{
path: 'order',
name: 'purchaseOrder',
component: lazyLoadView('purchase/order/'),
component: 'purchase/order/',
meta: {title: '采购订单'}
},
{
path: 'order/detail/:type(see|add|edit)/:id?',
props: true,
component: lazyLoadView('purchase/order/detail'),
component: 'purchase/order/detail',
meta: {
title: '采购订单详情页',
hidden: true,
dynamicTitle(to) {
const {type, id} = to.params
switch (type) {
@ -37,14 +37,16 @@ const router = {
{
path: 'inbound',
name: 'purchaseInbound',
component: lazyLoadView('purchase/inbound/'),
component: 'purchase/inbound/',
meta: {title: '采购入库'}
},
{
path: 'inbound/detail/:type(see|add|edit)/:id?',
props: true,
component: lazyLoadView('purchase/inbound/detail'),
component: 'purchase/inbound/detail',
meta: {
title: '采购入库详情页',
hidden: true,
dynamicTitle(to) {
const {type, id} = to.params
switch (type) {

@ -1,23 +1,23 @@
/*路由表:销售管理*/
import Layout from '@/layout'
import {lazyLoadView} from "@/router/util"
const router = {
path: '/doc/sell',
component: Layout,
meta: {title: '销售管理', icon: 'sell', alwaysShow: true},
component: 'Layout',
meta: {title: '销售管理', icon: 'svg-sell', alwaysShow: true},
children: [
{
path: 'order',
name: 'sellOrder',
component: lazyLoadView('sell/order/'),
component: 'sell/order/',
meta: {title: '销售订单'}
},
{
path: 'order/detail/:type(see|add|edit)/:id?',
props: true,
component: lazyLoadView('sell/order/detail'),
component: 'sell/order/detail',
meta: {
title: '销售订单详情页',
hidden: true,
dynamicTitle(to) {
const {type, id} = to.params
switch (type) {
@ -37,14 +37,16 @@ const router = {
{
path: 'outbound',
name: 'sellOutbound',
component: lazyLoadView('sell/outbound/'),
component: 'sell/outbound/',
meta: {title: '销售出库'}
},
{
path: 'outbound/detail/:type(see|add|edit)/:id?',
props: true,
component: lazyLoadView('sell/outbound/detail'),
component: 'sell/outbound/detail',
meta: {
title: '销售出库详情页',
hidden: true,
dynamicTitle(to) {
const {type, id} = to.params
switch (type) {

@ -1,16 +1,14 @@
/*路由表:库存管理*/
import Layout from '@/layout'
import {lazyLoadView} from "@/router/util"
const router = {
path: '/stock',
component: Layout,
meta: {title: '库存管理', icon: 'stock', alwaysShow: true},
component: 'Layout',
meta: {title: '库存管理', icon: 'svg-stock', alwaysShow: true},
children: [
{
path: 'current',
name: 'currentStock',
component: lazyLoadView('stock/current/'),
component: 'stock/current/',
meta: {title: '当前库存'}
}
]

@ -1,46 +1,50 @@
/*路由表:系统管理*/
import Layout from '@/layout'
import {lazyLoadView} from "@/router/util"
const router = {
path: '/system',
component: Layout,
meta: {title: '系统管理', icon: 'system', alwaysShow: true},
component: 'Layout',
meta: {title: '系统管理', icon: 'svg-system', alwaysShow: true},
children: [
{
path: 'department',
name: 'departmentManagement',
component: lazyLoadView('system/department/'),
component: 'system/department/',
meta: {title: '部门管理'}
},
{
path: 'menu',
name: 'menuManagement',
component: 'system/menu/',
meta: {title: '菜单管理', noCache: true}
},
{
path: 'role',
name: 'roleManagement',
component: lazyLoadView('system/role/'),
component: 'system/role/',
meta: {title: '角色管理'}
},
{
path: 'user',
name: 'userManagement',
component: lazyLoadView('system/user/'),
component: 'system/user/',
meta: {title: '用户管理'}
},
{
path: 'category',
name: 'categorySetting',
component: lazyLoadView('system/category/'),
component: 'system/category/',
meta: {title: '商品分类'}
},
{
path: 'customer',
name: 'customerManagement',
component: lazyLoadView('system/customer/'),
component: 'system/customer/',
meta: {title: '客户管理'}
},
{
path: 'supplier',
name: 'supplierManagement',
component: lazyLoadView('system/supplier/'),
component: 'system/supplier/',
meta: {title: '供应商管理'}
}
]

@ -1,26 +1,122 @@
import {pathToRegexp} from 'path-to-regexp'
import PageSkeleton from "@/component/Skeleton/PageSkeleton"
import {title} from "@/config"
import store from "@/store"
import Layout from '@/layout'
import {isEmpty} from "@/util"
import {isExternal} from "@/util/validate"
//拼接页面标题
export function setPageTitle(route) {
const pageTitle = route.meta.title
document.title = pageTitle ? `${pageTitle} - ${title}` : title
//json字符串转路由
export function parseRoutes(str) {
const parseOpt = (key, value) => {
if (key === 'dynamicTitle' && typeof value === 'string') {
return isEmpty(value) ? undefined : eval(`(${value})`)
}
return value
}
return JSON.parse(str, parseOpt)
}
//确定路由的标题
export function specifyRouteTitle(to, from) {
const {meta} = to
if (typeof meta.dynamicTitle === 'function') {
meta.title = meta.dynamicTitle(to, from)
//路由转json字符串
export function stringifyRoutes(routes) {
const stringifyOpt = (key, value) => {
if (key === 'dynamicTitle' && typeof value === 'function') {
return value.toString()
}
return value
}
return JSON.stringify(routes, stringifyOpt)
}
//将给定的白名单url转换为正则
export function transformWhiteList(list) {
return list.map(pathToRegexp)
//根据json数组生成路由
export function generateRoutes(jsonTree) {
//将深度为2的节点合并为由其最末子节点组成的数组
function mergeChildren(routes) {
function fun(child, parent = {path: ''}, list = []) {
const {children} = child
if (!isEmpty(parent.path)) {
child.path = `${parent.path}/${child.path}`
}
if (!children || !children.length) {
list.push(child)
}
else {
children.forEach(i => fun(i, child, list))
}
return list
}
routes.forEach(root => {
const {children} = root
if (children && children.length) {
const list = []
children.forEach(route => {
list.push(...fun(route))
})
root.children = list
}
})
}
//数据转换,同时剔除不合法的路由(外链、无子节点且无component且非iframe)
function transform(routes, depth = 1) {
for (let i = routes.length - 1; i >= 0; i--) {
const {path, component, children, meta = {}} = routes[i]
if (isEmpty(component)) {
//剔除外链
if (isExternal(path)) {
routes.splice(i, 1)
continue
}
delete routes[i].component
}
else {
//设置路由组件
if (component.toUpperCase() === 'LAYOUT') {
routes[i].component = Layout
}
else routes[i].component = lazyLoadView(component)
}
if (!children || children.length <= 0) {
//末级节点统一设置props=true,省的动态路由还要手动加
routes[i].props = true
}
else {
transform(children, depth + 1)
//如果子级被清空,并且无component且非iframe
if (children.length <= 0 && !meta.iframe) {
routes.splice(i, 1)
continue
}
//设置根节点的redirect
if (depth === 1) {
let rootPath = path
if (!rootPath.endsWith('/')) rootPath += '/'
routes[i].redirect = `${rootPath}${children[0].path}`
}
}
}
}
//路由页面懒加载
function lazyLoadView(path) {
if (path.endsWith('/')) path += 'index'
else path = path.replace('Page.vue', '')
const AsyncHandler = () => ({component: import(`@/view/${path}Page.vue`), loading: PageSkeleton})
return () => Promise.resolve({
functional: true,
render(h, {data, children}) {
return h(AsyncHandler, data, children)
}
})
}
mergeChildren(jsonTree)
transform(jsonTree)
return jsonTree
}
//子路由继承父路由meta上的{noAuth,noCache},优先使用子路由的值
@ -43,42 +139,3 @@ export function metaExtend(routes, parentMeta) {
route.children && metaExtend(route.children, route.meta)
})
}
//路由页面懒加载
export function lazyLoadView(path) {
if (path.endsWith('/')) path += 'index'
else path = path.replace('Page.vue', '')
const AsyncHandler = () => ({component: import(`@/view/${path}Page.vue`), loading: PageSkeleton})
return () => Promise.resolve({
functional: true,
render(h, {data, children}) {
return h(AsyncHandler, data, children)
}
})
}
//判断是否需要一次额外的redirect跳转
export function needExtraRedirect(to, from) {
//若是共用组件的路由页面之间的跳转,借助redirect避免组件复用
const a = to.meta.commonModule, b = from.meta.commonModule
return !isEmpty(a) && a === b
}
//初始化菜单和权限
export function initMenuAndResource() {
if (!store.state.resource.init) {
return store.dispatch('resource/init', store.state.user)
}
return Promise.resolve()
}
//判断是否需要打开iframe
export function iframeControl(to, from) {
let iframe = to.meta.iframe
const operate = iframe ? 'open' : 'close'
if (to.path === `/redirect${from.path}`) {
iframe = from.meta.iframe
}
return store.dispatch(`iframe/${operate}`, iframe)
}

@ -1,123 +1,114 @@
import path from 'path'
import {dynamicRoutes} from '@/router/define'
import {useBackendRoute} from '@/config'
import {addDynamicRoutes} from '@/router'
import {getDynamicRoutes} from '@/router/define'
import {stringifyRoutes, parseRoutes, metaExtend} from "@/router/util"
import {needAuth} from "@/util/auth"
import {createTree} from "@/util/tree"
import {getAllResources} from "@/api/system/resource"
import {getAll} from "@/api/system/resource"
import {isEmpty} from "@/util"
import {isExternal} from "@/util/validate"
const finalRoutes = transformOriginRoutes(dynamicRoutes)
clean(finalRoutes, false)
const state = {
routes: [],
sidebarMenus: [],
dataMap: {},
tree: [],
menus: [],
resourceMap: {},
resourceTree: [],
init: false
}
const mutations = {
routes(state, routes) {
clean(routes)
state.routes = routes
const sidebarMenus = routes
sort(sidebarMenus)
state.sidebarMenus = sidebarMenus
menus(state, menus) {
sort(menus)
state.menus = menus
},
data(state, data) {
data = data || []
state.dataMap = data.reduce((map, item) => {
map[item.url] = item.id
return map
}, {})
state.tree = createTree(data.filter(resource => resource.admin === false))
resource(state, {data, admin}) {
state.resourceMap = generateResourceMap(data)
//如果不是admin用户,那么过滤admin类型的权限
if (!admin) data = data.filter(i => !i.admin)
//解析meta,这里不处理meta.dynamicTitle
data.forEach(i => {
if (typeof i.meta === 'string' && !isEmpty(i.meta)) {
i.meta = JSON.parse(i.meta)
}
})
//为path为'/'的根节点设置默认名称
const root = data.find(i => i.pid === 0 && i.path === '/')
if (root) {
root.meta = {title: '初始节点'}
}
state.resourceTree = createTree(data)
},
setInit(state, sign) {
init(state, sign) {
state.init = sign
}
}
const actions = {
init({dispatch}, user) {
return Promise.all([
dispatch('initRoutes', user),
dispatch('initResource')
])
},
initRoutes({commit}, user) {
const {resources, admin} = user
return new Promise(resolve => {
const accessedRoutes = getAuthorizedRoutes({resources, admin})
commit('routes', accessedRoutes)
commit('setInit', true)
resolve()
})
},
initResource({commit}) {
return new Promise(resolve => {
getAllResources()
.then(data => {
commit('data', data)
resolve()
})
})
init({commit}, {resources, admin, addRoutes = false}) {
return getAll()
.then(data => {
const routes = transformOriginRouteData(data)
metaExtend(routes)
addRoutes && addDynamicRoutes(routes)
const menus = getAuthorizedMenus({resources, admin}, routes)
commit('menus', menus)
commit('resource', {data: data || [], admin})
commit('init', true)
})
}
}
//在原始路由数组基础上添加全路径
function transformOriginRoutes(routes) {
const res = JSON.parse(JSON.stringify(routes))
addFullPath(res)
return res
}
//删除不显示的路由(没有children且没有meta.title,左侧菜单需清除meta.hidden=true)
function clean(routes, cleanHidden = true) {
for (let i = routes.length - 1; i >= 0; i--) {
const {children, meta: {title, alwaysShow, hidden} = {}} = routes[i]
if (cleanHidden && hidden) {
routes.splice(i, 1)
continue
}
if (!children && !title) {
routes.splice(i, 1)
continue
//将后台返回的数据转换为合法的route数组
function transformOriginRouteData(data) {
if (!Array.isArray(data)) return []
if (!useBackendRoute) return getDynamicRoutes()
data = JSON.parse(JSON.stringify(data))
data = data.filter(i => {
if (i.type === 3 || !i.enable) {
return false
}
if (children) {
clean(children, cleanHidden)
if (children.length < 1 && !alwaysShow) {
routes.splice(i, 1)
['name', 'component', 'meta'].forEach(key => {
if (key in i && isEmpty(i[key])) {
delete i[key]
}
})
if (typeof i.meta === 'string') {
i.meta = parseRoutes(i.meta)
}
}
return true
})
return createTree(data)
}
//路由添加全路径
//获取经过权限控制后的菜单
function getAuthorizedMenus({resources, admin}, menus) {
menus = JSON.parse(JSON.stringify(menus))
clean(menus)
addFullPath(menus)
if (admin === true) return menus
if (!resources) return []
filter(menus, i => !needAuth(i) || i.fullPath in resources)
return menus
}
//菜单添加全路径
function addFullPath(routes, basePath = '/') {
routes.forEach(route => {
delete route.component
route.fullPath = isExternal(route.path) ? route.path : path.resolve(basePath, route.path)
route.children && addFullPath(route.children, route.fullPath)
})
}
//获取经过权限控制后的路由
function getAuthorizedRoutes({resources, admin}) {
if (admin === true) return finalRoutes
if (!resources) return []
filter(finalRoutes, i => !needAuth(i) || i.fullPath in resources)
return finalRoutes
}
//若没有children且未通过,则删除,若有,当children长度为0时删除
function filter(arr, fun) {
for (let i = arr.length - 1; i >= 0; i--) {
const {children} = arr[i]
if (!children) {
if (!children || children.length <= 0) {
!fun(arr[i]) && arr.splice(i, 1)
continue
}
@ -128,8 +119,53 @@ function filter(arr, fun) {
}
}
//删除不显示的菜单(没有children且没有meta.title,左侧菜单需清除meta.hidden=true)
function clean(menus, cleanHidden = true) {
for (let i = menus.length - 1; i >= 0; i--) {
const {children = [], meta: {title, alwaysShow, hidden} = {}} = menus[i]
if (cleanHidden && hidden) {
menus.splice(i, 1)
continue
}
if (children.length === 0) {
if (isEmpty(title)) {
menus.splice(i, 1)
}
continue
}
clean(children, cleanHidden)
if (children.length === 0 && !alwaysShow) {
menus.splice(i, 1)
}
//移除类似首页那样的无标题父级节点
if (isEmpty(title)) {
menus.splice(i, 1, ...children)
}
}
}
//菜单排序
function sort(routes) {
const getSortValue = item => {
const sort = deepTap(item)
return isEmpty(sort) ? 10000 : sort
}
const deepTap = item => {
const {name, children = [], meta: {title, hidden, sort} = {}} = item
if (hidden) return null
if (!isEmpty(sort)) return sort
//如果是类似首页那样的路由层级
if (isEmpty(name, title) && children.length === 1) {
return deepTap(children[0])
}
return null
}
routes.sort((pre, next) => {
pre = getSortValue(pre)
next = getSortValue(next)
@ -145,19 +181,53 @@ function sort(routes) {
})
}
const getSortValue = item => {
const sort = deepTap(item)
return isEmpty(sort) ? 10000 : sort
}
const deepTap = item => {
const {name, children = [], meta: {title, hidden, sort} = {}} = item
if (hidden) return null
if (!isEmpty(sort)) return sort
//如果是类似首页那样的路由层级
if (isEmpty(name, title) && children.length === 1) {
return deepTap(children[0])
//根据权限列表生成哈希表:<权限路径,权限id>
function generateResourceMap(resources) {
if (!Array.isArray(resources) || resources.length <= 0) {
return {}
}
return null
resources = JSON.parse(JSON.stringify(resources))
const result = {}
//临时表:<id,resource>
const map = resources.reduce((map, i) => {
//如果是接口类型的节点,直接放入结果表
if (i.type === 3) result[i.path] = i.id
else {
i.children = []
map[i.id] = i
}
return map
}, {})
//获取所有的菜单叶子节点
const menuLeaves = resources.filter(node => {
const parent = map[node.pid]
if (parent) {
parent.children.push(node)
node.parent = parent
}
return node.type === 2
})
//获取叶子菜单的全路径
const getMenuFullPath = menu => {
const parents = [menu.path]
let {parent} = menu
while (parent) {
parents.push(parent.path)
parent = parent.parent
}
return path.resolve(...parents.reverse())
}
//填充结果表
for (const menu of menuLeaves) {
result[getMenuFullPath(menu)] = menu.id
}
return result
}
export default {

@ -26,18 +26,14 @@ const mutations = createMutations(state, true)
const actions = {
login({commit, dispatch}, userInfo) {
const {username, password} = userInfo
return new Promise((resolve, reject) => {
login({username: username.trim(), password})
.then(user => {
user.admin === true && (user.roleName = '超级管理员')
user.avatar = autoCompleteUrl(user.avatar)
commit('$all', user)
setUser(user)
return dispatch('socket/init', user, {root: true})
})
.then(() => resolve())
.catch(error => reject(error))
})
return login({username: username.trim(), password})
.then(user => {
if (user.admin === true) user.roleName = '超级管理员'
user.avatar = autoCompleteUrl(user.avatar)
commit('$all', user)
setUser(user)
return dispatch('socket/init', user, {root: true})
})
},
logout({commit, state, dispatch}) {
@ -46,7 +42,7 @@ const actions = {
commit('prepareLogout', 'yes')
logout(state.token)
.then(() => {
commit('resource/setInit', false, {root: true})
commit('resource/init', false, {root: true})
return Promise.all([
dispatch('socket/close', null, {root: true}),
dispatch('removeUser'),

@ -1,18 +1,18 @@
import store from '@/store'
import {isEmpty} from "@/util/index"
//判断路由是否需要鉴权
//判断路由是否需要鉴权,需要则返回true
export function needAuth(route) {
if (route.path.startsWith('/redirect')) return false
const {meta} = route
return !meta || !meta.noAuth || isEmpty(meta.title) && isEmpty(meta.dynamicTitle)
return !meta || !meta.noAuth || !isEmpty(meta.title, meta.dynamicTitle)
}
//接口权限判断,有权限则返回true
export function auth(path) {
if (store.state.user.admin === true) return true
const resourceMap = store.state.resource.dataMap
const resourceMap = store.state.resource.resourceMap
if (!(path in resourceMap)) return true
const resources = store.state.user.resources

@ -1,5 +1,5 @@
import {isEmpty} from "@/util"
import request from "@/config/request"
import request from "@/api/request"
import {elError} from "@/util/message"
import {download} from "@/util/file"
import {flatTree} from "@/util/tree"

@ -1,4 +1,4 @@
import request from "@/config/request"
import request from "@/api/request"
import {attachmentUploadUrl, attachmentPrefix, filePreviewPrefix} from '@/config'
import {isEmpty, timeFormat} from "@/util"
import {isTxt} from "@/util/validate"

@ -16,6 +16,12 @@ export function resetObj(obj) {
else if (typeof obj[key] === 'number') {
obj[key] = 0
}
else if (typeof obj[key] === 'boolean') {
obj[key] = false
}
else if (obj[key] !== null && typeof obj[key] === 'object') {
resetObj(obj[key])
}
else obj[key] = null
})
}
@ -23,14 +29,22 @@ export function resetObj(obj) {
//简单合并对象
export function mergeObj(target, source) {
Object.keys(target).forEach(key => {
if (key in source) {
if (!isEmpty(source) && key in source) {
if (Array.isArray(target[key])) {
target[key] = source[key] || []
}
else if (typeof target[key] === 'number') {
target[key] = source[key] || 0
}
else target[key] = source[key]
else if (target[key] !== null && typeof target[key] === 'object') {
mergeObj(target[key], source[key])
}
else {
if (typeof source[key] === 'function') {
target[key] = source[key].toString()
}
else target[key] = source[key]
}
}
})
}

@ -1,27 +1,39 @@
import {createWorker} from '@/util/worker'
const DEFAULT_PROPS = {id: 'id', pid: 'pid', children: 'children', leafHasChildren: true}
/**
* 列表转树形结构
* @param {Array} list
* @param {Number|String} rootSign 根节点的pid值
* @param {String} idKey
* @param {String} pidKey
* @param {String} childrenKey
* @param {Number|String|Function} rootSign 根节点的判定方法
* @param {Object} props 树的配置项
* @returns {Array}
*/
export function createTree(list, rootSign = 0, idKey = 'id', pidKey = 'pid', childrenKey = 'children') {
export function createTree(list, rootSign = 0, props = {}) {
if (!list || list.length <= 0) return []
const {
id = DEFAULT_PROPS.id,
pid = DEFAULT_PROPS.pid,
children = DEFAULT_PROPS.children,
leafHasChildren = DEFAULT_PROPS.leafHasChildren
} = props
const info = {}
list.forEach(i => {
i[childrenKey] = []
info[i[idKey]] = i
info[i[id]] = i
if (leafHasChildren) i[children] = []
})
return list.filter(node => {
info[node[pidKey]] && info[node[pidKey]][childrenKey].push(node)
return node[pidKey] === rootSign
const key = node[pid]
const parent = info[key]
if (parent) {
if (!parent[children]) parent[children] = []
parent[children].push(node)
}
return typeof rootSign === 'function' ? rootSign(node) : key === rootSign
})
}
@ -212,8 +224,9 @@ function workerTree() {
info[i[idKey]] = i
})
return list.filter(node => {
info[node[pidKey]] && info[node[pidKey]][childrenKey].push(node)
return node[pidKey] === rootSign
const key = node[pidKey]
info[key] && info[key][childrenKey].push(node)
return key === rootSign
})
}

@ -2,7 +2,7 @@
<el-form ref="form" :model="form" :rules="rules" label-position="left">
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon="user"/>
<v-icon icon="svg-user"/>
</span>
<el-input
@ -18,7 +18,7 @@
<el-form-item prop="password">
<el-tooltip v-model="capsTooltip" :tabindex="-1" content="大写锁定已打开" manual placement="left">
<span class="svg-container">
<svg-icon icon="password"/>
<v-icon icon="svg-password"/>
</span>
</el-tooltip>
@ -34,7 +34,7 @@
/>
<span @click="showPwd" class="show-pwd">
<svg-icon :icon="passwordType === 'password' ? 'eye' : 'eye-open'"/>
<v-icon :icon="passwordType === 'password' ? 'svg-eye' : 'svg-eye-open'"/>
</span>
</el-form-item>
@ -45,7 +45,7 @@
<div class="flex" style="margin-top: 20px">
<p class="other-ways">
其他方式登录
<svg-icon v-for="i in otherWays" :key="i" :icon="i" @click="() => thirdPartyLogin(i)"/>
<v-icon v-for="i in otherWays" :key="i" :icon="`svg-${i}`" @click="() => thirdPartyLogin(i)"/>
</p>
<el-button type="text" @click="register">注册账户</el-button>
@ -89,7 +89,7 @@ export default {
this.$refs.form.validate(valid => {
if (!valid) return
this.loading = true
this.$puzzleVerify()
this.$verify()
.then(() => this.$store.dispatch('user/login', {
...this.form,
password: md5(this.form.password)
@ -107,7 +107,7 @@ export default {
elSuccess('登陆成功')
const redirect = this.$route.query.redirect || '/'
//0.2s
setTimeout(() => this.$router.push(redirect), 200)
setTimeout(() => this.$router.replace(redirect), 200)
},
thirdPartyLogin(channel) {

@ -2,7 +2,7 @@
<el-form ref="form" :model="form" :rules="rules" label-position="left">
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon="user"/>
<v-icon icon="svg-user"/>
</span>
<el-input ref="username" v-model="form.username" :maxlength="20" placeholder="请输入用户名"/>
</el-form-item>
@ -10,7 +10,7 @@
<el-form-item prop="pwd">
<el-tooltip v-model="capsTooltip" :tabindex="-1" content="大写锁定已打开" manual placement="left">
<span class="svg-container">
<svg-icon icon="password"/>
<v-icon icon="svg-password"/>
</span>
</el-tooltip>
<el-input v-model="form.pwd" placeholder="请输入密码" type="password" :maxlength="20"/>
@ -18,7 +18,7 @@
<el-form-item prop="repwd">
<span class="svg-container">
<svg-icon icon="password"/>
<v-icon icon="svg-password"/>
</span>
<el-input
v-model="form.repwd"

@ -27,7 +27,7 @@ export default {
return (
<el-dropdown class={customClass} size="medium" trigger="click" onCommand={context.listeners.select}>
<el-button type="text" title="选择背景动画">
<svg-icon icon="show"/>
<v-icon icon="svg-show"/>
</el-button>
<el-dropdown-menu slot="dropdown">
{

@ -12,7 +12,7 @@
<script>
import UploadFile from '@/component/UploadFile'
import request from "@/config/request"
import request from "@/api/request"
export default {
name: "uploadExample",

@ -10,7 +10,7 @@
</template>
<script>
import request from "@/config/request"
import request from "@/api/request"
export default {
name: "ApiSpeedTest",

@ -8,7 +8,7 @@
<el-tabs type="border-card">
<el-tab-pane label="svg-icons">
<div v-for="item of svgIcons" :key="item" class="icon-item">
<svg-icon :icon="item"/>
<v-icon :icon="`svg-${item}`"/>
<span>{{ item }}</span>
</div>
</el-tab-pane>

@ -18,7 +18,7 @@ export default {
return (
<el-row class="card-panel" type="flex" justify="space-between" {...context.data}>
<div class="card-panel-icon-wrapper" style={'color:' + color}>
<svg-icon icon={icon} class-name="card-panel-icon"/>
<v-icon icon={icon} class="card-panel-icon"/>
</div>
<div class="card-panel-description">
<div class="card-panel-text">{text}</div>
@ -87,7 +87,7 @@ export default {
height: 100%;
margin: 0 !important;
.svg-icon {
.icon {
display: block;
margin: 14px auto !important;
float: none !important;

@ -22,17 +22,37 @@ export default {
return {
loading: false,
list: [
{id: 'online', path: '/system/user', icon: 'user', color: variables.info, value: 0, text: '在线用户'},
{
id: 'online',
path: '/system/user',
icon: 'svg-user',
color: variables.info,
value: 0,
text: '在线用户'
},
{
id: 'purchase',
path: '/purchase/order',
icon: 'shopping',
path: '/doc/purchase/order',
icon: 'svg-shopping',
color: variables.primary,
value: 0,
text: '今日采购额'
},
{id: 'sell', path: '/sell/order', icon: 'sell', color: variables.warning, value: 0, text: '今日销售额'},
{id: 'profit', icon: 'money', color: variables.success, value: 0, text: '今日毛利润'},
{
id: 'sell',
path: '/doc/sell/order',
icon: 'svg-sell',
color: variables.warning,
value: 0,
text: '今日销售额'
},
{
id: 'profit',
icon: 'svg-money',
color: variables.success,
value: 0,
text: '今日毛利润'
},
]
}
},

@ -38,7 +38,7 @@
<el-date-picker :value="form.wtime" format="yyyy-MM-dd HH:mm:ss" readonly type="date"/>
</abstract-form-item>
<abstract-form-item label="内 容" full>
<tinymce-editor v-model="form.content" :readonly="!canSave"/>
<rich-text-editor v-model="form.content" :readonly="!canSave"/>
</abstract-form-item>
</abstract-form>
@ -54,10 +54,10 @@
<script>
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import AbstractFormItem from "@/component/AbstractForm/AbstractFormItem"
import AbstractFormItem from "@/component/AbstractForm/item"
import FormDialog from '@/component/FormDialog'
import TinymceEditor from "@/component/TinymceEditor"
import {SimpleMultipleUserSelector as UserSelector} from '@/component/biz/UserSelector'
import RichTextEditor from "@/component/editor/RichTextEditor"
import UserSelector from '@/component/biz/UserSelector/SimpleMultipleUserSelector'
import {baseUrl, add, update, publish, withdraw} from "@/api/message/manage"
import {isEmpty, mergeObj, resetObj} from '@/util'
import {auth} from "@/util/auth"
@ -68,7 +68,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractForm, AbstractFormItem, FormDialog, TinymceEditor, UserSelector},
components: {AbstractForm, AbstractFormItem, FormDialog, RichTextEditor, UserSelector},
props: {
value: {type: Boolean, default: false},
@ -139,9 +139,8 @@ export default {
methods: {
open() {
if (this.type === 'add') return
mergeObj(this.form, this.data)
const recipient = this.data.recipient || ''
this.form.recipient = recipient.split(',').map(i => parseInt(i))
const recipient = (this.data.recipient || '').split(',').filter(Boolean).map(i => parseInt(i))
mergeObj(this.form, {...this.data, recipient})
},
clearForm() {
@ -166,7 +165,7 @@ export default {
if (!v) return
this.loading = true
const data = this.transformForm()
let promise = this.type === 'add' ? add(data) : update(data)
const promise = this.type === 'add' ? add(data) : update(data)
promise
.then(({data, msg}) => {
this.needSearch = true

@ -57,7 +57,7 @@
import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, search, del} from "@/api/message/manage"
import {isEmpty} from '@/util'
import {auth} from "@/util/auth"

@ -51,11 +51,7 @@
</el-row>
<el-row v-loading="config.loading" class="table-container">
<abstract-table
:data="tableData"
@row-click="rowClick"
@expand-change="getSubList"
>
<abstract-table :data="tableData" @row-click="rowClick" @expand-change="getSubList">
<el-table-column align="center" type="expand">
<template v-slot="{row}">
<liner-progress :show="row._loading"/>
@ -92,7 +88,7 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, del, getSubById, search} from "@/api/doc/purchase/inbound"
export default {

@ -67,11 +67,7 @@
</el-row>
<el-row v-loading="config.loading" class="table-container">
<abstract-table
:data="tableData"
@row-click="rowClick"
@expand-change="getSubList"
>
<abstract-table :data="tableData" @row-click="rowClick" @expand-change="getSubList">
<el-table-column align="center" type="expand">
<template v-slot="{row}">
<liner-progress :show="row._loading"/>
@ -120,7 +116,7 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, del, getSubById, search} from "@/api/doc/purchase/order"
export default {

@ -123,7 +123,7 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, del, getSubById, search} from "@/api/doc/sell/order"
export default {

@ -95,7 +95,7 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, del, getSubById, search} from "@/api/doc/sell/outbound"
export default {

@ -65,7 +65,7 @@ import CategoryTree from '@/component/biz/CategoryTree'
import DetailDialog from "./DetailDialog"
import ExtraArea from '@/component/ExtraArea'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, search} from "@/api/stock/current"
import {isEmpty, debounce} from "@/util"
import {exportExcel} from "@/util/excel"

@ -1,23 +1,14 @@
<template>
<el-card v-show="show" v-loading="loading" :header="title" class="card-container" style="min-width: 520px;">
<el-form
ref="form"
:model="form"
:rules="rules"
class="edit-form"
label-position="right"
label-width="100px"
label-suffix=":"
status-icon
>
<abstract-form :model="form" :rules="rules" class="edit-form">
<el-form-item label="上级分类">
<el-input :value="parentObj?parentObj.name:''" readonly/>
<el-input :value="parentObj ? parentObj.name : ''" readonly/>
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="form.name" :readonly="type==='see'" maxlength="50"/>
<el-input v-model="form.name" :readonly="type === 'see'" maxlength="50"/>
</el-form-item>
<el-form-item label="类 型" prop="type">
<el-select v-model="form.type" :disabled="type==='see'">
<el-select v-model="form.type" :disabled="type === 'see'">
<el-option :value="0" label="分类"/>
<el-option :value="1" label="实体"/>
</el-select>
@ -33,16 +24,19 @@
</el-button>
</el-form-item>
</el-form>
</abstract-form>
</el-card>
</template>
<script>
import AbstractForm from "@/component/AbstractForm"
import {add, update} from "@/api/system/category"
import {isEmpty, mergeObj, waitUntilSuccess} from '@/util'
import {elConfirm} from "@/util/message"
import {add, update} from "@/api/system/category"
export default {
components: {AbstractForm},
props: ['list'],
data() {

@ -17,7 +17,7 @@ import {isEmpty, waitUntilSuccess} from '@/util'
import {elConfirm, elError, elSuccess} from "@/util/message"
import {del, getAll} from "@/api/system/category"
import ContextMenu from "@/component/ContextMenu"
import ContextMenuItem from "@/component/ContextMenu/ContextMenuItem"
import ContextMenuItem from "@/component/ContextMenu/item"
import CategoryTree from '@/component/biz/CategoryTree'
export default {

@ -78,7 +78,7 @@ import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import RegionSelector from "@/component/RegionSelector"
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, delCustomer, getCustomers, getLimitRegion} from "@/api/system/customer"
import {isEmpty} from '@/util'
import {auth} from "@/util/auth"

@ -53,12 +53,11 @@
* https://admin.iviewui.com/component/org_tree_page
*/
import ContextMenu from "@/component/ContextMenu"
import ContextMenuItem from "@/component/ContextMenu/ContextMenuItem"
import ContextMenuItem from "@/component/ContextMenu/item"
import EditDialog from "./component/EditDialog"
import OrgTreeView from "./component/OrgTreeView"
import ZoomControl from '@/component/ZoomControl'
import ZoomControl from './component/ZoomControl'
import {baseUrl, delDepartment, getDepartments} from "@/api/system/department"
import {isEmpty} from '@/util'
import {auth} from "@/util/auth"
import {elConfirm, elError, elSuccess} from "@/util/message"
import {createTreeByWorker} from "@/util/tree"

@ -0,0 +1,13 @@
export const nodeType = [
{label: '聚合菜单', value: 1, icon: 'el-icon-folder'},
{label: '页面菜单', value: 2, icon: 'el-icon-document'},
{label: '数据接口', value: 3, icon: 'el-icon-open'},
]
export function getNodeInfo({data: {type}} = {}) {
return nodeType.find(i => i.value === type)
}
export function getNodeTitle({data: {type, name, meta}}) {
return type === 3 ? name : meta.title
}

@ -0,0 +1,358 @@
<template>
<el-card>
<el-row class="button-group">
<template v-if="canAdd">
<el-button icon="el-icon-plus" size="small" type="primary" @click="addRoot">添加根节点</el-button>
<el-button icon="el-icon-plus" size="small" type="primary" @click="addChild">添加子节点</el-button>
</template>
<el-button v-if="canDel" icon="el-icon-delete" size="small" type="danger" @click="del"> </el-button>
<el-dropdown placement="bottom" size="small" @command="more">
<el-button plain size="small">
更多操作
<i class="el-icon-arrow-down el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{action:'expand',level:0}">展开全部</el-dropdown-item>
<el-dropdown-item :command="{action:'collapse',level:0}">收起全部</el-dropdown-item>
<el-dropdown-item :command="{action:'expand',level:1}">仅展开一级</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-row>
<el-row :gutter="100" style="margin-top: 20px;">
<el-col :xs="24" :sm="11" :md="9" :lg="8" :xl="6">
<el-alert show-icon :closable="false">
<template v-slot:title>
当前选择编辑
<el-tag v-if="curNode" size="mini" closable @close="() => curNode = null">
{{ getNodeTitle(curNode) }}
</el-tag>
</template>
</el-alert>
<el-input
v-model="search"
placeholder="输入菜单名搜索"
suffix-icon="el-icon-search"
size="small"
style="margin-top: 10px"
@input="v => $refs.tree.filter(v)"
/>
<div style="max-height: 660px;overflow: auto;margin-top: 10px">
<el-tree
ref="tree"
:data="menus"
node-key="id"
highlight-current
accordion
:expand-on-click-node="false"
:filter-node-method="filterNodeMethod"
@node-click="nodeClick"
>
<span slot-scope="{node}" class="el-tree-node__label">
<v-icon :icon="getNodeInfo(node).icon"/>
{{ getNodeTitle(node) }}
</span>
</el-tree>
</div>
</el-col>
<el-col :xs="24" :sm="13" :md="15" :lg="13" :xl="11">
<abstract-form v-if="showForm" :model="form" :rules="rules">
<el-form-item label="类型">
<el-select
v-if="showTypeSelector"
:value="form.type"
placeholder="请选择类型"
@change="typeChange"
>
<v-icon slot="prefix" :icon="getNodeInfo({data:form}).icon"/>
<el-option
v-for="{label,value,icon} in nodeType"
:key="value"
:label="label"
:value="value"
:disabled="value === 3"
>
<v-icon :icon="icon"/>
{{ label }}
</el-option>
</el-select>
<template v-else>
<v-icon :icon="getNodeInfo({data:form}).icon"/>
{{ getNodeInfo({data: form}).label }}
</template>
</el-form-item>
<el-form-item v-if="form.pid" label="上级菜单">
<v-icon :icon="getParentNodeInfo().icon"/>
{{ getParentNodeInfo().title }}
</el-form-item>
<el-form-item label="名称" prop="title">
<el-input v-if="form.type === 3" v-model="form.name" maxlength="10"/>
<template v-else>{{ form.meta.title }}</template>
</el-form-item>
<el-form-item label="路径" prop="path">
<el-input v-model="form.path" maxlength="100"/>
</el-form-item>
<!--菜单独有配置项-->
<template v-if="form.type !== 3">
<!--叶子菜单独有配置项-->
<template v-if="form.type === 2">
<el-form-item label="路由名称" prop="name">
<el-input v-model="form.name" maxlength="50"/>
</el-form-item>
<el-form-item label="菜单组件" prop="component">
<el-input v-model="form.component" maxlength="50"/>
</el-form-item>
</template>
<el-form-item label="meta" prop="meta">
<json-editor v-model="form.metaStr" @input="metaChange"/>
</el-form-item>
</template>
<el-form-item label="admin独有">
<el-switch v-model="form.admin" active-text="" inactive-text=""/>
</el-form-item>
<el-form-item label="是否启用">
<el-switch v-model="form.enable" active-text="" inactive-text=""/>
</el-form-item>
<el-form-item v-if="showSaveBtn">
<el-button
:loading="loading"
icon="el-icon-edit-outline"
size="small"
type="primary"
@click="save"
>
</el-button>
</el-form-item>
</abstract-form>
</el-col>
</el-row>
</el-card>
</template>
<script>
import treePageMixin from '@/mixin/treePageMixin'
import AbstractForm from "@/component/AbstractForm"
import JsonEditor from "@/component/editor/JsonEditor"
import {baseUrl, add, update, del} from '@/api/system/resource'
import {isEmpty, mergeObj, resetObj} from "@/util"
import {elSuccess, elError, elConfirm} from "@/util/message"
import {elTreeControl} from "@/util/tree"
import {auth} from "@/util/auth"
import {nodeType, getNodeInfo, getNodeTitle} from "./common"
export default {
name: "menuManagement",
mixins: [treePageMixin],
components: {AbstractForm, JsonEditor},
data() {
const validateTitle = (r, v, c) => {
const value = getNodeTitle({data: this.form})
c(isEmpty(value) ? '名称不能为空' : undefined)
}
return {
loading: false,
search: null,
type: 'add',// add or edit
nodeType,
form: {
id: null,
pid: null,
type: 0,
name: null,
path: null,
component: null,
meta: {
title: null,
dynamicTitle: null,
hidden: false,
alwaysShow: false,
sort: null,
icon: null,
affix: false,
noCache: false,
activeMenu: null,
noAuth: false,
iframe: null,
usePathKey: false,
useFullPathKey: false,
commonModule: null
},
metaStr: null,
admin: false,
enable: true
},
rules: {
title: [{validator: validateTitle, trigger: 'change'}],
path: [{required: true, message: '路径不能为空', trigger: 'change'}],
}
}
},
computed: {
canAdd() {
return auth(`${baseUrl}/add`)
},
canUpdate() {
return auth(`${baseUrl}/update`)
},
canDel() {
return auth(`${baseUrl}/del`)
},
menus() {
return this.$store.state.resource.resourceTree
},
showForm() {
return this.type === 'add' && this.form.pid === 0 || this.curNode
},
showTypeSelector() {
const isRoot = this.form.pid === 0
//1.
const noApiNode = this.form.type !== 3
//2.
const noChild = this.type === 'add' && isRoot
|| this.type === 'edit' && this.curNode.childNodes.length <= 0
//3.
const noInitialNode = this.form.path !== '/' || isRoot
//4.
const parentIsFolderMenu =
isRoot
? true
: this.type === 'add' && this.curNode.data.type === 1
|| this.type === 'edit' && this.curNode.parent.data.type === 1
return noApiNode && noChild && noInitialNode && parentIsFolderMenu
},
showSaveBtn() {
return this.type === 'add' && this.canAdd || this.type === 'edit' && this.canUpdate
}
},
methods: {
getNodeInfo,
getNodeTitle,
getParentNodeInfo() {
const node = this.type === 'add' ? this.curNode : this.curNode.parent
const {icon} = getNodeInfo(node)
return {icon, title: getNodeTitle(node)}
},
filterNodeMethod(value, data) {
if (isEmpty(value)) return true
return getNodeTitle({data}).includes(value)
},
afterNodeClick(data, node, ref, clear) {
if (clear) return this.clearForm()
this.type = 'edit'
this.$refs.form && this.$refs.form.clearValidate()
mergeObj(this.form, data)
if (data.meta) {
this.form.metaStr = JSON.stringify(data.meta, null, 2)
}
},
//json
metaChange(v) {
try {
v = JSON.parse(v)
}
catch (e) {
return
}
resetObj(this.form.meta)
mergeObj(this.form.meta, v)
},
//
typeChange(v) {
//component
if (v === 2 && this.form.type === 1) {
this.form.component = null
}
this.form.type = v
},
addRoot() {
if (this.loading) return
this.curNode = null
this.type = 'add'
this.clearForm()
this.form.pid = 0
this.form.type = 1
},
addChild() {
if (this.loading) return
if (!this.curNode) return elError('请选择一个节点')
const {data: {id, type}} = this.curNode
if (type === 3) return elError('数据接口不能作为父节点')
this.type = 'add'
this.clearForm()
this.form.pid = id
//
this.form.type = type === 2 ? 3 : 1
},
del() {
if (this.loading) return
if (!this.curNode) return elError('请选择要删除的节点')
elConfirm(`确定删除【${getNodeTitle(this.curNode)}】?`)
.then(() => {
this.loading = true
return del(this.curNode.data.id)
})
.then(() => {
elSuccess('删除成功')
this.init()
})
.finally(() => this.loading = false)
},
more({action, level}) {
if (this.loading) return
elTreeControl(this.$refs.tree, action, level)
},
init() {
this.curNode = null
this.clearForm()
return this.$store.dispatch('resource/init', this.$store.state.user)
},
clearForm() {
resetObj(this.form)
this.form.id = null
this.form.pid = null
this.form.metaStr = JSON.stringify({title: ''}, null, 2)
this.form.meta.sort = null
this.form.enable = true
},
save() {
if (this.loading) return
this.$refs.form.validate(v => {
if (!v) return
this.loading = true
const data = this.transformForm()
const promise = this.type === 'add' ? add(data) : update(data)
promise
.then(({data, msg}) => {
elSuccess(msg)
return this.init()
})
.finally(() => this.loading = false)
})
},
transformForm() {
return {...this.form, meta: this.form.metaStr, metaStr: undefined}
}
}
}
</script>

@ -30,11 +30,16 @@
<el-form-item label="权 限">
<el-tree
ref="perm"
:data="resource"
:props="{label:'name'}"
:data="resourceTree"
node-key="id"
show-checkbox
/>
accordion
>
<span slot-scope="{node}" class="el-tree-node__label">
<v-icon :icon="getNodeInfo(node).icon"/>
{{ getNodeTitle(node) }}
</span>
</el-tree>
</el-form-item>
</abstract-form>
@ -61,9 +66,10 @@ import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import FormDialog from '@/component/FormDialog'
import {getDepartments} from "@/api/system/department"
import {addRole, updateRole} from "@/api/system/role"
import {add, update} from "@/api/system/role"
import {isEmpty, mergeObj} from '@/util'
import {createTreeByWorker, elTreeControl} from '@/util/tree'
import {getNodeTitle, getNodeInfo} from "@/view/system/menu/common"
export default {
name: "EditDialog",
@ -120,16 +126,17 @@ export default {
}
},
resource() {
return this.$store.state.resource.tree
resourceTree() {
return this.$store.state.resource.resourceTree
}
},
methods: {
getNodeInfo,
getNodeTitle,
treeCommand({action, level}) {
elTreeControl(this.$refs.perm, action, level)
},
open() {
if (this.type !== 'edit') return
this.$nextTick(() => {
@ -149,27 +156,33 @@ export default {
this.$refs.dept && this.$refs.dept.setCheckedKeys([])
this.$nextTick(() => this.$refs.form.clearValidate())
},
cancel() {
this.closeDialog()
this.clearForm()
},
confirm() {
if (this.loading) return
this.$refs.form.validate(v => {
if (!v) return
this.loading = true
const obj = {
const data = {
...this.form,
departmentId: this.$refs.dept ? this.$refs.dept.getCheckedKeys().join(',') : [],
resourceId: this.$refs.perm.getCheckedKeys().join(',')
departmentId: this.getCheckedDept(),
resourceId: this.getCheckedRes()
}
const promise = this.type === 'add' ? addRole(obj) : updateRole(obj)
const promise = this.type === 'add' ? add(data) : update(data)
promise
.then(({msg}) => this.$emit('success', msg))
.finally(() => this.loading = false)
})
},
getCheckedDept() {
const ref = this.$refs.dept
return ref ? ref.getCheckedKeys().join(',') : null
},
getCheckedRes() {
const ref = this.$refs.perm
return ref ? ref.getCheckedKeys().join(',') : null
}
},

@ -32,10 +32,7 @@
</el-row>
<el-row v-loading="config.loading" class="table-container">
<abstract-table
:data="tableData"
@row-click="rowClick"
>
<abstract-table :data="tableData" @row-click="rowClick">
<el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="角色名" prop="name" show-overflow-tooltip/>
<el-table-column align="center" label="创建人" prop="cname" show-overflow-tooltip/>
@ -61,8 +58,8 @@
import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import {baseUrl, delRole, searchRoles} from "@/api/system/role"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, del, search} from "@/api/system/role"
import {isEmpty} from '@/util'
import {auth} from "@/util/auth"
import {elConfirm, elError, elSuccess} from "@/util/message"
@ -119,7 +116,7 @@ export default {
this.config.loading = true
this.row = null
this.type = 'see'
searchRoles(this.mergeSearchForm())
search(this.mergeSearchForm())
.then(({list, total}) => {
list.forEach(i => {
i.departmentId = this.str2intArray(i.departmentId)
@ -151,7 +148,7 @@ export default {
elConfirm(`确定删除角色【${name}】?`)
.then(() => {
this.config.operating = true
return delRole({id, name})
return del({id, name})
})
.then(() => {
elSuccess('删除成功')

@ -78,7 +78,7 @@ import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import RegionSelector from "@/component/RegionSelector"
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, delSupplier, getLimitRegion, getSuppliers} from "@/api/system/supplier"
import {isEmpty} from '@/util'
import {auth} from "@/util/auth"

@ -10,7 +10,7 @@
</template>
<script>
import {getRoles} from "@/api/system/role"
import {get} from "@/api/system/role"
export default {
name: "RoleSelector",
@ -25,7 +25,7 @@ export default {
methods: {
init() {
getRoles().then(data => this.roles = data)
get().then(data => this.roles = data)
},
emit(v) {

@ -83,7 +83,7 @@ import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './component/EditDialog'
import RoleSelector from './component/RoleSelector'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/SearchFormItem"
import SearchFormItem from "@/component/SearchForm/item"
import {baseUrl, delUser, getUsers, kick, resetUserPwd} from "@/api/system/user"
import {isEmpty} from '@/util'
import {auth} from "@/util/auth"

Loading…
Cancel
Save