Commit d0f828aa by qinjianhui

feat: 调整页面顶部菜单栏

parent 48f06c8d
...@@ -55,6 +55,7 @@ declare module 'vue' { ...@@ -55,6 +55,7 @@ declare module 'vue' {
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline'] ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
...@@ -63,6 +64,7 @@ declare module 'vue' { ...@@ -63,6 +64,7 @@ declare module 'vue' {
ImageView: typeof import('./src/components/ImageView.vue')['default'] ImageView: typeof import('./src/components/ImageView.vue')['default']
LogList: typeof import('./src/components/LogList.vue')['default'] LogList: typeof import('./src/components/LogList.vue')['default']
NavMenu: typeof import('./src/components/NavMenu.vue')['default'] NavMenu: typeof import('./src/components/NavMenu.vue')['default']
NavMenuItem: typeof import('./src/components/NavMenuItem.vue')['default']
RenderColumn: typeof import('./src/components/RenderColumn.vue')['default'] RenderColumn: typeof import('./src/components/RenderColumn.vue')['default']
RightClickMenu: typeof import('./src/components/RightClickMenu.vue')['default'] RightClickMenu: typeof import('./src/components/RightClickMenu.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
......
...@@ -2,39 +2,21 @@ ...@@ -2,39 +2,21 @@
<div> <div>
<div class="nav-menu"> <div class="nav-menu">
<div class="header-logo"> <div class="header-logo">
<img src="../assets/images/factory-logo.png" alt="logo" /> <img
style="vertical-align: middle"
src="../assets/images/factory-logo.png"
alt="logo"
/>
</div> </div>
<!-- 导航栏 --> <!-- 导航栏 -->
<el-menu <nav class="mega-nav">
class="el-menu-demo" <NavMenuItem
mode="horizontal" v-for="item in displayMenuList"
background-color="#001529"
text-color="#fff"
style="border-bottom: none"
:default-active="defaultActive"
router
>
<template v-for="item in menuList">
<el-menu-item
v-if="!item.children"
:key="item.id" :key="item.id"
:index="item.index" :item="item"
>{{ item.label }}</el-menu-item />
> </nav>
<el-sub-menu v-else :key="item.index" :index="item.index">
<template #title>
<span class="label">{{ item.label }}</span>
</template>
<el-menu-item
v-for="sub in item.children"
:key="sub.id"
:index="sub.index"
>{{ sub.label }}</el-menu-item
>
</el-sub-menu>
</template>
</el-menu>
<SideBar />
<div v-if="userInfo" class="user-info"> <div v-if="userInfo" class="user-info">
<span class="user-avatar" style="color: #fff; font-size: 14px"> <span class="user-avatar" style="color: #fff; font-size: 14px">
工厂: {{ userInfo.factoryCode }} 工厂: {{ userInfo.factoryCode }}
...@@ -63,6 +45,20 @@ ...@@ -63,6 +45,20 @@
</div> </div>
</div> </div>
<div ref="tabsContainer" class="nav-tabs"> <div ref="tabsContainer" class="nav-tabs">
<div
class="tabs-node tabs-node--fixed"
:class="{ 'is-active': activeTab === DASHBOARD_PATH }"
:style="{
backgroundColor: activeTab === DASHBOARD_PATH ? '#409EFF' : '',
color: activeTab === DASHBOARD_PATH ? '#fff' : '',
}"
@click="activeTab = DASHBOARD_PATH"
>
<div class="nav-tabs-node_label">
<span>{{ DASHBOARD_TITLE }}</span>
</div>
</div>
<el-icon <el-icon
v-if="showScrollArrows" v-if="showScrollArrows"
class="nav-tabs-arrow left" class="nav-tabs-arrow left"
...@@ -71,19 +67,17 @@ ...@@ -71,19 +67,17 @@
<ArrowLeft /> <ArrowLeft />
</el-icon> </el-icon>
<div ref="tabsScrollContainer" class="tabs-scroll-container">
<div <div
ref="tabsWrapper" ref="tabsInnerWrapper"
class="tabs-scroll-container"
:style="{ width: showScrollArrows ? 'calc(100% - 80px)' : '100%' }"
>
<div
class="tabs-wrapper" class="tabs-wrapper"
:style="{ transform: `translateX(${scrollOffset}px)` }" :style="{ transform: `translateX(${scrollOffset}px)` }"
> >
<div <div
v-for="item in tabs" v-for="item in scrollableTabs"
:key="item.name" :key="item.name"
class="tabs-node" class="tabs-node"
:class="{ 'is-active': item.name === activeTab }"
:style="{ :style="{
backgroundColor: item.name === activeTab ? '#409EFF' : '', backgroundColor: item.name === activeTab ? '#409EFF' : '',
color: item.name === activeTab ? '#fff' : '', color: item.name === activeTab ? '#fff' : '',
...@@ -92,12 +86,7 @@ ...@@ -92,12 +86,7 @@
> >
<div class="nav-tabs-node_label"> <div class="nav-tabs-node_label">
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
<!-- 首页不可删除 --> <el-icon class="el-icon-close" @click.stop="removeTab(item.name)">
<el-icon
v-if="item.path != '/dashboard' && tabs.length > 1"
class="el-icon-close"
@click.stop="removeTab(item.name)"
>
<CircleClose /> <CircleClose />
</el-icon> </el-icon>
</div> </div>
...@@ -171,14 +160,14 @@ import { ...@@ -171,14 +160,14 @@ import {
import { import {
ref, ref,
reactive, reactive,
watch,
computed, computed,
watch,
nextTick, nextTick,
onMounted, onMounted,
onUnmounted, onUnmounted,
} from 'vue' } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import Menu from '@/router/menu' import Menu, { type MenuItem } from '@/router/menu'
import userUserStore from '@/store/user' import userUserStore from '@/store/user'
import type { FormRules } from 'element-plus' import type { FormRules } from 'element-plus'
...@@ -187,13 +176,16 @@ import { useValue } from '@/utils/hooks/useValue' ...@@ -187,13 +176,16 @@ import { useValue } from '@/utils/hooks/useValue'
import { changePasswordApi } from '@/api/auth' import { changePasswordApi } from '@/api/auth'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import SideBar from './SideBar.vue' import NavMenuItem from './NavMenuItem.vue'
interface MenuItem { const removeMenuItemByIndex = (items: MenuItem[], indexToRemove: string) => {
index: string for (let i = items.length - 1; i >= 0; i--) {
id: number if (items[i].index === indexToRemove) {
label: string items.splice(i, 1)
children?: MenuItem[] } else if (items[i].children?.length) {
removeMenuItemByIndex(items[i].children!, indexToRemove)
}
}
} }
interface PasswordForm { interface PasswordForm {
oldPwd: string oldPwd: string
...@@ -201,14 +193,13 @@ interface PasswordForm { ...@@ -201,14 +193,13 @@ interface PasswordForm {
confirmPwd?: string confirmPwd?: string
} }
const route = useRoute() const route = useRoute()
// 使用 computed 替代直接赋值
const defaultActive = computed({
get: () => route.path,
set: (val) => {
router.push(val)
},
})
const menuList = reactive(Menu) const menuList = reactive(Menu)
const DASHBOARD_PATH = '/dashboard'
const DASHBOARD_TITLE = '概览'
const displayMenuList = computed(() =>
menuList.filter((item) => item.index !== DASHBOARD_PATH),
)
const userStore = userUserStore() const userStore = userUserStore()
const userInfo = userStore.user const userInfo = userStore.user
...@@ -285,9 +276,18 @@ const tabs = ref<Array<{ name: string; title: string; path: string }>>([]) ...@@ -285,9 +276,18 @@ const tabs = ref<Array<{ name: string; title: string; path: string }>>([])
const activeTab = ref('') const activeTab = ref('')
const router = useRouter() const router = useRouter()
// 添加标签页 const scrollableTabs = computed(() =>
tabs.value.filter((tab) => tab.path !== DASHBOARD_PATH),
)
const addTab = () => { const addTab = () => {
const { path, meta } = route const { path, meta } = route
if (path === DASHBOARD_PATH) {
activeTab.value = DASHBOARD_PATH
return
}
const existingTab = tabs.value.find((tab) => tab.path === path) const existingTab = tabs.value.find((tab) => tab.path === path)
if (!existingTab) { if (!existingTab) {
...@@ -302,105 +302,94 @@ const addTab = () => { ...@@ -302,105 +302,94 @@ const addTab = () => {
activeTab.value = path activeTab.value = path
} }
// 移除标签页
const removeTab = (targetName: string) => { const removeTab = (targetName: string) => {
const tabIndex = tabs.value.findIndex((tab) => tab.name === targetName) const tabIndex = tabs.value.findIndex((tab) => tab.name === targetName)
if (tabIndex !== -1) { if (tabIndex !== -1) {
tabs.value.splice(tabIndex, 1) tabs.value.splice(tabIndex, 1)
// 如果关闭的是当前激活的标签,则切换到最后一个标签
if (activeTab.value === targetName) { if (activeTab.value === targetName) {
const lastTab = tabs.value[tabs.value.length - 1] const lastTab = tabs.value[tabs.value.length - 1]
if (lastTab) {
nextTick(() => { nextTick(() => {
if (lastTab) {
activeTab.value = lastTab.name activeTab.value = lastTab.name
router.push(lastTab.path) router.push(lastTab.path)
}) } else {
activeTab.value = DASHBOARD_PATH
router.push(DASHBOARD_PATH)
} }
})
} }
} }
} }
const tabsContainer = ref<HTMLDivElement | null>(null) const tabsContainer = ref<HTMLDivElement | null>(null)
const tabsWrapper = ref<HTMLDivElement | null>(null) const tabsScrollContainer = ref<HTMLDivElement | null>(null)
const tabsInnerWrapper = ref<HTMLDivElement | null>(null)
const scrollOffset = ref(0) const scrollOffset = ref(0)
const scrollStep = 200 // 每次滚动的像素数 const scrollStep = 200
const showScrollArrows = ref(false) const showScrollArrows = ref(false)
// 检查是否需要显示滚动箭头
const checkScrollArrows = () => {
nextTick(() => {
if (!tabsContainer.value || !tabsWrapper.value) return
const containerWidth = tabsContainer.value.clientWidth const clampScrollOffset = () => {
const wrapperWidth = tabsWrapper.value.scrollWidth if (!tabsScrollContainer.value || !tabsInnerWrapper.value) return
// 如果包装器宽度大于容器宽度,显示箭头 const containerWidth = tabsScrollContainer.value.clientWidth
showScrollArrows.value = wrapperWidth > containerWidth const innerWidth = tabsInnerWrapper.value.scrollWidth
const maxScroll = Math.min(0, containerWidth - innerWidth)
// 调整滚动位置,确保不会超出边界 scrollOffset.value = Math.min(0, Math.max(maxScroll, scrollOffset.value))
if (showScrollArrows.value) { }
const maxScroll = -(wrapperWidth - containerWidth)
// 如果当前滚动位置已经超出最大可滚动范围,重新调整 const checkScrollArrows = () => {
if (scrollOffset.value < maxScroll) { nextTick(() => {
scrollOffset.value = maxScroll if (!tabsScrollContainer.value || !tabsInnerWrapper.value) return
}
// 确保不会滚动到容器左侧之外 const containerWidth = tabsScrollContainer.value.clientWidth
if (scrollOffset.value > 0) { const innerWidth = tabsInnerWrapper.value.scrollWidth
scrollOffset.value = 0
} showScrollArrows.value = innerWidth > containerWidth
} else { clampScrollOffset()
// 如果不需要滚动,重置滚动位置 ensureActiveTabVisible()
scrollOffset.value = 0
}
}) })
} }
// 添加窗口大小监听器
const resizeObserver = new ResizeObserver(checkScrollArrows) const resizeObserver = new ResizeObserver(checkScrollArrows)
const scrollTabs = (direction: 'left' | 'right') => { const scrollTabs = (direction: 'left' | 'right') => {
if (!tabsContainer.value || !tabsWrapper.value) return if (!tabsScrollContainer.value || !tabsInnerWrapper.value) return
const containerWidth = tabsContainer.value.clientWidth const containerWidth = tabsScrollContainer.value.clientWidth
const wrapperWidth = tabsWrapper.value.scrollWidth const innerWidth = tabsInnerWrapper.value.scrollWidth
const maxScroll = Math.min(0, containerWidth - innerWidth)
if (direction === 'left') { if (direction === 'left') {
// 向左滚动(显示前面的标签)
scrollOffset.value = Math.min(0, scrollOffset.value + scrollStep) scrollOffset.value = Math.min(0, scrollOffset.value + scrollStep)
} else { } else {
// 向右滚动(显示后面的标签)
const maxScroll = -(wrapperWidth - containerWidth)
scrollOffset.value = Math.max(maxScroll, scrollOffset.value - scrollStep) scrollOffset.value = Math.max(maxScroll, scrollOffset.value - scrollStep)
} }
} }
// 确保活动标签始终在可视范围内
const ensureActiveTabVisible = () => { const ensureActiveTabVisible = () => {
nextTick(() => { nextTick(() => {
if (!tabsContainer.value || !tabsWrapper.value) return if (!tabsScrollContainer.value || !tabsInnerWrapper.value) return
const activeTab = tabsWrapper.value.querySelector( const activeTabEl = tabsScrollContainer.value.querySelector(
'.tabs-node[style*="background-color: rgb(64, 158, 255)"]', '.tabs-node.is-active',
) as HTMLElement ) as HTMLElement | null
if (!activeTab) return if (!activeTabEl) return
const containerRect = tabsContainer.value.getBoundingClientRect() const containerWidth = tabsScrollContainer.value.clientWidth
const activeTabRect = activeTab.getBoundingClientRect() const tabLeft = activeTabEl.offsetLeft
const wrapperRect = tabsWrapper.value.getBoundingClientRect() const tabRight = tabLeft + activeTabEl.offsetWidth
const visibleLeft = -scrollOffset.value
// 计算活动标签相对于包装器的位置 const visibleRight = visibleLeft + containerWidth
const relativeLeft = activeTabRect.left - wrapperRect.left
const relativeRight = activeTabRect.right - wrapperRect.left if (tabLeft < visibleLeft) {
scrollOffset.value = -tabLeft
// 如果标签部分或完全在容器外 } else if (tabRight > visibleRight) {
if (relativeLeft < 0 || relativeRight > containerRect.width) { scrollOffset.value = -(tabRight - containerWidth)
// 调整滚动位置,使活动标签居中
const centerOffset = containerRect.width / 2 - activeTabRect.width / 2
scrollOffset.value = -relativeLeft + centerOffset
// 重新检查滚动边界
checkScrollArrows()
} }
clampScrollOffset()
}) })
} }
...@@ -418,12 +407,10 @@ watch(activeTab, (newTab) => { ...@@ -418,12 +407,10 @@ watch(activeTab, (newTab) => {
if (newTab) { if (newTab) {
router.push(newTab) router.push(newTab)
} }
// 监听活动标签变化时调整滚动
ensureActiveTabVisible() ensureActiveTabVisible()
checkScrollArrows() checkScrollArrows()
}) })
watch(tabs, checkScrollArrows) watch(scrollableTabs, checkScrollArrows)
// 初始化时确保活动标签可见
onMounted(() => { onMounted(() => {
let showCustomerManagement = false let showCustomerManagement = false
const userJson = localStorage.getItem('user') const userJson = localStorage.getItem('user')
...@@ -438,22 +425,17 @@ onMounted(() => { ...@@ -438,22 +425,17 @@ onMounted(() => {
} }
} }
if (!showCustomerManagement) { if (!showCustomerManagement) {
const systemSettingsMenu = menuList.find( removeMenuItemByIndex(menuList, '/system/customers')
(item: MenuItem) => item.label === '系统设置',
)
if (systemSettingsMenu && systemSettingsMenu.children) {
systemSettingsMenu.children = systemSettingsMenu.children.filter(
(child: MenuItem) => child.index !== '/system/customers',
)
}
} }
// 初始检查 // 初始检查
checkScrollArrows() checkScrollArrows()
// 监听容器大小变化
if (tabsContainer.value) { if (tabsContainer.value) {
resizeObserver.observe(tabsContainer.value) resizeObserver.observe(tabsContainer.value)
} }
if (tabsScrollContainer.value) {
resizeObserver.observe(tabsScrollContainer.value)
}
}) })
const toDownload = () => { const toDownload = () => {
...@@ -461,21 +443,23 @@ const toDownload = () => { ...@@ -461,21 +443,23 @@ const toDownload = () => {
} }
onUnmounted(() => { onUnmounted(() => {
// 清理监听器
resizeObserver.disconnect() resizeObserver.disconnect()
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.nav-tabs { .nav-tabs {
background-color: #f5f5f5; background-color: #f5f5f5;
padding: 10px 20px; padding: 10px 50px 10px 20px;
font-size: 14px; font-size: 14px;
display: flex; display: flex;
align-items: center; align-items: center;
position: relative;
z-index: 1;
} }
.tabs-scroll-container { .tabs-scroll-container {
overflow: hidden; overflow: hidden;
flex-grow: 1; flex: 1;
min-width: 0;
} }
.tabs-wrapper { .tabs-wrapper {
...@@ -483,12 +467,16 @@ onUnmounted(() => { ...@@ -483,12 +467,16 @@ onUnmounted(() => {
align-items: center; align-items: center;
gap: 10px; gap: 10px;
transition: transform 0.3s ease; transition: transform 0.3s ease;
pointer-events: none;
} }
.nav-tabs-arrow { .nav-tabs-arrow {
cursor: pointer; cursor: pointer;
color: #409eff; color: #409eff;
font-size: 20px; font-size: 20px;
flex-shrink: 0;
position: relative;
z-index: 2;
&.left { &.left {
margin-right: 10px; margin-right: 10px;
...@@ -509,9 +497,20 @@ onUnmounted(() => { ...@@ -509,9 +497,20 @@ onUnmounted(() => {
transition: all 0.3s; transition: all 0.3s;
box-shadow: var(--el-box-shadow-light); box-shadow: var(--el-box-shadow-light);
background-color: #fff; background-color: #fff;
pointer-events: auto;
flex-shrink: 0;
&--fixed {
margin-right: 10px;
}
&:hover { &:hover {
background-color: #c6e2ff; background-color: #c6e2ff;
} }
&.is-active:hover {
background-color: #409eff;
}
} }
.nav-tabs-node_label { .nav-tabs-node_label {
display: flex; display: flex;
...@@ -525,28 +524,21 @@ onUnmounted(() => { ...@@ -525,28 +524,21 @@ onUnmounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 40px; padding: 0 40px;
:deep(.el-sub-menu .el-sub-menu__icon-arrow) {
display: none;
}
} }
.el-menu-demo { .mega-nav {
flex: 1; flex: 1;
height: 100%; height: 100%;
border-bottom: none; display: flex;
margin-left: 20px; align-items: center;
:deep(.el-menu-item) { margin-left: 60px;
border-bottom: none !important;
}
:deep(.el-menu-item.is-active) {
background-color: #409eff !important;
color: #fff !important;
border: none !important;
}
} }
.user-info { .user-info {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.header-logo {
margin-left: 120px;
}
</style> </style>
<template>
<router-link
v-if="!hasChildren"
:to="item.index"
class="nav-menu-item"
:class="{ 'is-active': isActive }"
>
{{ item.label }}
</router-link>
<div
v-else
class="nav-menu-item has-children"
:class="{ 'is-active': isActive }"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
>
<span class="nav-menu-item-label">{{ item.label }}</span>
<div v-show="panelVisible" class="mega-panel">
<div v-for="group in item.children" :key="group.id" class="mega-group">
<div class="mega-group-title">{{ group.label }}</div>
<div class="mega-group-items">
<router-link
v-for="child in group.children"
:key="child.id"
:to="child.index"
class="mega-item"
@click="panelVisible = false"
>
<span class="mega-item-text" :title="child.label">{{
child.label
}}</span>
</router-link>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import type { MenuItem } from '@/router/menu'
const props = defineProps<{
item: MenuItem
}>()
const route = useRoute()
const panelVisible = ref(false)
const hasChildren = computed(
() => !!props.item.children && props.item.children.length > 0,
)
const isRouteIndex = (index: string) => index.startsWith('/')
const isActive = computed(() => {
return checkActive(props.item)
})
function checkActive(menuItem: MenuItem): boolean {
if (isRouteIndex(menuItem.index) && route.path === menuItem.index) {
return true
}
if (menuItem.children) {
return menuItem.children.some((child) => checkActive(child))
}
return false
}
const onMouseEnter = () => {
panelVisible.value = true
}
const onMouseLeave = () => {
panelVisible.value = false
}
</script>
<style lang="scss" scoped>
.nav-menu-item {
display: flex;
align-items: center;
height: 100%;
padding: 0 16px;
color: #fff;
font-size: 14px;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
transition: all 0.2s;
position: relative;
&:hover .nav-menu-item-label {
color: #409eff;
font-weight: 500;
}
&.is-active {
color: #409eff;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #409eff;
}
}
&.is-active .nav-menu-item-label {
font-weight: 500;
}
}
.has-children {
> span {
pointer-events: none;
}
}
.mega-panel {
position: absolute;
top: 100%;
left: 50%;
transform: translate(-50%, 0);
background: #fff;
border-radius: 4px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
padding: 12px 0;
z-index: 2000;
min-width: 280px;
width: 580px;
}
.mega-group {
display: flex;
align-items: flex-start;
padding: 8px 20px;
& + & {
border-top: 1px solid #f0f0f0;
}
}
.mega-group-title {
font-weight: bold;
color: #303133;
font-size: 14px;
white-space: nowrap;
min-width: 80px;
line-height: 32px;
}
.mega-group-items {
flex: 1;
min-width: 0;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.mega-item {
display: block;
min-width: 0;
padding: 6px 12px;
color: #333;
font-size: 14px;
text-decoration: none;
border-radius: 4px;
transition: all 0.2s;
&:hover {
color: #409eff;
}
&.router-link-exact-active {
color: #409eff;
font-weight: 600;
}
}
.mega-item-text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
...@@ -441,7 +441,7 @@ watch(getLogisticDrawer, async (value: boolean) => { ...@@ -441,7 +441,7 @@ watch(getLogisticDrawer, async (value: boolean) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.tool_warper { .tool_warper {
position: absolute; position: absolute;
top: 50px; top: 60px;
right: 0; right: 0;
bottom: 0; bottom: 0;
width: 40px; width: 40px;
......
# 顶部菜单修改
## 菜单
### 概览
### 物流
- 物流管理
- 物流公司
- 物流方式
- 物流分区
- 物流报价
- 运费试算
- 物流跟踪
- 发货地址
- 规则设置
- 申报规则
- 分拣配置
### 库存
- 库存管理
- 入库申请单
- 仓库预警
- 入库单
- 出库单
- 备货计划
- 仓库管理
- 仓库管理
- 库位管理
- 仓库规则
- 仓库规则
- 共享库存设置
### 订单
- 订单处理
- 定制订单
- POD 订单
- POD 订单(CN)
- POD 订单(US)
- 工厂订单(NEW)
- 取消后订单处理
- 订单查询
- POD(CN)订单跟踪
- POD(US)订单跟踪
- 订单规则
- POD(US)派单规则
- 配货分拣规则
### 供应
- 采购生产
- 备货计划
- 备货订单
- 打版管理
- 供应管理
- 供应商管理
### 财务
- 对账管理
- 定制对账单
- POD(CN)对账单
- POD(US)对账单
### 发货单
- 发货单查询
- 定制发货单
- POD 发货单
### 系统
- 系统管理
- 用户管理
- 功能角色管理
- 系统设置
- 工厂设置
- 下载生产客户端
其中菜单的数据结构为
```ts
interface MenuItem {
index: string
id: number
label: string
children?: MenuItem[]
}
// 菜单数据结构
const menu = [
{
route: 'xxx', // 路由地址
path: 'xxx', // 菜单路径
url: 'xxx', // 菜单url
id: 1, // 菜单id
pid: 0, // 父级id
type: 0, // 菜单类型 0: 菜单 1: 按钮
sortNo: 1, // 排序
enable: true, // 是否启用,用于权限控制
includeNamespace: '', // 包含命名空间
excludeNamespace: '', // 排除命名空间,用于权限控制
children: [
{
route: 'xxx', // 二级菜单不可点击 路由为空
path: 'xxx', // 二级菜单路径
url: 'xxx', // 二级菜单url
id: 1, // 菜单id
pid: 0, // 父级id
type: 0, // 菜单类型 0: 菜单 1: 按钮
sortNo: 1, // 排序
enable: true, // 是否启用,用于权限控制
children: [], // 三级菜单 结构与二级菜单相同
label: 'xxx', // 菜单名称
},
], //二级菜单
label: 'xxx', // 菜单名称
},
]
```
如果有菜单有子级的话,不可点击
\ No newline at end of file
...@@ -12,249 +12,245 @@ const menu: MenuItem[] = [ ...@@ -12,249 +12,245 @@ const menu: MenuItem[] = [
label: '概览', label: '概览',
}, },
{ {
index: '4', index: 'logistics',
id: 7, id: 2,
label: '物流', label: '物流',
children: [ children: [
{ {
index: '/logistics/logisticsCompany', index: 'logistics-manage',
id: 11, id: 21,
label: '物流公司', label: '物流管理',
}, children: [
{ { index: '/logistics/logisticsCompany', id: 211, label: '物流公司' },
index: '/logistics/logisticsMethod', { index: '/logistics/logisticsMethod', id: 212, label: '物流方式' },
id: 1,
label: '物流方式',
},
{ {
index: '/logistics/shippingAddress', index: '/logistics/logisticsPartition',
id: 2, id: 213,
label: '发货地址', label: '物流分区',
}, },
{ {
index: '/logistics/logisticsQuotation', index: '/logistics/logisticsQuotation',
id: 3, id: 214,
label: '物流报价', label: '物流报价',
}, },
{ {
index: '/logistics/declarationRule',
id: 4,
label: '申报规则',
},
{
index: '/logistics/logisticsPartition',
id: 5,
label: '物流分区',
},
{
index: '/logistics/logisticsCalculate', index: '/logistics/logisticsCalculate',
id: 6, id: 215,
label: '运费试算', label: '运费试算',
}, },
{ index: '/logistics/logisticsTracking', id: 216, label: '物流跟踪' },
{ index: '/logistics/shippingAddress', id: 217, label: '发货地址' },
],
},
{
index: 'logistics-rules',
id: 22,
label: '规则设置',
children: [
{ index: '/logistics/declarationRule', id: 221, label: '申报规则' },
{ {
index: '/logistics/sortingConfiguration', index: '/logistics/sortingConfiguration',
id: 7, id: 222,
label: '分拣配置', label: '分拣配置',
}, },
{ ],
index: '/logistics/logisticsTracking',
id: 8,
label: '物流跟踪',
}, },
], ],
}, },
{ {
index: '13', index: 'warehouse',
id: 13, id: 3,
label: '库存', label: '库存',
children: [ children: [
{ {
index: 'warehouse-inventory',
id: 31,
label: '库存管理',
children: [
{
index: '/warehouse/stocking-application', index: '/warehouse/stocking-application',
id: 126, id: 311,
label: '入库申请单', label: '入库申请单',
}, },
{ { index: '/warehouse/warning', id: 312, label: '仓库预警' },
index: '/warehouse/warning', { index: '/warehouse/receipt-doc', id: 313, label: '入库单' },
id: 125, { index: '/warehouse/issue-doc', id: 314, label: '出库单' },
label: '仓库预警', { index: '/warehouse/stocking-plan', id: 315, label: '备货计划' },
}, ],
{
index: '/warehouse/receipt-doc',
id: 123,
label: '入库单',
},
{
index: '/warehouse/issue-doc',
id: 124,
label: '出库单',
}, },
{ {
index: '/warehouse/manage', index: 'warehouse-manage',
id: 121, id: 32,
label: '仓库管理', label: '仓库管理',
children: [
{ index: '/warehouse/manage', id: 321, label: '仓库管理' },
{ index: '/warehouse/position', id: 322, label: '库位管理' },
],
}, },
{ {
index: '/warehouse/position', index: 'warehouse-rules',
id: 122, id: 33,
label: '库位管理', label: '仓库规则',
}, children: [
{ {
index: '/warehouse/stocking-plan', index: '/warehouse/warehouse-rule-setting',
id: 124, id: 331,
label: '备货计划', label: '共享库存设置',
},
],
}, },
], ],
}, },
{ {
index: '1', index: 'order',
id: 2, id: 4,
label: '订单', label: '订单',
children: [ children: [
{ {
index: '/order/list', index: 'order-process',
id: 1, id: 41,
label: '定制订单', label: '订单处理',
}, children: [
{ index: '/order/list', id: 411, label: '定制订单' },
{ index: '/pod-order/list', id: 412, label: 'POD订单' },
{ index: '/pod-cn-order/list', id: 413, label: 'POD订单(CN)' },
{ index: '/pod-us-order/list', id: 414, label: 'POD订单(US)' },
{ {
index: '/pod-order/list', index: '/order/factory-order-new',
id: 7, id: 415,
label: 'POD订单', label: '工厂订单(NEW)',
}, },
{ {
index: '/pod-cn-order/list', index: '/order/cancel-order-process',
id: 6, id: 416,
label: 'POD订单(CN)', label: '取消后订单处理',
}, },
{ ],
index: '/pod-us-order/list',
id: 8,
label: 'POD订单(US)',
}, },
{ {
index: 'order-query',
id: 42,
label: '订单查询',
children: [
{
index: '/pod-cn-order/orderTracking', index: '/pod-cn-order/orderTracking',
id: 9, id: 421,
label: 'POD(CN)订单跟踪', label: 'POD(CN)订单跟踪',
}, },
{ {
index: '/pod-us-order/orderTracking', index: '/pod-us-order/orderTracking',
id: 10, id: 422,
label: 'POD(US)订单跟踪', label: 'POD(US)订单跟踪',
}, },
{ ],
index: '/pod-us-order/podUsSchedulingRules',
id: 11,
label: 'POD(US)排单规则',
}, },
{ {
index: '/order/factory-order-new', index: 'order-rules',
id: 12, id: 43,
label: '工厂订单(NEW)', label: '订单规则',
}, children: [
{ {
index: '/order/cancel-order-process', index: '/pod-us-order/podUsSchedulingRules',
id: 13, id: 431,
label: '取消后订单处理', label: 'POD(US)派单规则',
}, },
{ {
index: '/order/allocation-sorting-rule', index: '/order/allocation-sorting-rule',
id: 14, id: 432,
label: '配货分拣规则', label: '配货分拣规则',
}, },
], ],
}, },
],
},
{ {
index: '14', index: 'supply',
id: 14, id: 5,
label: '供应', label: '供应',
children: [ children: [
{ {
label: '缺货统计', index: 'supply-production',
id: 51,
label: '采购生产',
children: [
{
index: '/supply/out-of-stock-statistics', index: '/supply/out-of-stock-statistics',
id: 1, id: 511,
label: '缺货统计',
}, },
{ { index: '/supply/stocking-order', id: 512, label: '备货订单' },
label: '备货订单', { index: '/typesetting-management/list', id: 513, label: '打版管理' },
index: '/supply/stocking-order', ],
id: 2,
}, },
{ {
index: '/supply/supplierManagement', index: 'supply-manage',
id: 3, id: 52,
label: '供应商管理', label: '供应管理',
children: [
{ index: '/supply/supplierManagement', id: 521, label: '供应商管理' },
],
}, },
], ],
}, },
{ {
index: '11', index: 'finance',
id: 3, id: 6,
label: '对账单', label: '财务',
children: [ children: [
{ {
index: '/account/statement-note', index: 'finance-reconciliation',
id: 1, id: 61,
label: '定制对账单', label: '对账管理',
}, children: [
{ { index: '/account/statement-note', id: 611, label: '定制对账单' },
index: '/account/pod-bill-order', { index: '/account/pod-bill-order', id: 612, label: 'POD(CN)对账单' },
id: 2,
label: 'POD(CN)对账单',
},
{ {
index: '/account/pod-us-bill-order', index: '/account/pod-us-bill-order',
id: 3, id: 613,
label: 'POD(US)对账单', label: 'POD(US)对账单',
}, },
], ],
}, },
],
},
{ {
index: '2', index: 'delivery',
id: 4, id: 7,
label: '发货单', label: '发货单',
children: [ children: [
{ {
index: '/system/delivery-note', index: 'delivery-query',
id: 1, id: 71,
label: '定制发货单', label: '发货单查询',
}, children: [
{ { index: '/system/delivery-note', id: 711, label: '定制发货单' },
index: '/pod-delivery-note/list', { index: '/pod-delivery-note/list', id: 712, label: 'POD发货单' },
id: 2,
label: 'POD发货单',
},
], ],
}, },
],
{
index: '/typesetting-management/list',
id: 5,
label: '打版管理',
}, },
{ {
index: '3', index: 'system',
id: 6, id: 8,
label: '系统设置', label: '系统',
children: [ children: [
{ {
index: '/system/user', index: 'system-manage',
id: 4, id: 81,
label: '用户管理', label: '系统管理',
}, children: [
{ { index: '/system/user', id: 811, label: '用户管理' },
index: '/system/downloadClient', { index: '/system/role-manage', id: 812, label: '功能角色管理' },
id: 4, ],
label: '下载生产客户端',
},
{
index: '/system/customers',
id: 6,
label: '客户管理',
}, },
{ {
index: '/setting/settingIndex', index: 'system-settings',
id: 8, id: 82,
label: '工厂设置', label: '系统设置',
children: [
{ index: '/setting/settingIndex', id: 821, label: '工厂设置' },
{ index: '/system/downloadClient', id: 822, label: '下载生产客户端' },
],
}, },
], ],
}, },
......
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
<div class="container"> <div class="container">
<RouterView /> <RouterView />
</div> </div>
<SideBar />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import NavMenu from '@/components/NavMenu.vue' import NavMenu from '@/components/NavMenu.vue'
import SideBar from '@/components/SideBar.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.home { .home {
......
...@@ -8,12 +8,8 @@ ...@@ -8,12 +8,8 @@
</div> </div>
<div class="cardBox"> <div class="cardBox">
<div class="card-box-item-container"> <div class="card-box-item-container">
<div <template v-for="(item, index) in formList" :key="index">
v-for="(item, index) in formList" <div v-if="item.type === 'RIIN'" class="card-box-item">
:key="index"
class="card-box-item"
>
<template v-if="item.type === 'RIIN'">
<div class="formBox"> <div class="formBox">
<div class="form-item"> <div class="form-item">
<span>转至RIIN生产</span> <span>转至RIIN生产</span>
...@@ -65,8 +61,8 @@ ...@@ -65,8 +61,8 @@
@click="saveConfiguration(item, index)" @click="saveConfiguration(item, index)"
>保存配置</ElButton >保存配置</ElButton
> >
</template> </div>
<template v-if="item.type === 'TRACK'"> <div v-if="item.type === 'TRACK'" class="card-box-item">
<div class="formBox"> <div class="formBox">
<div class="form-item"> <div class="form-item">
<span>物流轨迹跟踪</span> <span>物流轨迹跟踪</span>
...@@ -118,8 +114,8 @@ ...@@ -118,8 +114,8 @@
@click="saveConfiguration(item, index)" @click="saveConfiguration(item, index)"
>保存配置</ElButton >保存配置</ElButton
> >
</template>
</div> </div>
</template>
<div class="card-box-item"> <div class="card-box-item">
<div class="formBox"> <div class="formBox">
<div class="form-item"> <div class="form-item">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment