# 微信小程序常用组件和api
# swiper 轮播 image
<swiper indicator-dots="true" autoplay="true" interval="5000">
<block wx:for="{{swiperImgUrls}}">
<swiper-item >
<image src="{{item.url}}" mode="widthFix" class="img"></image>
</swiper-item>
</block>
</swiper>
# scroll-view
scroll-y垂直滚动 scroll-top滚动高度
<scroll-view hidden="{{isLyricShow}}" class="lyric-scroll" scroll-y scroll-top="{{scrollTop}}" scroll-with-animation="true">
<view class="lyric-panel">
<block wx:for="{{lrcList}}" wx:key="item">
<view class="lyric {{index==nowLyricIndex?'hightlight-lyric': ''}}">{{item.lrc}}</view>
</block>
</view>
</scroll-view>
# movable-area滑动 progress进度条
<!-- onchange事件:滑块移动就会触发,有多个type值 -->
<!-- onTouchEnd停止时触发 -->
<movable-area class="movable-area">
<movable-view direction="horizontal" class="movable-view"
damping="1000" x="{{movableDis}}" bindchange="onChange"
bindtouchend="onTouchEnd"
/>
</movable-area>
<progress stroke-width="4" backgroundColor="#969696"
activeColor="#fff" percent="{{progress}}"></progress>
# 直接在html上的三元运算符操作
<text class="iconfont {{isPlaying?'icon-zanting1':'icon-bofang1'}}" bind:tap="togglePlaying"></text>
# 跳转界面 navigator
wx.navigateTo({
url: `../../pages/musiclist/musiclist?playlistId=${this.properties.playlist.id}`,
})
wx.navigateBack() 返回某个界面
<!-- 也可以使用navigator标签跳转,hover-class去掉调转时的背景色 -->
<navigator class="content" hover-class="none" url="/pages/profile-playhistory/profile-playhistory">
<i class="iconfont icon-ziyuanldpi"></i>
<text class="text">最近播放</text>
<i class="iconfont icon-xiangyou"></i>
</navigator>
# 添加标题 NavigationBarTitle
小程序正上方的标题修改可以使用这个方法
wx.setNavigationBarTitle({
title: music.name,
})
# wx本地存储
wx.setStorageSync('musiclist', this.data.musiclist)//这是同步存储,也可以异步存储
wx.getStorageSync('musiclist')
# for循环
<!-- 默认item和index为内容和序列,可修改名字wx:for-item/wx:for-key -->
<view wx:for="{{arr}}" wx:key="*this">
<text>{{item}}</text>
</view>
<view wx:for="{{arr1}}" wx:key="id">
<text>{{item.name}}</text>
</view>
<!-- arr:[1,3,4,5,6],
arr1:[{id:1,name:11},{id:2,name:22}] -->
# setData
data: {
showTime: {
currentTime: '00:00',
totalTime: '00:00',
},
movableDis: 0,
progress: 0,
}
......
//并没有修改掉data里的值,只是本地存储这两个值
this.data.progress = event.detail.x / (movableAreaWidth - movableViewWidth) * 100
this.data.movableDis = event.detail.x
......
//使用setData修改了data里的值,和react的setData基本一致,在项目中也不要总是去触发,会影响性能
this.setData({
progress: this.data.progress,
movableDis: this.data.movableDis
})
.......
//setData还可以添加一个回调函数
this.setData({
loginShow: false,
}, () => {
this.setData({
modalShow: true,
})
})
# 获取自定义属性值 event.currentTarget.dataset
<view
class="musiclist-container {{item.id === playingId ? 'playing': ''}}"
bindtap="onSelect" data-musicid="{{item.id}}" data-index="{{index}}"
>
</view>
onSelect(event) {
// 事件源 事件处理函数 事件对象 事件类型
// console.log(event.currentTarget.dataset.musicid)
const ds = event.currentTarget.dataset
const musicid = ds.musicid
wx.navigateTo({
url: `../../pages/player/player?musicId=${musicid}&index=${ds.index}`,
})
}
# wx BackgroundAudioManager
// 获取全局唯一的背景音频管理器
const backgroundAudioManager = wx.getBackgroundAudioManager()
backgroundAudioManager.src = result.data[0].url
backgroundAudioManager.title = music.name
backgroundAudioManager.coverImgUrl = music.al.picUrl
backgroundAudioManager.singer = music.ar[0].name
backgroundAudioManager.epname = music.al.name
//播放和暂停
if (this.data.isPlaying) {
backgroundAudioManager.pause()
} else {
backgroundAudioManager.play()
}
//停止
backgroundAudioManager.stop()
如果需要在后台也播放音乐,在app.json中设置"requiredBackgroundModes":["audio"]
const backgroundAudioManager = wx.getBackgroundAudioManager()
methods: {
onTouchEnd() {
//当前时间
const currentTimeFmt = this._dateFormat(Math.floor(backgroundAudioManager.currentTime))
//设置播放时间
backgroundAudioManager.seek(duration * this.data.progress / 100)
},
_bindBGMEvent() {
backgroundAudioManager.onPlay(() => {
console.log('onPlay')
isMoving = false
this.triggerEvent('musicPlay')
})
backgroundAudioManager.onStop(() => {
console.log('onStop')
})
backgroundAudioManager.onPause(() => {
console.log('Pause')
this.triggerEvent('musicPause')
})
backgroundAudioManager.onWaiting(() => {
console.log('onWaiting')
})
//播放时部分手机不可以直接拿到播放时长,可以缓冲一分钟后去取,基本就没问题
backgroundAudioManager.onCanplay(() => {
console.log('onCanplay')
// console.log(backgroundAudioManager.duration)
if (typeof backgroundAudioManager.duration != 'undefined') {
this._setTime()
} else {
setTimeout(() => {
this._setTime()
}, 1000)
}
})
backgroundAudioManager.onTimeUpdate(() => {
// console.log('onTimeUpdate')
if (!isMoving) {
const currentTime = backgroundAudioManager.currentTime
const duration = backgroundAudioManager.duration
const sec = currentTime.toString().split('.')[0]
if (sec != currentSec) {
// console.log(currentTime)
const currentTimeFmt = this._dateFormat(currentTime)
this.setData({
movableDis: (movableAreaWidth - movableViewWidth) * currentTime / duration,
progress: currentTime / duration * 100,
['showTime.currentTime']: `${currentTimeFmt.min}:${currentTimeFmt.sec}`,
})
currentSec = sec
// 联动歌词
this.triggerEvent('timeUpdate', {
currentTime
})
}
}
})
backgroundAudioManager.onEnded(() => {
this.triggerEvent('musicEnd')
})
backgroundAudioManager.onError((res) => {
})
}
# wx手机系统信息
wx.getSystemInfo({
success(res) {
console.log(res)
// 求出1rpx的大小
lyricHeight = res.screenWidth / 750 * 64
},
})
/*{
SDKVersion: "2.8.1"
batteryLevel: 100
brand: "devtools"
deviceOrientation: "portrait"
errMsg: "getSystemInfo:ok"
fontSizeSetting: 16
language: "zh_CN"
model: "iPhone 6/7/8"
pixelRatio: 2
platform: "devtools"
safeArea:
bottom: 667
height: 647
left: 0
right: 375
top: 20
width: 375
__proto__: Object
screenHeight: 667
screenWidth: 375
statusBarHeight: 20
system: "iOS 10.0.1"
version: "7.0.4"
windowHeight: 603
windowWidth: 375
}*/
# wx.createSelectorQuery()
返回一个 SelectorQuery 对象实例。在自定义组件或包含自定义组件的页面中,应使用 this.createSelectorQuery() 来代替。
const query = wx.createSelectorQuery()
query.select('#the-id').boundingClientRect()
query.selectViewport().scrollOffset()
query.exec(function(res){
res[0].top // #the-id节点的上边界坐标
res[1].scrollTop // 显示区域的竖直滚动位置
})
# wx.request
wx.request({
url: 'test.php', //仅为示例,并非真实的接口地址
data: {
x: '',
y: ''
},
header: {
'content-type': 'application/json' // 默认值
},
success (res) {
console.log(res.data)
}
})
# wx父子组件通信
//子组件
properties: {
isSame: Boolean
},
this.triggerEvent('musicEnd')
//父组件
<view class="progress-bar">
<x-progress-bar bind:musicEnd="onNext" isSame="{{isSame}}" />
</view>
- 通信升级
//子组件1 x-progress-bar
this.triggerEvent('timeUpdate', {
currentTime
})
//子组件2 x-lyric
update(currentTime) {
console.log(currentTime)
}
<!-- 父组件中引用子组件 -->
<x-lyric class="lyric" isLyricShow="{{!isLyricShow}}" bind:tap="onChangeLyricShow" lyric="{{lyric}}" />
<view class="progress-bar">
<x-progress-bar bind:musicEnd="onNext" bind:timeUpdate="timeUpdate" bind:musicPlay="onPlay" bind:musicPause="onPause" isSame="{{isSame}}" />
</view>
//父组件监听到子组件1发来的timeUpdate消息,选择到子组件并且调用到子组件中的update方法
timeUpdate(event) {
this.selectComponent('.lyric').update(event.detail.currentTime)
},
- 上一个界面的函数调用 getCurrentPages()
//A1界面的操作方法
onPullDownRefresh: function() {
this.setData({
blogList: []
})
this._loadBlogList(0)
},
//A2界面
// 返回blog页面,并且刷新
wx.navigateBack()
const pages = getCurrentPages()
// console.log(pages)
// 取到上一个页面
const prevPage = pages[pages.length - 2]
prevPage.onPullDownRefresh()
# 设置全局属性
在app.js中用一个globalData可以是设置全局属性
this.globalData = {
playingMusicId: -1,
openid: -1,
}
.......
setPlayMusicId(musicId) {
this.globalData.playingMusicId = musicId
},
getPlayMusicId() {
return this.globalData.playingMusicId
},
- 触发全局方法
const app = getApp()
app.setPlayMusicId(parseInt(musicId) )
if (musicId == app.getPlayMusicId()) {
this.setData({
isSame: true
})
}
const playHistory = wx.getStorageSync(app.globalData.openid)
# wx组件样式隔离
组件中引用iconfont会出现无法使用,可以在组件中再额外定义一份wxss或者,使用externalClasses接受从父级界面传来的class
<!-- 接受外部传来的class不能修改样式,只能单独再去取一个class名 -->
<i class="iconfont icon-sousuo find"></i>
//子组件
externalClasses: [
'iconfont',
'icon-sousuo',
],
<!-- 父级界面传递的值 -->
<x-search iconfont="iconfont" icon-sousuo="icon-sousuo" bind:search="onSearch" />
也可以使用options选项来控制,这样就可以直接使用原来的class类来修改css样式
Component({
options: {
styleIsolation: 'isolated'
}
})
isolated 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;shared 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-shared 或 shared 的自定义组件。(这个选项在插件中不可用。)
# wxslot
Component({
options: {
multipleSlots:true//当需要配置多个插槽时开启这个选项,否则不需要
}
})
<view>
<text>00000000</text>
<slot name="modal-content"></slot>
</view>
//引入的子组件xxx
<xxx>
<view slot="modal-content">xxxyyy</view>
</xxx>
# wx userinfo获取个人信息和openid
<!-- login.wxml -->
<!-- 授权操作,open-type="getUserInfo"和bindgetuserinfo固定写法 -->
<x-bottom-modal modalShow="{{modalShow}}">
<view slot="modal-content">
<button class="login"
open-type="getUserInfo"
bindgetuserinfo="onGotUserInfo"
>获取微信授权信息</button>
</view>
</x-bottom-modal>
// login.js
//会弹出一个授权框,可拒绝和允许,操作完后会执行相应的请求
onGotUserInfo(event) {
console.log(event)
const userInfo = event.detail.userInfo
// 允许授权不包括openid
if (userInfo) {
this.setData({
modalShow: false
})
this.triggerEvent('loginsuccess', userInfo)
} else {
this.triggerEvent('loginfail')
}
}
<x-login modalShow="{{modalShow}}" bind:loginsuccess="onLoginSuccess" bind:loginfail="onLoginFail">
</x-login>
//页面,当点击按钮是判断有无登录信息,没有的话弹出login组件进行授权操作
wx.getSetting({
success:(res)=>{
if(res.authSetting['scope.userInfo']){
//wx.getUserInfo以前版本可以直接弹框授权,现在取消了,默认失败,除非已授权的情况下
wx.getUserInfo({
success:(res)=>{
this.onLoginSuccess({
detail:res.userInfo
})
}
})
}else{
this.setData({
modalShow:true
})
}
}
}),
//同时也监听子组件传来触发消息
onLoginSuccess(event){
console.log(event)
const detail = event.detail
wx.navigateTo({
url: `../blog-edit/blog-edit?nickName=${detail.nickName}&avatarUrl=${detail.avatarUrl}`,
})
},
onLoginFail(){
wx.showModal({
title:"注意",
content:"需要授权后才能发布"
})
},
# open-data
这种信息只能展示,不能存储,不需要授权
<open-data type="userAvatarUrl"></open-data>
<open-data type="userNickName"></open-data>
<open-data type="userCountry"></open-data>
<open-data type="userCity"></open-data>
# openid
// 云函数模板
// 部署:在 cloud-functions/login 文件夹右击选择 “上传并部署”
const cloud = require('wx-server-sdk')
// 初始化 cloud
cloud.init({
// API 调用都保持和云函数当前所在环境一致
env: cloud.DYNAMIC_CURRENT_ENV
})
/**
* 这个示例将经自动鉴权过的小程序用户 openid 返回给小程序端
*
* event 参数包含小程序端调用传入的 data
*
*/
exports.main = (event, context) => {
console.log(event)
console.log(context)
// 可执行其他自定义逻辑
// console.log 的内容可以在云开发云函数调用日志查看
// 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID(需满足 UNIONID 获取条件)等信息
const wxContext = cloud.getWXContext()
return {
event,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
env: wxContext.ENV,
}
}
wx.cloud.callFunction({
name:"login"
}).then(res=>{
console.log(res)
})
# 原生组件需要注意点
除了view text等小程序独有的组件。也可以使用textarea等标签,但注意的是,在绑定事件上,原生组件不支持bind:模式,bindinput√ bind:input×
# wx 上传图片
- wx.previewImage 图片预览
- wx.chooseImage 选择图片
<view class="image-list">
<!-- 显示图片 -->
<block wx:for="{{images}}" wx:key="*this">
<view class="image-wrap">
<image class="image" src="{{item}}" mode="aspectFill"
bind:tap="onPreviewImage" data-imgsrc="{{item}}">
</image>
<i class="iconfont icon-shanchu" bind:tap="onDelImage" data-index="{{index}}"></i>
</view>
</block>
<!-- 选择图片 -->
<view class="image-wrap selectphoto" hidden="{{!selectPhoto}}" bind:tap="onChooseImage">
<i class="iconfont icon-jiahao"></i>
</view>
</view>
data: {
// 输入的文字个数
wordsNum: 0,
footerBottom: 0,
images: [],
selectPhoto: true, // 添加图片元素是否显示
},
onChooseImage() {
// 还能再选几张图片
let max = MAX_IMG_NUM - this.data.images.length
wx.chooseImage({
count: max,//张数
sizeType: ['original', 'compressed'],//原生 压缩图片
sourceType: ['album', 'camera'],//相册 拍照
success: (res) => {
console.log(res)
this.setData({
images: this.data.images.concat(res.tempFilePaths)
})
// 还能再选几张图片
max = MAX_IMG_NUM - this.data.images.length
this.setData({
selectPhoto: max <= 0 ? false : true
})
},
})
},
onDelImage(event) {
this.data.images.splice(event.target.dataset.index, 1)
this.setData({
images: this.data.images
})
if (this.data.images.length == MAX_IMG_NUM - 1) {
this.setData({
selectPhoto: true,
})
}
},
onPreviewImage(event) {
// 6/9
wx.previewImage({
urls: this.data.images,//当前需要上传的图片
current: event.target.dataset.imgsrc,//当前图片的地址
})
},
# 小程序端调用数据库
可以不需要传入openid,而后台数据库可以拿到这个字段
- wx.cloud.uploadFile 图片上传到云存储
| 字段 | 说明 | 数据类型 | 默认值 | 必填 |
|---|---|---|---|---|
| cloudPath | 云存储路径,命名限制见文件名命名限制 | String | - | Y |
| filePath | 要上传文件资源的路径 | String | - | Y |
| config | 配置 | Object | - | N |
send() {
// 2、数据 -> 云数据库
// 数据库:内容、图片fileID、openid、昵称、头像、时间
// 1、图片 -> 云存储 fileID 云文件ID
if (content.trim() === '') {
wx.showModal({
title: '请输入内容',
content: '',
})
return
}
wx.showLoading({
title: '发布中',
mask: true,
})
let promiseArr = []
let fileIds = []
// 图片上传
for (let i = 0, len = this.data.images.length; i < len; i++) {
let p = new Promise((resolve, reject) => {
let item = this.data.images[i]
// 文件扩展名
let suffix = /\.\w+$/.exec(item)[0]
wx.cloud.uploadFile({
cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
filePath: item,
success: (res) => {
console.log(res.fileID)
fileIds = fileIds.concat(res.fileID)
resolve()
},
fail: (err) => {
console.error(err)
reject()
}
})
})
promiseArr.push(p)
}
// 存入到云数据库
Promise.all(promiseArr).then((res) => {
db.collection('blog').add({
data: {
...userInfo,
content,
img: fileIds,
createTime: db.serverDate(), // 服务端的时间
}
}).then((res) => {
wx.hideLoading()
wx.showToast({
title: '发布成功',
})
// 返回blog页面,并且刷新
wx.navigateBack()
const pages = getCurrentPages()
// console.log(pages)
// 取到上一个页面
const prevPage = pages[pages.length - 2]
prevPage.onPullDownRefresh()
})
}).catch((err) => {
wx.hideLoading()
wx.showToast({
title: '发布失败',
})
})
},
设置底部因键盘被遮住修改高度,可以在时间中找到键盘高度
onFocus(event) {
// 模拟器获取的键盘高度为0
// console.log(event)
this.setData({
footerBottom: event.detail.height,
})
},
# catch 和bind capture
bind绑定的事件默认会冒泡,而catch绑定的会阻止冒泡
事件捕获 小程序内,触摸类事件支持捕获阶段,捕获是先于冒泡的触发,绑定捕获事件,可以使用 capture-bind、capture-catch,后者将中断捕获阶段和取消冒泡阶段:
<!-- 在下面的代码中,点击 inner view 会先后调用 handleTap2、handleTap4、handleTap3、handleTap1。 -->
<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
<!-- 如果将上面代码中的第一个 capture-bind 改为 capture-catch,将只触发 handleTap2。 -->
<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
# wx事件对象
当事件触发时,处理函数会响应,传入 event 对象,通过 event 对象可以获取事件触发时候的一些信息,包括时间戳、detail 等。
因为小程序内的事件绑定都是在 WXML 中实现的,所以传递参数只能通过 WXML 上面的属性值来传递,例如下面的代码中,indexDetail 处理函数需要接收生活指数的名称和详情,来弹出弹层提示,这时候需要在标签上增加 data-xx 这样的属性,data-name 和 data-detail 就是两个属性,通过这两个值,可以在 indexDetail 内 event 对象的 target/currentTarget 的 dataset 获取参数。
<view class="life-style">
<view class="item" wx:for="{{lifeStyle}}" data-name="{{item.name}}"
data-detail="{{item.detail}}" bindtap="indexDetail">
<view class="title">
<icon type="{{item.icon}}"></icon>
{{item.name}}
</view>
<view class="content">{{item.info}}</view>
</view>
</view>
// weather/index.js
// 响应事件的处理函数
indexDetail(e) {
const {name, detail} = e.currentTarget.dataset
wx.showModal({
title: name,
content: detail,
showCancel: false
})
}
target 和 currentTarget 都有个 dataset,正确获取 dataset 的姿势是使用 currentTarget 的。
- target:触发事件的源组件
- currentTarget:事件绑定的当前组件
# 分享
<button open-type="share" data-blogid="{{blogId}}" data-blog="{{blog}}" class="share-btn" hover-class="share-hover">
<i class="iconfont icon-fenxiang icon"></i>
<text>分享</text>
</button>
/**
* 用户点击右上角分享
*/
onShareAppMessage: function(event) {
console.log(event)
let blogObj = event.target.dataset.blog
return {
title: blogObj.content,
path: `/pages/blog-comment/blog-comment?blogId=${blogObj._id}`,
// imageUrl: ''
}
}
# 背景图片
不支持本地图片,可使用oss或者转成base64
# wx小程序码
生成的小程序码是buffer,然后转到云存储
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函数入口函数
exports.main = async(event, context) => {
const wxContext = cloud.getWXContext()
const result = await cloud.openapi.wxacode.getUnlimited({
scene: wxContext.OPENID,
// page: "pages/blog/blog"
// lineColor: {
// 'r': 211,
// 'g': 60,
// 'b': 57
// },
// isHyaline: true
})
// console.log(result)
const upload = await cloud.uploadFile({
cloudPath: 'qrcode/' + Date.now() + '-' + Math.random() + '.png',
fileContent: result.buffer
})
return upload.fileID
}
{
"permissions": {
"openapi": [
"wxacode.getUnlimited"
]
}
}
onTapQrCode() {
wx.showLoading({
title: '生成中',
})
wx.cloud.callFunction({
name: 'getQrCode'
}).then((res) => {
console.log(res)
const fileId = res.result
wx.previewImage({
urls: [fileId],
current: fileId
})
wx.hideLoading()
})
},