想要完全动态地根据路由路径加载对应的页面组件,而不是预定义组件映射。以下是修改后的实现方案:
1. 修改后的主页面结构
<!-- pages/index/index.vue -->
<template>
<view class="page-container">
<!-- 左侧菜单 -->
<SideMenu
:menu-data="menuData"
v-model:is-collapsed="isCollapsed"
@item-click="handleMenuClick"
/>
<!-- 右侧内容区 -->
<view class="content-area" :class="{ 'collapsed': isCollapsed }">
<!-- 动态内容容器 -->
<view v-if="currentView" class="content-wrapper">
<component :is="currentView" />
</view>
<view v-else class="empty-content">
<text>请从左侧菜单选择内容</text>
</view>
</view>
</view>
</template>
<script lang="ts">
import { defineComponent, ref, shallowRef, defineAsyncComponent } from 'vue'
import SideMenu from '@/components/SideMenu.vue'
import { transformRoutesToMenu, updateMenuActiveStatus } from '@/utils/menu-utils'
import type { MenuItem } from '@/types/menu'
export default defineComponent({
components: { SideMenu },
setup() {
const isCollapsed = ref(false)
const currentView = shallowRef(null)
const currentPath = ref('')
// 菜单数据
const menuData = ref(transformRoutesToMenu([
{
path: '/pages/dashboard/index',
name: 'Dashboard',
meta: { title: '控制台', icon: 'home' }
},
{
path: '/pages/user/index',
name: 'User',
meta: { title: '用户管理', icon: 'person' }
},
{
path: '/pages/system/index',
name: 'System',
meta: { title: '系统管理', icon: 'settings' },
children: [
{
path: '/pages/system/role',
name: 'Role',
meta: { title: '角色管理', icon: 'staff' }
},
{
path: '/pages/system/menu',
name: 'Menu',
meta: { title: '菜单管理', icon: 'list' }
}
]
}
]))
// 动态加载组件
const loadComponent = async (path: string) => {
try {
// 根据路径动态生成组件路径
const componentPath = path.startsWith('/') ? path.slice(1) : path
currentView.value = defineAsyncComponent(() =>
import(`@/${componentPath}.vue`)
)
} catch (error) {
console.error('组件加载失败:', error)
currentView.value = null
}
}
// 处理菜单点击
const handleMenuClick = async (item: MenuItem) => {
if (!item.path) return
currentPath.value = item.path
await loadComponent(item.path)
updateMenuActiveStatus(menuData.value, item.path)
}
return {
isCollapsed,
menuData,
currentView,
handleMenuClick
}
}
})
</script>
2. 动态加载的关键实现
// 动态加载组件函数
const loadComponent = async (path: string) => {
try {
// 处理路径格式
let componentPath = path
.replace(/^\//, '') // 去除开头斜杠
.replace(/\/index$/, '') // 去除index结尾
// 特殊处理uniapp的页面路径
if (path.startsWith('/pages/')) {
componentPath = path.replace('/pages/', '')
}
currentView.value = defineAsyncComponent(() =>
import(/* webpackChunkName: "dynamic-[request]" */ `@/pages/${componentPath}.vue`)
)
} catch (error) {
console.error('组件加载失败:', error)
// 可以加载一个错误提示组件
currentView.value = defineAsyncComponent(() =>
import('@/components/ErrorPage.vue')
)
}
}
3. 路由路径处理建议
为了使动态导入更可靠,建议遵循以下路由规则:
- 所有页面放在
pages目录下 - 路由路径与文件路径保持一致,例如:
/pages/user/index→pages/user/index.vue/pages/system/role→pages/system/role.vue
4. 修改 SideMenu 组件
保持之前的 SideMenu 组件不变,只需确保点击菜单时传递完整的路由路径即可。
5. 添加错误处理组件
<!-- components/ErrorPage.vue -->
<template>
<view class="error-container">
<uni-icons type="close-circle" size="64" color="#f5222d" />
<text class="error-text">页面加载失败</text>
<button @click="retry" class="retry-btn">重试</button>
</view>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
emits: ['retry'],
setup(_, { emit }) {
const retry = () => {
emit('retry')
}
return { retry }
}
})
</script>
<style scoped>
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.error-text {
margin: 20px 0;
color: #f5222d;
font-size: 18px;
}
.retry-btn {
margin-top: 10px;
padding: 0 20px;
}
</style>
6. 完整的主页面更新
<script lang="ts">
import { defineComponent, ref, shallowRef, defineAsyncComponent } from 'vue'
import SideMenu from '@/components/SideMenu.vue'
import { transformRoutesToMenu, updateMenuActiveStatus } from '@/utils/menu-utils'
import type { MenuItem } from '@/types/menu'
export default defineComponent({
components: { SideMenu },
setup() {
const isCollapsed = ref(false)
const currentView = shallowRef(null)
const currentPath = ref('')
// 菜单数据
const menuData = ref(transformRoutesToMenu([
// 路由配置...
]))
// 动态加载组件
const loadComponent = async (path: string) => {
try {
let componentPath = path
.replace(/^\//, '')
.replace(/\/index$/, '')
if (path.startsWith('/pages/')) {
componentPath = path.replace('/pages/', '')
}
currentView.value = defineAsyncComponent({
loader: () => import(`@/pages/${componentPath}.vue`),
loadingComponent: defineAsyncComponent(() =>
import('@/components/Loading.vue')
),
errorComponent: defineAsyncComponent(() =>
import('@/components/ErrorPage.vue')
),
timeout: 5000
})
} catch (error) {
console.error('组件加载失败:', error)
currentView.value = defineAsyncComponent(() =>
import('@/components/ErrorPage.vue')
)
}
}
// 处理菜单点击
const handleMenuClick = async (item: MenuItem) => {
if (!item.path) return
currentPath.value = item.path
await loadComponent(item.path)
updateMenuActiveStatus(menuData.value, item.path)
}
// 初始化加载默认页面
onMounted(() => {
const defaultPath = '/pages/dashboard/index'
currentPath.value = defaultPath
loadComponent(defaultPath)
updateMenuActiveStatus(menuData.value, defaultPath)
})
return {
isCollapsed,
menuData,
currentView,
handleMenuClick
}
}
})
</script>
7. 实现优势
- 完全动态加载:无需预定义路由组件映射
- 代码分割:每个页面自动生成独立 chunk
- 错误处理:完善的加载失败处理机制
- 路径灵活:支持多种路径格式
- 性能优化:异步加载 + 加载状态提示
8. 注意事项
- 确保所有页面文件都放在
pages目录下 - 路由路径必须与文件路径匹配
- 生产环境部署时需要确保服务器正确返回这些动态页面
- 对于复杂项目,建议添加路由白名单验证
这样实现后,你的应用将完全动态地根据菜单路由加载对应的页面组件,无需预先定义组件映射关系,同时保持了良好的用户体验和错误处理能力。