feat: optimize router logic
This commit is contained in:
3
bun.lock
3
bun.lock
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 0,
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "template-vue-hucky",
|
||||
|
||||
@@ -23,6 +23,7 @@ export default typescriptEslint.config(
|
||||
rules: {
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -6,8 +6,8 @@
|
||||
<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>
|
||||
<h1 class="text-5xl font-bold">Hucky</h1>
|
||||
<p class="py-6">自动路由系统已加载</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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>
|
||||
@@ -5,91 +5,51 @@ import {
|
||||
} from "vue-router";
|
||||
|
||||
/**
|
||||
* Convert PascalCase to kebab-case and remove 'Page' suffix
|
||||
* @param name PascalCase name (e.g., AboutMePage)
|
||||
* @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
|
||||
* 通过 vite 的 meta.glob 从文件系统读取路由文件
|
||||
* @returns 适配 vue router 的路由数组
|
||||
*/
|
||||
function generateRoutesFromPages(): 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) {
|
||||
const componentName = getComponentNameFromPath(path);
|
||||
const routePath = generateRoutePathFromFilePath(path);
|
||||
|
||||
// Special case for home page
|
||||
const finalPath = routePath.toLowerCase() === "/home" ? "/" : routePath;
|
||||
for (const path in pagePathMap) {
|
||||
const routePath = getRoutePathFromFilePath(path);
|
||||
|
||||
routes.push({
|
||||
path: finalPath,
|
||||
name: componentName,
|
||||
component: pages[path],
|
||||
path: routePath,
|
||||
component: pagePathMap[path],
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// WebHash 使用 /# 开头处理前端页面路由
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
|
||||
Reference in New Issue
Block a user