Bläddra i källkod

chore: 补齐 alova 实例,并添加动态域名切换

feige996 10 månader sedan
förälder
incheckning
b5fd91913a
7 ändrade filer med 208 tillägg och 32 borttagningar
  1. 4 1
      env/.env
  2. 1 1
      package.json
  3. 8 2
      src/api/alova-foo.ts
  4. 5 0
      src/pages/about/alova.vue
  5. 102 28
      src/utils/request/alova.ts
  6. 66 0
      src/utils/request/enum.ts
  7. 22 0
      src/utils/request/types.ts

+ 4 - 1
env/.env

@@ -9,8 +9,11 @@ VITE_APP_PUBLIC_BASE=/
 
 # 登录页面
 VITE_LOGIN_URL = '/pages/login/index'
-
+# 第一个请求地址
 VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
+# 第二个请求地址
+VITE_API_SECONDARY_URL = 'https://ukw0y1.laf.run'
+
 VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload'
 
 # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "name": "unibest",
   "type": "commonjs",
-  "version": "3.1.0",
+  "version": "3.2.0",
   "description": "unibest - 最好的 uniapp 开发模板",
   "update-time": "2025-06-21",
   "author": {

+ 8 - 2
src/api/alova-foo.ts

@@ -1,12 +1,18 @@
 // alovaJS 还在整理中,有比较熟悉的开发者可以PR一下,省得我去摸索
-import { http } from '@/utils/request/alova'
+import { API_DOMAINS, http } from '@/utils/request/alova'
+
+export interface IFoo {
+  id: number
+  name: string
+}
 
 export function foo() {
-  return http.Get('/foo', {
+  return http.Get<IFoo>('/foo', {
     params: {
       name: '菲鸽',
       page: 1,
       pageSize: 10,
     },
+    meta: { domain: API_DOMAINS.SECONDARY }, // 用于切换请求地址
   })
 }

+ 5 - 0
src/pages/about/alova.vue

@@ -12,6 +12,7 @@ import { useRequest } from 'alova/client'
 import { foo } from '@/api/alova-foo'
 
 const initialData = undefined
+
 const { loading, data, send } = useRequest(foo, {
   initialData,
   immediate: true,
@@ -39,6 +40,10 @@ function reset() {
           {{ JSON.stringify(data) }}
         </view>
       </block>
+
+      <view class="text-red">
+        {{ data?.id }}
+      </view>
     </view>
     <button class="my-6 w-200px text-red" :disabled="!data" @click="reset">
       重置数据

+ 102 - 28
src/utils/request/alova.ts

@@ -1,37 +1,111 @@
+import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
+import type { IResponse } from './types'
 import AdapterUniapp from '@alova/adapter-uniapp'
 import { createAlova } from 'alova'
+import { createServerTokenAuthentication } from 'alova/client'
+import VueHook from 'alova/vue'
+import { toast } from '@/utils/toast'
+import { ContentTypeEnum, ResultEnum, ShowMessage } from './enum'
 
-const baseURL = JSON.parse(__VITE_APP_PROXY__)
-  ? import.meta.env.VITE_APP_PROXY_PREFIX
-  : import.meta.env.VITE_SERVER_BASEURL
+// 配置动态Tag
+export const API_DOMAINS = {
+  DEFAULT: import.meta.env.VITE_SERVER_BASEURL,
+  SECONDARY: import.meta.env.VITE_API_SECONDARY_URL,
+}
 
-// alovaJS 还在整理中,有比较熟悉的开发者可以PR一下,省得我去摸索
-// 主要是下面这个文件的TS整理,如何通过泛型传入想要的数据结构,得到对应的数据结构
-export const http = createAlova({
-  baseURL,
+/**
+ * 创建请求实例
+ */
+const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
+  typeof VueHook,
+  typeof uniappRequestAdapter
+>({
+  refreshTokenOnError: {
+    isExpired: (error) => {
+      return error.response?.status === ResultEnum.Unauthorized
+    },
+    handler: async () => {
+      try {
+        // await authLogin();
+      }
+      catch (error) {
+        // 切换到登录页
+        await uni.reLaunch({ url: '/pages/common/login/index' })
+        throw error
+      }
+    },
+  },
+})
+
+/**
+ * alova 请求实例
+ */
+const alovaInstance = createAlova({
+  baseURL: import.meta.env.VITE_API_BASE_URL,
   ...AdapterUniapp(),
-  async responded(res: UniApp.RequestSuccessCallbackResult, method) {
-    console.log('responded:', method, res)
-    // 请求成功的拦截器
-    // 状态码 2xx,参考 axios 的设计
-    const resData = res.data as IResData<any>
-    if (res.statusCode >= 200 && res.statusCode < 300) {
-      // 2.1 提取核心数据 res.data
-      return resData.data
+  timeout: 5000,
+  statesHook: VueHook,
+
+  beforeRequest: onAuthRequired((method) => {
+    // 设置默认 Content-Type
+    method.config.headers = {
+      ContentType: ContentTypeEnum.JSON,
+      Accept: 'application/json, text/plain, */*',
+      ...method.config.headers,
     }
-    else if (res.statusCode === 401) {
-      // 401错误  -> 清理用户信息,跳转到登录页
-      // userStore.clearUserInfo()
-      // uni.navigateTo({ url: '/pages/login/login' })
-      console.log(res)
-      throw new Error(resData.msg || '401错误')
+
+    const { config } = method
+    const ignoreAuth = !config.meta?.ignoreAuth
+    console.log('ignoreAuth===>', ignoreAuth)
+    // 处理认证信息   自行处理认证问题
+    if (ignoreAuth) {
+      const token = 'getToken()'
+      if (!token) {
+        throw new Error('[请求错误]:未登录')
+      }
+      // method.config.headers.token = token;
     }
-    else {
-      uni.showToast({
-        icon: 'none',
-        title: (resData).msg || '请求错误',
-      })
-      throw new Error(resData.msg || '请求错误')
+
+    // 处理动态域名
+    if (config.meta?.domain) {
+      method.baseURL = config.meta.domain
+      console.log('当前域名', method.baseURL)
     }
-  },
+  }),
+
+  responded: onResponseRefreshToken((response, method) => {
+    const { config } = method
+    const { requestType } = config
+    const {
+      statusCode,
+      data: rawData,
+      errMsg,
+    } = response as UniNamespace.RequestSuccessCallbackResult
+
+    // 处理特殊请求类型(上传/下载)
+    if (requestType === 'upload' || requestType === 'download') {
+      return response
+    }
+
+    // 处理 HTTP 状态码错误
+    if (statusCode !== 200) {
+      const errorMessage = ShowMessage(statusCode) || `HTTP请求错误[${statusCode}]`
+      console.error('errorMessage===>', errorMessage)
+      toast.error(errorMessage)
+      throw new Error(`${errorMessage}:${errMsg}`)
+    }
+
+    // 处理业务逻辑错误
+    const { code, message, data } = rawData as IResponse
+    if (code !== ResultEnum.Success) {
+      if (config.meta?.toast !== false) {
+        toast.warning(message)
+      }
+      throw new Error(`请求错误[${code}]:${message}`)
+    }
+    // 处理成功响应,返回业务数据
+    return data
+  }),
 })
+
+export const http = alovaInstance

+ 66 - 0
src/utils/request/enum.ts

@@ -0,0 +1,66 @@
+export enum ResultEnum {
+  Success = 0, // 成功
+  Error = 400, // 错误
+  Unauthorized = 401, // 未授权
+  Forbidden = 403, // 禁止访问(原为forbidden)
+  NotFound = 404, // 未找到(原为notFound)
+  MethodNotAllowed = 405, // 方法不允许(原为methodNotAllowed)
+  RequestTimeout = 408, // 请求超时(原为requestTimeout)
+  InternalServerError = 500, // 服务器错误(原为internalServerError)
+  NotImplemented = 501, // 未实现(原为notImplemented)
+  BadGateway = 502, // 网关错误(原为badGateway)
+  ServiceUnavailable = 503, // 服务不可用(原为serviceUnavailable)
+  GatewayTimeout = 504, // 网关超时(原为gatewayTimeout)
+  HttpVersionNotSupported = 505, // HTTP版本不支持(原为httpVersionNotSupported)
+}
+export enum ContentTypeEnum {
+  JSON = 'application/json;charset=UTF-8',
+  FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
+  FORM_DATA = 'multipart/form-data;charset=UTF-8',
+}
+/**
+ * 根据状态码,生成对应的错误信息
+ * @param {number|string} status 状态码
+ * @returns {string} 错误信息
+ */
+export function ShowMessage(status: number | string): string {
+  let message: string
+  switch (status) {
+    case 400:
+      message = '请求错误(400)'
+      break
+    case 401:
+      message = '未授权,请重新登录(401)'
+      break
+    case 403:
+      message = '拒绝访问(403)'
+      break
+    case 404:
+      message = '请求出错(404)'
+      break
+    case 408:
+      message = '请求超时(408)'
+      break
+    case 500:
+      message = '服务器错误(500)'
+      break
+    case 501:
+      message = '服务未实现(501)'
+      break
+    case 502:
+      message = '网络错误(502)'
+      break
+    case 503:
+      message = '服务不可用(503)'
+      break
+    case 504:
+      message = '网络超时(504)'
+      break
+    case 505:
+      message = 'HTTP版本不受支持(505)'
+      break
+    default:
+      message = `连接出错(${status})!`
+  }
+  return `${message},请检查网络或联系管理员!`
+}

+ 22 - 0
src/utils/request/types.ts

@@ -0,0 +1,22 @@
+// 通用响应格式
+export interface IResponse<T = any> {
+  code: number | string
+  data: T
+  message: string
+  status: string | number
+}
+
+// 分页请求参数
+export interface PageParams {
+  page: number
+  pageSize: number
+  [key: string]: any
+}
+
+// 分页响应数据
+export interface PageResult<T> {
+  list: T[]
+  total: number
+  page: number
+  pageSize: number
+}