@clownlee/http 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @fly/request
2
+
3
+ Axios 公共通用库,提供统一的 HTTP 请求能力和自动 token 刷新功能。
4
+
5
+ ## 功能特性
6
+
7
+ - ✅ TypeScript 支持
8
+ - ✅ baseUrl、headers 等配置封装
9
+ - ✅ 请求拦截器和响应拦截器
10
+ - ✅ 支持所有 HTTP 方法(get、post、put、patch、delete 等)
11
+ - ✅ 手动终止请求功能
12
+ - ✅ 自动添加 Bearer token
13
+ - ✅ 401 时自动刷新 token(通过 refreshToken)
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ npm install @fly/request
19
+ ```
20
+
21
+ ## 使用示例
22
+
23
+ ```typescript
24
+ import { init } from '@fly/request'
25
+ import { useUserStore } from '@/stores/user'
26
+
27
+ const userStore = useUserStore()
28
+
29
+ const http = init({
30
+ store: userStore, // pinia 实例
31
+ baseUrl: 'https://api.example.com',
32
+ timeout: 10000,
33
+ headers: {
34
+ 'Content-Type': 'application/json'
35
+ },
36
+ refresh: {
37
+ baseUrl: 'https://api.example.com', // 一般情况 = 最外层的 baseUrl
38
+ path: '/api/v1/auth/refresh',
39
+ name: 'Authorization', // 请求头名称
40
+ token: userStore.refreshToken // 通过 pinia localStorage 存储的 refreshToken 值
41
+ }
42
+ })
43
+
44
+ // 使用 HTTP 方法
45
+ const data = await http.get('/api/users')
46
+ const result = await http.post('/api/users', { name: 'John' })
47
+ await http.put('/api/users/1', { name: 'Jane' })
48
+ await http.patch('/api/users/1', { name: 'Jane' })
49
+ await http.delete('/api/users/1')
50
+ ```
51
+
52
+ ## API
53
+
54
+ ### init(config)
55
+
56
+ 初始化 HTTP 实例。
57
+
58
+ #### 参数
59
+
60
+ - `config.store`: Pinia store 实例,用于存储和获取 token
61
+ - `config.baseUrl`: 接口域名
62
+ - `config.timeout`: 超时时间(毫秒)
63
+ - `config.headers`: 请求头信息
64
+ - `config.refresh`: Token 刷新配置
65
+ - `refresh.baseUrl`: 刷新接口域名
66
+ - `refresh.path`: 刷新接口路径
67
+ - `refresh.name`: 请求头名称(默认为 'Authorization')
68
+ - `refresh.token`: refreshToken 值
69
+
70
+ #### 返回值
71
+
72
+ 返回 HTTP 实例,包含以下方法:
73
+ - `get(url, params?, config?)`
74
+ - `post(url, data?, config?)`
75
+ - `put(url, data?, config?)`
76
+ - `patch(url, data?, config?)`
77
+ - `delete(url, config?)`
78
+ - `request(config)`
79
+ - `createCancelToken()`: 创建取消令牌
80
+ - `cancelRequest(cancelToken, message?)`: 手动终止请求
81
+
@@ -0,0 +1,5 @@
1
+ import { RequestInitConfig, RefreshConfig, HttpRequestConfig, HttpResponse, HttpInstance, TokenResponse } from './types/request.types';
2
+ import { init } from './services/request.service';
3
+
4
+ export type { RequestInitConfig, RefreshConfig, HttpRequestConfig, HttpResponse, HttpInstance, TokenResponse, };
5
+ export default init;
@@ -0,0 +1,6 @@
1
+ import { RequestInitConfig, HttpInstance } from '../types/request.types';
2
+
3
+ /**
4
+ * 创建并初始化 HTTP 实例
5
+ */
6
+ export declare function init(config: RequestInitConfig): HttpInstance;
@@ -0,0 +1,118 @@
1
+ import { AxiosRequestConfig, AxiosResponse, CancelToken, CancelTokenSource, InternalAxiosRequestConfig } from 'axios';
2
+ import { Store } from 'pinia';
3
+
4
+ /**
5
+ * Token 刷新配置
6
+ */
7
+ export interface RefreshConfig {
8
+ /** 刷新接口域名 */
9
+ baseUrl: string;
10
+ /** 刷新接口路径 */
11
+ path: string;
12
+ /** 请求头名称(默认为 'Authorization') */
13
+ name?: string;
14
+ /** refreshToken 值(通过 pinia localStorage 存储) */
15
+ token: string;
16
+ /** refreshToken 值 添加前缀(默认为 'Bearer') */
17
+ prefix?: 'Bearer' | 'Basic' | 'Digest' | 'Token' | 'OAuth' | 'Custom';
18
+ }
19
+ /**
20
+ * 请求初始化配置
21
+ */
22
+ export interface RequestInitConfig {
23
+ /** Pinia store 实例,用于存储和获取 token */
24
+ store: Store;
25
+ /** 接口域名 */
26
+ baseUrl: string;
27
+ /** 超时时间(毫秒) */
28
+ timeout?: number;
29
+ /** 请求头信息 */
30
+ headers?: Record<string, string>;
31
+ /** Token 刷新配置 */
32
+ refresh: RefreshConfig;
33
+ }
34
+ /**
35
+ * HTTP 请求配置
36
+ */
37
+ export interface HttpRequestConfig extends Omit<AxiosRequestConfig, 'cancelToken' | 'headers'> {
38
+ /** 请求 URL */
39
+ url: string;
40
+ /** 请求方法 */
41
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
42
+ /** 查询参数 */
43
+ params?: Record<string, any>;
44
+ /** 请求体数据 */
45
+ data?: any;
46
+ /** 请求头 */
47
+ headers?: Record<string, string>;
48
+ /** 超时时间 */
49
+ timeout?: number;
50
+ /** 取消令牌(支持 CancelTokenSource 或 CancelToken) */
51
+ cancelToken?: CancelTokenSource | CancelToken;
52
+ }
53
+ /**
54
+ * HTTP 响应数据模型
55
+ */
56
+ export interface HttpResponse<T = any> extends Omit<AxiosResponse<T>, 'config'> {
57
+ data: T;
58
+ status: number;
59
+ statusText: string;
60
+ headers: Record<string, string>;
61
+ config: HttpRequestConfig;
62
+ }
63
+ /**
64
+ * HTTP 实例接口
65
+ */
66
+ export interface HttpInstance {
67
+ /**
68
+ * GET 请求
69
+ */
70
+ get<T = any>(url: string, params?: Record<string, any> | null, config?: AxiosRequestConfig): Promise<HttpResponse<T>>;
71
+ /**
72
+ * POST 请求
73
+ */
74
+ post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<HttpResponse<T>>;
75
+ /**
76
+ * PUT 请求
77
+ */
78
+ put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<HttpResponse<T>>;
79
+ /**
80
+ * PATCH 请求
81
+ */
82
+ patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<HttpResponse<T>>;
83
+ /**
84
+ * DELETE 请求
85
+ */
86
+ delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<HttpResponse<T>>;
87
+ /**
88
+ * 通用请求方法
89
+ */
90
+ request<T = any>(config: HttpRequestConfig): Promise<HttpResponse<T>>;
91
+ /**
92
+ * 创建取消令牌
93
+ */
94
+ createCancelToken(): CancelTokenSource;
95
+ /**
96
+ * 手动终止请求
97
+ */
98
+ cancelRequest(cancelToken: CancelTokenSource, message?: string): void;
99
+ }
100
+ /**
101
+ * 内部请求配置(包含刷新配置)
102
+ */
103
+ export interface InternalRequestConfig extends InternalAxiosRequestConfig {
104
+ _refreshConfig?: RefreshConfig;
105
+ _store?: Store;
106
+ _baseUrl?: string;
107
+ _retry?: boolean;
108
+ }
109
+ /**
110
+ * Token 响应数据模型
111
+ */
112
+ export interface TokenResponse {
113
+ accessToken: string;
114
+ refreshToken?: string;
115
+ tokenType?: string;
116
+ expiresIn?: number;
117
+ [key: string]: any;
118
+ }
@@ -0,0 +1,9 @@
1
+ import { InternalAxiosRequestConfig } from 'axios';
2
+ import { Store } from 'pinia';
3
+ import { InternalRequestConfig, RefreshConfig } from '../types/request.types';
4
+
5
+ /**
6
+ * 处理请求拦截器
7
+ * 自动添加 Bearer token
8
+ */
9
+ export declare function handleRequestInterceptor(config: InternalRequestConfig, store: Store, refreshConfig: RefreshConfig): InternalAxiosRequestConfig;
@@ -0,0 +1,12 @@
1
+ import { AxiosError, AxiosResponse } from 'axios';
2
+ import { RefreshConfig } from '../types/request.types';
3
+ import { Store } from 'pinia';
4
+
5
+ /**
6
+ * 处理响应拦截器(成功响应)
7
+ */
8
+ export declare function handleResponseInterceptor<T = any>(response: AxiosResponse<T>, store: Store, refreshConfig: RefreshConfig): Promise<AxiosResponse<T>>;
9
+ /**
10
+ * 处理响应错误拦截器
11
+ */
12
+ export declare function handleResponseError(error: AxiosError): Promise<any>;
@@ -0,0 +1,7 @@
1
+ import { Store } from 'pinia';
2
+ import { RefreshConfig } from '../types/request.types';
3
+
4
+ /**
5
+ * 处理 Token 刷新
6
+ */
7
+ export declare function handleTokenRefresh(refreshConfig: RefreshConfig, store: Store, refreshTokenFromResponse: string | undefined, retryFn: () => Promise<any>): Promise<any>;
@@ -0,0 +1,4 @@
1
+ import { init } from './http/services/request.service';
2
+
3
+ export type { RequestInitConfig, RefreshConfig, HttpRequestConfig, HttpResponse, HttpInstance, TokenResponse, } from './http';
4
+ export default init;
package/dist/index.js ADDED
@@ -0,0 +1,368 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import axios from "axios";
5
+ function getAccessTokenFromStore(store) {
6
+ const state = store.$state;
7
+ if (state && typeof state.accessToken === "string") {
8
+ return state.accessToken;
9
+ }
10
+ if (store.accessToken && typeof store.accessToken === "string") {
11
+ return store.accessToken;
12
+ }
13
+ try {
14
+ const stored = localStorage.getItem("accessToken");
15
+ if (stored) {
16
+ try {
17
+ return JSON.parse(stored);
18
+ } catch {
19
+ return stored;
20
+ }
21
+ }
22
+ } catch {
23
+ }
24
+ return null;
25
+ }
26
+ function handleRequestInterceptor(config, store, refreshConfig) {
27
+ if (store) {
28
+ const accessToken = getAccessTokenFromStore(store);
29
+ if (accessToken) {
30
+ const headerName = (refreshConfig == null ? void 0 : refreshConfig.name) || "Authorization";
31
+ config.headers = config.headers || {};
32
+ config.headers[headerName] = `Bearer ${accessToken}`;
33
+ }
34
+ }
35
+ return config;
36
+ }
37
+ let isRefreshing = false;
38
+ let refreshPromise = null;
39
+ let failedQueue = [];
40
+ function getRefreshTokenFromStore(store, refreshConfig, name = "refreshToken") {
41
+ const state = store.$state;
42
+ if (state && typeof state[name] === "string") {
43
+ return state[name];
44
+ }
45
+ if (store[name] && typeof store[name] === "string") {
46
+ return store[name];
47
+ }
48
+ try {
49
+ const stored = localStorage.getItem(name);
50
+ if (stored) {
51
+ try {
52
+ return JSON.parse(stored);
53
+ } catch {
54
+ return stored;
55
+ }
56
+ }
57
+ } catch {
58
+ }
59
+ if (name === "refreshToken" && refreshConfig.token) {
60
+ return refreshConfig.token;
61
+ }
62
+ return null;
63
+ }
64
+ function updateAccessTokenInStore(store, accessToken) {
65
+ const state = store.$state;
66
+ if (state && typeof state.accessToken !== "undefined") {
67
+ state.accessToken = accessToken;
68
+ }
69
+ if (typeof store.setAccessToken === "function") {
70
+ store.setAccessToken(accessToken);
71
+ }
72
+ if (typeof store.updateToken === "function") {
73
+ store.updateToken({ accessToken });
74
+ }
75
+ try {
76
+ localStorage.setItem("accessToken", JSON.stringify(accessToken));
77
+ } catch {
78
+ }
79
+ }
80
+ async function refreshTokenApi(refreshConfig, prefix, refreshToken) {
81
+ const { baseUrl, path, name = "Authorization" } = refreshConfig;
82
+ const url = path.startsWith("http") ? path : `${baseUrl}${path}`;
83
+ const response = await axios.post(
84
+ url,
85
+ {},
86
+ {
87
+ headers: {
88
+ [name]: `${prefix} ${refreshToken}`
89
+ }
90
+ }
91
+ );
92
+ return response.data;
93
+ }
94
+ async function handleTokenRefresh(refreshConfig, store, refreshTokenFromResponse, retryFn) {
95
+ let refreshToken = refreshTokenFromResponse;
96
+ if (!refreshToken) {
97
+ const tokenFromStore = getRefreshTokenFromStore(store, refreshConfig);
98
+ refreshToken = tokenFromStore || void 0;
99
+ }
100
+ let prefix = getRefreshTokenFromStore(store, refreshConfig, "prefix") || "Bearer";
101
+ if (!refreshToken) {
102
+ throw new Error("Refresh token not found");
103
+ }
104
+ if (isRefreshing && refreshPromise) {
105
+ try {
106
+ const newToken = await refreshPromise;
107
+ if (newToken.accessToken) {
108
+ updateAccessTokenInStore(store, newToken.accessToken);
109
+ }
110
+ failedQueue.forEach(({ resolve }) => {
111
+ resolve();
112
+ });
113
+ failedQueue = [];
114
+ return await retryFn();
115
+ } catch (refreshError) {
116
+ failedQueue.forEach(({ reject }) => {
117
+ reject(refreshError);
118
+ });
119
+ failedQueue = [];
120
+ throw refreshError;
121
+ }
122
+ }
123
+ isRefreshing = true;
124
+ refreshPromise = refreshTokenApi(refreshConfig, prefix, refreshToken);
125
+ try {
126
+ const newToken = await refreshPromise;
127
+ if (newToken.accessToken) {
128
+ updateAccessTokenInStore(store, newToken.accessToken);
129
+ }
130
+ if (newToken.refreshToken) {
131
+ const state = store.$state;
132
+ if (state && typeof state.refreshToken !== "undefined") {
133
+ state.refreshToken = newToken.refreshToken;
134
+ }
135
+ try {
136
+ localStorage.setItem("refreshToken", JSON.stringify(newToken.refreshToken));
137
+ } catch {
138
+ }
139
+ }
140
+ isRefreshing = false;
141
+ refreshPromise = null;
142
+ failedQueue.forEach(({ resolve }) => {
143
+ resolve();
144
+ });
145
+ failedQueue = [];
146
+ return await retryFn();
147
+ } catch (refreshError) {
148
+ isRefreshing = false;
149
+ refreshPromise = null;
150
+ failedQueue.forEach(({ reject }) => {
151
+ reject(refreshError);
152
+ });
153
+ failedQueue = [];
154
+ throw refreshError;
155
+ }
156
+ }
157
+ async function handleResponseInterceptor(response, store, refreshConfig) {
158
+ var _a, _b;
159
+ const responseData = response == null ? void 0 : response.data;
160
+ const code = responseData == null ? void 0 : responseData.code;
161
+ if ([2006, 2007, 2003, 2004, 2e3].includes(code)) {
162
+ if (!response.request.responseURL.endsWith("login") && !response.request.responseURL.endsWith("register") && !response.request.responseURL.endsWith("register/tenants")) {
163
+ window.parent.postMessage({
164
+ type: "error",
165
+ data: JSON.stringify({
166
+ code: responseData == null ? void 0 : responseData.code,
167
+ message: responseData == null ? void 0 : responseData.message,
168
+ action: "toLogin"
169
+ })
170
+ }, "*");
171
+ }
172
+ throw new Error(responseData == null ? void 0 : responseData.message);
173
+ } else if (code === 2001) {
174
+ const config = response.config;
175
+ if (!refreshConfig || !store) {
176
+ return response;
177
+ }
178
+ config._retry = true;
179
+ const newAccessToken = (_a = responseData == null ? void 0 : responseData.data) == null ? void 0 : _a.accessToken;
180
+ const refreshToken = (_b = responseData == null ? void 0 : responseData.data) == null ? void 0 : _b.refreshToken;
181
+ const retryFn = async () => {
182
+ const originalRequest = {
183
+ ...config,
184
+ headers: {
185
+ ...config.headers
186
+ }
187
+ };
188
+ if (newAccessToken) {
189
+ const headerName = (refreshConfig == null ? void 0 : refreshConfig.name) || "Authorization";
190
+ originalRequest.headers[headerName] = `Bearer ${newAccessToken}`;
191
+ }
192
+ return axios(originalRequest);
193
+ };
194
+ try {
195
+ return await handleTokenRefresh(refreshConfig, store, refreshToken, retryFn);
196
+ } catch (refreshError) {
197
+ throw refreshError;
198
+ }
199
+ } else if (code > 0) {
200
+ throw new Error(responseData == null ? void 0 : responseData.message);
201
+ }
202
+ return responseData;
203
+ }
204
+ async function handleResponseError(error) {
205
+ throw error;
206
+ }
207
+ class RequestServiceClass {
208
+ constructor() {
209
+ __publicField(this, "instance", null);
210
+ __publicField(this, "config", null);
211
+ }
212
+ /**
213
+ * 初始化 HTTP 实例
214
+ */
215
+ init(config) {
216
+ this.config = config;
217
+ this.instance = axios.create({
218
+ baseURL: config.baseUrl,
219
+ timeout: config.timeout || 1e4,
220
+ headers: config.headers || {}
221
+ });
222
+ this.instance.interceptors.request.use(
223
+ (config2) => {
224
+ const internalConfig = config2;
225
+ return handleRequestInterceptor(internalConfig, this.config.store, this.config.refresh);
226
+ },
227
+ (error) => {
228
+ return Promise.reject(error);
229
+ }
230
+ );
231
+ this.instance.interceptors.response.use(
232
+ async (response) => {
233
+ try {
234
+ return await handleResponseInterceptor(response, this.config.store, this.config.refresh);
235
+ } catch (error) {
236
+ return handleResponseError(error);
237
+ }
238
+ },
239
+ async (error) => {
240
+ return handleResponseError(error);
241
+ }
242
+ );
243
+ }
244
+ /**
245
+ * 重置 HTTP 实例
246
+ */
247
+ reset(config) {
248
+ this.config = config;
249
+ this.instance = axios.create({
250
+ baseURL: config.baseUrl,
251
+ timeout: config.timeout || 1e4,
252
+ headers: config.headers || {}
253
+ });
254
+ this.instance.interceptors.request.use(
255
+ (config2) => {
256
+ const internalConfig = config2;
257
+ return handleRequestInterceptor(internalConfig, this.config.store, this.config.refresh);
258
+ },
259
+ (error) => {
260
+ return Promise.reject(error);
261
+ }
262
+ );
263
+ this.instance.interceptors.response.use(
264
+ async (response) => {
265
+ return handleResponseInterceptor(response, this.config.store, this.config.refresh);
266
+ },
267
+ async (error) => {
268
+ return handleResponseError(error);
269
+ }
270
+ );
271
+ }
272
+ /**
273
+ * 获取 Axios 实例
274
+ */
275
+ getInstance() {
276
+ if (!this.instance) {
277
+ throw new Error("HTTP instance not initialized. Please call init() first.");
278
+ }
279
+ return this.instance;
280
+ }
281
+ /**
282
+ * 通用请求方法
283
+ */
284
+ async request(config) {
285
+ const instance = this.getInstance();
286
+ const axiosConfig = {
287
+ ...config,
288
+ // 如果 cancelToken 是 CancelTokenSource,提取其 token 属性
289
+ cancelToken: config.cancelToken && "token" in config.cancelToken ? config.cancelToken.token : config.cancelToken
290
+ };
291
+ const response = await instance.request(axiosConfig);
292
+ return response;
293
+ }
294
+ /**
295
+ * GET 请求
296
+ */
297
+ async get(url, params, config) {
298
+ return this.request({
299
+ ...config,
300
+ url,
301
+ method: "GET",
302
+ params: params ?? void 0
303
+ });
304
+ }
305
+ /**
306
+ * POST 请求
307
+ */
308
+ async post(url, data, config) {
309
+ return this.request({
310
+ ...config,
311
+ url,
312
+ method: "POST",
313
+ data
314
+ });
315
+ }
316
+ /**
317
+ * PUT 请求
318
+ */
319
+ async put(url, data, config) {
320
+ return this.request({
321
+ ...config,
322
+ url,
323
+ method: "PUT",
324
+ data
325
+ });
326
+ }
327
+ /**
328
+ * PATCH 请求
329
+ */
330
+ async patch(url, data, config) {
331
+ return this.request({
332
+ ...config,
333
+ url,
334
+ method: "PATCH",
335
+ data
336
+ });
337
+ }
338
+ /**
339
+ * DELETE 请求
340
+ */
341
+ async delete(url, config) {
342
+ return this.request({
343
+ ...config,
344
+ url,
345
+ method: "DELETE"
346
+ });
347
+ }
348
+ /**
349
+ * 创建取消令牌
350
+ */
351
+ createCancelToken() {
352
+ return axios.CancelToken.source();
353
+ }
354
+ /**
355
+ * 手动终止请求
356
+ */
357
+ cancelRequest(cancelToken, message) {
358
+ cancelToken.cancel(message);
359
+ }
360
+ }
361
+ function init(config) {
362
+ const service = new RequestServiceClass();
363
+ service.init(config);
364
+ return service;
365
+ }
366
+ export {
367
+ init as default
368
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@clownlee/http",
3
+ "version": "1.0.1",
4
+ "description": "公共 HTTP 请求库,提供统一的 HTTP 请求能力和自动 token 刷新功能",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "vite build",
20
+ "dev": "vite build --watch",
21
+ "type-check": "vue-tsc --noEmit"
22
+ },
23
+ "keywords": [
24
+ "fly",
25
+ "business",
26
+ "package"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "axios": "^1.6.0",
32
+ "pinia": "^2.1.7",
33
+ "vue": "^3.5.25"
34
+ },
35
+ "peerDependencies": {
36
+ "@element-plus/icons-vue": "^2.3.0",
37
+ "element-plus": "^2.12.0",
38
+ "vue-router": "^4.2.0"
39
+ },
40
+ "devDependencies": {
41
+ "@element-plus/icons-vue": "^2.3.0",
42
+ "@unocss/preset-uno": "^0.58.0",
43
+ "@unocss/vite": "^0.58.0",
44
+ "@types/jquery": "^3.5.33",
45
+ "@vitejs/plugin-vue": "^5.0.0",
46
+ "element-plus": "^2.12.0",
47
+ "sass": "^1.96.0",
48
+ "terser": "^5.31.0",
49
+ "typescript": "^5.3.0",
50
+ "unocss": "^0.58.0",
51
+ "unplugin-auto-import": "^0.17.0",
52
+ "unplugin-vue-components": "^0.27.0",
53
+ "vite": "^5.0.0",
54
+ "vite-plugin-css-injected-by-js": "^3.5.0",
55
+ "vite-plugin-dts": "^3.9.0",
56
+ "vue-router": "^4.2.0",
57
+ "vue-tsc": "^2.1.10"
58
+ }
59
+ }