小程序TodoList实践

7,365

TodoList实践

看完官方的文档介绍后,就想找个简单的例子来验证实现一下,TodoList MVC就很好了,简单容易。

之前都用JQ、Backbone、vue简单撸过,大概功能如下:

  • 添加todo
  • 储存在应用缓存
  • 列表展示
  • 区分状态显示:全部、未完成、已完成
  • 改变todo状态
  • 删除todo

根据以上功能,小程序完成如下:

GitHub: github.com/CH563/TodoL…

下面记录一下我的完成过程:

下载小程序开发工具:开发者工具下载

安装完成后,使用微信扫一扫登录,选好文件夹后创建即可,开发工具会自动生成以下目录:

pages/

app.js

app.json

app.wxss

文录绍构和具体配置查看官方文档:mp.weixin.qq.com/debug/wxado…

基本配置

由于平时开发习惯用Less,如果在直接使用小程序的wxss来编写的话,就恢复原生编写方式,大大的不便,所以直接使用了gulp来实时编译Less,和修改文件名为wxss。小程序开发工具是不支持Less,直接用vscode来开发,小程序开发工具是用实时预览和调试即可,vscode也有丰富的插件支待小程序语法提示。

// gulpfile.js
var gulp = require('gulp')
var less = require('gulp-less')
var plumber = require('gulp-plumber')
var rename = require('gulp-rename')

gulp.task('less', function () {
  return gulp.src('./app.less')
    .pipe(plumber())  // 错误处理
    .pipe(less()) // 编译less
    .pipe(rename((path) => path.extname = '.wxss')) // 编译后生成文件修改后缀为.wxss
    .pipe(gulp.dest('./'));
});
gulp.watch('./app.less', ['less']); // 实时监控app.less文件变化,运行任务

UI组件也直接引用了小程序支持的weui-wxss

@import "./weui.wxss";

在app.json定义好小程序页面路由和配色:

{
  "pages":[
    "pages/index/index"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#ca2100",
    "navigationBarTitleText": "TodoList",
    "navigationBarTextStyle":"white"
  }
}

页面开发

页面文件都存放在pages/目录下,每个功能页面都会创建一个文件夹,TodoList现只需一个页面完成即可

数据绑定使用 Mustache 语法(双大括号)将变量包起来

<text class="userinfo-nickname">{{userInfo.nickName}}</text>
<!-- 三元运算 -->
<text class="{{status === '1'?'active':''}}" data-status="1" bindtap="showStatus">全部</text>

添加todo

使用字段addShow来判断添加输入层显示隐藏即可

input输出框这里不是双向绑定,所以这里添加一个事件bindinput="setInput"来赋值实时变化

<view class="addForm {{addShow?'':'hide'}}">
    <view class="addForm-div">
      <input class="weui-input" placeholder="请输入todo" value="{{addText}}" bindinput="setInput" focus="{{focus}}" />
      <view class="addForm-btn">
        <button class="weui-btn mini-btn" type="warn" bindtap="addTodo" size="mini">确定添加</button>
        <button class="weui-btn mini-btn" type="default" bindtap="addTodoHide" size="mini">取消</button>
      </view>
    </view>
  </view>

实时赋值事件处理

setInput: function (e) {
    this.setData({
      addText: e.detail.value
    })
}

取消时,需要清空input的值,input里需要绑定value="{{addText}}"

Page({
 data:{
     //...
 }, 
 //...
 addTodoHide: function () {
    this.setData({
      addShow: false, // 控制添加输入面板隐藏
      focus: false, // 失去焦点
      addText: '' // 清空值
    })
 }
 //...
})

添加todo

Page({
 data:{
     //...
 }, 
 //...
 addTodo: function () {
    // 检查有没有输入
    if (!this.data.addText.trim()) {
      return
    }
    var temp = this.data.lists // 取出lists
    var addT = {
      id: new Date().getTime(), // 取当前时间
      title: this.data.addText,
      status: '0'
    }
    temp.push(addT) // 添加新的todo
    this.showCur(temp) // 处理当前状态的方法
    this.addTodoHide() // 添加成功后,隐藏添加面板方法
    wx.setStorage({ // 小程序异步缓存
      key:"lists",
      data: temp
    })
    wx.showToast({ // weui toast组件
      title: '添加成功!',
      icon: 'success',
      duration: 1000
    });
 }
 //...
})

列表部分

scroll-view内滚动

列表渲染,事件触发,利用data传参,bind绑定事件

<scroll-view class="lists" scroll-y>
    <!-- 判断列表是否为空 -->
    <block wx:if="{{curLists.length < 1}}">
      <view class="nodata">暂无数据</view>
    </block>
    <!-- 列表渲染 -->
    <view class="item" wx:for="{{curLists}}" wx:key="index">
      <!-- 内容view,绑定touch三个件事,来实现滑动册除 -->
      <view class="content" style="{{item.txtStyle}}" data-index="{{index}}" bindtouchstart="touchS" bindtouchmove="touchM" bindtouchend="touchE">
        <!-- checkbox图标,changeTodo事件来控制状态切换 -->
        <icon class="icon-small" type="{{item.status === '0'?'circle':'success'}}" size="23" data-item="{{item.id}}" bindtap="changeTodo"></icon>
        <text class="title {{item.status === '1'?'over':''}}">{{item.title}}</text>
        <!-- api.formatTime是使用了wxs模块化编写的模块 -->
        <text class="time">{{api.formatTime(item.id)}}</text>
      </view>
      <!-- 删除按钮,绑定删除事件 -->
      <view class="del" data-item="{{item.id}}" bindtap="delTodo"><text>删除</text></view>
    </view>
  </scroll-view>

滑动删除

效果:当向左滑动时,content跟随手指像左移动,同时右侧出现del按钮;当滑动距离大于按钮宽度一半松开手指时自动滑动到左侧显示出按钮,小于一半时自动回到原来的位置,隐藏按钮。

实现思路: content和del按钮分别是绝对定位,利用z-index层来控制让content来盖住del,当content向左滑动时,del按钮就会露出来。

微信小程序api提供的touch对象和3个有关手指触摸的函数(touchstart,touchmove,touchend)来实现content随手指移动

详细api说明,请查看:mp.weixin.qq.com/debug/wxado…

列表的content已绑定这个三个事件:bindtouchstart="touchS" bindtouchmove="touchM" bindtouchend="touchE"

实现方法:

注意txtStyle,在content在绑定这个属性的,实现跟随手指移动的style="{{item.txtStyle}}"

delBtnWidth为了del按钮的宽度,这里以rpx为单位

Page({
 data:{
     //...
 }, 
 //...
  touchS: function (e) {
    // console.log('开始:' + JSON.stringify(e))
    // 是否只有一个触摸点
    if(e.touches.length === 1){
      this.setData({
        // 触摸起始的X坐标
        startX: e.touches[0].clientX
      })
    }
  },
  touchM: function (e) {
    // console.log('移动:' + JSON.stringify(e))
    var _this = this
    if(e.touches.length === 1){
     // 触摸点的X坐标
      var moveX = e.touches[0].clientX
      // 计算手指起始点的X坐标与当前触摸点的X坐标的差值
      var disX = _this.data.startX - moveX
     // delBtnWidth 为右侧按钮区域的宽度
      var delBtnWidth = _this.data.delBtnWidth
      var txtStyle = ''
      if (disX == 0 || disX < 0){ // 如果移动距离小于等于0,文本层位置不变
        txtStyle = 'left:0'
      } else if (disX > 0 ){ // 移动距离大于0,文本层left值等于手指移动距离
        txtStyle = 'left:-' + disX + 'rpx'
        if(disX >= delBtnWidth){
          // 控制手指移动距离最大值为删除按钮的宽度
          txtStyle = 'left:-' + delBtnWidth + 'rpx'
        }
      }
      // 获取手指触摸的是哪一个item
      var index = e.currentTarget.dataset.index;
      var list = _this.data.curLists
      // 将拼接好的样式设置到当前item中
      list[index].txtStyle = txtStyle
      // 更新列表的状态
      this.setData({
        curLists: list
      });
    }
  },
  touchE: function (e) {
    // console.log('停止:' + JSON.stringify(e))
    var _this = this
    if(e.changedTouches.length === 1){
      // 手指移动结束后触摸点位置的X坐标
      var endX = e.changedTouches[0].clientX
      // 触摸开始与结束,手指移动的距离
      var disX = _this.data.startX - endX
      var delBtnWidth = _this.data.delBtnWidth
      // 如果距离小于删除按钮的1/2,不显示删除按钮
      var txtStyle = disX > delBtnWidth/2 ? 'left:-' + delBtnWidth + 'rpx' : 'left:0'
      // 获取手指触摸的是哪一项
      var index = e.currentTarget.dataset.index
      var list = _this.data.curLists
      list[index].txtStyle = txtStyle
      // 更新列表的状态
      _this.setData({
        curLists: list
      });
    }
  }
  //...
})

WXS实现时间格式

实现在效果如下:

这里我使用小程序的WXS

WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。了解详细

新建一个api.wxs文件,然后在index.wxml引用,定义模块名即可引用:

<!-- index.wxml -->
<wxs src="./api.wxs" module="api" />
...
<text class="time">{{api.formatTime(item.id)}}</text>

每个 wxs 模块均有一个内置的 module 对象

api.wxs文件和时间格式实现方法:

var formatTime = function(time){
  // 获取当前时间
  var getUnix = function () {
    var date = getDate()
    return date.getTime()
  }
  // 获取今天零点时间
  var getTodayUnix = function () {
    var date = getDate()
    date.setHours(0)
    date.setMinutes(0)
    date.setSeconds(0)
    date.setMilliseconds(0)
    return date.getTime()
  }
  // 获取今年的1月1日零点时间
  var getYearUnix = function () {
    var date = getDate()
    date.setMonth(0)
    date.setDate(1)
    date.setHours(0)
    date.setMinutes(0)
    date.setSeconds(0)
    date.setMilliseconds(0)
    return date.getTime()
  }
  // 获取标准时间
  var getLastDate = function (time) {
    var date = getDate(time)
    var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
    var day = date.getDay() < 10 ? '0' + (date.getDay()) : date.getDay()
    return date.getFullYear() + '-' + month + '-' + day
  }
  // 转换时间
  var getFormatTime = function (timestamp) {
    var now = getUnix()
    var today = getTodayUnix()
    var year = getYearUnix()
    var timer = (now - timestamp) / 1000
    var tip = ''
    if (timer <= 0) {
      tip = '刚刚'
    } else if (Math.floor(timer / 60) <= 0) {
      tip = '刚刚'
    } else if (timer < 3600) {
      tip = Math.floor(timer / 60) + '分钟前'
    } else if (timer >= 3600 && (timestamp - today >= 0)) {
      tip = Math.floor(timer / 3600) + '小时前'
    } else if (timer / 86400 <= 31) {
      tip = Math.ceil(timer / 86400) + '天前'
    } else {
      tip = getLastDate(timestamp)
    }
    return tip
  }
  return getFormatTime(+time)
}

// es6方法一样,导出formatTime方法
module.exports.formatTime = formatTime;

这里需要注意的是获取当前时间,WXS是不支持new Date(),它有自己本身的方法getDate()。使用方法跟new Date()一样。

至此已完成,TodoList实践例子,功能简单,入门好例子。

希望对大家有帮助,不足的地方请大家指点,刚啃完官方文档撸的例子。

原码下载:github.com/CH563/TodoL…

愿大家都升职加薪!