create-bubbles 0.1.11 → 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-monorepo-react-nestjs/.oxfmtrc.jsonc +8 -0
- package/template-vp-monorepo-react-nestjs/.oxlintrc.jsonc +3 -0
- package/template-vp-monorepo-react-nestjs/.specs//351/234/200/346/261/202.md +5 -0
- package/template-vp-monorepo-react-nestjs/.vite-hooks/commit-msg +1 -0
- package/template-vp-monorepo-react-nestjs/.vite-hooks/pre-commit +1 -0
- package/template-vp-monorepo-react-nestjs/.vscode/extensions.json +3 -0
- package/template-vp-monorepo-react-nestjs/README.md +29 -0
- package/template-vp-monorepo-react-nestjs/apps/service/.oxlintrc.jsonc +3 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.env +1 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.oxlintrc.jsonc +4 -0
- package/{template-vue-vite-biome → template-vp-monorepo-react-nestjs/apps/web}/index.html +4 -4
- package/template-vp-monorepo-react-nestjs/apps/web/package.json +39 -0
- package/template-vp-monorepo-react-nestjs/apps/web/public/favicon.svg +1 -0
- package/template-vp-monorepo-react-nestjs/apps/web/public/icons.svg +24 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/App.tsx +24 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/assets/hero.png +0 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/assets/react.svg +1 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/assets/vite.svg +1 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Loading/PageLoading.tsx +11 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/main.tsx +5 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/home/index.tsx +17 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/router/index.tsx +25 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/styles/index.css +7 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/types/auto-imports.d.ts +58 -0
- package/{template-vue-vite-biome → template-vp-monorepo-react-nestjs/apps/web}/tsconfig.json +5 -9
- package/template-vp-monorepo-react-nestjs/apps/web/vite.config.ts +32 -0
- package/template-vp-monorepo-react-nestjs/commitlint.config.js +0 -0
- package/template-vp-monorepo-react-nestjs/package.json +20 -0
- package/template-vp-monorepo-react-nestjs/packages/utils/README.md +23 -0
- package/template-vp-monorepo-react-nestjs/packages/utils/package.json +39 -0
- package/template-vp-monorepo-react-nestjs/packages/utils/src/index.ts +3 -0
- package/template-vp-monorepo-react-nestjs/packages/utils/tests/index.test.ts +6 -0
- package/template-vp-monorepo-react-nestjs/packages/utils/tsconfig.json +20 -0
- package/template-vp-monorepo-react-nestjs/packages/utils/tsdown.config.ts +9 -0
- package/template-vp-monorepo-react-nestjs/packages/utils/vite.config.ts +7 -0
- package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +23 -0
- package/template-vp-monorepo-react-nestjs/tsconfig.json +9 -0
- package/template-vp-monorepo-react-nestjs/vite.config.ts +8 -0
- package/template-vp-react/.vscode/settings.json +11 -0
- package/template-vp-react/package.json +10 -10
- 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/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
- package/template-vue-vite-eslint/package.json +29 -33
- package/template-vue-vite-eslint/src/main.ts +0 -1
- package/template-vue-vite-eslint/src/styles/index.scss +0 -3
- package/template-vue-vite-eslint/src/types/components.d.ts +0 -4
- package/template-vue-vite-eslint/src/utils/request/index.ts +4 -4
- package/template-vue-vite-eslint/vite.config.ts +2 -4
- package/template-vue-rsbuild-biome/.env +0 -5
- package/template-vue-rsbuild-biome/.env.development +0 -1
- package/template-vue-rsbuild-biome/.env.production +0 -1
- package/template-vue-rsbuild-biome/.prettierignore +0 -4
- package/template-vue-rsbuild-biome/.prettierrc +0 -18
- package/template-vue-rsbuild-biome/.vscode/settings.json +0 -13
- package/template-vue-rsbuild-biome/README.md +0 -29
- package/template-vue-rsbuild-biome/biome.json +0 -109
- package/template-vue-rsbuild-biome/index.html +0 -7
- package/template-vue-rsbuild-biome/lefthook.yml +0 -11
- package/template-vue-rsbuild-biome/package.json +0 -36
- package/template-vue-rsbuild-biome/rsbuild.config.ts +0 -41
- package/template-vue-rsbuild-biome/src/App.vue +0 -3
- package/template-vue-rsbuild-biome/src/index.ts +0 -13
- package/template-vue-rsbuild-biome/src/router/guard/index.tsx +0 -6
- package/template-vue-rsbuild-biome/src/router/guard/permissionGuard.ts +0 -8
- package/template-vue-rsbuild-biome/src/router/index.tsx +0 -14
- package/template-vue-rsbuild-biome/src/router/modules/index.tsx +0 -10
- package/template-vue-rsbuild-biome/src/store/index.ts +0 -7
- package/template-vue-rsbuild-biome/src/store/modules/user.ts +0 -17
- package/template-vue-rsbuild-biome/src/styles/index.css +0 -16
- package/template-vue-rsbuild-biome/src/types/auto-import.d.ts +0 -78
- package/template-vue-rsbuild-biome/src/types/components.d.ts +0 -15
- package/template-vue-rsbuild-biome/src/types/env.d.ts +0 -17
- package/template-vue-rsbuild-biome/src/utils/env.ts +0 -10
- package/template-vue-rsbuild-biome/src/utils/request/core/index.ts +0 -166
- package/template-vue-rsbuild-biome/src/utils/request/core/utils.ts +0 -38
- package/template-vue-rsbuild-biome/src/utils/request/index.ts +0 -39
- package/template-vue-rsbuild-biome/src/views/home/index.vue +0 -14
- package/template-vue-rsbuild-biome/tsconfig.json +0 -29
- package/template-vue-rsbuild-biome/uno.config.ts +0 -9
- package/template-vue-vite-biome/.env +0 -4
- package/template-vue-vite-biome/.env.development +0 -1
- package/template-vue-vite-biome/.env.production +0 -1
- package/template-vue-vite-biome/.gitlab-ci.yml +0 -84
- package/template-vue-vite-biome/.vscode/settings.json +0 -10
- package/template-vue-vite-biome/README.md +0 -3
- package/template-vue-vite-biome/biome.json +0 -109
- package/template-vue-vite-biome/commitlint.config.js +0 -1
- package/template-vue-vite-biome/lefthook.yml +0 -11
- package/template-vue-vite-biome/package.json +0 -50
- package/template-vue-vite-biome/public/vite.svg +0 -1
- package/template-vue-vite-biome/src/App.vue +0 -19
- package/template-vue-vite-biome/src/assets/icon/vue.svg +0 -1
- package/template-vue-vite-biome/src/components/Icon/svg-icon.vue +0 -29
- package/template-vue-vite-biome/src/layout/default/index.vue +0 -3
- package/template-vue-vite-biome/src/main.ts +0 -15
- package/template-vue-vite-biome/src/router/guard/index.ts +0 -7
- package/template-vue-vite-biome/src/router/guard/permissionGuard.ts +0 -13
- package/template-vue-vite-biome/src/router/index.ts +0 -17
- package/template-vue-vite-biome/src/router/modules/index.ts +0 -31
- package/template-vue-vite-biome/src/store/index.ts +0 -10
- package/template-vue-vite-biome/src/store/modules/user.ts +0 -17
- package/template-vue-vite-biome/src/styles/element-plus-variables.css +0 -4
- package/template-vue-vite-biome/src/styles/index.scss +0 -31
- package/template-vue-vite-biome/src/styles/variables.scss +0 -3
- package/template-vue-vite-biome/src/types/vite-env.d.ts +0 -13
- package/template-vue-vite-biome/src/utils/env.ts +0 -10
- package/template-vue-vite-biome/src/utils/request/core/index.ts +0 -166
- package/template-vue-vite-biome/src/utils/request/core/utils.ts +0 -38
- package/template-vue-vite-biome/src/utils/request/index.ts +0 -39
- package/template-vue-vite-biome/src/views/home/index.vue +0 -16
- package/template-vue-vite-biome/src/views/login/index.vue +0 -11
- package/template-vue-vite-biome/src/views/model/index.vue +0 -7
- package/template-vue-vite-biome/uno.config.ts +0 -10
- package/template-vue-vite-biome/vite.config.ts +0 -51
- package/template-vue-vite-eslint/.vscode/settings.json +0 -62
- package/template-vue-vite-eslint/src/styles/element-plus-variables.css +0 -3
- package/template-vue-vite-eslint/src/styles/plus-pro-components-variables.css +0 -3
- package/template-vue-vite-eslint/src/types/index.d.ts +0 -1
- /package/{template-vue-rsbuild-biome/public → template-vp-monorepo-react-nestjs/apps/web/src/router/modules}/.gitkeep +0 -0
- /package/{template-vue-vite-biome/src/styles/font.scss → template-vp-react-shadcn/src/api/.gitkeep} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type AnyRecord = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export function isPlainObject(value: unknown): value is AnyRecord {
|
|
4
|
+
return (
|
|
5
|
+
value !== null &&
|
|
6
|
+
typeof value === "object" &&
|
|
7
|
+
!Array.isArray(value) &&
|
|
8
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function deepMergeObject<T extends object>(source: T, target: Partial<T>) {
|
|
13
|
+
const result = { ...source } as AnyRecord;
|
|
14
|
+
|
|
15
|
+
for (const [key, value] of Object.entries(target)) {
|
|
16
|
+
const sourceValue = result[key];
|
|
17
|
+
result[key] =
|
|
18
|
+
isPlainObject(sourceValue) && isPlainObject(value)
|
|
19
|
+
? deepMergeObject(sourceValue, value)
|
|
20
|
+
: value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result as T;
|
|
24
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import reactHook from "alova/react";
|
|
2
|
+
import { toast } from "sonner";
|
|
3
|
+
|
|
4
|
+
import { navigator } from "@/router";
|
|
5
|
+
import { envVariables } from "@/utils/env";
|
|
6
|
+
|
|
7
|
+
import { createDualCallInstance } from "./alova-core";
|
|
8
|
+
|
|
9
|
+
function normalizeBaseUrl(apiAffix?: string) {
|
|
10
|
+
if (!apiAffix) return "/";
|
|
11
|
+
if (/^https?:\/\//.test(apiAffix) || apiAffix.startsWith("/")) {
|
|
12
|
+
return apiAffix;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return `/${apiAffix}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getBaseConfig(): Parameters<typeof createDualCallInstance>[0] {
|
|
19
|
+
return {
|
|
20
|
+
baseUrl: normalizeBaseUrl(envVariables.API_AFFIX),
|
|
21
|
+
statusMap: {
|
|
22
|
+
success: [200, 201, 204],
|
|
23
|
+
unAuthorized: 401,
|
|
24
|
+
},
|
|
25
|
+
codeMap: {
|
|
26
|
+
success: [200],
|
|
27
|
+
unAuthorized: [401],
|
|
28
|
+
},
|
|
29
|
+
responseDataKey: "data",
|
|
30
|
+
responseMessageKey: "msg",
|
|
31
|
+
commonHeaders: {},
|
|
32
|
+
successMessageFunc: (msg) => {
|
|
33
|
+
toast.success(msg);
|
|
34
|
+
},
|
|
35
|
+
errorMessageFunc: (msg) => {
|
|
36
|
+
toast.error(msg);
|
|
37
|
+
},
|
|
38
|
+
unAuthorizedResponseFunc: () => {
|
|
39
|
+
navigator("/login");
|
|
40
|
+
toast.error("登录过期或未登录");
|
|
41
|
+
},
|
|
42
|
+
statesHook: reactHook,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const alovaRequest = createDualCallInstance(getBaseConfig());
|
|
47
|
+
|
|
48
|
+
export default alovaRequest;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# request 使用说明
|
|
2
|
+
|
|
3
|
+
`src/utils/request/index.ts` 导出的是项目统一的 alova 请求实例,业务接口建议统一放在 `src/api` 里封装,页面和组件不要直接散写请求配置。
|
|
4
|
+
|
|
5
|
+
## 基础用法
|
|
6
|
+
|
|
7
|
+
请求方法会创建 alova `Method` 实例,不会在创建时立刻发送请求。可以在组件中交给 `useRequest`,也可以在普通函数里直接 `await`。
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import alovaRequest from "@/utils/request";
|
|
11
|
+
|
|
12
|
+
interface DramaItem {
|
|
13
|
+
id: number;
|
|
14
|
+
title: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface DramaListParams {
|
|
18
|
+
page: number;
|
|
19
|
+
pageSize: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const getDramaList = (params: DramaListParams) => {
|
|
23
|
+
return alovaRequest.Get<DramaItem[]>("/drama/list", { params });
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
组件中使用:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { useRequest } from "alova/client";
|
|
31
|
+
|
|
32
|
+
import { getDramaList } from "@/api";
|
|
33
|
+
|
|
34
|
+
export function DramaList() {
|
|
35
|
+
const { data, loading, error } = useRequest(getDramaList({ page: 1, pageSize: 20 }), {
|
|
36
|
+
initialData: [],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ...
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
普通异步函数中使用:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const list = await getDramaList({ page: 1, pageSize: 20 });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 请求参数
|
|
50
|
+
|
|
51
|
+
GET 查询参数放在第二个参数的 `params` 里:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
alovaRequest.Get<UserInfo>("/user/detail", {
|
|
55
|
+
params: {
|
|
56
|
+
id: 1,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
POST、PUT、PATCH、DELETE 的 body 放在第二个参数,配置放在第三个参数:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
alovaRequest.Post<CreateUserResult>("/user/create", {
|
|
65
|
+
name: "Tom",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
alovaRequest.Post<CreateUserResult>(
|
|
69
|
+
"/user/create",
|
|
70
|
+
{ name: "Tom" },
|
|
71
|
+
{
|
|
72
|
+
meta: {
|
|
73
|
+
isShowSuccessMessage: true,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 返回结构
|
|
80
|
+
|
|
81
|
+
默认按下面这种后端格式处理:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
interface ApiResponse<T> {
|
|
85
|
+
code: number;
|
|
86
|
+
data: T;
|
|
87
|
+
msg: string;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
默认规则:
|
|
92
|
+
|
|
93
|
+
- HTTP 状态码 `200`、`201`、`204` 视为成功。
|
|
94
|
+
- 业务 `code` 在 `codeMap.success` 中视为成功,当前默认是 `[200]`。
|
|
95
|
+
- 成功后返回 `data` 字段,所以 `alovaRequest.Get<User[]>` 的最终数据类型就是 `User[]`。
|
|
96
|
+
- 如果响应不是普通对象,会直接返回解析后的原始数据,比如字符串。
|
|
97
|
+
|
|
98
|
+
## 提示开关
|
|
99
|
+
|
|
100
|
+
单次请求可以通过 `meta` 控制是否转换响应、是否显示成功提示、是否显示错误提示。
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
alovaRequest.Post(
|
|
104
|
+
"/user/update",
|
|
105
|
+
{ id: 1, name: "Tom" },
|
|
106
|
+
{
|
|
107
|
+
meta: {
|
|
108
|
+
isShowSuccessMessage: true,
|
|
109
|
+
isShowErrorMessage: false,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
也可以创建一个带临时配置的请求实例:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const silentRequest = alovaRequest({
|
|
119
|
+
isShowErrorMessage: false,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
silentRequest.Get<UserInfo>("/user/detail", {
|
|
123
|
+
params: { id: 1 },
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 原始响应
|
|
128
|
+
|
|
129
|
+
下载文件、读取响应头、或不想走业务 `code/data/msg` 转换时,关闭 `isTransformResponse`。
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const downloadFile = () => {
|
|
133
|
+
return alovaRequest.Get<Response>("/file/download", {
|
|
134
|
+
meta: {
|
|
135
|
+
isTransformResponse: false,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const response = await downloadFile();
|
|
141
|
+
const blob = await response.blob();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 错误处理
|
|
145
|
+
|
|
146
|
+
HTTP 状态码错误、业务 `code` 错误都会抛出 `RequestError`,alova hook 会进入 `error` 分支。
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { RequestError } from "@/utils/request/alova-core";
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await getDramaList({ page: 1, pageSize: 20 });
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof RequestError) {
|
|
155
|
+
console.log(error.status, error.code, error.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`401` 会自动执行 `unAuthorizedResponseFunc`,当前行为是跳转到 `/login` 并提示登录过期。
|
|
161
|
+
|
|
162
|
+
## 环境变量
|
|
163
|
+
|
|
164
|
+
请求基础路径来自 `VITE_API_AFFIX`:
|
|
165
|
+
|
|
166
|
+
```env
|
|
167
|
+
VITE_API_AFFIX=/api
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
没有配置时会使用 `/`,避免出现 `/undefined`。
|
|
171
|
+
|
|
172
|
+
## 注意事项
|
|
173
|
+
|
|
174
|
+
- 业务接口优先写成函数并标注返回泛型,例如 `alovaRequest.Get<UserInfo>()`,组件里的 `useRequest` 会自动推断数据类型。
|
|
175
|
+
- 不要把 GET 查询参数直接作为第二个裸对象传入,第二个参数是配置对象,查询参数必须放到 `{ params }`。
|
|
176
|
+
- 需要局部关闭错误提示时优先用 `meta.isShowErrorMessage`,不要在组件里重复 toast。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
7
|
+
"types": ["vite/client"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
|
|
18
|
+
/* Linting */
|
|
19
|
+
"strict": true,
|
|
20
|
+
"noUnusedLocals": true,
|
|
21
|
+
"noUnusedParameters": true,
|
|
22
|
+
"erasableSyntaxOnly": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
"noUncheckedSideEffectImports": true,
|
|
25
|
+
|
|
26
|
+
"baseUrl": ".",
|
|
27
|
+
"paths": {
|
|
28
|
+
"@/*": ["src/*"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": ["src"]
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
4
|
+
import react from "@vitejs/plugin-react";
|
|
5
|
+
import AutoImport from "unplugin-auto-import/vite";
|
|
6
|
+
import { defineConfig, loadEnv } from "vite-plus";
|
|
7
|
+
|
|
8
|
+
export default defineConfig(({ mode }) => {
|
|
9
|
+
const root = process.cwd();
|
|
10
|
+
const env = loadEnv(mode, root);
|
|
11
|
+
const { VITE_PORT } = env;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
resolve: {
|
|
15
|
+
alias: {
|
|
16
|
+
"@": path.resolve(__dirname, "src"),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
server: {
|
|
20
|
+
port: Number(VITE_PORT),
|
|
21
|
+
host: "0.0.0.0",
|
|
22
|
+
},
|
|
23
|
+
plugins: [
|
|
24
|
+
react(),
|
|
25
|
+
AutoImport({
|
|
26
|
+
imports: ["react", "react-router", "react-dom"],
|
|
27
|
+
dts: "./src/types/auto-imports.d.ts",
|
|
28
|
+
}),
|
|
29
|
+
tailwindcss(),
|
|
30
|
+
],
|
|
31
|
+
staged: {
|
|
32
|
+
"*": "vp check --fix",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
});
|
|
@@ -11,50 +11,46 @@
|
|
|
11
11
|
"lint:fix": "eslint --fix"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@alova/adapter-axios": "^2.0.
|
|
15
|
-
"@
|
|
16
|
-
"@unocss/preset-wind3": "^66.
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"axios": "^1.
|
|
20
|
-
"dayjs": "^1.11.
|
|
14
|
+
"@alova/adapter-axios": "^2.0.18",
|
|
15
|
+
"@antdv-next/icons": "^1.0.6",
|
|
16
|
+
"@unocss/preset-wind3": "^66.6.8",
|
|
17
|
+
"alova": "^3.5.1",
|
|
18
|
+
"antdv-next": "^1.2.2",
|
|
19
|
+
"axios": "^1.16.0",
|
|
20
|
+
"dayjs": "^1.11.20",
|
|
21
21
|
"echarts": "^6.0.0",
|
|
22
|
-
"element-plus": "^2.12.0",
|
|
23
|
-
"nanoid": "^5.1.6",
|
|
24
22
|
"nprogress": "^0.2.0",
|
|
25
23
|
"pinia": "^3.0.4",
|
|
26
24
|
"pinia-plugin-persistedstate": "^4.7.1",
|
|
27
25
|
"plus-pro-components": "^0.1.30",
|
|
28
|
-
"radashi": "^12.
|
|
29
|
-
"vue": "^3.5.
|
|
30
|
-
"vue-
|
|
31
|
-
"vue-router": "^4.6.4",
|
|
32
|
-
"vue3-blocks-tree": "^0.6.2"
|
|
26
|
+
"radashi": "^12.9.0",
|
|
27
|
+
"vue": "^3.5.34",
|
|
28
|
+
"vue-router": "^5.0.6"
|
|
33
29
|
},
|
|
34
30
|
"devDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@commitlint/cli": "^20.
|
|
38
|
-
"@commitlint/config-conventional": "^20.
|
|
31
|
+
"@antdv-next/auto-import-resolver": "^1.0.7",
|
|
32
|
+
"@antfu/eslint-config": "^8.2.0",
|
|
33
|
+
"@commitlint/cli": "^20.5.3",
|
|
34
|
+
"@commitlint/config-conventional": "^20.5.3",
|
|
39
35
|
"@plus-pro-components/resolver": "^0.0.3",
|
|
40
|
-
"@types/node": "^24.
|
|
36
|
+
"@types/node": "^24.12.3",
|
|
41
37
|
"@types/nprogress": "^0.2.3",
|
|
42
|
-
"@unocss/eslint-plugin": "^66.
|
|
43
|
-
"@unocss/preset-attributify": "^66.
|
|
44
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
45
|
-
"@vue/tsconfig": "^0.
|
|
46
|
-
"eslint": "
|
|
47
|
-
"eslint-plugin-format": "^
|
|
38
|
+
"@unocss/eslint-plugin": "^66.6.8",
|
|
39
|
+
"@unocss/preset-attributify": "^66.6.8",
|
|
40
|
+
"@vitejs/plugin-vue": "^6.0.6",
|
|
41
|
+
"@vue/tsconfig": "^0.9.1",
|
|
42
|
+
"eslint": "10.3.0",
|
|
43
|
+
"eslint-plugin-format": "^2.0.1",
|
|
48
44
|
"fast-glob": "^3.3.3",
|
|
49
|
-
"lefthook": "^2.
|
|
50
|
-
"sass": "^1.
|
|
51
|
-
"typescript": "~
|
|
52
|
-
"unocss": "66.
|
|
53
|
-
"unplugin-auto-import": "^
|
|
54
|
-
"unplugin-vue-components": "^
|
|
55
|
-
"vite": "8.0.
|
|
45
|
+
"lefthook": "^2.1.6",
|
|
46
|
+
"sass": "^1.99.0",
|
|
47
|
+
"typescript": "~6.0.3",
|
|
48
|
+
"unocss": "66.6.8",
|
|
49
|
+
"unplugin-auto-import": "^21.0.0",
|
|
50
|
+
"unplugin-vue-components": "^32.0.0",
|
|
51
|
+
"vite": "8.0.11",
|
|
56
52
|
"vite-plugin-inspect": "^11.3.3",
|
|
57
53
|
"vite-plugin-svg-icons": "^2.0.1",
|
|
58
|
-
"vue-tsc": "^3.
|
|
54
|
+
"vue-tsc": "^3.2.8"
|
|
59
55
|
}
|
|
60
56
|
}
|