Przeglądaj źródła

Add runtime configuration for API keys, update dependencies for crypto-js and uuid, and enhance login functionality with Google Sign-In

0es 4 miesięcy temu
rodzic
commit
33f592311f

+ 118 - 0
app/api/example.ts

@@ -0,0 +1,118 @@
+/**
+ * Example API module
+ * This file demonstrates how to organize your API endpoints
+ */
+
+import { http } from '~/utils/request'
+import type { PaginationParams, PaginationResponse } from '~/types/api'
+
+// Define your API interfaces
+export interface User {
+  id: number
+  username: string
+  email: string
+  avatar?: string
+  createdAt: string
+}
+
+export interface LoginParams {
+  username: string
+  password: string
+}
+
+export interface LoginResponse {
+  token: string
+  user: User
+}
+
+/**
+ * User API
+ */
+export const userApi = {
+  /**
+   * User login
+   */
+  login(params: LoginParams) {
+    return http.post<LoginResponse>('/user/login', params)
+  },
+
+  /**
+   * Get current user info
+   */
+  getUserInfo() {
+    return http.get<User>('/user/info')
+  },
+
+  /**
+   * Update user info
+   */
+  updateUserInfo(data: Partial<User>) {
+    return http.put<User>('/user/info', data)
+  },
+
+  /**
+   * Get user list with pagination
+   */
+  getUserList(params: PaginationParams) {
+    return http.get<PaginationResponse<User>>('/user/list', params)
+  },
+
+  /**
+   * Logout
+   */
+  logout() {
+    return http.post('/user/logout')
+  },
+}
+
+/**
+ * Example: How to use in components
+ *
+ * Method 1: Direct use with http
+ * ```typescript
+ * import { userApi } from '~/api/example'
+ *
+ * const login = async () => {
+ *   try {
+ *     const result = await userApi.login({ username: 'test', password: '123456' })
+ *     console.log(result)
+ *   } catch (error) {
+ *     console.error(error)
+ *   }
+ * }
+ * ```
+ *
+ * Method 2: Use with useApi composable (with loading state)
+ * ```typescript
+ * import { userApi } from '~/api/example'
+ *
+ * const { loading, error, request } = useApi()
+ *
+ * const login = async () => {
+ *   const result = await request(() => userApi.login({
+ *     username: 'test',
+ *     password: '123456'
+ *   }))
+ *
+ *   if (result) {
+ *     console.log('Login success:', result)
+ *   }
+ * }
+ * ```
+ *
+ * Method 3: Use useApi methods directly
+ * ```typescript
+ * const { loading, error, post } = useApi()
+ *
+ * const login = async () => {
+ *   const result = await post('/user/login', {
+ *     username: 'test',
+ *     password: '123456'
+ *   })
+ *
+ *   if (result) {
+ *     console.log('Login success:', result)
+ *   }
+ * }
+ * ```
+ */

+ 103 - 0
app/api/user.ts

@@ -0,0 +1,103 @@
+/**
+ * User API module
+ */
+
+import { http } from '~/utils/request'
+import type {
+  UserInfoVO,
+  UserLoginVO,
+  UserThirdLoginDTO,
+} from '~/types/api'
+
+/**
+ * User API endpoints
+ */
+export const userApi = {
+  /**
+   * Get my user information
+   * @returns User info
+   */
+  getMyInfo() {
+    return http.post<UserInfoVO>('/user/my/info')
+  },
+
+  /**
+   * Google login
+   * @param params - Login params containing Google auth data
+   * @returns Login response with user info and token
+   */
+  loginWithGoogle(params: UserThirdLoginDTO) {
+    return http.post<UserLoginVO>('/user/login/google/enter', params)
+  },
+
+  /**
+   * User logout
+   * @returns Empty response
+   */
+  logout() {
+    return http.post<object>('/user/logout')
+  },
+}
+
+/**
+ * Usage examples:
+ *
+ * 1. Get user info:
+ * ```typescript
+ * import { userApi } from '~/api/user'
+ *
+ * const getUserInfo = async () => {
+ *   try {
+ *     const userInfo = await userApi.getMyInfo()
+ *     console.log(userInfo)
+ *   } catch (error) {
+ *     console.error('Failed to get user info:', error)
+ *   }
+ * }
+ * ```
+ *
+ * 2. Google login:
+ * ```typescript
+ * import { userApi } from '~/api/user'
+ *
+ * const handleGoogleLogin = async (googleData: string) => {
+ *   try {
+ *     const loginResult = await userApi.loginWithGoogle({ data: googleData })
+ *     console.log('Login success:', loginResult)
+ *     // Save token and user info
+ *   } catch (error) {
+ *     console.error('Login failed:', error)
+ *   }
+ * }
+ * ```
+ *
+ * 3. Logout:
+ * ```typescript
+ * import { userApi } from '~/api/user'
+ *
+ * const handleLogout = async () => {
+ *   try {
+ *     await userApi.logout()
+ *     console.log('Logout success')
+ *     // Clear local storage and redirect to login
+ *   } catch (error) {
+ *     console.error('Logout failed:', error)
+ *   }
+ * }
+ * ```
+ *
+ * 4. Use with useApi composable for reactive state:
+ * ```typescript
+ * import { userApi } from '~/api/user'
+ * import { useApi } from '~/composables/useApi'
+ *
+ * const { loading, error, request } = useApi()
+ *
+ * const getUserInfo = async () => {
+ *   const result = await request(() => userApi.getMyInfo())
+ *   if (result) {
+ *     console.log('User info:', result)
+ *   }
+ * }
+ * ```
+ */

+ 95 - 0
app/composables/useApi.ts

@@ -0,0 +1,95 @@
+import { http } from '~/utils/request'
+
+/**
+ * API composable for making HTTP requests
+ * Provides reactive state management for API calls
+ */
+export const useApi = () => {
+  const loading = ref(false)
+  const error = ref<string | null>(null)
+
+  /**
+   * Wrapper for making API requests with loading state
+   */
+  const request = async <T = unknown>(
+    requestFn: () => Promise<T>,
+  ): Promise<T | null> => {
+    loading.value = true
+    error.value = null
+
+    try {
+      const result = await requestFn()
+      return result
+    }
+    catch (err: unknown) {
+      const message = err instanceof Error ? err.message : 'Request failed'
+      error.value = message
+      console.error('API request error:', err)
+      return null
+    }
+    finally {
+      loading.value = false
+    }
+  }
+
+  /**
+   * GET request with loading state
+   */
+  const get = async <T = unknown>(
+    url: string,
+    params?: unknown,
+  ): Promise<T | null> => {
+    return request(() => http.get<T>(url, params))
+  }
+
+  /**
+   * POST request with loading state
+   */
+  const post = async <T = unknown>(
+    url: string,
+    data?: unknown,
+  ): Promise<T | null> => {
+    return request(() => http.post<T>(url, data))
+  }
+
+  /**
+   * PUT request with loading state
+   */
+  const put = async <T = unknown>(
+    url: string,
+    data?: unknown,
+  ): Promise<T | null> => {
+    return request(() => http.put<T>(url, data))
+  }
+
+  /**
+   * DELETE request with loading state
+   */
+  const del = async <T = unknown>(
+    url: string,
+    params?: unknown,
+  ): Promise<T | null> => {
+    return request(() => http.delete<T>(url, params))
+  }
+
+  /**
+   * PATCH request with loading state
+   */
+  const patch = async <T = unknown>(
+    url: string,
+    data?: unknown,
+  ): Promise<T | null> => {
+    return request(() => http.patch<T>(url, data))
+  }
+
+  return {
+    loading: readonly(loading),
+    error: readonly(error),
+    request,
+    get,
+    post,
+    put,
+    delete: del,
+    patch,
+  }
+}

+ 108 - 0
app/composables/useUser.ts

@@ -0,0 +1,108 @@
+/**
+ * User composable - provides user-related functionality
+ */
+
+import { userApi } from '~/api/user'
+import type { UserInfoVO } from '~/types/api'
+
+export const useUser = () => {
+  const userInfo = ref<UserInfoVO | null>(null)
+  const loading = ref(false)
+  const error = ref<string | null>(null)
+
+  /**
+   * Fetch current user info
+   */
+  const fetchUserInfo = async () => {
+    loading.value = true
+    error.value = null
+
+    try {
+      const result = await userApi.getMyInfo()
+      userInfo.value = result
+      return result
+    }
+    catch (err) {
+      const message = err instanceof Error ? err.message : 'Failed to fetch user info'
+      error.value = message
+      console.error('fetchUserInfo error:', err)
+      return null
+    }
+    finally {
+      loading.value = false
+    }
+  }
+
+  /**
+   * Google login
+   */
+  const loginWithGoogle = async (googleData: string) => {
+    loading.value = true
+    error.value = null
+
+    try {
+      const result = await userApi.loginWithGoogle({ data: googleData })
+
+      if (result) {
+        // Save token using useAuth
+        const { setAuth } = useAuth()
+        setAuth(result.token)
+
+        // Update user info
+        userInfo.value = {
+          userProfile: result.userProfile,
+        }
+
+        return result
+      }
+
+      return null
+    }
+    catch (err) {
+      const message = err instanceof Error ? err.message : 'Login failed'
+      error.value = message
+      console.error('loginWithGoogle error:', err)
+      return null
+    }
+    finally {
+      loading.value = false
+    }
+  }
+
+  /**
+   * Logout user
+   */
+  const logoutUser = async () => {
+    loading.value = true
+    error.value = null
+
+    try {
+      await userApi.logout()
+
+      // Clear token and user info
+      const { logout: clearAuth } = useAuth()
+      clearAuth()
+      userInfo.value = null
+
+      return true
+    }
+    catch (err) {
+      const message = err instanceof Error ? err.message : 'Logout failed'
+      error.value = message
+      console.error('logoutUser error:', err)
+      return false
+    }
+    finally {
+      loading.value = false
+    }
+  }
+
+  return {
+    userInfo: readonly(userInfo),
+    loading: readonly(loading),
+    error: readonly(error),
+    fetchUserInfo,
+    loginWithGoogle,
+    logoutUser,
+  }
+}

+ 8 - 2
app/pages/login.vue

@@ -14,10 +14,16 @@ definePageMeta({
 
 const router = useRouter()
 const { setAuth } = useAuth()
+const { loginWithGoogle } = useUser()
 
 // Google SignIn
-const handleOnSuccess = (response: AuthCodeFlowSuccessResponse) => {
-  console.log('Access Token: ', response.access_token)
+const handleOnSuccess = async (response: AuthCodeFlowSuccessResponse) => {
+  const result = await loginWithGoogle(response.access_token)
+
+  if (result) {
+    setAuth(result.token)
+    router.push('/')
+  }
 }
 
 const handleOnError = (errorResponse: AuthCodeFlowErrorResponse) => {

+ 37 - 0
app/types/api/common.ts

@@ -0,0 +1,37 @@
+// API response type definitions
+export interface ApiResponse<T = unknown> {
+  code: number
+  data: T
+  message: string
+}
+
+// API error type
+export interface ApiError {
+  code: number
+  message: string
+  details?: unknown
+}
+
+// Request config type
+export interface RequestConfig {
+  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
+  headers?: Record<string, string>
+  params?: Record<string, unknown>
+  data?: unknown
+  timeout?: number
+  needAuth?: boolean
+}
+
+// Pagination request
+export interface PaginationParams {
+  page: number
+  pageSize: number
+}
+
+// Pagination response
+export interface PaginationResponse<T> {
+  list: T[]
+  total: number
+  page: number
+  pageSize: number
+}

+ 2 - 0
app/types/api/index.ts

@@ -0,0 +1,2 @@
+export * from './common'
+export * from './user'

+ 28 - 0
app/types/api/user.ts

@@ -0,0 +1,28 @@
+// User Profile
+export interface UserProfileVO {
+  id: string
+  userNo: string
+  avatar: string
+  nickname: string
+  age: number
+  gender: number // 0=unknown, 1=male, 2=female
+  intro: string
+  playmate: boolean
+}
+
+// User Info Response
+export interface UserInfoVO {
+  userProfile: UserProfileVO
+}
+
+// User Login Response
+export interface UserLoginVO {
+  userProfile: UserProfileVO
+  createAt: number
+  token: string
+}
+
+// Google Login Request
+export interface UserThirdLoginDTO {
+  data: string
+}

+ 340 - 0
app/utils/request.ts

@@ -0,0 +1,340 @@
+import type { FetchOptions, FetchContext } from 'ofetch'
+import type { ApiResponse } from '~/types/api'
+import { MD5 } from 'crypto-js'
+import { v4 as uuidv4 } from 'uuid'
+
+const runtimeConfig = useRuntimeConfig()
+
+// API base configuration
+const API_CONFIG = {
+  baseURL: runtimeConfig.public.apiBase || '/api',
+  timeout: 30000,
+  secret: runtimeConfig.public.apiSecret || 'your-secret-key',
+  identity: runtimeConfig.public.apiIdentity || 'web',
+}
+
+// Device and app information
+const DEVICE_INFO = {
+  udid: '', // Will be initialized
+  app: API_CONFIG.identity as string,
+  device: '',
+  platform: 3,
+  channel: 'official',
+  api: 1,
+  version: '1.0.0',
+  network: 'wifi',
+}
+
+/**
+ * Initialize device info
+ */
+const initDeviceInfo = () => {
+  if (import.meta.client) {
+    // Generate or get UDID from localStorage
+    let udid = localStorage.getItem('device_udid')
+    if (!udid) {
+      udid = generateUUID()
+      localStorage.setItem('device_udid', udid)
+    }
+    DEVICE_INFO.udid = udid
+
+    // Get device info
+    DEVICE_INFO.device = navigator.userAgent
+
+    // Detect network type
+    interface NetworkInformation {
+      effectiveType?: string
+    }
+    const nav = navigator as Navigator & {
+      connection?: NetworkInformation
+      mozConnection?: NetworkInformation
+      webkitConnection?: NetworkInformation
+    }
+    const connection = nav.connection || nav.mozConnection || nav.webkitConnection
+    if (connection) {
+      DEVICE_INFO.network = connection.effectiveType || 'wifi'
+    }
+  }
+}
+
+/**
+ * Generate UUID
+ */
+const generateUUID = (): string => {
+  return `${uuidv4()}${uuidv4()}`.replace('-', '')
+}
+
+/**
+ * Generate request ID
+ */
+const generateRequestId = (): string => {
+  return uuidv4().replace('-', '')
+}
+
+/**
+ * Generate sign
+ */
+const generateSign = (id: string, time: number, token: string, body?: unknown): string => {
+  const { udid, app, device, platform, channel, api, version, network } = DEVICE_INFO
+  const bodyStr = body ? JSON.stringify(body) : ''
+
+  const signStr = `${id}${udid}${app}${device}${platform}${channel}${api}${version}${network}${time}${token}${API_CONFIG.secret}${bodyStr}`
+
+  return MD5(signStr).toString()
+}
+
+/**
+ * Create request instance with interceptors
+ */
+export const createRequest = () => {
+  const { getToken } = useAuth()
+
+  // Initialize device info on client side
+  if (import.meta.client && !DEVICE_INFO.udid) {
+    initDeviceInfo()
+  }
+
+  /**
+   * Request interceptor - add auth token and other headers
+   */
+  const onRequest = (ctx: FetchContext): void => {
+    const { options } = ctx
+
+    // Add base URL
+    if (!options.baseURL) {
+      options.baseURL = API_CONFIG.baseURL
+    }
+
+    // Initialize headers as a plain object
+    const headers: Record<string, string> = {}
+
+    // Copy existing headers if any
+    if (options.headers) {
+      const existingHeaders = options.headers as unknown as Record<string, string>
+      Object.assign(headers, existingHeaders)
+    }
+
+    // Generate request-specific values
+    const requestId = generateRequestId()
+    const time = Date.now()
+    const token = getToken() || ''
+
+    // Add custom API headers
+    headers['id'] = requestId
+    headers['udid'] = DEVICE_INFO.udid
+    headers['app'] = DEVICE_INFO.app
+    headers['device'] = DEVICE_INFO.device
+    headers['platform'] = String(DEVICE_INFO.platform)
+    headers['channel'] = DEVICE_INFO.channel
+    headers['api'] = String(DEVICE_INFO.api)
+    headers['version'] = DEVICE_INFO.version
+    headers['network'] = DEVICE_INFO.network
+    headers['time'] = String(time)
+
+    // Add token if exists
+    if (token) {
+      headers['token'] = token
+    }
+
+    // Generate and add signature
+    const body = options.body
+    const sign = generateSign(requestId, time, token, body)
+    headers['sign'] = sign
+
+    // Add default content type if not already set
+    if (!headers['Content-Type']) {
+      headers['Content-Type'] = 'application/json'
+    }
+
+    // Set headers (use type assertion to bypass strict type checking)
+    // @ts-expect-error - Headers type mismatch between HeadersInit and Headers
+    options.headers = headers
+  }
+
+  /**
+   * Response interceptor - handle success response
+   */
+  const onResponse = (ctx: FetchContext): void => {
+    if (!ctx.response) return
+
+    const data = ctx.response._data as ApiResponse
+
+    // If backend returns code field, check it
+    if (data && typeof data === 'object' && 'code' in data) {
+      // Success codes (you can adjust these based on your API)
+      if (data.code === 0 || data.code === 200) {
+        ctx.response._data = data.data
+        return
+      }
+
+      // Handle business errors
+      handleApiError(data.code, data.message)
+      throw new Error(data.message || 'Request failed')
+    }
+  }
+
+  /**
+   * Response error interceptor - handle HTTP errors
+   */
+  const onResponseError = (ctx: FetchContext): Promise<never> => {
+    const { error } = ctx
+    const response = ctx.response
+
+    // Handle HTTP status errors
+    if (response) {
+      const status = response.status
+      const data = response._data as { message?: string }
+
+      switch (status) {
+        case 401:
+          handleUnauthorized()
+          break
+        case 403:
+          showError('No permission to access this resource')
+          break
+        case 404:
+          showError('Resource not found')
+          break
+        case 500:
+          showError('Server error, please try again later')
+          break
+        default:
+          showError(data?.message || error?.message || 'Request failed')
+      }
+    }
+    else {
+      // Network error or timeout
+      const message = error?.message || ''
+      if (message.includes('timeout')) {
+        showError('Request timeout, please try again')
+      }
+      else if (message.includes('Network Error')) {
+        showError('Network error, please check your connection')
+      }
+      else {
+        showError(message || 'Unknown error occurred')
+      }
+    }
+
+    return Promise.reject(error)
+  }
+
+  // Create fetch instance
+  const request = $fetch.create({
+    baseURL: API_CONFIG.baseURL,
+    timeout: API_CONFIG.timeout,
+    onRequest,
+    onResponse,
+    onResponseError,
+  })
+
+  return request
+}
+
+/**
+ * Handle API business errors
+ */
+function handleApiError(code: number, message: string) {
+  // You can handle specific error codes here
+  switch (code) {
+    case 401:
+      handleUnauthorized()
+      break
+    case 403:
+      showError('No permission')
+      break
+    default:
+      if (message) {
+        showError(message)
+      }
+  }
+}
+
+/**
+ * Handle unauthorized access
+ */
+function handleUnauthorized() {
+  const { logout } = useAuth()
+  logout()
+
+  // Redirect to login page
+  if (import.meta.client) {
+    showError('Login expired, please login again')
+    navigateTo('/login')
+  }
+}
+
+/**
+ * Show error message
+ */
+function showError(message: string) {
+  if (import.meta.client) {
+    // You can replace this with your preferred notification library
+    // For example, using Vant's showToast
+    console.error(message)
+    // showToast({ message, type: 'fail' })
+  }
+}
+
+// Export default request instance
+export const request = createRequest()
+
+/**
+ * HTTP request methods wrapper
+ */
+export const http = {
+  /**
+   * GET request
+   */
+  get<T = unknown>(url: string, params?: unknown, options?: FetchOptions): Promise<T> {
+    return request(url, {
+      ...options,
+      method: 'GET',
+      params: params as Record<string, unknown>,
+    })
+  },
+
+  /**
+   * POST request
+   */
+  post<T = unknown>(url: string, data?: unknown, options?: FetchOptions): Promise<T> {
+    return request(url, {
+      ...options,
+      method: 'POST',
+      body: data as BodyInit,
+    })
+  },
+
+  /**
+   * PUT request
+   */
+  put<T = unknown>(url: string, data?: unknown, options?: FetchOptions): Promise<T> {
+    return request(url, {
+      ...options,
+      method: 'PUT',
+      body: data as BodyInit,
+    })
+  },
+
+  /**
+   * DELETE request
+   */
+  delete<T = unknown>(url: string, params?: unknown, options?: FetchOptions): Promise<T> {
+    return request(url, {
+      ...options,
+      method: 'DELETE',
+      params: params as Record<string, unknown>,
+    })
+  },
+
+  /**
+   * PATCH request
+   */
+  patch<T = unknown>(url: string, data?: unknown, options?: FetchOptions): Promise<T> {
+    return request(url, {
+      ...options,
+      method: 'PATCH',
+      body: data as BodyInit,
+    })
+  },
+}

+ 7 - 0
nuxt.config.ts

@@ -9,6 +9,13 @@ export default defineNuxtConfig({
   ],
   devtools: { enabled: true },
   css: ['./app/assets/css/main.css'],
+  runtimeConfig: {
+    public: {
+      apiBase: process.env.NUXT_PUBLIC_API_BASE,
+      apiSecret: process.env.NUXT_PUBLIC_API_SECRET,
+      apiIdentity: process.env.NUXT_PUBLIC_API_IDENTITY,
+    },
+  },
   compatibilityDate: '2025-07-15',
   vite: {
     plugins: [

+ 4 - 0
package.json

@@ -13,8 +13,10 @@
   },
   "dependencies": {
     "@vant/nuxt": "^1.0.7",
+    "crypto-js": "^4.2.0",
     "nuxt": "^4.2.1",
     "tailwindcss": "^4.1.17",
+    "uuid": "^13.0.0",
     "vant": "^4.9.21",
     "vue": "^3.5.24",
     "vue-router": "^4.6.3",
@@ -23,6 +25,8 @@
   "devDependencies": {
     "@nuxt/eslint": "1.10.0",
     "@tailwindcss/vite": "^4.1.17",
+    "@types/crypto-js": "^4.2.2",
+    "@types/node": "^24.10.1",
     "eslint": "^9.0.0",
     "sass": "^1.94.0",
     "typescript": "^5.9.3",

+ 27 - 0
yarn.lock

@@ -1734,6 +1734,11 @@
   dependencies:
     tslib "^2.4.0"
 
+"@types/crypto-js@^4.2.2":
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.2.tgz#771c4a768d94eb5922cc202a3009558204df0cea"
+  integrity sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==
+
 "@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@^1.0.8":
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
@@ -1744,6 +1749,13 @@
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
   integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
 
+"@types/node@^24.10.1":
+  version "24.10.1"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01"
+  integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==
+  dependencies:
+    undici-types "~7.16.0"
+
 "@types/parse-path@^7.0.0":
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/@types/parse-path/-/parse-path-7.1.0.tgz#1bdddfe4fb2038e76c7e622234a97d6a050a1be3"
@@ -2729,6 +2741,11 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6:
   dependencies:
     uncrypto "^0.1.3"
 
+crypto-js@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
+  integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
+
 css-declaration-sorter@^7.2.0:
   version "7.3.0"
   resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz#edc45c36bcdfea0788b1d4452829f142ef1c4a4a"
@@ -6019,6 +6036,11 @@ unctx@^2.4.1:
     magic-string "^0.30.17"
     unplugin "^2.1.0"
 
+undici-types@~7.16.0:
+  version "7.16.0"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"
+  integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
+
 unenv@^2.0.0-rc.23, unenv@^2.0.0-rc.24:
   version "2.0.0-rc.24"
   resolved "https://registry.yarnpkg.com/unenv/-/unenv-2.0.0-rc.24.tgz#dd0035c3e93fedfa12c8454e34b7f17fe83efa2e"
@@ -6218,6 +6240,11 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1:
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
 
+uuid@^13.0.0:
+  version "13.0.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8"
+  integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==
+
 vant@^4.9.21:
   version "4.9.21"
   resolved "https://registry.yarnpkg.com/vant/-/vant-4.9.21.tgz#c345da47beb1390600f3a7cdbde27854824e4482"