Introudction
In my previous article, I discussed the experience of developing a mid-platform project using vue-antd-admin, but I didn't cover the dynamic routing configuration. The main reason was that the official documentation seemed comprehensive and I hadn't encountered any issues. However, recently I encountered a requirement to develop a message system where one page needed to display all messages. When I configured the page route according to the official documentation, I found it displayed in full-screen mode, but I needed it to appear in the main content area beside the sidebar menu. After carefully reviewing the documentation and source code, I spent considerable time resolving this requirement and decided to share my findings in this article.
Routing and Menu Configuration
To keep it concise, you should refer to the official documentation for basic usage. Essentially, the vue-antd-admin project relies entirely on vue-router and follows its configuration rules. It provides both synchronous and asynchronous routing solutions:
Synchronous routing is straightforward, following the familiar vue-router configuration. You can refer to the example code in src/router/config.js.
The key is asynchronous routing. First, you need to configure a local route map file, which breaks down all complete routes into individual route configurations for registration. This file is located at /router/async/router.map.js.
Then, based on the route configuration returned by the API, you combine it with the map file to generate the final routes. The API returns route format looks like this:
[
{
router: 'root', // Matches the route with registerName = root in router.map.js
children: [ // Child routes for the root route
{
router: 'dashboard', // Matches the route with registerName = dashboard in router.map.js
children: ['workplace', 'analysis'], // Child routes for dashboard, matching registerName = workplace and analysis respectively
},
{
router: 'form', // Matches the route with registerName = form in router.map.js
children: [ // Child routes for form
'basicForm', // Matches the route with registerName = basicForm in router.map.js
'stepForm', // Matches the route with registerName = stepForm in router.map.js
{
router: 'advanceForm', // Matches the route with registerName = advanceForm in router.map.js
path: 'advance' // Overrides the path property of advanceForm route
}
]
},
{
router: 'basicForm', // Matches the route with registerName = basicForm in router.map.js
name: 'Permission Form', // Overrides the name property of basicForm route
icon: 'file-excel', // Overrides the icon property of basicForm route
authority: 'form' // Overrides the authority property of basicForm route
}
]
}
]
Here's the important part: asynchronous routes cannot cover all routes. Pages like 404 and login don't need to be configured in the API permissions. Therefore, we need a basic route configuration file where registered routes can be merged into the final route configuration.
This file is located at /router/async/config.async.js
The official documentation shows the basic route configuration as follows:
const routesConfig = [
'login', // Matches the route with registerName = login in router.map.js
'root', // Matches the route with registerName = root in router.map.js
{
router: 'exp404', // Matches the route with registerName = exp404 in router.map.js
path: '*', // Overrides the path property of exp404 route
name: '404' // Overrides the name property of exp404 route
},
{
router: 'exp403', // Matches the route with registerName = exp403 in router.map.js
path: '/403', // Overrides the path property of exp403 route
name: '403' // Overrides the name property of exp403 route
}
]
If you configure it this way, the final route file might look like this:
You can see that the notification center is at the same level as the root route. The effect would be like this:
It seems I need to add the notification route to the children property of the home page so it generates a new menu in the sidebar. Let me try modifying the configuration:
Then I discovered that the notification route wasn't in the children of /home, meaning it wasn't merged properly. The issue lies in the route merge function, specifically in src/utils/routerUtil.js in the loadRoutes function:
// This line in the loadRoutes function is crucial
const finalRoutes = mergeRoutes(basicOptions.routes, routes)
Let's see how it's defined:
/**
* Merge routes
* @param target {Route[]} Local basic routes
* @param source {Route[]} API asynchronous routes
* @returns {Route[]}
*/
function mergeRoutes(target, source) {
const routesMap = {}
target.forEach(item => routesMap[item.path] = item)
source.forEach(item => {
routesMap[item.path] = item
})
return Object.values(routesMap)
}
As you can see, it's a shallow copy function where properties with the same name get overwritten. I realized I needed to create a deep copy function. Suddenly, I noticed there's already a deep merge route function defined right below mergeRoutes!
/**
* Deep merge routes
* @param target {Route[]}
* @param source {Route[]}
* @returns {Route[]}
*/
function deepMergeRoutes(target, source) {
// Map route array
const mapRoutes = routes => {
const routesMap = {}
routes.forEach(item => {
routesMap[item.path] = {
...item,
children: item.children ? mapRoutes(item.children) : undefined
}
})
return routesMap
}
console.log(target, source)
const tarMap = mapRoutes(target)
const srcMap = mapRoutes(source)
// Merge routes
const merge = _merge(srcMap, tarMap)
// Convert to routes array
const parseRoutesMap = routesMap => {
return Object.values(routesMap).map(item => {
if (item.children) {
item.children = parseRoutesMap(item.children)
} else {
delete item.children
}
return item
})
}
return parseRoutesMap(merge)
}
As you can see, it uses a depth-first recursive approach for merging. I replaced mergeRoutes with deepMergeRoutes and found it actually worked!
Although this function isn't mentioned in the official documentation, the author clearly considered this requirement. This makes using the asynchronous routing solution much more convenient.
By the way, the official documentation also injects three properties in the meta metadata of routes: icon, invisible, and page. The invisible property controls whether it appears in the sidebar menu, which is very useful.
Conclusion
Through this specific requirement investigation, I've introduced the dynamic routing solution of vue-antd-admin. Initially, I thought it wasn't very user-friendly, but today I discovered the deepMergeRoutes function, which makes configuring local basic routes much easier. Now I think this asynchronous dynamic routing solution is quite good. Thumbs up!
Feel free to discuss technical issues. v: 1032151090 (Please note the source)
Original publication: Ethan_Zhou's personal homepage - Articles - Juejin