📚 uni-app组件 ------ 知识点详解与实战案例
✅ 本章学习目标
- 掌握 uni-app 常用容器组件:
scroll-view
、swiper
- 熟悉基础组件:
rich-text
、progress
- 掌握表单组件:
button
、input
、picker
、slider
、radio
、checkbox
、switch
、textarea
、form
- 了解媒体组件:
camera
、video
、map
- 能独立构建典型页面:注册页、个人中心页
- 理解组件通信、数据绑定、事件处理机制
🧱 一、容器组件
1. scroll-view 滚动视图
可滚动的视图容器,支持横向/纵向滚动、下拉刷新、上拉加载等。
🔧 语法结构:
html
<scroll-view
scroll-y="true" <!-- 纵向滚动 -->
scroll-x="false" <!-- 横向滚动 -->
scroll-top="0" <!-- 滚动顶部距离 -->
scroll-left="0" <!-- 滚动左侧距离 -->
@scroll="handleScroll" <!-- 滚动事件 -->
@scrolltoupper="onUpper" <!-- 滚动到顶部 -->
@scrolltolower="onLower" <!-- 滚动到底部 -->
>
<!-- 内容 -->
</scroll-view>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
scroll-y | Boolean | false | 是否允许纵向滚动 |
scroll-x | Boolean | false | 是否允许横向滚动 |
scroll-top | Number | 0 | 设置竖向滚动条位置 |
scroll-left | Number | 0 | 设置横向滚动条位置 |
enable-back-to-top | Boolean | false | 是否支持点击顶部回到顶部 |
💡 案例:纵向滚动列表 + 上拉加载更多
vue
<template>
<view class="container">
<scroll-view
scroll-y="true"
@scrolltolower="loadMore"
:scroll-top="scrollTop"
style="height: 100vh; padding: 20rpx;"
>
<view v-for="(item, index) in dataList" :key="index" class="item">
{{ item }}
</view>
<view v-if="loading" class="loading">正在加载...</view>
<view v-if="noMore" class="no-more">没有更多了</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
dataList: ['第1项', '第2项', '第3项'],
loading: false,
noMore: false,
scrollTop: 0
}
},
methods: {
loadMore() {
if (this.loading || this.noMore) return;
this.loading = true;
setTimeout(() => {
const newData = [];
for (let i = 0; i < 5; i++) {
newData.push(`新增第${this.dataList.length + 1 + i}项`);
}
this.dataList = [...this.dataList, ...newData];
this.loading = false;
if (this.dataList.length >= 20) {
this.noMore = true;
}
}, 1000);
}
}
}
</script>
<style scoped>
.item {
height: 80rpx;
line-height: 80rpx;
background: #f0f0f0;
margin: 10rpx 0;
padding: 0 20rpx;
border-radius: 10rpx;
}
.loading, .no-more {
text-align: center;
padding: 20rpx;
color: #999;
}
</style>
2. swiper 轮播图
用于实现轮播图效果,支持自动播放、指示器、循环播放等。
🔧 语法结构:
html
<swiper
autoplay="true" <!-- 自动播放 -->
interval="3000" <!-- 自动切换时间间隔 -->
duration="500" <!-- 滑动动画时长 -->
circular="true" <!-- 循环播放 -->
indicator-dots="true" <!-- 显示指示点 -->
@change="handleChange" <!-- 切换事件 -->
>
<swiper-item v-for="(item, index) in images" :key="index">
<image :src="item" mode="aspectFill" style="width: 100%; height: 100%;" />
</swiper-item>
</swiper>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
autoplay | Boolean | false | 是否自动播放 |
interval | Number | 5000 | 自动切换时间间隔(ms) |
duration | Number | 500 | 滑动动画时长(ms) |
circular | Boolean | false | 是否循环播放 |
indicator-dots | Boolean | false | 是否显示面板指示点 |
indicator-color | String | rgba(0,0,0,.3) | 指示点颜色 |
indicator-active-color | String | #007aff | 当前选中指示点颜色 |
💡 案例:带指示点+自动播放轮播图
vue
<template>
<view class="swiper-container">
<swiper
autoplay="true"
interval="3000"
duration="500"
circular="true"
indicator-dots="true"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#fff"
@change="onSwiperChange"
>
<swiper-item v-for="(img, index) in bannerImages" :key="index">
<image :src="img" mode="aspectFill" style="width: 100%; height: 100%;" />
</swiper-item>
</swiper>
<text class="current-index">当前第{{ currentIndex + 1 }}张</text>
</view>
</template>
<script>
export default {
data() {
return {
bannerImages: [
'https://via.placeholder.com/750x300?text=Banner1',
'https://via.placeholder.com/750x300?text=Banner2',
'https://via.placeholder.com/750x300?text=Banner3'
],
currentIndex: 0
}
},
methods: {
onSwiperChange(e) {
this.currentIndex = e.detail.current;
}
}
}
</script>
<style scoped>
.swiper-container {
position: relative;
}
.current-index {
position: absolute;
bottom: 20rpx;
right: 20rpx;
background: rgba(0,0,0,0.6);
color: white;
padding: 10rpx 20rpx;
border-radius: 20rpx;
font-size: 28rpx;
}
</style>
🧩 二、基础组件
1. rich-text 富文本渲染
用于渲染 HTML 格式的富文本内容(不支持所有标签和样式)。
🔧 语法结构:
html
<rich-text :nodes="richTextContent"></rich-text>
📌 注意事项:
- 仅支持部分 HTML 标签:
<div>
,<p>
,<span>
,<img>
,<a>
,<strong>
,<em>
等。 - 不支持 CSS 样式、JS 脚本、iframe 等。
- 图片需使用
mode="widthFix"
或设置宽高。
💡 案例:渲染含图片和加粗文字的富文本
vue
<template>
<view class="rich-container">
<rich-text :nodes="content"></rich-text>
</view>
</template>
<script>
export default {
data() {
return {
content: [
{
name: 'div',
attrs: { class: 'title' },
children: [{ type: 'text', text: '这是标题' }]
},
{
name: 'p',
children: [
{ type: 'text', text: '这是一段' },
{ name: 'strong', children: [{ type: 'text', text: '加粗' }] },
{ type: 'text', text: '的文字。' }
]
},
{
name: 'img',
attrs: {
src: 'https://via.placeholder.com/300x200?text=Image',
mode: 'widthFix',
style: 'width: 100%; margin: 20rpx 0;'
}
}
]
}
}
}
</script>
<style scoped>
.title {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
</style>
2. progress 进度条
显示任务进度或加载状态。
🔧 语法结构:
html
<progress
percent="50" <!-- 百分比 -->
show-info="true" <!-- 是否显示百分比文字 -->
stroke-width="6" <!-- 进度条宽度 -->
activeColor="#007AFF" <!-- 活动部分颜色 -->
backgroundColor="#E0E0E0" <!-- 背景颜色 -->
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
percent | Number | 0 | 百分比(0~100) |
show-info | Boolean | false | 是否在进度条右侧显示百分比 |
stroke-width | Number | 6 | 进度条宽度(单位 rpx) |
activeColor | String | #09BB07 | 已完成部分颜色 |
backgroundColor | String | #EBEBEB | 未完成部分颜色 |
active | Boolean | false | 是否从左到右动画 |
💡 案例:动态进度条 + 动画效果
vue
<template>
<view class="progress-container">
<progress
:percent="progressValue"
show-info
stroke-width="8"
activeColor="#FF6B6B"
backgroundColor="#ddd"
:active="true"
/>
<button @click="startProgress">开始加载</button>
</view>
</template>
<script>
export default {
data() {
return {
progressValue: 0
}
},
methods: {
startProgress() {
let timer = setInterval(() => {
if (this.progressValue >= 100) {
clearInterval(timer);
return;
}
this.progressValue += 5;
}, 200);
}
}
}
</script>
<style scoped>
.progress-container {
padding: 40rpx;
}
</style>
📝 三、表单组件
1. button 按钮
触发操作按钮,支持多种样式和事件。
🔧 语法结构:
html
<button
type="primary" <!-- 主按钮 -->
size="default" <!-- 大小 -->
plain="false" <!-- 是否镂空 -->
disabled="false" <!-- 是否禁用 -->
@click="handleClick"
form-type="submit" <!-- 表单提交类型 -->
>
点击我
</button>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
type | String | default | 按钮类型:primary / warn / default |
size | String | default | 尺寸:default / mini |
plain | Boolean | false | 是否镂空 |
disabled | Boolean | false | 是否禁用 |
loading | Boolean | false | 是否显示加载状态 |
form-type | String | - | 用于 form 组件内:submit / reset |
💡 案例:三种按钮 + 加载状态
vue
<template>
<view class="btn-container">
<button type="primary" @click="onClick">主按钮</button>
<button type="warn" plain @click="onWarn">警告按钮</button>
<button :loading="isLoading" @click="onLoading">加载中...</button>
</view>
</template>
<script>
export default {
data() {
return {
isLoading: false
}
},
methods: {
onClick() {
uni.showToast({ title: '点击主按钮', icon: 'success' });
},
onWarn() {
uni.showModal({
title: '提示',
content: '你点击了警告按钮',
showCancel: false
});
},
onLoading() {
this.isLoading = true;
setTimeout(() => {
this.isLoading = false;
uni.showToast({ title: '加载完成', icon: 'success' });
}, 2000);
}
}
}
</script>
<style scoped>
.btn-container {
padding: 40rpx;
}
</style>
2. input 输入框
单行文本输入框,支持密码、数字、邮箱等类型。
🔧 语法结构:
html
<input
type="text" <!-- 输入类型 -->
value="初始值" <!-- 当前值 -->
placeholder="提示文字" <!-- 占位符 -->
maxlength="10" <!-- 最大长度 -->
@input="onInput" <!-- 输入事件 -->
@confirm="onConfirm" <!-- 确认事件 -->
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
type | String | text | 类型:text / number / idcard / digit / tel / email / password |
value | String | '' | 输入框内容 |
placeholder | String | '' | 占位文字 |
maxlength | Number | 140 | 最大输入长度 |
disabled | Boolean | false | 是否禁用 |
focus | Boolean | false | 是否聚焦 |
confirm-type | String | done | 键盘确认键类型:search / go / next / send / done |
💡 案例:用户名+密码输入 + 验证
vue
<template>
<view class="input-container">
<input
type="text"
v-model="username"
placeholder="请输入用户名"
maxlength="20"
@input="checkUsername"
/>
<input
type="password"
v-model="password"
placeholder="请输入密码"
maxlength="16"
@input="checkPassword"
/>
<text v-if="errorMsg" style="color: red; margin-top: 20rpx;">{{ errorMsg }}</text>
<button @click="login">登录</button>
</view>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
errorMsg: ''
}
},
methods: {
checkUsername() {
if (this.username.length < 3) {
this.errorMsg = '用户名至少3个字符';
} else {
this.errorMsg = '';
}
},
checkPassword() {
if (this.password.length < 6) {
this.errorMsg = '密码至少6位';
} else {
this.errorMsg = '';
}
},
login() {
if (!this.username || !this.password) {
this.errorMsg = '请填写完整信息';
return;
}
uni.showToast({ title: '登录成功', icon: 'success' });
}
}
}
</script>
<style scoped>
.input-container {
padding: 40rpx;
}
input {
height: 80rpx;
line-height: 80rpx;
border: 1px solid #ccc;
border-radius: 10rpx;
padding: 0 20rpx;
margin: 20rpx 0;
}
</style>
3. picker 选择器
提供多种选择方式:普通选择、时间选择、日期选择、省市区选择。
🔧 语法结构:
html
<!-- 普通选择 -->
<picker
mode="selector"
:range="options"
:value="selectedIndex"
@change="onPickerChange"
>
<view>当前选中:{{ options[selectedIndex] }}</view>
</picker>
<!-- 时间选择 -->
<picker mode="time" @change="onTimeChange">选择时间</picker>
<!-- 日期选择 -->
<picker mode="date" @change="onDateChange">选择日期</picker>
<!-- 省市区选择 -->
<picker mode="region" @change="onRegionChange">选择地区</picker>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
mode | String | selector | 类型:selector / multiSelector / time / date / region |
range | Array | [] | 选项数组(selector模式) |
value | Number | 0 | 当前选中索引 |
start / end | String | - | 日期/时间范围限制 |
custom-item | String | - | 自定义选项显示内容 |
💡 案例:多列选择器 + 地区选择
vue
<template>
<view class="picker-container">
<picker
mode="selector"
:range="colors"
:value="colorIndex"
@change="onColorChange"
>
<view>颜色:{{ colors[colorIndex] }}</view>
</picker>
<picker
mode="region"
@change="onRegionChange"
>
<view>地区:{{ regionText }}</view>
</picker>
<picker
mode="multiSelector"
:range="multiRange"
:value="multiValue"
@change="onMultiChange"
>
<view>多列选择:{{ multiText }}</view>
</picker>
</view>
</template>
<script>
export default {
data() {
return {
colors: ['红色', '绿色', '蓝色', '黄色'],
colorIndex: 0,
regionText: '请选择地区',
multiRange: [['北京', '上海', '广州'], ['朝阳区', '海淀区', '浦东新区']],
multiValue: [0, 0],
multiText: '北京 - 朝阳区'
}
},
methods: {
onColorChange(e) {
this.colorIndex = e.detail.value;
},
onRegionChange(e) {
this.regionText = e.detail.value.join(' ');
},
onMultiChange(e) {
const val = e.detail.value;
this.multiValue = val;
this.multiText = `${this.multiRange[0][val[0]]} - ${this.multiRange[1][val[1]]}`;
}
}
}
</script>
<style scoped>
.picker-container {
padding: 40rpx;
}
</style>
4. slider 滑块
滑动选择数值,常用于音量、亮度调节等。
🔧 语法结构:
html
<slider
min="0" <!-- 最小值 -->
max="100" <!-- 最大值 -->
value="50" <!-- 当前值 -->
step="1" <!-- 步长 -->
show-value="true" <!-- 是否显示当前值 -->
@change="onChange"
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
min | Number | 0 | 最小值 |
max | Number | 100 | 最大值 |
value | Number | 0 | 当前值 |
step | Number | 1 | 步长 |
show-value | Boolean | false | 是否显示当前值 |
disabled | Boolean | false | 是否禁用 |
activeColor | String | #007AFF | 激活部分颜色 |
backgroundColor | String | #CCC | 背景颜色 |
💡 案例:音量调节滑块 + 实时反馈
vue
<template>
<view class="slider-container">
<slider
min="0"
max="100"
:value="volume"
step="5"
show-value
activeColor="#FF6B6B"
@change="onVolumeChange"
/>
<text>当前音量:{{ volume }}%</text>
</view>
</template>
<script>
export default {
data() {
return {
volume: 50
}
},
methods: {
onVolumeChange(e) {
this.volume = e.detail.value;
// 可在此处调用音频API控制音量
}
}
}
</script>
<style scoped>
.slider-container {
padding: 40rpx;
}
</style>
5. radio & checkbox 单选/复选框
用于选择单个或多个选项。
🔧 语法结构:
html
<!-- 单选组 -->
<radio-group @change="onRadioChange">
<label v-for="(item, index) in options" :key="index">
<radio :value="item.value" :checked="item.checked" />
{{ item.name }}
</label>
</radio-group>
<!-- 复选组 -->
<checkbox-group @change="onCheckboxChange">
<label v-for="(item, index) in options" :key="index">
<checkbox :value="item.value" :checked="item.checked" />
{{ item.name }}
</label>
</checkbox-group>
📌 注意事项:
- 必须用
radio-group
和checkbox-group
包裹。 - 使用
v-model
时注意绑定的是数组或字符串。
💡 案例:性别选择 + 兴趣爱好选择
vue
<template>
<view class="radio-checkbox-container">
<text>性别:</text>
<radio-group @change="onGenderChange">
<label v-for="item in genderOptions" :key="item.value">
<radio :value="item.value" :checked="item.checked" />
{{ item.name }}
</label>
</radio-group>
<text>兴趣爱好:</text>
<checkbox-group @change="onHobbyChange">
<label v-for="item in hobbyOptions" :key="item.value">
<checkbox :value="item.value" :checked="item.checked" />
{{ item.name }}
</label>
</checkbox-group>
<text>已选性别:{{ selectedGender }}</text>
<text>已选爱好:{{ selectedHobbies.join(', ') }}</text>
</view>
</template>
<script>
export default {
data() {
return {
genderOptions: [
{ value: 'male', name: '男', checked: true },
{ value: 'female', name: '女', checked: false }
],
hobbyOptions: [
{ value: 'reading', name: '阅读', checked: false },
{ value: 'sports', name: '运动', checked: false },
{ value: 'music', name: '音乐', checked: false }
],
selectedGender: 'male',
selectedHobbies: []
}
},
methods: {
onGenderChange(e) {
this.selectedGender = e.detail.value;
this.genderOptions.forEach(item => {
item.checked = item.value === e.detail.value;
});
},
onHobbyChange(e) {
this.selectedHobbies = e.detail.value;
this.hobbyOptions.forEach(item => {
item.checked = e.detail.value.includes(item.value);
});
}
}
}
</script>
<style scoped>
.radio-checkbox-container {
padding: 40rpx;
}
label {
display: flex;
align-items: center;
margin: 20rpx 0;
}
</style>
6. switch 开关
用于切换开关状态,如开启/关闭功能。
🔧 语法结构:
html
<switch
:checked="isOn"
@change="onSwitchChange"
color="#FF6B6B" <!-- 激活颜色 -->
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
checked | Boolean | false | 是否选中 |
disabled | Boolean | false | 是否禁用 |
color | String | #007AFF | 开关颜色 |
💡 案例:夜间模式开关
vue
<template>
<view class="switch-container">
<switch
:checked="nightMode"
@change="toggleNightMode"
color="#FF6B6B"
/>
<text>{{ nightMode ? '夜间模式已开启' : '夜间模式已关闭' }}</text>
</view>
</template>
<script>
export default {
data() {
return {
nightMode: false
}
},
methods: {
toggleNightMode(e) {
this.nightMode = e.detail.value;
if (this.nightMode) {
uni.setNavigationBarTitle({ title: '夜间模式' });
uni.setTabBarStyle({ backgroundColor: '#222' });
} else {
uni.setNavigationBarTitle({ title: '白天模式' });
uni.setTabBarStyle({ backgroundColor: '#ffffff' });
}
}
}
}
</script>
<style scoped>
.switch-container {
padding: 40rpx;
display: flex;
align-items: center;
}
</style>
7. textarea 多行输入框
支持多行文本输入,适合评论、描述等内容。
🔧 语法结构:
html
<textarea
value="初始内容"
placeholder="请输入内容"
maxlength="200"
auto-focus="false"
@input="onTextareaInput"
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
value | String | '' | 输入内容 |
placeholder | String | '' | 占位文字 |
maxlength | Number | 140 | 最大长度 |
auto-focus | Boolean | false | 是否自动聚焦 |
fixed | Boolean | false | 是否固定定位(避免键盘遮挡) |
disable-default-padding | Boolean | false | 是否禁用默认内边距 |
💡 案例:评论输入框 + 字数统计
vue
<template>
<view class="textarea-container">
<textarea
v-model="comment"
placeholder="请输入您的评论..."
maxlength="200"
auto-focus
@input="updateCount"
/>
<text style="font-size: 24rpx; color: #999;">{{ count }}/200</text>
<button @click="submitComment">提交评论</button>
</view>
</template>
<script>
export default {
data() {
return {
comment: '',
count: 0
}
},
methods: {
updateCount() {
this.count = this.comment.length;
},
submitComment() {
if (!this.comment.trim()) {
uni.showToast({ title: '评论不能为空', icon: 'none' });
return;
}
uni.showToast({ title: '评论提交成功', icon: 'success' });
this.comment = '';
this.count = 0;
}
}
}
</script>
<style scoped>
.textarea-container {
padding: 40rpx;
}
textarea {
height: 200rpx;
border: 1px solid #ccc;
border-radius: 10rpx;
padding: 20rpx;
font-size: 32rpx;
margin-bottom: 20rpx;
}
</style>
8. form 表单容器
用于包裹表单元素,支持提交、重置等操作。
🔧 语法结构:
html
<form @submit="onSubmit" @reset="onReset">
<input name="username" v-model="formData.username" placeholder="用户名" />
<input name="password" type="password" v-model="formData.password" placeholder="密码" />
<button form-type="submit">提交</button>
<button form-type="reset">重置</button>
</form>
📌 属性说明:
@submit
:表单提交事件@reset
:表单重置事件form-type="submit"
:提交按钮form-type="reset"
:重置按钮
💡 案例:用户注册表单 + 数据校验
vue
<template>
<view class="form-container">
<form @submit="onSubmit" @reset="onReset">
<input
name="username"
v-model="formData.username"
placeholder="请输入用户名"
maxlength="20"
/>
<input
name="email"
type="email"
v-model="formData.email"
placeholder="请输入邮箱"
/>
<input
name="password"
type="password"
v-model="formData.password"
placeholder="请输入密码"
maxlength="16"
/>
<input
name="confirmPassword"
type="password"
v-model="formData.confirmPassword"
placeholder="请确认密码"
/>
<button form-type="submit">注册</button>
<button form-type="reset">重置</button>
</form>
</view>
</template>
<script>
export default {
data() {
return {
formData: {
username: '',
email: '',
password: '',
confirmPassword: ''
}
}
},
methods: {
onSubmit(e) {
const { username, email, password, confirmPassword } = this.formData;
if (!username || !email || !password || !confirmPassword) {
uni.showToast({ title: '请填写完整信息', icon: 'none' });
return;
}
if (password !== confirmPassword) {
uni.showToast({ title: '两次密码不一致', icon: 'none' });
return;
}
if (!/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(email)) {
uni.showToast({ title: '邮箱格式错误', icon: 'none' });
return;
}
uni.showToast({ title: '注册成功', icon: 'success' });
console.log('提交数据:', this.formData);
},
onReset() {
this.formData = {
username: '',
email: '',
password: '',
confirmPassword: ''
};
}
}
}
</script>
<style scoped>
.form-container {
padding: 40rpx;
}
input {
height: 80rpx;
line-height: 80rpx;
border: 1px solid #ccc;
border-radius: 10rpx;
padding: 0 20rpx;
margin: 20rpx 0;
}
</style>
🎥 四、媒体组件
1. camera 相机组件
调用设备摄像头,可拍照或录制视频。
🔧 语法结构:
html
<camera
device-position="back" <!-- 前后摄像头 -->
flash="off" <!-- 闪光灯 -->
@error="onCameraError"
@stop="onCameraStop"
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
device-position | String | back | 摄像头方向:front / back |
flash | String | off | 闪光灯:off / on / auto |
@error | Function | - | 摄像头错误回调 |
@stop | Function | - | 录制停止回调 |
💡 案例:拍照功能 + 保存图片
vue
<template>
<view class="camera-container">
<camera
style="width: 100%; height: 400rpx;"
device-position="back"
flash="auto"
@error="onCameraError"
></camera>
<button @click="takePhoto">拍照</button>
<image v-if="photoSrc" :src="photoSrc" mode="aspectFit" style="width: 100%; height: 400rpx; margin-top: 20rpx;" />
</view>
</template>
<script>
export default {
data() {
return {
photoSrc: ''
}
},
methods: {
takePhoto() {
const ctx = uni.createCameraContext();
ctx.takePhoto({
quality: 'high',
success: (res) => {
this.photoSrc = res.tempImagePath;
uni.showToast({ title: '拍照成功', icon: 'success' });
},
fail: (err) => {
uni.showToast({ title: '拍照失败', icon: 'none' });
}
});
},
onCameraError(e) {
console.error('摄像头错误:', e.detail.errMsg);
uni.showToast({ title: '摄像头权限未开启', icon: 'none' });
}
}
}
</script>
<style scoped>
.camera-container {
padding: 40rpx;
}
</style>
2. video 视频播放器
播放本地或网络视频,支持全屏、倍速、弹幕等。
🔧 语法结构:
html
<video
src="https://example.com/video.mp4"
controls="true" <!-- 是否显示控制栏 -->
autoplay="false" <!-- 是否自动播放 -->
loop="false" <!-- 是否循环播放 -->
@play="onPlay"
@pause="onPause"
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
src | String | '' | 视频地址 |
controls | Boolean | true | 是否显示播放控件 |
autoplay | Boolean | false | 是否自动播放 |
loop | Boolean | false | 是否循环播放 |
poster | String | '' | 封面图 |
muted | Boolean | false | 是否静音 |
@play / @pause / @ended | Function | - | 播放/暂停/结束事件 |
💡 案例:播放视频 + 控制播放状态
vue
<template>
<view class="video-container">
<video
:src="videoUrl"
controls
:poster="poster"
@play="onPlay"
@pause="onPause"
@ended="onEnded"
style="width: 100%; height: 400rpx;"
/>
<text>播放状态:{{ playStatus }}</text>
<button @click="togglePlay">播放/暂停</button>
</view>
</template>
<script>
export default {
data() {
return {
videoUrl: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4',
poster: 'https://via.placeholder.com/750x400?text=Video+Poster',
playStatus: '未播放'
}
},
methods: {
onPlay() {
this.playStatus = '正在播放';
},
onPause() {
this.playStatus = '已暂停';
},
onEnded() {
this.playStatus = '播放结束';
},
togglePlay() {
const videoContext = uni.createVideoContext('myVideo');
if (this.playStatus === '正在播放') {
videoContext.pause();
} else {
videoContext.play();
}
}
}
}
</script>
<style scoped>
.video-container {
padding: 40rpx;
}
</style>
3. map 地图组件
显示地图,支持标记、缩放、定位等功能。
🔧 语法结构:
html
<map
:latitude="latitude"
:longitude="longitude"
scale="16"
:markers="markers"
@regionchange="onRegionChange"
/>
📌 属性说明:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
latitude | Number | 0 | 中心纬度 |
longitude | Number | 0 | 中心经度 |
scale | Number | 16 | 缩放级别(5~18) |
markers | Array | [] | 标记点数组 |
@regionchange | Function | - | 地图区域变化事件 |
💡 案例:显示当前位置 + 添加标记
vue
<template>
<view class="map-container">
<map
:latitude="latitude"
:longitude="longitude"
scale="16"
:markers="markers"
style="width: 100%; height: 400rpx;"
/>
<text>当前位置:{{ locationText }}</text>
<button @click="getCurrentLocation">获取当前位置</button>
</view>
</template>
<script>
export default {
data() {
return {
latitude: 39.9042,
longitude: 116.4074,
locationText: '北京',
markers: [
{
id: 1,
latitude: 39.9042,
longitude: 116.4074,
title: '天安门',
iconPath: '/static/location.png',
width: 30,
height: 30
}
]
}
},
methods: {
getCurrentLocation() {
uni.getLocation({
type: 'gcj02',
success: (res) => {
this.latitude = res.latitude;
this.longitude = res.longitude;
this.locationText = `纬度:${res.latitude}, 经度:${res.longitude}`;
this.markers = [{
id: 1,
latitude: res.latitude,
longitude: res.longitude,
title: '我的位置',
iconPath: '/static/location.png',
width: 30,
height: 30
}];
},
fail: () => {
uni.showToast({ title: '获取位置失败', icon: 'none' });
}
});
}
}
}
</script>
<style scoped>
.map-container {
padding: 40rpx;
}
</style>
🎯 综合性实战案例
🧩 案例一:典型注册页(综合运用表单组件)
包含用户名、邮箱、密码、确认密码、协议勾选、提交按钮等。
vue
<template>
<view class="register-page">
<view class="header">
<text>用户注册</text>
</view>
<form @submit="onRegister">
<input
v-model="form.username"
placeholder="请输入用户名(3-20字符)"
maxlength="20"
@input="validateUsername"
/>
<input
v-model="form.email"
type="email"
placeholder="请输入邮箱"
@input="validateEmail"
/>
<input
v-model="form.password"
type="password"
placeholder="请输入密码(6-16字符)"
maxlength="16"
@input="validatePassword"
/>
<input
v-model="form.confirmPassword"
type="password"
placeholder="请确认密码"
@input="validateConfirmPassword"
/>
<view class="agree-box">
<checkbox v-model="form.agree" />
<text>我已阅读并同意《用户协议》</text>
</view>
<button form-type="submit" :disabled="!canSubmit">注册</button>
</form>
<text v-if="errorMessage" style="color: red; margin-top: 20rpx;">{{ errorMessage }}</text>
</view>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
email: '',
password: '',
confirmPassword: '',
agree: false
},
errorMessage: ''
}
},
computed: {
canSubmit() {
return (
this.form.username.length >= 3 &&
this.form.email &&
this.form.password.length >= 6 &&
this.form.password === this.form.confirmPassword &&
this.form.agree
);
}
},
methods: {
validateUsername() {
if (this.form.username.length < 3) {
this.errorMessage = '用户名至少3个字符';
} else {
this.errorMessage = '';
}
},
validateEmail() {
const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
if (!reg.test(this.form.email)) {
this.errorMessage = '邮箱格式错误';
} else {
this.errorMessage = '';
}
},
validatePassword() {
if (this.form.password.length < 6) {
this.errorMessage = '密码至少6位';
} else {
this.errorMessage = '';
}
},
validateConfirmPassword() {
if (this.form.password !== this.form.confirmPassword) {
this.errorMessage = '两次密码不一致';
} else {
this.errorMessage = '';
}
},
onRegister() {
if (!this.canSubmit) {
uni.showToast({ title: '请检查表单填写', icon: 'none' });
return;
}
uni.showToast({ title: '注册成功', icon: 'success' });
console.log('注册数据:', this.form);
// 可在此处调用接口提交数据
}
}
}
</script>
<style scoped>
.register-page {
padding: 40rpx;
background: #f8f8f8;
}
.header {
font-size: 48rpx;
font-weight: bold;
text-align: center;
margin-bottom: 40rpx;
}
input {
height: 80rpx;
line-height: 80rpx;
border: 1px solid #ccc;
border-radius: 10rpx;
padding: 0 20rpx;
margin: 20rpx 0;
}
.agree-box {
display: flex;
align-items: center;
margin: 20rpx 0;
}
.agree-box checkbox {
margin-right: 10rpx;
}
button {
margin-top: 40rpx;
}
</style>
🧩 案例二:典型个人中心页(综合运用容器、媒体、基础组件)
包含头像、昵称、积分、设置开关、历史订单、退出登录等。
vue
<template>
<view class="profile-page">
<view class="header">
<image :src="user.avatar" mode="aspectFill" class="avatar" />
<text class="nickname">{{ user.nickname }}</text>
</view>
<scroll-view scroll-y="true" class="content">
<view class="section">
<text>账户信息</text>
<view class="info-item">
<text>手机号:</text>
<text>{{ user.phone }}</text>
</view>
<view class="info-item">
<text>积分:</text>
<text>{{ user.points }} 分</text>
</view>
</view>
<view class="section">
<text>功能设置</text>
<view class="setting-item">
<text>夜间模式</text>
<switch :checked="settings.nightMode" @change="toggleNightMode" />
</view>
<view class="setting-item">
<text>消息通知</text>
<switch :checked="settings.notification" @change="toggleNotification" />
</view>
</view>
<view class="section">
<text>历史订单</text>
<view v-for="(order, index) in orders" :key="index" class="order-item">
<text>订单号:{{ order.id }}</text>
<text>金额:¥{{ order.amount }}</text>
<text>状态:{{ order.status }}</text>
</view>
</view>
<view class="section">
<button @click="logout">退出登录</button>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
user: {
avatar: 'https://via.placeholder.com/100x100?text=User',
nickname: '小明同学',
phone: '138****1234',
points: 1250
},
settings: {
nightMode: false,
notification: true
},
orders: [
{ id: '20250101001', amount: 299, status: '已完成' },
{ id: '20250102002', amount: 199, status: '待发货' },
{ id: '20250103003', amount: 599, status: '已取消' }
]
}
},
methods: {
toggleNightMode(e) {
this.settings.nightMode = e.detail.value;
if (e.detail.value) {
uni.setNavigationBarTitle({ title: '夜间模式' });
uni.setTabBarStyle({ backgroundColor: '#222' });
} else {
uni.setNavigationBarTitle({ title: '个人中心' });
uni.setTabBarStyle({ backgroundColor: '#ffffff' });
}
},
toggleNotification(e) {
this.settings.notification = e.detail.value;
uni.showToast({
title: e.detail.value ? '通知已开启' : '通知已关闭',
icon: 'none'
});
},
logout() {
uni.showModal({
title: '确认退出',
content: '您确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.clearStorageSync();
uni.reLaunch({ url: '/pages/login/login' });
}
}
});
}
}
}
</script>
<style scoped>
.profile-page {
padding: 40rpx;
background: #f8f8f8;
}
.header {
display: flex;
align-items: center;
margin-bottom: 40rpx;
}
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.nickname {
font-size: 36rpx;
font-weight: bold;
}
.content {
height: calc(100vh - 200rpx);
}
.section {
background: white;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}
.section > text {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.info-item, .setting-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1px solid #eee;
}
.order-item {
padding: 20rpx 0;
border-bottom: 1px solid #eee;
}
.order-item text {
display: block;
margin: 5rpx 0;
}
button {
margin-top: 40rpx;
background: #FF6B6B;
color: white;
}
</style>
📝 本章小结
✅ 容器组件 :scroll-view
实现滚动交互,swiper
实现轮播图,是布局核心。
✅ 基础组件 :rich-text
渲染富文本,progress
显示进度,提升用户体验。
✅ 表单组件 :涵盖 input
、button
、picker
、slider
、radio/checkbox
、switch
、textarea
、form
,是数据采集核心。
✅ 媒体组件 :camera
、video
、map
扩展应用能力,满足多媒体需求。
✅ 综合案例:通过"注册页"和"个人中心页",将各组件串联,形成完整业务闭环。
📌 建议:
- 多动手实践,熟悉每个组件的属性和事件。
- 结合
uni-app
官方文档深入理解兼容性和平台差异。 - 学会组合使用组件,构建复杂交互页面。
🎯 进阶方向:
- 使用
ref
获取组件实例进行操作 - 结合
Vuex
管理全局状态 - 使用
uni.request
调用后端接口 - 优化性能:节流、防抖、虚拟列表等