微信小程序 消息模板 发送推送消息 快速测试 DEMO

首先 微信公众平台 后台添加模板消息

/utils/config.js

var config = {
  APPID:'wxe1af000000000000',
  APPSECRET:'050000000000000000000000000000',
  TEMPLATE_ID:{
    'msg': 'Y2idRTMVs4e_w72uSiPATptx3yFfIp2B97H8GXj_XXX'
  }
}
module.exports = config

/utils/wxapi.js

注意:正式环境中不允许访问 api.weixin.qq.com , 仅在测试环境下测试并开启不校验合法域名

var config = require('config.js')

const access_token = function(callback = function() {}) {
  wx.request({
    method: 'GET',
    url: 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + config.APPID + '&secret=' + config.APPSECRET,
    data: {},
    success: function(res) {
      console.log(res)
      callback(res)
    }
  })
}

const template = function(params={},callback=function(){}) {
  var data = {
    touser: params.openid,
    template_id: config.TEMPLATE_ID.msg,
    form_id: params.form_id,
    data: {
      "keyword1": {
        "value": "测试文字,通过",
        "color": "#4a4a4a"
      },
      "keyword2": {
        "value": new Date(),
        "color": "#9b9b9b"
      }
    },
    color: 'red', //颜色
    emphasis_keyword: 'keyword2.DATA' //需要着重显示的关键词
  }
  wx.request({
    url: 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=' + params.access_token,
    method: 'POST',
    data: data,
    success:function(res){
      console.log(res)
      callback(res)
    }
  })
}

const openid = function(callback=function(){}){
  wx.login({
    success: function (res) {
      if (res.code) {
        //发起网络请求 将code传给服务器
        wx.request({
          url: 'https://api.weixin.qq.com/sns/jscode2session?appid='+config.APPID+'&secret='+config.APPSECRET+'&js_code='+res.code+'&grant_type=authorization_code',
          data: {},
          success:function(res){
            console.log(res)
            callback(res)
          }
        })
      } else {
        console.log('获取用户登录态失败!' + res.errMsg)
      }
    }
  });
}

module.exports = {
  access_token: access_token,
  template: template,
  openid: openid
}

/pages/test/test.js

const wxapi = require('../../utils/wxapi.js')
Page({
  getFormID: function (e) {
    var that = this
    console.log(e)
    console.log(e.detail.formId)
    
    wx.showToast({
      title: e.detail.formId,
      icon:'none'
    })
    that.setData({
      form_id: e.detail.formId
    })
  },
  getAccessToken:function(){
    var that = this
    wxapi.access_token(function (res) {
      wx.showToast({
        title: res.data.access_token,
        icon: 'none'
      })
      that.setData({
        access_token: res.data.access_token
      })
    })
  },
  getOpenid:function(){
    var that = this
    wxapi.openid(function(res){
      wx.showToast({
        title: res.data.openid,
        icon:'none'
      })

      that.setData({
        openid:res.data.openid
      })
    })
  },
  sendTemplate: function () {
    var data = {
      'access_token': this.data.access_token,
      'form_id': this.data.form_id,
      'openid': this.data.openid,
    }
    wxapi.template(data, function (res) {

      wx.showToast({
        title: res.data.errmsg,
        icon: 'none',
        duration: 5000
      })
    })
  },
})

/pages/test/test.wxml

<form name='pushMsgFm' report-submit='true' bindsubmit='getFormID'> 
  <button size='mini' form-type="submit" class="zan-btn zan-btn--large zan-btn--danger payButton">获取 form_id</button>
</form>  
<button size='mini' bindtap='getAccessToken'>获取 access_token</button>
<button size='mini' bindtap='getOpenid'>获取 openid</button>
<button size='mini' bindtap='sendTemplate'>发送测试</button>

依次点击四个按钮即可测试

微信小程序 自带 GPS 定位的坑

微信小程序打卡定位调试了一天始终差五百多米。百度坐标转换各种问题都找了都没问题,最终发现是小程序自带定位有问题,文档里也没有写,大坑。

const getLocation = function(callback = function() {}) {
  wx.getLocation({
    type: 'GCJ02',
    success: function(res) {
      console.log('微信自带定位返回结果')
      console.log(res)
      wx.setClipboardData({
        data: JSON.stringify({ 'lat': lat, 'lng': lng }),
      })
      callback(res)
    }
  }) 
}
const getLocation = function(callback = function() {}) {
  wx.getLocation({
    type: 'gcj02',
    success: function(res) {
      console.log('微信自带定位返回结果')
      console.log(res)
      wx.setClipboardData({
        data: JSON.stringify({ 'lat': lat, 'lng': lng }),
      })
      callback(res)
    }
  }) 
}

大写的 GCJ02 便宜了五百多米, 小写的 gcj02 是正确的

小程序自带的 map 组件不受此影响,但就是因为不受影响我一直以为不是微信的问题。

卧了个大凸(艹皿艹 )

微信小程序 BASE64 encode 加密 decode 解密

utils/base64.js

var Base64 = {
  // private property
  _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  // public method for encoding
  encode: function(input) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;
    input = Base64._utf8_encode(input);

    while (i < input.length) {

      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }

      output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

    }

    return output;
  },

  // public method for decoding
  decode: function(input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    while (i < input.length) {

      enc1 = this._keyStr.indexOf(input.charAt(i++));
      enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++));
      enc4 = this._keyStr.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      output = output + String.fromCharCode(chr1);

      if (enc3 != 64) {
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output = output + String.fromCharCode(chr3);
      }

    }

    output = Base64._utf8_decode(output);

    return output;
  },

  // private method for UTF-8 encoding
  _utf8_encode: function(string) {
    string = string.replace(/\r\n/g, "\n");
    var utftext = "";

    for (var n = 0; n < string.length; n++) {

      var c = string.charCodeAt(n);

      if (c < 128) {
        utftext += String.fromCharCode(c);
      } else if ((c > 127) && (c < 2048)) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      } else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }

    }

    return utftext;
  },

  // private method for UTF-8 decoding
  _utf8_decode: function(utftext) {
    var string = "";
    var i = 0;
    var c = 0;
    var c1 = 0;
    var c2 = 0;

    while (i < utftext.length) {

      c = utftext.charCodeAt(i);

      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      } else if ((c > 191) && (c < 224)) {
        c2 = utftext.charCodeAt(i + 1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      } else {
        c2 = utftext.charCodeAt(i + 1);
        c3 = utftext.charCodeAt(i + 2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }

    }

    return string;
  }
}

module.exports = Base64

使用 引包

const base64 = require('base64.js')

微信小程序 封装 httpPost 方法 发送 PHPSESSID 保持登录连接

utils/config.js

var config = {
  APPID:'wxe1af0e00000000f3',
  BASE_URL:'https://xx.xxx.xx/xx/',
}
module.exports = config

utils/util.js 文件

var config = require('config.js')
function Post(url, data, cb,header=null) {

  if(header = 'session'){
    var headerObj = {
      'content-type': 'application/x-www-form-urlencoded',
      'Cookie': 'PHPSESSID=' + wx.getStorageSync('sessionid')
    }
  }else{
    var headerObj = {
      'content-type': 'application/x-www-form-urlencoded'
    }
  }
  wx.request({
    method: 'POST',
    url: config.BASE_URL + url,
    data: data,
    header: headerObj,
    success: (res) => {
      console.log("post 请求:" + config.BASE_URL + url);
      console.log(res.data)
      typeof cb == "function" && cb(res.data, "");
    },
    fail: (err) => {
      typeof cb == "function" && cb(null, err.errMsg)
      console.log("post 请求:" + config.BASE_URL + url);
      console.log(err);
    }
  })
} 
module.exports = {
  httpPost: Post,
}

使用 如 /pages/index.js

const util = require('../../utils/util.js')
test: function() {
	util.httpPost('index.php?s=/sign/gettimeplan',{'userid':805},function(res){
		console.log(res.result[0])
	},'session')
}

微信小程序 使用自带 rich-text 组件进行 HTML 富文本解析

JS

Page({

  /**
   * 页面的初始数据
   */
  data: {
    html: '<div class="div_class" style="line-height: 60px; color: red;">Hello World!</div><img src="/resource/test/test.jpg" style="width:200px;height:200px;"/>',    
  },
})

WXML

<rich-text nodes="{{html}}" data-data="{{html}}" bindtap="tap"></rich-text>

开发文档:

https://developers.weixin.qq.com/miniprogram/dev/component/rich-text.html

微信小程序 使用 wxParse HTML 富文本解析

下载地址 https://github.com/icindy/wxParse

将 wxParse 拷贝到项目目录下,本人拷至 /utils下,见引用

JS

const WxParse = require('../../utils/wxParse/wxParse.js')
Page({

  /**
   * 页面的初始数据
   */
  data: {
    
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.htmlTest()    
  },
  htmlTest:function(){
    var article = "<div>我是HTML代码</div><img src='/resource/test/test.jpg'/>"
    var that = this
    WxParse.wxParse('article', 'html', article, that, 5)
  },
})

WXML

<import src='../../utils/wxParse/wxParse.wxml' />
<template is="wxParse" data="{{wxParseData:article.nodes}}" />

WXSS

@import "/utils/wxParse/wxParse.wxss";

微信小程序 写一个 多级通讯录

无限级通讯录

WXML

<view class='title-container'>
  <view class="title" wx:for="{{branchTitle}}" wx:key="key" wx:for-index="index" wx:for-item="item" bindtap='toPre' data-level="{{item.level}}">
    <block wx:if="{{index==0}}">
      <text>{{item.title}}</text>
    </block>
    <block wx:else>
      <image class='next-icon' mode='aspectFit' src='/resource/cal/next.png'></image>
      <text>{{item.title}}</text>
    </block>
  </view>
</view>
<view class='address-container'>
  <view class='address-list' wx:for="{{address}}" wx:key="keyL" wx:for-index="indexL" wx:for-item="itemL">
    <block wx:if="{{itemL.type == 'branch'}}">
      <view class='address-one' bindtap='toId' data-id="{{itemL.id}}">
        <view class='left' style='background-color:red'>
          <view>{{itemL.name_slice}}</view>
        </view>
        <view class='right'>
          <view class='name-container'>
            <text class='name'>{{itemL.name}}</text>
          </view>
        </view>
      </view>
    </block>
    <block wx:elif="{{itemL.type == 'people'}}">
      <view class='address-one'>
        <view class='left'>
          <view>{{itemL.name_slice}}</view>
        </view>
        <view class='right'>
          <view class='name-container'>
            <text class='name'>{{itemL.name}}</text>
            <text class='job'>{{itemL.job}}</text>
          </view>
          <view class='tel-container'>
            <text class='tel'>{{itemL.tel}}</text>
          </view>
        </view>
      </view>
    </block>
  </view>
</view>

JS

// pages/addressbook/addressbook.js
const util = require('../../utils/util.js')
Page({

  /**
   * 页面的初始数据
   */
  data: {
    address: [],
    branchTitle: [{
      'level': 0,
      'title': '总部'
    }],
    level: 0,
  },

  toId: function (e) {
    console.log(e)
    var id = e.currentTarget.dataset.id

    var address = this.id2Detail(id)
    var title = this.id2Title(id)

    var oldBranchTitle = this.data.branchTitle
    var length = oldBranchTitle.length

    var branchTitle = oldBranchTitle
    branchTitle = branchTitle.concat([{
      'level': length + 1,
      'title': title
    }])

    var obj = {
      'address': JSON.stringify(address),
      'branchTitle': JSON.stringify(branchTitle)
    }

    var str = util.obj2Query(obj)
    wx.navigateTo({
      url: '/pages/addressbook/addressbook?' + str,
    })
    console.log(address)
  },
  toPre: function (e) {
    var level = e.currentTarget.dataset.level
    var currentLevel = this.data.level
    console.log(currentLevel)
    console.log('currentLevel - level = ' + currentLevel + '-' + level + '=' + (currentLevel - level))
    if (currentLevel - level != 0) {
      wx.navigateBack({
        delta: currentLevel - level
      })
    }

  },
  addressFormat: function (address = []) {
    var i
    for (i in address) {
      if (address[i].type == 'branch') {
        address[i]['name_slice'] = address[i].name.substr(0, 2)
      } else if (address[i].type == 'people') {
        address[i]['name_slice'] = address[i].name.substr(address[i].name.length - 2, 2)
      }
    }
    return address
  },
  id2Title: function (id) {
    var address = this.data.address
    var i
    for (i in address) {
      if (address[i].id == id) {
        return address[i].name
      }
    }
  },

  id2Detail: function (id) {
    var address = this.data.address
    var i
    for (i in address) {
      if (address[i].id == id) {
        return address[i].subclass
      }
    }
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    console.log('onLoad')
    console.log(options)
    if (options.hasOwnProperty('address')) {
      var address = JSON.parse(options.address)
      var branchTitle = JSON.parse(options.branchTitle)
      var level = branchTitle[branchTitle.length - 1].level
      address = this.addressFormat(address)
      this.setData({
        address: address,
        branchTitle: branchTitle,
        level: level
      })
    } else {
      this.init()
    }
  },
  init: function () {
    var address = [{
      'id': 1,
      'name': '总经办',
      'type': 'branch',
      'subclass': [{
        'id': 2,
        'type': 'people',
        'name': '张震岳',
        'job': '法务',
        'tel': '18888888888'
      },
      {
        'id': 3,
        'type': 'people',
        'name': '李宇春',
        'job': '法务',
        'tel': '18888888868'
      },
      ]
    },
    {
      'id': 4,
      'name': '人事行政部',
      'type': 'branch',
      'subclass': [{
        'id': 5,
        'type': 'people',
        'name': '佐助',
        'job': '人事总监',
        'tel': '1883333668'
      },
      {
        'id': 6,
        'type': 'people',
        'name': '鸣人',
        'job': '招聘主管',
        'tel': '1666688888'
      }
      ]
    },
    {
      'id': 8,
      'name': '运营部',
      'type': 'branch',
      'subclass': [{
        'id': 5,
        'type': 'people',
        'name': '易烊千玺',
        'job': '运营总监',
        'tel': '1883333668'
      },
      {
        'id': 6,
        'type': 'people',
        'name': '王源',
        'job': '总监助理',
        'tel': '1666688888'
      },
      {
        'id': 8,
        'name': '华北区',
        'type': 'branch',
        'subclass': [{
          'id': 8,
          'name': '北京芍药居',
          'type': 'branch',
          'subclass': [{
            'id': 5,
            'type': 'people',
            'name': '爱德华',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '阿尔冯思',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 38,
          'name': '天津芥园道',
          'type': 'branch',
          'subclass': [{
            'id': 25,
            'type': 'people',
            'name': '若林源三',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '大空翼',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 5,
          'type': 'people',
          'name': '郑少秋',
          'job': '华北区总监',
          'tel': '1883333668'
        },
        {
          'id': 6,
          'type': 'people',
          'name': '刘德华',
          'job': '华北区总监助理',
          'tel': '1666688888'
        }
        ]
      },
      {
        'id': 12,
        'name': '西南区',
        'type': 'branch',
        'subclass': [{
          'id': 18,
          'name': '昆明汇都店',
          'type': 'branch',
          'subclass': [{
            'id': 5,
            'type': 'people',
            'name': '邓紫棋',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '汪峰',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 36,
          'name': '成都武侯祠',
          'type': 'branch',
          'subclass': [{
            'id': 5,
            'type': 'people',
            'name': '林俊杰',
            'job': '人事总监',
            'tel': '1883333668'
          },
          {
            'id': 6,
            'type': 'people',
            'name': '王菲',
            'job': '招聘主管',
            'tel': '1666688888'
          }
          ]
        },
        {
          'id': 5,
          'type': 'people',
          'name': '萨琳娜',
          'job': '人事总监',
          'tel': '1883333668'
        },
        {
          'id': 6,
          'type': 'people',
          'name': '田馥甄',
          'job': '招聘主管',
          'tel': '1666688888'
        }
        ]
      },
      ]
    },
    {
      'id': 7,
      'type': 'people',
      'name': '孙总',
      'job': '董事长',
      'tel': '16666666666'
    }
    ]
    address = this.addressFormat(address)
    this.setData({
      address: address
    })
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    console.log('onshow')
    console.log(this.data.branchTitle)

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

WXSS

.title-container {
  padding: 30rpx 50rpx;
  font-size: 30rpx;
  background-color: #fefefe;
  box-shadow: 0 10rpx 10rpx #efefef;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}

.title {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}

.next-icon {
  width: 20rpx;
  height: 20rpx;
  margin: 0 10rpx;
}

.address-container {
  margin-top: 20rpx;
  background-color: #fefefe;
}

.address-list {
  padding: 30rpx 50rpx;
}

.address-one {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}

.address-one .left {
  width: 90rpx;
  height: 90rpx;
  border-radius: 50%;
  background-color: #2396f2;
  color: #fff;
  font-size: 30rpx;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  margin-right: 20rpx;
}

.address-one .right {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  font-size: 30rpx;
}

.name {
  font-weight: 700;
  margin-right: 20rpx;
}

.job {
  color: #aaa;
  font-size: 26rpx;
}

.tel {
  color: #aaa;
  font-size: 26rpx;
}

.swiper-tab {
  width: 100%;
  border-bottom: 2rpx solid #ccc;
  text-align: center;
  height: 88rpx;
  line-height: 88rpx;
  font-weight: bold;
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;
}

.swiper-tab-item {
  display: inline-block;
  /* width: 33.33%; */
  width: inherit;
  color: red;
}

.active {
  color: aqua;
  border-bottom: 4rpx solid red;
}

util.js

const obj2Query = function (params = new Object()) {
  console.log('obj2Query 方法')
  var arr = Object.keys(params)
  if (arr.length == 0) {
    return ''
  }
  var str = ''
  for (var i in params) {
    str = str + '&' + i + '=' + params[i]
  }
  return str
}

module.exports = {
  obj2Query: obj2Query
}

微信小程序 日历

JS

const app = getApp()
const util = require('../../utils/util.js')
const date = require('../../utils/date.js')

Page({
  data: {
    year: 0,
    month: 0,
    date: ['日', '一', '二', '三', '四', '五', '六'],
    dateArr: [],
    isToday: 0,
    isTodayWeek: false,
    todayIndex: 0,
    isClick:null
  },
  onLoad: function () {
    var now = new Date()
    console.log(now)
    if (now.length>0){
      var year = now.getFullYear();
      var month = now.getMonth() + 1;
      console.log('有 now')
    } else {
      var thedate = date.theDate()
      var year = date.getFullYear(thedate)
      var month = date.getMonth(thedate)
      console.log('没有 now')
    }
    this.start(year, month)
    this.isToday()
    console.log('onLoad done')

  },
  isToday:function(){
    let now = new Date();
    var theDay = '' + now.getFullYear() + util.formatNumber(now.getMonth() + 1) + util.formatNumber(now.getDate())
    this.setData({
      isToday: theDay,
      isClick: theDay
    })
  },
  start: function (year, month){
    month = Number(month)
    this.dateInit(year, month-1);
    this.setData({
      year: year,
      month: month,
    })
  },
  dateInit: function (setYear, setMonth) {
    //全部时间的月份都是按0~11基准,显示月份才+1
    let dateArr = [];						//需要遍历的日历数组数据
    let arrLen = 0;							//dateArr的数组长度
    let now = setYear ? new Date(setYear, setMonth) : new Date();
    let year = setYear || now.getFullYear();
    let nextYear = 0;
    let month = setMonth || now.getMonth();					//没有+1方便后面计算当月总天数
    let nextMonth = (month + 1) > 11 ? 1 : (month + 1);
    let startWeek = new Date(year , (month + 1) ,1).getDay();							//目标月1号对应的星期
    let dayNums = new Date(year, nextMonth, 0).getDate();				//获取目标月有多少天
    let obj = {};
    let num = 0;

    if (month + 1 > 11) {
      nextYear = year + 1;
      dayNums = new Date(nextYear, nextMonth, 0).getDate();
    }
    arrLen = startWeek + dayNums;
    console.log('startWeek')
    console.log(startWeek)
    for (let i = 0; i < arrLen; i++) {
      if (i >= startWeek) {
        num = i - startWeek + 1;
        obj = {
          isToday: '' + year + util.formatNumber(month + 1) + util.formatNumber(num),
          dateNum: num,
          weight: 6
        }
      } else {
        obj = {};
      }
      console.log(obj)
      dateArr[i] = obj;
    }
    console.log(dateArr)
    this.setData({
      dateArr: dateArr
    })

    let nowDate = new Date();
    let nowYear = nowDate.getFullYear();
    let nowMonth = nowDate.getMonth() + 1;
    let nowWeek = nowDate.getDay();
    let getYear = setYear || nowYear;
    let getMonth = setMonth >= 0 ? (setMonth + 1) : nowMonth;

    if (nowYear == getYear && nowMonth == getMonth) {
      this.setData({
        isTodayWeek: true,
        todayIndex: nowWeek
      })
    } else {
      this.setData({
        isTodayWeek: false,
        todayIndex: -1
      })
    }
  },
  lastMonth: function () {
    //全部时间的月份都是按0~11基准,显示月份才+1
    let year = this.data.month - 2 < 0 ? this.data.year - 1 : this.data.year;
    let month = this.data.month - 2 < 0 ? 11 : this.data.month - 2;
    this.start(year, Number(month) + 1)
  },
  nextMonth: function () {
    //全部时间的月份都是按0~11基准,显示月份才+1
    let year = this.data.month > 11 ? this.data.year + 1 : this.data.year;
    let month = this.data.month > 11 ? 0 : this.data.month;
    this.start(year, Number(month) + 1)
  },
  dateClick:function(res){
    console.log(res)
    var clickDay = res.currentTarget.dataset.date
    console.log(clickDay)
    this.setData({
      isClick:clickDay
    })
  }
})

WXML

<view class='wrap'>
  <view>
    <view class='date-show'>
      <view class='lt-arrow' bindtap='lastMonth'>
        <image src='/resource/cal/next.png' mode='aspectFit'></image>
      </view>
      {{year}}年{{month}}月
      <view class='rt-arrow' bindtap='nextMonth'>
        <image src='/resource/cal/next.png' mode='aspectFit'></image>
      </view>
    </view>
  </view>
  <view class='header'>
    <view wx:for='{{date}}' wx:key='{{key}}' class='{{(index == todayIndex) && isTodayWeek ? "weekMark" : ""}}'>{{item}}
      <view></view>
    </view>
  </view>
  <view class='date-box'>
    <view wx:for='{{dateArr}}' wx:key='{{key}}' class='{{isToday == item.isToday ? "nowDay" : ""}} {{isClick!=null && isClick == item.isToday ? "clickDay":""}}' data-date='{{item.isToday}}' bindtap='dateClick'>
      <view class='date-head'>
        <view>{{item.dateNum}}</view>
      </view>
      <view class='date-weight'>
        <block wx:if="{{item.weight}}">
          <view class='checkTwo'></view>
        </block>
      </view>
    </view>
  </view>
</view>

WXSS

.date-show{
	position: relative;
	width: 250rpx;
	font-family: PingFang-SC-Regular;
	font-size: 40rpx;
	color: #282828;
	text-align: center;
	margin: 59rpx auto 10rpx;
}
.lt-arrow,.rt-arrow{
	position: absolute;
	top: 1rpx;
	width: 60rpx;
	height: 60rpx;
}
.lt-arrow image,.rt-arrow image{
	width: 26rpx;
	height: 26rpx;
}
.lt-arrow{
	left: -110rpx;
	transform: rotate(180deg);
}
.rt-arrow{
	right: -100rpx;
}
.header{
	font-size: 0;
	padding: 0 24rpx;
}
.header>view{
	display: inline-block;
	width: 14.285%;
	color: #333;
	font-size: 30rpx;
	text-align: center;
	border-bottom: 1px solid #D0D0D0;
	padding: 10rpx 0;
}
.weekMark{
	position: relative;
}
.weekMark view{
	position: absolute;
	bottom: 0;
	left: 0;
	width: 100%;
	border-bottom: 1px solid #22A7F6;
}
.date-box{
	font-size: 0;
	padding: 10rpx 0;
}
.date-box>view{
	position: relative;
	display: inline-block;
	width: 14.285%;
	color: #020202;
	font-size: 40rpx;
	text-align: center;
	vertical-align: middle;
	margin: 10rpx 0 0;
}
.date-head{
	height: 60rpx;
	line-height: 60rpx;
	font-size: 26rpx;
}
.nowDay .date-head{
	width: 60rpx;
	border-radius: 50%;
	text-align: center;
	color: #fff;
	background-color: #22A7F6;
  opacity:0.5;
	margin: 0 auto;
}

.clickDay .date-head{
	width: 60rpx;
	border-radius: 50%;
	text-align: center;
	color: #fff;
	background-color: #22A7F6;
  opacity:1;
	margin: 0 auto;
}
.date-weight{
	font-size: 22rpx;
	padding: 15rpx 0 0;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
}
.nowDay .date-weight{
	color: #22A7F6;
}
.clickDay .date-weight{
	color: #22A7F6;
}
.one{
	position: absolute;
	bottom: 0;
	right: 5rpx;
	width: 20rpx;
	height: 20rpx;
	border-radius: 50%;
	background-color: red;
}
.two{
	position: absolute;
	bottom: 30rpx;
	right: 5rpx;
	width: 20rpx;
	height: 20rpx;
	border-radius: 50%;
	background-color: blue;
}
.checkTwo{
  width:10rpx;
  height:10rpx;
  border-radius:50%;
  background-color: blue;
}

util.js

const formatTime = date => {
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()

  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}

const formatNumber = n => {
  n = n.toString()
  return n[1] ? n : '0' + n
}

module.exports = {
  formatTime: formatTime,
  formatNumber: formatNumber
}

date.js

const app = getApp()
const util = require('util.js')
const dateAdd = function (startDate, days) {
  startDate = new Date(startDate);
  startDate = +startDate + days * 1000 * 60 * 60 * 24;
  startDate = new Date(startDate);
  var nextStartDate = startDate.getFullYear() + "-" + util.formatNumber((startDate.getMonth() + 1)) + "-" + util.formatNumber(startDate.getDate());
  return nextStartDate;
}

const theDate = function (num = 0){
  var thisDate = new Date();
  var myDate = new Date(thisDate.getTime() + 24 * 60 * 60 * 1000 * num)
  var theDate = myDate.getFullYear() + '-' + util.formatNumber((myDate.getMonth() + 1)) + '-' + util.formatNumber(myDate.getDate())
  return theDate
}

// 时间比较函数
const compareDate = function (startDate, endDate) {
  var arrStart = startDate.split("-");
  var startTime = new Date(arrStart[0], arrStart[1], arrStart[2]);
  var startTimes = startTime.getTime();
  var arrEnd = endDate.split("-");
  var endTime = new Date(arrEnd[0], arrEnd[1], arrEnd[2]);
  var endTimes = endTime.getTime();
  if (endTimes <= startTimes) {
    return false;
  }
  return true;
}

const getHour = function () {
  var myDate = new Date();
  var theHour = myDate.getHours()
  return theHour
}

const getDays = function (date1, date2) {
  var date1Str = date1.split("-");//将日期字符串分隔为数组,数组元素分别为年.月.日  
  //根据年 . 月 . 日的值创建Date对象  
  var date1Obj = new Date(date1Str[0], (date1Str[1] - 1), date1Str[2]);
  var date2Str = date2.split("-");
  var date2Obj = new Date(date2Str[0], (date2Str[1] - 1), date2Str[2]);
  var t1 = date1Obj.getTime();
  var t2 = date2Obj.getTime();
  var dateTime = 1000 * 60 * 60 * 24; //每一天的毫秒数  
  var minusDays = Math.floor(((t2 - t1) / dateTime));//计算出两个日期的天数差  
  var days = Math.abs(minusDays);//取绝对值  
  return days;
}  

const getWeek = function (theDate) {
  var weekDay = ["周天", "周一", "周二", "周三", "周四", "周五", "周六"];
  var weekName = weekDay[new Date(Date.parse(theDate)).getDay()]
  return weekName
}

const getDate = function (theDate) {
  var getDate = new Date(Date.parse(theDate)).getDate() 
  return util.formatNumber(getDate)
}

const getMonth = function (theDate) {
  var getMonth = new Date(Date.parse(theDate)).getMonth() + 1
  return util.formatNumber(getMonth)
}

const getFullYear = function (theDate) {
  var getFullYear = new Date(Date.parse(theDate)).getFullYear()
  return getFullYear
}

const diffDate = function (startDate, endDate) {
  console.log('调用了 diffDate 函数' + startDate + endDate)
  var startNewDate = new Date(startDate)
  var endNewDate = new Date(endDate)
  var startTime = startNewDate.getTime()
  var endTime = endNewDate.getTime()
  var diff = endTime - startTime
  var days = diff / (24 * 60 * 60)
  console.log(parseInt(days))
  return parseInt(days)
}

module.exports = {
  dateAdd: dateAdd,
  theDate: theDate,
  compareDate: compareDate,
  getHour: getHour,
  getWeek: getWeek,
  getDate: getDate,
  getMonth: getMonth,
  getFullYear: getFullYear,
  getDays:getDays,
  diffDate: diffDate
}

微信小程序 利用 swiper 制作 tab 选项卡

WXML

<view class="swiper-tab">
    <view class="swiper-tab-item {{currentTab==0?'active':''}}" data-current="0" bindtap="clickTab">一</view>
    <view class="swiper-tab-item {{currentTab==1?'active':''}}" data-current="1" bindtap="clickTab">二</view>
    <view class="swiper-tab-item {{currentTab==2?'active':''}}" data-current="2" bindtap="clickTab">三</view>
</view>

<swiper current="{{currentTab}}" duration="300"  bindchange="swiperTab">
    <swiper-item><view>这是第一屏内容</view></swiper-item>
    <swiper-item><view>这是第二屏内容</view></swiper-item>
    <swiper-item><view>这是第三屏内容</view></swiper-item>
</swiper>

JS

var app = getApp()
Page({
  data: {
    currentTab: 0
  },
  onLoad: function (options) {
    // 页面初始化 options为页面跳转所带来的参数
  },
  //滑动切换
  swiperTab: function (e) {
    var that = this;
    that.setData({
      currentTba: e.detail.current
    });
  },
  //点击切换
  clickTab: function (e) {
    var that = this;
    if (this.data.currentTab === e.target.dataset.current) {
      return false;
    } else {
      that.setData({
        currentTab: e.target.dataset.current
      })
    }
  }
})

WXSS

.swiper-tab{
    width: 100%;
    border-bottom: 2rpx solid #ccc;
    text-align: center;
    height: 88rpx;
    line-height: 88rpx;
    font-weight: bold;
}
.swiper-tab-item{
    display: inline-block;
    width: 33.33%;
    color:red;
}
.active{
    color:aqua;
    border-bottom: 4rpx solid red;
}