فهرست منبع

chore: bump update

Z.X.PING 10 ماه پیش
والد
کامیت
c19007d7c1

+ 1 - 1
.gitignore

@@ -27,7 +27,7 @@ dist
 docs/.vitepress/dist
 docs/.vitepress/cache
 
-types
+src/types
 
 # lock 文件还是不要了,我主要的版本写死就好了
 # pnpm-lock.yaml

+ 2 - 2
.vscode/settings.json

@@ -55,8 +55,8 @@
   "explorer.fileNesting.patterns": {
     "README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md",
     "pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts",
-    "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
-    "eslint.config.mjs": "tsconfig.json,.commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*"
+    "package.json": "tsconfig.json,pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
+    "eslint.config.mjs": ".commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*"
   },
 
   // // 保存的时候自动格式化

+ 4 - 2
package.json

@@ -1,7 +1,7 @@
 {
   "name": "unibest",
   "type": "commonjs",
-  "version": "3.0.0",
+  "version": "3.2.0",
   "description": "unibest - 最好的 uniapp 开发模板",
   "update-time": "2025-06-21",
   "author": {
@@ -79,6 +79,8 @@
     "lint:fix": "eslint --fix"
   },
   "dependencies": {
+    "@alova/adapter-uniapp": "^2.0.14",
+    "@alova/shared": "^1.3.1",
     "@dcloudio/uni-app": "3.0.0-4060620250520001",
     "@dcloudio/uni-app-harmony": "3.0.0-4060620250520001",
     "@dcloudio/uni-app-plus": "3.0.0-4060620250520001",
@@ -97,11 +99,11 @@
     "@dcloudio/uni-quickapp-webview": "3.0.0-4060620250520001",
     "@tanstack/vue-query": "^5.62.16",
     "abortcontroller-polyfill": "^1.7.8",
+    "alova": "^3.3.3",
     "dayjs": "1.11.10",
     "element-plus": "^2.10.2",
     "pinia": "2.0.36",
     "pinia-plugin-persistedstate": "3.2.1",
-    "qs": "6.5.3",
     "vue": "^3.4.21",
     "vue-i18n": "9.1.9",
     "wot-design-uni": "^1.9.1",

+ 38 - 9
pnpm-lock.yaml

@@ -16,6 +16,12 @@ importers:
 
   .:
     dependencies:
+      '@alova/adapter-uniapp':
+        specifier: ^2.0.14
+        version: 2.0.14(alova@3.3.3)
+      '@alova/shared':
+        specifier: ^1.3.1
+        version: 1.3.1
       '@dcloudio/uni-app':
         specifier: 3.0.0-4060620250520001
         version: 3.0.0-4060620250520001(@dcloudio/types@3.4.14)(postcss@8.4.49)(rollup@4.41.1)(vue@3.5.15(typescript@5.7.2))
@@ -70,6 +76,9 @@ importers:
       abortcontroller-polyfill:
         specifier: ^1.7.8
         version: 1.7.8
+      alova:
+        specifier: ^3.3.3
+        version: 3.3.3
       dayjs:
         specifier: 1.11.10
         version: 1.11.10
@@ -82,9 +91,6 @@ importers:
       pinia-plugin-persistedstate:
         specifier: 3.2.1
         version: 3.2.1(pinia@2.0.36(typescript@5.7.2)(vue@3.5.15(typescript@5.7.2)))
-      qs:
-        specifier: 6.5.3
-        version: 6.5.3
       vue:
         specifier: ^3.4.21
         version: 3.5.15(typescript@5.7.2)
@@ -233,6 +239,14 @@ importers:
 
 packages:
 
+  '@alova/adapter-uniapp@2.0.14':
+    resolution: {integrity: sha512-AHrS/evdhONySkmtYHs6Sh1D+a0+zjBeOltcYY2/az/KEPN9p/l4H9Nvy+ghLhzMzfKfnITxeeMOi5ANyERimw==}
+    peerDependencies:
+      alova: ^3.0.20
+
+  '@alova/shared@1.3.1':
+    resolution: {integrity: sha512-ijSOaFLUFcVzMKSY3avoEE5C03/p9atjMDPBwvNkwnzaCrhv6/m4A121NdadF8YlHCRuifyYfz90IyEdMXTsJg==}
+
   '@ampproject/remapping@2.3.0':
     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
     engines: {node: '>=6.0.0'}
@@ -2741,6 +2755,10 @@ packages:
   alien-signals@1.0.13:
     resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==}
 
+  alova@3.3.3:
+    resolution: {integrity: sha512-DB1t2qpQUERVaymz+u9bS71kcN5PzZ+G5LawHm9grabv++tfHOeYsKaW+LCpgMIJ/V7lQqFyqwOvTHGDph43Jw==}
+    engines: {node: '>= 18.0.0'}
+
   ansi-escapes@4.3.2:
     resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
     engines: {node: '>=8'}
@@ -5440,10 +5458,6 @@ packages:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
 
-  qs@6.5.3:
-    resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
-    engines: {node: '>=0.6'}
-
   quansync@0.2.10:
     resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
 
@@ -5461,6 +5475,9 @@ packages:
     resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
     engines: {node: '>= 0.6'}
 
+  rate-limiter-flexible@5.0.5:
+    resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==}
+
   raw-body@2.5.2:
     resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
     engines: {node: '>= 0.8'}
@@ -6433,6 +6450,13 @@ packages:
 
 snapshots:
 
+  '@alova/adapter-uniapp@2.0.14(alova@3.3.3)':
+    dependencies:
+      '@alova/shared': 1.3.1
+      alova: 3.3.3
+
+  '@alova/shared@1.3.1': {}
+
   '@ampproject/remapping@2.3.0':
     dependencies:
       '@jridgewell/gen-mapping': 0.3.5
@@ -10030,6 +10054,11 @@ snapshots:
 
   alien-signals@1.0.13: {}
 
+  alova@3.3.3:
+    dependencies:
+      '@alova/shared': 1.3.1
+      rate-limiter-flexible: 5.0.5
+
   ansi-escapes@4.3.2:
     dependencies:
       type-fest: 0.21.3
@@ -13257,8 +13286,6 @@ snapshots:
     dependencies:
       side-channel: 1.0.6
 
-  qs@6.5.3: {}
-
   quansync@0.2.10: {}
 
   querystringify@2.2.0: {}
@@ -13269,6 +13296,8 @@ snapshots:
 
   range-parser@1.2.1: {}
 
+  rate-limiter-flexible@5.0.5: {}
+
   raw-body@2.5.2:
     dependencies:
       bytes: 3.1.2

+ 16 - 0
src/api/alova-foo.ts

@@ -0,0 +1,16 @@
+import { http } from '@/utils/request/alova'
+
+export interface IFooItem {
+  id: string
+  name: string
+}
+
+export function foo() {
+  return http.Get<IFooItem>('/foo', {
+    params: {
+      name: '菲鸽',
+      page: 1,
+      pageSize: 10,
+    },
+  })
+}

+ 57 - 0
src/api/types/login.ts

@@ -0,0 +1,57 @@
+/**
+ * 用户信息
+ */
+export interface IUserInfoVo {
+  id: number
+  username: string
+  avatar: string
+  token: string
+}
+
+/**
+ * 登录返回的信息
+ */
+export interface IUserLogin {
+  id: string
+  username: string
+  token: string
+}
+
+/**
+ * 获取验证码
+ */
+export interface ICaptcha {
+  captchaEnabled: boolean
+  uuid: string
+  image: string
+}
+/**
+ * 上传成功的信息
+ */
+export interface IUploadSuccessInfo {
+  fileId: number
+  originalName: string
+  fileName: string
+  storagePath: string
+  fileHash: string
+  fileType: string
+  fileBusinessType: string
+  fileSize: number
+}
+/**
+ * 更新用户信息
+ */
+export interface IUpdateInfo {
+  id: number
+  name: string
+  sex: string
+}
+/**
+ * 更新用户信息
+ */
+export interface IUpdatePassword {
+  id: number
+  oldPassword: string
+  newPassword: string
+  confirmPassword: string
+}

+ 3 - 0
src/env.d.ts

@@ -29,3 +29,6 @@ interface ImportMetaEnv {
 interface ImportMeta {
   readonly env: ImportMetaEnv
 }
+
+declare const __VITE_APP_PROXY__: 'true' | 'false'
+declare const __UNI_PLATFORM__: 'app' | 'h5' | 'mp-alipay' | 'mp-baidu' | 'mp-kuaishou' | 'mp-lark' | 'mp-qq' | 'mp-tiktok' | 'mp-weixin' | 'mp-xiaochengxu'

+ 2 - 2
src/interceptors/request.ts

@@ -1,7 +1,7 @@
-import qs from 'qs'
 import { useUserStore } from '@/store'
 import { getEnvBaseUrl } from '@/utils'
 import { platform } from '@/utils/platform'
+import { stringifyQuery } from '@/utils/queryString'
 
 export type CustomRequestOptions = UniApp.RequestOptions & {
   query?: Record<string, any>
@@ -18,7 +18,7 @@ const httpInterceptor = {
   invoke(options: CustomRequestOptions) {
     // 接口请求支持通过 query 参数配置 queryString
     if (options.query) {
-      const queryStr = qs.stringify(options.query)
+      const queryStr = stringifyQuery(options.query)
       if (options.url.includes('?')) {
         options.url += `&${queryStr}`
       }

+ 4 - 4
src/layouts/fg-tabbar/tabbar.md

@@ -2,15 +2,15 @@
 
 `tabbar` 分为 `4 种` 情况:
 
-- `完全原生 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。
+- 0 `完全原生 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。
   - 优势:原生自带的 tabbar,最先渲染,有缓存。
   - 劣势:只能使用 2 组图片来切换选中和非选中状态,修改颜色只能重新换图片(或者用 iconfont)。
-- `半自定义 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。使用了第三方 UI 库的 `tabbar` 组件,并隐藏了原生 `tabbar` 的显示。
+- 1 `半自定义 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。使用了第三方 UI 库的 `tabbar` 组件,并隐藏了原生 `tabbar` 的显示。
   - 优势:可以随意配置自己想要的 `svg icon`,切换字体颜色方便。有缓存。可以实现各种花里胡哨的动效等。
   - 劣势:首次点击 tababr 会闪烁。
-- `全自定义 tabbar`,使用 `navigateTo` 切换 `tabbar`,`tabbar` 页面无缓存。使用了第三方 UI 库的 `tabbar` 组件。
+- 2 `全自定义 tabbar`,使用 `navigateTo` 切换 `tabbar`,`tabbar` 页面无缓存。使用了第三方 UI 库的 `tabbar` 组件。
   - 优势:可以随意配置自己想要的 svg icon,切换字体颜色方便。可以实现各种花里胡哨的动效等。
   - 劣势:首次点击 `tababr` 会闪烁,无缓存。
-- `无 tabbar`,只有一个页面入口,底部无 `tabbar` 显示;常用语临时活动页。
+- 3 `无 tabbar`,只有一个页面入口,底部无 `tabbar` 显示;常用语临时活动页。
 
 > 注意:花里胡哨的效果需要自己实现,本模版不提供。

+ 3 - 3
src/layouts/fg-tabbar/tabbarList.ts

@@ -11,9 +11,6 @@
 // TODO:通过这里切换使用tabbar的策略
 export const selectedTabbarStrategy = 0
 
-// 0 和 1 时,需要tabbar缓存
-export const cacheTabbarEnable = selectedTabbarStrategy < 2
-
 // selectedTabbarStrategy==0 时,需要填 iconPath 和 selectedIconPath
 // selectedTabbarStrategy==1 or 2 时,需要填 icon 和 iconType
 // selectedTabbarStrategy==3 时,tabbarList 不生效
@@ -49,6 +46,9 @@ export const tabbarList = [
   // },
 ]
 
+// 0 和 1 时,需要tabbar缓存
+export const cacheTabbarEnable = selectedTabbarStrategy < 2
+
 const _tabbar = {
   color: '#999999',
   selectedColor: '#018d71',

+ 8 - 0
src/pages.json

@@ -59,6 +59,14 @@
       "style": {
         "navigationBarTitleText": "关于"
       }
+    },
+    {
+      "path": "pages/about/alova",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationBarTitleText": "Alova 请求演示"
+      }
     }
   ],
   "subPackages": []

+ 9 - 0
src/pages/about/about.vue

@@ -20,6 +20,12 @@ const { safeAreaInsets } = uni.getSystemInfoSync()
 // }
 // testOxlint('oxlint')
 console.log('about')
+
+function gotoAlova() {
+  uni.navigateTo({
+    url: '/pages/about/alova',
+  })
+}
 </script>
 
 <template>
@@ -32,6 +38,9 @@ console.log('about')
     </view>
     <RequestComp />
     <UploadComp />
+    <button class="w-200px text-green" @click="gotoAlova">
+      前往 alova 页面
+    </button>
   </view>
 </template>
 

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

@@ -0,0 +1,51 @@
+<route lang="json5" type="page">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: 'Alova 请求演示',
+  },
+}
+</route>
+
+<script lang="ts" setup>
+import { useRequest } from 'alova/client'
+import { foo } from '@/api/alova-foo'
+
+const initialData = undefined
+const { loading, data, send } = useRequest(foo, {
+  initialData,
+  immediate: true,
+})
+console.log(data)
+function reset() {
+  data.value = initialData
+}
+</script>
+
+<template>
+  <view class="p-6 text-center">
+    <button class="my-6 w-200px text-green" @click="send">
+      发送请求
+    </button>
+    <view class="h-16">
+      <view v-if="loading">
+        loading...
+      </view>
+      <block v-else>
+        <view class="text-xl">
+          请求数据如下
+        </view>
+        <view class="text-green leading-8">
+          {{ JSON.stringify(data) }}
+        </view>
+      </block>
+    </view>
+    <button class="my-6 w-200px text-red" :disabled="!data" @click="reset">
+      重置数据
+    </button>
+  </view>
+</template>
+
+<style lang="scss" scoped>
+//
+</style>

+ 29 - 0
src/utils/queryString.ts

@@ -0,0 +1,29 @@
+/**
+ * 将对象序列化为URL查询字符串,用于替代第三方的 qs 库,节省宝贵的体积
+ * 支持基本类型值和数组,不支持嵌套对象
+ * @param obj 要序列化的对象
+ * @returns 序列化后的查询字符串
+ */
+export function stringifyQuery(obj: Record<string, any>): string {
+  if (!obj || typeof obj !== 'object' || Array.isArray(obj))
+    return ''
+
+  return Object.entries(obj)
+    .filter(([_, value]) => value !== undefined && value !== null)
+    .map(([key, value]) => {
+      // 对键进行编码
+      const encodedKey = encodeURIComponent(key)
+
+      // 处理数组类型
+      if (Array.isArray(value)) {
+        return value
+          .filter(item => item !== undefined && item !== null)
+          .map(item => `${encodedKey}=${encodeURIComponent(item)}`)
+          .join('&')
+      }
+
+      // 处理基本类型
+      return `${encodedKey}=${encodeURIComponent(value)}`
+    })
+    .join('&')
+}

+ 35 - 0
src/utils/request/alova.ts

@@ -0,0 +1,35 @@
+import AdapterUniapp from '@alova/adapter-uniapp'
+import { createAlova } from 'alova'
+
+const baseURL = JSON.parse(__VITE_APP_PROXY__)
+  ? import.meta.env.VITE_APP_PROXY_PREFIX
+  : import.meta.env.VITE_SERVER_BASEURL
+
+export const http = createAlova({
+  baseURL,
+  ...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
+    }
+    else if (res.statusCode === 401) {
+      // 401错误  -> 清理用户信息,跳转到登录页
+      // userStore.clearUserInfo()
+      // uni.navigateTo({ url: '/pages/login/login' })
+      console.log(res)
+      throw new Error(resData.msg || '401错误')
+    }
+    else {
+      uni.showToast({
+        icon: 'none',
+        title: (resData).msg || '请求错误',
+      })
+      throw new Error(resData.msg || '请求错误')
+    }
+  },
+})