🔧 uni-app Composables
组合式函数统一从 @/composables 导入,提供分页、表单、上传、登录等可复用逻辑。每个函数独立存在,按需导入即可。
import { usePaging, useFormData } from '@/composables'📄 数据加载与分页
usePaging
滚动分页管理器,适用于普通滚动列表页面。管理页码、加载状态和完成检测,refresh() 重置到第 1 页,loadMore() 追加下一页,返回数据不足 pageSize 时自动标记 finished。
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>
}<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() 方法。适用于需要下拉刷新和无限滚动的页面。
function useZPaging<T>(): {
pagingRef: Ref<any>
list: T[]
complete: (rows: T[]) => void
reload: () => void
}<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
响应式表单数据管理器,创建时深拷贝初始值,提供重置、局部更新和快照提取。
function useFormData<T extends object>(initialValue: T): {
formData: T
resetFormData: () => void
setFormData: (partial: Partial<T>) => void
getFormData: () => T
}<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。
function useUpload(): {
chooseImage: () => Promise<string>
uploadImage: (filePath?: string) => Promise<UploadResult>
}<script lang="ts" setup>
import { useUpload } from '@/composables'
const { uploadImage } = useUpload()
const handleUpload = async () => {
const result = await uploadImage()
formData.avatar = result.url
}
</script>useDeferredUpload
批量图片延迟上传。用户选择图片后先存储在本地,表单提交时再统一上传。适合需要先选图后提交的表单场景。
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
}<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 绑定。
function useLockFn<T extends (...args: any[]) => Promise<any>>(fn: T): {
loading: Ref<boolean>
run: (...args: Parameters<T>) => Promise<ReturnType<T> | undefined>
}<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
地址选择管理器。加载用户地址列表、管理选择弹窗、将选中地址回填到表单。
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
}<script lang="ts" setup>
import { useAddressPicker } from '@/composables'
const { currentAddress, addressPopup, openAddressPicker, fillAddressForm } = useAddressPicker({
onChoose: (addr) => {
fillAddressForm(orderForm)
},
})
</script>🔐 页面行为
useLogin
登录页面表单管理器。管理手机号/密码/验证码表单状态,提供短信倒计时(60 秒)、密码登录、短信登录和微信 OAuth 跳转。
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
}<script lang="ts" setup>
import { useLogin } from '@/composables'
const { form, canPasswordLogin, loginByPassword, sending, sendCode, timer } = useLogin()
</script>useRequireLoginPage
登录守卫 Composable。每次 onShow 时检查 Token,无 Token 时尝试静默登录,失败则跳转登录页。用于需要登录才能访问的页面。
function useRequireLoginPage(): {
requireLogin: (target?: string) => boolean
}<script lang="ts" setup>
import { useRequireLoginPage } from '@/composables'
const { requireLogin } = useRequireLoginPage()
onShow(() => {
if (!requireLogin()) return
// 已登录,加载数据
})
</script>useShare
分享信息构建器。从全局配置 Store 读取应用名称和分享图片,构建 onShareAppMessage / onShareTimeline 所需的分享信息对象。
function useShare(): {
buildShareInfo: (options?: { title?: string; path?: string; imageUrl?: string }) => {
title: string
path: string
imageUrl: string
}
}<script lang="ts" setup>
import { useShare } from '@/composables'
const { buildShareInfo } = useShare()
onShareAppMessage(() => buildShareInfo({ title: '自定义标题' }))
onShareTimeline(() => buildShareInfo())
</script>usePageTitle
页面标题同步器。从页面 URL query 读取 title 参数并同步到导航栏标题,提供 setTitle() 方法动态更新。
function usePageTitle(defaultTitle?: string): {
title: Ref<string>
setTitle: (value: string) => void
}<script lang="ts" setup>
import { usePageTitle } from '@/composables'
const { title } = usePageTitle('默认标题')
</script>