@creativault/powerdata-cli 0.0.1

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.
Files changed (69) hide show
  1. package/dist/index.js +240 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +55 -0
  4. package/template/nextjs_template/.dockerignore +12 -0
  5. package/template/nextjs_template/.editorconfig +15 -0
  6. package/template/nextjs_template/.env.example +43 -0
  7. package/template/nextjs_template/Dockerfile +57 -0
  8. package/template/nextjs_template/README.md +83 -0
  9. package/template/nextjs_template/_gitignore +55 -0
  10. package/template/nextjs_template/biome.json +58 -0
  11. package/template/nextjs_template/components.json +21 -0
  12. package/template/nextjs_template/eslint.config.mjs +16 -0
  13. package/template/nextjs_template/next.config.ts +31 -0
  14. package/template/nextjs_template/package.json +57 -0
  15. package/template/nextjs_template/postcss.config.mjs +8 -0
  16. package/template/nextjs_template/public/.gitkeep +0 -0
  17. package/template/nextjs_template/src/app/demo/page.tsx +258 -0
  18. package/template/nextjs_template/src/app/layout.tsx +46 -0
  19. package/template/nextjs_template/src/app/page.tsx +101 -0
  20. package/template/nextjs_template/src/components/layout/footer.tsx +11 -0
  21. package/template/nextjs_template/src/components/layout/header.tsx +25 -0
  22. package/template/nextjs_template/src/components/theme-provider.tsx +17 -0
  23. package/template/nextjs_template/src/components/theme-toggle.tsx +21 -0
  24. package/template/nextjs_template/src/components/ui/badge.tsx +35 -0
  25. package/template/nextjs_template/src/components/ui/button.tsx +56 -0
  26. package/template/nextjs_template/src/components/ui/card.tsx +82 -0
  27. package/template/nextjs_template/src/config/index.ts +1 -0
  28. package/template/nextjs_template/src/config/website.ts +9 -0
  29. package/template/nextjs_template/src/env.js +22 -0
  30. package/template/nextjs_template/src/hooks/index.ts +1 -0
  31. package/template/nextjs_template/src/lib/constants.ts +15 -0
  32. package/template/nextjs_template/src/lib/urls.ts +22 -0
  33. package/template/nextjs_template/src/lib/utils.ts +6 -0
  34. package/template/nextjs_template/src/middleware.ts +53 -0
  35. package/template/nextjs_template/src/routes.ts +47 -0
  36. package/template/nextjs_template/src/styles/globals.css +157 -0
  37. package/template/nextjs_template/src/test/setup.ts +1 -0
  38. package/template/nextjs_template/src/types/index.ts +25 -0
  39. package/template/nextjs_template/tsconfig.json +34 -0
  40. package/template/nextjs_template/vitest.config.mts +34 -0
  41. package/template/nextjs_template_monorepo/.dockerignore +11 -0
  42. package/template/nextjs_template_monorepo/.env.example +43 -0
  43. package/template/nextjs_template_monorepo/Dockerfile +65 -0
  44. package/template/nextjs_template_monorepo/README.md +55 -0
  45. package/template/nextjs_template_monorepo/components.json +21 -0
  46. package/template/nextjs_template_monorepo/eslint.config.mjs +16 -0
  47. package/template/nextjs_template_monorepo/next.config.ts +38 -0
  48. package/template/nextjs_template_monorepo/package.json +44 -0
  49. package/template/nextjs_template_monorepo/postcss.config.mjs +8 -0
  50. package/template/nextjs_template_monorepo/public/.gitkeep +0 -0
  51. package/template/nextjs_template_monorepo/src/app/demo/page.tsx +255 -0
  52. package/template/nextjs_template_monorepo/src/app/layout.tsx +46 -0
  53. package/template/nextjs_template_monorepo/src/app/page.tsx +102 -0
  54. package/template/nextjs_template_monorepo/src/components/layout/footer.tsx +11 -0
  55. package/template/nextjs_template_monorepo/src/components/layout/header.tsx +25 -0
  56. package/template/nextjs_template_monorepo/src/components/theme-provider.tsx +17 -0
  57. package/template/nextjs_template_monorepo/src/components/theme-toggle.tsx +21 -0
  58. package/template/nextjs_template_monorepo/src/config/index.ts +1 -0
  59. package/template/nextjs_template_monorepo/src/config/website.ts +13 -0
  60. package/template/nextjs_template_monorepo/src/env.js +22 -0
  61. package/template/nextjs_template_monorepo/src/hooks/index.ts +6 -0
  62. package/template/nextjs_template_monorepo/src/lib/utils.ts +2 -0
  63. package/template/nextjs_template_monorepo/src/middleware.ts +53 -0
  64. package/template/nextjs_template_monorepo/src/routes.ts +47 -0
  65. package/template/nextjs_template_monorepo/src/styles/globals.css +157 -0
  66. package/template/nextjs_template_monorepo/src/test/setup.ts +1 -0
  67. package/template/nextjs_template_monorepo/src/types/index.ts +30 -0
  68. package/template/nextjs_template_monorepo/tsconfig.json +34 -0
  69. package/template/nextjs_template_monorepo/vitest.config.mts +34 -0
@@ -0,0 +1,56 @@
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+ import { cn } from '@/lib/utils';
5
+
6
+ const buttonVariants = cva(
7
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
13
+ destructive:
14
+ 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
15
+ outline:
16
+ 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
17
+ secondary:
18
+ 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
19
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
20
+ link: 'text-primary underline-offset-4 hover:underline',
21
+ },
22
+ size: {
23
+ default: 'h-9 px-4 py-2',
24
+ sm: 'h-8 rounded-md px-3 text-xs',
25
+ lg: 'h-10 rounded-md px-8',
26
+ icon: 'h-9 w-9',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'default',
31
+ size: 'default',
32
+ },
33
+ }
34
+ );
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean;
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : 'button';
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+ );
54
+ Button.displayName = 'Button';
55
+
56
+ export { Button, buttonVariants };
@@ -0,0 +1,82 @@
1
+ import * as React from 'react';
2
+ import { cn } from '@/lib/utils';
3
+
4
+ const Card = React.forwardRef<
5
+ HTMLDivElement,
6
+ React.HTMLAttributes<HTMLDivElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div
9
+ ref={ref}
10
+ className={cn(
11
+ 'rounded-xl border bg-card text-card-foreground shadow',
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ ));
17
+ Card.displayName = 'Card';
18
+
19
+ const CardHeader = React.forwardRef<
20
+ HTMLDivElement,
21
+ React.HTMLAttributes<HTMLDivElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ className={cn('flex flex-col space-y-1.5 p-6', className)}
26
+ {...props}
27
+ />
28
+ ));
29
+ CardHeader.displayName = 'CardHeader';
30
+
31
+ const CardTitle = React.forwardRef<
32
+ HTMLDivElement,
33
+ React.HTMLAttributes<HTMLDivElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <div
36
+ ref={ref}
37
+ className={cn('font-semibold leading-none tracking-tight', className)}
38
+ {...props}
39
+ />
40
+ ));
41
+ CardTitle.displayName = 'CardTitle';
42
+
43
+ const CardDescription = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement>
46
+ >(({ className, ...props }, ref) => (
47
+ <div
48
+ ref={ref}
49
+ className={cn('text-sm text-muted-foreground', className)}
50
+ {...props}
51
+ />
52
+ ));
53
+ CardDescription.displayName = 'CardDescription';
54
+
55
+ const CardContent = React.forwardRef<
56
+ HTMLDivElement,
57
+ React.HTMLAttributes<HTMLDivElement>
58
+ >(({ className, ...props }, ref) => (
59
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
60
+ ));
61
+ CardContent.displayName = 'CardContent';
62
+
63
+ const CardFooter = React.forwardRef<
64
+ HTMLDivElement,
65
+ React.HTMLAttributes<HTMLDivElement>
66
+ >(({ className, ...props }, ref) => (
67
+ <div
68
+ ref={ref}
69
+ className={cn('flex items-center p-6 pt-0', className)}
70
+ {...props}
71
+ />
72
+ ));
73
+ CardFooter.displayName = 'CardFooter';
74
+
75
+ export {
76
+ Card,
77
+ CardHeader,
78
+ CardFooter,
79
+ CardTitle,
80
+ CardDescription,
81
+ CardContent,
82
+ };
@@ -0,0 +1 @@
1
+ export { websiteConfig } from './website';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 网站基础配置
3
+ */
4
+ export const websiteConfig = {
5
+ name: 'MyApp',
6
+ description: 'A modern Next.js application',
7
+ url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
8
+ locale: 'en',
9
+ } as const;
@@ -0,0 +1,22 @@
1
+ import { createEnv } from '@t3-oss/env-nextjs';
2
+ import { z } from 'zod';
3
+
4
+ export const env = createEnv({
5
+ server: {
6
+ NODE_ENV: z.enum(['development', 'test', 'production']),
7
+ DATABASE_URL: z.string().optional(),
8
+ // 后端服务 URL
9
+ API_URL: z.string().optional(),
10
+ },
11
+ client: {
12
+ NEXT_PUBLIC_APP_URL: z.string().optional(),
13
+ },
14
+ runtimeEnv: {
15
+ NODE_ENV: process.env.NODE_ENV,
16
+ DATABASE_URL: process.env.DATABASE_URL,
17
+ API_URL: process.env.API_URL,
18
+ NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
19
+ },
20
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
21
+ emptyStringAsUndefined: true,
22
+ });
@@ -0,0 +1 @@
1
+ // 自定义 hooks 导出
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 应用常量
3
+ */
4
+
5
+ // 分页默认值
6
+ export const DEFAULT_PAGE_SIZE = 20;
7
+ export const MAX_PAGE_SIZE = 100;
8
+
9
+ // 缓存时间 (秒)
10
+ export const CACHE_TTL = {
11
+ SHORT: 60, // 1 分钟
12
+ MEDIUM: 300, // 5 分钟
13
+ LONG: 3600, // 1 小时
14
+ DAY: 86400, // 1 天
15
+ } as const;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * URL 工具函数
3
+ */
4
+
5
+ /**
6
+ * 获取应用基础 URL
7
+ * 服务端使用环境变量,客户端使用 window.location.origin
8
+ */
9
+ export function getBaseUrl(): string {
10
+ if (typeof window !== 'undefined') {
11
+ return window.location.origin;
12
+ }
13
+ return process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
14
+ }
15
+
16
+ /**
17
+ * 构建完整 URL
18
+ */
19
+ export function buildUrl(path: string): string {
20
+ const base = getBaseUrl();
21
+ return `${base}${path}`;
22
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,53 @@
1
+ import { type NextRequest, NextResponse } from 'next/server';
2
+ import {
3
+ DEFAULT_LOGIN_REDIRECT,
4
+ protectedRoutePrefixes,
5
+ protectedRoutes,
6
+ routesNotAllowedByLoggedInUsers,
7
+ } from './routes';
8
+
9
+ /**
10
+ * Next.js Middleware
11
+ * https://nextjs.org/docs/app/building-your-application/routing/middleware
12
+ *
13
+ * 处理路由保护、认证重定向等逻辑
14
+ * 注意:middleware 中避免进行 API 或数据库调用,仅检查 cookie 存在性
15
+ */
16
+ export default async function middleware(req: NextRequest) {
17
+ const { nextUrl } = req;
18
+ const pathname = nextUrl.pathname;
19
+
20
+ // TODO: 替换为实际的 session 检查逻辑
21
+ // 示例:检查 session cookie 是否存在
22
+ const sessionCookie = req.cookies.get('session_token');
23
+ const isLoggedIn = !!sessionCookie;
24
+
25
+ // 检查是否为受保护路由
26
+ const isProtectedRoute =
27
+ protectedRoutes.some((route) => pathname === route) ||
28
+ protectedRoutePrefixes.some((prefix) => pathname.startsWith(prefix));
29
+
30
+ // 未登录用户访问受保护路由 → 重定向到登录页
31
+ if (isProtectedRoute && !isLoggedIn) {
32
+ const loginUrl = new URL('/auth/login', nextUrl);
33
+ loginUrl.searchParams.set('callbackUrl', pathname);
34
+ return NextResponse.redirect(loginUrl);
35
+ }
36
+
37
+ // 已登录用户访问登录/注册页 → 重定向到 Dashboard
38
+ const isAuthRoute = routesNotAllowedByLoggedInUsers.some(
39
+ (route) => pathname === route
40
+ );
41
+ if (isAuthRoute && isLoggedIn) {
42
+ return NextResponse.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl));
43
+ }
44
+
45
+ return NextResponse.next();
46
+ }
47
+
48
+ export const config = {
49
+ matcher: [
50
+ // 匹配所有路由,排除静态资源和 API
51
+ '/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
52
+ ],
53
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 应用路由配置
3
+ */
4
+ export enum Routes {
5
+ Root = '/',
6
+
7
+ // 营销页面
8
+ Features = '/features',
9
+ Pricing = '/pricing',
10
+ Blog = '/blog',
11
+ About = '/about',
12
+ Contact = '/contact',
13
+ PrivacyPolicy = '/privacy',
14
+ TermsOfService = '/terms',
15
+
16
+ // 认证路由
17
+ Login = '/auth/login',
18
+ Register = '/auth/register',
19
+ ForgotPassword = '/auth/forgot-password',
20
+
21
+ // 业务路由
22
+ Dashboard = '/dashboard',
23
+ Settings = '/settings',
24
+ }
25
+
26
+ /**
27
+ * 需要登录才能访问的路由
28
+ */
29
+ export const protectedRoutes: string[] = [Routes.Dashboard, Routes.Settings];
30
+
31
+ /**
32
+ * 需要登录才能访问的路由前缀
33
+ */
34
+ export const protectedRoutePrefixes: string[] = ['/dashboard', '/settings'];
35
+
36
+ /**
37
+ * 登录后默认跳转路由
38
+ */
39
+ export const DEFAULT_LOGIN_REDIRECT = Routes.Dashboard;
40
+
41
+ /**
42
+ * 已登录用户不允许访问的路由
43
+ */
44
+ export const routesNotAllowedByLoggedInUsers: string[] = [
45
+ Routes.Login,
46
+ Routes.Register,
47
+ ];
@@ -0,0 +1,157 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @plugin '@tailwindcss/typography';
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --color-card: var(--card);
11
+ --color-card-foreground: var(--card-foreground);
12
+ --color-popover: var(--popover);
13
+ --color-popover-foreground: var(--popover-foreground);
14
+ --color-primary: var(--primary);
15
+ --color-primary-foreground: var(--primary-foreground);
16
+ --color-secondary: var(--secondary);
17
+ --color-secondary-foreground: var(--secondary-foreground);
18
+ --color-muted: var(--muted);
19
+ --color-muted-foreground: var(--muted-foreground);
20
+ --color-accent: var(--accent);
21
+ --color-accent-foreground: var(--accent-foreground);
22
+ --color-destructive: var(--destructive);
23
+ --color-destructive-foreground: var(--destructive-foreground);
24
+ --color-border: var(--border);
25
+ --color-input: var(--input);
26
+ --color-ring: var(--ring);
27
+ --color-chart-1: var(--chart-1);
28
+ --color-chart-2: var(--chart-2);
29
+ --color-chart-3: var(--chart-3);
30
+ --color-chart-4: var(--chart-4);
31
+ --color-chart-5: var(--chart-5);
32
+ --color-sidebar: var(--sidebar);
33
+ --color-sidebar-foreground: var(--sidebar-foreground);
34
+ --color-sidebar-primary: var(--sidebar-primary);
35
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
36
+ --color-sidebar-accent: var(--sidebar-accent);
37
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
38
+ --color-sidebar-border: var(--sidebar-border);
39
+ --color-sidebar-ring: var(--sidebar-ring);
40
+
41
+ --radius-sm: calc(var(--radius) - 4px);
42
+ --radius-md: calc(var(--radius) - 2px);
43
+ --radius-lg: var(--radius);
44
+ --radius-xl: calc(var(--radius) + 4px);
45
+
46
+ --animate-accordion-down: accordion-down 0.2s ease-out;
47
+ --animate-accordion-up: accordion-up 0.2s ease-out;
48
+
49
+ @keyframes accordion-down {
50
+ from {
51
+ height: 0;
52
+ }
53
+ to {
54
+ height: var(--radix-accordion-content-height);
55
+ }
56
+ }
57
+
58
+ @keyframes accordion-up {
59
+ from {
60
+ height: var(--radix-accordion-content-height);
61
+ }
62
+ to {
63
+ height: 0;
64
+ }
65
+ }
66
+ }
67
+
68
+ :root {
69
+ --background: oklch(1.0 0 0);
70
+ --foreground: oklch(0.1448 0 0);
71
+ --card: oklch(1.0 0 0);
72
+ --card-foreground: oklch(0.1448 0 0);
73
+ --popover: oklch(1.0 0 0);
74
+ --popover-foreground: oklch(0.1448 0 0);
75
+ --primary: oklch(0.623 0.214 259.815);
76
+ --primary-foreground: oklch(0.9851 0 0);
77
+ --secondary: oklch(0.9702 0 0);
78
+ --secondary-foreground: oklch(0.2046 0 0);
79
+ --muted: oklch(0.9702 0 0);
80
+ --muted-foreground: oklch(0.5555 0 0);
81
+ --accent: oklch(0.9702 0 0);
82
+ --accent-foreground: oklch(0.2046 0 0);
83
+ --destructive: oklch(0.6368 0.2078 25.3313);
84
+ --destructive-foreground: oklch(0.9851 0 0);
85
+ --border: oklch(0.9219 0 0);
86
+ --input: oklch(0.9219 0 0);
87
+ --ring: oklch(0.1448 0 0);
88
+ --chart-1: oklch(0.6231 0.188 259.8145);
89
+ --chart-2: oklch(0.6056 0.2189 292.7172);
90
+ --chart-3: oklch(0.7686 0.1647 70.0804);
91
+ --chart-4: oklch(0.6959 0.1491 162.4796);
92
+ --chart-5: oklch(0.551 0.0234 264.3637);
93
+ --sidebar: oklch(1.0 0 0);
94
+ --sidebar-foreground: oklch(0.446 0.03 256.802);
95
+ --sidebar-primary: oklch(0.325 0 0);
96
+ --sidebar-primary-foreground: oklch(0.9881 0 0);
97
+ --sidebar-accent: oklch(0.9761 0 0);
98
+ --sidebar-accent-foreground: oklch(0.325 0 0);
99
+ --sidebar-border: oklch(0.9401 0 0);
100
+ --sidebar-ring: oklch(0.7731 0 0);
101
+ --radius: 0.5rem;
102
+ --spacing: 0.25rem;
103
+ }
104
+
105
+ .dark {
106
+ --background: oklch(0.1448 0 0);
107
+ --foreground: oklch(0.9851 0 0);
108
+ --card: oklch(0.1448 0 0);
109
+ --card-foreground: oklch(0.9851 0 0);
110
+ --popover: oklch(0.1448 0 0);
111
+ --popover-foreground: oklch(0.9851 0 0);
112
+ --primary: oklch(0.9851 0 0);
113
+ --primary-foreground: oklch(0.2046 0 0);
114
+ --secondary: oklch(0.2686 0 0);
115
+ --secondary-foreground: oklch(0.9851 0 0);
116
+ --muted: oklch(0.2686 0 0);
117
+ --muted-foreground: oklch(0.7155 0 0);
118
+ --accent: oklch(0.2686 0 0);
119
+ --accent-foreground: oklch(0.9851 0 0);
120
+ --destructive: oklch(0.3958 0.1331 25.723);
121
+ --destructive-foreground: oklch(0.9851 0 0);
122
+ --border: oklch(0.2686 0 0);
123
+ --input: oklch(0.2686 0 0);
124
+ --ring: oklch(0.8699 0 0);
125
+ --chart-1: oklch(0.5298 0.1932 262.0493);
126
+ --chart-2: oklch(0.6994 0.1339 165.4606);
127
+ --chart-3: oklch(0.7227 0.1502 60.5799);
128
+ --chart-4: oklch(0.6193 0.2029 312.7422);
129
+ --chart-5: oklch(0.6118 0.2093 6.1387);
130
+ --sidebar: oklch(0.2103 0.0059 285.8852);
131
+ --sidebar-foreground: oklch(0.9674 0.0013 286.3752);
132
+ --sidebar-primary: oklch(0.4882 0.2172 264.3763);
133
+ --sidebar-primary-foreground: oklch(1.0 0 0);
134
+ --sidebar-accent: oklch(0.2739 0.0055 286.0326);
135
+ --sidebar-accent-foreground: oklch(0.9674 0.0013 286.3752);
136
+ --sidebar-border: oklch(0.2739 0.0055 286.0326);
137
+ --sidebar-ring: oklch(0.8711 0.0055 286.286);
138
+ }
139
+
140
+ @layer base {
141
+ *,
142
+ ::after,
143
+ ::before,
144
+ ::backdrop,
145
+ ::file-selector-button {
146
+ border-color: var(--color-gray-200, currentColor);
147
+ }
148
+
149
+ * {
150
+ @apply border-border outline-ring/50;
151
+ }
152
+
153
+ body {
154
+ @apply bg-background text-foreground overscroll-none;
155
+ font-feature-settings: "rlig" 1, "calt" 1;
156
+ }
157
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/vitest';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 全局类型定义
3
+ */
4
+
5
+ // API 响应通用类型
6
+ export interface ApiResponse<T = unknown> {
7
+ code: number;
8
+ message: string;
9
+ data: T;
10
+ }
11
+
12
+ // 分页参数
13
+ export interface PaginationParams {
14
+ page: number;
15
+ pageSize: number;
16
+ }
17
+
18
+ // 分页响应
19
+ export interface PaginatedResponse<T> {
20
+ items: T[];
21
+ total: number;
22
+ page: number;
23
+ pageSize: number;
24
+ totalPages: number;
25
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"],
23
+ "@/public/*": ["./public/*"]
24
+ }
25
+ },
26
+ "include": [
27
+ "next-env.d.ts",
28
+ "**/*.ts",
29
+ "**/*.tsx",
30
+ ".next/types/**/*.ts",
31
+ ".next/dev/types/**/*.ts"
32
+ ],
33
+ "exclude": ["node_modules", "vitest.config.mts"]
34
+ }
@@ -0,0 +1,34 @@
1
+ import path from 'path';
2
+ import react from '@vitejs/plugin-react';
3
+ import { defineConfig } from 'vitest/config';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ test: {
8
+ environment: 'happy-dom',
9
+ globals: true,
10
+ setupFiles: ['./src/test/setup.ts'],
11
+ include: ['src/**/*.{test,spec}.{ts,tsx}'],
12
+ exclude: ['node_modules', '.next', 'dist'],
13
+ coverage: {
14
+ provider: 'v8',
15
+ reporter: ['text', 'json', 'html'],
16
+ reportsDirectory: './coverage',
17
+ exclude: [
18
+ 'node_modules/',
19
+ 'src/test/',
20
+ '**/*.d.ts',
21
+ '**/*.config.*',
22
+ '**/types/**',
23
+ ],
24
+ },
25
+ passWithNoTests: true,
26
+ testTimeout: 10000,
27
+ hookTimeout: 10000,
28
+ },
29
+ resolve: {
30
+ alias: {
31
+ '@': path.resolve(__dirname, './src'),
32
+ },
33
+ },
34
+ });
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ .next
3
+ .git
4
+ .gitignore
5
+ *.md
6
+ .env*.local
7
+ .vscode
8
+ .cursor
9
+ .idea
10
+ coverage
11
+ .turbo
@@ -0,0 +1,43 @@
1
+ # =============================================================================
2
+ # 环境变量配置
3
+ # =============================================================================
4
+
5
+ NODE_ENV=development
6
+
7
+ # -----------------------------------------------------------------------------
8
+ # 应用配置
9
+ # -----------------------------------------------------------------------------
10
+ NEXT_PUBLIC_APP_URL="http://localhost:3000"
11
+ PORT=3000
12
+
13
+ # 跳过环境变量验证 (Docker 构建时使用)
14
+ # SKIP_ENV_VALIDATION=true
15
+
16
+ # -----------------------------------------------------------------------------
17
+ # 数据库配置
18
+ # -----------------------------------------------------------------------------
19
+ # DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
20
+
21
+ # -----------------------------------------------------------------------------
22
+ # 后端 API 配置
23
+ # -----------------------------------------------------------------------------
24
+ # API_URL="http://localhost:8080"
25
+
26
+ # -----------------------------------------------------------------------------
27
+ # 认证配置 (better-auth)
28
+ # -----------------------------------------------------------------------------
29
+ # BETTER_AUTH_SECRET="your-secret-key"
30
+ # BETTER_AUTH_URL="http://localhost:3000"
31
+
32
+ # -----------------------------------------------------------------------------
33
+ # Sentry 错误监控
34
+ # -----------------------------------------------------------------------------
35
+ # SENTRY_DSN=""
36
+ # SENTRY_AUTH_TOKEN=""
37
+
38
+ # -----------------------------------------------------------------------------
39
+ # Stripe 支付
40
+ # -----------------------------------------------------------------------------
41
+ # STRIPE_SECRET_KEY=""
42
+ # STRIPE_WEBHOOK_SECRET=""
43
+ # NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=""