create-web-kit 25.728.2313 → 25.828.1844
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/dist/generators/nextjs-csr.js +51 -1
- package/dist/index.js +0 -0
- package/dist/templates/nextjs-csr/src/app/layout.tsx +52 -2
- package/package.json +1 -1
- package/dist/templates/nextjs-csr/.env +0 -1
- package/dist/templates/nextjs-csr/build-info.tsx +0 -20
- package/dist/templates/nextjs-csr/devcontainer.json +0 -32
- package/dist/templates/nextjs-csr/eslint.config.js +0 -1
- package/dist/templates/nextjs-csr/layout.tsx +0 -46
- package/dist/templates/nextjs-csr/next.config.js +0 -1
- package/dist/templates/nextjs-csr/not-found.tsx +0 -16
- package/dist/templates/nextjs-csr/query-provider.tsx +0 -45
- package/dist/templates/nextjs-csr/request.ts +0 -204
- package/dist/templates/nextjs-csr/show.tsx +0 -12
- package/dist/templates/nextjs-csr/theme-provider.tsx +0 -17
- package/dist/templates/nextjs-ssr/.env.local +0 -3
|
@@ -69,9 +69,59 @@ function copyConfigHuskyPackage(root) {
|
|
|
69
69
|
"**/*.{js,jsx,ts,tsx,json,css,scss,md}": ["prettier --write"],
|
|
70
70
|
"**/*.{js,jsx,ts,tsx}": ["eslint --fix"],
|
|
71
71
|
};
|
|
72
|
+
// 添加默认 SEO 配置(用于首页 metadata)
|
|
73
|
+
// 仅当不存在时写入,避免覆盖用户自定义内容
|
|
74
|
+
if (!pkg.seo) {
|
|
75
|
+
pkg.seo = {
|
|
76
|
+
title: "OneFile - 聚合对象存储上传平台",
|
|
77
|
+
description: "OneFile 是一个聚合上传平台,支持 OSS、COS、Cloudflare R2、AWS S3、Oracle Object Storage 等多云存储,提供大文件分片上传、断点续传与跨云文件管理。",
|
|
78
|
+
keywords: [
|
|
79
|
+
"OneFile",
|
|
80
|
+
"文件上传",
|
|
81
|
+
"大文件分片上传",
|
|
82
|
+
"对象存储",
|
|
83
|
+
"OSS",
|
|
84
|
+
"COS",
|
|
85
|
+
"R2",
|
|
86
|
+
"S3",
|
|
87
|
+
"Oracle Object Storage",
|
|
88
|
+
"多云存储",
|
|
89
|
+
"聚合上传",
|
|
90
|
+
"云存储管理",
|
|
91
|
+
],
|
|
92
|
+
og: {
|
|
93
|
+
title: "OneFile - 聚合对象存储上传平台",
|
|
94
|
+
description: "支持 OSS、COS、Cloudflare R2、AWS S3、Oracle Object Storage 等多云存储。提供大文件分片上传、断点续传、跨云文件统一管理。",
|
|
95
|
+
image: "https://onefile.h06i.com/pwa-512x512.png",
|
|
96
|
+
url: "https://onefile.h06i.com",
|
|
97
|
+
type: "website",
|
|
98
|
+
},
|
|
99
|
+
twitter: {
|
|
100
|
+
card: "summary_large_image",
|
|
101
|
+
title: "OneFile - 聚合对象存储上传平台",
|
|
102
|
+
description: "多云对象存储聚合上传,支持 OSS、COS、R2、S3,大文件分片上传与断点续传。",
|
|
103
|
+
image: "https://onefile.h06i.com/pwa-512x512.png",
|
|
104
|
+
},
|
|
105
|
+
jsonLd: {
|
|
106
|
+
"@context": "https://schema.org",
|
|
107
|
+
"@type": "WebSite",
|
|
108
|
+
name: "OneFile",
|
|
109
|
+
url: "https://onefile.h06i.com",
|
|
110
|
+
description: "OneFile 是一个聚合上传平台,支持 OSS、COS、Cloudflare R2、AWS S3、Oracle Object Storage 等多云存储,提供大文件分片上传、断点续传与跨云文件管理。",
|
|
111
|
+
publisher: {
|
|
112
|
+
"@type": "Organization",
|
|
113
|
+
name: "OneFile",
|
|
114
|
+
logo: {
|
|
115
|
+
"@type": "ImageObject",
|
|
116
|
+
image: "https://onefile.h06i.com/pwa-512x512.png",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
72
122
|
// 写回文件
|
|
73
123
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
74
|
-
console.log("✅ Updated package.json with husky
|
|
124
|
+
console.log("✅ Updated package.json with husky, lint-staged and SEO configuration");
|
|
75
125
|
}
|
|
76
126
|
catch (error) {
|
|
77
127
|
console.error("❌ Failed to update package.json:", error);
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -6,8 +6,24 @@ import { Providers } from "@/components/providers";
|
|
|
6
6
|
const inter = Inter({ subsets: ["latin"] });
|
|
7
7
|
|
|
8
8
|
export const metadata: Metadata = {
|
|
9
|
-
title:
|
|
10
|
-
description:
|
|
9
|
+
title: pkg.seo.title,
|
|
10
|
+
description: pkg.seo.description,
|
|
11
|
+
keywords: pkg.seo.keywords,
|
|
12
|
+
openGraph: {
|
|
13
|
+
title: pkg.seo.og.title,
|
|
14
|
+
description: pkg.seo.og.description,
|
|
15
|
+
url: pkg.seo.og.url,
|
|
16
|
+
type: pkg.seo.og.type as "website",
|
|
17
|
+
images: pkg.seo.og.image,
|
|
18
|
+
},
|
|
19
|
+
twitter: {
|
|
20
|
+
card: pkg.seo.twitter.card as "summary_large_image",
|
|
21
|
+
title: pkg.seo.twitter.title,
|
|
22
|
+
description: pkg.seo.twitter.description,
|
|
23
|
+
images: pkg.seo.twitter.image,
|
|
24
|
+
},
|
|
25
|
+
// 建议与 og.url 同步,利于生成绝对 URL
|
|
26
|
+
metadataBase: new URL(pkg.seo.og.url),
|
|
11
27
|
};
|
|
12
28
|
|
|
13
29
|
export default function RootLayout({
|
|
@@ -24,6 +40,40 @@ export default function RootLayout({
|
|
|
24
40
|
name="viewport"
|
|
25
41
|
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
|
26
42
|
/>
|
|
43
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
44
|
+
<link
|
|
45
|
+
rel="icon"
|
|
46
|
+
href="/favicon-32x32.png"
|
|
47
|
+
type="image/png"
|
|
48
|
+
sizes="32x32"
|
|
49
|
+
/>
|
|
50
|
+
<link
|
|
51
|
+
rel="icon"
|
|
52
|
+
href="/favicon-16x16.png"
|
|
53
|
+
type="image/png"
|
|
54
|
+
sizes="16x16"
|
|
55
|
+
/>
|
|
56
|
+
<link rel="manifest" href="/site.webmanifest" />
|
|
57
|
+
<meta
|
|
58
|
+
name="theme-color"
|
|
59
|
+
content="#FFFFFF"
|
|
60
|
+
media="(prefers-color-scheme: light)"
|
|
61
|
+
/>
|
|
62
|
+
<meta
|
|
63
|
+
name="theme-color"
|
|
64
|
+
content="#000000"
|
|
65
|
+
media="(prefers-color-scheme: dark)"
|
|
66
|
+
/>
|
|
67
|
+
{pkg.seo.jsonLd && (
|
|
68
|
+
<Script
|
|
69
|
+
id="onefile-jsonld"
|
|
70
|
+
type="application/ld+json"
|
|
71
|
+
// 保持纯字符串注入,符合 Google / Next.js 推荐
|
|
72
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(pkg.seo.jsonLd) }}
|
|
73
|
+
strategy="beforeInteractive"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</head>
|
|
27
77
|
{/* eslint-disable-next-line @next/next/no-before-interactive-script-outside-document */}
|
|
28
78
|
<script
|
|
29
79
|
dangerouslySetInnerHTML={{
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
NEXT_PUBLIC_API_URL='/api'
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
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']});
|
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
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,16 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
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;
|
|
@@ -1,17 +0,0 @@
|
|
|
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
|
-
}
|