262 lines
8.7 KiB
TypeScript
262 lines
8.7 KiB
TypeScript
|
|
import { RouteRecordRaw, RouteLocationNormalized } from 'vue-router';
|
|||
|
|
import { getDeviceType, DeviceType } from './deviceUtils';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 路由适配器接口
|
|||
|
|
*/
|
|||
|
|
export interface RouteAdapter {
|
|||
|
|
original: () => Promise<any>; // web桌面端
|
|||
|
|
mobile?: () => Promise<any>; // h5移动端
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 移动端路由映射表
|
|||
|
|
*/
|
|||
|
|
export const MOBILE_ROUTES_MAP: Record<string, string> = {
|
|||
|
|
// User Views
|
|||
|
|
'/home': '@/views/user/home/HomeView.mobile.vue',
|
|||
|
|
'/resource-center': '@/views/user/resource-center/ResourceCenterView.mobile.vue',
|
|||
|
|
'/resource-hot': '@/views/user/resource-center/HotResourceView.mobile.vue',
|
|||
|
|
'/search': '@/views/user/resource-center/SearchView.mobile.vue',
|
|||
|
|
'/course-center': '@/views/user/study-plan/CourseCenterView.mobile.vue',
|
|||
|
|
'/course-detail': '@/views/user/study-plan/CourseDetailView.mobile.vue',
|
|||
|
|
'/course-study': '@/views/user/study-plan/CourseStudyView.mobile.vue',
|
|||
|
|
'/study-tasks': '@/views/user/study-plan/StudyTasksView.mobile.vue',
|
|||
|
|
'/learning-task-detail': '@/views/user/study-plan/LearningTaskDetailView.mobile.vue',
|
|||
|
|
'/user-center': '@/views/user/user-center/UserCenterLayout.mobile.vue',
|
|||
|
|
'/personal-info': '@/views/user/user-center/profile/PersonalInfoView.mobile.vue',
|
|||
|
|
'/account-settings': '@/views/user/user-center/profile/AccountSettingsView.mobile.vue',
|
|||
|
|
'/my-achievements': '@/views/user/user-center/MyAchievementsView.mobile.vue',
|
|||
|
|
'/my-favorites': '@/views/user/user-center/MyFavoritesView.mobile.vue',
|
|||
|
|
'/learning-records': '@/views/user/user-center/LearningRecordsView.mobile.vue',
|
|||
|
|
'/my-messages': '@/views/user/message/MyMessageListView.mobile.vue',
|
|||
|
|
'/message-detail': '@/views/user/message/MyMessageDetailView.mobile.vue',
|
|||
|
|
|
|||
|
|
// Public Views
|
|||
|
|
'/login': '@/views/public/login/Login.mobile.vue',
|
|||
|
|
'/register': '@/views/public/login/Register.mobile.vue',
|
|||
|
|
'/forgot-password': '@/views/public/login/ForgotPassword.mobile.vue',
|
|||
|
|
'/article-show': '@/views/public/article/ArticleShowView.mobile.vue',
|
|||
|
|
'/article-add': '@/views/public/article/ArticleAddView.mobile.vue'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Layout映射表
|
|||
|
|
*/
|
|||
|
|
export const LAYOUT_MAP: Record<string, Record<DeviceType, string>> = {
|
|||
|
|
'NavigationLayout': {
|
|||
|
|
[DeviceType.MOBILE]: '@/layouts/MobileLayout.vue', // h5移动端
|
|||
|
|
[DeviceType.DESKTOP]: '@/layouts/NavigationLayout.vue' // web桌面端
|
|||
|
|
},
|
|||
|
|
'SidebarLayout': {
|
|||
|
|
[DeviceType.MOBILE]: '@/layouts/MobileLayout.vue', // h5移动端
|
|||
|
|
[DeviceType.DESKTOP]: '@/layouts/SidebarLayout.vue' // web桌面端
|
|||
|
|
},
|
|||
|
|
'BasicLayout': {
|
|||
|
|
[DeviceType.MOBILE]: '@/layouts/MobileLayout.vue', // h5移动端
|
|||
|
|
[DeviceType.DESKTOP]: '@/layouts/BasicLayout.vue' // web桌面端
|
|||
|
|
},
|
|||
|
|
'BlankLayout': {
|
|||
|
|
[DeviceType.MOBILE]: '@/layouts/BlankLayout.vue', // h5移动端
|
|||
|
|
[DeviceType.DESKTOP]: '@/layouts/BlankLayout.vue' // web桌面端
|
|||
|
|
},
|
|||
|
|
'PageLayout': {
|
|||
|
|
[DeviceType.MOBILE]: '@/layouts/MobileLayout.vue', // h5移动端
|
|||
|
|
[DeviceType.DESKTOP]: '@/layouts/PageLayout.vue' // web桌面端
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建响应式路由组件
|
|||
|
|
*/
|
|||
|
|
export function createResponsiveRoute(adapter: RouteAdapter): () => Promise<any> {
|
|||
|
|
return async () => {
|
|||
|
|
const deviceType = getDeviceType();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 尝试加载设备特定的组件
|
|||
|
|
if (deviceType === DeviceType.MOBILE && adapter.mobile) {
|
|||
|
|
return await adapter.mobile();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(`Failed to load device-specific component for ${deviceType}, falling back to original:`, error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 回退到原始组件(桌面端/web)
|
|||
|
|
return await adapter.original();
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取响应式Layout组件
|
|||
|
|
*/
|
|||
|
|
export function getResponsiveLayout(layoutName: string): () => Promise<any> {
|
|||
|
|
const deviceType = getDeviceType();
|
|||
|
|
const layoutMap = LAYOUT_MAP[layoutName];
|
|||
|
|
|
|||
|
|
if (!layoutMap) {
|
|||
|
|
console.warn(`Unknown layout: ${layoutName}, using original`);
|
|||
|
|
// 使用具体的导入路径
|
|||
|
|
switch (layoutName) {
|
|||
|
|
case 'BlankLayout':
|
|||
|
|
return () => import('@/layouts/BlankLayout.vue');
|
|||
|
|
case 'NavigationLayout':
|
|||
|
|
return () => import('@/layouts/NavigationLayout.vue');
|
|||
|
|
case 'SidebarLayout':
|
|||
|
|
return () => import('@/layouts/SidebarLayout.vue');
|
|||
|
|
case 'BasicLayout':
|
|||
|
|
return () => import('@/layouts/BasicLayout.vue');
|
|||
|
|
case 'PageLayout':
|
|||
|
|
return () => import('@/layouts/PageLayout.vue');
|
|||
|
|
default:
|
|||
|
|
throw new Error(`Unknown layout: ${layoutName}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const targetLayout = layoutMap[deviceType];
|
|||
|
|
|
|||
|
|
return async () => {
|
|||
|
|
try {
|
|||
|
|
// 使用具体的导入路径而不是动态路径
|
|||
|
|
switch (targetLayout) {
|
|||
|
|
case '@/layouts/BlankLayout.vue':
|
|||
|
|
return await import('@/layouts/BlankLayout.vue');
|
|||
|
|
case '@/layouts/NavigationLayout.vue':
|
|||
|
|
return await import('@/layouts/NavigationLayout.vue');
|
|||
|
|
case '@/layouts/SidebarLayout.vue':
|
|||
|
|
return await import('@/layouts/SidebarLayout.vue');
|
|||
|
|
case '@/layouts/BasicLayout.vue':
|
|||
|
|
return await import('@/layouts/BasicLayout.vue');
|
|||
|
|
case '@/layouts/MobileLayout.vue':
|
|||
|
|
return await import('@/layouts/MobileLayout.vue');
|
|||
|
|
case '@/layouts/PageLayout.vue':
|
|||
|
|
return await import('@/layouts/PageLayout.vue');
|
|||
|
|
default:
|
|||
|
|
throw new Error(`Unknown layout path: ${targetLayout}`);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(`Failed to load responsive layout ${targetLayout}, falling back to original:`, error);
|
|||
|
|
// 回退到原始layout
|
|||
|
|
switch (layoutName) {
|
|||
|
|
case 'BlankLayout':
|
|||
|
|
return await import('@/layouts/BlankLayout.vue');
|
|||
|
|
case 'NavigationLayout':
|
|||
|
|
return await import('@/layouts/NavigationLayout.vue');
|
|||
|
|
case 'SidebarLayout':
|
|||
|
|
return await import('@/layouts/SidebarLayout.vue');
|
|||
|
|
case 'BasicLayout':
|
|||
|
|
return await import('@/layouts/BasicLayout.vue');
|
|||
|
|
case 'PageLayout':
|
|||
|
|
return await import('@/layouts/PageLayout.vue');
|
|||
|
|
default:
|
|||
|
|
throw new Error(`Unknown layout: ${layoutName}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建自适应路由配置
|
|||
|
|
*/
|
|||
|
|
export function createAdaptiveRoute(
|
|||
|
|
path: string,
|
|||
|
|
originalComponent: string,
|
|||
|
|
layoutName?: string,
|
|||
|
|
meta?: any
|
|||
|
|
): RouteRecordRaw {
|
|||
|
|
// 创建具体的导入函数而不是使用动态路径
|
|||
|
|
const getOriginalComponent = () => {
|
|||
|
|
switch (originalComponent) {
|
|||
|
|
case '@/views/public/login/Login.vue':
|
|||
|
|
return import('@/views/public/login/Login.vue');
|
|||
|
|
case '@/views/public/login/Register.vue':
|
|||
|
|
return import('@/views/public/login/Register.vue');
|
|||
|
|
case '@/views/public/login/ForgotPassword.vue':
|
|||
|
|
return import('@/views/public/login/ForgotPassword.vue');
|
|||
|
|
default:
|
|||
|
|
throw new Error(`Unknown component: ${originalComponent}`);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getMobileComponent = (): (() => Promise<any>) | null => {
|
|||
|
|
const mobilePath = MOBILE_ROUTES_MAP[path];
|
|||
|
|
if (!mobilePath) return null;
|
|||
|
|
|
|||
|
|
switch (mobilePath) {
|
|||
|
|
case '@/views/public/login/Login.mobile.vue':
|
|||
|
|
return () => import('@/views/public/login/Login.mobile.vue');
|
|||
|
|
case '@/views/public/login/Register.mobile.vue':
|
|||
|
|
return () => import('@/views/public/login/Register.mobile.vue');
|
|||
|
|
case '@/views/public/login/ForgotPassword.mobile.vue':
|
|||
|
|
return () => import('@/views/public/login/ForgotPassword.mobile.vue');
|
|||
|
|
default:
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const adapter: RouteAdapter = {
|
|||
|
|
original: getOriginalComponent
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 检查是否有移动端版本
|
|||
|
|
const mobileImportFunction = getMobileComponent();
|
|||
|
|
if (mobileImportFunction) {
|
|||
|
|
adapter.mobile = mobileImportFunction;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果指定了Layout,应用响应式Layout
|
|||
|
|
if (layoutName) {
|
|||
|
|
const route: RouteRecordRaw = {
|
|||
|
|
path,
|
|||
|
|
component: getResponsiveLayout(layoutName),
|
|||
|
|
children: [
|
|||
|
|
{
|
|||
|
|
path: '',
|
|||
|
|
component: createResponsiveRoute(adapter),
|
|||
|
|
meta
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
meta
|
|||
|
|
};
|
|||
|
|
return route;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const route: RouteRecordRaw = {
|
|||
|
|
path,
|
|||
|
|
component: createResponsiveRoute(adapter),
|
|||
|
|
meta
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return route;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 监听屏幕尺寸变化,重新加载路由
|
|||
|
|
*/
|
|||
|
|
export function setupRouteWatcher(router: any) {
|
|||
|
|
let currentDeviceType = getDeviceType();
|
|||
|
|
|
|||
|
|
const handleResize = () => {
|
|||
|
|
const newDeviceType = getDeviceType();
|
|||
|
|
if (newDeviceType !== currentDeviceType) {
|
|||
|
|
currentDeviceType = newDeviceType;
|
|||
|
|
// 重新加载当前路由以应用新的组件
|
|||
|
|
const currentRoute = router.currentRoute.value;
|
|||
|
|
router.replace({
|
|||
|
|
...currentRoute,
|
|||
|
|
query: {
|
|||
|
|
...currentRoute.query,
|
|||
|
|
_refresh: Date.now() // 强制重新加载
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.addEventListener('resize', handleResize);
|
|||
|
|
|
|||
|
|
// 返回清理函数
|
|||
|
|
return () => {
|
|||
|
|
window.removeEventListener('resize', handleResize);
|
|||
|
|
};
|
|||
|
|
}
|