feat: 新增二级菜单子导航系统(SubNav + redirectMenu)
- 新增 SubNav 组件:Navbar 下方水平子导航条,展示当前二级菜单下的三级页面,支持滚动和下拉分组 - 新增 redirectMenu 页面:点击二级菜单时卡片式展示子页面列表 - SidebarItem 深度 >=1 时改为叶子节点渲染(小圆点标记),激活高亮回退到二级菜单 - Navbar 布局重构为 Flexbox,面包屑/SubNav 根据场景切换 - 新增 productionLine Vuex 模块,轧辊研磨页改为 store 方式读取产线数据 - Sidebar activeMenu 逻辑增强:自动定位当前页所属二级菜单
This commit is contained in:
@@ -494,9 +494,9 @@
|
||||
<script>
|
||||
import { listRollInfo, getRollInfo, addRollInfo, updateRollInfo, delRollInfo } from '@/api/mes/roll/rollInfo'
|
||||
import { listRollGrind, addRollGrind, updateRollGrind, delRollGrind, getMonthlyStats } from '@/api/mes/roll/rollGrind'
|
||||
import { listProductionLine } from '@/api/wms/productionLine'
|
||||
import { listData, addData, updateData, delData } from '@/api/system/dict/data'
|
||||
import rollLineMixin from '../rollLineMixin'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'GrindRoom',
|
||||
@@ -504,7 +504,6 @@ export default {
|
||||
dicts: ['mes_roll_operator'],
|
||||
data() {
|
||||
return {
|
||||
productionLines: [],
|
||||
filterLineId: null,
|
||||
lineTabOrder: [],
|
||||
|
||||
@@ -549,6 +548,8 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['productionLines']),
|
||||
|
||||
currentUserName() {
|
||||
return this.$store.state.user.name || this.$store.getters.name || ''
|
||||
},
|
||||
@@ -606,9 +607,7 @@ export default {
|
||||
this.filterLineId = this.lineId
|
||||
const uid = this.$store.state.user?.userId || 0
|
||||
try { this.lineTabOrder = JSON.parse(localStorage.getItem(`grind_line_order_${uid}`) || '[]') } catch { /* ignore */ }
|
||||
listProductionLine({ pageNum: 1, pageSize: 100 }).then(res => {
|
||||
this.productionLines = res.rows || []
|
||||
})
|
||||
this.$store.dispatch('productionLine/getProductionLines')
|
||||
this.loadRolls()
|
||||
},
|
||||
|
||||
|
||||
252
klp-ui/src/views/redirectMenu.vue
Normal file
252
klp-ui/src/views/redirectMenu.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<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)
|
||||
const item = {
|
||||
...c,
|
||||
_ownPath: ownPath,
|
||||
_fullPath: ownPath,
|
||||
_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>
|
||||
.submenu-page {
|
||||
padding: 16px 20px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.submenu-page__header {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.submenu-page__parent-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.submenu-page__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.submenu-page__empty {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.submenu-group {
|
||||
width: calc(50% - 6px);
|
||||
min-width: 280px;
|
||||
background: #fafbfc;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-group__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-group__icon {
|
||||
font-size: 16px;
|
||||
color: var(--current-color, #409EFF);
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.submenu-group__text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.submenu-group__count {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-right: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.submenu-group__arrow {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.submenu-group__items {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.submenu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px 8px 24px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-item__folder {
|
||||
font-size: 13px;
|
||||
color: #e6a23c;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.submenu-item__doc {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.submenu-item__text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.submenu-item__arrow {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
flex-shrink: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.submenu-item--leaf {
|
||||
.submenu-item__doc {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user