skills/vue-router/SKILL.md
Vue Router 4 開發規範:路由設計、Navigation Guard、動態載入、Meta 型別安全與權限控制。
npx skillsauth add CloudyWing/ai-dotfiles vue-routerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
當偵測到專案使用 Vue Router 4(含 vue-router 4.x 相依套件)或使用者要求撰寫路由邏輯時,請自動套用以下規範。
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';
const routes: ReadonlyArray<RouteRecordRaw> = [
{
path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
children: [
{
path: '',
name: 'Home',
component: () => import('@/views/HomeView.vue')
},
{
path: 'orders',
name: 'OrderList',
component: () => import('@/views/orders/OrderListView.vue'),
meta: { requiresAuth: true }
},
{
path: 'orders/:id',
name: 'OrderDetail',
component: () => import('@/views/orders/OrderDetailView.vue'),
meta: { requiresAuth: true },
props: true
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/LoginView.vue'),
meta: { guestOnly: true }
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFoundView.vue')
}
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
});
export default router;
| 項目 | 規則 | 範例 |
| --- | --- | --- |
| Route name | PascalCase | OrderDetail, UserProfile |
| Path | kebab-case | /order-items, /user-profile |
| 參數 | camelCase | :orderId, :userId |
| View 檔名 | PascalCase + View 後綴 | OrderDetailView.vue |
<RouterView /> 渲染子路由內容。// ✅ 正確:動態 import
{
path: '/orders',
component: () => import('@/views/orders/OrderListView.vue')
}
// ❌ 錯誤:靜態 import(會全部打包進主 bundle)
import OrderListView from '@/views/orders/OrderListView.vue';
{
path: '/orders',
component: OrderListView
}
// router/index.ts
router.beforeEach(async (to, from) => {
const authStore = useAuthStore();
// 需要認證的路由
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
return {
name: 'Login',
query: { redirect: to.fullPath }
};
}
// 僅限訪客(已登入不可進入)
if (to.meta.guestOnly && authStore.isAuthenticated) {
return { name: 'Home' };
}
});
{
path: '/admin',
component: () => import('@/views/admin/AdminView.vue'),
beforeEnter: (to, from) => {
const authStore = useAuthStore();
if (!authStore.hasRole('admin')) {
return { name: 'Forbidden' };
}
}
}
<script setup lang="ts">
import { onBeforeRouteLeave } from 'vue-router';
const hasUnsavedChanges = ref(false);
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('尚有未儲存的變更,確定離開?');
if (!answer) {
return false;
}
}
});
</script>
useXxxStore()(而非模組頂層),確保 Pinia 已初始化。false 取消導航;回傳路由物件重導向;不回傳或回傳 true 允許通過。async/await。// router/types.ts
export {};
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean;
guestOnly?: boolean;
roles?: ReadonlyArray<string>;
title?: string;
}
}
tsconfig.json 涵蓋。to.meta.requiresAuth 等存取具備型別檢查。// 路由參數自動映射為元件 Props
{
path: '/orders/:id',
component: () => import('@/views/orders/OrderDetailView.vue'),
props: true // :id 會傳為 props.id
}
{
path: '/orders/:id',
component: () => import('@/views/orders/OrderDetailView.vue'),
props: (route) => ({
id: Number(route.params.id) // 轉型
})
}
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
// ✅ 使用 name 導航(路徑變更時不需改程式碼)
router.push({ name: 'OrderDetail', params: { id: orderId } });
// ✅ 附帶 query
router.push({ name: 'OrderList', query: { status: 'pending' } });
// ✅ 替換歷史記錄(返回鍵不會回到前一頁)
router.replace({ name: 'Home' });
// ❌ 避免:硬編碼路徑
router.push(`/orders/${orderId}`);
name,避免硬編碼路徑。params,查詢條件使用 query。router.afterEach((to) => {
const title = to.meta.title;
document.title = title ? `${title} | MyApp` : 'MyApp';
});
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition; // 瀏覽器返回時恢復滾動位置
}
if (to.hash) {
return { el: to.hash }; // 錨點定位
}
return { top: 0 }; // 新頁面回到頂端
}
});
大型專案可將路由定義拆分為多個模組:
// router/modules/order.ts
import type { RouteRecordRaw } from 'vue-router';
export const orderRoutes: ReadonlyArray<RouteRecordRaw> = [
{
path: 'orders',
name: 'OrderList',
component: () => import('@/views/orders/OrderListView.vue'),
meta: { requiresAuth: true, title: '訂單列表' }
},
{
path: 'orders/:id',
name: 'OrderDetail',
component: () => import('@/views/orders/OrderDetailView.vue'),
meta: { requiresAuth: true, title: '訂單明細' },
props: true
}
];
// router/index.ts
import { orderRoutes } from './modules/order';
import { customerRoutes } from './modules/customer';
const routes: ReadonlyArray<RouteRecordRaw> = [
{
path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
children: [
...orderRoutes,
...customerRoutes
]
}
];
tools
產生或補齊 .gitattributes,統一行尾處理、二進位識別與 lock files 標記,保留既有自訂偏好。
development
產生或補齊前端 Lint 設定(Prettier + ESLint Flat Config),統一格式化與程式碼品質規則,保留既有自訂偏好。
testing
依據事實校閱報告修改技術文件:以事實層為不可違反的約束,由改檔者負責表達層的措辭與行文連貫。Use when the user asks to apply fact-check results to a document, or to edit a document based on a previously produced fact-check-report.md.
data-ai
多份資料檔整合流程。當需要將兩份以上的資料檔(如 JSON、CSV)合併、補齊闕漏欄位或去重成單一檔案時使用。以 dry-run、筆數核對與抽樣比對降低整合錯誤。