Merge remote-tracking branch 'origin/huangzhenbao-admin'
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<template>
|
||||
<view class="layout-root">
|
||||
<view v-if="!isAuthReady" class="auth-loading-overlay" style="flex: 1; display: flex; align-items: center; justify-content: center; height: 100vh; background-color: #f5f5f5;">
|
||||
<text style="color: #666; font-size: 16px;">身份鉴权中...</text>
|
||||
</view>
|
||||
<template v-else>
|
||||
<!-- 统一遮罩层 (复刻 CRMEB: 用于所有 Overlay 状态) -->
|
||||
<view
|
||||
class="mobile-mask"
|
||||
@@ -17,7 +21,7 @@
|
||||
@toggle="toggleMainAsideCollapse"
|
||||
@menu-click="onTopMenuClick"
|
||||
:asideWidth="ASIDE_W"
|
||||
/>
|
||||
></AdminAside>
|
||||
|
||||
<!-- 二级侧边栏 (1:1 复刻 CRMEB 抽屉/Dock 平滑切换) -->
|
||||
<AdminSubSider
|
||||
@@ -31,7 +35,7 @@
|
||||
:asideWidth="layoutMode === 'mobile' ? 0 : ASIDE_W"
|
||||
:width="SUB_W"
|
||||
@sub-click="onSubClick"
|
||||
/>
|
||||
></AdminSubSider>
|
||||
|
||||
<!-- 右侧内容区 -->
|
||||
<view
|
||||
@@ -46,7 +50,7 @@
|
||||
@search="onSearch"
|
||||
@refresh="onRefresh"
|
||||
@notify="onNotify"
|
||||
/>
|
||||
></AdminHeader>
|
||||
|
||||
<!-- 标签页 (CRMEB风格) - 移动端可以隐藏或滚动 -->
|
||||
<AdminTagsView
|
||||
@@ -58,25 +62,24 @@
|
||||
@close-other="onCloseOther"
|
||||
@close-all="onCloseAll"
|
||||
@refresh="onRefresh"
|
||||
/>
|
||||
></AdminTagsView>
|
||||
|
||||
<!-- 内容展示区 (内部路由渲染) -->
|
||||
<view class="content-scroll">
|
||||
<view class="content-inner" :class="{ 'is-mobile': isMobile }">
|
||||
<slot></slot>
|
||||
<component :is="currentComponent" v-if="!isPageLoading && currentComponent != null" />
|
||||
<AdminPageLoading v-if="isPageLoading" />
|
||||
<slot v-if="hasAccess"></slot>
|
||||
<component :is="currentComponent" v-if="hasAccess && !isPageLoading && currentComponent != null"></component>
|
||||
<AdminPageLoading v-if="isPageLoading"></AdminPageLoading>
|
||||
</view>
|
||||
<AdminFooter />
|
||||
<AdminFooter></AdminFooter>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ensureAnalyticsLogin } from '@/services/analytics/authGuard.uts'
|
||||
import { getCurrentUser } from '@/utils/store.uts'
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import AdminAside from '@/layouts/admin/components/AdminAside.uvue'
|
||||
import AdminSubSider from '@/layouts/admin/components/AdminSubSider.uvue'
|
||||
import AdminHeader from '@/layouts/admin/components/AdminHeader.uvue'
|
||||
@@ -113,11 +116,14 @@ import {
|
||||
closeAllTabs,
|
||||
toggleMainAsideCollapse as storeToggleCollapse,
|
||||
toggleSubSider as storeToggleSubSider,
|
||||
initNavState
|
||||
initNavState,
|
||||
restoreNavState
|
||||
} from '@/layouts/admin/store/adminNavStore.uts'
|
||||
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
|
||||
|
||||
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
|
||||
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
|
||||
import { ensureAdminSession, handleSessionExpired } from '@/layouts/admin/utils/adminAuth.uts'
|
||||
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
@@ -132,6 +138,13 @@ const SUB_W = 200
|
||||
|
||||
// 页面加载状态
|
||||
const isPageLoading = ref(false)
|
||||
const isAuthReady = ref(false)
|
||||
|
||||
const hasAccess = computed<boolean>(() => {
|
||||
|
||||
|
||||
return hasAdminModuleAccess(activeTopMenuId.value)
|
||||
})
|
||||
|
||||
const hasNotification = ref<boolean>(false)
|
||||
|
||||
@@ -260,6 +273,8 @@ const breadcrumb = computed<Array<{id: string, title: string}>>(() => {
|
||||
|
||||
// 当前渲染的组件
|
||||
const currentComponent = computed<any>(() => {
|
||||
|
||||
|
||||
const route = findRouteById(activeRouteId.value)
|
||||
if (!route) return null
|
||||
return getComponent(route.componentKey)
|
||||
@@ -383,22 +398,39 @@ function onNotify(): void {
|
||||
let resizeTid: any = null
|
||||
|
||||
onMounted(async () => {
|
||||
if (!ensureAnalyticsLogin({ toastTitle: '请先登录以访问管理后台' })) return
|
||||
|
||||
const profile = await getCurrentUser()
|
||||
const role = profile?.role
|
||||
if (!role || !['admin', 'analytics'].includes(role)) {
|
||||
uni.showToast({ title: '权限不足', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
}, 800)
|
||||
// 挂载鉴权相关的事件监听器,并在首次进入时阻断认证
|
||||
try {
|
||||
const isOk = await ensureAdminSession()
|
||||
if (!isOk) {
|
||||
return // 鉴权失败内部已处理跳转,终止后续挂载
|
||||
}
|
||||
} catch (e) {
|
||||
handleSessionExpired()
|
||||
return
|
||||
}
|
||||
|
||||
uni.$on('AUTH_SESSION_EXPIRED', () => { handleSessionExpired() })
|
||||
|
||||
initNavState()
|
||||
if (props.currentPage != '') {
|
||||
openRoute(props.currentPage as string)
|
||||
// 增加 visibilitychange 监听:在用户电脑休眠或者长时间放置重新激活时,核验会话
|
||||
// #ifdef H5
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
// #endif
|
||||
|
||||
isAuthReady.value = true
|
||||
|
||||
// 第二段:尝试从 sessionStorage 恢复上次打开的页签状态
|
||||
// restoreNavState 会校验路由有效性和权限,过滤无效项后重建 tabs
|
||||
const restored = restoreNavState()
|
||||
if (!restored) {
|
||||
// 首次打开 / 缓存为空 / 恢复失败 → 走默认初始化(只有首页tab)
|
||||
initNavState()
|
||||
if (props.currentPage != '') {
|
||||
openRoute(props.currentPage as string)
|
||||
}
|
||||
}
|
||||
// restored=true 时,activeRouteId 已由 restoreNavState 正确设置,无需额外跳转
|
||||
|
||||
// 初始化窗口宽度
|
||||
windowWidth.value = uni.getWindowInfo().windowWidth
|
||||
@@ -428,20 +460,48 @@ onMounted(async () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ===== Auth State Handlers =====
|
||||
let _lastVisibilityCheck = 0
|
||||
const handleVisibilityChange = async () => {
|
||||
// #ifdef H5
|
||||
if (document.visibilityState === 'visible') {
|
||||
const now = Date.now()
|
||||
// 节流机制,防止频繁切换标签页产生过多校验
|
||||
if (now - _lastVisibilityCheck < 10000) return
|
||||
_lastVisibilityCheck = now
|
||||
|
||||
// 简易验证即可,如果底层 token 已经失效而 fetch 拿不到,就会报出 401 触发全局 AUTH_SESSION_EXPIRED
|
||||
try {
|
||||
await ensureAdminSession()
|
||||
} catch(e) {}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('AUTH_SESSION_EXPIRED')
|
||||
// #ifdef H5
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-root {
|
||||
--admin-page-padding-desktop: 12px;
|
||||
--admin-page-padding-mobile: 8px;
|
||||
--admin-section-gap: 12px;
|
||||
--admin-card-padding: 16px;
|
||||
--admin-page-padding-desktop: 20px;
|
||||
--admin-page-padding-mobile: 10px;
|
||||
--admin-section-gap: 20px;
|
||||
--admin-card-padding: 24px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #f0f2f5;
|
||||
background: #f5f7f9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -485,7 +545,7 @@ onMounted(async () => {
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
transition: margin-left 300ms ease;
|
||||
background: #f0f2f5;
|
||||
background: #f5f7f9;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -515,7 +575,7 @@ onMounted(async () => {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
overflow-x: auto; /* 允许横向滚动,兼容极端窄屏 */
|
||||
background: #f0f2f5;
|
||||
background: #f5f7f9;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
# 🎉 CRMEB 路由系统清理完成
|
||||
|
||||
## 清理日期
|
||||
|
||||
2026年2月2日
|
||||
|
||||
## 清理内容
|
||||
|
||||
### 1. pages.json 配置清理
|
||||
|
||||
✅ **删除了整个 pages/mall/admin 子包配置**
|
||||
|
||||
- 移除:60+ 个旧管理页面配置
|
||||
- 减少:从 80+ KB → 12.4 KB
|
||||
- 保留:主入口 `pages/mall/admin/homePage/index`
|
||||
|
||||
**清理前的 subPackages:**
|
||||
|
||||
```json
|
||||
{
|
||||
"root": "pages/mall/admin",
|
||||
"pages": [
|
||||
{ "path": "content/index", ... },
|
||||
{ "path": "design/index", ... },
|
||||
{ "path": "user-management", ... },
|
||||
// ... 57 more pages ...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**清理后的 subPackages:**
|
||||
|
||||
- pages/mall/consumer (消费端)
|
||||
- pages/mall/delivery (配送端)
|
||||
- pages/mall/analytics (数据分析)
|
||||
- pages/mall/merchant (商家中心)
|
||||
- pages/mall/service (客服工作台)
|
||||
|
||||
### 2. 废弃文件删除
|
||||
|
||||
✅ **删除:`layouts/admin/utils/menu.uts`**
|
||||
|
||||
- 原因:使用旧路径格式(如 `/pages/mall/admin/user-management`)
|
||||
- 替代:adminRoutes.uts 使用规范路径(如 `/pages/mall/admin/user/list`)
|
||||
- 确认:无任何文件引用此文件
|
||||
|
||||
### 3. 代码重复清理(之前完成)
|
||||
|
||||
✅ **AdminLayout.uvue: 394行 → 227行**
|
||||
|
||||
- 删除:45+ 行重复的导航代码
|
||||
- 保留:纯 CRMEB 内部路由逻辑
|
||||
|
||||
## 警告说明
|
||||
|
||||
### Vue Router 警告(可安全忽略)
|
||||
|
||||
```
|
||||
[Vue Router warn]: No match found for location with path "/pages/mall/admin/user-management?action=config"
|
||||
```
|
||||
|
||||
**为什么出现:**
|
||||
|
||||
- uni-app-x 框架在初始化时检测到旧路由引用
|
||||
- 或某些历史代码尝试注册路由
|
||||
|
||||
**为什么可以忽略:**
|
||||
|
||||
- ✅ 管理后台使用**内部路由系统**(state-driven),不依赖 Vue Router
|
||||
- ✅ 路由切换通过 `openRoute()` 和 `<component :is="currentComponent" />` 实现
|
||||
- ✅ adminRoutes.uts 配置完整正确
|
||||
- ✅ 不影响功能运行
|
||||
|
||||
## 当前架构
|
||||
|
||||
### 路由系统文件结构
|
||||
|
||||
```
|
||||
layouts/admin/
|
||||
├── router/
|
||||
│ ├── adminRoutes.uts ← 核心路由配置(9个顶级菜单,30+路由)
|
||||
│ └── adminComponentMap.uts ← 组件映射(30+组件静态导入)
|
||||
├── store/
|
||||
│ └── adminNavStore.uts ← 导航状态管理(标签页、菜单选中)
|
||||
└── AdminLayout.uvue ← 布局容器(227行,纯净)
|
||||
```
|
||||
|
||||
### 路由配置示例
|
||||
|
||||
```typescript
|
||||
// adminRoutes.uts 中的正确格式
|
||||
{
|
||||
id: 'user_list',
|
||||
title: '用户管理',
|
||||
path: '/pages/mall/admin/user/list', // ✅ 规范路径
|
||||
componentKey: 'UserList',
|
||||
parentId: 'user',
|
||||
groupId: 'user-manage'
|
||||
}
|
||||
|
||||
// ❌ 旧 menu.uts 的错误格式(已删除)
|
||||
{
|
||||
id: 'user-list',
|
||||
title: '用户管理',
|
||||
path: '/pages/mall/admin/user-management' // ❌ 不规范
|
||||
}
|
||||
```
|
||||
|
||||
## 验证结果
|
||||
|
||||
### 文件系统
|
||||
|
||||
```powershell
|
||||
✅ pages.json: 526 lines, 12.4 KB
|
||||
✅ AdminLayout.uvue: 227 lines
|
||||
✅ adminRoutes.uts: 564 lines
|
||||
✅ 废弃文件已删除: menu.uts
|
||||
```
|
||||
|
||||
### 编译状态
|
||||
|
||||
```
|
||||
✅ JSON 语法: 正确
|
||||
✅ ESLint: 仅警告(vue/comment-directive),无致命错误
|
||||
✅ 500 错误: 已消除(Vite 不再预加载 60+ 旧页面)
|
||||
```
|
||||
|
||||
### 保留的 subPackages
|
||||
|
||||
```json
|
||||
{
|
||||
"subPackages": [
|
||||
{ "root": "pages/mall/consumer" }, // 消费端 (8页)
|
||||
{ "root": "pages/mall/delivery" }, // 配送端 (6页)
|
||||
{ "root": "pages/mall/analytics" }, // 数据分析 (5页)
|
||||
{ "root": "pages/mall/merchant" }, // 商家中心 (3页)
|
||||
{ "root": "pages/mall/service" } // 客服 (3页)
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 系统运行说明
|
||||
|
||||
### 管理后台路由流程
|
||||
|
||||
1. **入口加载**: `pages/mall/admin/homePage/index` → AdminLayout.uvue
|
||||
2. **内部路由**: adminNavStore.openRoute() → 更新 activeRouteId
|
||||
3. **组件切换**: computed currentComponent → adminComponentMap.get(componentKey)
|
||||
4. **渲染**: `<component :is="currentComponent" />`
|
||||
|
||||
### 无需 pages.json 配置
|
||||
|
||||
管理后台的所有 30+ 页面路由都通过内部路由系统管理,**不需要在 pages.json 中配置**。这就是为什么可以安全删除 pages/mall/admin 子包配置。
|
||||
|
||||
### 标签页系统
|
||||
|
||||
- 默认固定: 首页(home_index)
|
||||
- 动态添加: 点击菜单时自动添加到 tabs 数组
|
||||
- 状态持久: ref/computed 响应式管理
|
||||
|
||||
## 下一步测试
|
||||
|
||||
### 建议测试流程
|
||||
|
||||
1. **启动开发服务器**
|
||||
|
||||
```bash
|
||||
npm run dev:h5
|
||||
```
|
||||
|
||||
2. **检查浏览器控制台**
|
||||
- 应该没有 404/500 错误
|
||||
- Vue Router 警告可忽略(一次性,不影响功能)
|
||||
|
||||
3. **功能测试**
|
||||
- ✅ 顶部菜单切换(9个菜单)
|
||||
- ✅ 侧边栏导航
|
||||
- ✅ 标签页操作(打开/关闭)
|
||||
- ✅ 组件渲染(30+ PlaceholderPage)
|
||||
|
||||
4. **性能验证**
|
||||
- 页面加载速度(不再预加载 60+ 无用页面)
|
||||
- 内存占用(静态组件映射)
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **已完成:**
|
||||
|
||||
- pages.json 清理(删除 60+ 页配置,减少 70KB)
|
||||
- AdminLayout.uvue 代码去重(删除 45+ 行)
|
||||
- 废弃文件删除(menu.uts)
|
||||
- 架构统一(全部使用 adminRoutes.uts)
|
||||
|
||||
🎯 **核心优势:**
|
||||
|
||||
- **内部路由系统**:不依赖 uni.navigateTo() 或 Vue Router
|
||||
- **状态驱动**:ref/computed 实现响应式路由
|
||||
- **静态映射**:所有组件预导入(uni-app-x 限制)
|
||||
- **CRMEB 1:1**:完整复刻 CRMEB v5 路由体系
|
||||
|
||||
🔍 **可安全忽略的警告:**
|
||||
|
||||
- Vue Router 警告(框架初始化时的历史遗留检测)
|
||||
- vue/comment-directive ESLint 警告(代码注释格式)
|
||||
|
||||
---
|
||||
|
||||
**🎊 路由系统清理完成!系统已就绪可供测试。**
|
||||
@@ -53,11 +53,15 @@ function getIconText(icon: string): string {
|
||||
'product': '📦',
|
||||
'order': '📜',
|
||||
'marketing': '📉',
|
||||
'content': '📝',
|
||||
'share': '✈️',
|
||||
'customer-service': '💬',
|
||||
'finance': '💰',
|
||||
'statistic': '📊',
|
||||
'content': '📝',
|
||||
'decoration': '🎨',
|
||||
'app': '📱',
|
||||
'setting': '⚙️',
|
||||
'maintenance': '🛠️'
|
||||
'tool': '🛠️',
|
||||
'statistic': '📊'
|
||||
}
|
||||
return iconMap[icon] || icon.charAt(0).toUpperCase()
|
||||
}
|
||||
|
||||
@@ -1,47 +1,142 @@
|
||||
<template>
|
||||
<view class="header">
|
||||
<view class="header-left">
|
||||
<!-- 移动端菜单切换按钮 (CSS 控制显隐) -->
|
||||
<!-- 移动端菜单切换按钮(CSS 控制显隐) -->
|
||||
<view class="menu-toggle mobile-only" @click="onToggleSubSider">
|
||||
<text class="menu-icon">☰</text>
|
||||
</view>
|
||||
|
||||
<!-- Desktop/Tablet Hamburger (1:1 复刻 CRMEB 切换二级侧边栏) -->
|
||||
<!-- Desktop/Tablet Hamburger -->
|
||||
<view class="menu-toggle desktop-only" @click="onToggleSubSider">
|
||||
<text class="menu-icon">☰</text>
|
||||
</view>
|
||||
|
||||
<view class="breadcrumb-container desktop-only">
|
||||
<text class="crumb" v-for="(item, index) in breadcrumb" :key="item.id">
|
||||
<text class="crumb" v-for="(item, index) in breadcrumb" :key="item.id">
|
||||
{{ item.title }}
|
||||
<text v-if="index < breadcrumb.length - 1" class="separator"> / </text>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 移动端简单标题 (CSS 控制显隐) -->
|
||||
|
||||
<text class="mobile-title mobile-only">{{ currentTitle }}</text>
|
||||
</view>
|
||||
|
||||
<view class="header-right">
|
||||
<view class="hbtn" @click="$emit('search')"><text>🔍</text></view>
|
||||
<view v-if="!isMobile" class="hbtn" @click="$emit('refresh')"><text>⟳</text></view>
|
||||
<view class="hbtn" @click="$emit('search')"><text>🔍</text></view>
|
||||
<view v-if="!isMobile" class="hbtn" @click="$emit('refresh')"><text>🔄</text></view>
|
||||
<view class="hbtn" @click="$emit('notify')">
|
||||
<text>🔔</text>
|
||||
<view class="dot" v-if="hasNotification"></view>
|
||||
</view>
|
||||
|
||||
<!-- 用户个人中心 / 下拉菜单 -->
|
||||
<view class="admin-user-menu"
|
||||
@mouseenter="handleUserMenuOver"
|
||||
@mouseleave="handleUserMenuLeave"
|
||||
>
|
||||
<view class="admin-user-trigger" @click="toggleUserMenu">
|
||||
<text class="user-name">{{ userName }}</text>
|
||||
<text class="user-arrow">▼</text>
|
||||
</view>
|
||||
|
||||
<view v-if="showUserMenu" class="admin-user-dropdown">
|
||||
<view class="admin-user-dropdown-item" @click.stop="goToUserCenter">
|
||||
<text class="admin-user-dropdown-text">个人中心</text>
|
||||
</view>
|
||||
<view class="admin-user-dropdown-item" @click.stop="handleLogout">
|
||||
<text class="admin-user-dropdown-text">退出登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
toggleSubSider,
|
||||
showSubSider,
|
||||
layoutMode,
|
||||
import { ref, computed } from 'vue'
|
||||
import {
|
||||
toggleSubSider,
|
||||
layoutMode,
|
||||
isOverlayVisible,
|
||||
isMobileMenuOpen
|
||||
isMobileMenuOpen,
|
||||
openRoute
|
||||
} from '@/layouts/admin/store/adminNavStore.uts'
|
||||
import { state, logout } from '@/utils/store.uts'
|
||||
import { clearAdminRoleCache, getCurrentAdminRole } from '@/layouts/admin/utils/role.uts'
|
||||
|
||||
const showUserMenu = ref(false)
|
||||
const userName = computed((): string => {
|
||||
if (state.userProfile?.username != null && state.userProfile!.username != '') return state.userProfile!.username as string
|
||||
if (state.authUser != null) {
|
||||
if (state.authUser!.getString('email') != null && state.authUser!.getString('email') != '') return state.authUser!.getString('email') as string
|
||||
if (state.authUser!.getString('phone') != null && state.authUser!.getString('phone') != '') return state.authUser!.getString('phone') as string
|
||||
if (state.authUser!.getString('id') != null && state.authUser!.getString('id') != '') return (state.authUser!.getString('id') as string).substring(0, 8)
|
||||
}
|
||||
return '未知用户'
|
||||
})
|
||||
|
||||
let hideMenuTimer: number | null = null
|
||||
|
||||
function handleUserMenuOver(e: any) {
|
||||
// #ifdef H5
|
||||
if (hideMenuTimer !== null) {
|
||||
clearTimeout(hideMenuTimer as number)
|
||||
hideMenuTimer = null
|
||||
}
|
||||
showUserMenu.value = true
|
||||
// #endif
|
||||
}
|
||||
|
||||
function handleUserMenuLeave(e: any) {
|
||||
// #ifdef H5
|
||||
if (hideMenuTimer !== null) {
|
||||
clearTimeout(hideMenuTimer as number)
|
||||
}
|
||||
hideMenuTimer = setTimeout(() => {
|
||||
showUserMenu.value = false
|
||||
hideMenuTimer = null
|
||||
}, 150) as number
|
||||
// #endif
|
||||
}
|
||||
|
||||
function toggleUserMenu(e: any) {
|
||||
if (e && typeof e.stopPropagation === 'function') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
showUserMenu.value = !showUserMenu.value
|
||||
}
|
||||
|
||||
function goToUserCenter(e: any) {
|
||||
if (e && typeof e.stopPropagation === 'function') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
showUserMenu.value = false
|
||||
openRoute('home_user_center')
|
||||
}
|
||||
|
||||
function handleLogout(e: any) {
|
||||
if (e && typeof e.stopPropagation === 'function') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
showUserMenu.value = false
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
logout()
|
||||
clearAdminRoleCache()
|
||||
uni.removeStorageSync('token')
|
||||
// Force the layout cleanup to wait for the dialog to disappear
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
breadcrumb: Array<{id: string, title: string}>
|
||||
@@ -56,12 +151,6 @@ defineEmits<{
|
||||
(e:'toggle-mobile-menu'): void
|
||||
}>()
|
||||
|
||||
/**
|
||||
* 核心切换逻辑:
|
||||
* 1. Desktop: 切换 showSubSider (Dock状态)
|
||||
* 2. Tablet: 切换 isOverlayVisible (Overlay状态)
|
||||
* 3. Mobile: 切换 isMobileMenuOpen (Mobile Aside)
|
||||
*/
|
||||
function onToggleSubSider(): void {
|
||||
if (layoutMode.value === 'desktop') {
|
||||
toggleSubSider()
|
||||
@@ -81,13 +170,16 @@ const currentTitle = computed((): string => {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.header{
|
||||
.header {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
height: 56px;
|
||||
background:#fff;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
}
|
||||
@@ -105,6 +197,7 @@ const currentTitle = computed((): string => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
@@ -124,9 +217,9 @@ const currentTitle = computed((): string => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.crumb {
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
.crumb {
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 响应式控制 */
|
||||
@@ -151,29 +244,108 @@ const currentTitle = computed((): string => {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.header-right{
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
gap: 10px;
|
||||
.header-right {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
.hbtn{
|
||||
|
||||
.hbtn {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
background:#f6f7fb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f6f7fb;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dot{
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background:#ff4d4f;
|
||||
position:absolute;
|
||||
background: #ff4d4f;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
}
|
||||
|
||||
/* 后台用户菜单部分 */
|
||||
.admin-user-menu {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 100%; /* 和 header 高度一致,确保悬停不中断 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.admin-user-trigger {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-arrow {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.admin-user-dropdown {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
right: 0;
|
||||
min-width: 120px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
|
||||
z-index: 10000;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.admin-user-dropdown-item {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.admin-user-dropdown-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
.admin-user-dropdown-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
.admin-user-dropdown-item:hover .admin-user-dropdown-text {
|
||||
color: #1890ff;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
</style>
|
||||
|
||||
@@ -6,25 +6,180 @@
|
||||
* value: 组件引用
|
||||
*
|
||||
* 注意:
|
||||
* 1. 组件已切换为 defineAsyncComponent 异步导入,优化 H5 环境下的加载性能与包体积
|
||||
* 1. 组件已切换为 静态导入 (Static Import),以解决 H5 环境下的加载异常 (net::ERR_CACHE_READ_FAILURE)
|
||||
* 2. 组件路径使用 @ 别名
|
||||
* 3. 占位组件统一使用 PlaceholderPage
|
||||
*/
|
||||
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
// 导入占位组件
|
||||
import PlaceholderPage from '@/layouts/admin/components/PlaceholderPage.uvue'
|
||||
|
||||
// 导入首页(内部组件,不包含 AdminLayout)
|
||||
import HomeIndex from '@/layouts/admin/pages/HomeIndex.uvue'
|
||||
import UserCenter from '@/pages/mall/admin/userCenter/index.uvue'
|
||||
|
||||
// 用户、商品、订单模块已改为 defineAsyncComponent 异步加载,移除静态导入以优化 H5 加载性能
|
||||
// --- 店铺模块 ---
|
||||
import ShopManage from '@/pages/mall/admin/shop/manage.uvue'
|
||||
import ShopCreate from '@/pages/mall/admin/shop/create.uvue'
|
||||
|
||||
// 营销设置模块暂时使用 PlaceholderPage
|
||||
// 避免循环依赖问题
|
||||
// --- 用户模块 ---
|
||||
import UserStatistic from '@/pages/mall/admin/user/statistics/index.uvue'
|
||||
import UserList from '@/pages/mall/admin/user/management/index.uvue'
|
||||
import UserLevel from '@/pages/mall/admin/user/level/index.uvue'
|
||||
import UserGroup from '@/pages/mall/admin/user/grouping/index.uvue'
|
||||
import UserLabel from '@/pages/mall/admin/user/label/index.uvue'
|
||||
import UserMemberConfig from '@/pages/mall/admin/user/config/index.uvue'
|
||||
|
||||
// 营销、内容、财务、客服、装修等模块已改为 defineAsyncComponent 异步加载,移除静态导入以优化 H5 加载性能
|
||||
// --- 商品模块 ---
|
||||
import ProductStatistic from '@/pages/mall/admin/product/product-statistics/index.uvue'
|
||||
import ProductList from '@/pages/mall/admin/product/product-management/index.uvue'
|
||||
import ProductEdit from '@/pages/mall/admin/product/product-management/components/edit.uvue'
|
||||
import ProductMemberPrice from '@/pages/mall/admin/product/product-management/components/member-price.uvue'
|
||||
import ProductClassify from '@/pages/mall/admin/product/classification/index.uvue'
|
||||
import ProductReply from '@/pages/mall/admin/product/reviews/index.uvue'
|
||||
import ProductAttr from '@/pages/mall/admin/product/specifications/index.uvue'
|
||||
import ProductParam from '@/pages/mall/admin/product/parameters/index.uvue'
|
||||
import ProductLabel from '@/pages/mall/admin/product/labels/index.uvue'
|
||||
import ProductProtection from '@/pages/mall/admin/product/protection/index.uvue'
|
||||
|
||||
// --- 订单模块 ---
|
||||
import OrderList from '@/pages/mall/admin/order/order-management/index.uvue'
|
||||
import OrderStatistic from '@/pages/mall/admin/order/order-statistics/index.uvue'
|
||||
import OrderRefund from '@/pages/mall/admin/order/aftersales-order/index.uvue'
|
||||
import OrderCashier from '@/pages/mall/admin/order/cashier-order/index.uvue'
|
||||
import OrderVerify from '@/pages/mall/admin/order/write-off-records/index.uvue'
|
||||
import OrderConfig from '@/pages/mall/admin/order/order-configuration/index.uvue'
|
||||
|
||||
// --- 营销模块 ---
|
||||
import MarketingCouponList from '@/pages/mall/admin/marketing/coupon/coupon-list/index.uvue'
|
||||
import MarketingCouponUser from '@/pages/mall/admin/marketing/coupon/claim-record/index.uvue'
|
||||
import MarketingIntegralStatistic from '@/pages/mall/admin/marketing/points/statistics/index.uvue'
|
||||
import MarketingIntegralProduct from '@/pages/mall/admin/marketing/points/products/index.uvue'
|
||||
import MarketingIntegralOrder from '@/pages/mall/admin/marketing/points/orders/index.uvue'
|
||||
import MarketingIntegralRecord from '@/pages/mall/admin/marketing/points/record/index.uvue'
|
||||
import MarketingIntegralConfig from '@/pages/mall/admin/marketing/points/config/index.uvue'
|
||||
import MarketingLotteryList from '@/pages/mall/admin/marketing/lottery/list/index.uvue'
|
||||
import MarketingLotteryConfig from '@/pages/mall/admin/marketing/lottery/configuration/index.uvue'
|
||||
import MarketingBargainProduct from '@/pages/mall/admin/marketing/bargain/products/index.uvue'
|
||||
import MarketingBargainList from '@/pages/mall/admin/marketing/bargain/list/index.uvue'
|
||||
import MarketingCombinationProduct from '@/pages/mall/admin/marketing/combination/products/index.uvue'
|
||||
import MarketingCombinationList from '@/pages/mall/admin/marketing/combination/list/index.uvue'
|
||||
import MarketingCombinationCreate from '@/pages/mall/admin/marketing/combination/index.uvue'
|
||||
import MarketingSeckillList from '@/pages/mall/admin/marketing/seckill/list/index.uvue'
|
||||
import MarketingSeckillProduct from '@/pages/mall/admin/marketing/seckill/products/index.uvue'
|
||||
import MarketingSeckillConfig from '@/pages/mall/admin/marketing/seckill/config/index.uvue'
|
||||
import MarketingMemberType from '@/pages/mall/admin/marketing/member/type/index.uvue'
|
||||
import MarketingMemberRight from '@/pages/mall/admin/marketing/member/right/index.uvue'
|
||||
import MarketingMemberCard from '@/pages/mall/admin/marketing/member/kami-membership/index.uvue'
|
||||
import MarketingMemberRecord from '@/pages/mall/admin/marketing/member/record/index.uvue'
|
||||
import MarketingMemberConfig from '@/pages/mall/admin/marketing/member/config/index.uvue'
|
||||
import MarketingLiveRoom from '@/pages/mall/admin/marketing/live/live-management/index.uvue'
|
||||
import MarketingLiveProduct from '@/pages/mall/admin/marketing/live/products-management/index.uvue'
|
||||
import MarketingLiveAnchor from '@/pages/mall/admin/marketing/live/streamer-management/index.uvue'
|
||||
import MarketingRechargeQuota from '@/pages/mall/admin/marketing/recharge/amount-setting/index.uvue'
|
||||
import MarketingRechargeConfig from '@/pages/mall/admin/marketing/recharge/config/index.uvue'
|
||||
import MarketingCheckinConfig from '@/pages/mall/admin/marketing/checkin/config/index.uvue'
|
||||
import MarketingCheckinReward from '@/pages/mall/admin/marketing/checkin/reward/index.uvue'
|
||||
import MarketingNewcomerGift from '@/pages/mall/admin/marketing/newcomer/index.uvue'
|
||||
import MarketingStatisticIndex from '@/pages/mall/admin/marketing/marketing-statistics/index.uvue'
|
||||
|
||||
// --- 内容模块 ---
|
||||
import CmsArticle from '@/pages/mall/admin/cms/article/index.uvue'
|
||||
import CmsCategory from '@/pages/mall/admin/cms/category/index.uvue'
|
||||
|
||||
// --- 财务模块 ---
|
||||
import FinanceTransactionStats from '@/pages/mall/admin/finance/transaction-statistics/index.uvue'
|
||||
import FinanceWithdrawal from '@/pages/mall/admin/finance/finance-operations/request/index.uvue'
|
||||
import FinanceInvoice from '@/pages/mall/admin/finance/finance-operations/management/index.uvue'
|
||||
import FinanceRecharge from '@/pages/mall/admin/finance/finance-record/recharge-record/index.uvue'
|
||||
import FinanceCapitalFlow from '@/pages/mall/admin/finance/finance-record/flow/index.uvue'
|
||||
import FinanceBill from '@/pages/mall/admin/finance/finance-record/billing-record/index.uvue'
|
||||
import FinanceCommission from '@/pages/mall/admin/finance/commission-record/index.uvue'
|
||||
import FinanceBalanceStats from '@/pages/mall/admin/finance/balance-record/statistics/index.uvue'
|
||||
import FinanceBalanceRecord from '@/pages/mall/admin/finance/balance-record/record/index.uvue'
|
||||
|
||||
// --- 设置模块 ---
|
||||
import SettingSystemConfig from '@/pages/mall/admin/setting/system/index.uvue'
|
||||
import SettingMessageIndex from '@/pages/mall/admin/setting/message/index.uvue'
|
||||
import SettingProtocolIndex from '@/pages/mall/admin/setting/agreement/index.uvue'
|
||||
import SettingTicketIndex from '@/pages/mall/admin/setting/receipt/index.uvue'
|
||||
import SettingAuthRole from '@/pages/mall/admin/setting/auth/role-management/index.uvue'
|
||||
import SettingAuthAdmin from '@/pages/mall/admin/setting/auth/admin-management/index.uvue'
|
||||
import SettingAuthPermission from '@/pages/mall/admin/setting/auth/menu-management/index.uvue'
|
||||
import SettingDeliveryStaff from '@/pages/mall/admin/setting/delivery/management/index.uvue'
|
||||
import SettingDeliveryStation from '@/pages/mall/admin/setting/delivery/setting/station/index.uvue'
|
||||
import SettingDeliveryVerifier from '@/pages/mall/admin/setting/delivery/setting/verifier/index.uvue'
|
||||
import SettingDeliveryTemplate from '@/pages/mall/admin/setting/delivery/setting/template/index.uvue'
|
||||
import SettingInterfaceOnepassConfig from '@/pages/mall/admin/setting/interface/onepass/config/index.uvue'
|
||||
import SettingInterfaceOnepassIndex from '@/pages/mall/admin/setting/interface/onepass/index.uvue'
|
||||
import SettingInterfaceStorage from '@/pages/mall/admin/setting/interface/storage/index.uvue'
|
||||
import SettingInterfaceCollect from '@/pages/mall/admin/setting/interface/collect/index.uvue'
|
||||
import SettingInterfaceLogistics from '@/pages/mall/admin/setting/interface/logistics/index.uvue'
|
||||
import SettingInterfaceESheet from '@/pages/mall/admin/setting/interface/e-sheet/index.uvue'
|
||||
import SettingInterfaceSms from '@/pages/mall/admin/setting/interface/sms/index.uvue'
|
||||
import SettingInterfacePayment from '@/pages/mall/admin/setting/interface/payment/index.uvue'
|
||||
|
||||
// --- 分销模块 ---
|
||||
import DistributionPromoter from '@/pages/mall/admin/distribution/distributor-management/index.uvue'
|
||||
import DistributionLevel from '@/pages/mall/admin/distribution/level/index.uvue'
|
||||
import DistributionSetting from '@/pages/mall/admin/distribution/setting/index.uvue'
|
||||
import DivisionList from '@/pages/mall/admin/distribution/business-division/business-division-list/index.uvue'
|
||||
import DivisionAgent from '@/pages/mall/admin/distribution/business-division/agent-list/index.uvue'
|
||||
import DivisionApply from '@/pages/mall/admin/distribution/business-division/agent-application/index.uvue'
|
||||
|
||||
// --- 客服模块 ---
|
||||
import KefuList from '@/pages/mall/admin/kefu/list/index.uvue'
|
||||
import KefuWords from '@/pages/mall/admin/kefu/rhetoric/index.uvue'
|
||||
import KefuFeedback from '@/pages/mall/admin/kefu/user-message/index.uvue'
|
||||
import KefuAutoReply from '@/pages/mall/admin/kefu/auto-reply/index.uvue'
|
||||
import KefuConfig from '@/pages/mall/admin/kefu/config/index.uvue'
|
||||
|
||||
// --- 装修模块 ---
|
||||
import DecorationHome from '@/pages/mall/admin/decoration/homepage-decoration/index.uvue'
|
||||
import DecorationCategory from '@/pages/mall/admin/decoration/product-category/index.uvue'
|
||||
import DecorationUser from '@/pages/mall/admin/decoration/personal-center/index.uvue'
|
||||
import DecorationData from '@/pages/mall/admin/decoration/data-config/index.uvue'
|
||||
import DecorationStyle from '@/pages/mall/admin/decoration/theme-style/index.uvue'
|
||||
import DecorationMaterial from '@/pages/mall/admin/decoration/material-management/index.uvue'
|
||||
import DecorationLink from '@/pages/mall/admin/decoration/link-management/index.uvue'
|
||||
|
||||
// --- 应用模块 ---
|
||||
import AppWechatMenu from '@/pages/mall/admin/app/wechat/menu/index.uvue'
|
||||
import AppWechatNews from '@/pages/mall/admin/app/wechat/management/index.uvue'
|
||||
import AppWechatReplyFollow from '@/pages/mall/admin/app/wechat/reply/follow/index.uvue'
|
||||
import AppWechatReplyKeyword from '@/pages/mall/admin/app/wechat/reply/keyword/index.uvue'
|
||||
import AppWechatReplyInvalid from '@/pages/mall/admin/app/wechat/reply/invalid/index.uvue'
|
||||
import AppWechatConfig from '@/pages/mall/admin/app/wechat/config/index.uvue'
|
||||
import AppRoutineDownload from '@/pages/mall/admin/app/routine/download/index.uvue'
|
||||
import AppRoutineConfig from '@/pages/mall/admin/app/routine/config/index.uvue'
|
||||
import AppMobileConfig from '@/pages/mall/admin/app/mobile/config/index.uvue'
|
||||
import AppMobileVersion from '@/pages/mall/admin/app/mobile/version/index.uvue'
|
||||
import AppPcDesign from '@/pages/mall/admin/app/pc/design/index.uvue'
|
||||
import AppPcConfig from '@/pages/mall/admin/app/pc/config/index.uvue'
|
||||
|
||||
// --- 维护模块 ---
|
||||
import MaintainDevConfig from '@/pages/mall/admin/maintain/dev-config/category/index.uvue'
|
||||
import MaintainDevData from '@/pages/mall/admin/maintain/dev-config/combination-data/index.uvue'
|
||||
import MaintainDevTask from '@/pages/mall/admin/maintain/dev-config/cron-job/index.uvue'
|
||||
import MaintainDevAuth from '@/pages/mall/admin/maintain/dev-config/permission/index.uvue'
|
||||
import MaintainDevModule from '@/pages/mall/admin/maintain/dev-config/module-config/index.uvue'
|
||||
import MaintainDevEvent from '@/pages/mall/admin/maintain/dev-config/custom-event/index.uvue'
|
||||
import MaintainSecurityCache from '@/pages/mall/admin/maintain/security/refresh-cache/index.uvue'
|
||||
import MaintainSecurityLog from '@/pages/mall/admin/maintain/security/system-log/index.uvue'
|
||||
import MaintainSecurityUpgrade from '@/pages/mall/admin/maintain/security/online-upgrade/index.uvue'
|
||||
import MaintainDataLogistics from '@/pages/mall/admin/maintain/data/logistics/index.uvue'
|
||||
import MaintainDataCity from '@/pages/mall/admin/maintain/data/city-data/index.uvue'
|
||||
import MaintainDataClear from '@/pages/mall/admin/maintain/data/clear/index.uvue'
|
||||
import MaintainApiAccount from '@/pages/mall/admin/maintain/api/account/index.uvue'
|
||||
import MaintainLangList from '@/pages/mall/admin/maintain/lang/list/index.uvue'
|
||||
import MaintainLangDetail from '@/pages/mall/admin/maintain/lang/detail/index.uvue'
|
||||
import MaintainLangRegion from '@/pages/mall/admin/maintain/lang/region/index.uvue'
|
||||
import MaintainLangConfig from '@/pages/mall/admin/maintain/lang/config/index.uvue'
|
||||
import MaintainToolDb from '@/pages/mall/admin/maintain/dev-tools/database/index.uvue'
|
||||
import MaintainToolFile from '@/pages/mall/admin/maintain/dev-tools/file/index.uvue'
|
||||
import MaintainToolApi from '@/pages/mall/admin/maintain/dev-tools/interface/index.uvue'
|
||||
import MaintainToolDic from '@/pages/mall/admin/maintain/dev-tools/data-dictionary/index.uvue'
|
||||
import MaintainSysInfo from '@/pages/mall/admin/maintain/sys/info/index.uvue'
|
||||
|
||||
/**
|
||||
* 组件映射表
|
||||
@@ -32,180 +187,175 @@ import HomeIndex from '@/layouts/admin/pages/HomeIndex.uvue'
|
||||
export const componentMap: Map<string, any> = new Map([
|
||||
// 首页
|
||||
['HomeIndex', HomeIndex],
|
||||
|
||||
['UserCenter', UserCenter],
|
||||
|
||||
// 店铺模块
|
||||
['ShopManage', ShopManage],
|
||||
['ShopCreate', ShopCreate],
|
||||
|
||||
// 用户模块
|
||||
['UserStatistic', defineAsyncComponent(() => import('@/pages/mall/admin/user/statistics/index.uvue'))],
|
||||
['UserList', defineAsyncComponent(() => import('@/pages/mall/admin/user/management/index.uvue'))],
|
||||
['UserLevel', defineAsyncComponent(() => import('@/pages/mall/admin/user/level/index.uvue'))],
|
||||
['UserGroup', defineAsyncComponent(() => import('@/pages/mall/admin/user/grouping/index.uvue'))],
|
||||
['UserLabel', defineAsyncComponent(() => import('@/pages/mall/admin/user/label/index.uvue'))],
|
||||
['UserMemberConfig', defineAsyncComponent(() => import('@/pages/mall/admin/user/configuration/index.uvue'))],
|
||||
['UserStatistic', UserStatistic],
|
||||
['UserList', UserList],
|
||||
['UserLevel', UserLevel],
|
||||
['UserGroup', UserGroup],
|
||||
['UserLabel', UserLabel],
|
||||
['UserMemberConfig', UserMemberConfig],
|
||||
|
||||
// 商品模块
|
||||
['ProductStatistic', defineAsyncComponent(() => import('@/pages/mall/admin/product/product-statistics/index.uvue'))],
|
||||
['ProductList', defineAsyncComponent(() => import('@/pages/mall/admin/product/product-management/index.uvue'))],
|
||||
['ProductEdit', defineAsyncComponent(() => import('@/pages/mall/admin/product/product-management/edit.uvue'))],
|
||||
['ProductMemberPrice', defineAsyncComponent(() => import('@/pages/mall/admin/product/product-management/member-price.uvue'))],
|
||||
['ProductClassify', defineAsyncComponent(() => import('@/pages/mall/admin/product/classification/index.uvue'))],
|
||||
['ProductReply', defineAsyncComponent(() => import('@/pages/mall/admin/product/reviews/index.uvue'))],
|
||||
['ProductAttr', defineAsyncComponent(() => import('@/pages/mall/admin/product/specifications/index.uvue'))],
|
||||
['ProductParam', defineAsyncComponent(() => import('@/pages/mall/admin/product/parameters/index.uvue'))],
|
||||
['ProductLabel', defineAsyncComponent(() => import('@/pages/mall/admin/product/labels/index.uvue'))],
|
||||
['ProductProtection', defineAsyncComponent(() => import('@/pages/mall/admin/product/protection/index.uvue'))],
|
||||
['ProductStatistic', ProductStatistic],
|
||||
['ProductList', ProductList],
|
||||
['ProductEdit', ProductEdit],
|
||||
['ProductMemberPrice', ProductMemberPrice],
|
||||
['ProductClassify', ProductClassify],
|
||||
['ProductReply', ProductReply],
|
||||
['ProductAttr', ProductAttr],
|
||||
['ProductParam', ProductParam],
|
||||
['ProductLabel', ProductLabel],
|
||||
['ProductProtection', ProductProtection],
|
||||
|
||||
// 订单模块
|
||||
['OrderList', defineAsyncComponent(() => import('@/pages/mall/admin/order/order-management/index.uvue'))],
|
||||
['OrderStatistic', defineAsyncComponent(() => import('@/pages/mall/admin/order/order-statistics/index.uvue'))],
|
||||
['OrderRefund', defineAsyncComponent(() => import('@/pages/mall/admin/order/aftersales-order/index.uvue'))],
|
||||
['OrderCashier', defineAsyncComponent(() => import('@/pages/mall/admin/order/cashier-order/index.uvue'))],
|
||||
['OrderVerify', defineAsyncComponent(() => import('@/pages/mall/admin/order/write-off-records/index.uvue'))],
|
||||
['OrderConfig', defineAsyncComponent(() => import('@/pages/mall/admin/order/order-configuration/index.uvue'))],
|
||||
['OrderList', OrderList],
|
||||
['OrderStatistic', OrderStatistic],
|
||||
['OrderRefund', OrderRefund],
|
||||
['OrderCashier', OrderCashier],
|
||||
['OrderVerify', OrderVerify],
|
||||
['OrderConfig', OrderConfig],
|
||||
|
||||
// 营销模块已改为异步加载
|
||||
// 1. 优惠券
|
||||
['MarketingCouponList', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/coupon/list.uvue'))],
|
||||
['MarketingCouponUser', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/coupon/user.uvue'))],
|
||||
// 2. 积分管理
|
||||
['MarketingIntegralStatistic', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/integral/statistic.uvue'))],
|
||||
['MarketingIntegralProduct', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/integral/list.uvue'))],
|
||||
['MarketingIntegralOrder', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/integral/order.uvue'))],
|
||||
['MarketingIntegralRecord', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/integral/record.uvue'))],
|
||||
['MarketingIntegralConfig', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/integral/config.uvue'))],
|
||||
// 3. 抽奖管理
|
||||
['MarketingLotteryList', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/lottery/list.uvue'))],
|
||||
['MarketingLotteryConfig', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/lottery/config.uvue'))],
|
||||
// 4. 砍价管理
|
||||
['MarketingBargainProduct', PlaceholderPage],
|
||||
['MarketingBargainList', PlaceholderPage],
|
||||
// 5. 拼团管理
|
||||
['MarketingCombinationProduct', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/combination/product.uvue'))],
|
||||
['MarketingCombinationList', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/combination/list.uvue'))],
|
||||
['MarketingCombinationCreate', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/combination/create.uvue'))],
|
||||
// 6. 秒杀管理
|
||||
['MarketingSeckillList', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/seckill/list.uvue'))],
|
||||
['MarketingSeckillProduct', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/seckill/product.uvue'))],
|
||||
['MarketingSeckillConfig', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/seckill/config.uvue'))],
|
||||
// 7. 付费会员
|
||||
['MarketingMemberType', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/member/type.uvue'))],
|
||||
['MarketingMemberRight', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/member/right.uvue'))],
|
||||
['MarketingMemberCard', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/member/card.uvue'))],
|
||||
['MarketingMemberRecord', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/member/record.uvue'))],
|
||||
['MarketingMemberConfig', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/member/config.uvue'))],
|
||||
// 8. 直播管理
|
||||
['MarketingLiveRoom', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/live/room.uvue'))],
|
||||
['MarketingLiveProduct', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/live/product.uvue'))],
|
||||
['MarketingLiveAnchor', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/live/anchor.uvue'))],
|
||||
// 9. 用户充值
|
||||
['MarketingRechargeQuota', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/recharge/quota.uvue'))],
|
||||
['MarketingRechargeConfig', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/recharge/config.uvue'))],
|
||||
// 10. 每日签到
|
||||
['MarketingCheckinConfig', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/checkin/config.uvue'))],
|
||||
['MarketingCheckinReward', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/checkin/reward.uvue'))],
|
||||
// 11. 渠道码 & 新人礼
|
||||
// 营销模块
|
||||
['MarketingCouponList', MarketingCouponList],
|
||||
['MarketingCouponUser', MarketingCouponUser],
|
||||
['MarketingIntegralStatistic', MarketingIntegralStatistic],
|
||||
['MarketingIntegralProduct', MarketingIntegralProduct],
|
||||
['MarketingIntegralOrder', MarketingIntegralOrder],
|
||||
['MarketingIntegralRecord', MarketingIntegralRecord],
|
||||
['MarketingIntegralConfig', MarketingIntegralConfig],
|
||||
['MarketingLotteryList', MarketingLotteryList],
|
||||
['MarketingLotteryConfig', MarketingLotteryConfig],
|
||||
['MarketingBargainProduct', MarketingBargainProduct],
|
||||
['MarketingBargainList', MarketingBargainList],
|
||||
['MarketingCombinationProduct', MarketingCombinationProduct],
|
||||
['MarketingCombinationList', MarketingCombinationList],
|
||||
['MarketingCombinationCreate', MarketingCombinationCreate],
|
||||
['MarketingSeckillList', MarketingSeckillList],
|
||||
['MarketingSeckillProduct', MarketingSeckillProduct],
|
||||
['MarketingSeckillConfig', MarketingSeckillConfig],
|
||||
['MarketingMemberType', MarketingMemberType],
|
||||
['MarketingMemberRight', MarketingMemberRight],
|
||||
['MarketingMemberCard', MarketingMemberCard],
|
||||
['MarketingMemberRecord', MarketingMemberRecord],
|
||||
['MarketingMemberConfig', MarketingMemberConfig],
|
||||
['MarketingLiveRoom', MarketingLiveRoom],
|
||||
['MarketingLiveProduct', MarketingLiveProduct],
|
||||
['MarketingLiveAnchor', MarketingLiveAnchor],
|
||||
['MarketingRechargeQuota', MarketingRechargeQuota],
|
||||
['MarketingRechargeConfig', MarketingRechargeConfig],
|
||||
['MarketingCheckinConfig', MarketingCheckinConfig],
|
||||
['MarketingCheckinReward', MarketingCheckinReward],
|
||||
['MarketingChannelList', PlaceholderPage],
|
||||
['MarketingNewcomerGift', defineAsyncComponent(() => import('@/pages/mall/admin/marketing/newcomer/index.uvue'))],
|
||||
['MarketingNewcomerGift', MarketingNewcomerGift],
|
||||
['MarketingStatisticIndex', MarketingStatisticIndex],
|
||||
|
||||
// 内容模块
|
||||
['CmsArticle', defineAsyncComponent(() => import('@/pages/mall/admin/cms/article/list.uvue'))],
|
||||
['CmsCategory', defineAsyncComponent(() => import('@/pages/mall/admin/cms/category/list.uvue'))],
|
||||
['CmsArticle', CmsArticle],
|
||||
['CmsCategory', CmsCategory],
|
||||
|
||||
// 财务模块
|
||||
['FinanceTransactionStats', defineAsyncComponent(() => import('@/pages/mall/admin/finance/transaction_stats.uvue'))],
|
||||
['FinanceWithdrawal', defineAsyncComponent(() => import('@/pages/mall/admin/finance/withdrawal.uvue'))],
|
||||
['FinanceInvoice', defineAsyncComponent(() => import('@/pages/mall/admin/finance/invoice.uvue'))],
|
||||
['FinanceRecharge', defineAsyncComponent(() => import('@/pages/mall/admin/finance/recharge.uvue'))],
|
||||
['FinanceCapitalFlow', defineAsyncComponent(() => import('@/pages/mall/admin/finance/capital_flow.uvue'))],
|
||||
['FinanceBill', defineAsyncComponent(() => import('@/pages/mall/admin/finance/bill.uvue'))],
|
||||
['FinanceCommission', defineAsyncComponent(() => import('@/pages/mall/admin/finance/commission.uvue'))],
|
||||
['FinanceBalanceStats', defineAsyncComponent(() => import('@/pages/mall/admin/finance/balance_stats.uvue'))],
|
||||
['FinanceBalanceRecord', defineAsyncComponent(() => import('@/pages/mall/admin/finance/balance_record.uvue'))],
|
||||
['FinanceTransactionStats', FinanceTransactionStats],
|
||||
['FinanceWithdrawal', FinanceWithdrawal],
|
||||
['FinanceInvoice', FinanceInvoice],
|
||||
['FinanceRecharge', FinanceRecharge],
|
||||
['FinanceCapitalFlow', FinanceCapitalFlow],
|
||||
['FinanceBill', FinanceBill],
|
||||
['FinanceCommission', FinanceCommission],
|
||||
['FinanceBalanceStats', FinanceBalanceStats],
|
||||
['FinanceBalanceRecord', FinanceBalanceRecord],
|
||||
|
||||
// 数据模块 - 暂时使用占位组件
|
||||
// 数据模块
|
||||
['StatisticIndex', PlaceholderPage],
|
||||
|
||||
// 设置模块
|
||||
['SettingSystemConfig', defineAsyncComponent(() => import('@/pages/mall/admin/setting/system/config.uvue'))],
|
||||
['SettingMessageIndex', defineAsyncComponent(() => import('@/pages/mall/admin/setting/message.uvue'))],
|
||||
['SettingProtocolIndex', defineAsyncComponent(() => import('@/pages/mall/admin/setting/agreement.uvue'))],
|
||||
['SettingTicketIndex', 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'))],
|
||||
['SettingDeliveryVerifier', defineAsyncComponent(() => import('@/pages/mall/admin/setting/delivery/verifier.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'))],
|
||||
['SettingSystemConfig', SettingSystemConfig],
|
||||
['SettingMessageIndex', SettingMessageIndex],
|
||||
['SettingProtocolIndex', SettingProtocolIndex],
|
||||
['SettingTicketIndex', SettingTicketIndex],
|
||||
['SettingAuthRole', SettingAuthRole],
|
||||
['SettingAuthAdmin', SettingAuthAdmin],
|
||||
['SettingAuthPermission', SettingAuthPermission],
|
||||
['SettingDeliveryStaff', SettingDeliveryStaff],
|
||||
['SettingDeliveryStation', SettingDeliveryStation],
|
||||
['SettingDeliveryVerifier', SettingDeliveryVerifier],
|
||||
['SettingDeliveryTemplate', SettingDeliveryTemplate],
|
||||
['SettingInterfaceOnepassConfig', SettingInterfaceOnepassConfig],
|
||||
['SettingInterfaceOnepassIndex', SettingInterfaceOnepassIndex],
|
||||
['SettingInterfaceStorage', SettingInterfaceStorage],
|
||||
['SettingInterfaceCollect', SettingInterfaceCollect],
|
||||
['SettingInterfaceLogistics', SettingInterfaceLogistics],
|
||||
['SettingInterfaceESheet', SettingInterfaceESheet],
|
||||
['SettingInterfaceSms', SettingInterfaceSms],
|
||||
['SettingInterfacePayment', SettingInterfacePayment],
|
||||
|
||||
// 分销模块
|
||||
['DistributionStatistic', PlaceholderPage],
|
||||
['DistributionPromoter', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/promoter/index.uvue'))],
|
||||
['DistributionLevel', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/level/index.uvue'))],
|
||||
['DistributionSetting', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/setting/index.uvue'))],
|
||||
['DivisionList', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/division/list.uvue'))],
|
||||
['DivisionAgent', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/division/agent.uvue'))],
|
||||
['DivisionApply', defineAsyncComponent(() => import('@/pages/mall/admin/distribution/division/apply.uvue'))],
|
||||
['DistributionPromoter', DistributionPromoter],
|
||||
['DistributionLevel', DistributionLevel],
|
||||
['DistributionSetting', DistributionSetting],
|
||||
['DivisionList', DivisionList],
|
||||
['DivisionAgent', DivisionAgent],
|
||||
['DivisionApply', DivisionApply],
|
||||
|
||||
// 客服模块
|
||||
['KefuList', defineAsyncComponent(() => import('@/pages/mall/admin/kefu/list.uvue'))],
|
||||
['KefuWords', defineAsyncComponent(() => import('@/pages/mall/admin/kefu/words.uvue'))],
|
||||
['KefuFeedback', defineAsyncComponent(() => import('@/pages/mall/admin/kefu/feedback.uvue'))],
|
||||
['KefuAutoReply', defineAsyncComponent(() => import('@/pages/mall/admin/kefu/auto_reply.uvue'))],
|
||||
['KefuConfig', defineAsyncComponent(() => import('@/pages/mall/admin/kefu/config.uvue'))],
|
||||
['KefuList', KefuList],
|
||||
['KefuWords', KefuWords],
|
||||
['KefuFeedback', KefuFeedback],
|
||||
['KefuAutoReply', KefuAutoReply],
|
||||
['KefuConfig', KefuConfig],
|
||||
|
||||
// 装修模块
|
||||
['DecorationHome', defineAsyncComponent(() => import('@/pages/mall/admin/decoration/home.uvue'))],
|
||||
['DecorationCategory', defineAsyncComponent(() => import('@/pages/mall/admin/decoration/category.uvue'))],
|
||||
['DecorationUser', defineAsyncComponent(() => import('@/pages/mall/admin/decoration/user.uvue'))],
|
||||
['DecorationData', defineAsyncComponent(() => import('@/pages/mall/admin/decoration/data-config.uvue'))],
|
||||
['DecorationStyle', defineAsyncComponent(() => import('@/pages/mall/admin/design/theme-style.uvue'))],
|
||||
['DecorationMaterial', defineAsyncComponent(() => import('@/pages/mall/admin/design/material.uvue'))],
|
||||
['DecorationLink', defineAsyncComponent(() => import('@/pages/mall/admin/design/link-management.uvue'))],
|
||||
['DecorationHome', DecorationHome],
|
||||
['DecorationCategory', DecorationCategory],
|
||||
['DecorationUser', DecorationUser],
|
||||
['DecorationData', DecorationData],
|
||||
['DecorationStyle', DecorationStyle],
|
||||
['DecorationMaterial', DecorationMaterial],
|
||||
['DecorationLink', DecorationLink],
|
||||
|
||||
// 应用模块
|
||||
['AppWechatMenu', defineAsyncComponent(() => import('@/pages/mall/admin/app/wechat/menu.uvue'))],
|
||||
['AppWechatNews', defineAsyncComponent(() => import('@/pages/mall/admin/app/wechat/news.uvue'))],
|
||||
['AppWechatReplyFollow', defineAsyncComponent(() => import('@/pages/mall/admin/app/wechat/reply/follow.uvue'))],
|
||||
['AppWechatReplyKeyword', defineAsyncComponent(() => import('@/pages/mall/admin/app/wechat/reply/keyword.uvue'))],
|
||||
['AppWechatReplyInvalid', defineAsyncComponent(() => import('@/pages/mall/admin/app/wechat/reply/invalid.uvue'))],
|
||||
['AppWechatConfig', defineAsyncComponent(() => import('@/pages/mall/admin/app/wechat/config.uvue'))],
|
||||
['AppRoutineDownload', defineAsyncComponent(() => import('@/pages/mall/admin/app/routine/download.uvue'))],
|
||||
['AppRoutineConfig', defineAsyncComponent(() => import('@/pages/mall/admin/app/routine/config.uvue'))],
|
||||
['AppMobileConfig', defineAsyncComponent(() => import('@/pages/mall/admin/app/mobile/config.uvue'))],
|
||||
['AppMobileVersion', defineAsyncComponent(() => import('@/pages/mall/admin/app/mobile/version.uvue'))],
|
||||
['AppPcDesign', defineAsyncComponent(() => import('@/pages/mall/admin/app/pc/design.uvue'))],
|
||||
['AppPcConfig', defineAsyncComponent(() => import('@/pages/mall/admin/app/pc/config.uvue'))],
|
||||
['AppWechatMenu', AppWechatMenu],
|
||||
['AppWechatNews', AppWechatNews],
|
||||
['AppWechatReplyFollow', AppWechatReplyFollow],
|
||||
['AppWechatReplyKeyword', AppWechatReplyKeyword],
|
||||
['AppWechatReplyInvalid', AppWechatReplyInvalid],
|
||||
['AppWechatConfig', AppWechatConfig],
|
||||
['AppRoutineDownload', AppRoutineDownload],
|
||||
['AppRoutineConfig', AppRoutineConfig],
|
||||
['AppMobileConfig', AppMobileConfig],
|
||||
['AppMobileVersion', AppMobileVersion],
|
||||
['AppPcDesign', AppPcDesign],
|
||||
['AppPcConfig', AppPcConfig],
|
||||
|
||||
// 维护模块
|
||||
['MaintainDevConfig', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-config/category.uvue'))],
|
||||
['MaintainDevData', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-config/combination-data.uvue'))],
|
||||
['MaintainDevTask', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-config/cron-job.uvue'))],
|
||||
['MaintainDevAuth', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-config/permission.uvue'))],
|
||||
['MaintainDevModule', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-config/module-config.uvue'))],
|
||||
['MaintainDevEvent', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-config/custom-event.uvue'))],
|
||||
['MaintainSecurityCache', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/security/refresh-cache.uvue'))],
|
||||
['MaintainSecurityLog', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/security/system-log.uvue'))],
|
||||
['MaintainSecurityUpgrade', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/security/online-upgrade.uvue'))],
|
||||
['MaintainDataLogistics', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/data/logistics.uvue'))],
|
||||
['MaintainDataCity', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/data/city.uvue'))],
|
||||
['MaintainDataClear', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/data/clear.uvue'))],
|
||||
['MaintainApiAccount', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/api/account.uvue'))],
|
||||
['MaintainLangList', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/lang/list.uvue'))],
|
||||
['MaintainLangDetail', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/lang/detail.uvue'))],
|
||||
['MaintainLangRegion', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/lang/region.uvue'))],
|
||||
['MaintainLangConfig', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/lang/config.uvue'))],
|
||||
['MaintainToolDb', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-tools/database.uvue'))],
|
||||
['MaintainToolFile', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-tools/file.uvue'))],
|
||||
['MaintainToolApi', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-tools/api.uvue'))],
|
||||
['MaintainToolDic', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/dev-tools/data-dict.uvue'))],
|
||||
['MaintainSysInfo', defineAsyncComponent(() => import('@/pages/mall/admin/maintain/sys/info.uvue'))]
|
||||
['MaintainDevConfig', MaintainDevConfig],
|
||||
['MaintainDevData', MaintainDevData],
|
||||
['MaintainDevTask', MaintainDevTask],
|
||||
['MaintainDevAuth', MaintainDevAuth],
|
||||
['MaintainDevModule', MaintainDevModule],
|
||||
['MaintainDevEvent', MaintainDevEvent],
|
||||
['MaintainSecurityCache', MaintainSecurityCache],
|
||||
['MaintainSecurityLog', MaintainSecurityLog],
|
||||
['MaintainSecurityUpgrade', MaintainSecurityUpgrade],
|
||||
['MaintainDataLogistics', MaintainDataLogistics],
|
||||
['MaintainDataCity', MaintainDataCity],
|
||||
['MaintainDataClear', MaintainDataClear],
|
||||
['MaintainApiAccount', MaintainApiAccount],
|
||||
['MaintainLangList', MaintainLangList],
|
||||
['MaintainLangDetail', MaintainLangDetail],
|
||||
['MaintainLangRegion', MaintainLangRegion],
|
||||
['MaintainLangConfig', MaintainLangConfig],
|
||||
['MaintainToolDb', MaintainToolDb],
|
||||
['MaintainToolFile', MaintainToolFile],
|
||||
['MaintainToolApi', MaintainToolApi],
|
||||
['MaintainToolDic', MaintainToolDic],
|
||||
['MaintainSysInfo', MaintainSysInfo]
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,8 @@ export type RouteRecord = {
|
||||
/**
|
||||
* 菜单分组类型
|
||||
*/
|
||||
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
|
||||
|
||||
export type MenuGroup = {
|
||||
id: string
|
||||
title: string
|
||||
@@ -65,12 +67,22 @@ export const topMenus: TopMenu[] = [
|
||||
order: 1,
|
||||
groups: []
|
||||
},
|
||||
{
|
||||
id: 'shop',
|
||||
title: '店铺',
|
||||
icon: 'shop',
|
||||
path: '/pages/mall/admin/shop/manage',
|
||||
order: 2,
|
||||
groups: [
|
||||
{ id: 'shop-manage', title: '', order: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
title: '用户',
|
||||
icon: 'user',
|
||||
path: '/pages/mall/admin/user/management/index',
|
||||
order: 2,
|
||||
order: 3,
|
||||
groups: [
|
||||
{ id: 'user-manage', title: '', order: 1 }
|
||||
]
|
||||
@@ -79,7 +91,7 @@ export const topMenus: TopMenu[] = [
|
||||
id: 'order',
|
||||
title: '订单',
|
||||
icon: 'order',
|
||||
path: '/pages/mall/admin/order/list',
|
||||
path: '/pages/mall/admin/order/order-management/index',
|
||||
order: 3,
|
||||
groups: [
|
||||
{ id: 'order-manage', title: '', order: 1 }
|
||||
@@ -119,10 +131,11 @@ export const topMenus: TopMenu[] = [
|
||||
id: 'distribution',
|
||||
title: '分销',
|
||||
icon: 'share',
|
||||
path: '/pages/mall/admin/distribution/statistic',
|
||||
path: '/pages/mall/admin/distribution/promoter/index',
|
||||
order: 6,
|
||||
groups: [
|
||||
{ id: 'distribution-manage', title: '', order: 1 }
|
||||
{ id: 'distribution-manage', title: '', order: 1 },
|
||||
{ id: 'distribution-division', title: '事业部', order: 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -230,9 +243,39 @@ export const routes: RouteRecord[] = [
|
||||
order: 1
|
||||
},
|
||||
|
||||
// ========== 用户模块 ==========
|
||||
// ========== 店铺模块 ==========
|
||||
{
|
||||
id: 'user_statistic',
|
||||
id: 'shop_manage',
|
||||
title: '店铺管理',
|
||||
path: '/pages/mall/admin/shop/manage',
|
||||
componentKey: 'ShopManage',
|
||||
parentId: 'shop',
|
||||
groupId: 'shop-manage',
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'shop_create',
|
||||
title: '申请入驻',
|
||||
path: '/pages/mall/admin/shop/create',
|
||||
componentKey: 'ShopCreate',
|
||||
parentId: 'shop',
|
||||
groupId: 'shop-manage',
|
||||
hidden: true,
|
||||
order: 2
|
||||
},
|
||||
|
||||
// ========== 用户模块 ==========
|
||||
// ========== 个人中心 ==========
|
||||
{
|
||||
id: 'home_user_center',
|
||||
title: '个人中心',
|
||||
path: '/pages/mall/admin/userCenter/index',
|
||||
componentKey: 'UserCenter',
|
||||
order: 100
|
||||
},
|
||||
|
||||
{
|
||||
id: 'user_statistic',
|
||||
title: '用户统计',
|
||||
path: '/pages/mall/admin/user/statistics/index',
|
||||
componentKey: 'UserStatistic',
|
||||
@@ -281,6 +324,15 @@ export const routes: RouteRecord[] = [
|
||||
auth: ['user-user-level'],
|
||||
order: 5
|
||||
},
|
||||
{
|
||||
id: 'user_config',
|
||||
title: '用户配置',
|
||||
path: '/pages/mall/admin/user/config/index',
|
||||
componentKey: 'UserMemberConfig',
|
||||
parentId: 'user',
|
||||
groupId: 'user-manage',
|
||||
order: 6
|
||||
},
|
||||
|
||||
// ========== 商品模块 ==========
|
||||
{
|
||||
@@ -399,7 +451,7 @@ export const routes: RouteRecord[] = [
|
||||
{
|
||||
id: 'order_list',
|
||||
title: '订单管理',
|
||||
path: '/pages/mall/admin/order/list',
|
||||
path: '/pages/mall/admin/order/order-management/index',
|
||||
componentKey: 'OrderList',
|
||||
parentId: 'order',
|
||||
groupId: 'order-manage',
|
||||
@@ -474,7 +526,7 @@ export const routes: RouteRecord[] = [
|
||||
{
|
||||
id: 'marketing_integral_statistic',
|
||||
title: '积分统计',
|
||||
path: '/pages/mall/admin/marketing/integral/statistic',
|
||||
path: '/pages/mall/admin/marketing/points/statistic',
|
||||
componentKey: 'MarketingIntegralStatistic',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-integral',
|
||||
@@ -484,7 +536,7 @@ export const routes: RouteRecord[] = [
|
||||
{
|
||||
id: 'marketing_integral_product',
|
||||
title: '积分商品',
|
||||
path: '/pages/mall/admin/marketing/integral/product',
|
||||
path: '/pages/mall/admin/marketing/points/product',
|
||||
componentKey: 'MarketingIntegralProduct',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-integral',
|
||||
@@ -494,7 +546,7 @@ export const routes: RouteRecord[] = [
|
||||
{
|
||||
id: 'marketing_integral_order',
|
||||
title: '积分订单',
|
||||
path: '/pages/mall/admin/marketing/integral/order',
|
||||
path: '/pages/mall/admin/marketing/points/order',
|
||||
componentKey: 'MarketingIntegralOrder',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-integral',
|
||||
@@ -504,7 +556,7 @@ export const routes: RouteRecord[] = [
|
||||
{
|
||||
id: 'marketing_integral_record',
|
||||
title: '积分记录',
|
||||
path: '/pages/mall/admin/marketing/integral/record',
|
||||
path: '/pages/mall/admin/marketing/points/record',
|
||||
componentKey: 'MarketingIntegralRecord',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-integral',
|
||||
@@ -514,7 +566,7 @@ export const routes: RouteRecord[] = [
|
||||
{
|
||||
id: 'marketing_integral_config',
|
||||
title: '积分配置',
|
||||
path: '/pages/mall/admin/marketing/integral/config',
|
||||
path: '/pages/mall/admin/marketing/points/config',
|
||||
componentKey: 'MarketingIntegralConfig',
|
||||
parentId: 'marketing',
|
||||
groupId: 'marketing-integral',
|
||||
@@ -905,6 +957,7 @@ export const routes: RouteRecord[] = [
|
||||
componentKey: 'DistributionStatistic',
|
||||
parentId: 'distribution',
|
||||
groupId: 'distribution-manage',
|
||||
hidden: true,
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
@@ -936,30 +989,30 @@ export const routes: RouteRecord[] = [
|
||||
},
|
||||
{
|
||||
id: 'division_list',
|
||||
title: '事业部管理',
|
||||
title: '事业部列表',
|
||||
path: '/pages/mall/admin/distribution/division/list',
|
||||
componentKey: 'DivisionList',
|
||||
parentId: 'distribution',
|
||||
groupId: 'distribution-manage',
|
||||
order: 5
|
||||
groupId: 'distribution-division',
|
||||
order: 1
|
||||
},
|
||||
{
|
||||
id: 'division_agent',
|
||||
title: '代理商管理',
|
||||
title: '代理商列表',
|
||||
path: '/pages/mall/admin/distribution/division/agent',
|
||||
componentKey: 'DivisionAgent',
|
||||
parentId: 'distribution',
|
||||
groupId: 'distribution-manage',
|
||||
order: 6
|
||||
groupId: 'distribution-division',
|
||||
order: 2
|
||||
},
|
||||
{
|
||||
id: 'division_apply',
|
||||
title: '事业部申请',
|
||||
title: '代理商申请',
|
||||
path: '/pages/mall/admin/distribution/division/apply',
|
||||
componentKey: 'DivisionApply',
|
||||
parentId: 'distribution',
|
||||
groupId: 'distribution-manage',
|
||||
order: 7
|
||||
groupId: 'distribution-division',
|
||||
order: 3
|
||||
},
|
||||
|
||||
// ========== 客服模块 ==========
|
||||
@@ -1173,7 +1226,10 @@ export const routes: RouteRecord[] = [
|
||||
* 获取所有一级菜单
|
||||
*/
|
||||
export function getTopMenus(): TopMenu[] {
|
||||
return topMenus.sort((a, b) => a.order - b.order)
|
||||
// 基于 role 的模块过滤
|
||||
return topMenus
|
||||
.filter(m => hasAdminModuleAccess(m.id))
|
||||
.sort((a, b) => a.order - b.order)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,10 @@ export type MenuNode = {
|
||||
}
|
||||
|
||||
export const settingSubSiderMenu: MenuNode[] = [
|
||||
{ id: 'system_setting_group', title: '系统设置', type: 'group', children: [
|
||||
{ id: 'setting_system_config', title: '系统配置', type: 'page', path: '/pages/mall/admin/setting/system/config' }
|
||||
]
|
||||
},
|
||||
{ id: 'setting_message_index', title: '消息管理', type: 'page', path: '/pages/mall/admin/setting/message' },
|
||||
{ id: 'setting_protocol_index', title: '协议设置', type: 'page', path: '/pages/mall/admin/setting/agreement' },
|
||||
{ id: 'setting_ticket_index', title: '小票配置', type: 'page', path: '/pages/mall/admin/setting/ticket' },
|
||||
|
||||
@@ -12,6 +12,26 @@ import {
|
||||
getTopMenus
|
||||
} from '@/layouts/admin/router/adminRoutes.uts'
|
||||
import { addView, activeFullPath, visitedViews } from './tagsViewStore.uts'
|
||||
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
|
||||
|
||||
// ============================================
|
||||
// Tabs 持久化 keys(使用 sessionStorage,与 token 策略一致)
|
||||
// sessionStorage 在同一标签页 F5 刷新后保留,关闭标签页后自动清除
|
||||
// ============================================
|
||||
const TAB_STORAGE_KEY = 'admin_opened_tabs'
|
||||
const ACTIVE_ROUTE_KEY = 'admin_active_route'
|
||||
const LAST_SUB_STORAGE_KEY = 'admin_last_sub_by_menu'
|
||||
|
||||
/** 将当前 tabs / activeRouteId / lastSubIdByMenu 写入 sessionStorage */
|
||||
function persistNavState(): void {
|
||||
// #ifdef H5
|
||||
try {
|
||||
sessionStorage.setItem(TAB_STORAGE_KEY, JSON.stringify(tabs.value))
|
||||
sessionStorage.setItem(ACTIVE_ROUTE_KEY, activeRouteId.value)
|
||||
sessionStorage.setItem(LAST_SUB_STORAGE_KEY, JSON.stringify(lastSubIdByMenu.value))
|
||||
} catch (_) {}
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签页类型
|
||||
@@ -77,14 +97,34 @@ export const isOverlayVisible = ref<boolean>(false)
|
||||
* @param addTab 是否添加到标签页
|
||||
*/
|
||||
export function openRoute(routeId: string, addTab: boolean = true): void {
|
||||
|
||||
|
||||
const route = findRouteById(routeId)
|
||||
if (!route) {
|
||||
console.warn(`[AdminNav] Route not found: ${routeId}`)
|
||||
return
|
||||
}
|
||||
|
||||
// 基于 role 的页面访问拦截
|
||||
// route.parentId 对应上方 topMenus 的 id。这里校验是否有权限
|
||||
const moduleId = route.parentId ? route.parentId : route.id.split('_')[0]
|
||||
if (!hasAdminModuleAccess(moduleId)) {
|
||||
uni.showToast({
|
||||
title: '您没有权限访问该模块',
|
||||
icon: 'none'
|
||||
})
|
||||
console.warn(`[AdminNav] Access denied for role to module: ${moduleId}`)
|
||||
// 回退到首页
|
||||
if (routeId !== 'home_index') {
|
||||
openRoute('home_index', addTab)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 更新当前路由
|
||||
activeRouteId.value = routeId
|
||||
|
||||
|
||||
|
||||
// 更新一级菜单选中态
|
||||
if (route.parentId) {
|
||||
@@ -99,6 +139,9 @@ export function openRoute(routeId: string, addTab: boolean = true): void {
|
||||
// 添加到标签页
|
||||
if (addTab) {
|
||||
addTabItem(route)
|
||||
} else {
|
||||
// 即使不新增 tab,activeRouteId 变化了也要持久化
|
||||
persistNavState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +172,9 @@ function addTabItem(route: RouteRecord): void {
|
||||
// 更新新版 TagsViewStore
|
||||
addView(route, route.path)
|
||||
activeFullPath.value = route.path
|
||||
|
||||
// 持久化到 sessionStorage
|
||||
persistNavState()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,6 +203,8 @@ export function closeTab(tabId: string): void {
|
||||
}
|
||||
|
||||
tabs.value.splice(index, 1)
|
||||
// 持久化
|
||||
persistNavState()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,6 +219,8 @@ export function closeOtherTabs(keepTabId: string): void {
|
||||
if (!stillExists) {
|
||||
openRoute(keepTabId, false)
|
||||
}
|
||||
// 持久化
|
||||
persistNavState()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,6 +234,8 @@ export function closeAllTabs(): void {
|
||||
if (homeTab) {
|
||||
openRoute(homeTab.id, false)
|
||||
}
|
||||
// 持久化
|
||||
persistNavState()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +253,7 @@ export function toggleSubSider(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化导航状态
|
||||
* 初始化导航状态(首次进入 / 恢复失败时的兜底)
|
||||
* 在 AdminLayout 组件 onMounted 时调用
|
||||
*/
|
||||
export function initNavState(): void {
|
||||
@@ -223,6 +275,86 @@ export function initNavState(): void {
|
||||
openRoute('home_index', false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 sessionStorage 恢复导航状态(F5刷新后调用)
|
||||
* - 校验每个缓存 tab 的路由是否仍然存在
|
||||
* - 校验是否仍有权限
|
||||
* - 过滤无效项后重建 tabs
|
||||
* - 恢复 activeRouteId 和菜单高亮
|
||||
* @returns true=恢复成功,false=无有效缓存或恢复失败(调用方应走 initNavState 兜底)
|
||||
*/
|
||||
export function restoreNavState(): boolean {
|
||||
// #ifdef H5
|
||||
try {
|
||||
const rawTabs = sessionStorage.getItem(TAB_STORAGE_KEY)
|
||||
if (!rawTabs) return false
|
||||
|
||||
const savedTabs = JSON.parse(rawTabs) as TabItem[]
|
||||
if (!Array.isArray(savedTabs) || savedTabs.length === 0) return false
|
||||
|
||||
// 校验并过滤:路由必须存在且有权限
|
||||
const validTabs = savedTabs.filter(tab => {
|
||||
const route = findRouteById(tab.id)
|
||||
if (!route) return false // 路由已不存在(文件删除或路由表变更)
|
||||
const moduleId = route.parentId ?? tab.id.split('_')[0]
|
||||
return hasAdminModuleAccess(moduleId) // 检查权限
|
||||
})
|
||||
|
||||
// 首页 tab 始终保留(防止全部被过滤后无处可去)
|
||||
const homeRoute = findRouteById('home_index')
|
||||
if (homeRoute && !validTabs.some(t => t.id === 'home_index')) {
|
||||
validTabs.unshift({
|
||||
id: homeRoute.id,
|
||||
title: homeRoute.title,
|
||||
path: homeRoute.path,
|
||||
isAffix: homeRoute.isAffix || false
|
||||
})
|
||||
}
|
||||
|
||||
if (validTabs.length === 0) return false
|
||||
|
||||
// 重建 tabs
|
||||
tabs.value = validTabs
|
||||
validTabs.forEach(tab => {
|
||||
const route = findRouteById(tab.id)
|
||||
if (route) addView(route, route.path)
|
||||
})
|
||||
|
||||
// 恢复 lastSubIdByMenu
|
||||
try {
|
||||
const rawLast = sessionStorage.getItem(LAST_SUB_STORAGE_KEY)
|
||||
if (rawLast) {
|
||||
lastSubIdByMenu.value = JSON.parse(rawLast) as Record<string, string>
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// 恢复 activeRouteId
|
||||
const savedActive = sessionStorage.getItem(ACTIVE_ROUTE_KEY)
|
||||
const activeIsValid = savedActive != null &&
|
||||
findRouteById(savedActive) != null &&
|
||||
validTabs.some(t => t.id === savedActive)
|
||||
|
||||
const targetId = activeIsValid ? savedActive! : validTabs[0].id
|
||||
activeRouteId.value = targetId
|
||||
activeFullPath.value = findRouteById(targetId)?.path ?? ''
|
||||
|
||||
const activeRoute = findRouteById(targetId)
|
||||
if (activeRoute?.parentId) {
|
||||
activeTopMenuId.value = activeRoute.parentId
|
||||
} else {
|
||||
activeTopMenuId.value = targetId.split('_')[0]
|
||||
}
|
||||
|
||||
console.log('[AdminNav] 已从缓存恢复导航状态, tabs:', validTabs.length, 'active:', targetId)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.warn('[AdminNav] 恢复导航状态失败,将走默认初始化:', e)
|
||||
return false
|
||||
}
|
||||
// #endif
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 currentPage 同步状态
|
||||
* 用于页面组件传入 currentPage prop 时的状态同步
|
||||
|
||||
81
layouts/admin/utils/adminAuth.uts
Normal file
81
layouts/admin/utils/adminAuth.uts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { state, getCurrentUser } from '@/utils/store.uts'
|
||||
import { clearAdminRoleCache, refreshAdminRole } from './role.uts'
|
||||
import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
let __isHandlingExpired = false
|
||||
|
||||
/**
|
||||
* 统一“登录过期”闭环处理
|
||||
* - 防止重复弹窗
|
||||
* - 清理所有认证、用户相关缓存
|
||||
* - 重置状态树
|
||||
* - 统一跳转回登录页
|
||||
*/
|
||||
export function handleSessionExpired(reason?: string) {
|
||||
if (__isHandlingExpired) return
|
||||
__isHandlingExpired = true
|
||||
|
||||
console.warn('[AdminAuth] 执行会话过期统一闭环:', reason ?? '未知原因')
|
||||
|
||||
// 1. 弹出提示 (确保只弹一次)
|
||||
uni.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
// 2. 清除本地相关存储
|
||||
try {
|
||||
supa.signOut()
|
||||
} catch(e){}
|
||||
clearAdminRoleCache()
|
||||
|
||||
// 3. 重置全局业务状态树,防止其他组件看到旧内存残影
|
||||
state.isLoggedIn = false
|
||||
state.authUser = null
|
||||
state.userProfile = { username: '', email: '' }
|
||||
|
||||
// 4. 跳转登录页
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
setTimeout(() => { __isHandlingExpired = false }, 1000)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 统一的后台启动恢复授权流程
|
||||
* 返回是否恢复/保持有效的登录态
|
||||
*/
|
||||
export async function ensureAdminSession(): Promise<boolean> {
|
||||
try {
|
||||
// ① 等待 hydrateSessionFromStorage() 完成(从storage恢复token→异步网络请求)
|
||||
// 必须在 getSession() 之前 await,否则刷新后 this.session 仍为 null,
|
||||
// 会被误判为未登录并触发 handleSessionExpired。
|
||||
try { await supaReady } catch (_) {}
|
||||
|
||||
const sessionInfo = supa.getSession()
|
||||
if (sessionInfo.session == null) {
|
||||
console.warn('[AdminAuth] 没有发现凭证,要求重新登录')
|
||||
handleSessionExpired('No credentials found')
|
||||
return false
|
||||
}
|
||||
|
||||
// 主动检查并补齐
|
||||
const role = await refreshAdminRole()
|
||||
if (role === 'unknown' || (state.userProfile != null && state.userProfile!.id == null)) {
|
||||
// 等等,如果是断网状态呢?其实 store 里已经用 status <= 0 判断过断网了。
|
||||
// 上一步在 store/getCurrentUser 时,如果返回 401 才会真正导致清空。
|
||||
console.warn('[AdminAuth] Session被认为无效或用户确权失败')
|
||||
handleSessionExpired('Role verification failed')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('[AdminAuth] 鉴权启动异常:', e)
|
||||
handleSessionExpired('Auth exception')
|
||||
return false
|
||||
}
|
||||
}
|
||||
131
layouts/admin/utils/role.uts
Normal file
131
layouts/admin/utils/role.uts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { state, getCurrentUser } from '@/utils/store.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
/**
|
||||
* 将任意角色类型的原始值格式化为标准化的应用角色
|
||||
*/
|
||||
export function normalizeRole(rawRole: any | null): string {
|
||||
if (rawRole == null || rawRole === undefined) return 'unknown'
|
||||
const roleStr = String(rawRole).trim().toLowerCase()
|
||||
if (roleStr === 'admin') return 'admin'
|
||||
if (roleStr === 'merchant') return 'merchant'
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为纯后台管理员
|
||||
*/
|
||||
export function isAdminRole(role: string): boolean {
|
||||
return normalizeRole(role) === 'admin'
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为商户角色
|
||||
*/
|
||||
export function isMerchantRole(role: string): boolean {
|
||||
return normalizeRole(role) === 'merchant'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的标准化角色 (同步方法)
|
||||
*/
|
||||
export function getCurrentAdminRole(): string {
|
||||
// 1. 最高优先级:当前响应式内存 userProfile(已查数据库)
|
||||
if (state.userProfile != null && state.userProfile!.role != null) {
|
||||
const memRole = normalizeRole(state.userProfile!.role)
|
||||
if (memRole === 'admin' || memRole === 'merchant') {
|
||||
return memRole
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Auth Session兜底获取(Tab 隔离):
|
||||
const sessionUser = supa.getSession().user
|
||||
if (sessionUser != null) {
|
||||
const meta = sessionUser.get("user_metadata") as UTSJSONObject | null
|
||||
if (meta != null && meta.getString("role") != null) {
|
||||
const metaRole = normalizeRole(meta.getString("role"))
|
||||
if (metaRole === "admin" || metaRole === "merchant") return metaRole
|
||||
}
|
||||
}
|
||||
|
||||
console.warn("[AdminRole] 未能获取到有效的管理端角色,准备安全降级...")
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理本地相关角色和管理端缓存 (登出时调用)
|
||||
*/
|
||||
export function clearAdminRoleCache(): void {
|
||||
// 清理 admin 专属
|
||||
uni.removeStorageSync('adminRole')
|
||||
uni.removeStorageSync('admin_role')
|
||||
uni.removeStorageSync('merchant_id')
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并写入最新的 adminRole (用于 Login 后或者 Layout 挂载时强制刷新)
|
||||
*/
|
||||
export async function refreshAdminRole(): Promise<string> {
|
||||
const userStrProfile = await getCurrentUser()
|
||||
let finalRole = 'unknown'
|
||||
|
||||
if (userStrProfile != null && userStrProfile.role != null) {
|
||||
finalRole = normalizeRole(userStrProfile.role)
|
||||
console.log('[AdminRole] 从 ak_users 读取真实身份成功:', finalRole)
|
||||
} else {
|
||||
// metadata fallback
|
||||
const sessionInfo = supa.getSession()
|
||||
if (sessionInfo.user != null) {
|
||||
const meta = sessionInfo.user?.get("user_metadata") as UTSJSONObject | null
|
||||
if (meta != null && meta.getString('role') != null) {
|
||||
finalRole = normalizeRole(meta.getString('role'))
|
||||
console.log('[AdminRole] 从 Auth Metadata 读取兜底身份:', finalRole)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (finalRole !== 'unknown') {
|
||||
// uni.setStorageSync('adminRole', finalRole) // 移除缓存耦合,强制按单例会话状态刷新
|
||||
if (state.userProfile != null) {
|
||||
state.userProfile!.role = finalRole
|
||||
}
|
||||
console.log('[AdminRole] 最新角色已写入状态和缓存:', finalRole)
|
||||
}
|
||||
|
||||
return finalRole
|
||||
}
|
||||
|
||||
export function getVisibleTopMenuIds(role: string): string[] {
|
||||
const normRole = normalizeRole(role)
|
||||
if (normRole === 'admin') {
|
||||
return ['home', 'shop', 'user', 'order', 'product', 'marketing', 'distribution', 'kefu', 'finance', 'cms', 'decoration', 'app', 'setting', 'maintain']
|
||||
}
|
||||
|
||||
if (normRole === 'merchant') {
|
||||
return ['home', 'shop', 'order', 'product', 'marketing', 'finance']
|
||||
}
|
||||
|
||||
return ['home']
|
||||
}
|
||||
|
||||
export function hasAdminModuleAccess(moduleId: string | undefined): boolean {
|
||||
if (!moduleId) return true
|
||||
|
||||
const role = getCurrentAdminRole()
|
||||
const normRole = normalizeRole(role)
|
||||
|
||||
if (normRole === 'unknown') {
|
||||
return moduleId === 'home'
|
||||
}
|
||||
|
||||
if (normRole === 'admin') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (normRole === 'merchant') {
|
||||
const allowed = ['home', 'shop', 'order', 'product', 'marketing', 'finance']
|
||||
return allowed.includes(moduleId)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user