feat: optimize router logic

This commit is contained in:
2026-01-29 20:57:04 +08:00
parent 15885463d3
commit 110aec7d5f
7 changed files with 35 additions and 184 deletions

View File

@@ -1,5 +1,6 @@
{ {
"lockfileVersion": 0, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "template-vue-hucky", "name": "template-vue-hucky",

View File

@@ -23,6 +23,7 @@ export default typescriptEslint.config(
rules: { rules: {
"vue/singleline-html-element-content-newline": "off", "vue/singleline-html-element-content-newline": "off",
"vue/max-attributes-per-line": "off", "vue/max-attributes-per-line": "off",
"vue/multi-word-component-names": "off",
}, },
}, },
); );

View File

@@ -1,19 +0,0 @@
<script lang="ts" setup>
// About page component
</script>
<template>
<div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<h1 class="text-5xl font-bold">关于我们</h1>
<p class="py-6">这是一个自动路由注册的示例页面</p>
<p>路径将自动转换为 /about</p>
</div>
</div>
</div>
</template>
<style scoped>
/* Component styles */
</style>

View File

@@ -1,77 +0,0 @@
<script lang="ts" setup>
import { range } from "radash";
import { usePagination } from "@/utils/pagination";
const merchandise = ref([
{
id: 1,
name: "商品1",
},
]);
const { currentPage, changePage, pageNumbers, totalPages, getPaginatedData } =
usePagination(() => merchandise.value);
const handlePageChange = (page: number) => {
changePage(page);
};
onMounted(async () => {
await new Promise(() => {
setTimeout(() => {
merchandise.value = Array.from(range(1, 50)).map((item) => ({
id: item,
name: `商品${item}`,
}));
}, 1000);
});
});
</script>
<template>
<div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<h1 class="text-5xl font-bold">帮助</h1>
</div>
<div>
<ul>
<li v-for="item in getPaginatedData" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
<button
class="join-item btn btn-sm"
:disabled="currentPage === 1"
@click="handlePageChange(currentPage - 1)"
>
«
</button>
<button
v-for="page in pageNumbers"
:key="page"
class="join-item btn btn-sm"
:class="{
'btn-active': page === currentPage,
'btn-disabled': page === '...',
}"
@click="page !== '...' && handlePageChange(Number(page))"
>
{{ page }}
</button>
<button
class="join-item btn btn-sm"
:disabled="currentPage === totalPages"
@click="handlePageChange(currentPage + 1)"
>
»
</button>
</div>
</div>
</template>
<style scoped>
/* Component styles */
</style>

View File

@@ -6,8 +6,8 @@
<div class="hero min-h-screen bg-base-200"> <div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center"> <div class="hero-content text-center">
<div class="max-w-md"> <div class="max-w-md">
<h1 class="text-5xl font-bold">首页</h1> <h1 class="text-5xl font-bold">Hucky</h1>
<p class="py-6">欢迎使用自动路由系统</p> <p class="py-6">自动路由系统已加载</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,15 +0,0 @@
<script lang="ts" setup></script>
<template>
<div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<h1 class="text-5xl font-bold">我的</h1>
<p class="py-6">这是一个自动路由注册的示例页面</p>
<p>路径将自动转换为 /panel/me</p>
</div>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -5,91 +5,51 @@ import {
} from "vue-router"; } from "vue-router";
/** /**
* Convert PascalCase to kebab-case and remove 'Page' suffix * 通过 vite 的 meta.glob 从文件系统读取路由文件
* @param name PascalCase name (e.g., AboutMePage) * @returns 适配 vue router 的路由数组
* @returns kebab-case path (e.g., about-me)
*/
function formatPathFromComponentName(name: string): string {
// Remove .vue extension if present
const baseName = name.endsWith(".vue") ? name.slice(0, -4) : name;
// Remove Page suffix if present
const withoutPageSuffix = baseName.endsWith("Page")
? baseName.slice(0, -4)
: baseName;
// Convert PascalCase to kebab-case
return withoutPageSuffix
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
.toLowerCase();
}
/**
* Extract component name from file path
* @param filePath Path to the component file
* @returns Component name without extension
*/
function getComponentNameFromPath(filePath: string): string {
// Get the file name from the path
return filePath.split("/").pop() || "";
}
/**
* Generate route path from full file path, preserving directory structure
* @param filePath Full path to the component file (e.g., "@/pages/test/AbcPage.vue")
* @returns Route path (e.g., "/test/abc")
*/
function generateRoutePathFromFilePath(filePath: string): string {
// Remove the "@/pages" prefix and get the relative path
const relativePath = filePath.replace(/^@\/pages\//, "");
// Split into directory parts and filename
const pathParts = relativePath.split("/");
const fileName = pathParts.pop() || "";
const directories = pathParts.splice(3);
// Transform the filename using existing logic
const transformedFileName = formatPathFromComponentName(fileName);
// Combine directory path with transformed filename
const fullPath =
directories.length > 0
? `/${directories.join("/")}/${transformedFileName}`
: `/${transformedFileName}`;
return fullPath;
}
/**
* Generate routes from page components using Vite's import.meta.glob
* @returns Array of route configurations
*/ */
function generateRoutesFromPages(): RouteRecordRaw[] { function generateRoutesFromPages(): RouteRecordRaw[] {
const routes: RouteRecordRaw[] = []; const routes: RouteRecordRaw[] = [];
const pages = import.meta.glob("@/pages/**/*.vue"); // @ 路径已经在 vite 和 ts 进行配置
// 仅读取 .vue 结尾的文件
const pagePathMap = import.meta.glob("@/pages/**/*.vue");
for (const path in pages) { for (const path in pagePathMap) {
const componentName = getComponentNameFromPath(path); const routePath = getRoutePathFromFilePath(path);
const routePath = generateRoutePathFromFilePath(path);
// Special case for home page
const finalPath = routePath.toLowerCase() === "/home" ? "/" : routePath;
routes.push({ routes.push({
path: finalPath, path: routePath,
name: componentName, component: pagePathMap[path],
component: pages[path],
}); });
} }
return routes; return routes;
} }
// Generate routes from pages directory /**
* 根据文件系统路径生成路由路径
* @param filePath Full path to the component file (e.g., "@/pages/test/AbcPage.vue")
* @returns Route path (e.g., "test/abc")
*/
function getRoutePathFromFilePath(filePath: string): string {
// 移除 "/src/pages/" 前缀和 ".vue" 后缀获得相对路径
const relativePath = filePath
.replace(/^\/src\/pages\//, "")
.replace(/\.vue$/, "");
// 将 index 替换为空并将结尾多余的 / 符号删除
const noIndexPath = relativePath.replace(/index/, "").replace(/\/$/, "");
// 将路径调整成以 / 开头
const intactPath = `/${noIndexPath}`;
return intactPath;
}
const routes: RouteRecordRaw[] = generateRoutesFromPages(); const routes: RouteRecordRaw[] = generateRoutesFromPages();
// WebHash 使用 /# 开头处理前端页面路由
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes, routes,