Selaa lähdekoodia

添加路由鉴权,封装axios

yangg 1 vuosi sitten
vanhempi
sitoutus
89cf73527e

+ 4 - 1
package.json

@@ -33,12 +33,14 @@
     "maska": "^1.5.0",
     "path": "^0.12.7",
     "pinia": "^2.0.22",
+    "qs": "^6.12.1",
     "sweetalert2": "^11.5.1",
     "swiper": "^8.4.4",
     "tippy.vue": "^3.2.1",
     "ts-md5": "^1.3.1",
     "uuid": "^9.0.1",
     "vant": "^4.9.0",
+    "vee-validate": "^4.5.8",
     "vue": "^3.2.37",
     "vue-clipboard3": "^2.0.0",
     "vue-countup-v3": "^1.0.14",
@@ -57,7 +59,8 @@
     "vue3-perfect-scrollbar": "^1.6.0",
     "vue3-popper": "^1.5.0",
     "vue3-quill": "^0.2.9",
-    "vue3-uuid": "^1.0.0"
+    "vue3-uuid": "^1.0.0",
+    "yup": "^1.4.0"
   },
   "devDependencies": {
     "@intlify/vite-plugin-vue-i18n": "^6.0.3",

+ 1 - 1
src/api/api.ts

@@ -1,4 +1,4 @@
-import { request } from './axios'
+import { request } from '@/utils/axios'
 import { Md5 } from "ts-md5";
 /**
  * @description -封装User类型的接口方法

+ 0 - 70
src/api/axios.ts

@@ -1,70 +0,0 @@
-import axios from 'axios';
-import { showMessage } from './status';   // 引入状态码文件
-import { ElMessage } from 'element-plus'  // 引入el 提示框,这个项目里用什么组件库这里引什么
-
-// 设置接口超时时间
-axios.defaults.timeout = 60000;
-
-// 请求地址,这里是动态赋值的的环境变量,下一篇会细讲,这里跳过
-// @ts-ignore
-console.log(import.meta.env.VITE_API_URL);
-axios.defaults.baseURL = 'http://192.168.8.103:9999'//import.meta.env.VITE_API_URL;   
-
-//http request 拦截器
-axios.interceptors.request.use(
-  config => {
-  // 配置请求头
-    config.headers = {
-      //'Content-Type':'application/x-www-form-urlencoded',   // 传参方式表单
-      'Content-Type':'application/json;charset=UTF-8',        // 传参方式json
-      'token':''              // 这里自定义配置,这里传的是token
-    };
-    return config;
-  },
-  error => {
-    return Promise.reject(error);
-  }
-);
-
-//http response 拦截器
-axios.interceptors.response.use(
-  response => {
-    return response;
-  },
-  error => {
-    const {response} = error;
-    if (response) {
-      // 请求已发出,但是不在2xx的范围
-      showMessage(response.status);           // 传入响应码,匹配响应码对应信息
-      return Promise.reject(response.data);
-    } else {
-      ElMessage.warning('网络连接异常,请稍后再试!');
-    }
-  }
-);
-
-// 封装 GET POST 请求并导出
-export function request(url='',params={},type='POST'){
-//设置 url params type 的默认值
-return new Promise((resolve,reject)=>{
-  let promise
-  if( type.toUpperCase()==='GET' ){
-    promise = axios({
-      url,
-      params
-    })
-  }else if( type.toUpperCase()=== 'POST' ){
-    promise = axios({
-      method:'POST',
-      url,
-      data:params
-    })
-  }
-  //处理返回
-  promise.then(res=>{
-    resolve(res)
-  }).catch(err=>{
-    reject(err)
-  })
-})
-}

+ 120 - 24
src/router/index.ts

@@ -2,196 +2,292 @@ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
 import { useAppStore } from '@/stores/index';
 import appSetting from '@/app-setting';
 
-/*import HomeView from '../views/index.vue';*/
- import HomeView from '../views/auth/boxed-signin.vue'; 
+import HomeView from '../views/index.vue';
+/*import HomeView from '../views/auth/boxed-signin.vue';*/
 
 const routes: RouteRecordRaw[] = [
     // dashboard
-    { path: '/', name: 'home', component: HomeView },
+    { path: '/', name: 'home', component: HomeView ,meta: {
+        requiresAuth: true
+    },},
+    {
+        path: '/dashboard',
+        name: 'dashboard',
+        meta: {
+            requiresAuth: true
+        },
+        component: () => import(/* webpackChunkName: "apps-scrumboard" */ '../views/apps/scrumboard.vue'),
+    },
     {
         path: '/apps/scrumboard',
         name: 'scrumboard',
         component: () => import(/* webpackChunkName: "apps-scrumboard" */ '../views/apps/scrumboard.vue'),
     },
-   
+    {
+        path: '/login',
+        name: 'login',
+        meta: {
+            requiresAuth: false
+        },
+        component: () => import(/* webpackChunkName: "apps-scrumboard" */ '../views/auth/boxed-signin.vue'),
+    },
 
     // apps
     {
         path: '/apps/applist',
         name: 'applist',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "apps-chat" */ '../views/apps/applist.vue'),
     },
     {
         path: '/apps/custom',
         name: 'custom',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "apps-chat" */ '../views/apps/custom.vue'),
     },
     {
         path: '/apps/stock',
         name: 'stock',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "apps-chat" */ '../views/apps/stock.vue'),
     },
     {
         path: '/apps/order',
         name: 'order',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "apps-chat" */ '../views/apps/order.vue'),
     },
     {
         path: '/apps/goods',
         name: 'goods',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "apps-chat" */ '../views/apps/goods.vue'),
     },
-    
+
 
     //user
     {
         path: '/users/contacts',
         name: 'contacts',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "apps-contacts" */ '../views/users/contacts.vue'),
     },
-    
-        
+
+
     {
-            path: '/users/profile',
-            name: 'profile',
-            component: () => import(/* webpackChunkName: "users-profile" */ '../views/users/profile.vue'),
+        path: '/users/profile',
+        name: 'profile',
+        meta: {
+            requiresAuth: true
+        },
+        component: () => import(/* webpackChunkName: "users-profile" */ '../views/users/profile.vue'),
     },
 
     {
         path: '/users/user-account-settings',
         name: 'user-account-settings',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "users-profile" */ '../views/users/user-account-settings.vue'),
     },
     // components
     {
         path: '/components/tabs',
         name: 'tabs',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-tabs" */ '../views/components/tabs.vue'),
     },
     {
         path: '/components/accordions',
         name: 'accordions',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-accordions" */ '../views/components/accordions.vue'),
     },
     {
         path: '/components/modals',
         name: 'modals',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-modals" */ '../views/components/modals.vue'),
     },
     {
         path: '/components/cards',
         name: 'cards',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-cards" */ '../views/components/cards.vue'),
     },
     {
         path: '/components/carousel',
         name: 'carousel',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-carousel" */ '../views/components/carousel.vue'),
     },
     {
         path: '/components/countdown',
         name: 'countdown',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-countdown" */ '../views/components/countdown.vue'),
     },
     {
         path: '/components/counter',
         name: 'counter',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-counter" */ '../views/components/counter.vue'),
     },
     {
         path: '/components/sweetalert',
         name: 'sweetalert',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-sweetalert" */ '../views/components/sweetalert.vue'),
     },
     {
         path: '/components/timeline',
         name: 'timeline',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-timeline" */ '../views/components/timeline.vue'),
     },
     {
         path: '/components/notifications',
         name: 'notifications',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-notifications" */ '../views/components/notifications.vue'),
     },
     {
         path: '/components/media-object',
         name: 'media-object',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-media-object" */ '../views/components/media-object.vue'),
     },
     {
         path: '/components/list-group',
         name: 'list-group',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-list-group" */ '../views/components/list-group.vue'),
     },
     {
         path: '/components/pricing-table',
         name: 'pricing-table',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-pricing-table" */ '../views/components/pricing-table.vue'),
     },
     {
         path: '/components/lightbox',
         name: 'lightbox',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "components-lightbox" */ '../views/components/lightbox.vue'),
     },
 
-   
+
     //charts
     {
         path: '/charts',
         name: 'charts',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "charts" */ '../views/charts.vue'),
     },
 
-    
 
-    
+
+
     //auth
     {
         path: '/auth/cover-login',
         name: 'cover-login',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "forms-clipboard" */ '../views/auth/cover-login.vue'),
     },
     {
         path: '/auth/boxed-password-reset',
         name: 'boxed-password-reset',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "forms-clipboard" */ '../views/auth/boxed-password-reset.vue'),
     },
     {
         path: '/auth/boxed-signin',
         name: 'boxed-signin',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "forms-clipboard" */ '../views/auth/boxed-signin.vue'),
     },
-    
+
     {
         path: '/auth/cover-password-reset',
         name: 'cover-password-reset',
+        meta: {
+            requiresAuth: true
+        },
         component: () => import(/* webpackChunkName: "forms-clipboard" */ '../views/auth/cover-password-reset.vue'),
     },
-    
+
 ];
 
 const router = createRouter({
     history: createWebHistory(),
-    linkExactActiveClass: 'active',
+    //linkExactActiveClass: 'active',
     routes,
-    scrollBehavior(to, from, savedPosition) {
+    /* scrollBehavior(to, from, savedPosition) {
         if (savedPosition) {
             return savedPosition;
         } else {
             return { left: 0, top: 0 };
         }
-    },
+    }, */
 });
-
 router.beforeEach((to, from, next) => {
     const store = useAppStore();
-
-    if (to?.meta?.layout == 'auth') {
-        store.setMainLayout('auth');
+    console.log(to.meta.requiresAuth && !store.user);
+    if (to.meta.requiresAuth && !store.user) {
+        next({ name: 'login'});
     } else {
-        store.setMainLayout('app');
+        next();
     }
-    next(true);
 });
 router.afterEach((to, from, next) => {
     appSetting.changeAnimation();

+ 14 - 4
src/stores/index.ts

@@ -1,6 +1,11 @@
 import { defineStore } from 'pinia';
 import i18n from '@/i18n';
 import appSetting from '@/app-setting';
+import { UserInfo } from '@/types/common'
+import { getItem, setItem } from '@/utils/storage'
+import { USER } from '../utils/constants'
+
+type User = ({ token: string } & UserInfo) | null
 
 export const useAppStore = defineStore('app', {
     state: () => ({
@@ -16,12 +21,13 @@ export const useAppStore = defineStore('app', {
         sidebar: false,
         languageList: [
             { code: 'zh', name: 'Chinese' },
-          
+
             { code: 'en', name: 'English' },
-        
+
         ],
         isShowMainLoader: true,
         semidark: false,
+        user: getItem<User>(USER)
     }),
 
     actions: {
@@ -34,7 +40,7 @@ export const useAppStore = defineStore('app', {
             this.theme = payload;
             if (payload == 'light') {
                 this.isDarkMode = false;
-            } 
+            }
             else if (payload == 'dark') {
                 this.isDarkMode = true;
             } else if (payload == 'system') {
@@ -90,7 +96,7 @@ export const useAppStore = defineStore('app', {
             i18n.global.locale.value = payload;
             localStorage.setItem('i18n_locale', payload);
             this.locale = payload;
-            if(this.locale?.toLowerCase() === 'ae') {
+            if (this.locale?.toLowerCase() === 'ae') {
                 this.toggleRTL('rtl');
             } else {
                 this.toggleRTL('ltr');
@@ -105,6 +111,10 @@ export const useAppStore = defineStore('app', {
                 this.isShowMainLoader = false;
             }, 500);
         },
+        setUser(payload: User) {
+            this.user = payload
+            setItem(USER, this.user)
+        }
     },
     getters: {},
 });

+ 28 - 0
src/types/common.ts

@@ -0,0 +1,28 @@
+// src\api\types\common.ts
+export interface DemoData {
+    title: string
+    date: number
+  }
+  
+  export interface Menu {
+    id: number,
+    routePath: string
+    routeName: string
+    name: string
+    icon: string
+    children?: Menu[]
+  }
+  
+  export interface UserInfo {
+    id: number
+    account: string
+    realName: string
+  }
+  
+  export interface LoginResponse {
+    token: string
+    userInfo: UserInfo
+    menus: Menu[]
+    uniqueAuth: string[]
+  }
+  

+ 10 - 0
src/types/vue-router.d.ts

@@ -0,0 +1,10 @@
+// src\types\vue-router.d.ts
+import 'vue-router'
+
+declare module 'vue-router' {
+  // eslint-disable-next-line no-unused-vars
+  interface RouteMeta {
+    title?: string
+    requiresAuth?: boolean
+  }
+}

+ 115 - 0
src/utils/axios.ts

@@ -0,0 +1,115 @@
+import axios, { AxiosRequestConfig, AxiosRequestHeaders } from 'axios';
+import { showMessage } from './status';   // 引入状态码文件
+import { ElMessage, ElMessageBox } from 'element-plus'  // 引入el 提示框,这个项目里用什么组件库这里引什么
+import { useAppStore } from '@/stores/index'
+import router from '@/router'
+
+// 设置接口超时时间
+axios.defaults.timeout = 60000;
+
+// 请求地址,这里是动态赋值的的环境变量,下一篇会细讲,这里跳过
+// @ts-ignore
+axios.defaults.baseURL = 'http://192.168.8.103:9999'//import.meta.env.VITE_API_URL;   
+
+// 请求拦截器
+axios.interceptors.request.use(function (config: AxiosRequestConfig) {
+  const store = useAppStore();
+  config.headers['Content-Type'] = 'application/json;charset=UTF-8';
+  // 统一设置用户身份 token
+  if (store.user && store.user.token) {
+    (config.headers as AxiosRequestHeaders).Authorization = `Bearer ${store.user.token}`
+  }
+  return config
+}, function (error) {
+  return Promise.reject(error)
+})
+
+// 控制登录过期的锁(是否正在处理登录过期)
+let isRefreshing = false
+
+// 响应拦截器
+axios.interceptors.response.use(function (response) {
+  // 统一处理接口响应错误,如 token 过期无效、服务端异常等
+  const status = response.data.status
+  const store = useAppStore();
+  /* 正确响应 */
+  if (!status || status === 200) {
+    return response
+  }
+
+  /* 错误响应 */
+
+  // 登录过期处理
+  if (status === 401) {
+    if (isRefreshing) return Promise.reject(response)
+
+    isRefreshing = true
+
+    ElMessageBox.confirm('登录已过期,您可以取消停留在此页面,或确认重新登录', '登录过期', {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消'
+    })
+      .then(() => {
+        // 清除本地登录状态
+        store.setUser(null)
+
+        // 跳转到登录页
+        router.push({
+          name: 'login',
+          query: {
+            redirect: router.currentRoute.value.fullPath
+          }
+        })
+      })
+      .catch(() => { })
+      .finally(() => {
+        isRefreshing = false
+      })
+
+    // 抛出异常
+    return Promise.reject(response)
+  }
+
+  // 其他错误响应
+  return response;
+}, function (error) {
+  const { response } = error;
+  if (response) {
+    // 请求已发出,但是不在2xx的范围
+    showMessage(response.code); // 传入响应码,匹配响应码对应信息
+    ElMessage.error(response.data.message || '请求失败,请稍后重试');
+    return Promise.reject(response.data);
+  } else {
+    // 处理网络错误或请求未发出的情况
+    ElMessage.error('网络连接异常,请稍后再试!');
+    return Promise.reject(error);
+  }
+})
+
+
+
+// 封装 GET POST 请求并导出
+export function request(url = '', params = {}, type = 'POST') {
+  //设置 url params type 的默认值
+  return new Promise((resolve, reject) => {
+    let promise
+    if (type.toUpperCase() === 'GET') {
+      promise = axios({
+        url,
+        params
+      })
+    } else if (type.toUpperCase() === 'POST') {
+      promise = axios({
+        method: 'POST',
+        url,
+        data: params
+      })
+    }
+    //处理返回
+    promise.then(res => {
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}

+ 4 - 0
src/utils/constants.ts

@@ -0,0 +1,4 @@
+/**
+ * 静态常量
+ */
+export const USER = 'USER'

+ 0 - 0
src/api/status.ts → src/utils/status.ts


+ 22 - 0
src/utils/storage.ts

@@ -0,0 +1,22 @@
+// src\utils\storage.ts
+export const getItem = <T>(key: string) => {
+    const data = window.localStorage.getItem(key)
+    if (!data) return null
+    try {
+      return JSON.parse(data) as T
+    } catch (error) {
+      return null
+    }
+  }
+  
+  export const setItem = (key: string, value: Object | string | null) => {
+    if (typeof value !== 'string') {
+      value = JSON.stringify(value)
+    }
+    window.localStorage.setItem(key, value as string)
+  }
+  
+  export const removeItem = (key: string) => {
+    window.localStorage.removeItem(key)
+  }
+  

+ 11 - 4
src/views/auth/boxed-signin.vue

@@ -116,16 +116,23 @@ let validcodeImg = ref('');
 const inputValue = reactive({
     username: '',
     password: '',
-    uuid:'',
-    scene:'LOGIN',
+    uuid: '',
+    scene: 'LOGIN',
     captcha: ''
 });
 const handleSubmit = async () => {
     try {
-        inputValue.uuid=uuid
+        inputValue.uuid = uuid
         const response = await UserService.login(inputValue);
         console.log('Login successful:', response);
-        router.push('/dashboard'); // Redirect to dashboard after successful login
+        store.setUser({
+            ...response.data,
+            token: response.data.refresh_token
+        })
+        router.replace({
+            name: 'home'
+        })
+        //router.push('/dashboard'); // Redirect to dashboard after successful login
     } catch (error) {
         console.error('Login failed:', error);
     }

+ 60 - 1
yarn.lock

@@ -814,7 +814,7 @@
     "@vue/compiler-dom" "3.4.27"
     "@vue/shared" "3.4.27"
 
-"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1":
+"@vue/devtools-api@^6.0.0-beta.15", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1":
   version "6.6.1"
   resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83"
   integrity sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==
@@ -2210,6 +2210,11 @@ object-hash@^3.0.0:
   resolved "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
   integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
 
+object-inspect@^1.13.1:
+  version "1.13.1"
+  resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
+  integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
+
 object-is@^1.1.5:
   version "1.1.6"
   resolved "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07"
@@ -2614,11 +2619,23 @@ prompts@^2.4.2:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
+property-expr@^2.0.5:
+  version "2.0.6"
+  resolved "https://registry.npmmirror.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8"
+  integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==
+
 proxy-from-env@^1.1.0:
   version "1.1.0"
   resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
   integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
 
+qs@^6.12.1:
+  version "6.12.1"
+  resolved "https://registry.npmmirror.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a"
+  integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==
+  dependencies:
+    side-channel "^1.0.6"
+
 queue-microtask@^1.2.2:
   version "1.2.3"
   resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -2795,6 +2812,16 @@ shebang-regex@^3.0.0:
   resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
   integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
 
+side-channel@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
+  integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
+  dependencies:
+    call-bind "^1.0.7"
+    es-errors "^1.3.0"
+    get-intrinsic "^1.2.4"
+    object-inspect "^1.13.1"
+
 signal-exit@^3.0.3:
   version "3.0.7"
   resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
@@ -3072,6 +3099,11 @@ through2@^4.0.2:
   dependencies:
     readable-stream "3"
 
+tiny-case@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03"
+  integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==
+
 tiny-emitter@^2.0.0:
   version "2.1.0"
   resolved "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
@@ -3098,6 +3130,11 @@ to-regex-range@^5.0.1:
   dependencies:
     is-number "^7.0.0"
 
+toposort@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
+  integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==
+
 tree-kill@^1.2.2:
   version "1.2.2"
   resolved "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
@@ -3118,6 +3155,11 @@ tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2:
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
   integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
 
+type-fest@^2.19.0:
+  version "2.19.0"
+  resolved "https://registry.npmmirror.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
+  integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
+
 typescript@^4.6.4:
   version "4.9.5"
   resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
@@ -3182,6 +3224,13 @@ vant@^4.9.0:
     "@vant/use" "^1.6.0"
     "@vue/shared" "^3.4.26"
 
+vee-validate@^4.5.8:
+  version "4.5.8"
+  resolved "https://registry.npmmirror.com/vee-validate/-/vee-validate-4.5.8.tgz#8e394978ef3b74e589b869c626cd2a7d391ac81c"
+  integrity sha512-XZx2J93rlET49CdIIoxG9R6wQNoT3RxUUw9ar3QbIhczpzbtlm4BQ+TpCA9DoYHKFlApcXnE28WU7m4quoPsCA==
+  dependencies:
+    "@vue/devtools-api" "^6.0.0-beta.15"
+
 vite@^5.2.11:
   version "5.2.12"
   resolved "https://registry.npmmirror.com/vite/-/vite-5.2.12.tgz#3536c93c58ba18edea4915a2ac573e6537409d97"
@@ -3428,3 +3477,13 @@ yauzl@^2.10.0:
   dependencies:
     buffer-crc32 "~0.2.3"
     fd-slicer "~1.1.0"
+
+yup@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.npmmirror.com/yup/-/yup-1.4.0.tgz#898dcd660f9fb97c41f181839d3d65c3ee15a43e"
+  integrity sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==
+  dependencies:
+    property-expr "^2.0.5"
+    tiny-case "^1.0.3"
+    toposort "^2.0.2"
+    type-fest "^2.19.0"