Skip to content

组件系统概览

介绍

RuoYi-Plus-UniApp 移动端提供了完整的组件系统,由 WD UI 组件库自定义业务组件组成。WD UI 基于 Wot Design Uni 深度改造,使用 Vue 3 + TypeScript + Composition API 构建,提供 90+ 个高质量组件。

核心特性:

  • 丰富完整 - 90+ 个组件,覆盖基础、布局、导航、表单、展示、反馈全场景
  • 类型安全 - 完整的 TypeScript 类型定义
  • 统一规范 - 组件命名、Props、Events、Slots 遵循统一设计规范
  • 主题定制 - 支持 CSS 变量自定义样式和主题
  • 跨平台兼容 - 支持微信小程序、H5、App 等多平台

目录结构

src/
├── wd/                          # WD UI 组件库
│   ├── components/              # 组件实现
│   │   ├── common/             # 公共工具模块
│   │   ├── composables/        # 组合式函数
│   │   └── wd-button/          # 组件目录
│   ├── locale/                 # 国际化语言包
│   └── index.ts                # 统一导出
├── components/                  # 自定义业务组件
│   ├── auth/                   # 认证组件
│   └── tabbar/                 # 标签栏组件
└── pages/                       # 页面组件

组件分类

基础组件 (6个)

组件说明主要用途
Button按钮触发操作、提交表单
Icon图标展示图标
Text文本格式化文本展示
Transition过渡动画元素动画效果
Resize尺寸监听监听元素尺寸变化
ConfigProvider全局配置配置默认属性和主题

布局组件 (7个)

组件说明主要用途
Layout布局容器Row/Col 栅格布局
Grid宫格图标导航、功能入口
Cell单元格列表项展示
Divider分割线内容分隔
Space间距元素间距管理
Row行容器栅格布局行
Col列容器栅格布局列

导航组件 (13个)

组件说明主要用途
Navbar导航栏页面顶部导航
Tabbar标签栏底部导航切换
Tabs标签页内容分类切换
Sidebar侧边导航侧边分类导航
IndexBar索引栏列表索引
Steps步骤条流程进度展示
Pagination分页数据分页
Sticky粘性布局固定元素定位
Backtop回到顶部快速返回顶部
DropMenu下拉菜单筛选条件选择

表单组件 (22个)

组件说明主要用途
Input输入框文本输入
Textarea文本域多行文本输入
Radio/RadioGroup单选框单选选择
Checkbox/CheckboxGroup复选框多选选择
Switch开关开关切换
Rate评分星级评分
Slider滑块数值范围选择
Stepper步进器数值增减
Picker选择器单列/多列选择
DatetimePicker时间选择日期时间选择
Calendar日历日期范围选择
Upload上传文件/图片上传
Search搜索搜索输入
Form表单表单容器和验证

展示组件 (18个)

组件说明主要用途
Tag标签标签展示
Badge徽标数字角标
Progress进度条进度展示
Circle环形进度环形进度展示
Image图片图片展示、懒加载
Swiper轮播图片轮播
Collapse折叠面板内容折叠展开
NoticeBar通知栏滚动通知
CountDown倒计时倒计时展示
Card卡片卡片容器
Skeleton骨架屏加载占位
StatusTip状态提示空态、错误提示

反馈组件 (24个)

组件说明主要用途
Toast轻提示操作结果提示
Loading加载加载状态展示
MessageBox消息弹窗确认、提示弹窗
ActionSheet动作面板操作选项选择
Popup弹出层自定义弹出内容
SwipeAction滑动操作列表项滑动操作
Notify通知顶部通知消息
Overlay遮罩层遮罩背景
Popover气泡气泡提示框
Fab浮动按钮悬浮操作按钮
PullRefresh下拉刷新列表下拉刷新

组件使用

全局注册(推荐)

通过 easycom 机制自动全局注册,无需手动导入:

json
// pages.json
{
  "easycom": {
    "autoscan": true,
    "custom": {
      "^wd-(.*)": "@/wd/components/wd-$1/wd-$1.vue"
    }
  }
}
vue
<template>
  <!-- 直接使用,无需导入 -->
  <wd-button type="primary" @click="handleClick">按钮</wd-button>
  <wd-input v-model="value" placeholder="请输入" />
</template>

<script lang="ts" setup>
import { ref } from 'vue'
const value = ref('')
const handleClick = () => console.log('clicked')
</script>

组合式函数

vue
<script lang="ts" setup>
import { useToast, useMessage, useNotify } from '@/wd'

const toast = useToast()
const message = useMessage()
const notify = useNotify()

// Toast
toast.success('操作成功')
toast.error('操作失败')
toast.loading('加载中...')

// Message
const result = await message.confirm({
  title: '提示',
  content: '确定删除吗?'
})

// Notify
notify.success('成功提示')
</script>

工具函数

typescript
import { dayjs, CommonUtil } from '@/wd'

const now = dayjs()
const formatted = dayjs().format('YYYY-MM-DD HH:mm:ss')

Props 系统

定义规范

typescript
interface WdButtonProps {
  /** 按钮类型 */
  type?: 'primary' | 'success' | 'info' | 'warning' | 'error' | 'default'
  /** 按钮尺寸 */
  size?: 'small' | 'medium' | 'large'
  /** 是否禁用 */
  disabled?: boolean
  /** 是否加载中 */
  loading?: boolean
}

const props = withDefaults(defineProps<WdButtonProps>(), {
  type: 'default',
  size: 'medium',
  disabled: false,
  loading: false
})

常见类型

typescript
interface ComponentProps {
  // 基础类型
  title?: string
  count?: number
  disabled?: boolean
  items?: string[]

  // 枚举类型
  type?: 'primary' | 'success' | 'warning' | 'error'

  // 样式类型
  customStyle?: string
  customClass?: string
}

Events 系统

定义规范

typescript
interface WdButtonEmits {
  click: [event: Event]
  longpress: [event: Event]
}

const emit = defineEmits<WdButtonEmits>()

const handleClick = (event: Event) => {
  emit('click', event)
}

使用示例

vue
<template>
  <wd-button @click="handleClick" @longpress="handleLongpress">按钮</wd-button>
  <wd-input v-model="value" @input="handleInput" @change="handleChange" />
</template>

<script lang="ts" setup>
const handleClick = (event: Event) => console.log('clicked', event)
const handleInput = (value: string) => console.log('input', value)
</script>

Slots 系统

默认插槽

vue
<wd-button type="primary">提交表单</wd-button>

<wd-cell title="标题">
  <template #default>自定义内容</template>
</wd-cell>

具名插槽

vue
<wd-cell>
  <template #title>
    <view class="custom-title">
      <wd-icon name="user" />
      <text>用户信息</text>
    </view>
  </template>
  <template #icon>
    <wd-icon name="setting" color="#ff0000" />
  </template>
</wd-cell>

作用域插槽

vue
<wd-picker :columns="columns">
  <template #option="{ item, index }">
    <view class="custom-option">{{ index + 1 }}. {{ item.label }}</view>
  </template>
</wd-picker>

组件通信

Props / Events

vue
<!-- 父组件 -->
<child-component :user="user" @update="handleUpdate" />

<!-- 子组件 -->
<script lang="ts" setup>
const props = defineProps<{ user: User }>()
const emit = defineEmits<{ update: [user: User] }>()
</script>

v-model 双向绑定

vue
<wd-input v-model="username" />

<!-- 多个 v-model -->
<custom-form v-model:username="form.username" v-model:password="form.password" />

Provide / Inject

vue
<!-- 祖先组件 -->
<script lang="ts" setup>
import { provide } from 'vue'
const theme = ref('light')
provide('theme', theme)
</script>

<!-- 后代组件 -->
<script lang="ts" setup>
import { inject } from 'vue'
const theme = inject('theme')
</script>

Ref 获取组件实例

vue
<template>
  <wd-form ref="formRef" :model="form">
    <wd-input v-model="form.username" prop="username" />
  </wd-form>
</template>

<script lang="ts" setup>
import type { FormInstance } from '@/wd'

const formRef = ref<FormInstance>()

const handleSubmit = async () => {
  await formRef.value?.validate()
}
</script>

Pinia 状态管理

typescript
// stores/user.ts
export const useUserStore = defineStore('user', () => {
  const userInfo = ref<User | null>(null)
  const isLoggedIn = computed(() => !!userInfo.value)

  const login = async (username: string, password: string) => {
    // 登录逻辑
  }

  return { userInfo, isLoggedIn, login }
})

自定义组件开发

组件模板

vue
<template>
  <view :class="rootClass" :style="rootStyle">
    <slot />
  </view>
</template>

<script lang="ts" setup>
import { computed } from 'vue'

defineOptions({
  name: 'CustomComponent',
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: 'shared'
  }
})

interface Props {
  type?: 'default' | 'primary'
  disabled?: boolean
  customStyle?: string
  customClass?: string
}

const props = withDefaults(defineProps<Props>(), {
  type: 'default',
  disabled: false
})

const emit = defineEmits<{
  click: [event: Event]
}>()

const rootClass = computed(() => [
  'custom-component',
  `custom-component--${props.type}`,
  { 'custom-component--disabled': props.disabled },
  props.customClass
])

const rootStyle = computed(() => props.customStyle)

defineExpose({
  // 暴露方法
})
</script>

性能优化

按需渲染

vue
<!-- 频繁切换用 v-show -->
<wd-popup v-show="showPopup" />

<!-- 条件渲染用 v-if -->
<view v-if="userType === 'admin'" />

列表优化

vue
<wd-paging ref="pagingRef" :fetch-api="fetchList" :page-size="20">
  <template #default="{ item }">
    <wd-cell :title="item.name" />
  </template>
</wd-paging>

计算属性缓存

typescript
// 推荐:使用计算属性
const filteredList = computed(() => list.value.filter(item => item.active))

// 不推荐:使用方法
const getFilteredList = () => list.value.filter(item => item.active)

组件懒加载

typescript
const HeavyComponent = defineAsyncComponent(
  () => import('@/components/heavy-component/index.vue')
)

最佳实践

单一职责

vue
<!-- 好:职责单一 -->
<user-avatar :avatar="user.avatar" />
<user-info :user="user" />

<!-- 不好:职责混乱 -->
<user-profile :user="user" :show-avatar="true" :show-info="true" />

Props 设计

typescript
// 提供合理默认值
const props = withDefaults(defineProps<ButtonProps>(), {
  type: 'default',
  size: 'medium',
  disabled: false
})

// 使用描述性名称
interface Props {
  buttonText: string   // 好
  isLoading: boolean   // 好
  text: string         // 不好
  flag: boolean        // 不好
}

样式规范

scss
// 使用 BEM 命名
.wd-button {
  &__icon { }
  &--primary { }
  &--disabled { }
}

// 使用 CSS 变量
.wd-button {
  background-color: var(--wd-button-bg-color, #ffffff);
}

// 使用 rpx 单位
.component {
  padding: 32rpx;
  font-size: 28rpx;
}

常见问题

组件不显示

检查 easycom 配置:

json
{
  "easycom": {
    "custom": {
      "^wd-(.*)": "@/wd/components/wd-$1/wd-$1.vue"
    }
  }
}

v-model 不生效

确保组件正确实现:

vue
<script lang="ts" setup>
interface Props {
  modelValue: string  // 必须是 modelValue
}
interface Emits {
  'update:modelValue': [value: string]  // 必须是 update:modelValue
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
</script>

ref 获取不到实例

使用 defineExpose 暴露方法:

vue
<script lang="ts" setup>
const validate = () => { }
defineExpose({ validate })  // 必须显式暴露
</script>

平台差异

使用条件编译:

vue
<!-- #ifdef MP-WEIXIN -->
<button open-type="getUserInfo">获取用户信息</button>
<!-- #endif -->

<!-- #ifdef H5 -->
<button @click="handleLogin">登录</button>
<!-- #endif -->

统一使用 rpx 单位:

scss
.component {
  width: 750rpx;   // 推荐
  // width: 375px; // 避免
}