Skip to content

🔧 uni-app Composables

组合式函数统一从 @/composables 导入,提供分页、表单、上传、登录等可复用逻辑。每个函数独立存在,按需导入即可。

ts
import { usePaging, useFormData } from '@/composables'

📄 数据加载与分页

usePaging

滚动分页管理器,适用于普通滚动列表页面。管理页码、加载状态和完成检测,refresh() 重置到第 1 页,loadMore() 追加下一页,返回数据不足 pageSize 时自动标记 finished

ts
function usePaging<T>(options: {
  pageSize?: number
  request: (params: { page_no: number; page_size: number }) => Promise<T[] | { list?: T[] }>
}): {
  list: T[]
  loading: Ref<boolean>
  finished: Ref<boolean>
  pageNo: Ref<number>
  refresh: () => Promise<void>
  loadMore: () => Promise<void>
}
vue
<script lang="ts" setup>
import { usePaging } from '@/composables'
import { articleList } from '@/apis/system/article'

const { list, loading, finished, refresh, loadMore } = usePaging({
  request: (params) => articleList(params),
})

onLoad(() => refresh())
</script>

<template>
  <base-list :items="list">
    <view v-for="item in list" :key="item.id">{{ item.title }}</view>
  </base-list>
  <base-loading v-if="loading" />
  <view v-if="finished" class="text-center">没有更多了</view>
  <view @click="loadMore">加载更多</view>
</template>

useZPaging

z-paging 插件的 ref 式 API 封装。持有模板 ref、响应式列表,并代理 complete()reload() 方法。适用于需要下拉刷新和无限滚动的页面。

ts
function useZPaging<T>(): {
  pagingRef: Ref<any>
  list: T[]
  complete: (rows: T[]) => void
  reload: () => void
}
vue
<template>
  <z-paging ref="pagingRef" @query="onQuery">
    <view v-for="item in list" :key="item.id">{{ item.title }}</view>
  </z-paging>
</template>

<script lang="ts" setup>
import { useZPaging } from '@/composables'
import { articleList } from '@/apis/system/article'

const { pagingRef, list, complete } = useZPaging()

const onQuery = async (pageNo: number, pageSize: number) => {
  const res = await articleList({ page_no: pageNo, page_size: pageSize })
  complete(res.list || [])
}
</script>

📝 表单与上传

useFormData

响应式表单数据管理器,创建时深拷贝初始值,提供重置、局部更新和快照提取。

ts
function useFormData<T extends object>(initialValue: T): {
  formData: T
  resetFormData: () => void
  setFormData: (partial: Partial<T>) => void
  getFormData: () => T
}
vue
<script lang="ts" setup>
import { useFormData } from '@/composables'

const { formData, resetFormData, setFormData, getFormData } = useFormData({
  name: '',
  mobile: '',
  avatar: '',
})

// 局部更新
setFormData({ name: '张三' })

// 获取快照(非响应式副本)
const data = getFormData()
</script>

useUpload

单张图片选择 + 上传。chooseImage() 打开系统图片选择器,uploadImage() 选择后上传并返回 UploadResult

ts
function useUpload(): {
  chooseImage: () => Promise<string>
  uploadImage: (filePath?: string) => Promise<UploadResult>
}
vue
<script lang="ts" setup>
import { useUpload } from '@/composables'

const { uploadImage } = useUpload()

const handleUpload = async () => {
  const result = await uploadImage()
  formData.avatar = result.url
}
</script>

useDeferredUpload

批量图片延迟上传。用户选择图片后先存储在本地,表单提交时再统一上传。适合需要先选图后提交的表单场景。

ts
function useDeferredUpload(options?: {
  maxCount?: number      // 默认 8
  formData?: Record<string, any>
  sizeType?: string[]
  sourceType?: string[]
  resolveValue?: (item: any) => string
}): {
  attachments: Ref<any[]>
  chooseImages: () => Promise<void>
  previewAttachment: (index: number) => void
  removeAttachment: (index: number) => void
  uploadAttachments: () => Promise<string[]>
  clearAttachments: () => void
}
vue
<script lang="ts" setup>
import { useDeferredUpload } from '@/composables'

const { attachments, chooseImages, removeAttachment, uploadAttachments } = useDeferredUpload({
  maxCount: 9,
})

const handleSubmit = async () => {
  // 提交时才真正上传
  const urls = await uploadAttachments()
  await submitForm({ images: urls })
}
</script>

<template>
  <view v-for="(img, index) in attachments" :key="index">
    <image :src="img" />
    <view @click="removeAttachment(index)">删除</view>
  </view>
  <view @click="chooseImages">+ 添加图片</view>
</template>

useLockFn

防止异步函数重复执行。run() 执行期间再次调用会直接返回 undefined,同时暴露 loading ref 用于 UI 绑定。

ts
function useLockFn<T extends (...args: any[]) => Promise<any>>(fn: T): {
  loading: Ref<boolean>
  run: (...args: Parameters<T>) => Promise<ReturnType<T> | undefined>
}
vue
<script lang="ts" setup>
import { useLockFn } from '@/composables'

const { loading, run: handleSubmit } = useLockFn(async () => {
  await submitForm(formData)
  uni.showToast({ title: '提交成功' })
})
</script>

<template>
  <button :loading="loading" @click="handleSubmit">提交</button>
</template>

useAddressPicker

地址选择管理器。加载用户地址列表、管理选择弹窗、将选中地址回填到表单。

ts
function useAddressPicker(options?: {
  pageSize?: number
  onChoose?: (address: any) => void
}): {
  addressItems: Ref<any[]>
  currentAddress: Ref<any>
  addressPopup: Ref<boolean>
  addressRegion: Ref<any[]>
  fillAddressForm: (form: any) => void
  chooseAddress: (item: any) => void
  loadAddresses: () => Promise<void>
  openAddressPicker: () => void
}
vue
<script lang="ts" setup>
import { useAddressPicker } from '@/composables'

const { currentAddress, addressPopup, openAddressPicker, fillAddressForm } = useAddressPicker({
  onChoose: (addr) => {
    fillAddressForm(orderForm)
  },
})
</script>

🔐 页面行为

useLogin

登录页面表单管理器。管理手机号/密码/验证码表单状态,提供短信倒计时(60 秒)、密码登录、短信登录和微信 OAuth 跳转。

ts
function useLogin(): {
  form: { mobile: string; password: string; code: string }
  submitting: Ref<boolean>
  sending: Ref<boolean>
  timer: Ref<number>
  h5LoginMode: Ref<string>
  phoneReady: ComputedRef<boolean>
  canPasswordLogin: ComputedRef<boolean>
  canSmsLogin: ComputedRef<boolean>
  sendCode: () => Promise<void>
  loginByPassword: () => Promise<void>
  loginBySms: () => Promise<void>
  buildWechatOauthUrl: () => string
}
vue
<script lang="ts" setup>
import { useLogin } from '@/composables'

const { form, canPasswordLogin, loginByPassword, sending, sendCode, timer } = useLogin()
</script>

useRequireLoginPage

登录守卫 Composable。每次 onShow 时检查 Token,无 Token 时尝试静默登录,失败则跳转登录页。用于需要登录才能访问的页面。

ts
function useRequireLoginPage(): {
  requireLogin: (target?: string) => boolean
}
vue
<script lang="ts" setup>
import { useRequireLoginPage } from '@/composables'

const { requireLogin } = useRequireLoginPage()

onShow(() => {
  if (!requireLogin()) return
  // 已登录,加载数据
})
</script>

useShare

分享信息构建器。从全局配置 Store 读取应用名称和分享图片,构建 onShareAppMessage / onShareTimeline 所需的分享信息对象。

ts
function useShare(): {
  buildShareInfo: (options?: { title?: string; path?: string; imageUrl?: string }) => {
    title: string
    path: string
    imageUrl: string
  }
}
vue
<script lang="ts" setup>
import { useShare } from '@/composables'

const { buildShareInfo } = useShare()

onShareAppMessage(() => buildShareInfo({ title: '自定义标题' }))
onShareTimeline(() => buildShareInfo())
</script>

usePageTitle

页面标题同步器。从页面 URL query 读取 title 参数并同步到导航栏标题,提供 setTitle() 方法动态更新。

ts
function usePageTitle(defaultTitle?: string): {
  title: Ref<string>
  setTitle: (value: string) => void
}
vue
<script lang="ts" setup>
import { usePageTitle } from '@/composables'

const { title } = usePageTitle('默认标题')
</script>