@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
|
+
# Router System Development Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Router System Overview](#router-system-overview)
|
|
6
|
+
2. [Router Services and Interfaces](#router-services-and-interfaces)
|
|
7
|
+
3. [Router Middleware and Permissions](#router-middleware-and-permissions)
|
|
8
|
+
4. [Router Navigation and Hooks](#router-navigation-and-hooks)
|
|
9
|
+
5. [Best Practices and Examples](#best-practices-and-examples)
|
|
10
|
+
|
|
11
|
+
## Router System Overview
|
|
12
|
+
|
|
13
|
+
### 1. Router Architecture
|
|
14
|
+
|
|
15
|
+
The project combines Next.js App Router with custom router services:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Router Layer Service Layer
|
|
19
|
+
┌──────────────┐ ┌──────────────┐
|
|
20
|
+
│ Page Routes │ │Router Service│
|
|
21
|
+
├──────────────┤ ├──────────────┤
|
|
22
|
+
│ Middleware │ ◄─────┤ Navigation │
|
|
23
|
+
├──────────────┤ ├──────────────┤
|
|
24
|
+
│ Auth Control│ │Auth Service │
|
|
25
|
+
└──────────────┘ └──────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Route Types
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// 1. Basic route configuration
|
|
32
|
+
export const routes = {
|
|
33
|
+
// Public routes
|
|
34
|
+
home: '/',
|
|
35
|
+
login: '/login',
|
|
36
|
+
register: '/register',
|
|
37
|
+
|
|
38
|
+
// Admin routes
|
|
39
|
+
admin: {
|
|
40
|
+
root: '/admin',
|
|
41
|
+
users: '/admin/users',
|
|
42
|
+
settings: '/admin/settings'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 2. Route metadata
|
|
47
|
+
interface RouteMetadata {
|
|
48
|
+
title: string;
|
|
49
|
+
auth?: boolean;
|
|
50
|
+
roles?: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Route configuration
|
|
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
|
+
## Router Services and Interfaces
|
|
70
|
+
|
|
71
|
+
### 1. Router Service Interface
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// 1. Router interface definition
|
|
75
|
+
export interface RouterInterface {
|
|
76
|
+
// Navigation methods
|
|
77
|
+
navigate(path: string, options?: NavigateOptions): void;
|
|
78
|
+
replace(path: string, options?: NavigateOptions): void;
|
|
79
|
+
back(): void;
|
|
80
|
+
|
|
81
|
+
// Route state
|
|
82
|
+
getCurrentRoute(): string;
|
|
83
|
+
getRouteParams(): Record<string, string>;
|
|
84
|
+
|
|
85
|
+
// Route guards
|
|
86
|
+
beforeEach(guard: NavigationGuard): void;
|
|
87
|
+
afterEach(hook: NavigationHook): void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 2. Navigation options
|
|
91
|
+
interface NavigateOptions {
|
|
92
|
+
query?: Record<string, string>;
|
|
93
|
+
state?: unknown;
|
|
94
|
+
locale?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 3. Navigation guards
|
|
98
|
+
type NavigationGuard = (to: string, from: string) => boolean | Promise<boolean>;
|
|
99
|
+
type NavigationHook = (to: string, from: string) => void;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Router Service Implementation
|
|
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
|
+
// Navigate to home
|
|
113
|
+
gotoHome(): void {
|
|
114
|
+
this.navigate(routes.home);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Navigate to login
|
|
118
|
+
gotoLogin(): void {
|
|
119
|
+
this.navigate(routes.login);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Basic navigation method
|
|
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
|
+
// Get current route
|
|
136
|
+
getCurrentRoute(): string {
|
|
137
|
+
return window.location.pathname.replace(
|
|
138
|
+
new RegExp(`^/${this.i18n.currentLocale}`),
|
|
139
|
+
''
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Router Middleware and Permissions
|
|
146
|
+
|
|
147
|
+
### 1. Router Middleware
|
|
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. Language middleware
|
|
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. Authentication middleware
|
|
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
|
+
// Configure middleware matching paths
|
|
174
|
+
export const config = {
|
|
175
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 2. Permission Control
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// 1. Permission component
|
|
183
|
+
export function PrivateRoute({
|
|
184
|
+
children,
|
|
185
|
+
roles
|
|
186
|
+
}: PropsWithChildren<{ roles?: string[] }>) {
|
|
187
|
+
const auth = useAuth();
|
|
188
|
+
const locale = useLocale();
|
|
189
|
+
|
|
190
|
+
// Check authentication status
|
|
191
|
+
if (!auth.isAuthenticated) {
|
|
192
|
+
return redirect(`/${locale}/login`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check role permissions
|
|
196
|
+
if (roles && !roles.some(role => auth.hasRole(role))) {
|
|
197
|
+
return redirect(`/${locale}/403`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return children;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 2. Use in page
|
|
204
|
+
export default function AdminPage() {
|
|
205
|
+
return (
|
|
206
|
+
<PrivateRoute roles={['admin']}>
|
|
207
|
+
<AdminDashboard />
|
|
208
|
+
</PrivateRoute>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Router Navigation and Hooks
|
|
214
|
+
|
|
215
|
+
### 1. Navigation Components
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// 1. Localized link component
|
|
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. Navigation menu
|
|
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. Router Hooks
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// 1. Use router hook
|
|
259
|
+
export function useRouteGuard() {
|
|
260
|
+
const router = useRouter();
|
|
261
|
+
const auth = useAuth();
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
// Check authentication status on route change
|
|
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. Use in application
|
|
279
|
+
export function App() {
|
|
280
|
+
useRouteGuard();
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<RouterProvider>
|
|
284
|
+
{/* Application content */}
|
|
285
|
+
</RouterProvider>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Best Practices and Examples
|
|
291
|
+
|
|
292
|
+
### 1. Route Organization
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// 1. Organize routes by feature
|
|
296
|
+
app /
|
|
297
|
+
[locale] /
|
|
298
|
+
public / // Public route group
|
|
299
|
+
page.tsx; // Homepage
|
|
300
|
+
about /
|
|
301
|
+
page.tsx(
|
|
302
|
+
// About page
|
|
303
|
+
auth
|
|
304
|
+
) / // Auth route group
|
|
305
|
+
login /
|
|
306
|
+
page.tsx;
|
|
307
|
+
register /
|
|
308
|
+
page.tsx(admin) / // Admin route group
|
|
309
|
+
admin /
|
|
310
|
+
layout.tsx; // Admin layout
|
|
311
|
+
page.tsx; // Admin homepage
|
|
312
|
+
users / page.tsx; // User management
|
|
313
|
+
|
|
314
|
+
// 2. Route constants
|
|
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. Type-Safe Routes
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// 1. Route type definitions
|
|
335
|
+
type Route = keyof typeof ROUTES;
|
|
336
|
+
type PublicRoute = keyof typeof ROUTES.PUBLIC;
|
|
337
|
+
type AdminRoute = keyof typeof ROUTES.ADMIN;
|
|
338
|
+
|
|
339
|
+
// 2. Type-safe navigation
|
|
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. Use type-safe routes
|
|
355
|
+
function Navigation() {
|
|
356
|
+
const router = useTypedRouter();
|
|
357
|
+
|
|
358
|
+
const handleClick = () => {
|
|
359
|
+
router.push(ROUTES.ADMIN.USERS); // Type-safe
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 3. Route Metadata
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// 1. Metadata type
|
|
368
|
+
interface PageMetadata {
|
|
369
|
+
title: string;
|
|
370
|
+
description?: string;
|
|
371
|
+
auth?: boolean;
|
|
372
|
+
roles?: string[];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// 2. Route metadata configuration
|
|
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. Generate metadata
|
|
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
|
+
## Summary
|
|
406
|
+
|
|
407
|
+
The project's router system follows these principles:
|
|
408
|
+
|
|
409
|
+
1. **Layered Architecture**:
|
|
410
|
+
- Page router layer
|
|
411
|
+
- Service layer
|
|
412
|
+
- Middleware layer
|
|
413
|
+
|
|
414
|
+
2. **Type Safety**:
|
|
415
|
+
- Route constants
|
|
416
|
+
- Type definitions
|
|
417
|
+
- Compile-time checking
|
|
418
|
+
|
|
419
|
+
3. **Permission Control**:
|
|
420
|
+
- Route guards
|
|
421
|
+
- Role permissions
|
|
422
|
+
- Middleware interception
|
|
423
|
+
|
|
424
|
+
4. **Best Practices**:
|
|
425
|
+
- Route organization
|
|
426
|
+
- Type safety
|
|
427
|
+
- Metadata management
|