Vue3動態(tài)路由踩坑實戰(zhàn)記錄
一、問題背景
最近在開發(fā)一個基于 Vue 3 + Element Plus + Koa 后端的航天管理系統(tǒng)時,遇到了一個令人困擾的問題:無論點擊哪個菜單,頁面始終顯示固定頁面,也就是我寫的首頁內容。
下面我將詳細記錄整個排查過程,既是項目復盤,也希望能幫到遇到同類問題的開發(fā)者。
二、問題現(xiàn)象
核心現(xiàn)象非常典型,具體表現(xiàn)為:
點擊菜單 "用戶管理" → URL 變?yōu)?/system/user → 頁面顯示 "我是首頁內容" 點擊菜單 "運載火箭" → URL 變?yōu)?/space/rocket → 頁面依然顯示 "我是首頁內容"
關鍵特征:
路由地址正確變化,說明路由跳轉邏輯正常;
頁面內容始終不變,始終顯示我寫的首頁內容,說明路由匹配成功,但對應組件未正確渲染;
控制臺無明顯報錯,排查難度增加。
三、項目基礎結構
src/
├── router/
│ ├── index.js # 基礎路由 + 路由守衛(wèi)
│ └── dynamicRoutes.js # 動態(tài)路由注冊邏輯
├── layout/
│ └── index.vue # 公共布局(側邊欄 + 主內容區(qū))
├── views/
│ ├── Home.vue # 首頁組件(我寫的首頁內容)
│ ├── Login.vue # 登錄頁組件
│ ├── system/ # 系統(tǒng)管理相關頁面
│ ├── space/ # 航天器相關頁面
│ └── 其他功能模塊頁面
├── store/
│ └── index.js # 用戶狀態(tài)管理(登錄、角色等)
└── 其他配置文件
server/
└── routes/
└── menu.js # 后端菜單接口,返回動態(tài)菜單數據四、排查過程(從易到難,逐步定位)
第一步:檢查基礎路由配置
首先排查最基礎的靜態(tài)路由配置,查看 src/router/index\.js:
// src/router/index.js (主路由配置)
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layout/index.vue' // 公共布局組件
import { useUserStore } from '@/store/index.js'
// 基礎路由
const baseRoutes = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue')
},
{
path: '/',
component: Layout, // 全局唯一布局
name: 'HomeLayout', // 關鍵:給父路由命名,用于動態(tài)路由掛載
redirect: '/home',
children: [
{
path: 'home',
name: 'Home',
component: () => import('@/views/Home.vue'), // 對應我寫的首頁內容
meta: { requiresAuth: true }
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes: baseRoutes
})
// 路由守衛(wèi):權限攔截
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
console.log('=== 路由守衛(wèi)調試 ===')
console.log('要去的位置:', to.path)
console.log('用戶角色:', userStore.roles)
console.log('路由元信息:', to.meta)
if (to.path === '/login') {
next()
return
}
// 檢查是否登錄
if (!userStore.isLoggedIn) {
next('/login')
return
}
// 檢查路由權限
if (to.meta.requiresAuth) {
// 如果路由需要特定角色
if (to.meta.roles && to.meta.roles.length > 0) {
const hasRole = to.meta.roles.some(role => userStore.roles.includes(role))
if (!hasRole) {
next('/403') // 無權限頁面(可自行實現(xiàn))
return
}
}
}
next()
})
export default router
排查結論:基礎路由配置無問題,我寫的首頁內容能正常顯示,登錄頁跳轉正常,路由守衛(wèi)邏輯嚴謹,排除基礎路由導致的問題。
第二步:檢查動態(tài)路由注冊(核心坑 1)
接下來排查動態(tài)路由注冊邏輯,查看 src/router/dynamicRoutes\.js 最初的寫法:
// 最初的錯誤寫法 ?
router.addRoute({
path: child.path, // 例如:/system/user
component: Layout, // 錯誤:重復創(chuàng)建 Layout 組件
children: [...]
})
問題原因:
動態(tài)路由被注冊為 獨立的頂層路由,而非掛載到已有的 HomeLayout 父路由下,導致系統(tǒng)中存在多個獨立的 Layout 組件,路由匹配混亂,子頁面無法嵌入主布局,最終 fallback 到我寫的首頁內容。
修復方案:將動態(tài)路由掛載到已有的 HomeLayout 父路由下(通過父路由 name 掛載):
// 正確寫法 ?
router.addRoute('HomeLayout', {
path: routePath,
name: routePath.replace(/\//g, '-'),
component: () => import(...),
meta: child.meta
})
第三步:檢查組件路徑拼接(核心坑 2)
修復路由掛載方式后,問題依然存在,打開控制臺發(fā)現(xiàn) Vite 報錯:
[plugin:vite:vue] src/views/views/space/rocket.vue At least one <template> or <script> is required
問題原因:組件路徑拼接錯誤,使用相對路徑導致路徑重復:
// 錯誤寫法 ?
const importPath = `../views${child.path}.vue`
// child.path = '/space/rocket'
// 最終解析結果:../views/space/rocket.vue → 實際路徑為 src/views/views/space/rocket.vue(重復 views)
修復方案:使用項目根目錄絕對路徑,避免路徑解析錯誤:
// 正確寫法 ?
const importPath = `/src/views${child.path}.vue`
第四步:檢查 Vite 動態(tài) import 別名解析(核心坑 3)
嘗試使用項目中常用的 @ 別名簡化路徑,發(fā)現(xiàn)動態(tài) import 中別名無法生效:
// 錯誤寫法 ? Vite 無法解析動態(tài)路徑中的 @ 別名
import(`@/views${child.path}.vue`)
// 正確寫法 ? 使用絕對路徑
import(`/src/views${child.path}.vue`)
問題原因:Vite 的別名解析的在靜態(tài) import 中能正常工作,但在動態(tài) import 中,由于路徑是動態(tài)拼接的,無法被 Vite 靜態(tài)分析,導致別名解析失敗,組件加載失敗,最終顯示我寫的首頁內容。
解決方案:動態(tài) import 一律使用絕對路徑 /src/views/xxx,放棄 @ 別名。
第五步:檢查登錄后動態(tài)路由重新加載(核心坑 4)
修復路徑問題后,頁面依然不切換,控制臺出現(xiàn) 401 錯誤:
401 Unauthorized - 請求菜單數據被拒絕
問題原因:動態(tài)路由最初在應用啟動時就加載,但此時用戶尚未登錄,沒有 Token,請求后端菜單接口被拒絕,導致動態(tài)路由未成功注冊,路由匹配時只能匹配到我寫的首頁內容。
修復方案:在登錄成功后,拿到 Token 再重新初始化加載動態(tài)路由:
// Login.vue 登錄邏輯(關鍵代碼)
const handleLogin = async () => {
try {
await loginFormRef.value.validate()
loading.value = true
// 調用登錄接口
const response = await axios.post('http://localhost:3000/api/auth/login', {
username: loginForm.username,
password: loginForm.password
})
if (response.data.code === 200) {
// 登錄成功,保存用戶信息到 store
userStore.login(response.data.data)
// 設置 axios 默認請求頭(攜帶 Token)
axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.data.token}`
// 關鍵:登錄后重新加載動態(tài)路由
await initDynamicRoutes(router)
ElMessage.success(response.data.message)
router.push('/')
} else {
ElMessage.error(response.data.message)
}
} catch (error) {
console.error('登錄失敗:', error)
const message = error.response?.data?.message || '登錄失敗,請重試'
ElMessage.error(message)
} finally {
loading.value = false
}
}
第六步:清理不存在的頁面(隱性坑)
最后,排查發(fā)現(xiàn)后端返回的菜單數據中,有部分菜單項對應的前端頁面并不存在(例如:空間站、衛(wèi)星等頁面),導致動態(tài) import 加載組件失敗,路由匹配降級到我寫的首頁內容。
修復方案:注釋掉后端菜單中不存在的菜單項,保證后端菜單路徑與前端 views 目錄下的文件一一對應:
// server/routes/menu.js - 注釋掉不存在的頁面
{
key: '航天器',
icon: '??',
children: [
{ name: '運載火箭', path: '/space/rocket', meta: { ... } },
{ name: '載人飛船', path: '/space/spaceship', meta: { ... } },
// { name: '空間站', path: '/space/station', meta: { ... } }, // 不存在,注釋
// { name: '衛(wèi)星', path: '/space/satellite', meta: { ... } }, // 不存在,注釋
]
}
五、問題總結(一張表看懂所有坑)
| 問題現(xiàn)象 | 根本原因 | 解決方案 |
|---|---|---|
| 路由跳轉頁面不變,始終顯示我寫的首頁內容 | 動態(tài)路由注冊為頂層路由,重復創(chuàng)建 Layout | 將動態(tài)路由掛載到父路由 HomeLayout 下 |
| Vite 報錯,組件路徑重復(views/views) | 相對路徑拼接錯誤,導致路徑解析異常 | 使用 /src/views 絕對路徑拼接組件路徑 |
| 動態(tài) import 別名 @ 失效 | Vite 無法靜態(tài)分析動態(tài)路徑中的別名 | 放棄別名,統(tǒng)一使用絕對路徑導入 |
| 加載動態(tài)路由接口 401 | 應用初始化時未登錄,無 Token 無法請求菜單 | 登錄成功后再重新加載動態(tài)路由 |
| 組件加載失敗,路由降級到我寫的首頁內容 | 后端菜單與前端實際頁面文件不匹配 | 清理無效菜單項,保持路徑一致 |
六、最終完整可運行源碼
1. 動態(tài)路由注冊 src/router/dynamicRoutes.js
import axios from 'axios'
import Layout from '@/layout/index.vue'
export async function initDynamicRoutes(router) {
console.log('?? 開始加載動態(tài)路由...')
try {
const token = localStorage.getItem('token')
console.log('?? token:', token ? '存在' : '不存在')
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
console.log('?? 正在請求菜單數據...')
const res = await axios.get('http://localhost:3000/api/menu/list')
const menuList = res.data.data
console.log('?? 獲取到菜單數據:', JSON.stringify(menuList, null, 2))
let count = 0
menuList.forEach(group => {
group.children.forEach(child => {
if (child.path === '/') return
// 組件路徑:絕對路徑,避免解析錯誤
const importPath = `/src/views${child.path}.vue`
console.log('?? 組件路徑:', importPath)
// 路由路徑(移除開頭的 /,避免路由層級錯誤)
const routePath = child.path.startsWith('/') ? child.path.slice(1) : child.path
console.log('??? 路由路徑:', routePath)
// 注冊動態(tài)路由(掛載到 HomeLayout 父路由下)
router.addRoute('HomeLayout', {
path: routePath,
name: routePath.replace(/\//g, '-'), // 路由 name 去斜杠,避免沖突
component: () => import(/* @vite-ignore */ importPath).catch(() => {
console.warn(`?? 組件不存在: ${importPath}`)
return import('/src/views/Home.vue') // 降級到我寫的首頁內容
}),
meta: child.meta // 繼承菜單的權限元信息
})
count++
console.log(`? 已注冊路由 ${count}:`, routePath)
})
})
const allRoutes = router.getRoutes()
console.log('?? 所有注冊的路由:', allRoutes.map(r => ({ path: r.path, name: r.name, parentName: r.parentName })))
console.log('? 所有動態(tài)路由加載完成,共注冊', count, '個路由')
} catch (err) {
console.error('? 路由加載失敗', err.message || err)
if (err.response?.status === 401) {
// 401 清除 Token,跳轉登錄頁
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
}
}
}
2. 登錄頁完整代碼 Login.vue
<template>
<div class="login-container">
<div class="login-form">
<h2 class="login-title">航天管理系統(tǒng)</h2>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
label-width="0px"
class="login-form-content"
>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="請輸入用戶名"
size="large"
prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="請輸入密碼"
size="large"
prefix-icon="Lock"
/>
</el-form-item>
<el-form-item prop="role">
<el-select
v-model="loginForm.role"
placeholder="選擇角色"
size="large"
style="width: 100%"
>
<el-option label="普通用戶" value="user" />
<el-option label="管理員" value="admin" />
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
style="width: 100%"
:loading="loading"
@click="handleLogin"
>
登錄
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/index.js'
import { initDynamicRoutes } from '@/router/dynamicRoutes.js'
import axios from 'axios'
const router = useRouter()
const userStore = useUserStore()
const loginFormRef = ref()
const loading = ref(false)
const loginForm = reactive({
username: '',
password: '',
role: ''
})
const loginRules = {
username: [
{ required: true, message: '請輸入用戶名', trigger: 'blur' }
],
password: [
{ required: true, message: '請輸入密碼', trigger: 'blur' }
],
role: [
{ required: true, message: '請選擇角色', trigger: 'change' }
]
}
const handleLogin = async () => {
try {
await loginFormRef.value.validate()
loading.value = true
// 調用真實的登錄API
const response = await axios.post('http://localhost:3000/api/auth/login', {
username: loginForm.username,
password: loginForm.password
})
console.log('=== 登錄返回數據 ===')
console.log('完整響應:', response.data)
console.log('用戶數據:', response.data.data)
if (response.data.code === 200) {
// 登錄成功,保存用戶信息到store
userStore.login(response.data.data)
// 設置axios默認header(攜帶Token)
axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.data.token}`
// 關鍵:重新加載動態(tài)路由
await initDynamicRoutes(router)
ElMessage.success(response.data.message)
router.push('/')
} else {
ElMessage.error(response.data.message)
}
} catch (error) {
console.error('登錄失敗:', error)
const message = error.response?.data?.message || '登錄失敗,請重試'
ElMessage.error(message)
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, rgba(8, 19, 47, 0.85), rgba(2, 12, 31, 0.65)), url('/images/1.jpeg') no-repeat center center;
background-size: cover;
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 24px;
}
.login-container::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at top left, rgba(255, 255, 255, 0.14), transparent 30%), radial-gradient(circle at bottom right, rgba(255, 255, 255, 0.08), transparent 25%);
z-index: 1;
}
.login-form {
position: relative;
z-index: 2;
width: 360px;
max-width: 100%;
padding: 12px 38px 22px 38px;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(16px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.5);
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.22);
}
.login-title {
text-align: center;
margin-bottom: 23px;
color: #1f2a3c;
font-size: 26px;
font-weight: 700;
}
.login-form-content {
max-width: 100%;
}
.el-form-item {
margin-bottom: 18px;
}
.el-input__inner,
.el-select .el-input__inner {
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(31, 42, 60, 0.16);
border-radius: 10px;
box-shadow: inset 0 1px 2px rgba(31, 42, 60, 0.06);
}
.el-input__inner:focus,
.el-select .el-input__inner:focus {
border-color: #409eff;
}
.el-input__inner:hover,
.el-select .el-input__inner:hover {
border-color: rgba(31, 42, 60, 0.24);
}
.el-button {
border-radius: 10px;
font-weight: 600;
background: #409eff;
border-color: #409eff;
}
.el-button:hover {
background: #3a8de0;
}
</style>
3. 后端菜單接口 server/routes/menu.js
// server/routes/menu.js - 菜單路由相關
const Router = require('koa-router')
const router = new Router()
// 完整的菜單數據
const allMenus = [
{
key: '首頁',
icon: '??',
children: [
{ name: '首頁', path: '/', meta: { title: '首頁', requiresAuth: false } }
]
},
{
key: '航天器',
icon: '??',
children: [
{ name: '運載火箭', path: '/space/rocket', meta: { title: '運載火箭', requiresAuth: true, roles: ['user', 'admin'] } },
{ name: '載人飛船', path: '/space/spaceship', meta: { title: '載人飛船', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '空間站', path: '/space/station', meta: { title: '空間站', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '衛(wèi)星', path: '/space/satellite', meta: { title: '衛(wèi)星', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '探測器', path: '/space/probe', meta: { title: '深空探測器', requiresAuth: true, roles: ['user', 'admin'] } }
]
},
{
key: '發(fā)射任務',
icon: '??',
children: [
{ name: '任務規(guī)劃', path: '/mission/plan', meta: { title: '任務規(guī)劃', requiresAuth: true, roles: ['user', 'admin'] } },
{ name: '發(fā)射記錄', path: '/mission/record', meta: { title: '發(fā)射記錄', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '在軌任務', path: '/mission/orbit', meta: { title: '在軌任務', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '返回任務', path: '/mission/return', meta: { title: '返回任務', requiresAuth: true, roles: ['user', 'admin'] } }
]
},
{
key: '測控通信',
icon: '??',
children: [
{ name: '地面測控', path: '/tracking/ground', meta: { title: '地面測控', requiresAuth: true, roles: ['user', 'admin'] } },
{ name: '航天測控網', path: '/tracking/network', meta: { title: '航天測控網', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '數據傳輸', path: '/tracking/data', meta: { title: '數據傳輸', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '深空通信', path: '/tracking/deepspace', meta: { title: '深空通信', requiresAuth: true, roles: ['user', 'admin'] } }
]
},
{
key: '航天員',
icon: '?????',
children: [
{ name: '航天員列表', path: '/astronaut/list', meta: { title: '航天員列表', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '訓練計劃', path: '/astronaut/training', meta: { title: '訓練計劃', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '出艙活動', path: '/astronaut/eva', meta: { title: '出艙活動', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '生活保障', path: '/astronaut/life', meta: { title: '生活保障', requiresAuth: true, roles: ['user', 'admin'] } }
]
},
{
key: '科學實驗',
icon: '??',
children: [
{ name: '微重力實驗', path: '/experiment/gravity', meta: { title: '微重力實驗', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '生命科學', path: '/experiment/life', meta: { title: '生命科學', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '材料科學', path: '/experiment/material', meta: { title: '材料科學', requiresAuth: true, roles: ['user', 'admin'] } },
// { name: '天文觀測', path: '/experiment/astronomy', meta: { title: '天文觀測', requiresAuth: true, roles: ['user', 'admin'] } }
]
},
{
key: '系統(tǒng)管理',
icon: '??',
children: [
{ name: '用戶管理', path: '/system/user', meta: { title: '用戶管理', requiresAuth: true, roles: ['admin'] } },
{ name: '角色管理', path: '/system/role', meta: { title: '角色管理', requiresAuth: true, roles: ['admin'] } },
{ name: '菜單管理', path: '/system/menu', meta: { title: '菜單管理', requiresAuth: true, roles: ['admin'] } },
// { name: '權限管理', path: '/system/permission', meta: { title: '權限管理', requiresAuth: true, roles: ['admin'] } },
{ name: '崗位管理', path: '/system/position', meta: { title: '崗位管理', requiresAuth: true, roles: ['admin'] } }
]
}
]
// 根據用戶權限動態(tài)返回菜單
router.get('/api/menu/list', async (ctx) => {
// 從認證中間件獲取用戶信息(需自行實現(xiàn)認證中間件)
const userRoles = ctx.state.user?.roles || []
// 過濾菜單:根據用戶角色
const filteredMenus = allMenus.map(group => {
const filteredChildren = group.children.filter(child => {
const meta = child.meta
// 不需要認證的菜單(如首頁)
if (!meta.requiresAuth) return true
// 需要認證但沒有角色限制的菜單
if (!meta.roles) return false
// 檢查用戶是否有權限訪問
return meta.roles.some(role => userRoles.includes(role))
})
// 如果分組下有子菜單,則保留分組;否則過濾掉
return filteredChildren.length > 0 ? {
...group,
children: filteredChildren
} : null
}).filter(Boolean)
ctx.body = {
code: 200,
message: 'success',
data: filteredMenus
}
})
module.exports = {
router,
allMenus
}
七、經驗教訓與避坑要點
路由結構要統(tǒng)一:所有功能頁面應共享同一個 Layout 組件,動態(tài)路由必須掛載到父路由下,避免重復創(chuàng)建 Layout,導致路由匹配混亂,最終始終顯示我寫的首頁內容。
動態(tài) import 注意事項:Vite 環(huán)境下,動態(tài) import 無法解析
@別名,使用絕對路徑/src/views/xxx更可靠,避免路徑解析錯誤,防止組件加載失敗后顯示首頁內容。權限相關動態(tài)路由:動態(tài)路由依賴用戶 Token 和菜單接口,必須在登錄成功、拿到 Token 后再重新加載,不能在應用初始化時加載,否則動態(tài)路由注冊失敗,會一直顯示我寫的首頁內容。
數據與文件要同步:后端返回的菜單數據,必須與前端
views目錄下的實際頁面文件一一對應,避免出現(xiàn)“菜單存在但頁面不存在”的情況,防止組件加載失敗降級到首頁內容。排查技巧:遇到“URL 變、頁面不變,始終顯示我寫的首頁內容”的問題,優(yōu)先排查 3 點:① 動態(tài)路由掛載是否正確;② 組件導入路徑是否正確;③ 動態(tài)路由是否成功注冊。
八、結束語
這個看似簡單的“頁面始終顯示我寫的首頁內容”問題,牽扯到路由配置、動態(tài)導入、權限校驗、前后端數據同步等多個層面,也讓我深刻體會到:前端開發(fā)中,細節(jié)決定成敗。
如果你也遇到了類似的動態(tài)路由問題,希望這篇文章能幫你快速定位問題、解決問題,少走彎路。
到此這篇關于Vue3動態(tài)路由踩坑實戰(zhàn)記錄的文章就介紹到這了,更多相關Vue3動態(tài)路由踩坑內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue+Ant Design進行大數據量下拉框卡頓與表單提交優(yōu)化
在現(xiàn)代前端開發(fā)中,處理大數據量渲染和表單交互是常見的挑戰(zhàn),本文將探討如何優(yōu)化 Ant Design Vue 下拉框在大數據量情況下的性能問題,并解決表單提交后重復提示的問題,需要的可以了解下2025-03-03
Vue組件傳值方式(props屬性,父到子,子到父,兄弟傳值)
這篇文章主要介紹了Vue組件傳值方式(props屬性,父到子,子到父,兄弟傳值),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
antd vue table跨行合并單元格,并且自定義內容實例
這篇文章主要介紹了antd vue table跨行合并單元格,并且自定義內容實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Vue 中使用vue2-highcharts實現(xiàn)top功能的示例
下面小編就為大家分享一篇Vue 中使用vue2-highcharts實現(xiàn)top功能的示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03

