【微信小程序项目实战】TodoList-项目主体搭设(2)

JS 部分

为便于分析各个组件的相互作用与原理,故先从 JS 入手,而后再完善 HTML 部分

以下所有代码(除了 import 导包语句)都写在 todo.jsPage({}) 对象内


数据 data

作为一款小而精的 todolist 小程序,我们仅需下方五个属性即可解决所有代码逻辑

  1. inputValue 双向绑定输入框内容
  2. todoList 当前的 todo 表
  3. currentUndo 当前未完成的待办事项数
  4. allComplete 事项是否全部完成
  5. isFocus 用于搜索框自动获取焦点
data: {
    inputValue: '',
    todoList: [],
    currentUndo: 0,
    allComplete: false,
    isFocus:false
  },

onShow

由于我们没有在 app.json 中设置总标题,故在每个页面打开后,最好都在 onShow 钩子里面显式指定当前页面标题

// 进入此页面时,自动设置好标题
  onShow: function () {
    wx.setNavigationBarTitle({
      title: 'Todo待办事项'
    })
  },

输入框双向绑定

由于微信小程序 功能太少太烂 ,特别是仅仅提供了单向绑定,而不像 vue 还有 v-model 执行双向绑定;

所有这里需要开发者自行实现双向绑定功能,具体流程即:监听输入框内容,内容发生改变触发回调函数修改对应的值,而通过单向绑定,实现值的动态变化

// 输入框双向绑定处理
  inputChange(e) {
    this.setData({
      // e.detail.value可视为一种固定写法,可以从input类型的组件获取其值
      inputValue: e.detail.value
    })
  },

保存与读取

代码不难理解,即我们需要在每次对 todo 进行增删后,都自带保存数据到本地,这样子下次开启小程序就可直接读取数据了

onLoad 回调保证页面加载时自动从内存读取 todolist 数据

// 保存todos到本地内存
  saveTodos() {
    wx.setStorageSync('todo_list', this.data.todoList)
  },
  // 加载todos列表
  loadTodos() {
    let todos = wx.getStorageSync('todo_list')
    if (todos) {
      let undo = todos.filter((item) => {
        return !item.complete
      }).length
      this.setData({
        todoList: todos,
        currentUndo: undo
      })
    }
    console.log(todos);
  },

  // 页面加载钩子调用loadTodos
  onLoad() {
    this.loadTodos()
  },

添加新的待办事项

添加新待办事项的代码逻辑

// 将新的待办事项添加到栏内
  addTodo() {
    // 如果编辑框文本为空或者仅有空格的话,不予添加
    if (!this.data.inputValue || !this.data.inputValue.trim()) return

    // 每次都需要单独取出todolist然后push新的内容进去,最后setdata,你可以将其当做固定套路
    let todos = this.data.todoList
    todos.push({
      title: this.data.inputValue,
      complete: false
    })
    this.setData({
      inputValue: "",
      todoList: todos,
      currentUndo: this.data.currentUndo + 1,   // 添加新待办,未完成数+1
      isFocus:true  // 添加完一个待办后,自动令编辑框获得焦点,省去客户重复点击过程
    })

    // 每次修改完毕都必须保存一下!
    this.saveTodos()
  },

完成待办事项

请注意此处 complete 状态的设置:点击一次完成事项,再点击一次取消完成变为待办,此时就需要对 complete 做出判断,以动态增减 currentUndo 的数值量

// 点击完成单个事项
  toggleTodo(e) {
    let index = e.currentTarget.dataset.index
    let todos = this.data.todoList
    todos[index].complete = !todos[index].complete
    this.setData({
      todoList: todos,
      currentUndo: this.data.currentUndo + (todos[index].complete ? -1 : 1),
    })
    this.saveTodos()
  },
  // 选中全部的待办事项
  toggleAllTodos() {
    this.data.allComplete = !this.data.allComplete
    let todos = this.data.todoList
    todos.forEach(i => {
      i.complete = this.data.allComplete
    })
    this.setData({
      todoList: todos,
      currentUndo: this.data.allComplete ? 0 : todos.length
    })
    this.saveTodos()
  },

删除待办事项

删除单个待办事项的方法为通过索引找到该 todo,并使用 splice 删去,之后更新数据即可

删除多个待办事项时需要配合 foreach 方法

// 删除单个待办事项
  removeTodo(e) {
    let index = e.currentTarget.dataset.index
    let todos = this.data.todoList
    let remove = todos.splice(index, 1)[0]
    this.setData({
      todoList: todos,
      currentUndo: this.data.currentUndo - (remove.complete ? 0 : 1)
    })
    this.saveTodos()
  },
  // 删除选中项
  removeTodos(e) {
    let todos = this.data.todoList
    let remain = []
    todos.forEach(i => {
      if (!i.complete) remain.push(i)
    })
    this.setData({
      todoList: remain
    })
    this.saveTodos()

    // wx自带的手机振动接口,vibrateShort表示振动15ms
    wx.vibrateShort()
  },

WXML

顶部输入框

这里涉及到的 t-input 配置项可以自行前往官网查询 API,这里不做过多解释
bind:change、bind:blur、bind:enter 分别表示 内容改变、是否获取焦点、按下回车键 后的回调函数

对于 t-button,如果他在 t-input 内部,则需使用 slot="suffix" 指定插槽来安放该 button

<!-- 顶部添加栏 -->
<view class="todo-input">
  <t-input
    value="{{inputValue}}"
    style="border-radius: 12rpx;"
    clearable
    placeholder="请输入事项名称"
    confirm-type="done"
    bind:change="inputChange"
    bind:blur="isFocus=!isFocus"
    bind:enter="addTodo"
    focus="{{isFocus}}"
  >
    <t-button
      wx:if="{{inputValue}}"
      slot="suffix"
      theme="light"
      size="small"
      bindtap="addTodo"
    >
      添加
    </t-button>
  </t-input>
</view>

主体

block 配以 wx:if,实现状态页显示:当待办事项列表为空时,动态判断并显示 404 页面

<!-- 当todo列表存在数据时,渲染该页面 -->
<block wx:if="{{todoList.length}}">
  <view class="todo-control">
    <image bindtap="toggleAllTodos" src="../../image/pages/all.png"></image>
    <text wx:if="{{currentUndo}}">待完成任务 {{currentUndo}}</text>
    <image
      bindtap="removeTodos"
      src="../../image/pages/delete.png"
      wx:if="{{todoList.length>currentUndo}}"
    ></image>
  </view>

  <view class="todo-itembox">
    <view
      class="todo-items {{item.complete?'comp':''}}"
      wx:for="{{todoList}}"
      wx:key="index"
      data-index="{{index}}"
      bindtap="toggleTodo"
    >
      <icon
        class="checkbox"
        type="{{ item.complete ? 'success' : 'circle' }}"
      ></icon>
      <text class="title">{{ item.title }}</text>
      <icon
        class="remove"
        type="clear"
        size="16"
        catchtap="removeTodo"
        data-index=" {{index}} "
      />
    </view>
  </view>
</block>

<!-- 当todo列表为空时,渲染该页面 -->
<block wx:else>
  <view class="todo-empty">
    <image src="../../image/state/no-data.png"></image>
    <view style="color: gray;">当前还没有待办事项哦~</view>
  </view>
</block>

回到顶部按钮
<!-- 回到顶部悬浮按钮 -->
<!-- 判断当且仅当列表项多于6个时,才会显示该悬浮按钮 -->
<t-fab wx:if="{{todoList.length>6}}" icon="arrow-up" bind:click="fabBack2Top" />

CSS 部分由于不方便表述,故留到文末以源码的形式展现给大家


完整代码

JS
import Message from "tdesign-miniprogram/message/index";

// pages/todo/todo.js
Page({
  data: {
    inputValue: "",
    todoList: [],
    currentUndo: 0,
    allComplete: false,
    isFocus: false,
  },

  // 进入此页面时,自动设置好标题
  onShow: function () {
    wx.setNavigationBarTitle({
      title: "Todo待办事项",
    });
  },

  // 输入框双向绑定处理
  inputChange(e) {
    this.setData({
      inputValue: e.detail.value,
    });
  },

  // 保存todos到本地内存
  saveTodos() {
    wx.setStorageSync("todo_list", this.data.todoList);
  },
  // 加载todos列表
  loadTodos() {
    let todos = wx.getStorageSync("todo_list");
    if (todos) {
      let undo = todos.filter((item) => {
        return !item.complete;
      }).length;
      this.setData({
        todoList: todos,
        currentUndo: undo,
      });
    }
    console.log(todos);
  },

  // 页面加载钩子调用loadTodos
  onLoad() {
    this.loadTodos();
  },

  // 将新的待办事项添加到栏内
  addTodo() {
    if (!this.data.inputValue || !this.data.inputValue.trim()) return;
    let todos = this.data.todoList;
    todos.push({
      title: this.data.inputValue,
      complete: false,
    });
    this.setData({
      inputValue: "",
      todoList: todos,
      currentUndo: this.data.currentUndo + 1,
      isFocus: true,
    });
    this.saveTodos();
  },

  // 悬浮按钮回到顶部
  fabBack2Top() {
    wx.pageScrollTo({
      duration: 500,
      scrollTop: 0,
    });
  },

  // 点击完成单个事项
  toggleTodo(e) {
    let index = e.currentTarget.dataset.index;
    let todos = this.data.todoList;
    todos[index].complete = !todos[index].complete;
    this.setData({
      todoList: todos,
      currentUndo: this.data.currentUndo + (todos[index].complete ? -1 : 1),
    });
    this.saveTodos();
  },
  // 选中全部的待办事项
  toggleAllTodos() {
    this.data.allComplete = !this.data.allComplete;
    let todos = this.data.todoList;
    todos.forEach((i) => {
      i.complete = this.data.allComplete;
    });
    this.setData({
      todoList: todos,
      currentUndo: this.data.allComplete ? 0 : todos.length,
    });
    this.saveTodos();
  },

  // 删除单个待办事项
  removeTodo(e) {
    let index = e.currentTarget.dataset.index;
    let todos = this.data.todoList;
    let remove = todos.splice(index, 1)[0];
    this.setData({
      todoList: todos,
      currentUndo: this.data.currentUndo - (remove.complete ? 0 : 1),
    });
    this.saveTodos();
  },
  // 删除选中项
  removeTodos(e) {
    let todos = this.data.todoList;
    let remain = [];
    todos.forEach((i) => {
      if (!i.complete) remain.push(i);
    });
    this.setData({
      todoList: remain,
    });
    if (this.data.currentUndo === 0) {
      Message.success({
        context: this,
        content: "完成所有任务,休息一下吧!",
        duration: 2000,
        offset: [20, 32],
        closeBtn: true,
      });
    }
    this.saveTodos();
    wx.vibrateShort();
  },
});

WXML
<view class="todo-container">
  <!-- 顶部添加栏 -->
  <view class="todo-input">
    <t-input
      value="{{inputValue}}"
      style="border-radius: 12rpx;"
      clearable
      placeholder="请输入事项名称"
      confirm-type="done"
      bind:change="inputChange"
      bind:blur="isFocus=!isFocus"
      bind:enter="addTodo"
      focus="{{isFocus}}"
    >
      <t-button
        wx:if="{{inputValue}}"
        slot="suffix"
        theme="light"
        size="small"
        bindtap="addTodo"
      >
        添加
      </t-button>
    </t-input>
  </view>

  <!-- 当todo列表存在数据时,渲染该页面 -->
  <block wx:if="{{todoList.length}}">
    <view class="todo-control">
      <image bindtap="toggleAllTodos" src="../../image/pages/all.png"></image>
      <text wx:if="{{currentUndo}}">待完成任务 {{currentUndo}}</text>
      <image
        bindtap="removeTodos"
        src="../../image/pages/delete.png"
        wx:if="{{todoList.length>currentUndo}}"
      ></image>
    </view>

    <view class="todo-itembox">
      <view
        class="todo-items {{item.complete?'comp':''}}"
        wx:for="{{todoList}}"
        wx:key="index"
        data-index="{{index}}"
        bindtap="toggleTodo"
      >
        <icon
          class="checkbox"
          type="{{ item.complete ? 'success' : 'circle' }}"
        ></icon>
        <text class="title">{{ item.title }}</text>
        <icon
          class="remove"
          type="clear"
          size="16"
          catchtap="removeTodo"
          data-index=" {{index}} "
        />
      </view>
    </view>
  </block>
  <!-- 当todo列表为空时,渲染该页面 -->
  <block wx:else>
    <view class="todo-empty">
      <image src="../../image/state/no-data.png"></image>
      <view style="color: gray;">当前还没有待办事项哦~</view>
    </view>
  </block>

  <!-- 回到顶部悬浮按钮 -->
  <!-- 判断当且仅当列表项多于6个时,才会显示该悬浮按钮 -->
  <t-fab
    wx:if="{{todoList.length>6}}"
    icon="arrow-up"
    bind:click="fabBack2Top"
  />
</view>

WXSS
.todo-container {
  margin: 0;
  padding: 0;
  width: 100vw;
  min-height: 100vh;
  background-color: #ededed;

  display: flex;
  flex-direction: column;

  position: relative;
}

.todo-input {
  margin: 12rpx 32rpx;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.todo-empty {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
}
.todo-empty image {
  width: 450rpx;
  height: 450rpx;
}

/* 悬浮按钮 */
.fab {
  position: absolute;
  right: 0;
  bottom: 0;
}

/* 多选操纵栏 */
.todo-control {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  height: 80rpx;
  margin: 12rpx 32rpx;
}
.todo-control image {
  height: 70rpx;
  width: 70rpx;
}

/* todo项目展示 */
.todo-items {
  border-radius: 8rpx;
  height: 120rpx;
  background-color: white;
  margin: 10rpx 32rpx;

  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}
.comp {
  background-color: lightgray;
}
.todo-items .checkbox {
  margin-left: 24rpx;
}
.todo-items .title {
  min-width: 70%;
  max-width: 70%;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}
.todo-items .remove {
  margin-right: 24rpx;
}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhillery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值