Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
Starter Web Vue
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Package Registry
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
product
kim3-web-vue
Starter Web Vue
Commits
0b042bcf
Commit
0b042bcf
authored
Jul 07, 2021
by
水落(YangLei)
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 菜单权限处理
parent
445c4639
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
223 additions
and
434 deletions
+223
-434
src/App.vue
src/App.vue
+4
-5
src/components/menu/SideMenu.vue
src/components/menu/SideMenu.vue
+2
-7
src/components/menu/menu.js
src/components/menu/menu.js
+56
-30
src/components/page/PageHeader.vue
src/components/page/PageHeader.vue
+5
-34
src/main.js
src/main.js
+1
-3
src/pages/frame/components/header/LayoutTopHeader.vue
src/pages/frame/components/header/LayoutTopHeader.vue
+30
-19
src/pages/frame/components/header/LayoutTopHeaderLang.vue
src/pages/frame/components/header/LayoutTopHeaderLang.vue
+3
-1
src/pages/frame/components/setting/Setting.vue
src/pages/frame/components/setting/Setting.vue
+3
-5
src/pages/frame/components/setting/SettingItem.vue
src/pages/frame/components/setting/SettingItem.vue
+10
-10
src/pages/frame/components/tab/LayoutTabsHeader.vue
src/pages/frame/components/tab/LayoutTabsHeader.vue
+34
-90
src/pages/frame/layouts/AdminLayout.vue
src/pages/frame/layouts/AdminLayout.vue
+50
-28
src/pages/frame/layouts/CommonLayout.vue
src/pages/frame/layouts/CommonLayout.vue
+1
-12
src/pages/frame/layouts/PageLayout.vue
src/pages/frame/layouts/PageLayout.vue
+16
-46
src/pages/frame/store/settingModule.js
src/pages/frame/store/settingModule.js
+1
-8
src/pages/frame/view/template/TabsTemplateView.vue
src/pages/frame/view/template/TabsTemplateView.vue
+5
-10
src/router/config.js
src/router/config.js
+0
-7
src/router/guards.js
src/router/guards.js
+1
-33
src/router/index.js
src/router/index.js
+0
-2
src/utils/routerUtil.js
src/utils/routerUtil.js
+1
-84
No files found.
src/App.vue
View file @
0b042bcf
...
...
@@ -7,7 +7,6 @@
<
script
>
import
{
enquireScreen
}
from
'
./utils/commonUtil
'
;
import
{
mapState
,
mapMutations
}
from
'
vuex
'
;
import
{
getI18nKey
}
from
'
@/utils/routerUtil
'
;
import
{
changeThemeColor
}
from
'
@/utils/themeUtil
'
;
export
default
{
...
...
@@ -22,7 +21,7 @@ export default {
this
.
setLanguage
(
this
.
lang
);
enquireScreen
(
isMobile
=>
this
.
setDevice
(
isMobile
));
},
mounted
()
{},
watch
:
{
lang
(
val
)
{
this
.
setLanguage
(
val
);
...
...
@@ -31,15 +30,15 @@ export default {
$route
()
{
this
.
setHtmlTitle
();
},
'
theme.mode
'
:
function
(
val
)
{
'
theme.mode
'
(
val
)
{
let
closeMessage
=
this
.
$message
.
loading
(
`您选择了主题模式
${
val
}
, 正在切换...`
);
changeThemeColor
(
this
.
theme
.
color
,
val
).
then
(
closeMessage
);
},
'
theme.color
'
:
function
(
val
)
{
'
theme.color
'
(
val
)
{
let
closeMessage
=
this
.
$message
.
loading
(
`您选择了主题色
${
val
}
, 正在切换...`
);
changeThemeColor
(
val
,
this
.
theme
.
mode
).
then
(
closeMessage
);
},
layout
:
function
()
{
layout
()
{
window
.
dispatchEvent
(
new
Event
(
'
resize
'
));
},
},
...
...
src/components/menu/SideMenu.vue
View file @
0b042bcf
...
...
@@ -8,12 +8,12 @@
:trigger=
"null"
>
<div
:class=
"['logo', theme]"
>
<router-link
to=
"/
dashboard/workplace
"
>
<router-link
to=
"/"
>
<img
src=
"@/assets/img/logo.png"
/>
<h1>
{{
systemName
}}
</h1>
</router-link>
</div>
<i-menu
:theme=
"theme"
:collapsed=
"collapsed"
:options=
"menuData"
@
select=
"onSelect"
class=
"menu"
/>
<i-menu
:theme=
"theme"
:collapsed=
"collapsed"
:options=
"menuData"
class=
"menu"
/>
</a-layout-sider>
</
template
>
...
...
@@ -51,11 +51,6 @@ export default {
},
...
mapState
(
'
settingModule
'
,
[
'
isMobile
'
,
'
systemName
'
]),
},
methods
:
{
onSelect
(
obj
)
{
this
.
$emit
(
'
menuSelect
'
,
obj
);
},
},
};
</
script
>
...
...
src/components/menu/menu.js
View file @
0b042bcf
...
...
@@ -34,6 +34,7 @@
import
Menu
from
'
ant-design-vue/es/menu
'
;
import
Icon
from
'
ant-design-vue/es/icon
'
;
import
{
getUserInfo
}
from
'
@/utils
'
;
import
{
mapState
}
from
'
vuex
'
;
const
{
Item
,
SubMenu
}
=
Menu
;
...
...
@@ -67,24 +68,21 @@ export default {
sOpenKeys
:
[],
cachedOpenKeys
:
[],
menuData
:
[],
allMenuList
:
[],
};
},
computed
:
{
...
mapState
(
'
settingModule
'
,
[
'
layout
'
]),
menuTheme
()
{
return
this
.
theme
==
'
light
'
?
this
.
theme
:
'
dark
'
;
},
},
created
()
{
const
{
menuList
}
=
getUserInfo
();
// 增加查询速度
this
.
menuData
=
menuList
.
filter
(
i
=>
i
.
menuType
===
'
MENU
'
);
this
.
allMenuList
=
menuList
;
this
.
updateMenu
();
// // 自定义国际化配置
// if (this.i18n && this.i18n.messages) {
// const messages = this.i18n.messages;
// Object.keys(messages).forEach(key => {
// this.$i18n.mergeLocaleMessage(key, messages[key]);
// });
// }
},
watch
:
{
i18n
(
val
)
{
...
...
@@ -103,9 +101,6 @@ export default {
this
.
sOpenKeys
=
this
.
cachedOpenKeys
;
}
},
$route
:
function
()
{
this
.
updateMenu
();
},
sOpenKeys
(
val
)
{
this
.
$emit
(
'
openChange
'
,
val
);
this
.
$emit
(
'
update:openKeys
'
,
val
);
...
...
@@ -123,17 +118,37 @@ export default {
}
return
!
icon
||
icon
==
'
none
'
?
null
:
h
(
Icon
,
{
props
:
{
type
:
icon
}
});
},
renderMenuItem
(
h
,
menu
)
{
const
tag
=
'
router-link
'
;
const
config
=
{
props
:
{
to
:
menu
.
path
},
props
:
{
to
:
menu
.
menuUrl
},
attrs
:
{
style
:
'
overflow:hidden;white-space:normal;text-overflow:clip;
'
},
};
// 菜单
if
(
menu
.
menuType
===
'
MENU
'
)
{
return
h
(
Item
,
{
key
:
menu
.
menuId
},
[
h
(
tag
,
config
,
[
this
.
renderIcon
(
h
,
menu
.
menuIcon
,
menu
.
menuId
),
menu
.
menuName
]),
]);
}
return
h
(
Item
,
{
key
:
menu
.
menuId
},
[
h
(
tag
,
config
,
[
this
.
renderIcon
(
h
,
menu
.
menuIcon
,
menu
.
menuId
),
menu
.
menuName
]),
h
(
'
a
'
,
{
attrs
:
{
style
:
'
overflow:hidden;white-space:normal;text-overflow:clip;
'
},
on
:
{
click
(
e
)
{
e
.
preventDefault
();
},
},
},
[
this
.
renderIcon
(
h
,
menu
.
menuIcon
,
menu
.
menuId
),
menu
.
menuName
],
),
]);
},
renderSubMenu
(
h
,
menu
)
{
const
subItem
=
[
h
(
...
...
@@ -146,52 +161,63 @@ export default {
),
];
const
itemArr
=
[];
menu
.
children
.
forEach
(
item
=>
{
itemArr
.
push
(
this
.
renderItem
(
h
,
item
));
});
return
h
(
SubMenu
,
{
key
:
menu
.
menuId
},
subItem
.
concat
(
itemArr
));
},
renderItem
(
h
,
menu
)
{
return
menu
.
children
&&
menu
.
children
.
length
?
this
.
renderSubMenu
(
h
,
menu
)
:
this
.
renderMenuItem
(
h
,
menu
);
},
renderMenu
(
h
,
menuTree
)
{
let
menuArr
=
[];
menuTree
.
forEach
((
menu
,
i
)
=>
{
const
menuArr
=
[];
menuTree
.
forEach
(
menu
=>
{
menuArr
.
push
(
this
.
renderItem
(
h
,
menu
));
});
return
menuArr
;
},
updateMenu
()
{
this
.
selectedKeys
=
this
.
getSelectedKey
(
this
.
$route
);
this
.
selectedKeys
=
this
.
getSelectedKey
();
// 混合模式 不管
if
(
this
.
layout
!==
'
mix
'
)
this
.
sOpenKeys
=
this
.
getOpenKeysByPath
(
this
.
$route
.
path
);
},
// let openKeys = matchedRoutes.map(item => item.path);
getSelectedKey
()
{
const
{
path
}
=
this
.
$route
;
let
routeToMenu
=
this
.
menuData
.
find
(
m
=>
m
.
menuUrl
===
path
);
// openKeys = openKeys.slice(0, openKeys.length - 1)
;
if
(
this
.
layout
!==
'
mix
'
)
return
[
routeToMenu
.
menuId
]
;
// if (!fastEqual(openKeys, this.sOpenKeys)) {
// this.collapsed || this.mode === 'horizontal'
// ? (this.cachedOpenKeys = openKeys)
// : (this.sOpenKeys = openKeys);
// }
this
.
sOpenKeys
=
this
.
getOpenKeysByPath
(
this
.
$route
.
path
);
},
getSelectedKey
(
route
)
{
const
{
path
}
=
route
;
return
[
this
.
menuData
.
find
(
m
=>
m
.
menuUrl
===
path
).
menuId
];
// 说明这是头部菜单
if
(
this
.
mode
===
'
horizontal
'
)
{
let
parentMenuId
=
routeToMenu
.
parentMenuId
;
while
(
parentMenuId
!==
0
)
{
routeToMenu
=
this
.
allMenuList
.
find
(
m
=>
m
.
menuId
===
parentMenuId
);
parentMenuId
=
routeToMenu
.
parentMenuId
;
}
return
[
routeToMenu
.
menuId
];
}
return
[];
},
getOpenKeysByPath
(
path
)
{
const
{
menuList
}
=
getUserInfo
();
const
{
parentMenuId
}
=
this
.
menuData
.
find
(
m
=>
m
.
menuUrl
===
path
);
if
(
parentMenuId
===
0
)
return
[];
const
parentMenus
=
[
parentMenuId
];
const
res
=
[
parentMenuId
];
while
(
parentMenus
.
length
)
{
const
menuId
=
parentMenus
.
pop
();
const
parentMenu
=
m
enuList
.
find
(
m
=>
m
.
menuId
===
menuId
);
const
parentMenu
=
this
.
allM
enuList
.
find
(
m
=>
m
.
menuId
===
menuId
);
if
(
parentMenu
.
parentMenuId
!==
0
)
{
parentMenus
.
push
(
parentMenu
.
parentMenuId
);
res
.
push
(
parentMenu
.
parentMenuId
);
...
...
@@ -215,7 +241,7 @@ export default {
this
.
sOpenKeys
=
val
;
},
click
:
obj
=>
{
obj
.
selectedKeys
=
[
obj
.
key
];
this
.
selectedKeys
=
[
obj
.
key
];
this
.
$emit
(
'
select
'
,
obj
);
},
},
...
...
src/components/page/PageHeader.vue
View file @
0b042bcf
<
template
>
<div
:class=
"['page-header', layout, pageWidth]"
>
<a-breadcrumb>
<a-breadcrumb-item
:key=
"index"
v-for=
"(item, index) in breadcrumb"
>
<span>
{{
item
}}
</span>
</a-breadcrumb-item>
</a-breadcrumb>
</div>
<a-breadcrumb
style=
"margin: 10px 0"
>
<a-breadcrumb-item
:key=
"index"
v-for=
"(item, index) in breadcrumb"
>
<span>
{{
item
}}
</span>
</a-breadcrumb-item>
</a-breadcrumb>
</
template
>
<
script
>
import
{
mapState
}
from
'
vuex
'
;
export
default
{
name
:
'
PageHeader
'
,
props
:
{
title
:
{
type
:
[
String
,
Boolean
],
required
:
false
,
},
breadcrumb
:
{
type
:
Array
,
required
:
false
,
},
logo
:
{
type
:
String
,
required
:
false
,
},
avatar
:
{
type
:
String
,
required
:
false
,
},
},
computed
:
{
...
mapState
(
'
settingModule
'
,
[
'
layout
'
,
'
showPageTitle
'
,
'
pageWidth
'
]),
},
};
</
script
>
<
style
lang=
"less"
scoped
>
.page-header {
padding: 16px 24px;
&.head.fixed {
margin: auto;
max-width: 1400px;
}
}
</
style
>
src/main.js
View file @
0b042bcf
...
...
@@ -8,7 +8,7 @@ import VueI18n from 'vue-i18n';
import
{
accountModule
,
settingModule
}
from
'
./pages/frame/store
'
;
import
App
from
'
./App.vue
'
;
import
Plugins
from
'
./plugins
'
;
import
{
load
Routes
,
load
Guards
,
setAppOptions
}
from
'
./utils/routerUtil
'
;
import
{
loadGuards
,
setAppOptions
}
from
'
./utils/routerUtil
'
;
import
guards
from
'
./router/guards
'
;
import
{
loadResponseInterceptor
}
from
'
./utils/requestUtil
'
;
import
langUtils
from
'
@/utils/langUtils
'
;
...
...
@@ -56,8 +56,6 @@ Vue.use(Plugins);
//设置应用配置
setAppOptions
({
router
,
store
,
i18n
});
// 加载路由
loadRoutes
();
// 加载路由守卫
loadGuards
(
guards
,
{
router
,
store
,
i18n
,
message
:
Vue
.
prototype
.
$message
});
...
...
src/pages/frame/components/header/LayoutTopHeader.vue
View file @
0b042bcf
<
template
>
<a-layout-header
:class=
"[headerTheme, 'admin-header']"
>
<div
:class=
"['admin-header-wide', layout, pageWidth]"
>
<router-link
v-if=
"isMobile || layout === 'head'"
to=
"/"
:class=
"['logo', isMobile ? null : 'pc', headerTheme]"
>
<router-link
v-if=
"isMobile || layout === 'head'"
to=
"/"
:class=
"['logo', isMobile ? null : 'pc', headerTheme]"
>
<img
width=
"32"
src=
"@/assets/img/logo.png"
/>
<h1
v-if=
"!isMobile"
>
{{
systemName
}}
</h1>
<h1
v-if=
"!isMobile"
>
{{
systemName
}}
</h1>
</router-link>
<a-icon
v-if=
"showToggleCollapse"
class=
"trigger"
:type=
"collapsed ? 'menu-unfold' : 'menu-fold'"
@
click=
"toggleCollapse"
/>
<div
v-if=
"layout !== 'side' && !isMobile"
class=
"admin-header-menu"
:style=
"`width: $
{menuWidth};`">
<i-menu
class=
"head-menu"
:theme=
"headerTheme"
mode=
"horizontal"
:options=
"menuData"
@
select=
"onSelect"
/>
<a-icon
v-if=
"showToggleCollapse"
class=
"trigger"
:type=
"collapsed ? 'menu-unfold' : 'menu-fold'"
@
click=
"toggleCollapse"
/>
<div
v-if=
"layout !== 'side' && !isMobile"
class=
"admin-header-menu"
:style=
"`width: $
{menuWidth};`"
>
<i-menu
class=
"head-menu"
:theme=
"headerTheme"
mode=
"horizontal"
:options=
"menuData"
@
select=
"onSelect"
/>
</div>
<div
:class=
"['admin-header-right', headerTheme]"
>
<a-tooltip
class=
"header-item"
title=
"帮助文档"
placement=
"bottom"
>
<a
href=
"https://iczer.gitee.io/vue-antd-admin-docs/"
target=
"_blank"
>
<a-icon
type=
"question-circle-o"
/>
</a>
</a-tooltip>
<layout-top-header-notice
class=
"header-item"
/>
<layout-top-header-avatar
class=
"header-item"
/>
<layout-top-header-lang
class=
"header-item"
/>
</div>
</div>
</a-layout-header>
...
...
@@ -37,8 +50,7 @@ export default {
components
:
{
IMenu
,
LayoutTopHeaderAvatar
,
LayoutTopHeaderNotice
,
LayoutTopHeaderLang
},
props
:
[
'
collapsed
'
,
'
menuData
'
],
data
()
{
return
{
};
return
{};
},
computed
:
{
...
mapState
(
'
settingModule
'
,
[
'
theme
'
,
'
isMobile
'
,
'
layout
'
,
'
systemName
'
,
'
pageWidth
'
]),
...
...
@@ -54,14 +66,13 @@ export default {
const
extraWidth
=
searchActive
?
'
600px
'
:
'
400px
'
;
return
`calc(
${
headWidth
}
-
${
extraWidth
}
)`
;
},
showToggleCollapse
(){
if
(
this
.
layout
!==
'
head
'
&&
this
.
isMobile
===
false
)
{
showToggleCollapse
()
{
if
(
this
.
layout
!==
'
head
'
&&
this
.
isMobile
===
false
)
{
return
true
;
}
else
{
}
else
{
return
false
;
}
}
}
,
},
methods
:
{
toggleCollapse
()
{
...
...
src/pages/frame/components/header/LayoutTopHeaderLang.vue
View file @
0b042bcf
...
...
@@ -10,6 +10,7 @@
</
template
>
<
script
>
import
{
setUserInfoByRequest
}
from
'
@/utils
'
;
import
langUtils
from
'
@/utils/langUtils
'
;
export
default
{
...
...
@@ -27,8 +28,9 @@ export default {
},
},
methods
:
{
change
({
key
})
{
async
change
({
key
})
{
langUtils
.
set
(
key
);
await
setUserInfoByRequest
();
window
.
location
.
reload
();
},
},
...
...
src/pages/frame/components/setting/Setting.vue
View file @
0b042bcf
<
template
>
<div
class=
"side-setting
"
>
<div
:class=
"$style['side-setting']
"
>
<setting-item>
<a-button
@
click=
"saveSetting"
type=
"primary"
icon=
"save"
>
{{
$t
(
'
save
'
)
}}
</a-button>
<a-button
@
click=
"resetSetting"
type=
"dashed"
icon=
"redo"
style=
"float: right"
>
...
...
@@ -306,7 +306,7 @@ export default {
};
</
script
>
<
style
lang=
"less"
scoped
>
<
style
lang=
"less"
module
>
.side-setting {
min-height: 100%;
background-color: @base-bg-color;
...
...
@@ -315,9 +315,7 @@ export default {
line-height: 1.5;
word-wrap: break-word;
position: relative;
.flex {
display: flex;
}
.select-item {
width: 80px;
}
...
...
src/pages/frame/components/setting/SettingItem.vue
View file @
0b042bcf
<
template
>
<div
class=
"setting-item
"
>
<h3
v-if=
"title"
class=
"title"
>
{{
title
}}
</h3>
<slot
></slot
>
<div
:class=
"$style['setting-item']
"
>
<h3
v-if=
"title"
:class=
"$style.title"
>
{{
title
}}
</h3>
<slot
/
>
</div>
</
template
>
...
...
@@ -12,14 +12,14 @@ export default {
};
</
script
>
<
style
lang=
"less"
scoped
>
<
style
lang=
"less"
module
>
.setting-item {
margin-bottom: 24px;
.title {
font-size: 14px;
color: @title-color
;
line-height: 22px
;
margin-bottom: 1
2px;
}
}
.title {
font-size: 14px
;
color: @title-color
;
line-height: 2
2px;
margin-bottom: 12px;
}
</
style
>
src/pages/frame/components/tab/LayoutTabsHeader.vue
View file @
0b042bcf
<
template
>
<div
:class=
"['tabs-head', layout, pageWidth]"
>
<a-tabs
type=
"editable-card"
:class=
"[
'tabs-container',
layout,
pageWidth,
{ affixed: affixed, 'fixed-header': fixedHeader, collapsed: adminLayout.collapsed },
]"
:active-key="active"
:hide-add="true"
>
<a-tooltip
placement=
"left"
:title=
"lockTitle"
slot=
"tabBarExtraContent"
>
<a-tabs
type=
"editable-card"
:active-key=
"active"
:hide-add=
"true"
>
<a-tooltip
placement=
"left"
:title=
"lockTitle"
slot=
"tabBarExtraContent"
>
<a-icon
theme=
"filled"
@
click=
"onLockClick"
class=
"header-lock"
:type=
"fixedTabs ? 'lock' : 'unlock'"
/>
</a-tooltip>
<a-tab-pane
v-for=
"page in pageList"
:key=
"page.fullPath"
>
<div
slot=
"tab"
class=
"tab"
@
contextmenu=
"e => onContextmenu(page.fullPath, e)"
>
<a-icon
theme=
"filled"
@
click=
"onLockClick"
class=
"header-lock"
:type=
"fixedTabs ? 'lock' : 'unlock'"
@
click=
"onRefresh(page)"
:class=
"['icon-sync',
{ hide: page.fullPath !== active
&&
!page.loading }]"
:type="page.loading ? 'loading' : 'sync'"
/>
</a-tooltip>
<a-tab-pane
v-for=
"page in pageList"
:key=
"page.fullPath"
>
<div
slot=
"tab"
class=
"tab"
@
contextmenu=
"e => onContextmenu(page.fullPath, e)"
>
<a-icon
@
click=
"onRefresh(page)"
:class=
"['icon-sync',
{ hide: page.fullPath !== active
&&
!page.loading }]"
:type="page.loading ? 'loading' : 'sync'"
/>
<div
class=
"title"
@
click=
"onTabClick(page.fullPath)"
>
{{
pageName
(
page
)
}}
</div>
<a-icon
v-if=
"!page.unclose"
@
click=
"onClose(page.fullPath)"
class=
"icon-close"
type=
"close"
/>
</div>
</a-tab-pane>
</a-tabs>
<div
v-if=
"affixed"
class=
"virtual-tabs"
></div>
</div>
<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>
</
template
>
<
script
>
import
{
mapState
,
mapMutations
}
from
'
vuex
'
;
import
{
getI18nKey
}
from
'
@/utils/routerUtil
'
;
import
layoutTabHeaderI18n
from
'
./i18n
'
;
import
{
getUserInfo
}
from
'
@/utils
'
;
export
default
{
name
:
'
LayoutTabsHeader
'
,
...
...
@@ -56,18 +43,24 @@ export default {
data
()
{
return
{
affixed
:
false
,
menuList
:
[],
};
},
inject
:
[
'
adminLayout
'
],
created
()
{
this
.
affixed
=
this
.
fixedTabs
;
const
{
menuList
}
=
getUserInfo
();
this
.
menuList
=
menuList
.
filter
(
i
=>
i
.
menuType
===
'
MENU
'
);
},
computed
:
{
...
mapState
(
'
settingModule
'
,
[
'
layout
'
,
'
pageWidth
'
,
'
fixedHeader
'
,
'
fixedTabs
'
,
'
customTitles
'
]),
lockTitle
()
{
return
this
.
$t
(
this
.
fixedTabs
?
'
unlock
'
:
'
lock
'
);
},
},
methods
:
{
...
mapMutations
(
'
settingModule
'
,
[
'
setFixedTabs
'
]),
onLockClick
()
{
...
...
@@ -96,8 +89,7 @@ export default {
},
pageName
(
page
)
{
const
pagePath
=
page
.
fullPath
.
split
(
'
?
'
)[
0
];
const
custom
=
this
.
customTitles
.
find
(
item
=>
item
.
path
===
pagePath
);
return
(
custom
&&
custom
.
title
)
||
page
.
title
||
this
.
$t
(
getI18nKey
(
page
.
keyPath
));
return
this
.
menuList
.
find
(
m
=>
m
.
menuUrl
===
pagePath
)?.
menuName
;
},
},
};
...
...
@@ -113,6 +105,8 @@ export default {
.title {
display: inline-block;
height: 100%;
user-select: none;
-webkit-user-select: none;
}
.icon-close {
font-size: 12px;
...
...
@@ -136,54 +130,4 @@ export default {
}
}
}
.tabs-head {
margin: 0 auto;
&.head.fixed {
width: 1400px;
}
}
.tabs-container {
margin: -16px auto 8px;
transition: top, left 0.2s;
.header-lock {
font-size: 18px;
cursor: pointer;
color: @primary-3;
&:hover {
color: @primary-color;
}
}
&.affixed {
margin: 0 auto;
top: 0px;
padding: 8px 24px 0;
position: fixed;
height: 48px;
z-index: 1;
background-color: @layout-body-background;
&.side,
&.mix {
right: 0;
left: 256px;
&.collapsed {
left: 80px;
}
}
&.head {
width: inherit;
padding: 8px 0 0;
&.fluid {
left: 0;
right: 0;
padding: 8px 24px 0;
}
}
&.fixed-header {
top: 64px;
}
}
}
.virtual-tabs {
height: 48px;
}
</
style
>
src/pages/frame/layouts/AdminLayout.vue
View file @
0b042bcf
...
...
@@ -39,6 +39,7 @@
:menuData="headMenuData"
:collapsed="collapsed"
@toggleCollapse="toggleCollapse"
@menuSelect="menuSelect"
/>
<a-layout-header
:class=
"[
...
...
@@ -46,18 +47,16 @@
{ 'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage },
]"
v-show="fixedHeader"
>
</a-layout-header>
<a-layout-content
class=
"admin-layout-content"
:style=
"`min-height: $
{minHeight}px;`">
<div
style=
"position: relative"
>
<slot></slot>
</div>
/>
<a-layout-content
class=
"admin-layout-content"
>
<slot
/>
</a-layout-content>
</a-layout>
</a-layout>
</
template
>
<
script
>
import
LayoutTopHeader
from
'
../components/header/LayoutTopHeader
'
;
import
LayoutTopHeader
from
'
../components/header/LayoutTopHeader
.vue
'
;
import
Drawer
from
'
@/components/tool/Drawer
'
;
import
SideMenu
from
'
@/components/menu/SideMenu.vue
'
;
import
Setting
from
'
../components/setting/Setting
'
;
...
...
@@ -76,6 +75,9 @@ export default {
showSetting
:
false
,
drawerOpen
:
false
,
menuData
:
[],
firstMenu
:
[],
subMenu
:
[],
menuList
:
[],
};
},
provide
()
{
...
...
@@ -84,17 +86,14 @@ export default {
};
},
watch
:
{
$route
(
val
)
{
this
.
setActivated
(
val
);
},
layout
()
{
this
.
setActivated
(
this
.
$route
);
},
isMobile
(
val
)
{
if
(
!
val
)
{
this
.
drawerOpen
=
false
;
}
},
layout
(
layout
)
{
this
.
updateSildeMenu
(
layout
);
},
},
computed
:
{
...
mapState
(
'
settingModule
'
,
[
...
...
@@ -110,8 +109,6 @@ export default {
'
multiPage
'
,
]),
...
mapGetters
(
'
settingModule
'
,
[
'
firstMenu
'
,
'
subMenu
'
]),
hideSettingExtend
()
{
if
(
this
.
hideSetting
===
false
&&
process
.
env
.
NODE_ENV
===
'
development
'
)
{
return
false
;
...
...
@@ -131,41 +128,63 @@ export default {
},
headMenuData
()
{
const
{
layout
,
menuData
,
firstMenu
}
=
this
;
console
.
log
(
'
first
'
,
firstMenu
);
return
layout
===
'
mix
'
?
firstMenu
:
menuData
;
},
sideMenuData
()
{
const
{
layout
,
menuData
,
subMenu
}
=
this
;
console
.
log
(
'
sub
'
,
subMenu
);
return
layout
===
'
mix
'
?
subMenu
:
menuData
;
},
},
methods
:
{
...
mapMutations
(
'
settingModule
'
,
[
'
correctPageMinHeight
'
,
'
setActivatedFirst
'
]),
...
mapMutations
(
'
settingModule
'
,
[
'
correctPageMinHeight
'
]),
toggleCollapse
()
{
this
.
collapsed
=
!
this
.
collapsed
;
},
onMenuSelect
()
{
this
.
toggleCollapse
();
},
setActivated
(
route
)
{
if
(
this
.
layout
===
'
mix
'
)
{
let
matched
=
route
.
matched
;
matched
=
matched
.
slice
(
0
,
matched
.
length
-
1
);
const
{
firstMenu
}
=
this
;
for
(
let
menu
of
firstMenu
)
{
if
(
matched
.
findIndex
(
item
=>
item
.
path
===
menu
.
fullPath
)
!==
-
1
)
{
this
.
setActivatedFirst
(
menu
.
fullPath
);
break
;
}
updateSildeMenu
(
layout
)
{
if
(
layout
===
'
mix
'
)
{
const
{
path
}
=
this
.
$route
;
let
currentMenu
=
this
.
menuList
.
find
(
i
=>
i
.
menuUrl
===
path
);
let
parentMenuId
=
currentMenu
.
parentMenuId
;
while
(
parentMenuId
!==
0
)
{
currentMenu
=
this
.
menuList
.
find
(
i
=>
i
.
menuId
===
parentMenuId
);
parentMenuId
=
currentMenu
.
parentMenuId
;
}
this
.
subMenu
=
currentMenu
.
children
||
[];
}
},
menuSelect
(
obj
)
{
console
.
log
(
'
切换
'
,
obj
);
// 拿到 菜单id
const
menuId
=
obj
.
key
;
const
currentMenu
=
this
.
menuList
.
find
(
i
=>
i
.
menuId
===
menuId
);
if
(
currentMenu
.
menuType
===
'
MENU
'
)
{
this
.
subMenu
=
[];
return
;
}
this
.
subMenu
=
currentMenu
.
children
||
[];
},
},
created
()
{
this
.
correctPageMinHeight
(
this
.
minHeight
-
24
);
this
.
setActivated
(
this
.
$route
);
cons
t
userInfo
=
getUserInfo
(
);
const
menuData
=
convertListToTree
(
userInfo
?.
menuList
||
[],
false
,
true
);
const
{
menuList
}
=
getUserInfo
(
);
cons
ole
.
log
(
menuList
);
const
menuData
=
convertListToTree
(
menuList
||
[],
false
,
true
);
this
.
menuData
=
menuData
;
this
.
menuList
=
menuList
;
this
.
firstMenu
=
JSON
.
parse
(
JSON
.
stringify
(
menuData
)).
map
(
i
=>
{
delete
i
.
children
;
return
i
;
});
},
beforeDestroy
()
{
this
.
correctPageMinHeight
(
-
this
.
minHeight
+
24
);
...
...
@@ -195,6 +214,9 @@ export default {
}
}
.admin-layout-main {
display: flow-root;
position: initial;
.admin-header {
top: 0;
right: 0;
...
...
src/pages/frame/layouts/CommonLayout.vue
View file @
0b042bcf
<
template
>
<div
class=
"common-layout"
>
<div
class=
"content"
>
<slot
></slot
>
<slot
/
>
</div>
</div>
</
template
>
<
script
>
import
{
mapState
}
from
'
vuex
'
;
export
default
{
name
:
'
CommonLayout
'
,
computed
:
{
...
mapState
(
'
settingModule
'
,
[
'
footerLinks
'
,
'
copyright
'
]),
},
};
</
script
>
<
style
scoped
lang=
"less"
>
.common-layout {
display: flex;
...
...
src/pages/frame/layouts/PageLayout.vue
View file @
0b042bcf
...
...
@@ -4,12 +4,11 @@
ref=
"pageHeader"
:style=
"`margin-top: $
{multiPage ? 0 : -24}px`"
:breadcrumb="breadcrumb"
:title="pageTitle"
:logo="logo"
:avatar="avatar"
>
<slot
name=
"action"
slot=
"action"
></slot
>
<slot
slot=
"content"
name=
"headerContent"
></slot
>
<slot
name=
"action"
slot=
"action"
/
>
<slot
slot=
"content"
name=
"headerContent"
/
>
<div
slot=
"content"
v-if=
"!this.$slots.headerContent && desc"
>
<p>
{{
desc
}}
</p>
<div
v-if=
"this.linkList"
class=
"link"
>
...
...
@@ -20,16 +19,15 @@
</div>
<slot
v-if=
"this.$slots.extra"
slot=
"extra"
name=
"extra"
></slot>
</page-header>
<div
ref=
"page"
:class=
"['page-content', layout, pageWidth]"
>
<slot></slot>
</div>
<slot
/>
</div>
</template>
<
script
>
import
PageHeader
from
'
@/components/page/PageHeader
'
;
import
PageHeader
from
'
@/components/page/PageHeader
.vue
'
;
import
{
mapState
,
mapMutations
}
from
'
vuex
'
;
import
{
getI18nKey
}
from
'
@/utils/routerUtil
'
;
import
{
getUserInfo
}
from
'
@/utils
'
;
export
default
{
name
:
'
PageLayout
'
,
...
...
@@ -61,33 +59,17 @@ export default {
this
.
updatePageHeight
();
},
created
()
{
const
{
menuList
}
=
getUserInfo
();
this
.
menuList
=
menuList
;
this
.
page
=
this
.
$route
.
meta
.
page
;
},
beforeDestroy
()
{
this
.
updatePageHeight
(
0
);
},
computed
:
{
...
mapState
(
'
settingModule
'
,
[
'
layout
'
,
'
multiPage
'
,
'
pageMinHeight
'
,
'
pageWidth
'
,
'
customTitles
'
]),
pageTitle
()
{
let
pageTitle
=
this
.
page
&&
this
.
page
.
title
;
return
this
.
customTitle
||
(
pageTitle
&&
this
.
$t
(
pageTitle
))
||
this
.
title
||
this
.
routeName
;
},
routeName
()
{
const
route
=
this
.
$route
;
return
this
.
$t
(
getI18nKey
(
route
.
matched
[
route
.
matched
.
length
-
1
].
path
));
},
...
mapState
(
'
settingModule
'
,
[
'
layout
'
,
'
multiPage
'
,
'
pageMinHeight
'
,
'
pageWidth
'
]),
breadcrumb
()
{
let
page
=
this
.
page
;
let
breadcrumb
=
page
&&
page
.
breadcrumb
;
if
(
breadcrumb
)
{
let
i18nBreadcrumb
=
[];
breadcrumb
.
forEach
(
item
=>
{
i18nBreadcrumb
.
push
(
this
.
$t
(
item
));
});
return
i18nBreadcrumb
;
}
else
{
return
this
.
getRouteBreadcrumb
();
}
return
this
.
getRouteBreadcrumb
();
},
marginCorrect
()
{
return
this
.
multiPage
?
24
:
0
;
...
...
@@ -96,18 +78,14 @@ export default {
methods
:
{
...
mapMutations
(
'
settingModule
'
,
[
'
correctPageMinHeight
'
]),
getRouteBreadcrumb
()
{
let
routes
=
this
.
$route
.
matched
;
const
path
=
this
.
$route
.
path
;
let
breadcrumb
=
[];
routes
.
filter
(
item
=>
path
.
includes
(
item
.
path
))
.
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
;
const
currentMenu
=
this
.
menuList
.
find
(
m
=>
m
.
menuUrl
===
path
);
let
parentMenuId
=
currentMenu
.
parentMenuId
;
const
breadcrumb
=
[
currentMenu
.
menuName
];
while
(
parentMenuId
!==
0
)
{
const
parentMenu
=
this
.
menuList
.
find
(
m
=>
m
.
menuId
===
parentMenuId
);
breadcrumb
.
unshift
(
parentMenu
.
menuName
);
parentMenuId
=
parentMenu
.
parentMenuId
;
}
return
breadcrumb
;
},
...
...
@@ -135,12 +113,4 @@ export default {
}
}
}
.page-content {
position: relative;
&.head.fixed {
margin: 0 auto;
max-width: 1400px;
}
}
</
style
>
src/pages/frame/store/settingModule.js
View file @
0b042bcf
...
...
@@ -42,14 +42,6 @@ export default {
return
menuItem
;
});
},
subMenu
(
state
)
{
const
{
menuData
,
activatedFirst
}
=
state
;
if
(
menuData
.
length
>
0
&&
!
menuData
[
0
].
fullPath
)
{
formatFullPath
(
menuData
);
}
const
current
=
menuData
.
find
(
menu
=>
menu
.
fullPath
===
activatedFirst
);
return
(
current
&&
current
.
children
)
||
[];
},
},
mutations
:
{
setDevice
(
state
,
isMobile
)
{
...
...
@@ -59,6 +51,7 @@ export default {
state
.
theme
=
theme
;
},
setLayout
(
state
,
layout
)
{
console
.
log
(
layout
);
state
.
layout
=
layout
;
},
setMultiPage
(
state
,
multiPage
)
{
...
...
src/pages/frame/view/template/TabsTemplateView.vue
View file @
0b042bcf
...
...
@@ -10,15 +10,10 @@
@
refresh=
"refresh"
@
contextmenu=
"onContextmenu"
/>
<div
:class=
"['tabs-view-content', layout, pageWidth]"
:style=
"`margin-top: $
{multiPage ? -20 : 0}px`"
>
<a-keep-alive
:exclude-keys=
"excludeKeys"
v-if=
"multiPage && cachePage"
v-model=
"clearCaches"
>
<router-view
v-if=
"!refreshing"
ref=
"tabContent"
:key=
"$route.fullPath"
/>
</a-keep-alive>
<router-view
ref=
"tabContent"
v-else-if=
"!refreshing"
/>
</div>
<a-keep-alive
:exclude-keys=
"excludeKeys"
v-if=
"multiPage && cachePage"
v-model=
"clearCaches"
>
<router-view
v-if=
"!refreshing"
ref=
"tabContent"
:key=
"$route.fullPath"
/>
</a-keep-alive>
<router-view
ref=
"tabContent"
v-else-if=
"!refreshing"
/>
</admin-layout>
</
template
>
...
...
@@ -28,7 +23,7 @@ import Contextmenu from '@/components/menu/Contextmenu.vue';
import
{
mapState
,
mapMutations
}
from
'
vuex
'
;
import
{
getI18nKey
}
from
'
@/utils/routerUtil
'
;
import
AKeepAlive
from
'
@/components/cache/AKeepAlive
'
;
import
LayoutTabsHeader
from
'
../../components/tab/LayoutTabsHeader
'
;
import
LayoutTabsHeader
from
'
../../components/tab/LayoutTabsHeader
.vue
'
;
import
templateI18n
from
'
./i18n
'
;
export
default
{
...
...
src/router/config.js
View file @
0b042bcf
...
...
@@ -24,7 +24,6 @@ const hasAuthorityRoutes = [
path
:
'
/
'
,
component
:
TabsTemplateView
,
redirect
:
'
/home
'
,
name
:
'
首页
'
,
children
:
[
{
path
:
'
home
'
,
...
...
@@ -59,12 +58,6 @@ const hasAuthorityRoutes = [
{
path
:
'
system
'
,
name
:
'
系统管理
'
,
meta
:
{
icon
:
'
setting
'
,
page
:
{
cacheAble
:
true
,
},
},
component
:
PageTemplateView
,
children
:
[
{
...
...
src/router/guards.js
View file @
0b042bcf
...
...
@@ -57,43 +57,11 @@ const authorityGuard = (to, from, next, options) => {
if
(
!
hasAuthority
(
to
))
{
message
.
warning
(
`对不起,您无权访问页面:
${
to
.
fullPath
}
,请联系管理员`
);
next
({
path
:
'
/403
'
});
// NProgress.done()
}
else
{
next
();
}
};
/**
* 混合导航模式下一级菜单跳转重定向
* @param to
* @param from
* @param next
* @param options
* @returns {*}
*/
const
redirectGuard
=
(
to
,
from
,
next
,
options
)
=>
{
const
{
store
}
=
options
;
const
getFirstChild
=
routes
=>
{
const
route
=
routes
[
0
];
if
(
!
route
.
children
||
route
.
children
.
length
===
0
)
{
return
route
;
}
return
getFirstChild
(
route
.
children
);
};
if
(
store
.
state
.
settingModule
.
layout
===
'
mix
'
)
{
const
firstMenu
=
store
.
getters
[
'
settingModule/firstMenu
'
];
if
(
firstMenu
.
find
(
item
=>
item
.
fullPath
===
to
.
fullPath
))
{
store
.
commit
(
'
settingModule/setActivatedFirst
'
,
to
.
fullPath
);
const
subMenu
=
store
.
getters
[
'
settingModule/subMenu
'
];
if
(
subMenu
.
length
>
0
)
{
const
redirect
=
getFirstChild
(
subMenu
);
return
next
({
path
:
redirect
.
fullPath
});
}
}
}
next
();
};
/**
* 进度条结束
* @param to
...
...
@@ -106,6 +74,6 @@ const progressDone = () => {
};
export
default
{
beforeEach
:
[
progressStart
,
loginGuard
,
authorityGuard
,
redirectGuard
],
beforeEach
:
[
progressStart
,
loginGuard
,
authorityGuard
],
afterEach
:
[
progressDone
],
};
src/router/index.js
View file @
0b042bcf
import
syncConfig
from
'
./config
'
;
import
{
formatRoutes
}
from
'
../utils/routerUtil
'
;
// 不需要登录拦截的路由配置
const
loginIgnore
=
{
...
...
@@ -22,7 +21,6 @@ const loginIgnore = {
*/
function
initRouter
()
{
const
options
=
syncConfig
;
formatRoutes
(
options
.
routes
);
return
options
;
}
...
...
src/utils/routerUtil.js
View file @
0b042bcf
...
...
@@ -70,29 +70,6 @@ function parseRoutes(routesConfig, routerMap) {
return
routes
;
}
/**
* 加载路由
* @param routesConfig {RouteConfig[]} 路由配置
*/
function
loadRoutes
(
routesConfig
)
{
// 应用配置
const
{
router
,
store
,
i18n
}
=
appOptions
;
routesConfig
=
store
.
getters
[
'
accountModule/routesConfig
'
];
// 提取路由国际化数据
mergeI18nFromRoutes
(
i18n
,
router
.
options
.
routes
);
// 初始化Admin后台菜单数据
const
rootRoute
=
router
.
options
.
routes
.
find
(
item
=>
item
.
path
===
'
/
'
);
const
menuRoutes
=
rootRoute
&&
rootRoute
.
children
;
if
(
menuRoutes
)
{
store
.
commit
(
'
settingModule/setMenuData
'
,
menuRoutes
);
}
}
/**
* 深度合并路由
* @param target {Route[]}
...
...
@@ -131,57 +108,6 @@ function deepMergeRoutes(target, source) {
return
parseRoutesMap
(
merge
);
}
/**
* 格式化路由
* @param routes 路由配置
*/
function
formatRoutes
(
routes
)
{
routes
.
forEach
(
route
=>
{
const
{
path
}
=
route
;
if
(
!
path
.
startsWith
(
'
/
'
)
&&
path
!==
'
*
'
)
{
route
.
path
=
'
/
'
+
path
;
}
});
formatAuthority
(
routes
);
}
/**
* 格式化路由的权限配置
* @param routes 路由
* @param pAuthorities 父级路由权限配置集合
*/
function
formatAuthority
(
routes
,
pAuthorities
=
[])
{
routes
.
forEach
(
route
=>
{
const
meta
=
route
.
meta
;
const
defaultAuthority
=
pAuthorities
[
pAuthorities
.
length
-
1
]
||
{
permission
:
'
*
'
};
if
(
meta
)
{
let
authority
=
{};
if
(
!
meta
.
authority
)
{
authority
=
defaultAuthority
;
}
else
if
(
typeof
meta
.
authority
===
'
string
'
)
{
authority
.
permission
=
meta
.
authority
;
}
else
if
(
typeof
meta
.
authority
===
'
object
'
)
{
authority
=
meta
.
authority
;
const
{
role
}
=
authority
;
if
(
typeof
role
===
'
string
'
)
{
authority
.
role
=
[
role
];
}
if
(
!
authority
.
permission
&&
!
authority
.
role
)
{
authority
=
defaultAuthority
;
}
}
meta
.
authority
=
authority
;
}
else
{
const
authority
=
defaultAuthority
;
route
.
meta
=
{
authority
};
}
route
.
meta
.
pAuthorities
=
pAuthorities
;
if
(
route
.
children
)
{
formatAuthority
(
route
.
children
,
[...
pAuthorities
,
route
.
meta
.
authority
]);
}
});
}
/**
* 从路由 path 解析 i18n key
* @param path
...
...
@@ -213,13 +139,4 @@ function loadGuards(guards, options) {
});
}
export
{
parseRoutes
,
loadRoutes
,
formatAuthority
,
getI18nKey
,
loadGuards
,
deepMergeRoutes
,
formatRoutes
,
setAppOptions
,
};
export
{
parseRoutes
,
getI18nKey
,
loadGuards
,
deepMergeRoutes
,
setAppOptions
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment