Skip to content

移动端组件使用

本文档介绍 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-uni

2. 配置自动引入

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. 导航组件

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
}

相关资源

下一步

基于 MIT 许可发布