diff --git a/layouts/admin/AdminLayout.uvue b/layouts/admin/AdminLayout.uvue index c8458b4f..09e51631 100644 --- a/layouts/admin/AdminLayout.uvue +++ b/layouts/admin/AdminLayout.uvue @@ -22,14 +22,15 @@ @@ -86,9 +87,12 @@ import { } from '@/layouts/admin/router/adminRoutes.uts' import type { TopMenu, MenuGroup, RouteRecord } from '@/layouts/admin/router/adminRoutes.uts' +import { MenuNode, settingSubSiderMenu } from '@/layouts/admin/router/settingSubSiderMenu.uts' + import { activeTopMenuId, activeRouteId, + lastSubIdByMenu, tabs, isMainAsideCollapsed, showSubSider, @@ -123,20 +127,66 @@ const isSubSiderOverlay = computed(() => { return layoutMode.value === 'tablet' }) +// 当前路由的路径 +const currentRoutePath = computed(() => { + const route = findRouteById(activeRouteId.value) + return route ? route.path : '' +}) + +// 将旧的 Group + Routes 转换为新的 Tree 结构 (支持 3 级嵌套) +const activeMenuTree = computed(() => { + if (activeTopMenuId.value === 'setting') { + return settingSubSiderMenu + } + + const tree: MenuNode[] = [] + activeGroups.value.forEach(group => { + const routes = activeRoutes.value.get(group.id) + if (routes != null && routes.length > 0) { + const children: MenuNode[] = [] + routes.forEach(route => { + children.push({ + id: route.id, + title: route.title, + type: 'page', + path: route.path + } as MenuNode) + }) + + // 1:1 复刻 CRMEB: 如果分组标题为空,直接将子菜单平铺在顶层,不渲染分组行 + if (group.title === '') { + children.forEach(child => { + tree.push(child) + }) + } else { + tree.push({ + id: group.id, + title: group.title, + type: 'group', + children: children + } as MenuNode) + } + } + }) + + return tree +}) + /** * 核心逻辑:二级菜单是否可见 - * 1. 如果有子菜单内容 (activeGroups > 0) + * 1. 如果有子菜单内容 (activeMenuTree > 0) * 2. 如果是 Desktop 且 showSubSider 为真 (Dock模式) * 3. 如果是 Tablet/Mobile 且 isOverlayVisible 为真 (Overlay模式) */ const isSubSiderVisible = computed(() => { - if (activeGroups.value.length === 0) return false + // 首页模块通常不显示 SubSider + if (activeTopMenuId.value === 'home' || activeMenuTree.value.length === 0) return false if (layoutMode.value === 'desktop') { return showSubSider.value } - // Tablet 和 Mobile 模式下,作为 Overlay 受 isOverlayVisible 控制 + // Tablet 和 Mobile 模式下,直接由 isOverlayVisible 控制 return isOverlayVisible.value }) @@ -155,7 +205,7 @@ const mainLeft = computed(() => { let left = ASIDE_W // 只要不是 Mobile,主侧栏 70px 始终 Dock // 只有在 Desktop 模式且二级菜单处于 Dock 模式显示时,才累加宽度 - if (layoutMode.value === 'desktop' && showSubSider.value && activeGroups.value.length > 0) { + if (layoutMode.value === 'desktop' && showSubSider.value && activeMenuTree.value.length > 0) { left += SUB_W } @@ -199,6 +249,24 @@ const currentComponent = computed(() => { return getComponent(route.componentKey) }) +// 监听路由变化,同步状态 (处理从 Tabs 或外部跳转的情况) +watch(() => activeRouteId.value, (newId) => { + const route = findRouteById(newId) + if (route && route.parentId) { + // 同步一级菜单 + if (activeTopMenuId.value !== route.parentId) { + activeTopMenuId.value = route.parentId + } + // 同步最后访问记录 + lastSubIdByMenu.value[route.parentId] = newId + + // 如果是 Desktop 且 SubSider 关着,自动打开(除非用户手动关了) + if (layoutMode.value === 'desktop' && !showSubSider.value) { + showSubSider.value = true + } + } +}, { immediate: true }) + // ============================================ // 事件处理 // ============================================ @@ -206,27 +274,46 @@ const currentComponent = computed(() => { function onTopMenuClick(menu: TopMenu): void { activeTopMenuId.value = menu.id - // 1:1 复刻 CRMEB 交互: - // 1. 如果有子菜单:Desktop 下 dock,Tablet/Mobile 下唤起 Overlay - if (menu.groups.length > 0) { - if (layoutMode.value === 'desktop') { - showSubSider.value = true - } else { - isOverlayVisible.value = true - } + // 1:1 复刻 CRMEB 交互:点击 Aside 立即展示 SubSider + if (layoutMode.value === 'desktop') { + showSubSider.value = true } else { - // 2. 如果没有子菜单:直接跳转并关闭所有 Overlay + isOverlayVisible.value = true + isMobileMenuOpen.value = false // 如果主侧栏开着,关掉它,开二级 + } + + // 1:1 复刻 CRMEB 交互:点击一级菜单后,自动跳转到上一次记录的子页面或第一个子页面 + let targetId = lastSubIdByMenu.value[menu.id] + if (!targetId && activeMenuTree.value.length > 0) { + const firstNode = activeMenuTree.value[0] + if (firstNode.type === 'page') { + targetId = firstNode.id + } else if (firstNode.children != null && firstNode.children!.length > 0) { + targetId = firstNode.children![0].id + } + } + + if (targetId) { + openRoute(targetId) + } else { + // 兜底逻辑:如果没有二级菜单,跳转到 index openRoute(menu.id + '_index') - closeAllMenu() } } -function onRouteClick(routeId: string): void { - openRoute(routeId) - // 1:1 复刻 CRMEB:在移动端或平板叠加模式下,点击具体子路由后自动收起 - if (layoutMode.value !== 'desktop') { - closeAllMenu() - } +function onSubClick(payload: { id: string, path: string }): void { + // CRMEB 跳转逻辑 + openRoute(payload.id) + + // 更新最后访问记录 + lastSubIdByMenu.value[activeTopMenuId.value] = payload.id + + // 重要:1:1 复刻 CRMEB,点击二级菜单项后,SubSider 通常保持显示状态 + // 只有在 Mobile 模式下,用户如果是想“跳转并收起”,通常需要点击遮罩或关闭按钮,但这里我们按桌面版常驻逻辑 + // 如果需要移动端点击自动关闭,才解开下面注释 + // if (layoutMode.value === 'mobile') { + // closeAllMenu() + // } } function onTabClick(tab: TabItem): void { diff --git a/layouts/admin/components/AdminAside.uvue b/layouts/admin/components/AdminAside.uvue index cb424d26..b2870425 100644 --- a/layouts/admin/components/AdminAside.uvue +++ b/layouts/admin/components/AdminAside.uvue @@ -109,45 +109,34 @@ function onLogoClick(): void { .aside-menu { flex: 1; - padding: 8px 0; + padding: 0; /* CRMEB typically no padding here */ overflow-y: scroll; } .menu-item { - height: 60px; + height: 50px; /* 1:1 CRMEB columnsAside height */ display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; - color: rgba(255, 255, 255, 0.65); + color: rgba(255, 255, 255, 0.7); transition: all 0.3s; position: relative; &:hover { background: rgba(255, 255, 255, 0.05); - color: #fff; } &.active { - background: #1890ff; + background: #1890ff; /* CRMEB 主色蓝 */ color: #fff; - - &::before { - content: ''; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 3px; - background: #fff; - } } } .menu-icon { - font-size: 24px; - margin-bottom: 4px; + font-size: 18px; /* CRMEB icons are smaller than 24px usually */ + margin-bottom: 2px; .icon-text { display: block; @@ -156,6 +145,7 @@ function onLogoClick(): void { .menu-title { font-size: 12px; + transform: scale(0.9); /* CRMEB text is tiny */ text-align: center; text { diff --git a/layouts/admin/components/AdminSubsider.uvue b/layouts/admin/components/AdminSubsider.uvue index 1bd2aa0e..9b2f21e7 100644 --- a/layouts/admin/components/AdminSubsider.uvue +++ b/layouts/admin/components/AdminSubsider.uvue @@ -1,57 +1,160 @@ diff --git a/layouts/admin/router/adminComponentMap.uts b/layouts/admin/router/adminComponentMap.uts index b6789ef2..8a7a2c60 100644 --- a/layouts/admin/router/adminComponentMap.uts +++ b/layouts/admin/router/adminComponentMap.uts @@ -123,10 +123,25 @@ export const componentMap: Map = new Map([ // 数据模块 - 暂时使用占位组件 ['StatisticIndex', PlaceholderPage], - // 设置模块 - 暂时使用占位组件 + // 设置模块 ['SettingSystemConfig', defineAsyncComponent(() => import('@/pages/mall/admin/setting/system/config.uvue'))], - ['SettingSystemAdmin', PlaceholderPage], - ['SettingSystemRole', PlaceholderPage], + ['SettingMessage', defineAsyncComponent(() => import('@/pages/mall/admin/setting/message.uvue'))], + ['SettingAgreement', defineAsyncComponent(() => import('@/pages/mall/admin/setting/agreement.uvue'))], + ['SettingTicket', defineAsyncComponent(() => import('@/pages/mall/admin/setting/ticket.uvue'))], + ['SettingAuthRole', defineAsyncComponent(() => import('@/pages/mall/admin/setting/auth/role.uvue'))], + ['SettingAuthAdmin', defineAsyncComponent(() => import('@/pages/mall/admin/setting/auth/admin.uvue'))], + ['SettingAuthPermission', defineAsyncComponent(() => import('@/pages/mall/admin/setting/auth/permission.uvue'))], + ['SettingDeliveryStaff', defineAsyncComponent(() => import('@/pages/mall/admin/setting/delivery/staff.uvue'))], + ['SettingDeliveryStation', defineAsyncComponent(() => import('@/pages/mall/admin/setting/delivery/station.uvue'))], + ['SettingDeliveryTemplate', defineAsyncComponent(() => import('@/pages/mall/admin/setting/delivery/template.uvue'))], + ['SettingInterfaceOnepassConfig', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/onepass/config.uvue'))], + ['SettingInterfaceOnepassIndex', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/onepass/index.uvue'))], + ['SettingInterfaceStorage', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/storage.uvue'))], + ['SettingInterfaceCollect', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/collect.uvue'))], + ['SettingInterfaceLogistics', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/logistics.uvue'))], + ['SettingInterfaceESheet', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/e-sheet.uvue'))], + ['SettingInterfaceSms', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/sms.uvue'))], + ['SettingInterfacePayment', defineAsyncComponent(() => import('@/pages/mall/admin/setting/interface/payment.uvue'))], // 分销模块 ['DistributionStatistic', PlaceholderPage], diff --git a/layouts/admin/router/adminRoutes.uts b/layouts/admin/router/adminRoutes.uts index 3c8407c9..81a20135 100644 --- a/layouts/admin/router/adminRoutes.uts +++ b/layouts/admin/router/adminRoutes.uts @@ -92,7 +92,7 @@ export const topMenus: TopMenu[] = [ path: '/pages/mall/admin/product/statistic', order: 4, groups: [ - { id: 'product-manage', title: '商品管理', order: 1 } + { id: 'product-manage', title: '', order: 1 } ] }, { @@ -156,7 +156,7 @@ export const topMenus: TopMenu[] = [ path: '/pages/mall/admin/cms/article/list', order: 9, groups: [ - { id: 'cms-manage', title: '内容管理', order: 1 } + { id: 'cms-manage', title: '', order: 1 } ] }, { @@ -187,7 +187,10 @@ export const topMenus: TopMenu[] = [ order: 12, groups: [ { id: 'setting-system', title: '系统设置', order: 1 }, - { id: 'setting-application', title: '应用设置', order: 2 } + { id: 'setting-message', title: '通知管理', order: 2 }, + { id: 'setting-auth', title: '权限管理', order: 3 }, + { id: 'setting-delivery', title: '物流设置', order: 4 }, + { id: 'setting-interface', title: '接口设置', order: 5 } ] }, { @@ -892,6 +895,7 @@ export const routes: RouteRecord[] = [ }, // ========== 设置模块 ========== + // 1. 系统设置 { id: 'setting_systemConfig', title: '系统设置', @@ -902,27 +906,168 @@ export const routes: RouteRecord[] = [ auth: ['admin-setting-system-config'], order: 1 }, + + // 2. 通知管理 { - id: 'setting_systemAdmin', - title: '管理员管理', - path: '/pages/mall/admin/setting/system/admin', - componentKey: 'SettingSystemAdmin', + id: 'setting_message', + title: '消息管理', + path: '/pages/mall/admin/setting/message', + componentKey: 'SettingMessage', parentId: 'setting', - groupId: 'setting-system', - auth: ['admin-setting-system-admin'], + groupId: 'setting-message', + order: 1 + }, + { + id: 'setting_agreement', + title: '协议管理', + path: '/pages/mall/admin/setting/agreement', + componentKey: 'SettingAgreement', + parentId: 'setting', + groupId: 'setting-message', order: 2 }, { - id: 'setting_systemRole', - title: '角色管理', - path: '/pages/mall/admin/setting/system/role', - componentKey: 'SettingSystemRole', + id: 'setting_ticket', + title: '客服设置', + path: '/pages/mall/admin/setting/ticket', + componentKey: 'SettingTicket', parentId: 'setting', - groupId: 'setting-system', - auth: ['admin-setting-system-role'], + groupId: 'setting-message', order: 3 }, + // 3. 权限管理 + { + id: 'setting_auth_role', + title: '角色管理', + path: '/pages/mall/admin/setting/auth/role', + componentKey: 'SettingAuthRole', + parentId: 'setting', + groupId: 'setting-auth', + order: 1 + }, + { + id: 'setting_auth_admin', + title: '管理员管理', + path: '/pages/mall/admin/setting/auth/admin', + componentKey: 'SettingAuthAdmin', + parentId: 'setting', + groupId: 'setting-auth', + order: 2 + }, + { + id: 'setting_auth_permission', + title: '权限管理', + path: '/pages/mall/admin/setting/auth/permission', + componentKey: 'SettingAuthPermission', + parentId: 'setting', + groupId: 'setting-auth', + order: 3 + }, + + // 4. 物流设置 + { + id: 'setting_delivery_staff', + title: '配送员管理', + path: '/pages/mall/admin/setting/delivery/staff', + componentKey: 'SettingDeliveryStaff', + parentId: 'setting', + groupId: 'setting-delivery', + order: 1 + }, + { + id: 'setting_delivery_station', + title: '提货点管理', + path: '/pages/mall/admin/setting/delivery/station', + componentKey: 'SettingDeliveryStation', + parentId: 'setting', + groupId: 'setting-delivery', + order: 2 + }, + { + id: 'setting_delivery_template', + title: '运费模板', + path: '/pages/mall/admin/setting/delivery/template', + componentKey: 'SettingDeliveryTemplate', + parentId: 'setting', + groupId: 'setting-delivery', + order: 3 + }, + + // 5. 接口设置 + { + id: 'setting_interface_onepass_config', + title: '总平台配置', + path: '/pages/mall/admin/setting/interface/onepass/config', + componentKey: 'SettingInterfaceOnepassConfig', + parentId: 'setting', + groupId: 'setting-interface', + order: 1 + }, + { + id: 'setting_interface_onepass_index', + title: '账号列表', + path: '/pages/mall/admin/setting/interface/onepass/index', + componentKey: 'SettingInterfaceOnepassIndex', + parentId: 'setting', + groupId: 'setting-interface', + order: 2 + }, + { + id: 'setting_interface_storage', + title: '存储配置', + path: '/pages/mall/admin/setting/interface/storage', + componentKey: 'SettingInterfaceStorage', + parentId: 'setting', + groupId: 'setting-interface', + order: 3 + }, + { + id: 'setting_interface_collect', + title: '商品采集', + path: '/pages/mall/admin/setting/interface/collect', + componentKey: 'SettingInterfaceCollect', + parentId: 'setting', + groupId: 'setting-interface', + order: 4 + }, + { + id: 'setting_interface_logistics', + title: '物流查询', + path: '/pages/mall/admin/setting/interface/logistics', + componentKey: 'SettingInterfaceLogistics', + parentId: 'setting', + groupId: 'setting-interface', + order: 5 + }, + { + id: 'setting_interface_esheet', + title: '电子面单', + path: '/pages/mall/admin/setting/interface/e-sheet', + componentKey: 'SettingInterfaceESheet', + parentId: 'setting', + groupId: 'setting-interface', + order: 6 + }, + { + id: 'setting_interface_sms', + title: '短信接口', + path: '/pages/mall/admin/setting/interface/sms', + componentKey: 'SettingInterfaceSms', + parentId: 'setting', + groupId: 'setting-interface', + order: 7 + }, + { + id: 'setting_interface_payment', + title: '商城支付', + path: '/pages/mall/admin/setting/interface/payment', + componentKey: 'SettingInterfacePayment', + parentId: 'setting', + groupId: 'setting-interface', + order: 8 + }, + // ========== 分销模块 ========== { id: 'distribution_statistic', @@ -1055,7 +1200,7 @@ export const routes: RouteRecord[] = [ order: 6 }, { - id: 'decoration_link', + id: 'DecorationLink', title: '链接管理', path: '/pages/mall/admin/decoration/link', componentKey: 'DecorationLink', @@ -1064,6 +1209,30 @@ export const routes: RouteRecord[] = [ order: 7 }, + // ========== 设置模块 (1:1 复刻 CRMEB 路由结构) ========== + // 通知管理 + { id: 'setting_message_index', title: '消息管理', path: '/pages/mall/admin/setting/message/index', componentKey: 'SettingMessageIndex', parentId: 'setting', groupId: 'setting-message', order: 1 }, + { id: 'setting_protocol_index', title: '协议设置', path: '/pages/mall/admin/setting/protocol/index', componentKey: 'SettingProtocolIndex', parentId: 'setting', groupId: 'setting-message', order: 2 }, + { id: 'setting_ticket_index', title: '小票配置', path: '/pages/mall/admin/setting/ticket/index', componentKey: 'SettingTicketIndex', parentId: 'setting', groupId: 'setting-message', order: 3 }, + + // 权限管理 + { id: 'setting_auth_role', title: '角色管理', path: '/pages/mall/admin/setting/auth/role/index', componentKey: 'SettingAuthRole', parentId: 'setting', groupId: 'setting-auth', order: 1 }, + { id: 'setting_auth_admin', title: '管理员列表', path: '/pages/mall/admin/setting/auth/admin-list/index', componentKey: 'SettingAuthAdmin', parentId: 'setting', groupId: 'setting-auth', order: 2 }, + { id: 'setting_auth_perm', title: '权限设置', path: '/pages/mall/admin/setting/auth/permission/index', componentKey: 'SettingAuthPerm', parentId: 'setting', groupId: 'setting-auth', order: 3 }, + + // 物流设置 + { id: 'setting_delivery_courier', title: '配送员管理', path: '/pages/mall/admin/setting/delivery/courier/index', componentKey: 'SettingDeliveryCourier', parentId: 'setting', groupId: 'setting-delivery', order: 1 }, + { id: 'setting_delivery_pickup', title: '提货点设置', path: '/pages/mall/admin/setting/delivery/pickup/index', componentKey: 'SettingDeliveryPickup', parentId: 'setting', groupId: 'setting-delivery', order: 2 }, + { id: 'setting_delivery_freight', title: '运费模板', path: '/pages/mall/admin/setting/delivery/freight/index', componentKey: 'SettingDeliveryFreight', parentId: 'setting', groupId: 'setting-delivery', order: 3 }, + + // 接口设置 + { id: 'setting_api_storage', title: '系统存储配置', path: '/pages/mall/admin/setting/api/yht/storage/index', componentKey: 'SettingApiStorage', parentId: 'setting', groupId: 'setting-interface', order: 1 }, + { id: 'setting_api_collect', title: '商品采集配置', path: '/pages/mall/admin/setting/api/yht/collect/index', componentKey: 'SettingApiCollect', parentId: 'setting', groupId: 'setting-interface', order: 2 }, + { id: 'setting_api_logistics', title: '物流查询配置', path: '/pages/mall/admin/setting/api/yht/logistics/index', componentKey: 'SettingApiLogistics', parentId: 'setting', groupId: 'setting-interface', order: 3 }, + { id: 'setting_api_waybill', title: '电子面单配置', path: '/pages/mall/admin/setting/api/yht/waybill/index', componentKey: 'SettingApiWaybill', parentId: 'setting', groupId: 'setting-interface', order: 4 }, + { id: 'setting_api_sms', title: '短信接口配置', path: '/pages/mall/admin/setting/api/yht/sms/index', componentKey: 'SettingApiSms', parentId: 'setting', groupId: 'setting-interface', order: 5 }, + { id: 'setting_api_pay', title: '商城支付配置', path: '/pages/mall/admin/setting/api/yht/pay/index', componentKey: 'SettingApiPay', parentId: 'setting', groupId: 'setting-interface', order: 6 }, + // ========== 应用模块 ========== { id: 'app_statistic', diff --git a/layouts/admin/router/settingSubSiderMenu.uts b/layouts/admin/router/settingSubSiderMenu.uts new file mode 100644 index 00000000..d4e86291 --- /dev/null +++ b/layouts/admin/router/settingSubSiderMenu.uts @@ -0,0 +1,37 @@ +export type MenuNode = { + id: string + title: string + type: 'group' | 'page' + path?: string // type=page 必填 + children?: MenuNode[] // type=group 必填 +} + +export const settingSubSiderMenu: MenuNode[] = [ + { id: 'setting_message_index', title: '消息管理', type: 'page', path: '/pages/mall/admin/setting/message/index' }, + { id: 'setting_protocol_index', title: '协议设置', type: 'page', path: '/pages/mall/admin/setting/protocol/index' }, + { id: 'setting_ticket_index', title: '小票配置', type: 'page', path: '/pages/mall/admin/setting/ticket/index' }, + { id: 'auth_group', title: '管理权限', type: 'group', children: [ + { id: 'setting_auth_role', title: '角色管理', type: 'page', path: '/pages/mall/admin/setting/auth/role/index' }, + { id: 'setting_auth_admin', title: '管理员列表', type: 'page', path: '/pages/mall/admin/setting/auth/admin-list/index' }, + { id: 'setting_auth_perm', title: '权限设置', type: 'page', path: '/pages/mall/admin/setting/auth/permission/index' } + ] + }, + { id: 'delivery_group', title: '发货设置', type: 'group', children: [ + { id: 'setting_delivery_courier', title: '配送员管理', type: 'page', path: '/pages/mall/admin/setting/delivery/courier/index' }, + { id: 'setting_delivery_pickup', title: '提货点设置', type: 'page', path: '/pages/mall/admin/setting/delivery/pickup/index' }, + { id: 'setting_delivery_freight', title: '运费模板', type: 'page', path: '/pages/mall/admin/setting/delivery/freight/index' } + ] + }, + { id: 'api_group', title: '接口配置', type: 'group', children: [ + { id: 'yh_tong', title: '一号通', type: 'group', children: [ + { id: 'setting_api_storage', title: '系统存储配置', type: 'page', path: '/pages/mall/admin/setting/api/yht/storage/index' }, + { id: 'setting_api_collect', title: '商品采集配置', type: 'page', path: '/pages/mall/admin/setting/api/yht/collect/index' }, + { id: 'setting_api_logistics', title: '物流查询配置', type: 'page', path: '/pages/mall/admin/setting/api/yht/logistics/index' }, + { id: 'setting_api_waybill', title: '电子面单配置', type: 'page', path: '/pages/mall/admin/setting/api/yht/waybill/index' }, + { id: 'setting_api_sms', title: '短信接口配置', type: 'page', path: '/pages/mall/admin/setting/api/yht/sms/index' }, + { id: 'setting_api_pay', title: '商城支付配置', type: 'page', path: '/pages/mall/admin/setting/api/yht/pay/index' } + ] + } + ] + } +] diff --git a/layouts/admin/store/adminNavStore.uts b/layouts/admin/store/adminNavStore.uts index a88eb58f..de3ab582 100644 --- a/layouts/admin/store/adminNavStore.uts +++ b/layouts/admin/store/adminNavStore.uts @@ -11,6 +11,7 @@ import { buildDefaultTabs, getTopMenus } from '@/layouts/admin/router/adminRoutes.uts' +import { addView, activeFullPath, visitedViews } from './tagsViewStore.uts' /** * 标签页类型 @@ -32,6 +33,12 @@ export const activeTopMenuId = ref('home') /** 当前激活的路由ID */ export const activeRouteId = ref('home_index') +/** 记录每个一级模块上一次访问的二级路由ID (CRMEB 体验增强) */ +export const lastSubIdByMenu = ref>({}) + +/** 标记是否由用户手动关闭了 SubSider (移动端) */ +export const isManualClosed = ref(false) + /** 打开的标签页列表 */ export const tabs = ref([]) @@ -82,6 +89,8 @@ export function openRoute(routeId: string, addTab: boolean = true): void { // 更新一级菜单选中态 if (route.parentId) { activeTopMenuId.value = route.parentId + // 记录该模块最后访问的子路由 + lastSubIdByMenu.value[route.parentId] = routeId } else { // 首页等顶级路由 activeTopMenuId.value = routeId.split('_')[0] @@ -116,6 +125,10 @@ function addTabItem(route: RouteRecord): void { isAffix: route.isAffix || false }) } + + // 更新新版 TagsViewStore + addView(route, route.path) + activeFullPath.value = route.path } /** @@ -201,6 +214,11 @@ export function initNavState(): void { isAffix: r.isAffix || false })) + // 初始化 TagsViewStore + defaultTabs.forEach(r => { + addView(r, r.path) + }) + // 打开首页 openRoute('home_index', false) } diff --git a/layouts/admin/store/tagsViewStore.uts b/layouts/admin/store/tagsViewStore.uts new file mode 100644 index 00000000..01497a4f --- /dev/null +++ b/layouts/admin/store/tagsViewStore.uts @@ -0,0 +1,126 @@ +/** + * TagsView 状态管理 + * 复刻 CRMEB 风格的标签栏逻辑 + */ + +import { ref } from 'vue' +import type { RouteRecord } from '@/layouts/admin/router/adminRoutes.uts' + +/** + * 标签页视图类型 + */ +export type TagView = { + id: string // 对应路由ID + title: string // 标题 + path: string // 基础路径 + fullPath: string // 完整路径(包含参数) + name: string // 组件Key + meta: { + affix?: boolean + noCache?: boolean + } +} + +/** 访问过的页面列表 */ +export const visitedViews = ref([]) + +/** 缓存的页面列表 (用于 keep-alive) */ +export const cachedViews = ref([]) + +/** 开启的标签页详情 */ +export const activeFullPath = ref('') + +// ============================================ +// Actions +// ============================================ + +/** + * 添加视图 + */ +export function addView(route: RouteRecord, fullPath: string): void { + // 1. 添加到访问列表 + if (visitedViews.value.some(v => v.fullPath === fullPath)) return + + // 如果 ID 相同但 fullPath 不同(参数变化), 则更新或者替换 + const existingIndex = visitedViews.value.findIndex(v => v.id === route.id) + + const newView: TagView = { + id: route.id, + title: route.title, + path: route.path, + fullPath: fullPath, + name: route.componentKey, + meta: { + affix: route.isAffix || false, + noCache: false // 默认为缓存,如果有特殊需求可扩展 + } + } + + if (existingIndex > -1) { + // 同一路由不同参数, 更新记录 + visitedViews.value[existingIndex] = newView + } else { + visitedViews.value.push(newView) + } + + // 2. 添加到缓存列表 + if (!route.componentKey) return + if (cachedViews.value.includes(route.componentKey)) return + cachedViews.value.push(route.componentKey) +} + +/** + * 删除视图 + */ +export function delView(view: TagView): void { + const index = visitedViews.value.findIndex(v => v.fullPath === view.fullPath) + if (index > -1 && !visitedViews.value[index].meta.affix) { + visitedViews.value.splice(index, 1) + } + + const cacheIndex = cachedViews.value.indexOf(view.name) + if (cacheIndex > -1) { + cachedViews.value.splice(cacheIndex, 1) + } +} + +/** + * 关闭其他标签 + */ +export function delOthersViews(view: TagView): void { + visitedViews.value = visitedViews.value.filter(v => v.meta.affix || v.fullPath === view.fullPath) + cachedViews.value = visitedViews.value.map(v => v.name) +} + +/** + * 关闭右侧标签 + */ +export function delRightTags(view: TagView): void { + const index = visitedViews.value.findIndex(v => v.fullPath === view.fullPath) + if (index === -1) return + + visitedViews.value = visitedViews.value.filter((v, i) => { + return i <= index || v.meta.affix + }) + cachedViews.value = visitedViews.value.map(v => v.name) +} + +/** + * 关闭所有 + */ +export function delAllViews(): void { + const affixTags = visitedViews.value.filter(v => v.meta.affix) + visitedViews.value = affixTags + cachedViews.value = affixTags.map(v => v.name) +} + +/** + * 刷新当前视图 (通常结合全局事件通知组件) + */ +export function refreshView(view: TagView): void { + const index = cachedViews.value.indexOf(view.name) + if (index > -1) { + cachedViews.value.splice(index, 1) + } + // 通知框架重新加载该组件的逻辑通常在组件内部处理或通过 key 变化 +} diff --git a/pages/mall/admin/setting/agreement.uvue b/pages/mall/admin/setting/agreement.uvue new file mode 100644 index 00000000..10d80c88 --- /dev/null +++ b/pages/mall/admin/setting/agreement.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/auth/admin.uvue b/pages/mall/admin/setting/auth/admin.uvue new file mode 100644 index 00000000..a3fe4068 --- /dev/null +++ b/pages/mall/admin/setting/auth/admin.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/auth/permission.uvue b/pages/mall/admin/setting/auth/permission.uvue new file mode 100644 index 00000000..cb9c5c56 --- /dev/null +++ b/pages/mall/admin/setting/auth/permission.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/auth/role.uvue b/pages/mall/admin/setting/auth/role.uvue new file mode 100644 index 00000000..9ebc8c82 --- /dev/null +++ b/pages/mall/admin/setting/auth/role.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/delivery/staff.uvue b/pages/mall/admin/setting/delivery/staff.uvue new file mode 100644 index 00000000..a0dc1ded --- /dev/null +++ b/pages/mall/admin/setting/delivery/staff.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/delivery/station.uvue b/pages/mall/admin/setting/delivery/station.uvue new file mode 100644 index 00000000..cb848d40 --- /dev/null +++ b/pages/mall/admin/setting/delivery/station.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/delivery/template.uvue b/pages/mall/admin/setting/delivery/template.uvue new file mode 100644 index 00000000..55733264 --- /dev/null +++ b/pages/mall/admin/setting/delivery/template.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/collect.uvue b/pages/mall/admin/setting/interface/collect.uvue new file mode 100644 index 00000000..aa80b437 --- /dev/null +++ b/pages/mall/admin/setting/interface/collect.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/e-sheet.uvue b/pages/mall/admin/setting/interface/e-sheet.uvue new file mode 100644 index 00000000..5a25a5e2 --- /dev/null +++ b/pages/mall/admin/setting/interface/e-sheet.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/logistics.uvue b/pages/mall/admin/setting/interface/logistics.uvue new file mode 100644 index 00000000..1aacab1c --- /dev/null +++ b/pages/mall/admin/setting/interface/logistics.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/onepass/config.uvue b/pages/mall/admin/setting/interface/onepass/config.uvue new file mode 100644 index 00000000..b8bd3b6f --- /dev/null +++ b/pages/mall/admin/setting/interface/onepass/config.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/onepass/index.uvue b/pages/mall/admin/setting/interface/onepass/index.uvue new file mode 100644 index 00000000..c0366097 --- /dev/null +++ b/pages/mall/admin/setting/interface/onepass/index.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/payment.uvue b/pages/mall/admin/setting/interface/payment.uvue new file mode 100644 index 00000000..c27a802e --- /dev/null +++ b/pages/mall/admin/setting/interface/payment.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/sms.uvue b/pages/mall/admin/setting/interface/sms.uvue new file mode 100644 index 00000000..07e58033 --- /dev/null +++ b/pages/mall/admin/setting/interface/sms.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/interface/storage.uvue b/pages/mall/admin/setting/interface/storage.uvue new file mode 100644 index 00000000..a53f763d --- /dev/null +++ b/pages/mall/admin/setting/interface/storage.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/message.uvue b/pages/mall/admin/setting/message.uvue new file mode 100644 index 00000000..13442107 --- /dev/null +++ b/pages/mall/admin/setting/message.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/system/agreement.uvue b/pages/mall/admin/setting/system/agreement.uvue new file mode 100644 index 00000000..d6720e55 --- /dev/null +++ b/pages/mall/admin/setting/system/agreement.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/system/message.uvue b/pages/mall/admin/setting/system/message.uvue new file mode 100644 index 00000000..13442107 --- /dev/null +++ b/pages/mall/admin/setting/system/message.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/system/ticket.uvue b/pages/mall/admin/setting/system/ticket.uvue new file mode 100644 index 00000000..c5f02ac2 --- /dev/null +++ b/pages/mall/admin/setting/system/ticket.uvue @@ -0,0 +1,23 @@ + + + + + diff --git a/pages/mall/admin/setting/ticket.uvue b/pages/mall/admin/setting/ticket.uvue new file mode 100644 index 00000000..5f729ad6 --- /dev/null +++ b/pages/mall/admin/setting/ticket.uvue @@ -0,0 +1,23 @@ + + + + +