@qlover/create-app 0.7.14 → 0.7.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/README.en.md +131 -0
- package/dist/templates/next-app/README.md +115 -20
- package/dist/templates/next-app/docs/en/api.md +387 -0
- package/dist/templates/next-app/docs/en/component.md +544 -0
- package/dist/templates/next-app/docs/en/database.md +496 -0
- package/dist/templates/next-app/docs/en/development-guide.md +727 -0
- package/dist/templates/next-app/docs/en/env.md +563 -0
- package/dist/templates/next-app/docs/en/i18n.md +287 -0
- package/dist/templates/next-app/docs/en/index.md +166 -0
- package/dist/templates/next-app/docs/en/page.md +457 -0
- package/dist/templates/next-app/docs/en/project-structure.md +177 -0
- package/dist/templates/next-app/docs/en/router.md +427 -0
- package/dist/templates/next-app/docs/en/theme.md +532 -0
- package/dist/templates/next-app/docs/en/validator.md +478 -0
- package/dist/templates/next-app/docs/zh/api.md +387 -0
- package/dist/templates/next-app/docs/zh/component.md +544 -0
- package/dist/templates/next-app/docs/zh/database.md +496 -0
- package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
- package/dist/templates/next-app/docs/zh/env.md +563 -0
- package/dist/templates/next-app/docs/zh/i18n.md +287 -0
- package/dist/templates/next-app/docs/zh/index.md +166 -0
- package/dist/templates/next-app/docs/zh/page.md +457 -0
- package/dist/templates/next-app/docs/zh/project-structure.md +177 -0
- package/dist/templates/next-app/docs/zh/router.md +427 -0
- package/dist/templates/next-app/docs/zh/theme.md +532 -0
- package/dist/templates/next-app/docs/zh/validator.md +476 -0
- package/package.json +1 -1
- package/dist/templates/next-app/docs/env.md +0 -94
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# 路由系统开发指南
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
1. [路由系统概述](#路由系统概述)
|
|
6
|
+
2. [路由服务和接口](#路由服务和接口)
|
|
7
|
+
3. [路由中间件和权限](#路由中间件和权限)
|
|
8
|
+
4. [路由导航和钩子](#路由导航和钩子)
|
|
9
|
+
5. [最佳实践和示例](#最佳实践和示例)
|
|
10
|
+
|
|
11
|
+
## 路由系统概述
|
|
12
|
+
|
|
13
|
+
### 1. 路由架构
|
|
14
|
+
|
|
15
|
+
项目采用 Next.js App Router 和自定义路由服务相结合的架构:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
路由层 服务层
|
|
19
|
+
┌──────────────┐ ┌──────────────┐
|
|
20
|
+
│ 页面路由 │ │ 路由服务 │
|
|
21
|
+
├──────────────┤ ├──────────────┤
|
|
22
|
+
│ 中间件 │ ◄─────┤ 导航服务 │
|
|
23
|
+
├──────────────┤ ├──────────────┤
|
|
24
|
+
│ 权限控制 │ │ 权限服务 │
|
|
25
|
+
└──────────────┘ └──────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. 路由类型
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// 1. 基础路由配置
|
|
32
|
+
export const routes = {
|
|
33
|
+
// 公共路由
|
|
34
|
+
home: '/',
|
|
35
|
+
login: '/login',
|
|
36
|
+
register: '/register',
|
|
37
|
+
|
|
38
|
+
// 管理路由
|
|
39
|
+
admin: {
|
|
40
|
+
root: '/admin',
|
|
41
|
+
users: '/admin/users',
|
|
42
|
+
settings: '/admin/settings'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 2. 路由元数据
|
|
47
|
+
interface RouteMetadata {
|
|
48
|
+
title: string;
|
|
49
|
+
auth?: boolean;
|
|
50
|
+
roles?: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. 路由配置
|
|
54
|
+
const routeMetadata: Record<keyof typeof routes, RouteMetadata> = {
|
|
55
|
+
home: {
|
|
56
|
+
title: 'page.home.title'
|
|
57
|
+
},
|
|
58
|
+
login: {
|
|
59
|
+
title: 'page.login.title'
|
|
60
|
+
},
|
|
61
|
+
admin: {
|
|
62
|
+
title: 'page.admin.title',
|
|
63
|
+
auth: true,
|
|
64
|
+
roles: ['admin']
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 路由服务和接口
|
|
70
|
+
|
|
71
|
+
### 1. 路由服务接口
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// 1. 路由接口定义
|
|
75
|
+
export interface RouterInterface {
|
|
76
|
+
// 导航方法
|
|
77
|
+
navigate(path: string, options?: NavigateOptions): void;
|
|
78
|
+
replace(path: string, options?: NavigateOptions): void;
|
|
79
|
+
back(): void;
|
|
80
|
+
|
|
81
|
+
// 路由状态
|
|
82
|
+
getCurrentRoute(): string;
|
|
83
|
+
getRouteParams(): Record<string, string>;
|
|
84
|
+
|
|
85
|
+
// 路由守卫
|
|
86
|
+
beforeEach(guard: NavigationGuard): void;
|
|
87
|
+
afterEach(hook: NavigationHook): void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 2. 导航选项
|
|
91
|
+
interface NavigateOptions {
|
|
92
|
+
query?: Record<string, string>;
|
|
93
|
+
state?: unknown;
|
|
94
|
+
locale?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 3. 导航守卫
|
|
98
|
+
type NavigationGuard = (to: string, from: string) => boolean | Promise<boolean>;
|
|
99
|
+
type NavigationHook = (to: string, from: string) => void;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. 路由服务实现
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
@injectable()
|
|
106
|
+
export class RouterService implements RouterInterface {
|
|
107
|
+
constructor(
|
|
108
|
+
@inject(I18nService) private i18n: I18nServiceInterface,
|
|
109
|
+
@inject(AuthService) private auth: AuthServiceInterface
|
|
110
|
+
) {}
|
|
111
|
+
|
|
112
|
+
// 导航到首页
|
|
113
|
+
gotoHome(): void {
|
|
114
|
+
this.navigate(routes.home);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 导航到登录页
|
|
118
|
+
gotoLogin(): void {
|
|
119
|
+
this.navigate(routes.login);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 基础导航方法
|
|
123
|
+
navigate(path: string, options?: NavigateOptions): void {
|
|
124
|
+
const locale = options?.locale || this.i18n.currentLocale;
|
|
125
|
+
const localePath = `/${locale}${path}`;
|
|
126
|
+
|
|
127
|
+
if (options?.query) {
|
|
128
|
+
const queryString = new URLSearchParams(options.query).toString();
|
|
129
|
+
window.location.href = `${localePath}?${queryString}`;
|
|
130
|
+
} else {
|
|
131
|
+
window.location.href = localePath;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 获取当前路由
|
|
136
|
+
getCurrentRoute(): string {
|
|
137
|
+
return window.location.pathname.replace(
|
|
138
|
+
new RegExp(`^/${this.i18n.currentLocale}`),
|
|
139
|
+
''
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 路由中间件和权限
|
|
146
|
+
|
|
147
|
+
### 1. 路由中间件
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// middleware.ts
|
|
151
|
+
import { NextResponse } from 'next/server';
|
|
152
|
+
import type { NextRequest } from 'next/server';
|
|
153
|
+
import { i18nConfig } from '@config/i18n';
|
|
154
|
+
|
|
155
|
+
export async function middleware(request: NextRequest) {
|
|
156
|
+
const { pathname } = request.nextUrl;
|
|
157
|
+
|
|
158
|
+
// 1. 语言中间件
|
|
159
|
+
if (!i18nConfig.supportedLngs.some((lng) => pathname.startsWith(`/${lng}`))) {
|
|
160
|
+
return NextResponse.redirect(
|
|
161
|
+
new URL(`/${i18nConfig.defaultLocale}${pathname}`, request.url)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 2. 认证中间件
|
|
166
|
+
const token = request.cookies.get('token');
|
|
167
|
+
if (pathname.startsWith('/admin') && !token) {
|
|
168
|
+
const locale = pathname.split('/')[1];
|
|
169
|
+
return NextResponse.redirect(new URL(`/${locale}/login`, request.url));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 配置中间件匹配路径
|
|
174
|
+
export const config = {
|
|
175
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 2. 权限控制
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// 1. 权限组件
|
|
183
|
+
export function PrivateRoute({
|
|
184
|
+
children,
|
|
185
|
+
roles
|
|
186
|
+
}: PropsWithChildren<{ roles?: string[] }>) {
|
|
187
|
+
const auth = useAuth();
|
|
188
|
+
const locale = useLocale();
|
|
189
|
+
|
|
190
|
+
// 检查认证状态
|
|
191
|
+
if (!auth.isAuthenticated) {
|
|
192
|
+
return redirect(`/${locale}/login`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 检查角色权限
|
|
196
|
+
if (roles && !roles.some(role => auth.hasRole(role))) {
|
|
197
|
+
return redirect(`/${locale}/403`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return children;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 2. 在页面中使用
|
|
204
|
+
export default function AdminPage() {
|
|
205
|
+
return (
|
|
206
|
+
<PrivateRoute roles={['admin']}>
|
|
207
|
+
<AdminDashboard />
|
|
208
|
+
</PrivateRoute>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 路由导航和钩子
|
|
214
|
+
|
|
215
|
+
### 1. 导航组件
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// 1. 本地化链接组件
|
|
219
|
+
export function LocaleLink({
|
|
220
|
+
href,
|
|
221
|
+
locale,
|
|
222
|
+
children,
|
|
223
|
+
...props
|
|
224
|
+
}: LocaleLinkProps) {
|
|
225
|
+
const currentLocale = useLocale();
|
|
226
|
+
const finalLocale = locale || currentLocale;
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<Link href={`/${finalLocale}${href}`} {...props}>
|
|
230
|
+
{children}
|
|
231
|
+
</Link>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 2. 导航菜单
|
|
236
|
+
export function AdminNav() {
|
|
237
|
+
const { navItems } = useStore(adminPageManager);
|
|
238
|
+
const locale = useLocale();
|
|
239
|
+
const t = useTranslations();
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<Menu>
|
|
243
|
+
{navItems.map(item => (
|
|
244
|
+
<Menu.Item key={item.key}>
|
|
245
|
+
<LocaleLink href={item.pathname}>
|
|
246
|
+
{t(item.i18nKey)}
|
|
247
|
+
</LocaleLink>
|
|
248
|
+
</Menu.Item>
|
|
249
|
+
))}
|
|
250
|
+
</Menu>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 2. 路由钩子
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// 1. 使用路由钩子
|
|
259
|
+
export function useRouteGuard() {
|
|
260
|
+
const router = useRouter();
|
|
261
|
+
const auth = useAuth();
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
// 路由变化时检查认证状态
|
|
265
|
+
const handleRouteChange = (url: string) => {
|
|
266
|
+
if (url.startsWith('/admin') && !auth.isAuthenticated) {
|
|
267
|
+
router.push('/login');
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
router.events.on('routeChangeStart', handleRouteChange);
|
|
272
|
+
return () => {
|
|
273
|
+
router.events.off('routeChangeStart', handleRouteChange);
|
|
274
|
+
};
|
|
275
|
+
}, [router, auth]);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 2. 在应用中使用
|
|
279
|
+
export function App() {
|
|
280
|
+
useRouteGuard();
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<RouterProvider>
|
|
284
|
+
{/* 应用内容 */}
|
|
285
|
+
</RouterProvider>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## 最佳实践和示例
|
|
291
|
+
|
|
292
|
+
### 1. 路由组织
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// 1. 按功能组织路由
|
|
296
|
+
app /
|
|
297
|
+
[locale] /
|
|
298
|
+
public / // 公共路由组
|
|
299
|
+
page.tsx; // 首页
|
|
300
|
+
about /
|
|
301
|
+
page.tsx(
|
|
302
|
+
// 关于页面
|
|
303
|
+
auth
|
|
304
|
+
) / // 认证路由组
|
|
305
|
+
login /
|
|
306
|
+
page.tsx;
|
|
307
|
+
register /
|
|
308
|
+
page.tsx(admin) / // 管理路由组
|
|
309
|
+
admin /
|
|
310
|
+
layout.tsx; // 管理布局
|
|
311
|
+
page.tsx; // 管理首页
|
|
312
|
+
users / page.tsx; // 用户管理
|
|
313
|
+
|
|
314
|
+
// 2. 路由常量
|
|
315
|
+
export const ROUTES = {
|
|
316
|
+
PUBLIC: {
|
|
317
|
+
HOME: '/',
|
|
318
|
+
ABOUT: '/about'
|
|
319
|
+
},
|
|
320
|
+
AUTH: {
|
|
321
|
+
LOGIN: '/login',
|
|
322
|
+
REGISTER: '/register'
|
|
323
|
+
},
|
|
324
|
+
ADMIN: {
|
|
325
|
+
ROOT: '/admin',
|
|
326
|
+
USERS: '/admin/users'
|
|
327
|
+
}
|
|
328
|
+
} as const;
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 2. 类型安全的路由
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// 1. 路由类型定义
|
|
335
|
+
type Route = keyof typeof ROUTES;
|
|
336
|
+
type PublicRoute = keyof typeof ROUTES.PUBLIC;
|
|
337
|
+
type AdminRoute = keyof typeof ROUTES.ADMIN;
|
|
338
|
+
|
|
339
|
+
// 2. 类型安全的导航
|
|
340
|
+
function useTypedRouter() {
|
|
341
|
+
const router = useRouter();
|
|
342
|
+
const locale = useLocale();
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
push: (route: Route) => {
|
|
346
|
+
router.push(`/${locale}${route}`);
|
|
347
|
+
},
|
|
348
|
+
replace: (route: Route) => {
|
|
349
|
+
router.replace(`/${locale}${route}`);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 3. 使用类型安全的路由
|
|
355
|
+
function Navigation() {
|
|
356
|
+
const router = useTypedRouter();
|
|
357
|
+
|
|
358
|
+
const handleClick = () => {
|
|
359
|
+
router.push(ROUTES.ADMIN.USERS); // 类型安全
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 3. 路由元数据
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// 1. 元数据类型
|
|
368
|
+
interface PageMetadata {
|
|
369
|
+
title: string;
|
|
370
|
+
description?: string;
|
|
371
|
+
auth?: boolean;
|
|
372
|
+
roles?: string[];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// 2. 路由元数据配置
|
|
376
|
+
const pageMetadata: Record<Route, PageMetadata> = {
|
|
377
|
+
[ROUTES.PUBLIC.HOME]: {
|
|
378
|
+
title: 'page.home.title',
|
|
379
|
+
description: 'page.home.description'
|
|
380
|
+
},
|
|
381
|
+
[ROUTES.ADMIN.ROOT]: {
|
|
382
|
+
title: 'page.admin.title',
|
|
383
|
+
auth: true,
|
|
384
|
+
roles: ['admin']
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// 3. 生成元数据
|
|
389
|
+
export async function generateMetadata({
|
|
390
|
+
params: { locale }
|
|
391
|
+
}: {
|
|
392
|
+
params: { locale: string };
|
|
393
|
+
}): Promise<Metadata> {
|
|
394
|
+
const t = await getTranslations(locale);
|
|
395
|
+
const route = getCurrentRoute();
|
|
396
|
+
const meta = pageMetadata[route];
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
title: t(meta.title),
|
|
400
|
+
description: meta.description ? t(meta.description) : undefined
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## 总结
|
|
406
|
+
|
|
407
|
+
项目的路由系统遵循以下原则:
|
|
408
|
+
|
|
409
|
+
1. **分层架构**:
|
|
410
|
+
- 页面路由层
|
|
411
|
+
- 服务层
|
|
412
|
+
- 中间件层
|
|
413
|
+
|
|
414
|
+
2. **类型安全**:
|
|
415
|
+
- 路由常量
|
|
416
|
+
- 类型定义
|
|
417
|
+
- 编译时检查
|
|
418
|
+
|
|
419
|
+
3. **权限控制**:
|
|
420
|
+
- 路由守卫
|
|
421
|
+
- 角色权限
|
|
422
|
+
- 中间件拦截
|
|
423
|
+
|
|
424
|
+
4. **最佳实践**:
|
|
425
|
+
- 路由组织
|
|
426
|
+
- 类型安全
|
|
427
|
+
- 元数据管理
|