create-web-kit 25.728.1502 → 25.728.2252

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.
@@ -27,7 +27,7 @@ export const FRAMEWORKS = [
27
27
  workingDir: "target",
28
28
  },
29
29
  {
30
- command: "pnpm add -D prettier @types/node",
30
+ command: "pnpm add -D prettier @types/node husky lint-staged prettier-plugin-tailwindcss @trivago/prettier-plugin-sort-imports eslint-config-prettier",
31
31
  description: "Installing development dependencies",
32
32
  workingDir: "target",
33
33
  },
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { execSync } from "node:child_process";
3
4
  import { copyTemplateFiles } from "../utils/template.js";
4
5
  const TEMPLATE_NAME = "nextjs-csr";
5
6
  const TEMPLATE_FILES = [
@@ -7,6 +8,11 @@ const TEMPLATE_FILES = [
7
8
  { source: "prettier.config.json", destination: ".prettierrc", isJson: true },
8
9
  { source: "eslint.config.mjs", destination: "eslint.config.mjs" },
9
10
  { source: "next.config.ts", destination: "next.config.ts" },
11
+ { source: ".env.test", destination: ".env.test" },
12
+ { source: ".env.development", destination: ".env.development" },
13
+ { source: ".env.production", destination: ".env.production" },
14
+ { source: ".husky/pre-commit", destination: ".husky/pre-commit" },
15
+ { source: ".husky/_/husky.sh", destination: ".husky/_/husky.sh" },
10
16
  // DevContainer
11
17
  {
12
18
  source: ".devcontainer/devcontainer.json",
@@ -41,8 +47,83 @@ const TEMPLATE_FILES = [
41
47
  export function createNextjsCSRFiles(root) {
42
48
  // Copy all template files
43
49
  copyTemplateFiles(TEMPLATE_NAME, TEMPLATE_FILES, root);
44
- // Copy IE compatibility page from assets
50
+ // Configure package.json with husky and lint-staged
51
+ copyConfigHuskyPackage(root);
52
+ // Copy IE compatibility page
45
53
  copyIECompatibilityPage(root);
54
+ // Initialize husky after all files are copied
55
+ initializeHusky(root);
56
+ }
57
+ function copyConfigHuskyPackage(root) {
58
+ const pkgPath = path.join(root, "package.json");
59
+ try {
60
+ const pkgContent = fs.readFileSync(pkgPath, "utf-8");
61
+ const pkg = JSON.parse(pkgContent);
62
+ // 添加 prepare 脚本
63
+ if (!pkg.scripts) {
64
+ pkg.scripts = {};
65
+ }
66
+ pkg.scripts.prepare = "husky";
67
+ // 添加 lint-staged 配置
68
+ pkg["lint-staged"] = {
69
+ "**/*.{js,jsx,ts,tsx,json,css,scss,md}": ["prettier --write"],
70
+ "**/*.{js,jsx,ts,tsx}": ["eslint --fix"],
71
+ };
72
+ // 写回文件
73
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
74
+ console.log("✅ Updated package.json with husky and lint-staged configuration");
75
+ }
76
+ catch (error) {
77
+ console.error("❌ Failed to update package.json:", error);
78
+ }
79
+ }
80
+ function initializeHusky(root) {
81
+ try {
82
+ console.log("🔧 Initializing Git repository...");
83
+ try {
84
+ execSync("git init", {
85
+ cwd: root,
86
+ stdio: "inherit",
87
+ });
88
+ console.log("✅ Git repository initialized");
89
+ }
90
+ catch (gitError) {
91
+ console.warn("⚠️ Git init failed or already initialized:", gitError);
92
+ }
93
+ execSync("npx husky install", {
94
+ cwd: root,
95
+ stdio: "inherit",
96
+ });
97
+ // 确保 .husky 目录存在
98
+ const huskyDir = path.join(root, ".husky");
99
+ if (!fs.existsSync(huskyDir)) {
100
+ fs.mkdirSync(huskyDir, { recursive: true });
101
+ }
102
+ // 确保 .husky/_/ 目录存在
103
+ const huskyUnderscoreDir = path.join(huskyDir, "_");
104
+ if (!fs.existsSync(huskyUnderscoreDir)) {
105
+ fs.mkdirSync(huskyUnderscoreDir, { recursive: true });
106
+ }
107
+ // 创建 pre-commit 钩子文件
108
+ const preCommitPath = path.join(huskyDir, "pre-commit");
109
+ const preCommitContent = `#!/usr/bin/env sh
110
+ . "$(dirname -- "$0")/_/husky.sh"
111
+
112
+ npx lint-staged
113
+ `;
114
+ fs.writeFileSync(preCommitPath, preCommitContent);
115
+ // 设置执行权限
116
+ try {
117
+ fs.chmodSync(preCommitPath, 0o755);
118
+ }
119
+ catch (chmodError) {
120
+ console.warn("⚠️ Could not set execute permission for pre-commit hook");
121
+ }
122
+ console.log("✅ Husky pre-commit hook created successfully");
123
+ }
124
+ catch (error) {
125
+ console.error("❌ Failed to initialize husky:", error);
126
+ }
46
127
  }
47
128
  function copyIECompatibilityPage(root) {
48
129
  const ieHtmlPath = path.join(path.dirname(new URL(import.meta.url).pathname), "../assets/html/ie.html");
@@ -51,21 +132,7 @@ function copyIECompatibilityPage(root) {
51
132
  ieHtmlContent = fs.readFileSync(ieHtmlPath, "utf-8");
52
133
  }
53
134
  catch (error) {
54
- // Fallback content if file doesn't exist
55
- ieHtmlContent = `<!DOCTYPE html>
56
- <html lang="zh-CN">
57
- <head>
58
- <meta charset="UTF-8">
59
- <meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1" />
60
- <meta name="renderer" content="webkit" />
61
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
62
- <title>浏览器兼容性提示</title>
63
- </head>
64
- <body>
65
- <h1>请升级您的浏览器</h1>
66
- <p>您正在使用过时的浏览器版本,请升级到现代浏览器以获得更好的体验。</p>
67
- </body>
68
- </html>`;
135
+ console.error(error);
69
136
  }
70
137
  // Ensure public directory exists
71
138
  const publicDir = path.join(root, "public");
package/dist/index.js CHANGED
File without changes
@@ -0,0 +1 @@
1
+ NEXT_PUBLIC_API_URL='/api'
@@ -0,0 +1,2 @@
1
+ NEXT_PUBLIC_API_URL=/api
2
+ NEXT_PUBLIC_BASE_URL=/
@@ -0,0 +1,2 @@
1
+ NEXT_PUBLIC_API_URL=/api
2
+ NEXT_PUBLIC_BASE_URL=/
@@ -0,0 +1,2 @@
1
+ NEXT_PUBLIC_API_URL=/api
2
+ NEXT_PUBLIC_BASE_URL=/
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env sh
2
+ if [ -z "$husky_skip_init" ]; then
3
+ debug () {
4
+ if [ "$HUSKY_DEBUG" = "1" ]; then
5
+ echo "husky (debug) - $1"
6
+ fi
7
+ }
8
+
9
+ readonly hook_name="$(basename -- "$0")"
10
+ debug "starting $hook_name..."
11
+
12
+ if [ "$HUSKY" = "0" ]; then
13
+ debug "HUSKY env variable is set to 0, skipping hook"
14
+ exit 0
15
+ fi
16
+
17
+ if [ -f ~/.huskyrc ]; then
18
+ debug "sourcing ~/.huskyrc"
19
+ . ~/.huskyrc
20
+ fi
21
+
22
+ readonly husky_skip_init=1
23
+ export husky_skip_init
24
+ sh -e "$0" "$@"
25
+ exitcode="$?"
26
+
27
+ if [ $exitcode != 0 ]; then
28
+ echo "husky - $hook_name hook exited with code $exitcode (error)"
29
+ fi
30
+
31
+ if [ $exitcode = 127 ]; then
32
+ echo "husky - command not found in PATH=$PATH"
33
+ fi
34
+
35
+ exit $exitcode
36
+ fi
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npx lint-staged
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+
5
+ import pkg from "../../package.json";
6
+
7
+ export default function BuildInfo() {
8
+ useEffect(() => {
9
+ const print = (key: string, value: string) =>
10
+ console.log(
11
+ `%c ${key} %c ${value} %c `,
12
+ "background:#20232a ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff",
13
+ "background:#61dafb ;padding: 1px; border-radius: 0 3px 3px 0; color: #20232a; font-weight: bold;",
14
+ "background:transparent"
15
+ );
16
+ print(pkg.name, pkg.version);
17
+ print("build time", `${process.env.NEXT_PUBLIC_BUILD_TIME}`);
18
+ }, []);
19
+ return null;
20
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "node:v22.9.0",
3
+ "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
4
+ "customizations": {
5
+ "vscode": {
6
+ "extensions": [
7
+ "bradlc.vscode-tailwindcss",
8
+ "esbenp.prettier-vscode",
9
+ "dbaeumer.vscode-eslint",
10
+ "ms-vscode.js-debug",
11
+ "yoavbls.pretty-ts-errors",
12
+ "github.vscode-github-actions"
13
+ ]
14
+ }
15
+ },
16
+ "remoteUser": "node",
17
+ "mounts": [
18
+ "source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly",
19
+ "source=/etc/localtime,target=/etc/localtime,type=bind",
20
+ "source=/etc/timezone,target=/etc/timezone,type=bind"
21
+ ],
22
+ "remoteEnv": {
23
+ "SSH_AUTH_SOCK": "/ssh-agent",
24
+ "TZ": "Asia/Shanghai"
25
+ },
26
+ "initializeCommand": "mkdir -p ${localEnv:HOME}/.ssh",
27
+ "runArgs": [
28
+ "--volume=/run/host-services/ssh-auth.sock:/ssh-agent",
29
+ "--network=host",
30
+ "--privileged"
31
+ ]
32
+ }
@@ -0,0 +1 @@
1
+ function a(){var j=['exports','8439805unyrZv','20CLzhGp','204MFuYtu','353171WqpWCm','5752240UZcguO','8QMoVtA','1441488epJfXP','141458YyeURG','4031432nplTnP','18gedOKn','6223648plOSgN'];a=function(){return j;};return a();}var i=b;function b(c,d){var e=a();return b=function(f,g){f=f-0x122;var h=e[f];return h;},b(c,d);}(function(c,d){var h=b,e=c();while(!![]){try{var f=parseInt(h(0x12b))/0x1*(parseInt(h(0x12d))/0x2)+-parseInt(h(0x12c))/0x3+-parseInt(h(0x124))/0x4+parseInt(h(0x12a))/0x5+parseInt(h(0x128))/0x6*(parseInt(h(0x129))/0x7)+-parseInt(h(0x122))/0x8*(-parseInt(h(0x123))/0x9)+-parseInt(h(0x127))/0xa*(parseInt(h(0x126))/0xb);if(f===d)break;else e['push'](e['shift']());}catch(g){e['push'](e['shift']());}}}(a,0xd410e),module[i(0x125)]={'extends':['next/core-web-vitals','prettier']});
@@ -0,0 +1,46 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const inter = Inter({ subsets: ["latin"] });
6
+
7
+ export const metadata: Metadata = {
8
+ title: "Create Next App",
9
+ description: "Generated by create next app",
10
+ };
11
+
12
+ export default function RootLayout({
13
+ children,
14
+ }: {
15
+ children: React.ReactNode;
16
+ }) {
17
+ return (
18
+ <html lang="en" suppressHydrationWarning>
19
+ <head>
20
+ <meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1" />
21
+ <meta name="renderer" content="webkit" />
22
+ <meta
23
+ name="viewport"
24
+ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
25
+ />
26
+ {/* eslint-disable-next-line @next/next/no-before-interactive-script-outside-document */}
27
+ <script
28
+ dangerouslySetInnerHTML={{
29
+ __html: `
30
+ (function() {
31
+ var isIE = /MSIE|Trident/.test(navigator.userAgent);
32
+ var isOldIE = /MSIE [1-9]\\.|MSIE 10\\./.test(navigator.userAgent);
33
+ if (isOldIE) {
34
+ window.location.href = '/ie.html';
35
+ }
36
+ })();
37
+ `,
38
+ }}
39
+ />
40
+ </head>
41
+ <body className={inter.className} suppressHydrationWarning>
42
+ {children}
43
+ </body>
44
+ </html>
45
+ );
46
+ }
@@ -0,0 +1 @@
1
+ const i=b;(function(c,d){const h=b,e=c();while(!![]){try{const f=parseInt(h(0x161))/0x1*(parseInt(h(0x168))/0x2)+-parseInt(h(0x166))/0x3*(parseInt(h(0x167))/0x4)+parseInt(h(0x15a))/0x5+parseInt(h(0x165))/0x6*(-parseInt(h(0x162))/0x7)+-parseInt(h(0x15d))/0x8*(parseInt(h(0x159))/0x9)+-parseInt(h(0x164))/0xa+parseInt(h(0x15c))/0xb*(parseInt(h(0x163))/0xc);if(f===d)break;else e['push'](e['shift']());}catch(g){e['push'](e['shift']());}}}(a,0x60e63));function a(){const j=['1912850ssemiU','env','11uzboFD','5787960fGMLTg','output','yyyy-MM-dd\x20HH:mm','NEXT_PUBLIC_BUILD_TIME','4528iaNFDg','7uZdizK','16573704ndScoW','5735480zRdDHq','1290126pMrYji','11703ZKzRWX','580BYSuUz','314Aiqhwg','9QzTloI'];a=function(){return j;};return a();}import{format}from'date-fns';const nextConfig={},proxy=async()=>{return[{'source':'/api/:path*','destination':'http://localhost:8000/api/:path*'}];};switch(process[i(0x15b)]['NODE_ENV']){case'production':nextConfig[i(0x15e)]='export',nextConfig['images']={},nextConfig['images']['unoptimized']=!![],nextConfig['distDir']='dist';break;case'development':nextConfig['rewrites']=proxy;break;}process[i(0x15b)][i(0x160)]=format(new Date(),i(0x15f));function b(c,d){const e=a();return b=function(f,g){f=f-0x159;let h=e[f];return h;},b(c,d);}export default nextConfig;
@@ -1,28 +1,28 @@
1
- import { format } from 'date-fns';
2
-
3
- const nextConfig = {};
1
+ import { format } from "date-fns";
2
+ import { NextConfig } from "next";
3
+ const nextConfig: NextConfig = {};
4
4
 
5
5
  const proxy = async () => {
6
- return [
7
- {
8
- source: '/api/:path*',
9
- destination: 'http://localhost:8000/api/:path*',
10
- },
11
- ];
6
+ return [
7
+ {
8
+ source: "/api/:path*",
9
+ destination: "http://localhost:8000/api/:path*",
10
+ },
11
+ ];
12
12
  };
13
13
 
14
14
  switch (process.env.NODE_ENV) {
15
- case 'production':
16
- nextConfig.output = 'export';
17
- nextConfig.images = {};
18
- nextConfig.images.unoptimized = true;
19
- nextConfig.distDir = 'dist';
20
- break;
21
- case 'development':
22
- nextConfig.rewrites = proxy;
23
- break;
15
+ case "production":
16
+ nextConfig.output = "export";
17
+ nextConfig.images = {};
18
+ nextConfig.images.unoptimized = true;
19
+ nextConfig.distDir = "dist";
20
+ break;
21
+ case "development":
22
+ nextConfig.rewrites = proxy;
23
+ break;
24
24
  }
25
25
 
26
- process.env.NEXT_PUBLIC_BUILD_TIME = format(new Date(), 'yyyy-MM-dd HH:mm');
26
+ process.env.NEXT_PUBLIC_BUILD_TIME = format(new Date(), "yyyy-MM-dd HH:mm");
27
27
 
28
28
  export default nextConfig;
@@ -0,0 +1,16 @@
1
+ import Link from "next/link";
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div className="flex min-h-screen flex-col items-center justify-center">
6
+ <h2 className="text-2xl font-bold">页面未找到</h2>
7
+ <p className="mt-4 text-gray-600">抱歉,您访问的页面不存在。</p>
8
+ <Link
9
+ href="/"
10
+ className="mt-6 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
11
+ >
12
+ 返回首页
13
+ </Link>
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
+ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
5
+ import { useState } from "react";
6
+
7
+ export function QueryProvider({ children }: { children: React.ReactNode }) {
8
+ const [queryClient] = useState(
9
+ () =>
10
+ new QueryClient({
11
+ defaultOptions: {
12
+ queries: {
13
+ // 数据缓存时间 (默认 5 分钟)
14
+ staleTime: 5 * 60 * 1000,
15
+ // 数据在内存中的缓存时间 (默认 5 分钟)
16
+ gcTime: 5 * 60 * 1000,
17
+ // 重试次数
18
+ retry: 3,
19
+ // 重试延迟
20
+ retryDelay: (attemptIndex) =>
21
+ Math.min(1000 * 2 ** attemptIndex, 30000),
22
+ // 窗口重新获得焦点时是否重新获取数据
23
+ refetchOnWindowFocus: false,
24
+ // 网络重新连接时是否重新获取数据
25
+ refetchOnReconnect: true,
26
+ },
27
+ mutations: {
28
+ // 重试次数
29
+ retry: 1,
30
+ // 重试延迟
31
+ retryDelay: 1000,
32
+ },
33
+ },
34
+ })
35
+ );
36
+
37
+ return (
38
+ <QueryClientProvider client={queryClient}>
39
+ {children}
40
+ {/* {process.env.NODE_ENV === 'development' && (
41
+ <ReactQueryDevtools initialIsOpen={false} />
42
+ )} */}
43
+ </QueryClientProvider>
44
+ );
45
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * HTTP 请求类封装(强类型,无 any)
3
+ * 配合 React Query 使用,简化超时和重试逻辑
4
+ */
5
+
6
+ import { toast } from "sonner";
7
+
8
+ interface RequestConfig {
9
+ baseURL?: string;
10
+ headers?: Record<string, string>;
11
+ }
12
+
13
+ interface RequestOptions extends RequestInit {
14
+ params?: Record<string, string | number | boolean>;
15
+ data?: unknown; // 用于传递请求体
16
+ }
17
+
18
+ interface ApiResponse<T> {
19
+ data: T;
20
+ status?: number;
21
+ ok?: boolean;
22
+ code?: number; // 后端业务状态码
23
+ msg?: string | null; // 后端消息
24
+ }
25
+
26
+ // 后端 API 响应结构
27
+ interface BackendResponse<T> {
28
+ code: number;
29
+ msg: string | null;
30
+ data: T;
31
+ }
32
+
33
+ export class HttpClient {
34
+ private baseURL: string;
35
+ private defaultHeaders: Record<string, string>;
36
+
37
+ constructor(config: RequestConfig = {}) {
38
+ this.baseURL = config.baseURL || "";
39
+ this.defaultHeaders = {
40
+ "Content-Type": "application/json",
41
+ ...config.headers,
42
+ };
43
+ }
44
+ updateToken(token: string) {
45
+ // this.defaultHeaders['Authorization'] = `Bearer ${token}`;
46
+ this.defaultHeaders["token"] = `${token}`;
47
+ }
48
+
49
+ private buildURL(url: string, params?: Record<string, unknown>): string {
50
+ let fullURL = url.startsWith("http") ? url : `${this.baseURL}${url}`;
51
+
52
+ if (params) {
53
+ const searchParams = new URLSearchParams();
54
+ Object.entries(params).forEach(([key, value]) => {
55
+ if (value !== null && value !== undefined) {
56
+ searchParams.append(key, String(value));
57
+ }
58
+ });
59
+ const paramString = searchParams.toString();
60
+ if (paramString) {
61
+ fullURL += `${fullURL.includes("?") ? "&" : "?"}${paramString}`;
62
+ }
63
+ }
64
+
65
+ return fullURL;
66
+ }
67
+
68
+ async request<T>(
69
+ url: string,
70
+ options: RequestOptions = {}
71
+ ): Promise<ApiResponse<T>> {
72
+ const { params, data, ...fetchOptions } = options;
73
+
74
+ const fullURL = this.buildURL(url, params);
75
+
76
+ const headers: HeadersInit = {
77
+ ...this.defaultHeaders,
78
+ ...(fetchOptions.headers || {}),
79
+ };
80
+
81
+ let body = fetchOptions.body;
82
+
83
+ // 如果提供了 data,优先使用
84
+ if (data !== undefined) {
85
+ if (data instanceof FormData) {
86
+ body = data;
87
+ Reflect.deleteProperty(headers, "Content-Type"); // FormData 不需要手动设置 Content-Type
88
+ } else {
89
+ body = JSON.stringify(data);
90
+ }
91
+ }
92
+ try {
93
+ const response = await fetch(fullURL, {
94
+ ...fetchOptions,
95
+ headers,
96
+ body,
97
+ });
98
+ const contentType = response.headers.get("content-type");
99
+ let responseData: T;
100
+ if (contentType?.includes("application/json")) {
101
+ const jsonResponse: BackendResponse<T> = await response.json();
102
+ if (jsonResponse.code == 401) {
103
+ // 清除本地存储
104
+ localStorage.clear();
105
+
106
+ // 退出用户状态
107
+ if (typeof window !== "undefined") {
108
+ // 动态导入store避免循环依赖
109
+ import("@/store")
110
+ .then(({ useStore }) => {
111
+ const { logout, openLoginDialog } = useStore.getState();
112
+ logout();
113
+ openLoginDialog();
114
+ })
115
+ .catch(console.error);
116
+ }
117
+
118
+ toast.error("登录信息已过期,请重新登录");
119
+ }
120
+ return jsonResponse;
121
+ } else if (contentType?.startsWith("text/")) {
122
+ responseData = (await response.text()) as T;
123
+ } else {
124
+ responseData = (await response.blob()) as T;
125
+ }
126
+
127
+ return {
128
+ data: responseData,
129
+ status: response.status,
130
+ ok: response.ok,
131
+ };
132
+ } catch (error: unknown) {
133
+ if (error instanceof Error) {
134
+ throw new Error(error.message);
135
+ }
136
+ throw new Error("网络请求失败");
137
+ }
138
+ }
139
+
140
+ // GET 请求
141
+ get<T>(
142
+ url: string,
143
+ params?: Record<string, string | number | boolean>,
144
+ options?: Omit<RequestOptions, "params">
145
+ ) {
146
+ return this.request<T>(url, { ...options, method: "GET", params });
147
+ }
148
+
149
+ // POST 请求
150
+ post<T = unknown, B = unknown>(
151
+ url: string,
152
+ data?: B,
153
+ options?: Omit<RequestOptions, "data">
154
+ ) {
155
+ return this.request<T>(url, { ...options, method: "POST", data });
156
+ }
157
+
158
+ // PUT 请求
159
+ put<T = unknown, B = unknown>(
160
+ url: string,
161
+ data?: B,
162
+ options?: Omit<RequestOptions, "data">
163
+ ) {
164
+ return this.request<T>(url, { ...options, method: "PUT", data });
165
+ }
166
+
167
+ // PATCH 请求
168
+ patch<T = unknown, B = unknown>(
169
+ url: string,
170
+ data?: B,
171
+ options?: Omit<RequestOptions, "data">
172
+ ) {
173
+ return this.request<T>(url, { ...options, method: "PATCH", data });
174
+ }
175
+
176
+ // DELETE 请求
177
+ delete<T = unknown>(url: string, options?: RequestOptions) {
178
+ return this.request<T>(url, { ...options, method: "DELETE" });
179
+ }
180
+
181
+ // 上传文件
182
+ upload<T = unknown>(
183
+ url: string,
184
+ formData: FormData,
185
+ options?: Omit<RequestOptions, "data" | "body">
186
+ ) {
187
+ return this.request<T>(url, { ...options, method: "POST", data: formData });
188
+ }
189
+ }
190
+
191
+ // 创建默认实例
192
+ export const http = new HttpClient({
193
+ baseURL: `${process.env.NEXT_PUBLIC_API_URL || "/api"}`,
194
+ });
195
+
196
+ // 快捷导出函数
197
+ export const get = http.get.bind(http);
198
+ export const post = http.post.bind(http);
199
+ export const put = http.put.bind(http);
200
+ export const patch = http.patch.bind(http);
201
+ export const del = http.delete.bind(http);
202
+ export const upload = http.upload.bind(http);
203
+
204
+ export default http;
@@ -0,0 +1,12 @@
1
+ interface ShowProps {
2
+ when: boolean;
3
+ fallback?: React.ReactNode;
4
+ }
5
+
6
+ export default function Show({
7
+ children,
8
+ when,
9
+ fallback = null,
10
+ }: React.PropsWithChildren<ShowProps>) {
11
+ return when ? children : fallback;
12
+ }
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
4
+ import * as React from "react";
5
+
6
+ export function ThemeProvider({ children }: React.PropsWithChildren) {
7
+ return (
8
+ <NextThemesProvider
9
+ attribute="class"
10
+ defaultTheme="system"
11
+ enableSystem
12
+ disableTransitionOnChange
13
+ >
14
+ {children}
15
+ </NextThemesProvider>
16
+ );
17
+ }
@@ -0,0 +1,3 @@
1
+ NEXTAUTH_URL=http://localhost:3000
2
+ NEXTAUTH_SECRET=your-secret-key
3
+ DATABASE_URL="your-database-url"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-web-kit",
3
- "version": "25.0728.1502",
3
+ "version": "25.0728.2252",
4
4
  "description": "A powerful scaffolding tool for creating modern frontend projects with Vue, Next.js, and Electron templates",
5
5
  "type": "module",
6
6
  "bin": {