uni-app小程序开发-组件

相关文档

简介

https://uniapp.dcloud.net.cn/

组件

https://uniapp.dcloud.net.cn/component/

API

https://uniapp.dcloud.net.cn/api/

演示

https://hellouniapp.dcloud.net.cn/pages/component/view/view

判断环境

https://uniapp.dcloud.net.cn/api/other/getAccountInfoSync.html#getaccountinfosync

示例

1
2
3
const accountInfo = uni.getAccountInfoSync();
console.log("appId",accountInfo.miniProgram.appId); // 小程序 appId
console.log("envVersion",accountInfo.miniProgram.envVersion); // 当前环境版本

其中miniProgram.envVersion

  • develop开发版:开发工具中运行或真机调试时
  • trial体验版:提交后设置为体验版
  • release正式版:提交并审核上架后
  • gray灰度版(仅支付宝小程序支持)

状态栏/导航条

状态栏

官方默认的高度--status-bar-height的值是固定的25px

这就导致不同型号的手机的状态栏的高度是不一样的,导致实际效果并不好。

导航栏在不同的型号上是不受影响的都是44px

1
2
3
4
.status_bar {
height: var(--status-bar-height);
width: 100%;
}

JS中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
data() {
return {
windowInfo: {},
}
},
beforeMount() {
this.windowInfo = uni.getWindowInfo()
},
onLoad() {},
methods: {}
}
</script>

状态栏设置

1
2
<!-- 这里是状态栏 -->
<view class="status_bar" :style="{height:windowInfo.statusBarHeight+'px'}"></view>

导航条

pages.json

1
2
3
4
5
6
7
8
"globalStyle": {
"navigationBarTextStyle": "black",
//#ifdef MP-ALIPAY
"navigationBarBackgroundColor": "#ffffff",
//#endif
"navigationBarTitleText": "",
"backgroundColor": "#f8f8f8"
},

导航条颜色一般是通过navigationBarTextStyle配置

导航栏标题颜色及状态栏前景颜色,仅支持 black/white

但是支付宝小程序不支持

支付宝是通过navigationBarBackgroundColor影响,只有值是#ffffff的时候文字就是黑的,其它值文字都是白的。

样式中使用

有这种情况,假如我们有一个组件使用的定位是粘性定位,这就要设置top的值,并且top应该是状态栏和导航的和。

这种情况是不能用sass的变量的,sass的变量不是运行时的变量,而状态栏的高度我们只能在运行时获取,所以我们可以用CSS变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<view :style="{ '--statusBarHeight': statusBarHeight, '--statusAndNaviBarHeight': statusAndNaviBarHeight }">
</view>
</template>

<script>
export default {
data() {
return {
statusBarHeight: '25px',
statusAndNaviBarHeight: '69px'
};
},
beforeMount() {
let windowInfo = uni.getWindowInfo();
this.statusBarHeight = windowInfo.statusBarHeight + 'px';
this.statusAndNaviBarHeight = windowInfo.statusBarHeight + 44 + 'px';
}
};
</script>

样式中直接这样用就行了

1
2
3
position: sticky;
top: var(--statusAndNaviBarHeight);
z-index: 2;

注意

不要在App.vue中设置变量,页面中无法获取。

TabBar

pages.json 中 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"tabBar": {
"color": "#555555",
"selectedColor": "#FA9C31",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"height": "50px",
"fontSize": "12px",
"iconWidth": "24px",
"spacing": "6px",
"list": [{
"pagePath": "pages/index/index",
"iconPath": "/static/imgs/tab/tab_home1.png",
"selectedIconPath": "/static/imgs/tab/tab_home2.png",
"text": "首页"
}, {
"pagePath": "pages/category/index",
"iconPath": "/static/imgs/tab/tab_category1.png",
"selectedIconPath": "/static/imgs/tab/tab_category2.png",
"text": "分类"
}, {
"pagePath": "pages/usercenter/index",
"iconPath": "/static/imgs/tab/tab_user1.png",
"selectedIconPath": "/static/imgs/tab/tab_user2.png",
"text": "我的"
}]
}
}

如果要用图标字体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"height": "50px",
"fontSize": "10px",
"iconWidth": "24px",
"spacing": "3px",
"iconfontSrc": "/static/iconfont.ttf", // app tabbar 字体.ttf文件路径 app 3.4.4+
"list": [{
"pagePath": "pages/index/index",
"iconPath": "/static/imgs/tab/tab_home1.png",
"selectedIconPath": "/static/imgs/tab/tab_home2.png",
"text": "首页",
"iconfont": { // 优先级高于 iconPath,该属性依赖 tabbar 根节点的 iconfontSrc
"text": "\ue102",
"selectedText": "\ue103",
"fontSize": "17px",
"color": "#000000",
"selectedColor": "#0000ff"
}
}]
}
}

交互反馈

API 说明
uni.showToast 显示提示框
uni.hideToast 隐藏提示框
uni.showLoading 显示加载提示框
uni.hideLoading 隐藏加载提示框
uni.showModal 显示模态弹窗
uni.showActionSheet 显示菜单列表

消息

1
2
3
4
5
6
uni.showToast({
title: "领取成功",
duration: 3000
});

uni.hideToast();

Loading

1
2
3
4
5
uni.showLoading({
title: '请求中...',
});

uni.hideLoading();

如果没设置title,默认为加载中

1
uni.showLoading();

提示带确定

1
2
3
4
5
6
7
8
9
10
uni.showModal({
title: '提示',
content: '这是一个警告框示例',
showCancel: false, // 是否显示取消按钮
success: function (res) {
if (res.confirm) {
console.log('用户点击确定');
}
}
});

和支付宝小程序的下面的方法类似

1
2
3
my.alert({
content: '支付失败'
});

确认提示框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uni.showModal({
title: '取消订单',
content: '你确定要取消订单吗?',
showCancel: true, // 是否显示取消按钮
cancelText: '取消', // 取消按钮的文本
confirmText: '确定', // 确定按钮的文本
success: (res) => {
if (res.confirm) {
console.log('用户点击确定');
// 处理点击确定后的逻辑
} else if (res.cancel) {
console.log('用户点击取消');
// 处理点击取消后的逻辑
}
}
});

底部菜单

1
2
3
4
5
6
7
8
9
uni.showActionSheet({
itemList: ['A', 'B', 'C'],
success: function (res) {
console.log('选中了第' + (res.tapIndex + 1) + '个按钮');
},
fail: function (res) {
console.log(res.errMsg);
}
});

展示类组件

Image

1
<image src="/static/imgs/home/search.png" mode="scaleToFill"></image>

如果我们想只设置宽度,让高度自适应

1
<image src="/static/imgs/01.jpg" mode="widthFix"></image>

mode 有效值

默认为scaleToFill

mode 有 14 种模式,其中 5 种是缩放模式,9 种是裁剪模式。

模式 说明
缩放 scaleToFill 不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素
缩放 aspectFit 保持纵横比缩放图片,使图片的长边能完全显示出来。
也就是说,可以完整地将图片显示出来。
缩放 aspectFill 保持纵横比缩放图片,只保证图片的短边能完全显示出来。
也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
缩放 widthFix 宽度不变,高度自动变化,保持原图宽高比不变
缩放 heightFix 高度不变,宽度自动变化,保持原图宽高比不变
App 和 H5 平台 HBuilderX 2.9.3+ 支持、微信小程序需要基础库 2.10.3
裁剪 top 不缩放图片,只显示图片的顶部区域
裁剪 bottom 不缩放图片,只显示图片的底部区域
裁剪 center 不缩放图片,只显示图片的中间区域
裁剪 left 不缩放图片,只显示图片的左边区域
裁剪 right 不缩放图片,只显示图片的右边区域
裁剪 top left 不缩放图片,只显示图片的左上边区域
裁剪 top right 不缩放图片,只显示图片的右上边区域
裁剪 bottom left 不缩放图片,只显示图片的左下边区域
裁剪 bottom right 不缩放图片,只显示图片的右下边区域

文本展示

1
<text>{{text}}</text>

轮播图

1
2
3
4
5
6
7
8
9
10
11
<swiper class="swiper" circular :indicator-dots="true" :autoplay="true" interval="5000" duration="500">
<swiper-item>
<view class="swiper-item uni-bg-red"></view>
</swiper-item>
<swiper-item>
<view class="swiper-item uni-bg-green"></view>
</swiper-item>
<swiper-item>
<view class="swiper-item uni-bg-blue"></view>
</swiper-item>
</swiper>

官方文档

https://uniapp.dcloud.net.cn/component/swiper.html

其中

  • interval 是多长时间切换下一张 单位毫秒

  • duration 一张切换的时长 单位毫秒

样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.swiper {
height: 386rpx;
background-color: white;

.swiper-item {
height: 100%;
width: 100%;
}

.uni-bg-red {
background-color: red;
}

.uni-bg-green {
background-color: green;
}

.uni-bg-blue {
background-color: blue;
}
}

输入类组件

输入框

1
2
3
4
5
6
<input
class="z_input_border"
focus
v-model="inRealName"
placeholder="请输入姓名"
/>

多行输入

1
<textarea class="info_detail" disabled auto-height :value="info_text"></textarea>

输入

1
2
3
4
5
6
7
8
<textarea
v-if="seletList[seletIndex].value == 110"
class="info_detail z_margin_top_l"
style="height: 200rpx"
:maxlength="100"
v-model="info_text"
>
</textarea>

选择类组件

单选

单选

1
2
<radio value="r1" :checked="false" />
<radio value="r1" :checked="true" />

注意

radio上没法绑定事件,绑定事件需要外面套一个radio-group

大小调整

1
<radio value="r1" checked="false" style="transform: scale(0.7)" />

单选和文字

1
<label class="radio"><radio value="r1" checked="true" />选中</label>

样式调整

1
<radio value="r1" :checked="true" color="#fa7e31" style="transform: scale(0.7)" />

单选按钮组

1
2
3
4
5
6
<radio-group @change="radioChange">
<label class="radio" v-for="(item, index) in genderArr" :key="index">
<radio :value="item" color="#fa7e31" :checked="gender === item" />
{{ item }}
</label>
</radio-group>

方法

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
data() {
return {
genderArr: ['男', '女'],
gender: "女"
}
},
methods: {
radioChange: function(evt) {
this.gender = evt.detail.value;
}
}
}

注意

radio-group上不支持绑定值,还得在radio上控制选中与未选中。

对象列表

1
2
3
4
5
6
7
8
9
10
11
12
<radio-group @change="radioChange">
<view v-for="(item, index) in seletList" :key="index">
<label class="radio z_flex_h_l_r">
{{ item.name }}
<radio
:value="index"
color="#fa7e31"
:checked="seletIndex === index"
/>
</label>
</view>
</radio-group>

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export default {
data() {
return {
orderCode: '',
info_text: '',
seletList: [
{
name: '押金问题',
value: 10
},
{
name: '租金问题',
value: 20
},
{
name: '多拍/错拍',
value: 30
}
],
seletIndex: 0
};
},
methods: {
radioChange: function (evt) {
this.seletIndex = evt.detail.value;
},
}
};

弹出菜单

在支付宝小程序中是中间弹出,有的是底部弹出

https://uniapp.dcloud.net.cn/component/uniui/uni-combox.html

1
2
3
4
5
6
7
8
<picker
@change="bindPickerChange"
:value="seletIndex"
range-key="name"
:range="seletList"
>
<view class="uni-input">{{ seletList[seletIndex].name }}</view>
</picker>

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { reactive, ref } from 'vue';
let seletList = reactive([
{
name: '顺丰快递',
value: 1
},
{
name: '德邦快递',
value: 2
}
]);

const seletIndex = ref(0);
function bindPickerChange(e) {
seletIndex.value = e.detail.value;
}

选项式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default {
data() {
return {
seletList: [
{
name: '顺丰快递',
value: 1
},
{
name: '德邦快递',
value: 2
}
],
seletIndex: 0
};
},
methods: {
bindPickerChange(e) {
this.seletIndex = e.detail.value;
}
}
};

注意

e.detail.value不是对象中的value,而是选中项的索引。

弹出层

https://ext.dcloud.net.cn/plugin?name=uni-popup

https://uniapp.dcloud.net.cn/component/uniui/uni-popup.html

使用

1
<uni-popup ref="popup" type="center" :animation="false" :safe-area="false">中间弹出 Popup</uni-popup>

打开关闭

1
2
3
4
5
6
openPop() {
this.$refs.popup.open()
},
close() {
this.$refs.popup.close()
}

或者代码中指定位置

1
2
//如果传入参数 ,type 属性将失效 ,仅支持 ['top','left','bottom','right','center'] 
this.$refs.popup.open('bottom');

蒙版点击不关闭

1
2
3
4
5
6
7
8
<uni-popup
ref="popup"
type="center"
:is-mask-click="false"
:animation="false"
:safe-area="false"
>
</uni-popup>

弹出方位

模板中type支持的类型如下:

但是不建议用后面三种,这三种本质还是上面的5种。

属性名 说明
top 顶部弹出
center 居中弹出
bottom 底部弹出
left 左侧弹出
right 右侧弹出
message 预置样式 :消息提示
dialog 预置样式 :对话框
share 预置样式 :底部弹出分享示例

问题

下弹窗不显示

当从下面弹出的时候默认的:safe-area="true"会导致弹窗不显示,改成false就可以了。

1
2
3
<uni-popup ref="popup" type="bottom" :animation="true" :safe-area="false">
<comp-goods-stats-pop></comp-goods-stats-pop>
</uni-popu

禁用页面滚动

使用组件时,会发现内容部分滚动到底时,继续划动会导致底层页面的滚动,这就是滚动穿透。

但由于平台自身原因,除了h5平台外 ,其他平台都不能在在组件内禁止滚动穿透,所以页面内需要用户特殊处理一下。

弹出层显示的时候禁止页面滚动

1
2
3
4
5
<view class="goods_detail_outer" :style="{ overflow: popIsShow ? 'hidden' : 'visible' }"></view>

<uni-popup ref="popup" type="bottom" :animation="true" :safe-area="false" @change="popStateChange">
<comp-goods-stats-pop></comp-goods-stats-pop>
</uni-popup>

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default {
data() {
return {
popIsShow: false
};
},
methods: {
openZulinPop() {
this.$refs.popup.open();
},
close() {
this.$refs.popup.close();
},
popStateChange(e) {
this.popIsShow = e.show;
},
}
};

日期选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<picker mode="date" :value="date" start="2020-01-01" end="2030-01-01" @change="bindDateChange">
<view class="z_text_base">{{ date }}</view>
</picker>

<script>
// 引入moment.js
import moment from 'moment';
moment.locale('zh-cn'); //指定语言

export default {
data() {
return {
date: '2024-07-29'
};
},
methods: {
bindDateChange(e) {
this.date = moment(e.detail.value).format('YYYY-MM-DD');
}
}
};
</script>

安装

1
npm install moment

引用

1
2
3
// 引入moment.js
import moment from 'moment';
moment.locale('zh-cn'); //指定语言

使用

1
moment().format('YYYY-MM-DD');

图片选择

https://uniapp.dcloud.net.cn/component/uniui/uni-file-picker.html

模板

1
<uni-file-picker limit="3" title="最多选择3张图片" @select="selectImg" @delete="delIMG"></uni-file-picker>

图片上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
export default {
data() {
return {
imgPathArr: [],
imageUrlArr: []
};
},
methods: {
async selectImg(e) {
const tempFilePaths = e.tempFilePaths;
for (let i = 0; i < tempFilePaths.length; i++) {
let imgPath = tempFilePaths[i];
this.imgPathArr.push(imgPath);
try {
let imgUrl = await this.uploadFile(imgPath);
this.imageUrlArr.push(imgUrl);
} catch (e) {}
}
console.info(this.imgPathArr);
console.info(this.imageUrlArr);
},
delIMG(e) {
let delIndex = this.imgPathArr.indexOf(e.tempFilePath);
console.info(e.tempFilePath);
console.info('删除的索引为:' + delIndex);
this.imgPathArr.splice(delIndex, 1);
this.imageUrlArr.splice(delIndex, 1);
console.info(this.imgPathArr);
console.info(this.imageUrlArr);
},
uploadFile(imgPath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://xxx.xxx.com/api/uploadpic2/',
filePath: imgPath,
name: 'file',
header: { 'Content-Type': 'multipart/form-data' },
success: (response) => {
let obj = JSON.parse(response.data);
resolve(obj.imgUrl);
}
});
});
}
}
};

选择文件的回调或者删除回调地址都是类似于下面的形式

1
https://resource/apmlce3576a795169631d401bbc08d272fba.png

收货地址选择

JS

1
2
3
4
5
6
7
8
uni.chooseAddress({
success: (res) => {
console.info(JSON.stringify(res));
},
fail: (err) => {
console.log('选择地址失败:', err);
}
});

用户选择后返回的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"userName": "张XX",
"telNumber": "15225178123",
"countyName": "中国",
"provinceName": "河南省",
"cityName": "郑州市",
"detailInfo": "河南省-郑州市-荥阳市-京城路街道索河路广武路交叉口XX家属院X楼",
"result": {
"address": "河南省-郑州市-荥阳市-京城路街道索河路广武路交叉口XX家属院X楼",
"area": "荥阳市",
"city": "郑州市",
"country": "中国",
"fullname": "张XX",
"mobilePhone": "15225178123",
"prov": "河南省",
"street": ""
},
"resultStatus": "9000"
}

可以看出

外层和result中的数据基本一致,内层的会更详细一点。

建议在这样写

支付宝的详细地址中新添加的是没有省市区的,但是之前的地址详情中却有,这里判断了一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
chooseAddress() {
uni.chooseAddress({
success: (res) => {
this.username = res.userName;
this.userphone = res.telNumber;
let ssqStr = `${res.result.prov}-${res.result.city}-${res.result.area}`;
let addressStr = res.result.address;
if (addressStr.indexOf(ssqStr) != -1) {
this.userAddress = addressStr;
} else {
let addressAllStr = `${ssqStr}-${res.result.address}`;
this.userAddress = addressAllStr;
}
},
fail: (err) => {
console.log('选择地址失败:', err);
}
});
}

web-view

web-view 是一个 web 浏览器组件,可以用来承载网页的容器,会自动铺满整个页面。

小程序仅支持加载网络网页,不支持本地html。

小程序端 web-view 组件一定有原生导航栏,下面一定是全屏的 web-view 组件,navigationStyle: custom 对 web-view 组件无效,外层嵌套view也不起作用。

定义样式(小程序无效)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<view>
<web-view :webview-styles="webviewStyles"
src="https://www.psvmc.cn/zjtools/z/browserinfo/index.html"></web-view>
</view>
</template>

<script>
export default {
data() {
return {
webviewStyles: {
progress: {
color: '#fa7e31'
}
}
}
}
}
</script>

富文本

1
2
<rich-text :nodes="getHtmlFitImg(goodsDetail.comm)">
</rich-text>

图片处理,让详情中的图片的最大宽度为100%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
getHtmlFitImg(htmlStr) {
if (htmlStr) {
return htmlStr.replace(
/(<img\s+.*?style=")(.*?)(".*?>)/g,
function (match, p1, p2, p3) {
var newStyle =
'max-width:100%;height:auto;display:block;margin:0 auto';
return p1 + newStyle + p3;
}
);
} else {
return '';
}
},

复制到剪贴板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在方法中调用复制到剪贴板的操作
function copyTextToClipboard(text) {
uni.setClipboardData({
data: text,
success: function () {
uni.showToast({
title: '复制成功',
icon: 'success',
duration: 2000
});
}
});
}

// 使用示例
copyTextToClipboard('要复制的文本内容');

自定义组件

传统vue组件,需要安装、引用、注册,三个步骤后才能使用组件。

easycom将其精简为一步。

只要组件安装在项目的components目录下或uni_modules目录下,并符合components/组件名称/组件名称.(vue|uvue)目录结构,就可以不用引用、注册,直接在页面中使用。

状态栏占位组件

状态栏我们可能每个页面都用,为了方便,我们可以定义为组件。

文件目录/components/z-statusbar/z-statusbar.vue

页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<view class="status_bar" :style="{ height: windowInfo.statusBarHeight + 'px' }"></view>
</template>

<script>
export default {
name: 'z-statusbar',
data() {
return {
windowInfo: {}
};
},
beforeMount() {
this.windowInfo = uni.getWindowInfo();
}
};
</script>

<style>
.status_bar {
height: var(--status-bar-height);
width: 100%;
flex: none;
}
</style>

这样我们就可以直接使用了

1
<z-statusbar></z-statusbar>

导航条占位组件

文件目录/components/z-navigationbar/z-navigationbar.vue

页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<!-- 这里是导航 -->
<view class="navigation_bar"></view>
</template>

<script>
export default {
name: 'z-navigationbar',
data() {
return {};
}
};
</script>

<style>
.navigation_bar {
height: 44px;
width: 100%;
flex: none;
}
</style>

这样我们就可以直接使用了

1
<z-navigationbar></z-navigationbar>

拨打电话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
callPhone(phone) {
if (!phone) {
return;
}
uni.makePhoneCall({
phoneNumber: phone,
success: function () {
console.log('拨打电话成功');
},
fail: function () {
console.log('拨打电话失败');
}
});
}