create-bubbles 0.1.12 → 0.1.13
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/index.js +2 -1
- package/package.json +1 -1
- package/template-vp-react/.vscode/settings.json +11 -0
- package/template-vp-react-shadcn/.env +1 -0
- package/template-vp-react-shadcn/.oxfmtrc.jsonc +8 -0
- package/template-vp-react-shadcn/.oxlintrc.jsonc +3 -0
- package/template-vp-react-shadcn/.vite-hooks/commit-msg +1 -0
- package/template-vp-react-shadcn/.vite-hooks/pre-commit +1 -0
- package/template-vp-react-shadcn/.vscode/extensions.json +3 -0
- package/template-vp-react-shadcn/.vscode/settings.json +12 -0
- package/template-vp-react-shadcn/agents.md +44 -0
- package/template-vp-react-shadcn/commitlint.config.js +1 -0
- package/template-vp-react-shadcn/components.json +25 -0
- package/template-vp-react-shadcn/index.html +13 -0
- package/template-vp-react-shadcn/package.json +56 -0
- package/template-vp-react-shadcn/public/favicon.svg +1 -0
- package/template-vp-react-shadcn/public/icons.svg +24 -0
- package/template-vp-react-shadcn/src/App.tsx +24 -0
- package/template-vp-react-shadcn/src/api/.gitkeep +0 -0
- package/template-vp-react-shadcn/src/assets/hero.png +0 -0
- package/template-vp-react-shadcn/src/assets/react.svg +1 -0
- package/template-vp-react-shadcn/src/assets/vite.svg +1 -0
- package/template-vp-react-shadcn/src/components/Loading/PageLoading.tsx +11 -0
- package/template-vp-react-shadcn/src/components/Player/VideoJS/index.tsx +163 -0
- package/template-vp-react-shadcn/src/components/Player/index.ts +7 -0
- package/template-vp-react-shadcn/src/components/ui/button.tsx +67 -0
- package/template-vp-react-shadcn/src/components/ui/sonner.tsx +43 -0
- package/template-vp-react-shadcn/src/components/ui/spinner.tsx +17 -0
- package/template-vp-react-shadcn/src/hooks/.gitkeep +0 -0
- package/template-vp-react-shadcn/src/lib/utils.ts +6 -0
- package/template-vp-react-shadcn/src/main.tsx +5 -0
- package/template-vp-react-shadcn/src/pages/home/index.tsx +96 -0
- package/template-vp-react-shadcn/src/router/index.tsx +25 -0
- package/template-vp-react-shadcn/src/router/modules/.gitkeep +0 -0
- package/template-vp-react-shadcn/src/store/.gitkeep +0 -0
- package/template-vp-react-shadcn/src/styles/index.css +16 -0
- package/template-vp-react-shadcn/src/styles/shadcn.css +127 -0
- package/template-vp-react-shadcn/src/types/auto-imports.d.ts +58 -0
- package/template-vp-react-shadcn/src/utils/env/index.tsx +6 -0
- package/template-vp-react-shadcn/src/utils/request/alova-core/index.ts +276 -0
- package/template-vp-react-shadcn/src/utils/request/alova-core/utils.ts +24 -0
- package/template-vp-react-shadcn/src/utils/request/index.ts +48 -0
- package/template-vp-react-shadcn/src/utils/request/readme.md +176 -0
- package/template-vp-react-shadcn/tsconfig.json +32 -0
- package/template-vp-react-shadcn/vite.config.ts +35 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useTheme } from "next-themes";
|
|
2
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
|
3
|
+
import {
|
|
4
|
+
CircleCheckIcon,
|
|
5
|
+
InfoIcon,
|
|
6
|
+
TriangleAlertIcon,
|
|
7
|
+
OctagonXIcon,
|
|
8
|
+
Loader2Icon,
|
|
9
|
+
} from "lucide-react";
|
|
10
|
+
|
|
11
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
12
|
+
const { theme = "system" } = useTheme();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Sonner
|
|
16
|
+
theme={theme as ToasterProps["theme"]}
|
|
17
|
+
className="toaster group"
|
|
18
|
+
icons={{
|
|
19
|
+
success: <CircleCheckIcon className="size-4" />,
|
|
20
|
+
info: <InfoIcon className="size-4" />,
|
|
21
|
+
warning: <TriangleAlertIcon className="size-4" />,
|
|
22
|
+
error: <OctagonXIcon className="size-4" />,
|
|
23
|
+
loading: <Loader2Icon className="size-4 animate-spin" />,
|
|
24
|
+
}}
|
|
25
|
+
style={
|
|
26
|
+
{
|
|
27
|
+
"--normal-bg": "var(--popover)",
|
|
28
|
+
"--normal-text": "var(--popover-foreground)",
|
|
29
|
+
"--normal-border": "var(--border)",
|
|
30
|
+
"--border-radius": "var(--radius)",
|
|
31
|
+
} as React.CSSProperties
|
|
32
|
+
}
|
|
33
|
+
toastOptions={{
|
|
34
|
+
classNames: {
|
|
35
|
+
toast: "cn-toast",
|
|
36
|
+
},
|
|
37
|
+
}}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export { Toaster };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Loader2Icon } from "lucide-react";
|
|
2
|
+
import type { ComponentProps } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
function Spinner({ className, ...props }: ComponentProps<"svg">) {
|
|
7
|
+
return (
|
|
8
|
+
<Loader2Icon
|
|
9
|
+
role="status"
|
|
10
|
+
aria-label="Loading"
|
|
11
|
+
className={cn("size-4 animate-spin", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { Spinner };
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { PlayCircle } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { VideoJS, VIDEO_JS_SOURCE_TYPES, type VideoJSSourceType } from "@/components/Player";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
const HOME_TEXT = {
|
|
9
|
+
player: "VideoJS 播放器",
|
|
10
|
+
title: "HLS / MP4 播放示例",
|
|
11
|
+
play: "播放",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
type DemoSource = {
|
|
15
|
+
id: Exclude<VideoJSSourceType, "auto" | "native">;
|
|
16
|
+
name: string;
|
|
17
|
+
src: string;
|
|
18
|
+
type: string;
|
|
19
|
+
poster: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const DEMO_SOURCES: DemoSource[] = [
|
|
23
|
+
{
|
|
24
|
+
id: VIDEO_JS_SOURCE_TYPES.HLS,
|
|
25
|
+
name: "HLS",
|
|
26
|
+
src: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
|
27
|
+
type: "application/vnd.apple.mpegurl",
|
|
28
|
+
poster: "https://image.mux.com/x36xhzz/thumbnail.jpg?time=0",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: VIDEO_JS_SOURCE_TYPES.MP4,
|
|
32
|
+
name: "MP4",
|
|
33
|
+
src: "https://media.w3.org/2010/05/sintel/trailer.mp4",
|
|
34
|
+
type: "video/mp4",
|
|
35
|
+
poster: "https://media.w3.org/2010/05/sintel/poster.png",
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const Home = () => {
|
|
40
|
+
const [activeSourceType, setActiveSourceType] = useState<DemoSource["id"]>(
|
|
41
|
+
VIDEO_JS_SOURCE_TYPES.HLS,
|
|
42
|
+
);
|
|
43
|
+
const activeSource =
|
|
44
|
+
DEMO_SOURCES.find((source) => source.id === activeSourceType) ?? DEMO_SOURCES[0];
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<main className="min-h-screen bg-zinc-950 px-4 py-6 text-white sm:px-6 lg:px-8">
|
|
48
|
+
<section className="mx-auto flex w-full max-w-6xl flex-col gap-5">
|
|
49
|
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
<p className="text-sm font-medium text-emerald-300">{HOME_TEXT.player}</p>
|
|
52
|
+
<h1 className="max-w-2xl text-3xl font-semibold tracking-normal text-white sm:text-4xl">
|
|
53
|
+
{HOME_TEXT.title}
|
|
54
|
+
</h1>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="flex rounded-lg border border-white/10 bg-white/5 p-1">
|
|
58
|
+
{DEMO_SOURCES.map((source) => {
|
|
59
|
+
const isActive = source.id === activeSource.id;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Button
|
|
63
|
+
key={source.id}
|
|
64
|
+
type="button"
|
|
65
|
+
variant="ghost"
|
|
66
|
+
className={cn(
|
|
67
|
+
"h-9 gap-2 rounded-md px-4 text-white hover:bg-white/10 hover:text-white",
|
|
68
|
+
isActive && "bg-white text-zinc-950 hover:bg-white hover:text-zinc-950",
|
|
69
|
+
)}
|
|
70
|
+
onClick={() => setActiveSourceType(source.id)}
|
|
71
|
+
>
|
|
72
|
+
<PlayCircle className="size-4" />
|
|
73
|
+
{source.name}
|
|
74
|
+
</Button>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<VideoJS
|
|
81
|
+
key={activeSource.src}
|
|
82
|
+
src={activeSource.src}
|
|
83
|
+
type={activeSource.type}
|
|
84
|
+
sourceType={activeSource.id}
|
|
85
|
+
poster={activeSource.poster}
|
|
86
|
+
preload="metadata"
|
|
87
|
+
className="border border-white/10 shadow-2xl shadow-black/40"
|
|
88
|
+
/>
|
|
89
|
+
</section>
|
|
90
|
+
|
|
91
|
+
<Button>{HOME_TEXT.play}</Button>
|
|
92
|
+
</main>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default Home;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createBrowserRouter, Navigate, type RouteObject } from "react-router";
|
|
2
|
+
|
|
3
|
+
export const lazyLoad = (path: string, fileName = "index") => {
|
|
4
|
+
const Module = lazy(() => import(`@/pages/${path}/${fileName}.tsx`));
|
|
5
|
+
return <Module />;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const routes: RouteObject[] = [
|
|
9
|
+
{
|
|
10
|
+
path: "/",
|
|
11
|
+
id: "index",
|
|
12
|
+
element: <Navigate to={"/home"} />,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
path: "/home",
|
|
16
|
+
id: "home",
|
|
17
|
+
element: lazyLoad("home"),
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const router = createBrowserRouter(routes, {
|
|
22
|
+
basename: "/",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const navigator = router.navigate;
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
@import "shadcn/tailwind.css";
|
|
2
|
+
|
|
3
|
+
@custom-variant dark (&:is(.dark *));
|
|
4
|
+
|
|
5
|
+
@theme inline {
|
|
6
|
+
--font-heading: var(--font-sans);
|
|
7
|
+
--font-sans: "Geist Variable", sans-serif;
|
|
8
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
9
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
10
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
11
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
12
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
13
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
14
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
15
|
+
--color-sidebar: var(--sidebar);
|
|
16
|
+
--color-chart-5: var(--chart-5);
|
|
17
|
+
--color-chart-4: var(--chart-4);
|
|
18
|
+
--color-chart-3: var(--chart-3);
|
|
19
|
+
--color-chart-2: var(--chart-2);
|
|
20
|
+
--color-chart-1: var(--chart-1);
|
|
21
|
+
--color-ring: var(--ring);
|
|
22
|
+
--color-input: var(--input);
|
|
23
|
+
--color-border: var(--border);
|
|
24
|
+
--color-destructive: var(--destructive);
|
|
25
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
26
|
+
--color-accent: var(--accent);
|
|
27
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
28
|
+
--color-muted: var(--muted);
|
|
29
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
30
|
+
--color-secondary: var(--secondary);
|
|
31
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
32
|
+
--color-primary: var(--primary);
|
|
33
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
34
|
+
--color-popover: var(--popover);
|
|
35
|
+
--color-card-foreground: var(--card-foreground);
|
|
36
|
+
--color-card: var(--card);
|
|
37
|
+
--color-foreground: var(--foreground);
|
|
38
|
+
--color-background: var(--background);
|
|
39
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
40
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
41
|
+
--radius-lg: var(--radius);
|
|
42
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
43
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
44
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
45
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:root {
|
|
49
|
+
--background: oklch(1 0 0);
|
|
50
|
+
--foreground: oklch(0.145 0 0);
|
|
51
|
+
--card: oklch(1 0 0);
|
|
52
|
+
--card-foreground: oklch(0.145 0 0);
|
|
53
|
+
--popover: oklch(1 0 0);
|
|
54
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
55
|
+
--primary: rgb(47 123 255);
|
|
56
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
57
|
+
--secondary: oklch(0.97 0 0);
|
|
58
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
59
|
+
--muted: oklch(0.97 0 0);
|
|
60
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
61
|
+
--accent: oklch(0.97 0 0);
|
|
62
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
63
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
64
|
+
--border: oklch(0.922 0 0);
|
|
65
|
+
--input: oklch(0.922 0 0);
|
|
66
|
+
--ring: rgb(47 123 255);
|
|
67
|
+
--chart-1: oklch(0.87 0 0);
|
|
68
|
+
--chart-2: oklch(0.556 0 0);
|
|
69
|
+
--chart-3: oklch(0.439 0 0);
|
|
70
|
+
--chart-4: oklch(0.371 0 0);
|
|
71
|
+
--chart-5: oklch(0.269 0 0);
|
|
72
|
+
--radius: 0.625rem;
|
|
73
|
+
--sidebar: oklch(0.985 0 0);
|
|
74
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
75
|
+
--sidebar-primary: rgb(47 123 255);
|
|
76
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
77
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
78
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
79
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
80
|
+
--sidebar-ring: rgb(47 123 255);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.dark {
|
|
84
|
+
--background: oklch(0.145 0 0);
|
|
85
|
+
--foreground: oklch(0.985 0 0);
|
|
86
|
+
--card: oklch(0.205 0 0);
|
|
87
|
+
--card-foreground: oklch(0.985 0 0);
|
|
88
|
+
--popover: oklch(0.205 0 0);
|
|
89
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
90
|
+
--primary: rgb(47 123 255);
|
|
91
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
92
|
+
--secondary: oklch(0.269 0 0);
|
|
93
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
94
|
+
--muted: oklch(0.269 0 0);
|
|
95
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
96
|
+
--accent: oklch(0.269 0 0);
|
|
97
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
98
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
99
|
+
--border: oklch(1 0 0 / 10%);
|
|
100
|
+
--input: oklch(1 0 0 / 15%);
|
|
101
|
+
--ring: rgb(47 123 255);
|
|
102
|
+
--chart-1: oklch(0.87 0 0);
|
|
103
|
+
--chart-2: oklch(0.556 0 0);
|
|
104
|
+
--chart-3: oklch(0.439 0 0);
|
|
105
|
+
--chart-4: oklch(0.371 0 0);
|
|
106
|
+
--chart-5: oklch(0.269 0 0);
|
|
107
|
+
--sidebar: oklch(0.205 0 0);
|
|
108
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
109
|
+
--sidebar-primary: rgb(47 123 255);
|
|
110
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
111
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
112
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
113
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
114
|
+
--sidebar-ring: rgb(47 123 255);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@layer base {
|
|
118
|
+
* {
|
|
119
|
+
@apply border-border outline-ring/50;
|
|
120
|
+
}
|
|
121
|
+
body {
|
|
122
|
+
@apply bg-background text-foreground;
|
|
123
|
+
}
|
|
124
|
+
html {
|
|
125
|
+
@apply font-sans;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/* prettier-ignore */
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
// noinspection JSUnusedGlobalSymbols
|
|
5
|
+
// Generated by unplugin-auto-import
|
|
6
|
+
// biome-ignore lint: disable
|
|
7
|
+
export {}
|
|
8
|
+
declare global {
|
|
9
|
+
const Activity: typeof import('react').Activity
|
|
10
|
+
const Fragment: typeof import('react').Fragment
|
|
11
|
+
const Suspense: typeof import('react').Suspense
|
|
12
|
+
const cache: typeof import('react').cache
|
|
13
|
+
const cacheSignal: typeof import('react').cacheSignal
|
|
14
|
+
const createContext: typeof import('react').createContext
|
|
15
|
+
const createPortal: typeof import('react-dom').createPortal
|
|
16
|
+
const createRef: typeof import('react').createRef
|
|
17
|
+
const flushSync: typeof import('react-dom').flushSync
|
|
18
|
+
const forwardRef: typeof import('react').forwardRef
|
|
19
|
+
const lazy: typeof import('react').lazy
|
|
20
|
+
const memo: typeof import('react').memo
|
|
21
|
+
const preconnect: typeof import('react-dom').preconnect
|
|
22
|
+
const prefetchDNS: typeof import('react-dom').prefetchDNS
|
|
23
|
+
const preinit: typeof import('react-dom').preinit
|
|
24
|
+
const preinitModule: typeof import('react-dom').preinitModule
|
|
25
|
+
const preload: typeof import('react-dom').preload
|
|
26
|
+
const preloadModule: typeof import('react-dom').preloadModule
|
|
27
|
+
const startTransition: typeof import('react').startTransition
|
|
28
|
+
const use: typeof import('react').use
|
|
29
|
+
const useActionState: typeof import('react').useActionState
|
|
30
|
+
const useCallback: typeof import('react').useCallback
|
|
31
|
+
const useContext: typeof import('react').useContext
|
|
32
|
+
const useDebugValue: typeof import('react').useDebugValue
|
|
33
|
+
const useDeferredValue: typeof import('react').useDeferredValue
|
|
34
|
+
const useEffect: typeof import('react').useEffect
|
|
35
|
+
const useEffectEvent: typeof import('react').useEffectEvent
|
|
36
|
+
const useFormStatus: typeof import('react-dom').useFormStatus
|
|
37
|
+
const useHref: typeof import('react-router').useHref
|
|
38
|
+
const useId: typeof import('react').useId
|
|
39
|
+
const useImperativeHandle: typeof import('react').useImperativeHandle
|
|
40
|
+
const useInRouterContext: typeof import('react-router').useInRouterContext
|
|
41
|
+
const useInsertionEffect: typeof import('react').useInsertionEffect
|
|
42
|
+
const useLayoutEffect: typeof import('react').useLayoutEffect
|
|
43
|
+
const useLocation: typeof import('react-router').useLocation
|
|
44
|
+
const useMemo: typeof import('react').useMemo
|
|
45
|
+
const useNavigate: typeof import('react-router').useNavigate
|
|
46
|
+
const useNavigationType: typeof import('react-router').useNavigationType
|
|
47
|
+
const useOptimistic: typeof import('react').useOptimistic
|
|
48
|
+
const useOutlet: typeof import('react-router').useOutlet
|
|
49
|
+
const useOutletContext: typeof import('react-router').useOutletContext
|
|
50
|
+
const useParams: typeof import('react-router').useParams
|
|
51
|
+
const useReducer: typeof import('react').useReducer
|
|
52
|
+
const useRef: typeof import('react').useRef
|
|
53
|
+
const useResolvedPath: typeof import('react-router').useResolvedPath
|
|
54
|
+
const useRoutes: typeof import('react-router').useRoutes
|
|
55
|
+
const useState: typeof import('react').useState
|
|
56
|
+
const useSyncExternalStore: typeof import('react').useSyncExternalStore
|
|
57
|
+
const useTransition: typeof import('react').useTransition
|
|
58
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import type { Alova, AlovaDefaultCacheAdapter, AlovaOptions } from "alova";
|
|
2
|
+
import { createAlova } from "alova";
|
|
3
|
+
import adapterFetch, { type FetchRequestAdapter, type FetchRequestInit } from "alova/fetch";
|
|
4
|
+
import type { ReactHookExportType } from "alova/react";
|
|
5
|
+
|
|
6
|
+
import { deepMergeObject, isPlainObject } from "./utils";
|
|
7
|
+
|
|
8
|
+
type StatusMatcher = number | number[];
|
|
9
|
+
type HeaderValue = string | (() => string);
|
|
10
|
+
type RequestAlovaGenerics = {
|
|
11
|
+
Responded: unknown;
|
|
12
|
+
Transformed: unknown;
|
|
13
|
+
RequestConfig: FetchRequestInit;
|
|
14
|
+
Response: Response;
|
|
15
|
+
ResponseHeader: Headers;
|
|
16
|
+
L1Cache: AlovaDefaultCacheAdapter;
|
|
17
|
+
L2Cache: AlovaDefaultCacheAdapter;
|
|
18
|
+
StatesExport: ReactHookExportType<unknown>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
interface StatusMap {
|
|
22
|
+
success?: StatusMatcher;
|
|
23
|
+
unAuthorized?: StatusMatcher;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CodeMap {
|
|
27
|
+
success?: number[];
|
|
28
|
+
unAuthorized?: number[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BaseRequestOption {
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
timeout?: number;
|
|
34
|
+
commonHeaders?: Record<string, HeaderValue>;
|
|
35
|
+
statusMap?: StatusMap;
|
|
36
|
+
codeMap?: CodeMap;
|
|
37
|
+
responseDataKey?: string;
|
|
38
|
+
responseMessageKey?: string;
|
|
39
|
+
isTransformResponse?: boolean;
|
|
40
|
+
isShowSuccessMessage?: boolean;
|
|
41
|
+
successDefaultMessage?: string;
|
|
42
|
+
isShowErrorMessage?: boolean;
|
|
43
|
+
errorDefaultMessage?: string;
|
|
44
|
+
statesHook?: AlovaOptions<RequestAlovaGenerics>["statesHook"];
|
|
45
|
+
successMessageFunc?: (message: string) => void;
|
|
46
|
+
errorMessageFunc?: (message: string) => void;
|
|
47
|
+
unAuthorizedResponseFunc?: () => void;
|
|
48
|
+
requestAdapter?: FetchRequestAdapter;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CustomConfig {
|
|
52
|
+
isTransformResponse?: boolean;
|
|
53
|
+
isShowSuccessMessage?: boolean;
|
|
54
|
+
isShowErrorMessage?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
declare module "alova" {
|
|
58
|
+
interface AlovaCustomTypes {
|
|
59
|
+
meta: CustomConfig;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface RequestErrorOptions {
|
|
64
|
+
status?: number;
|
|
65
|
+
code?: unknown;
|
|
66
|
+
data?: unknown;
|
|
67
|
+
response?: Response;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class RequestError extends Error {
|
|
71
|
+
status?: number;
|
|
72
|
+
code?: unknown;
|
|
73
|
+
data?: unknown;
|
|
74
|
+
response?: Response;
|
|
75
|
+
|
|
76
|
+
constructor(message: string, options: RequestErrorOptions = {}) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.name = "RequestError";
|
|
79
|
+
this.status = options.status;
|
|
80
|
+
this.code = options.code;
|
|
81
|
+
this.data = options.data;
|
|
82
|
+
this.response = options.response;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type RequestInstance = Alova<RequestAlovaGenerics>;
|
|
87
|
+
export type DualCallInstance = RequestInstance & ((option?: CustomConfig) => RequestInstance);
|
|
88
|
+
|
|
89
|
+
type RequestOption = BaseRequestOption & CustomConfig;
|
|
90
|
+
|
|
91
|
+
const DEFAULT_REQUEST_ADAPTER = adapterFetch();
|
|
92
|
+
const DEFAULT_RESPONSE_DATA_KEY = "data";
|
|
93
|
+
const DEFAULT_RESPONSE_MESSAGE_KEY = "message";
|
|
94
|
+
const DEFAULT_SUCCESS_MESSAGE = "操作成功";
|
|
95
|
+
const DEFAULT_ERROR_MESSAGE = "服务异常";
|
|
96
|
+
const DEFAULT_REQUEST_OPTION: RequestOption = {
|
|
97
|
+
baseUrl: "/",
|
|
98
|
+
timeout: 0,
|
|
99
|
+
statusMap: {
|
|
100
|
+
success: [200, 201, 204],
|
|
101
|
+
unAuthorized: 401,
|
|
102
|
+
},
|
|
103
|
+
codeMap: {
|
|
104
|
+
success: [200],
|
|
105
|
+
unAuthorized: [401],
|
|
106
|
+
},
|
|
107
|
+
responseDataKey: DEFAULT_RESPONSE_DATA_KEY,
|
|
108
|
+
responseMessageKey: DEFAULT_RESPONSE_MESSAGE_KEY,
|
|
109
|
+
isTransformResponse: true,
|
|
110
|
+
isShowSuccessMessage: false,
|
|
111
|
+
successDefaultMessage: DEFAULT_SUCCESS_MESSAGE,
|
|
112
|
+
isShowErrorMessage: true,
|
|
113
|
+
errorDefaultMessage: DEFAULT_ERROR_MESSAGE,
|
|
114
|
+
requestAdapter: DEFAULT_REQUEST_ADAPTER,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
function isMatchedStatus(status: number, matcher?: StatusMatcher) {
|
|
118
|
+
if (matcher === undefined) return status >= 200 && status < 300;
|
|
119
|
+
return Array.isArray(matcher) ? matcher.includes(status) : matcher === status;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isMatchedCode(code: unknown, codes?: number[]) {
|
|
123
|
+
if (!codes?.length) return true;
|
|
124
|
+
|
|
125
|
+
const normalizedCode = Number(code);
|
|
126
|
+
return Number.isFinite(normalizedCode) && codes.includes(normalizedCode);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getResponseMessage(data: unknown, messageKey: string, defaultMessage: string) {
|
|
130
|
+
if (!isPlainObject(data)) return defaultMessage;
|
|
131
|
+
|
|
132
|
+
const message = data[messageKey] ?? data.message ?? data.msg;
|
|
133
|
+
if (typeof message === "string") return message || defaultMessage;
|
|
134
|
+
if (typeof message === "number") return String(message);
|
|
135
|
+
|
|
136
|
+
return defaultMessage;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getRuntimeConfig(option: RequestOption, customConfig?: CustomConfig) {
|
|
140
|
+
return {
|
|
141
|
+
isTransformResponse: customConfig?.isTransformResponse ?? option.isTransformResponse ?? true,
|
|
142
|
+
isShowSuccessMessage:
|
|
143
|
+
customConfig?.isShowSuccessMessage ?? option.isShowSuccessMessage ?? false,
|
|
144
|
+
isShowErrorMessage: customConfig?.isShowErrorMessage ?? option.isShowErrorMessage ?? true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function parseResponse(response: Response) {
|
|
149
|
+
if (!response.body || response.status === 204) return undefined;
|
|
150
|
+
|
|
151
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
152
|
+
if (contentType.includes("application/json")) return response.json();
|
|
153
|
+
if (contentType.startsWith("text/")) return response.text();
|
|
154
|
+
|
|
155
|
+
const clonedResponse = response.clone();
|
|
156
|
+
try {
|
|
157
|
+
return await response.json();
|
|
158
|
+
} catch {
|
|
159
|
+
try {
|
|
160
|
+
return await clonedResponse.text();
|
|
161
|
+
} catch {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function createInstance(option: RequestOption): RequestInstance {
|
|
168
|
+
const mergeOption = deepMergeObject(DEFAULT_REQUEST_OPTION, option);
|
|
169
|
+
|
|
170
|
+
return createAlova({
|
|
171
|
+
baseURL: mergeOption.baseUrl,
|
|
172
|
+
timeout: mergeOption.timeout,
|
|
173
|
+
statesHook: mergeOption.statesHook,
|
|
174
|
+
requestAdapter: mergeOption.requestAdapter ?? DEFAULT_REQUEST_ADAPTER,
|
|
175
|
+
beforeRequest: (method) => {
|
|
176
|
+
for (const [key, value] of Object.entries(mergeOption.commonHeaders ?? {})) {
|
|
177
|
+
method.config.headers[key] = typeof value === "function" ? value() : value;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
cacheFor: null,
|
|
181
|
+
responded: {
|
|
182
|
+
onSuccess: async (response, method) => {
|
|
183
|
+
const runtimeConfig = getRuntimeConfig(mergeOption, method.config.meta);
|
|
184
|
+
if (!runtimeConfig.isTransformResponse) return response;
|
|
185
|
+
|
|
186
|
+
const data = await parseResponse(response);
|
|
187
|
+
const responseMessageKey = mergeOption.responseMessageKey ?? DEFAULT_RESPONSE_MESSAGE_KEY;
|
|
188
|
+
const errorDefaultMessage = mergeOption.errorDefaultMessage ?? DEFAULT_ERROR_MESSAGE;
|
|
189
|
+
|
|
190
|
+
if (!isMatchedStatus(response.status, mergeOption.statusMap?.success)) {
|
|
191
|
+
if (isMatchedStatus(response.status, mergeOption.statusMap?.unAuthorized)) {
|
|
192
|
+
mergeOption.unAuthorizedResponseFunc?.();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const errorMessage = getResponseMessage(data, responseMessageKey, errorDefaultMessage);
|
|
196
|
+
if (runtimeConfig.isShowErrorMessage) {
|
|
197
|
+
mergeOption.errorMessageFunc?.(errorMessage);
|
|
198
|
+
}
|
|
199
|
+
throw new RequestError(errorMessage, {
|
|
200
|
+
status: response.status,
|
|
201
|
+
data,
|
|
202
|
+
response,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!isPlainObject(data)) return data;
|
|
207
|
+
|
|
208
|
+
const responseCode = data.code;
|
|
209
|
+
const responseMessage = getResponseMessage(
|
|
210
|
+
data,
|
|
211
|
+
responseMessageKey,
|
|
212
|
+
mergeOption.successDefaultMessage ?? DEFAULT_SUCCESS_MESSAGE,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
responseCode !== undefined &&
|
|
217
|
+
!isMatchedCode(responseCode, mergeOption.codeMap?.success)
|
|
218
|
+
) {
|
|
219
|
+
if (isMatchedCode(responseCode, mergeOption.codeMap?.unAuthorized)) {
|
|
220
|
+
mergeOption.unAuthorizedResponseFunc?.();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const errorMessage = getResponseMessage(data, responseMessageKey, errorDefaultMessage);
|
|
224
|
+
if (runtimeConfig.isShowErrorMessage) {
|
|
225
|
+
mergeOption.errorMessageFunc?.(errorMessage);
|
|
226
|
+
}
|
|
227
|
+
throw new RequestError(errorMessage, {
|
|
228
|
+
status: response.status,
|
|
229
|
+
code: responseCode,
|
|
230
|
+
data,
|
|
231
|
+
response,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (runtimeConfig.isShowSuccessMessage) {
|
|
236
|
+
mergeOption.successMessageFunc?.(responseMessage);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const responseDataKey = mergeOption.responseDataKey ?? DEFAULT_RESPONSE_DATA_KEY;
|
|
240
|
+
return responseDataKey in data ? data[responseDataKey] : data;
|
|
241
|
+
},
|
|
242
|
+
onError: (error) => {
|
|
243
|
+
const errorMessage =
|
|
244
|
+
error instanceof Error
|
|
245
|
+
? error.message
|
|
246
|
+
: (mergeOption.errorDefaultMessage ?? DEFAULT_ERROR_MESSAGE);
|
|
247
|
+
|
|
248
|
+
if (mergeOption.isShowErrorMessage) {
|
|
249
|
+
mergeOption.errorMessageFunc?.(errorMessage);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
throw error;
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function createDualCallInstance(baseConfig: BaseRequestOption): DualCallInstance {
|
|
259
|
+
const defaultInstance = createInstance(baseConfig);
|
|
260
|
+
const dualInstance = ((option?: CustomConfig) => {
|
|
261
|
+
if (!option) return defaultInstance;
|
|
262
|
+
return createInstance(deepMergeObject(baseConfig, option));
|
|
263
|
+
}) as DualCallInstance;
|
|
264
|
+
|
|
265
|
+
Object.assign(dualInstance, defaultInstance);
|
|
266
|
+
dualInstance.Get = defaultInstance.Get.bind(defaultInstance);
|
|
267
|
+
dualInstance.Post = defaultInstance.Post.bind(defaultInstance);
|
|
268
|
+
dualInstance.Put = defaultInstance.Put.bind(defaultInstance);
|
|
269
|
+
dualInstance.Delete = defaultInstance.Delete.bind(defaultInstance);
|
|
270
|
+
dualInstance.Patch = defaultInstance.Patch.bind(defaultInstance);
|
|
271
|
+
dualInstance.Head = defaultInstance.Head.bind(defaultInstance);
|
|
272
|
+
dualInstance.Options = defaultInstance.Options.bind(defaultInstance);
|
|
273
|
+
dualInstance.Request = defaultInstance.Request.bind(defaultInstance);
|
|
274
|
+
|
|
275
|
+
return dualInstance;
|
|
276
|
+
}
|