# 微信小程序常用组件和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()
    })
  },
最后更新: 8/8/2021, 8:26:21 PM