微信小程序 Notes | 开发常用事例(四)

435 阅读7分钟

前言

那啥,关于小程序整理了几篇笔记,多多少少对个人而言有点作用,下面附上对应的文章链接:

希望多多少少可以帮助到像我一样的前端小白。

这年头,挣钱不易,小作坊生存不易!

且行且珍惜吧。

1、List item 和 button 冲突怎么玩?

这个事情是这样的,由于韩总死乞白赖的非要列表新增转发 PDF 功能,由于微信小程序限制只有 button 才具有开放的一些权限,所以直接采用 button 包裹 image 的方案,如下:

<view class="news"> 
  <block wx:for="{{ newsList }}" wx:for-index="index" wx:key="news">
    <view class="news-item" bindtap="onNewsItemClick" data-newsID="{{ item.newID }}">
      <text class="content">{{ item.content }}</text>
      <view class="item_new_bottom">
        <text class="createTime">{{ item.createTime }}</text>
        <button open-type="share" bindtap="onShareAppMessage" hover-stop-propagation="true"
          data-shareid="{{ item.newID }}">
          <image src="/images/ic_share.svg" mode="aspectFit"></image>
        </button>
      </view>
    </view>
    <van-divider wx:if="{{ index != newsList.length -1 }}" />
  </block>
</view>

效果如下:

有个比较尴尬的问题是,当点击分享图标时,对应的 item 事件也会执行,查阅了官方手册后。

将 button 事件类型调整为:catchtap 即可。

针对这两种方式,这里简单理解下。

  • 使用 catchtap 方式暖男,且只对你一个人暖,也就是说事件不会再次向上传递,自我消费;
  • bindtap 方式则是渣男,挨个宠幸。

2、如何分享页面并携带参数?

这个需求是真恶心,先来看下效果图:

简单说下步骤:

  • button 设置 open-type 为 share;
  • onShareAppMessage() 中设置 title、path 以及 imageUrl;
  • onLoad 中接收到解析即可。

下面附上关键 js 代码:

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
  let sharePDFId = parseInt(options.sharePDFId);
  if (sharePDFId >= 0) {
    var newFilePath = wx.env.USER_DATA_PATH + '/' + this.data.newsList[sharePDFId].content + '.pdf';
    let downloadPDFUrl = this.data.newsList[sharePDFId].pdfUrl;
    handleLoadPDF(newFilePath, downloadPDFUrl);
  }
},

/**
 * 用户点击右上角分享
 */
onShareAppMessage: function (res) {
  let that = this;
  let sharePDFId = parseInt(res.target.dataset.shareid);
  return {
    title: that.data.newsList[sharePDFId].content + '.pdf',
    path: '/pages/index/index?sharePDFId=' + sharePDFId,
    imageUrl: urlUtils.getComposeUrl('/images/img_share_pdf.png')
  }
},

3、如何实现列表点击 item title 变色,markers 同时变色?

先来看个效果吧,可能我得描述不是那么准确:

思路都是一样的:

  • 知晓当前 item 点击位置;
  • 更新 markers 中对应的 marker。

给出部分页面代码:

<view class="port_desc">
  <map id="map" bindmarkertap="onMarkersClick" setting="{{ setting }}" show-location markers="{{ markers }}"/>
  <scroll-view scroll-y>
    <block wx:for="{{ portList }}" wx:key="port">
      <view class="item_port" bindtap="onPortItemClick" data-portid="{{ item.portId }}" data-index="{{ index }}">
        
    </block>
  </scroll-view>
</view>

对应的 js 文件:

Page({

  /**
   * 页面的初始数据
   */
  data: { 
    mCurPosititon: 0,  
    markers: [{
      iconPath: "/images/icon_prot.png",
      id: 0,
      latitude: 19.731021,
      longitude: 109.205006,
      width: 30,
      height: 36
    }, {
      iconPath: "/images/icon_prot.png",
      id: 1,
      latitude: 20.022159,
      longitude: 110.283528,
      width: 30,
      height: 36
    }], 
  },
 
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    this.refreshMarkers(0);
  },

  /**
   * 港口 item 点击 - 地图 markers 平移
   */
  onPortItemClick: function (event) {
    let that = this;
    let currentId = event.currentTarget.dataset.portid;
    let portBean = that.data.portList[currentId];
    // 平移 markers 到地图中心
    this.mapContext.moveToLocation({
      longitude: portBean.longitude,
      latitude: portBean.latitude,
      success(res) {
        console.log('---> 平移成功 ', res);
      },
      fail(err) {
        console.log('---> 平移失败 ', err);
      }
    });
    that.refreshMarkers(1);
    that.setData({
      mCurPosititon: event.currentTarget.dataset.index,
    });
    that.refreshMarkers(0);
  },
 
  /**
   * 刷新当前选中的 Markers 点
   */
  refreshMarkers: function (type) {
    let that = this;
    var tempMarkers = that.data.markers;
    tempMarkers[that.data.mCurPosititon].iconPath = type == 0 ? '/images/icon_prot_sel.png' : '/images/icon_prot.png';
    that.setData({
      markers: tempMarkers,
    });
  }

})

有时候想想,这东西真的是相通的。遇到问题,静下心来,慢慢梳理,别慌。

4、wxml 中的三元运算符使用

这个比较 easy,直接放上代码:

<text class="title" style="color:{{ mCurPosititon == index ? 'red' : 'black' }}">{{ item.title }}</text>

5、map 如何自定义气泡窗口,支持动态切换,并且支持点击?

先来看个效果图,一目了然:

这块内容相对 easy,直接放上代码咯。

首先是 js 关键代码:

/**
 * 页面的初始数据
 */
data: { 
  portName: '',
  markerId: 0, 
  markers: [{
    id: 0, iconPath: "/images/icon_prot.png",
    latitude: 19.731021, longitude: 109.205006,
    width: 30, height: 36, customCallout: {
      anchorX: 0,
      anchorY: 0,
      display: "ALWAYS"
    }
  }, {
    id: 1, iconPath: "/images/icon_prot.png",
    latitude: 20.022159, longitude: 110.283528,
    width: 30, height: 36, customCallout: {
      anchorX: 0,
      anchorY: 0,
      display: "ALWAYS"
    }
  }, ],
  portList: [{
    portId: 0, markerId: 0, title: '洋浦港',
    desc: '洋浦港....',
    avatar: 'https:/xxxx9e.jpg',
    latitude: 19.731021, longitude: 109.205006,
  }, {
    portId: 1, markerId: 1, title: '海口港',
    desc: '海口港xxx',
    avatar: 'https://xxxae.jpeg',
    latitude: 20.022159, longitude: 110.283528,
  }, ]
},

/**
 * 生命周期函数--监听页面显示
 */
onShow: function () {
  let that = this; 
  // 初始化数据
  let portBean = that.data.portList[0];
  that.setData({
    portName: portBean.title,
    markerId: portBean.markerId
  });
},

/**
 * 港口 item 点击 - 地图 markers 平移
 */
onPortItemClick: function (event) {
  let that = this;
  let currentId = event.currentTarget.dataset.portid;
  let portBean = that.data.portList[currentId];
  // 平移 markers 到地图中心
  this.mapContext.moveToLocation({
    longitude: portBean.longitude,
    latitude: portBean.latitude,
    success(res) {
      console.log('---> 平移成功 ', res);
    },
    fail(err) {
      console.log('---> 平移失败 ', err);
    }
  });
  that.refreshMarkers(1);
  that.setData({
    mCurPosititon: event.currentTarget.dataset.index,
  });
  that.refreshMarkers(0);
  // 更新气泡数据
  that.setData({
    portName: portBean.title,
    markerId: portBean.markerId
  });
},

/**
 * Markers 点击查看详情
 */
bindcallouttap: function (event) {
  let markerId = parseInt(event.detail.markerId); // 其实这就是 id,为了实现对应的详情切换
  wx.navigateTo({
    url: '/pages/portDetail/portDetail?portId=' + markerId
  })
},

/**
 * 刷新当前选中的 Markers 点
 */
refreshMarkers: function (type) {
  let that = this;
  var tempMarkers = that.data.markers;
  tempMarkers[that.data.mCurPosititon].iconPath = type == 0 ? '/images/icon_prot_sel.png' : '/images/icon_prot.png';
  that.setData({
    markers: tempMarkers,
  });
} 

然后就是对应的 wxml 关键代码:

<map bindcallouttap="bindcallouttap" id="map" setting="{{ setting }}" show-location markers="{{ markers }}">
  <cover-view slot="callout">
    <cover-view marker-id="{{ markerId }}">
      <cover-view class="map_custiom_callout">
        <cover-view class="portName">{{ portName }}</cover-view>
      </cover-view>
    </cover-view>
  </cover-view>
</map>
<scroll-view scroll-y>
  <block wx:for="{{ portList }}" wx:key="port">
    <view class="item_port" bindtap="onPortItemClick" data-portid="{{ item.portId }}" data-index="{{ index }}">
      <!-- ... -->
  </block>
</scroll-view>

这块从一开始自己就进入了一个误区。其实很多事情都是循循渐进,太过于急功近利,反而有点得不偿失了。无论身处何地,保持自身冷静,条理分析。

6、关于那些烦人的相对路径处理

相信大家都遇到过如下情况,比如我定义一个 urlUtils 工具类,那么在对应使用的 js 中就需要通过如下方式引用:

  • const urlUtils = require('../../utils/urlUtils.js')

看到前面的 ../ 就说烦不烦?

咨询大佬,大佬提供了一种使用绝对路径方案,如下:

Step 1: app.js 新增 require 方法:

require: function ($url) { return require($url) },

Step 2: 替换原有很 low 的方式。

//获取应用实例
const app = getApp();
const urlUtils = app.require('utils/urlUtils.js');

ummm,爽多了。哈哈哈。

对了,记得关闭「上传时进行代码保护」

7、如何实现点击图片弹出并播放视频?

还是老规矩,放个效果图,稍等,等我录制,😂

附上对应 wxml 内容:

<video id="videoID" bindfullscreenchange="bindFullScreenChange" 
	direction="0" controls="true" src="{{ videoLink }}"
    	show-fullscreen-btn="{{ false }}"></video>

这里还单独给了一个样式:

video {
  display: none;
}

然后就是对应的 js:

/**
 * 视频进入和退出全屏时触发,event.detail = {fullScreen, direction},direction 有效值为 vertical 或 horizontal
 */
bindFullScreenChange: function (event) {
  this.videoContext.pause();
  this.setData({
    videoLink: null,
  });
},

function handleVideoPlay(that, videoUrl) {
  that.setData({
    videoLink: videoUrl
  });
  that.videoContext.requestFullScreen();
  setTimeout(function () {
    that.videoContext.play();
  }, 600);
}

这里特意说一下,这里按照官方设置 true/false,在真机上无效,需要用 {{ }} 去包裹每个 Boolean 值即可。

这点官方文档体验性不太好。

8、搜索结果高亮显示

效果如下所示:

一起来看看这万恶的资本主义:

一时怒怼一时爽,爽完还得去撸码,谁让咱是底层卑微的打工人呢?

最初的想法是,按照 Android 之前直接替换标签加样式的方案,结果小程序直接把替换的标签展示出来了。

度娘了一波,得到的方案几乎差不多,最终都是拆分 title,然后去匹配搜索的 key,并设置对应的高亮 CSS,有的大佬直接一把梭,手撸自定义组件,尔等直接佩服佩服,告辞~

好啦,俏皮话不多说,先附上 wxml 文件,已省略其他无关代码片段:

<view class="container">
  <van-search 
      bind:search="onSearchClick" 
      clearable input-align="center" 
      value="{{ searchKey }}" 
      shape="round" focus
      placeholder="请输入搜索关键词" /> 
  <block 
      wx:if="{{ exhResultList.length }}" 
      wx:for='{{ exhResultList }}' 
      wx:for-index="index" wx:key="exh">
    <view 
      class="tab_item" 
      catchtap="onActionItemClick" 
      data-itemid="{{ item.exh_id }}"> 
      <view class="tab_info">
      	<!-- 关键是这块 -->
        <view class="title">
          <!-- 循环遍历匹配搜索关键字,并设置高亮 CSS -->
          <text 
            wx:for="{{ item.exh_name }}" 
            class="{{item == searchKey ? 'searchHigh' : '' }}">{{ item }}</text>
        </view> 
      </view>
    </view>
    <van-divider wx:if="{{ index != exhResultList.length -1 }}" />
  </block> 
</view>

随后简单附上对应高亮的 CSS,其实就是个设置字体颜色:

.searchHigh {
  color: red;
}

最后的 js 关键代码:

data: { 
  searchKey:'', 
},    
  
onSearchClick: function (event) {
  var that = this; 
  // 搜索关键字记得去除前后空格
  that.setData({
    exhResultList: [],
    searchKey : that.trim(event.detail)
  });
  // ...
},

/**
 * 去除前后空格
 * @param {} s 
 */
trim: function (s) {
  return s.replace(/(^\s*)|(\s*$)/g, "");
},

9、文字环绕图片效果

效果如下:

关键就是 float:left 直接附上 wxml 代码:

<view class="header">
  <image src="{{ mHeaderImage }}" mode="aspectFit"></image>
  <label>
    忽略众多文字内容...
  </label>
</view>

对应 CSS:

.header {}

.header image {
  float: left;
  width: 180rpx;
  height: 180rpx;
}

.header label {
  font-size: 24rpx;
}

10、记录个列表展示方式实现

简单举个效果,到时候大家举一反三即可。实现原理一样,业务需求则比较复杂咯。

主要用到了 vant 提供的 Layout 组件,它将一行氛围 24 列栅格,暂时称为栅格布局吧。

直接丢代码咯:

<view class="hot_line_class">
  <van-row>
    <block wx:for="{{ mHotAuthorList }}" wx:key="*this">
      <van-col span="6"> 
        <view class="item_hot_line">
          <image class="avatar" src="{{ item.avatar }}" mode="aspectFit"></image>
          <text class="name">{{ item.jobName }}</text>
          <text class="mobile">{{ item.name }}</text>
        </view>
      </van-col>
    </block>
  </van-row>
</view>

span="6" 这里代表一行 4 列,24 / 6 = 4。剩下诸如此类。

还有个比较关键的地方是千万记得给 item 设置宽度 100%,不然当列表内容为奇数时,最后一位显示比较奇怪,别问我咋知道的。

后记

总觉得每个小例子尽量配一个演示效果,方便直接上手,用最快捷的方式证明是否和预期一致,转过头来看,是增加了不少麻烦。一分耕耘一分收获,希望可以帮助到和我一样的前端小白白。

暴力解法也好,最优解也好,首先我觉得是先解出来,随后逐步优化。一段看似简洁高效的代码,背后谁人能知作者心血。

没必要力求最优,挨个击破也未尝不是一种好方式。

ummm,96 年的都被叫叔叔了,应该是前端老白白了(手动滑稽~)。

一起努力呀,万一一不小心和我鸡老大肩并肩了呢~

参考资料