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

无限级通讯录

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
}

MacOS 自带 PHP 执行 php-fpm 的问题及解决

MacOS 下执行 php-fpm 会报错

ERROR: failed to open configuration file '/private/etc/php-fpm.conf': No such file or directory (2)
ERROR: failed to load configuration file '/private/etc/php-fpm.conf'
ERROR: FPM initialization failed

不能打开配置文件,因为没有 php-fpm.conf 文件,复制 php-fpm.conf.default 文件,改名为 php-fpm.conf,然后再根据需要改动配置。

cp /private/etc/php-fpm.conf.default /private/etc/php-fpm.conf

在此执行 php-fpm

WARNING: Nothing matches the include pattern '/private/etc/php-fpm.d/*.conf' from /private/etc/php-fpm.conf at line 125.
ERROR: failed to open error_log (/usr/var/log/php-fpm.log): No such file or directory (2)
ERROR: failed to post process the configuration
ERROR: FPM initialization failed

WARNING 找不到配置文件夹

cd /private/etc/php-fpm.d 
sudo cp www.conf.default www.conf

ERROR 不能打开错误日志文件。因为没有这个目录,配置到 /usr/local/var/log 目录。

vim /private/etc/php-fpm.conf,将 error_log 配置为 /usr/local/var/log/php-fpm.log

再次执行 php-fpm 依然报错

NOTICE: [pool www] 'user' directive is ignored when FPM is not running as root
NOTICE: [pool www] 'group' directive is ignored when FPM is not running as root

sudo php-fpm,再次报错:

ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (48)
ERROR: FPM initialization failed

编辑 www.conf,修改 listen 为 127.0.0.1:9999。

sudo vim /private/etc/php-fpm.d/www.conf

最后再次开启

开启php-fpm: sudo php-fpm -D

nginx 使用 php-fpm

nginx 配置文件

vim /usr/local/etc/nginx/nginx.conf
server {
    listen       8080;
    server_name  localhost;

    location / {
        root   /Users/guofeng/wwwroot/thinkphp/public;
        index  index.html index.htm index.php;
    }
    location ~ \.php$ {
        root /Users/guofeng/wwwroot/thinkphp/public;
        fastcgi_pass   127.0.0.1:9999;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

Swoole 适配 ThinkPHP 5 ab 压力测试 与 原生 ThinkPHP 对比

swoole 适配 thinkphp 5.0.21

Server Software:        swoole-http-server
Server Hostname:        127.0.0.1
Server Port:            9501

Document Path:          /
Document Length:        39 bytes

Concurrency Level:      10
Time taken for tests:   2.378 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1870000 bytes
HTML transferred:       390000 bytes
Requests per second:    4204.38 [#/sec] (mean)
Time per request:       2.378 [ms] (mean)
Time per request:       0.238 [ms] (mean, across all concurrent requests)
Transfer rate:          767.79 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:     1    2   1.2      2      21
Waiting:        1    2   1.1      2      21
Total:          1    2   1.1      2      21

与原生 ThinkPHP 对比

Server Software:        nginx/1.15.3
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /index.php
Document Length:        39 bytes

Concurrency Level:      10
Time taken for tests:   48.468 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      2020000 bytes
HTML transferred:       390000 bytes
Requests per second:    206.32 [#/sec] (mean)
Time per request:       48.468 [ms] (mean)
Time per request:       4.847 [ms] (mean, across all concurrent requests)
Transfer rate:          40.70 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2  75.8      0    3611
Processing:     9   45  73.4     42    3651
Waiting:        9   45  73.4     42    3651
Total:          9   47 105.2     42    3651

原生 PHP 输出 hello world

Server Software:        nginx/1.15.3
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /hello_world.php
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   0.870 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      870000 bytes
HTML transferred:       55000 bytes
Requests per second:    5749.02 [#/sec] (mean)
Time per request:       1.739 [ms] (mean)
Time per request:       0.174 [ms] (mean, across all concurrent requests)
Transfer rate:          976.88 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:     1    2   0.5      1       5
Waiting:        1    1   0.4      1       4
Total:          1    2   0.5      2       5

本机配置

Swoole 适配 ThinkPHP 5.0.21

新增代码

server/http_server.php

<?php
/**
 * Created by PhpStorm.
 * User: guofeng
 * Date: 2018/9/8
 * Time: 上午11:21
 */

$http = new swoole_http_server('0.0.0.0',9501);
$http->set([
    'enable_static_handler'=>true,
    'document_root'=>'../public/static',
    'worker_num'=>4,
]);
$http->on('WorkerStart',function(swoole_server $server,$worker_id){
    // 定义应用目录
    define('APP_PATH', __DIR__ . '/../application/');
    // ThinkPHP 引导文件
    // 1. 加载基础文件
    require __DIR__ . '/../thinkphp/base.php';
});
$http->on('request',function ($request,$response) use($http){

    $_SERVER = [];
    if(isset($request->server)){
        foreach($request->server as $k => $v){
            $_SERVER[strtoupper($k)] = $v;
        }
    }

    if(isset($request->header)){
        foreach($request->header as $k => $v){
            $_SERVER[strtoupper($k)] = $v;
        }
    }

    $_GET = [];
    if(isset($request->get)){
        foreach($request->get as $k => $v){
            $_GET[$k] = $v;
        }
    }

    $_POST = [];
    if(isset($request->post)){
        foreach($request->post as $k => $v){
            $_POST[$k] = $v;
        }
    }

    // 2. 执行应用
    ob_start();

    try{
        think\App::run()->send();
    }catch (\Exception $e){
        //todo
    }

    $res = ob_get_contents();
    ob_end_clean();
    $response->end($res);
});
$http->start();

ThinkPHP 核心文件修改

thinkphp/library/think/Request.php

/**
 * 获取当前请求URL的pathinfo信息(含URL后缀)
 * @access public
 * @return string
 */
public function pathinfo()
{
    if(isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='/'){
        return ltrim($_SERVER['PATH_INFO'], '/');
    }
    //if (is_null($this->pathinfo)) {
        if (isset($_GET[Config::get('var_pathinfo')])) {
            // 判断URL里面是否有兼容模式参数
            $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
            unset($_GET[Config::get('var_pathinfo')]);
        } elseif (IS_CLI) {
            // CLI模式下 index.php module/controller/action/params/...
            $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
        }

        // 分析PATHINFO信息
        if (!isset($_SERVER['PATH_INFO'])) {
            foreach (Config::get('pathinfo_fetch') as $type) {
                if (!empty($_SERVER[$type])) {
                    $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
                    substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
                    break;
                }
            }
        }
        $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
    //}
    return $this->pathinfo;
}
/**
 * 获取当前请求URL的pathinfo信息(不含URL后缀)
 * @access public
 * @return string
 */
public function path()
{
    //if (is_null($this->path)) {
        $suffix   = Config::get('url_html_suffix');
        $pathinfo = $this->pathinfo();
        if (false === $suffix) {
            // 禁止伪静态访问
            $this->path = $pathinfo;
        } elseif ($suffix) {
            // 去除正常的URL后缀
            $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
        } else {
            // 允许任何后缀访问
            $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
        }
    //}
    return $this->path;
}

访问方式

命令行 启动

php http_server.php

访问地址

http://localhost:9501/?s=index/index/index
http://localhost:9501/index/index/index

JS 两个数组合并

//concat()把两个或者多个数组链接在一起,但是不改变已经存在的数组
//而是返回一个链接之后的新数组
var a = [1,2,3];
a.concat([4,5]);
console.log(a);
//此处输出为 [1, 2, 3]

var a = [1,2,3];
a = a.concat([4,5]);
console.log(a);
//此处输出为 [1, 2, 3 ,4 ,5]

Web 生成图片 html2canvas 示例

http://blog.liuguofeng.com/wp-content/uploads/2018/09/html2canvas.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Using an existing canvas to draw on</title>
    <style>	
        button {
            clear: both;
            display: block;
        }
    </style>
</head>
<body>
<div><h1>这是HTML</h1>
	
    <div id="content" style="width:200px;height:200px;background: rgba(100, 255, 255, 0.5);">
	<img src='timg.jpg' style="width:100px;height:60px;">
	<span>这是文字</span>
	</div>
</div>
<h1>生成的图片</h1>
<canvas width="200" height="200"></canvas>
<script type="text/javascript" src="html2canvas.js"></script>
<button>点击生成图片</button>
<script type="text/javascript">
    var canvas = document.querySelector("canvas");
    document.querySelector("button").addEventListener("click", function() {
        html2canvas(document.querySelector("#content"), {canvas: canvas}).then(function(canvas) {
            console.log('Drew on the existing canvas');
        });
    }, false);
</script>
</body>
</html>

微信小程序 日历

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
}