移动端组件使用
本文档介绍 vue-uniapp-template 项目中常用组件的使用方法。
wot-design-uni 组件库
项目使用 wot-design-uni 作为基础组件库,这是一个基于 Vue 3 和 TypeScript 构建的高质量组件库。
组件库特性
- ✅ 70+ 组件:提供丰富的 UI 组件
- ✅ Vue 3 + TS:完整的 TypeScript 类型支持
- ✅ Wot Design 规范:遵循统一的设计规范
- ✅ 暗黑模式:支持深色主题
- ✅ 国际化:内置多语言支持
- ✅ 自定义主题:灵活的主题定制
- ✅ 按需引入:通过 easycom 自动按需引入
安装和配置
1. 安装依赖
bash
pnpm add wot-design-uni2. 配置自动引入
在 pages.json 中配置 easycom 自动引入:
json
{
"easycom": {
"autoscan": true,
"custom": {
"^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue"
}
},
"pages": [
// 页面配置
]
}3. TypeScript 支持
在 tsconfig.json 中配置类型支持:
json
{
"compilerOptions": {
"types": ["wot-design-uni/global"]
}
}常用组件
1. 基础组件
Button - 按钮
vue
<template>
<view class="demo-block">
<!-- 按钮类型 -->
<wd-button type="primary">主要按钮</wd-button>
<wd-button type="success">成功按钮</wd-button>
<wd-button type="info">信息按钮</wd-button>
<wd-button type="warning">警告按钮</wd-button>
<wd-button type="error">危险按钮</wd-button>
<!-- 按钮尺寸 -->
<wd-button type="primary" size="large" block>块级按钮</wd-button>
<wd-button type="primary" size="medium">中等按钮</wd-button>
<wd-button type="primary" size="small">小型按钮</wd-button>
<!-- 按钮状态 -->
<wd-button type="primary" plain>空心按钮</wd-button>
<wd-button type="primary" disabled>禁用按钮</wd-button>
<wd-button type="primary" loading>加载中</wd-button>
<wd-button type="primary" icon="add-circle">图标按钮</wd-button>
</view>
</template>
<style scoped lang="scss">
.demo-block {
padding: 20rpx;
.wd-button {
margin: 10rpx;
}
}
</style>Icon - 图标
vue
<template>
<view class="demo-icons">
<wd-icon name="add-circle" size="24px" />
<wd-icon name="check" size="24px" color="#07c160" />
<wd-icon name="close" size="24px" color="#ee0a24" />
<wd-icon name="arrow-right" size="24px" />
<wd-icon name="search" size="24px" />
<wd-icon name="star" size="24px" />
</view>
</template>
<style scoped lang="scss">
.demo-icons {
display: flex;
gap: 20rpx;
padding: 20rpx;
}
</style>2. 表单组件
Input - 输入框
vue
<template>
<view class="input-demo">
<wd-cell-group border>
<!-- 基础用法 -->
<wd-input
v-model="value1"
label="用户名"
placeholder="请输入用户名"
clearable
/>
<!-- 密码输入 -->
<wd-input
v-model="value2"
type="password"
label="密码"
placeholder="请输入密码"
show-password
clearable
/>
<!-- 数字输入 -->
<wd-input
v-model="value3"
type="number"
label="年龄"
placeholder="请输入年龄"
/>
<!-- 带图标 -->
<wd-input
v-model="value4"
label="手机号"
prefix-icon="phone"
suffix-icon="arrow-right"
placeholder="请输入手机号"
/>
<!-- 禁用状态 -->
<wd-input
v-model="value5"
label="用户ID"
placeholder="系统生成"
disabled
/>
<!-- 多行文本 -->
<wd-input
v-model="value6"
type="textarea"
label="备注"
placeholder="请输入备注"
:rows="3"
/>
</wd-cell-group>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const value1 = ref('')
const value2 = ref('')
const value3 = ref('')
const value4 = ref('')
const value5 = ref('1001')
const value6 = ref('')
</script>Form - 表单
vue
<template>
<view class="form-page">
<wd-form ref="formRef" :model="form" :rules="rules">
<wd-cell-group border>
<wd-input
v-model="form.username"
label="用户名"
placeholder="请输入用户名"
prop="username"
clearable
required
/>
<wd-input
v-model="form.password"
type="password"
label="密码"
placeholder="请输入密码"
prop="password"
show-password
clearable
required
/>
<wd-input
v-model="form.phone"
label="手机号"
placeholder="请输入手机号"
prop="phone"
clearable
/>
<wd-input
v-model="form.email"
label="邮箱"
placeholder="请输入邮箱"
prop="email"
clearable
/>
</wd-cell-group>
<view class="button-group">
<wd-button type="primary" size="large" block @click="handleSubmit">
提交
</wd-button>
<wd-button size="large" block @click="handleReset">
重置
</wd-button>
</view>
</wd-form>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const formRef = ref()
const form = ref({
username: '',
password: '',
phone: '',
email: ''
})
const rules = {
username: [
{ required: true, message: '请输入用户名' },
{ pattern: /^[a-zA-Z0-9_]{3,16}$/, message: '用户名为3-16位字母数字下划线' }
],
password: [
{ required: true, message: '请输入密码' },
{ pattern: /.{6,}/, message: '密码不能少于6位' }
],
phone: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
],
email: [
{ pattern: /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/, message: '请输入正确的邮箱' }
]
}
const handleSubmit = () => {
formRef.value.validate().then(() => {
uni.showToast({
title: '验证通过',
icon: 'success'
})
console.log('表单数据:', form.value)
}).catch((errors: any) => {
uni.showToast({
title: '请检查表单',
icon: 'none'
})
})
}
const handleReset = () => {
formRef.value.reset()
}
</script>
<style scoped lang="scss">
.form-page {
padding: 20rpx;
}
.button-group {
margin-top: 40rpx;
.wd-button {
margin-bottom: 20rpx;
}
}
</style>Picker - 选择器
vue
<template>
<view>
<wd-cell title="选择城市" :value="cityText" @click="showPicker = true" />
<wd-picker
v-model="showPicker"
:columns="columns"
@confirm="onConfirm"
@cancel="showPicker = false"
/>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const showPicker = ref(false)
const cityText = ref('')
const columns = ref([
{ label: '北京', value: '110000' },
{ label: '上海', value: '310000' },
{ label: '广州', value: '440100' },
{ label: '深圳', value: '440300' }
])
const onConfirm = ({ selectedItems }: any) => {
cityText.value = selectedItems[0].label
showPicker.value = false
}
</script>3. 反馈组件
Toast - 提示
typescript
// 成功提示
uni.$wdToast.success('操作成功')
// 失败提示
uni.$wdToast.error('操作失败')
// 警告提示
uni.$wdToast.warning('请注意')
// 加载提示
uni.$wdToast.loading('加载中...')
// 自定义时长
uni.$wdToast.show({
message: '2秒后关闭',
duration: 2000
})MessageBox - 消息弹框
vue
<template>
<view>
<wd-button @click="handleConfirm">确认框</wd-button>
<wd-button @click="handleAlert">提示框</wd-button>
<wd-button @click="handlePrompt">输入框</wd-button>
</view>
</template>
<script setup lang="ts">
const handleConfirm = () => {
uni.$wdMessageBox.confirm({
title: '提示',
message: '确定要删除吗?',
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(() => {
console.log('确认删除')
}).catch(() => {
console.log('取消删除')
})
}
const handleAlert = () => {
uni.$wdMessageBox.alert({
title: '提示',
message: '这是一条消息提示',
confirmButtonText: '知道了'
})
}
const handlePrompt = () => {
uni.$wdMessageBox.prompt({
title: '请输入',
message: '请输入用户名',
inputPattern: /^[a-zA-Z0-9_]{3,16}$/,
inputErrorMessage: '用户名为3-16位字母数字下划线'
}).then((value: string) => {
console.log('输入的值:', value)
})
}
</script>ActionSheet - 动作面板
vue
<template>
<view>
<wd-button @click="show = true">打开动作面板</wd-button>
<wd-action-sheet
v-model="show"
:actions="actions"
@select="onSelect"
@cancel="show = false"
/>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const show = ref(false)
const actions = ref([
{ name: '选项1' },
{ name: '选项2' },
{ name: '选项3', subname: '描述信息' },
{ name: '禁用选项', disabled: true }
])
const onSelect = (item: any) => {
console.log('选中:', item.name)
show.value = false
}
</script>4. 展示组件
Cell - 单元格
vue
<template>
<wd-cell-group border>
<!-- 基础用法 -->
<wd-cell title="单元格" value="内容" />
<wd-cell title="单元格" value="内容" label="描述信息" />
<!-- 带图标 -->
<wd-cell title="单元格" icon="setting" is-link />
<!-- 点击跳转 -->
<wd-cell title="跳转页面" is-link @click="handleClick" />
<!-- 自定义内容 -->
<wd-cell title="自定义">
<template #value>
<wd-switch v-model="checked" />
</template>
</wd-cell>
</wd-cell-group>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const checked = ref(false)
const handleClick = () => {
uni.navigateTo({
url: '/pages/detail/detail'
})
}
</script>List - 列表
vue
<template>
<view>
<wd-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<wd-cell
v-for="item in list"
:key="item.id"
:title="item.title"
:label="item.desc"
/>
</wd-list>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref<any[]>([])
const loading = ref(false)
const finished = ref(false)
const page = ref(1)
const onLoad = async () => {
// 模拟加载数据
const newData = Array.from({ length: 10 }, (_, index) => ({
id: (page.value - 1) * 10 + index + 1,
title: `标题 ${(page.value - 1) * 10 + index + 1}`,
desc: '这是描述信息'
}))
list.value.push(...newData)
loading.value = false
page.value++
// 数据全部加载完成
if (list.value.length >= 40) {
finished.value = true
}
}
</script>5. 导航组件
Navbar - 导航栏
vue
<template>
<view>
<wd-navbar
title="标题"
left-text="返回"
left-arrow
@click-left="handleBack"
>
<template #right>
<wd-icon name="search" @click="handleSearch" />
</template>
</wd-navbar>
<view class="content">
<!-- 页面内容 -->
</view>
</view>
</template>
<script setup lang="ts">
const handleBack = () => {
uni.navigateBack()
}
const handleSearch = () => {
uni.navigateTo({
url: '/pages/search/search'
})
}
</script>Tabbar - 标签栏
通常在 pages.json 中配置,也可以用组件方式:
vue
<template>
<wd-tabbar v-model="active" fixed placeholder>
<wd-tabbar-item icon="home-outline" title="首页" />
<wd-tabbar-item icon="category-outline" title="分类" />
<wd-tabbar-item icon="cart-outline" title="购物车" :badge="3" />
<wd-tabbar-item icon="user-outline" title="我的" :dot="true" />
</wd-tabbar>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const active = ref(0)
watch(active, (index) => {
const pages = [
'/pages/index/index',
'/pages/category/category',
'/pages/cart/cart',
'/pages/user/user'
]
uni.switchTab({
url: pages[index]
})
})
</script>6. 其他组件
Upload - 上传
vue
<template>
<wd-upload
v-model="fileList"
:limit="9"
:max-size="10 * 1024 * 1024"
:action="uploadUrl"
:headers="headers"
@success="handleSuccess"
@error="handleError"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const fileList = ref<any[]>([])
const uploadUrl = ref('https://api.youlai.tech/api/v1/files/upload')
const headers = ref({
Authorization: 'Bearer ' + uni.getStorageSync('token')
})
const handleSuccess = (res: any) => {
console.log('上传成功', res)
}
const handleError = (err: any) => {
console.error('上传失败', err)
uni.showToast({
title: '上传失败',
icon: 'none'
})
}
</script>Dialog - 对话框
vue
<template>
<view>
<wd-button @click="show = true">打开对话框</wd-button>
<wd-dialog
v-model="show"
title="提示"
@confirm="handleConfirm"
@cancel="show = false"
>
<view style="padding: 20rpx;">
这是对话框内容
</view>
</wd-dialog>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const show = ref(false)
const handleConfirm = () => {
console.log('确认')
show.value = false
}
</script>自定义组件
创建自定义组件
UserCard - 用户卡片组件
vue
<!-- components/UserCard.vue -->
<template>
<view class="user-card" @click="handleClick">
<image :src="user.avatar" class="avatar" />
<view class="info">
<view class="name">{{ user.name }}</view>
<view class="role">{{ user.roleName }}</view>
</view>
<wd-icon name="arrow-right" size="16px" color="#999" />
</view>
</template>
<script setup lang="ts">
interface User {
id: string
avatar: string
name: string
roleName: string
}
defineProps<{
user: User
}>()
const emit = defineEmits<{
click: []
}>()
const handleClick = () => {
emit('click')
}
</script>
<style scoped lang="scss">
.user-card {
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
border-radius: 10rpx;
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.info {
flex: 1;
.name {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.role {
font-size: 24rpx;
color: #999;
}
}
}
</style>使用自定义组件
vue
<template>
<view>
<UserCard
v-for="user in list"
:key="user.id"
:user="user"
@click="handleUserClick(user)"
/>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import UserCard from '@/components/UserCard.vue'
const list = ref([
{ id: '1', avatar: '/static/avatar1.png', name: '张三', roleName: '管理员' },
{ id: '2', avatar: '/static/avatar2.png', name: '李四', roleName: '普通用户' }
])
const handleUserClick = (user: any) => {
uni.navigateTo({
url: `/pages/user/detail?id=${user.id}`
})
}
</script>最佳实践
1. 组件封装原则
- 单一职责:一个组件只做一件事
- 可复用:通过 props 配置实现灵活性
- 低耦合:组件间通过事件通信
- 易维护:代码简洁,注释清晰
2. 性能优化
- 使用
v-if而不是v-show进行条件渲染 - 长列表使用虚拟滚动或分页加载
- 图片使用懒加载
- 合理使用
computed缓存计算结果
3. 样式规范
- 使用
rpx作为尺寸单位 - 统一使用 SCSS 预处理器
- 遵循 BEM 命名规范
- 提取公共样式变量
4. TypeScript 类型
typescript
// types/user.ts
export interface User {
id: string
username: string
nickname: string
avatar?: string
phone?: string
email?: string
status: number
createTime: string
}
export interface UserPageQuery {
pageNum: number
pageSize: number
keywords?: string
status?: number
}