统一在SubNav和redirectMenu组件中处理路由的完整路径,支持解析字符串格式的query参数,将其转换为对象格式存入_fullPath字段,保留原有的路径处理逻辑
303 lines
7.0 KiB
Vue
303 lines
7.0 KiB
Vue
<template>
|
|
<div class="submenu-page">
|
|
<div class="submenu-page__header">
|
|
<span class="submenu-page__parent-title">{{ parentTitle }}</span>
|
|
</div>
|
|
<div v-if="tree.length" class="submenu-page__list">
|
|
<div v-for="group in tree" :key="group._ownPath" class="submenu-group">
|
|
<div class="submenu-group__title" @click="goTo(group)">
|
|
<span class="submenu-group__icon">
|
|
<svg-icon v-if="group.meta && group.meta.icon" :icon-class="group.meta.icon" />
|
|
<i v-else class="el-icon-folder-opened" />
|
|
</span>
|
|
<span class="submenu-group__text">{{ group._title }}</span>
|
|
<span v-if="group._hasChildren" class="submenu-group__count">{{ group._children.length }}项</span>
|
|
<i v-if="group._hasChildren" class="el-icon-arrow-right submenu-group__arrow" />
|
|
</div>
|
|
<div v-if="group._hasChildren" class="submenu-group__items">
|
|
<div
|
|
v-for="child in group._children"
|
|
:key="child._ownPath"
|
|
class="submenu-item"
|
|
:class="{ 'submenu-item--leaf': !child._hasChildren }"
|
|
@click="goTo(child)"
|
|
>
|
|
<i v-if="child._hasChildren" class="el-icon-folder submenu-item__folder" />
|
|
<i v-else class="el-icon-document submenu-item__doc" />
|
|
<span class="submenu-item__text">{{ child._title }}</span>
|
|
<i v-if="child._hasChildren" class="el-icon-arrow-right submenu-item__arrow" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="submenu-page__empty">暂无可访问的菜单</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import path from 'path'
|
|
|
|
export default {
|
|
name: 'RedirectMenu',
|
|
computed: {
|
|
parentPath() {
|
|
return this.$route.query.parent || ''
|
|
},
|
|
parentTitle() {
|
|
return this.$route.query.title || this.parentPath
|
|
},
|
|
tree() {
|
|
const sidebarRouters = this.$store.state.permission.sidebarRouters
|
|
const children = this.findChildren(sidebarRouters, this.parentPath, '')
|
|
if (!children) return []
|
|
return this.buildTree(children, this.parentPath)
|
|
}
|
|
},
|
|
methods: {
|
|
findChildren(routes, targetPath, basePath) {
|
|
for (const route of routes) {
|
|
if (route.hidden) continue
|
|
const fullPath = basePath ? path.resolve(basePath, route.path) : route.path
|
|
if (fullPath === targetPath) {
|
|
return route.children || []
|
|
}
|
|
if (route.children && route.children.length > 0) {
|
|
const result = this.findChildren(route.children, targetPath, fullPath)
|
|
if (result) return result
|
|
}
|
|
}
|
|
return null
|
|
},
|
|
buildTree(children, parentPath) {
|
|
return children
|
|
.filter(c => !c.hidden)
|
|
.map(c => {
|
|
const ownPath = c.path.startsWith('/') ? c.path : path.resolve(parentPath, c.path)
|
|
const title = (c.meta && c.meta.title) || c.name || c.path || ''
|
|
const hasChildren = c.children && c.children.length > 0 && c.children.some(gc => !gc.hidden)
|
|
let routeFullPath = ownPath
|
|
if (c.query) {
|
|
try {
|
|
routeFullPath = { path: ownPath, query: JSON.parse(c.query) }
|
|
} catch (e) {
|
|
routeFullPath = ownPath
|
|
}
|
|
}
|
|
const item = {
|
|
...c,
|
|
_ownPath: ownPath,
|
|
_fullPath: routeFullPath,
|
|
_title: title,
|
|
_hasChildren: hasChildren
|
|
}
|
|
if (hasChildren) {
|
|
item._children = this.buildTree(c.children, ownPath)
|
|
}
|
|
return item
|
|
})
|
|
},
|
|
goTo(item) {
|
|
if (item._hasChildren) {
|
|
this.$router.push({
|
|
path: '/redirect/subMenu',
|
|
query: { parent: item._ownPath, title: item._title }
|
|
})
|
|
} else if (item.path) {
|
|
this.$router.push(item._fullPath)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
$c: #5F7BA0;
|
|
|
|
.submenu-page {
|
|
padding: 20px 24px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
// ===== HEADER =====
|
|
|
|
.submenu-page__header {
|
|
margin-bottom: 20px;
|
|
padding: 0 0 12px 0;
|
|
border-bottom: 1px solid #f0f2f5;
|
|
}
|
|
|
|
.submenu-page__parent-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
}
|
|
|
|
// ===== SECTIONS =====
|
|
|
|
.submenu-page__list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 28px;
|
|
}
|
|
|
|
.submenu-page__empty {
|
|
text-align: center;
|
|
padding: 40px 0;
|
|
color: #909399;
|
|
font-size: 13px;
|
|
}
|
|
|
|
// ===== GROUP =====
|
|
|
|
.submenu-group {
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
overflow: visible;
|
|
}
|
|
|
|
.submenu-group__title {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 0 10px 0;
|
|
margin-bottom: 12px;
|
|
cursor: pointer;
|
|
border-bottom: 1px solid #f5f6f8;
|
|
background: transparent;
|
|
transition: border-color 0.15s;
|
|
user-select: none;
|
|
|
|
&:hover {
|
|
border-bottom-color: $c;
|
|
.submenu-group__text { color: $c; }
|
|
.submenu-group__arrow { color: $c; transform: translateX(3px); }
|
|
}
|
|
}
|
|
|
|
.submenu-group__icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 10px;
|
|
background: rgba($c, 0.08);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 10px;
|
|
flex-shrink: 0;
|
|
font-size: 16px;
|
|
color: $c;
|
|
}
|
|
|
|
.submenu-group__text {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
transition: color 0.15s;
|
|
}
|
|
|
|
.submenu-group__count {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
background: #f5f6f8;
|
|
padding: 2px 10px;
|
|
border-radius: 10px;
|
|
margin-right: 8px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.submenu-group__arrow {
|
|
font-size: 13px;
|
|
color: #c0c4cc;
|
|
flex-shrink: 0;
|
|
transition: transform 0.2s, color 0.2s;
|
|
}
|
|
|
|
// ===== TILE GRID =====
|
|
|
|
.submenu-group__items {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
gap: 10px;
|
|
padding: 0;
|
|
background: transparent;
|
|
}
|
|
|
|
// ===== TILE =====
|
|
|
|
.submenu-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 16px 10px 14px;
|
|
cursor: pointer;
|
|
background: #fff;
|
|
border: 1px solid #f0f2f5;
|
|
border-radius: 10px;
|
|
text-align: center;
|
|
transition: border-color 0.15s, box-shadow 0.15s;
|
|
|
|
&:hover {
|
|
border-color: $c;
|
|
box-shadow: 0 2px 10px rgba($c, 0.1);
|
|
.submenu-item__arrow { opacity: 1; }
|
|
}
|
|
|
|
&:active {
|
|
background: #fafbfd;
|
|
}
|
|
}
|
|
|
|
.submenu-item__folder,
|
|
.submenu-item__doc {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 10px;
|
|
flex-shrink: 0;
|
|
font-size: 18px;
|
|
color: $c;
|
|
background: rgba($c, 0.07);
|
|
}
|
|
|
|
.submenu-item__folder {
|
|
color: #e6a23c;
|
|
background: rgba(#e6a23c, 0.08);
|
|
}
|
|
|
|
.submenu-item__text {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: #555;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.submenu-item__arrow {
|
|
font-size: 11px;
|
|
color: #c0c4cc;
|
|
margin-top: 6px;
|
|
opacity: 0;
|
|
transition: opacity 0.15s;
|
|
}
|
|
|
|
.submenu-item--leaf {
|
|
.submenu-item__doc {
|
|
color: #a8abb2;
|
|
background: #f5f6f8;
|
|
}
|
|
}
|
|
</style>
|