第3章 3.3 uView UI 组件库:让界面开发快 10 倍
🎯 开场:为什么你的页面写得那么慢?
上一章我们聊了 uni.$emit 和 mitt,学会了跨页面传递消息的技巧。小明兴奋地给班长展示了他的「班级通讯录」 demo,结果班长一看皱眉头:
「功能是有了,但这界面也太丑了吧?按钮灰不溜秋的,表格歪歪扭扭……」
小明回来问我:「老师,我 CSS 也不太好,有没有办法不写样式代码,快速做出好看的页面?」
有!这就是今天要学的 uView UI 组件库。
你有没有遇到过这些头疼的情况?
- 想做个登录页,光调输入框样式就花了半小时
- 做个表格展示数据,在不同手机上样式乱成一团
- 想弹个对话框确认,原生 API 写起来贼复杂
uView 就是来解决这些问题的——别人写好的好看组件,拿来直接用。就像你去超市买半成品菜,回家热一下就能吃,不用从种菜开始。
学完这章,你就能:
1. 5 分钟搭出一个带表单验证的登录页
2. 10 分钟做出一个可以增删改\n\n
\n\n
\n\n查的表格
3. 3 行代码弹出一个漂亮的确认框
🧱 基础:uView 是什么?怎么用?
3.3.1 uView 安装:比泡面还简单
uView 是 uniapp 生态里最受欢迎的 UI 组件库,组件丰富、文档清晰、样式好看。目前主流用 uView 2.x 版本。
方式一:Vue 2 项目(HBuliderX 可视化)
打开 HBuilderX → 找到项目 → 右键 → 【使用 npm 安装依赖】→ 运行命令:
npm init -y
npm install uview-ui
方式二:Vue 3 项目
npm install uview-ui@next
装完之后,需要引入并注册。找到 main.js,添加这几行:
// main.js
import App from './App'
// ⬇️ 引入 uView
import uView from 'uview-ui'
Vue.use(uView)
const app = new Vue({
...App
})
app.$mount()
配置 easycom(让组件自动可用)
找到 pages.json,在 globalStyle 同级添加:
{
"easycom": {
"autoscan": true,
"custom": {
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
}
}
}
💡 说白了:这一步就是在告诉小程序「看到
u-开头的标签,就是 uView 的组件,直接拿来用就行」
3.3.2 第一个组件:uButton 按钮
最基础的组件,先从按钮开始找感觉。
<template>
<view class="container">
<!-- 普通按钮 -->
<uButton text="点我试试" @click="handleClick"></uButton>
<!-- 带主题色的按钮 -->
<uButton text="主要按钮" type="primary" style="margin-top: 20rpx;"></uButton>
<!-- 镂空按钮 -->
<uButton text="次要按钮" type="info" plain style="margin-top: 20rpx;"></uButton>
</view>
</template>
<script>
export default {
methods: {
handleClick() {
uni.showToast({ title: '按钮被点击了!', icon: 'none' });
}
}
}
</script>
代码解释:
- text 属性:按钮上显示的文字
- type="primary":使用主题色(蓝色)
- plain:镂空效果,背景透明
🎯 敲黑板:uView 的组件都遵循「属性配置样式」的原则,不用写 CSS,直接改参数就行
3.3.3 表单利器:u-form 和 u-input
想做登录页?表单是灵魂。uView 的表单组件自带验证功能,比手写 validation 省心 100 倍。
<template>
<view class="container">
<u-form :model="form" :rules="rules" ref="formRef">
<!-- 用户名输入框 -->
<u-form-item label="用户名" prop="username">
<u-input v-model="form.username" placeholder="请输入用户名" />
</u-form-item>
<!-- 密码输入框 -->
<u-form-item label="密码" prop="password">
<u-input v-model="form.password" type="password" placeholder="请输入密码" />
</u-form-item>
<!-- 提交按钮 -->
<uButton type="primary" @click="submitForm" style="margin-top: 40rpx;">
登录
</uButton>
</u-form>
</view>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
password: ''
},
// 验证规则
rules: {
username: [
{ required: true, message: '请填写用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请填写密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' }
]
}
}
},
methods: {
submitForm() {
this.$refs.formRef.validate().then(res => {
uni.showToast({ title: '验证通过,提交成功!', icon: 'success' });
}).catch(err => {
uni.showToast({ title: '请检查输入', icon: 'error' });
});
}
}
}
</script>
这段代码干了啥:
1. u-form 包住整个表单,设置验证规则
2. u-form-item 是每个表单项,自动带 label 标签
3. u-input 是输入框,type="password" 自动变成密码样式(隐藏字符)
4. rules 里写验证规则,required: true 就是「必填」
🎯 说白了:uView 的表单验证就是你告诉它「这个要填」「那个至少6位」,然后点提交时它自动帮你检查,不用自己写 if 判断
3.3.4 数据展示:u-table 表格
如果你是班长,要展示班级成绩单,表格组件就派上用场了。
<template>
<view class="container">
<u-table>
<u-tr>
<u-th>姓名</u-th>
<u-th>语文</u-th>
<u-th>数学</u-th>
<u-th>总分</u-th>
</u-tr>
<u-tr v-for="(student, index) in students" :key="index">
<u-td>{{ student.name }}</u-td>
<u-td>{{ student.chinese }}</u-td>
<u-td>{{ student.math }}</u-td>
<u-td>{{ student.chinese + student.math }}</u-td>
</u-tr>
</u-table>
</view>
</template>
<script>
export default {
data() {
return {
students: [
{ name: '张小明', chinese: 92, math: 88 },
{ name: '李华', chinese: 85, math: 95 },
{ name: '王小红', chinese: 78, math: 82 }
]
}
}
}
</script>
💡 小技巧:
u-th是表头(加粗),u-td是数据格子,v-for循环渲染每一行
3.3.5 弹窗必备:u-popup 弹出层
想做一个「删除确认」弹窗?3 行代码搞定:
<template>
<view class="container">
<uButton @click="showPopup = true">删除</uButton>
<u-popup mode="center" :show="showPopup" @close="showPopup = false">
<view style="padding: 40rpx; text-align: center;">
<text>确定要删除吗?</text>
<view style="margin-top: 30rpx;">
<uButton size="small" @click="showPopup = false">取消</uButton>
<uButton size="small" type="error" @click="doDelete">确定</uButton>
</view>
</view>
</u-popup>
</view>
</template>
<script>
export default {
data() {
return {
showPopup: false
}
},
methods: {
doDelete() {
this.showPopup = false
uni.showToast({ title: '删除成功', icon: 'success' })
}
}
}
</script>
mode="center":从屏幕中间弹出:show="showPopup":绑定一个布尔值控制显示/隐藏@close:点击遮罩层或调用方法时触发
🔥 实战:3 个项目练手
📦 项目 1:5 分钟做一个「设置页面」
目标:用 uView 组件快速搭一个个人设置页面,包含头像、昵称、性别选择、退出按钮。
<template>
<view class="container">
<!-- 头像区域 -->
<view class="avatar-section">
<u-avatar size="120" src="https://via.placeholder.com/120"></u-avatar>
<text class="nickname">{{ nickname }}</text>
</view>
<!-- 设置列表 -->
<u-cell-group>
<u-cell icon="edit" title="修改昵称" :value="nickname" is-link></u-cell>
<u-cell icon="man" title="性别" :value="genderText" is-link @click="changeGender"></u-cell>
<u-cell icon="bell" title="消息通知" value="已开启" is-link></u-cell>
<u-cell icon="info" title="关于我们" is-link></u-cell>
</u-cell-group>
<!-- 退出按钮 -->
<uButton type="error" plain style="margin: 40rpx;" @click="logout">
退出登录
</uButton>
</view>
</template>
<script>
export default {
data() {
return {
nickname: '小明同学',
gender: 'male'
}
},
computed: {
genderText() {
return this.gender === 'male' ? '男' : '女'
}
},
methods: {
changeGender() {
uni.showActionSheet({
itemList: ['男', '女'],
success: (res) => {
this.gender = res.tapIndex === 0 ? 'male' : 'female'
}
})
},
logout() {
uni.showModal({
title: '提示',
content: '确定要退出吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '已退出', icon: 'none' })
}
}
})
}
}
}
</script>
<style scoped>
.container { padding: 20rpx; }
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 0;
background: linear-gradient(#007aff, #4a90e2);
border-radius: 0 0 40rpx 40rpx;
margin-bottom: 20rpx;
}
.nickname {
color: #fff;
font-size: 32rpx;
margin-top: 20rpx;
}
</style>
预期效果:一个渐变背景的顶部个人卡片 + 带图标的设置列表 + 红色退出按钮
🎯 这个项目用到的组件:
u-avatar、u-cell-group、u-cell、u-button,都是 uView 的常用组件
📦 项目 2:10 分钟做一个「商品列表」
目标:从 JSON 数据读取商品列表,点击加入购物车,弹出成功提示。
<template>
<view class="container">
<view class="title">今日推荐</view>
<!-- 商品列表 -->
<view class="goods-list">
<view class="goods-item" v-for="goods in goodsList" :key="goods.id">
<image class="goods-image" :src="goods.image" mode="aspectFill"></image>
<view class="goods-info">
<text class="goods-name">{{ goods.name }}</text>
<text class="goods-desc">{{ goods.desc }}</text>
<view class="goods-bottom">
<text class="goods-price">¥{{ goods.price }}</text>
<u-button size="mini" type="primary" @click="addCart(goods)">
加购
</u-button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
goodsList: [
{
id: 1,
name: '新疆阿克苏苹果',
desc: '脆甜多汁,产地直发',
price: 39.9,
image: 'https://via.placeholder.com/200x200?text=苹果'
},
{
id: 2,
name: '东北五常大米',
desc: '香糯软滑,稻花香品种',
price: 68.0,
image: 'https://via.placeholder.com/200x200?text=大米'
},
{
id: 3,
name: '云南高原蜂蜜',
desc: '纯天然野生蜂蜜',
price: 88.8,
image: 'https://via.placeholder.com/200x200?text=蜂蜜'
}
]
}
},
methods: {
addCart(goods) {
uni.showToast({
title: `已加入购物车:${goods.name}`,
icon: 'none',
duration: 2000
})
}
}
}
</script>
<style scoped>
.container { padding: 20rpx; }
.title {
font-size: 36rpx;
font-weight: bold;
padding: 20rpx 0;
}
.goods-list { display: flex; flex-direction: column; gap: 20rpx; }
.goods-item {
display: flex;
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}
.goods-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
}
.goods-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
}
.goods-name { font-size: 30rpx; font-weight: bold; }
.goods-desc { font-size: 24rpx; color: #999; margin-top: 10rpx; }
.goods-bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
.goods-price { font-size: 32rpx; color: #ff6b6b; font-weight: bold; }
</style>
预期效果:竖向列表展示商品卡片,包含图片、名称、描述、价格和加购按钮
📦 项目 3:15 分钟做一个「待办清单」
目标:结合表单和弹窗,做一个可以添加、完成、删除待办事项的小工具。
<template>
<view class="container">
<!-- 顶部标题 -->
<view class="header">
<text class="title">我的待办</text>
<text class="count">{{ doneCount }}/{{ todoList.length }}</text>
</view>
<!-- 待办列表 -->
<view class="todo-list">
<view
class="todo-item"
v-for="(todo, index) in todoList"
:key="index"
:class="{ completed: todo.done }"
>
<u-checkbox-group>
<u-checkbox
:checked="todo.done"
@change="toggleTodo(index)"
></u-checkbox>
</u-checkbox-group>
<text class="todo-text">{{ todo.text }}</text>
<u-button size="mini" type="error" plain @click="deleteTodo(index)">
删除
</u-button>
</view>
</view>
<!-- 空状态提示 -->
<view v-if="todoList.length === 0" class="empty">
<text>还没有待办,添加一个吧~</text>
</view>
<!-- 底部添加区域 -->
<view class="add-section">
<u-input
v-model="newTodo"
placeholder="输入新待办..."
:focus="true"
></u-input>
<u-button type="primary" @click="addTodo">添加</u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
todoList: [
{ text: '完成第三章练习题', done: false },
{ text: '整理笔记', done: true },
{ text: '复习跨页通讯', done: false }
],
newTodo: ''
}
},
computed: {
doneCount() {
return this.todoList.filter(t => t.done).length
}
},
methods: {
addTodo() {
if (!this.newTodo.trim()) {
uni.showToast({ title: '内容不能为空', icon: 'none' })
return
}
this.todoList.push({
text: this.newTodo,
done: false
})
this.newTodo = ''
uni.showToast({ title: '添加成功', icon: 'success' })
},
toggleTodo(index) {
this.todoList[index].done = !this.todoList[index].done
},
deleteTodo(index) {
uni.showModal({
title: '确认删除',
content: '确定删除这条待办?',
success: (res) => {
if (res.confirm) {
this.todoList.splice(index, 1)
uni.showToast({ title: '已删除', icon: 'success' })
}
}
})
}
}
}
</script>
<style scoped>
.container {
min-height: 100vh;
background: #f5f5f5;
padding: 20rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
}
.title { font-size: 36rpx; font-weight: bold; }
.count { color: #999; }
.todo-list { display: flex; flex-direction: column; gap: 16rpx; }
.todo-item {
display: flex;
align-items: center;
background: #fff;
padding: 24rpx;
border-radius: 12rpx;
gap: 16rpx;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #999;
}
.todo-text { flex: 1; font-size: 28rpx; }
.empty {
text-align: center;
padding: 100rpx 0;
color: #999;
}
.add-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
gap: 16rpx;
padding: 20rpx;
background: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
}
</style>
预期效果:
- 顶部显示标题和完成进度(2/3)
- 中间是待办列表,可勾选完成、可删除
- 底部固定输入框和添加按钮
- 空列表时显示友好提示
🎯 这个项目综合运用了:表单组件(u-input、u-button)、列表渲染(v-for)、状态管理(done 标记)、确认对话框(uni.showModal)
💪 进阶:3 个坑 + 1 个优化 + 1 个调试技巧
❌ 坑 1:组件样式不生效?
错误写法:
<u-button class="my-btn">按钮</u-button>
<style>
.my-btn { background: red !important; } /* 不生效 */
</style>
正确写法:
<u-button custom-class="my-btn">按钮</u-button>
<style>
/* 使用 uView 提供的类名或者直接用内联样式 */
.my-btn { background: red !important; }
</style>
💡 uView 组件内部有自己的样式作用域,要用
custom-class属性来覆盖
❌ 坑 2:popup 关闭后数据没更新?
错误写法:
// 弹窗关闭后,数据还是旧的
this.showPopup = false
this.loadData() // 异步请求可能还没完成
正确写法:
// 等待动画完成后再操作
this.showPopup = false
setTimeout(() => {
this.loadData()
}, 300)
或者用 uView 提供的 @close 事件:
<u-popup @close="onPopupClose">
<script>
methods: {
onPopupClose() {
this.loadData()
}
}
</script>
❌ 坑 3:form 验证不触发?
错误写法:
<u-form :model="form" :rules="rules" ref="formRef">
<u-form-item label="邮箱" prop="email">
<u-input v-model="form.email"></u-input>
</u-form-item>
</u-form>
正确写法:
rules: {
email: [
{ required: true, message: '请输入邮箱', trigger: ['blur', 'change'] }
]
}
💡 验证规则要指定
trigger,而且输入框失焦和变化时都可能触发
⚡ 性能优化:列表渲染用 key
如果你的列表数据量大(超过 100 条),记得给 v-for 加 key:
<!-- ✅ 好习惯 -->
<u-tr v-for="(student, index) in students" :key="student.id">
<!-- ❌ 大数据量时会很慢 -->
<u-tr v-for="(student, index) in students">
🔍 调试技巧:console.log 配合 easycom
uView 组件虽然好用,但出问题时不方便调试。这时候可以给组件外层包一个临时 view:
<!-- 临时调试:看看组件接收了什么 props -->
<view>{{ form }}</view>
<u-form :model="form">
...
</u-form>
或者在 methods 里打印关键变量:
methods: {
submitForm() {
console.log('表单数据:', this.form)
console.log('验证规则:', this.rules)
// ... 验证逻辑
}
}
✏️ 练习题
练习 1(2 分钟):改按钮文字
- 输入:在项目 1 的基础上,把「退出登录」按钮改成「修改密码」
- 预期输出:页面显示「修改密码」按钮
- 提示:找到 uButton 的 text 属性
练习 2(3 分钟):加个判断
- 输入:在项目 2 的 addCart 方法里,如果价格大于 50 元,弹出「价格有点贵哦」提示
- 预期输出:点击 68 元的大米时,弹出额外提示
- 提示:用 if (goods.price > 50) 判断
练习 3(5 分钟):换个数据源
- 输入:用以下 JSON 替换项目 2 的商品数据:
[
{ id: 1, name: '纯棉T恤', desc: '透气舒适', price: 59.9, image: 'https://via.placeholder.com/200' },
{ id: 2, name: '运动短裤', desc: '速干面料', price: 89.0, image: 'https://via.placeholder.com/200' }
]
- 预期输出:列表显示新商品而非原来的水果
- 提示:直接替换
goodsList的值
练习 4(10 分钟):串起两个项目
- 输入:把项目 2(商品列表)和项目 3(待办清单)合并成一个页面——添加待办时自动保存到本地存储(uni.setStorageSync),页面加载时读取(uni.getStorageSync)
- 预期输出:刷新页面后待办不丢失
- 提示:用 uni.setStorageSync('todos', this.todoList) 保存
练习 5(5 分钟):读图找错
- 假设你运行代码后看到以下报错截图(报错信息:Cannot read property 'validate' of undefined)
- 问题:哪里写错了?怎么修?
- 提示:检查 ref="formRef" 和 this.$refs.formRef 是否匹配
📚 作业:做一个「通讯录管理」
需求:做一个简易通讯录,可以:
1. 展示通讯录(用 u-table 或 u-cell-group)
2. 添加联系人(弹出 u-popup 包含表单:姓名、手机号)
3. 删除联系人(滑动或点击删除,需确认)
4. 本地持久化(关闭再打开,数据不丢失)
加分项:
- 编辑联系人功能
- 按姓名搜索过滤
验收标准:
- 能添加、查看、删除联系人
- 数据保存到本地存储
- 界面用 uView 组件,有基本样式
提交方式:评论区贴核心代码或 GitHub 链接
🎯 总结
这一章我们学了 3 个核心要点:
- uView 安装引入:npm 安装 + easycom 配置 = 5 分钟搞定
- 核心组件:u-button、u-form、u-table、u-popup 覆盖 80% 常见需求
- 组合用法:表单验证 + 列表渲染 + 弹窗确认 = 真实场景小工具
💡 下回我们要聊的是 uni-ui 官方组件库——它和 uView 有什么区别?什么时候该用哪个?学完你就清楚了!
推荐资源:
- uView 官方文档(组件演示超全)
- uView GitHub(有问题可以提 Issue)
- 视频教程:B 站搜索「uView 组件库教程」
互动时间:你在项目里用过 uView 的哪个组件觉得最好用?我个人最喜欢 u-popup,弹窗效果比原生漂亮多了。欢迎在评论区聊聊,老粉优先回复!

评论(0)