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": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "template-vue-hucky",
|
"name": "template-vue-hucky",
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 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>
|
||||||
@@ -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";
|
} 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,
|
||||||
|
|||||||
Reference in New Issue
Block a user