vue part5.2 vuex todolist 改写
代码
main部分
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="stylesheet" href="./static/css/bootstrap.css"> <title>vue_demo</title> <style> .router-link-active { color: red !important; } </style> </head> <body> <div id="app"> </div> <!--app --> </body> </html>
src/base.css
body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(225, 225, 225, 0.2), 0 1px 2px rgba(0, 0, 0,0.15); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; }
src/main.js
/** * Created by infaa on 2018/9/19. */ import Vue from 'vue' import App from './components/App' import store from './store' import './base.css' /* eslint-disable no-new */ new Vue({ el: '#app', render: h => h(App), store })
工具部分
src/util/storageUtil.js
/** * Created by infaa on 2018/9/20. */ const TODO_KEY = 'todos_key' export default { saveTodos (value) { window.localStorage.setItem(TODO_KEY, JSON.stringify(value)) }, readTodos () { return JSON.parse(window.localStorage.getItem(TODO_KEY) || '[]') } }
store 部分 (vuex)
src/store/action.js
/** * Created by infaa on 2018/9/22. */ import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODOS, CLEAR_ALL_COMPLETED, RECEIVE_TODOS} from './mutation-types' import storeageUtil from '../util/storageUtil' export default { addTodo ({commit}, todo) { commit(ADD_TODO, {todo}) }, deleteTodo ({commit}, index) { commit(DELETE_TODO, {index}) }, selectAllTodos ({commit}, check) { commit(SELECT_ALL_TODOS, {check}) }, clearALLCompleted ({commit}) { commit(CLEAR_ALL_COMPLETED) }, reqTodos ({commit}) { // 模拟异步获取延迟1秒 setTimeout(() => { const todos = storeageUtil.readTodos() commit(RECEIVE_TODOS, todos) }, 1000) } }
src/store/getters.js
/** * Created by infaa on 2018/9/22. */ export default { totalCount (state) { return state.todos.length }, completeCount (state) { return state.todos.reduce((preTotal, todo) => { return preTotal + (todo.complete ? 1 : 0) }, 0) }, isAllSelected (state, getters) { return getters.totalCount === getters.completeCount && state.todos.length > 0 } }
src/store/index.js
/** * Created by infaa on 2018/9/22. */ import Vue from 'vue' import Vuex from 'vuex' import state from './state' import mutations from './mutations' import actions from './actions' import getters from './getters' Vue.use(Vuex) /* eslint-disable new-cap */ export default new Vuex.Store({ state, mutations, actions, getters })
src/store/mutation-types.js
/** * Created by infaa on 2018/9/22. * mutation 名称常量 */ export const ADD_TODO = 'add_todo' export const DELETE_TODO = 'delete_todo' export const SELECT_ALL_TODOS = 'select_all_todos' export const CLEAR_ALL_COMPLETED = 'clear_all_completed' export const RECEIVE_TODOS = 'receive_todos' // 接收todos
src/store/mutation.js
/** * Created by infaa on 2018/9/22. */ import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODOS, CLEAR_ALL_COMPLETED, RECEIVE_TODOS} from './mutation-types' export default { [ADD_TODO] (state, {todo}) { state.todos.unshift(todo) }, [DELETE_TODO] (state, {index}) { state.todos.splice(index, 1) }, [SELECT_ALL_TODOS] (state, {check}) { state.todos.forEach(todo => (todo.complete = check)) }, [CLEAR_ALL_COMPLETED] (state) { state.todos = state.todos.filter(todo => (!todo.complete)) }, [RECEIVE_TODOS] (state, todos) { state.todos = todos } }
src/store/state.js
/** * Created by infaa on 2018/9/22. */ export default { todos: [] }
组件部分
components/App.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <TodoHeader></TodoHeader> <TodoList></TodoList> <TodoFooter></TodoFooter> </div> </div> </template> <script> import TodoHeader from './TodoHeader.vue' import TodoList from './TodoList.vue' import TodoFooter from './TodoFooter.vue' // import storageUtil from '../util/storageUtil.js' export default { mounted () { // 发送命令到acitong,模拟异步获取todos数据,由action处理 this.$store.dispatch('reqTodos') }, components: { TodoHeader, TodoList, TodoFooter } // methods: { // // }, // watch: { // todos: { // deep: true, // handler: storageUtil.saveTodos // } // // } } </script> <style> .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
components/TodoFooter.vue
<template> <div class="todo-footer"> <label> <input type="checkbox" v-model="isAllChecked"> </label> <span> <span>已完成{{completeCount}}</span>/全部{{totalCount}} </span> <button class="btn btn-danger" v-show="completeCount" @click="clearALLCompleted">清除已完成任务</button> </div> </template> <script> import {mapGetters, mapActions} from 'vuex' export default { computed: { ...mapGetters(['totalCount', 'completeCount', 'isAllSeleted']), isAllChecked: { get () { return this.$store.getters.isAllSelected }, set (value) { this.$store.dispatch('selectAllTodos', value) } } }, methods: { ...mapActions(['clearALLCompleted']) } } </script> <style> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
components/TodoHeader.vue
<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="inputTodo" @keyup.enter="addItem"> </div> </template> <script> export default { data () { return { inputTodo: '' } }, methods: { addItem () { const title = this.inputTodo.trim() if (!title) { alert('title Kong') return } const todo = { title, complete: false } this.$store.dispatch('addTodo', todo) this.inputTodo = '' // 注意这里要操作this对象而不是函数内局部变量title // 验证合法性 生成对象 添加 还原 } } } </script> <style> .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.75), 0 0 8px rgba(82, 168, 236, 0.5); } </style>
components/TodoItem.vue
<template> <li :style="{background: bgColor}" @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)"> <label> <input type="checkbox" v-model="todo.complete"> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="deleteItem" v-show="isShow">删除</button> </li> </template> <script> export default { props: { todo: Object, index: Number }, data () { return { isShow: false, bgColor: 'white' } }, methods: { deleteItem () { const index = this.index const todo = this.todo if (window.confirm(`删除${todo.title}`)) { this.$store.dispatch('deleteTodo', index) } }, handleEnter (isEnter) { if (isEnter) { this.isShow = true this.bgColor = 'grey' } else { this.isShow = false this.bgColor = 'white' } } } } </script> <style> /*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { /*float: left;*/ cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } </style>
components/TodoList.vue
<template> <ul class="todo-main"> <TodoItem v-for="(todo,index) in todos" :todo="todo" :key="index" :index="index"></TodoItem> </ul> </template> <script> import {mapState} from 'vuex' import TodoItem from './TodoItem.vue' import storeageUtil from '../util/storageUtil' export default { components: { TodoItem }, computed: { ...mapState(['todos']) }, watch: { todos: { deep: true, handler: storeageUtil.saveTodos } } } </script> <style> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>