Al implementar rutas dinámicas en Vue 3 con Vite, pueden surgir ciertos inconvenientes, especialmente cuando los datos de ruta provienen de una API externa y requieren un procesamiento previo.
La importación estática de componentes usando import() funciona sin problemas:
component: () => import('@/components/ExampleComponent.vue')
Sin embargo, al intentar cargar rutas dinámicamente desde datos recibidos del back end, es necesario un paso adicional de transformación de datos si el formato no es el esperado. A continuación, se presenta un ejemplo de cómo reestructurar datos anidados para que se ajusten al formato de rutas de Vue Router:
const transformarDatosRuta = (datosApi) => {
const rutasProcesadas = datosApi.map((elemento) => {
// Función recursiva para procesar subrutas
const procesarSubRutas = (subRutas) =>
(subRutas || []).map((subElemento) => ({
path: subElemento.path,
name: subElemento.name,
// Asigna 'Layout' si es un nivel superior, de lo contrario usa la ruta
component: subElemento.nivelPermiso === 1 ? 'Layout' : subElemento.path,
meta: {
id: subElemento.id,
icon: subElemento.icon || undefined,
title: subElemento.title,
},
// Manejo recursivo de subniveles
...(subElemento.siguientesNiveles
? {
children: procesarSubRutas(subElemento.siguientesNiveles),
}
: {}),
}));
// Crea un nuevo objeto para la ruta actual
const rutaActual = {
path: elemento.path,
name: elemento.name,
component: elemento.nivelPermiso === 1 ? 'Layout' : elemento.path,
meta: {
id: elemento.id,
icon: elemento.icon || undefined,
title: elemento.title,
},
// Procesamiento recursivo de subrutas
...(elemento.siguientesNiveles
? {
children: procesarSubRutas(elemento.siguientesNiveles),
}
: {}),
};
// Elimina la propiedad 'children' si está vacía para evitar problemas de renderizado
if (rutaActual.children && rutaActual.children.length === 0) {
delete rutaActual.children;
}
return rutaActual;
});
// La adaptación a 'siguientesNiveles' se debe a nombres variables en la API
// Si la API usa consistentemente 'children', esta parte puede simplificarse.
return rutasProcesadas;
};
Una vez que los datos están en el formato correcto, la adición de estas rutas se maneja típicamente dentro de un guardia de ruta (router.beforeEach) en un archivo como permission.ts:
import { usePermissionStore } from '@/stores/permission';
import { useUserStore } from '@/stores/user';
import router from '@/router'; // Asume que tu router está configurado aquí
router.beforeEach(async (to, from, next) => {
const PermissionStore = usePermissionStore();
const UserStore = useUserStore();
if (to.path === '/login') {
next(); // Permitir acceso a la página de login
} else {
// Si las rutas aún no se han generado, las generamos
if (!PermissionStore.routes.length) {
try {
// Obtener las rutas dinámicas basadas en los roles del usuario
const dynamicRoutes = await PermissionStore.generateRoutes(UserStore.roles);
// Procesar y añadir las rutas generadas al router
await addDynamicRoutes(dynamicRoutes);
// Redirigir a la ruta actual para asegurar que se carguen correctamente
next({ ...to, replace: true });
} catch (error) {
console.error("Error generating routes:", error);
next('/login'); // Redirigir al login en caso de error
}
} else {
next(); // Las rutas ya existen, continuar
}
}
});
// Función para añadir rutas dinámicamente
const addDynamicRoutes = async (routesConfig) => {
routesConfig.forEach((routeItem) => {
// Si el componente es 'Layout', resuélvelo desde el directorio de layout
if (routeItem.component === 'Layout') {
routeItem.component = resolveComponent('layout/index.vue');
} else {
// De lo contrario, resuélvelo desde el directorio de vistas
routeItem.component = resolveComponent(routeItem.path);
}
// Si la ruta tiene hijos, procesarlos recursivamente
if (routeItem.children && Array.isArray(routeItem.children)) {
addDynamicRoutes(routeItem.children);
}
// Añadir la ruta configurada al router
router.addRoute(routeItem);
});
};
// Función para resolver componentes de forma dinámica
const resolveComponent = (componentPath) => {
// Mapeo de todos los archivos .vue en el directorio de vistas
const views = import.meta.glob('/src/views/**/*.vue');
// Mapeo de todos los archivos .vue en el directorio de layout
const layouts = import.meta.glob('/src/layout/**/*.vue');
let componentResolver;
if (componentPath.startsWith('layout/')) {
// Si la ruta comienza con 'layout/', busca en el directorio de layouts
componentResolver = layouts[`/src/${componentPath}`];
} else {
// De lo contrario, busca en el directorio de vistas
componentResolver = views[`/src/views${componentPath}.vue`];
}
if (!componentResolver) {
throw new Error(`Componente no encontrado: ${componentPath}. Verifique la ruta y la extensión .vue.`);
}
return componentResolver;
};