vuex + elementui实现一个Todolist应用

前言

前两天复习了一下vuex,想着做一个todolist实践下知识点,于是结合饿了么的热门ui框架element写了这个demo。文章内容较长,如果对vuex和elementui比较熟悉,可以直接看源码,交互步骤并不复杂,算是一个入门级的vuex和element-ui项目,适合练手。

源码下载地址:vuex-todolist


vuex + elementui实现一个Todolist应用_第1张图片


技术栈

vue2 + vuex + axios + element-ui

实现步骤

step01:基础页面配置

使用vue-cli脚手架创建一个项目,选择手动配置:

vue create vuex-totolist

配置内容如下:
vuex + elementui实现一个Todolist应用_第2张图片
安装element-ui

npm i element-ui -S

在 main.js 中写入以下内容:

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'

Vue.config.productionTip = false
Vue.use(ElementUI)

new Vue({
     
  render: h => h(App)
}).$mount('#app')

在App.vue中写入如下内容

<template>
  <div id="app">
    <el-input placeholder="请输入待办事项" class="td-input" ></el-input>
    <el-button type="primary">添加事项</el-button>
    <el-main class="td-main" >
        <el-table
          ref="multipleTable"
          :data="list"
          style="width: 100%">
          <el-table-column
            type="selection"
            width="55">
          </el-table-column>
          <el-table-column
            label="待办事项"
            width="350">
            <template slot-scope="item">{
     {
      item.row.info }}</template></el-checkbox> -->
          </el-table-column>
          <el-table-column
            prop="name"
            label="删除"
            width="50">
            <el-button size="mini" class="btn-close" type="primary" icon="el-icon-close" circle></el-button>
          </el-table-column>
        </el-table>
        <div class="footer">
          <span>0条剩余</span>
          <el-radio-group size="small" v-model="radio1">
            <el-radio-button class="btn-radio" label="全部"></el-radio-button>
            <el-radio-button class="btn-radio" label="未完成"></el-radio-button>
            <el-radio-button class="btn-radio" label="已完成"></el-radio-button>
          </el-radio-group>
          <el-button>清除已完成</el-button>
        </div>
    </el-main>
  </div>
</template>

<script>

export default {
     
  name: 'app',
  data () {
     
    return {
     
      list: [],
      radio1: '全部'
    }
  }
}
</script>

<style>
#app {
     
  width: 610px;
  margin: 0 auto;
}
.td-input {
     
  width: 500px;
  margin-right: 10px;
}
.td-main {
     
  width: 500px;
  margin-top: 10px;
  border-radius: 4px;
  border: 1px solid #DCDFE6;
}
.btn-close {
     
  float: right;
}
.el-button--mini.is-circle {
     
  padding: 3px;
}
.footer {
     
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 15px;
}
</style>

创建一个list.json文件,放入到public目录中

[
  {
     
    "id": 0,
    "info": "学习MVC设计模式",
    "done": false
  },
  {
      "id": 1, "info": "学习Node.js", "done": false },
  {
     
    "id": 2,
    "info": "下班去健身房",
    "done": false
  },
  {
      "id": 3, "info": "晚上十一点淘宝秒杀", "done": false },
  {
      "id": 4, "info": "周末自制照烧鸡腿", "done": false }
]

vuex + elementui实现一个Todolist应用_第3张图片

step02:实现列表数据的动态加载

先在main.js中加载store

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
import store from './store'	//引入store

Vue.config.productionTip = false
Vue.use(ElementUI)

new Vue({
     
  store,	//挂载store
  render: h => h(App)
}).$mount('#app')

安装axios,通过axios请求列表数据

npm i axios -S

在store.js中引入axios

import axios from 'axios'

接着在actions中定义一个函数getList,接收一个参数context,然后在函数中直接发起get请求,请求的是当前项目里的list.json,请求成功后就可以指定一个回调函数,然后写一个解构赋值data。

getList (context) {
     
    axios.get('/list.json').then(({
      data }) => {
     
  })
}

因为该函数尚未被调用,接下来回到App.vue中定义一个生命周期函数created,在里面发起异步请求this.$store.dispatch(‘getList’),来触发actions。App.vue组件被渲染的时候,就会调用该函数。

created () {
     
  this.$store.dispatch('getList')
},

之后在store中,将actions内请求的数据挂载到state中。先定义一个数组list来存放所有的任务列表。

state: {
     
  list: []
}

如果要在actions中为list重新赋值,只能调用mutations为对应的state赋值。在mutations中定义一个方法initList,传入两个参数:第一个是固定state,第二个是自定义list(之后谁调用了该函数,就必须传入一个list)。拿到list后,进行赋值操作,就可以赋值到state上面改变list的值。

mutations: {
     
  initList (state, list) {
     
    state.list = list
  }
},

回到actions中,通过context调用commit触发mutation中的initList。因为data获取了数据,所以作为参数传递到mutations里面去。

actions: {
     
  //调用getList
  getList (context) {
     
  	//发起请求,拿到返回的数据
    axios.get('/list.json').then(({
      data }) => {
     
      //调用mutations,传入initList参数,成功给state中的list赋值
      context.commit('initList', data)
    })
  }
}

进入App.vue中,引入mapState。

import {
      mapState } from 'vuex'

添加计算属性,里面调用mapState,传入一个数组,将数据映射过来。全局的list数据就会变成App.vue里的计算属性了

computed: {
     
  ...mapState(['list'])
}

此时list.json上的数据成功加载到列表上。


step03:实现文本框内容的双向数据绑定

在store的state中定义一个数据节点inputValue。

state: {
     
  list: [],
  //定义数据节点
  inputValue: 'test'
}

在App.vue中,将全局的inputValue映射为当前的一个计算属性。然后绑定到输入框中。

computed: {
     
  //inputValue映射为计算属性
  ...mapState(['list', 'inputValue'])
}
<el-input 
	placeholder="请输入待办事项" 
	class="td-input" 
	:value="inputValue">
el-input>

此时在输入框中出现了文本,实现了单向数据绑定。在这里插入图片描述

接下来给文本框绑定一个input事件,用来监听文本框的变化

<el-input 
	placeholder="请输入待办事项" 
	class="td-input" 
	:value="inputValue"
	@input="handleInputChange">
el-input>

增加一个methods方法,在里面定义handleInputChange,传入一个参数val

methods: {
     
  handleInputChange (val) {
     
  }
}

在store的mutations中定义一个函数,传入state和val两个参数,为store中的inputValue赋值,将值同步保存到inputValue中

setInputValue (state, val) {
     
  state.inputValue = val
}

接着回到App.vue的methods方法中,使用this.$store.commit调用setInputValue,将文本框中的变化保存到state,实现双向绑定

handleInputChange (val) {
     
  this.$store.commit('setInputValue', val)
}

step04:实现点击添加事项

为按钮绑定一个单击事件

<el-button type="primary" @click="addItemToList">添加事项el-button>

向列表中新增 item 项,在方法内判断用户输入的值是否为空,如果不为空,则向store中添加列表项

addItemToList () {
     
  if (this.inputValue.trim().length <= 0) {
     
    return this.$message.warning('添加事项不能为空!')
  }
  this.$store.commit('addItem')
}
// 回到store,在state中添加
nextId: 5,

//新增一个mutations
addItem (state) {
     
  const obj = {
     
    id: state.nextId,
    info: state.inputValue.trim(),
    // 完成状态默认是false
    done: false
  }
  // 将对象追加到list中
  state.list.push(obj)
  // 添加成功后,id自动递增,保证不重复
  state.nextId++
  // 清空文本框
  state.inputValue = ''
}

step05:实现删除事项

在删除链接中绑定删除事件函数,在methods中添加对应方法

<el-button slot-scope="item" @click="removeItemById(item.row.id)" size="mini" class="btn-close" type="primary" icon="el-icon-close" circle></el-button>
// 很据Id删除对应的任务事项
removeItemById (id) {
     
  this.$store.commit('removeItem', id)
}

在store中新增一个mutations

// 根据Id删除对应的任务事项
removeItem(state, id) {
     
  // 根据Id查找对应项的索引
  const i = state.list.findIndex(x => x.id === id)
  // 根据索引,删除对应的元素
  if (i !== -1) {
     
    state.list.splice(i, 1)
  }
}

step06:实现复选框状态的绑定

element-ui提供了selection-change,当选择项发生变化时会触发该事件,需要配合ref使用

<el-table
  ref="multipleTable"
  @selection-change="handleSelectionChange">
</el-table>

在methods中定义

handleSelectionChange (val) {
     
  this.multipleSelection = val
}
checked () {
     
  this.$nextTick(() => {
     
    this.list.forEach(row => {
     
 		this.$refs.multipleTable.toggleRowSelection(row, row.done)
    })
  })
}

在watch当中触发checked方法,这一步是为了确保在数据变化后调用toggleRowSelection

watch: {
     
  list: function (val) {
     
    this.checked()
  }
}

step07:修改任务完成状态

点击复选框时拿到最新的状态,以及当前点击这一行的id值,监听复选框状态改变的事件。
element-ui给table提供了事件select和select-all,分别为当用户手动勾选数据行和全选 Checkbox 时触发的事件。在el-table中通过事件绑定的方式监听select和select-all

<el-table
  @select="statusChanged"
  @select-all="statusChangedAll">
</el-table>

在methods中定义方法statusChanged,接收两个参数,通过第二个参数获取选中行的id和状态,赋值到参数对象中,然后触发mutations修改对应的选中状态

statusChanged (val, row) {
     
  const param = {
     
    id: row.id,
    // 点击后状态取反
    status: !row.done
  }
  this.$store.commit('changeStatus', param)
}

在mutations中新增changeStatus接收传递过来的参数,先查找索引,根据索引找到对象并修改

changeStatus (state, param) {
     
  const i = state.list.findIndex(x => x.id === param.id)
  if (i !== -1) {
     
    state.list[i].done = param.status
  }
}

定义方法statusChangedAll,传入一个参数val,val长度不为0,则全选、否则取消全选,以此触发对应的mutations,改变状态

statusChangedAll (val) {
     
  if (val.length !== 0) {
     
    this.$store.commit('changeStatusAll')
  } else {
     
    this.$store.commit('cleanStatusAll')
  }
}
// 修改全选后的状态
changeStatusAll (state) {
     
  // console.log(params)
  state.list.forEach(row => {
     
    row.done = true
  })
},
// 取消全选后的状态
cleanStatusAll (state) {
     
  state.list.forEach(row => {
     
    row.done = false
  })
}

step08:统计未完成任务数量

这里需要用到getters,getters是一个包装器,不会修改state中的原数据。在list数组中调用filter函数,过滤掉未完成的任务,形成新的数组,将这个数组的长度return

getters: {
     
 // 统计未完成的任务数量
 unDoneLength (state) {
     
   return state.list.filter(x => x.done === false).length
 }
}

在App.vue中引入,在computed计算属性中使用

import {
      mapState, mapGetters } from 'vuex'

computed: {
     
  ...mapGetters(['unDoneLength'])
}

剩余条数处修改为

<span>{
    {unDoneLength}}条剩余span>

step09:清除未完成任务

绑定一个click事件

<el-button @click="clean">清除已完成el-button>

在methods中添加方法,触发对应的mutations

// 清除已完成任务
clean () {
     
  this.$store.commit('cleanDone')
}

在mutations中,过滤未完成的任务,重新赋值给state中的list

cleanDone (state) {
     
  state.list = state.list.filter(x => x.done === false)
}

step10:实现列表状态的切换显示

根据官方文档,Radio 单选框的事件处理函数需要绑定在Radio-group或者Ridio中,这里我们绑定到el-radio-group上面,回调函数是选中的按钮 label 值

<el-radio-group size="small" v-model="radio1" @change="changeList">
</el-radio-group>

在methods中定义一个方法,触发对应mutations

changeList (key) {
     
  this.$store.commit('changeViewKey', key)
}

因为组件中的所有数据都存储在store中,需要先在state中定义一个视图的key值,默认为全部。当点击不同选项的时候,将最新的key赋值在上面

viewKey: '全部'
// 修改视图的关键字
changeViewKey(state, key) {
     
  state.viewKey = key
}

将全局的viewKey值映射为App.vue组件中的计算属性。因为列表所有数据都是store中的list原封不动传递过来的,要实现按需切换,需要使用getters对数据进行包装处理

...mapState(['list', 'inputValue', 'viewKey'])

在store中新增一个getters节点,通过label上的值判断展示的内容

infolist (state) {
     
  if (state.viewKey === '全部') {
     
    return state.list
  }
  if (state.viewKey === '未完成') {
     
    return state.list.filter(x => !x.done)
  }
  if (state.viewKey === '已完成') {
     
    return state.list.filter(x => x.done)
  }
  return state.list
}

在App.vue中新增一个mapGetters节点,将el-table中的数据源切换为getters事件,watch中也改为infolist

...mapGetters(['unDoneLength', 'infolist'])
<el-table :data="infolist"><el-table>
watch: {
     
  infolist: function (val) {
     
    this.checked()
  }
}

小结

我在做这个项目的过程中遇到element-ui的坑还是有一些的,有些百度上没有解决方案(也可能是我搜的方式不对),最后都是直接看官方文档、在思否上提问和看github上的issue上找到问题的解决方法。如果大家有幸踩到坑,不妨也如法炮制一下。

你可能感兴趣的:(vue实战,vue,elementui)