Vuex + localStorage + html实现简易todolist

1.项目结构

2.Vuex,什么是Vuex?

官方文档上的介绍是:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

https://vuex.vuejs.org/zh/

我的理解是Vuex类似于一个 '全局变量' 管理器,你可以在这个 '管理器' 中对全局变量进行监听、修改、取值、赋值等操作;

基础用法:

2.1在Vue项目初始化时添加Vuex的安装,项目初始化后会生成 store 文件夹,index.js文件是Vuex的入口文件:

2.1.1.namespaced:true,vuex中的store是模块管理,在store的index.js中引入各个模块时为了解决不同模块命名冲突的问题,将 namespace置为 true可以加上模块名后调用不同模块的mutations、actions等属性;

2.1.2 state,当前模块管理的‘状态‘ ,可以理解为index模块中管理的变量;

2.1.3 mutations,当前模块的提交方法,所有对state中的 '状态' 的改动都需要通过mutations来进行,只接受两个参数,一个是默认参数,当前模块的state对象,另一个是传入的数据,传入多个数据只接受第1个,在同一模块下调用其他mutation要使用 this.otherMuation来进行调用;

2.1.4 actions,类似于mutations但是actions是用来提交mutations,他并不像mutations那样直接更改管理的 '状态',actions可以包含异步操作,比如在可以actions调用一个接口,在接口的回调用再提交mutations;

2.1.5 modules,引入的模块;

2.2 新建一个todolist模块,新建todo.js文件,unfinishedList和finishedList没有用到可以忽略; 

2.3 调用一次mutations来更改 '状态':

 1 let body = {
 2     title:'测试数据',
 3     date:'2020-03-01',
 4     content:'测试内容测试内容'
 5 };
 6 // 提交todo模块中的pushStuff,传入的数据是一个body对象;数据将会被push到todo模块的list中
 7 this.$store.commit("todo/pushStuff", body);
 8 
 9 // 如果不加模块名,则默认提交index文件中的mutation
10 // this.$store.commit("someMutaion", body);

2.4 获取一次 '状态':

 1 // 在Vue项目的任何 .vue文件中使用 下列语句进行 '状态' 的获取
 2 
 3 ...
 4 mounted(){
 5 // this.$store.state.(模块名).(模块state中的变量);
 6 // 获取的是todo模块中的 '状态'
 7 
 8 // this.$store.state.(index中state中的变量);
 9 // 获取的是index模块中的状态
10    
11     let list = this.$store.state.todo.list;
12 }

2.5 到这一步,已经完成一次Vuex的基础使用;

3.localStorage

3.1 localStorage是window对象中的一个存储对象属性,将传入的数据以键值对(Key/Value)的形式存储起来;Vuex中管理的 '状态' 在页面刷新时会丢失,配合localStorage本地存储数据实现数据持久化;

3.2 基础用法:

从localStorage取出数据:

 1 ...
 2 mounted(){
 3 if (window.localStorage) {
 4         let storage = window.localStorage;
 5 // localStorage存有内容
 6         if (storage.length > 0) {
 7 /*
 8  * 取出 key为 'staffList' 的值,取出来的值是JSON字符串需要
 9  * 用JSON.parse()转成对象,将取出来的值存入Vuex
10  */
11      if (localList && localList.length > 0) {
12             this.$store.commit("todo/concatStaff", JSON.parse(localList));
13           }
14     }
15 }else{
16 console.log('浏览器不支持 localStorage');    
17     }
18 }
19 ...

向localStorage存储数据:

 1 let body={title:"标题",date:"2020-01-01",content:"内容"}; 2 // 将数据转换成JSON字符串再处存入 3 window.localStorage.setItem('body',JSON.stringify(body)); 

4.项目代码:

App.vue

 1 <template>
 2   <div id="app">
 3     <div id="nav">
 4       <router-link to="/">Home</router-link>|
 5       <router-link to="/about">About</router-link>
 6     </div>
 7     <router-view />
 8   </div>
 9 </template>
10 <script>
11 export default {
12   created() {
13     this.localStoreCheck();
14     // window绑定 '刷新前' 触发的事件,将数据存储在localStorage中
15     window.addEventListener("beforeunload", () => {
16       this.localStorageSet();
17     });
18   },
19   computed: {},
20   beforeMount() {
21     this.localStoreCheck();
22   },
23   methods: {
24     localStoreCheck() {
25       if (window.localStorage) {
26         let storage = window.localStorage;
27         if (storage.length > 0) {
28           let localList = storage.getItem("staffList");
29           if (localList && localList.length > 0) {
30             this.$store.commit("todo/concatStaff", JSON.parse(localList));
31           }
32         }
33       } else {
34         console.log("浏览器不支持 localStorage");
35       }
36     },
37     localStorageSet() {
38       let list = JSON.stringify(this.$store.state.todo.list);
39       console.log("---------", list);
40       localStorage.setItem("staffList", list);
41     },
42   },
43 };
44 </script>
45 
46 <style>
47 #app {
48   font-family: Avenir, Helvetica, Arial, sans-serif;
49   -webkit-font-smoothing: antialiased;
50   -moz-osx-font-smoothing: grayscale;
51   text-align: center;
52   color: #2c3e50;
53 }
54 
55 #nav {
56   padding: 30px;
57 }
58 
59 #nav a {
60   font-weight: bold;
61   color: #2c3e50;
62 }
63 
64 #nav a.router-link-exact-active {
65   color: #42b983;
66 }
67 </style>

Home.vue

 1 <template>
 2   <div class="home">
 3     <ToDoList></ToDoList>
 4   </div>
 5 </template>
 6 
 7 <script>
 8 import ToDoList from "@/views/todolist/ToDoList";
 9 export default {
10   name: "Home",
11   components: {
12     ToDoList,
13   },
14   mounted() {
15   },
16   data() {
17     return {};
18   },
19   methods: {},
20 };
21 </script>
22 <style lang="less" scoped>
23 </style>

 

ToDoList.vue

  1 <template>
  2   <div class="container">
  3     <div class="lt-form">
  4       <form class="form">
  5         <div class="title-label">
  6           <label for="title">{{TITLE}}</label>
  7           <input
  8             :disabled="status!=='SUBMIT'?false:true"
  9             autocomplete="off"
 10             type="text"
 11             id="title"
 12             v-model="form.title"
 13             data-rule="title:required"
 14           />
 15         </div>
 16 
 17         <div class="date-label">
 18           <label for="date">{{DATE}}</label>
 19           <input
 20             :disabled="status!=='SUBMIT'?false:true"
 21             autocomplete="off"
 22             type="date"
 23             id="date"
 24             v-model="form.date"
 25           />
 26         </div>
 27 
 28         <div class="content-label">
 29           <label for="content">{{CONTENT}}</label>
 30           <textarea
 31             :disabled="status!=='SUBMIT'?false:true"
 32             id="content"
 33             rows="5"
 34             v-model="form.content"
 35           ></textarea>
 36         </div>
 37         <div class="btns" v-if="status==='ADD'">
 38           <div class="btn btn-submit">
 39             <input type="button" value="提交" @click="submit" />
 40           </div>
 41           <div class="btn btn-reset">
 42             <input type="button" value="清空" @click="reset" />
 43           </div>
 44           <div class="btn btn-cancle">
 45             <input type="button" value="取消" @click="cancle" />
 46           </div>
 47         </div>
 48         <div class="btns" v-else-if="status==='EDIT'">
 49           <div class="btn btn-save">
 50             <input type="button" value="保存" @click="save" />
 51           </div>
 52           <div class="btn btn-cancle">
 53             <input type="button" value="取消" @click="cancle" />
 54           </div>
 55         </div>
 56         <div class="btns" v-else>
 57           <div class="btn btn-new">
 58             <input type="button" value="新增" @click="newItem" />
 59           </div>
 60           <div class="btn btn-delete">
 61             <input type="button" value="删除" @click="deleteItem" />
 62           </div>
 63           <div class="btn btn-edit">
 64             <input type="button" value="修改" @click="modifyItem" />
 65           </div>
 66         </div>
 67       </form>
 68     </div>
 69     <div class="rt-part">
 70       <div class="rt-nav">
 71         <div class="rt-nav-block">
 72           <div
 73             :class="'nav-item item-'+index "
 74             @click="clickHandler(index,item)"
 75             v-for="(item,index) in arr "
 76             :key="index"
 77           >
 78             <span>{{item.title|doSlice}}</span>
 79             <span>{{item.date}}</span>
 80             <span>{{item.content|doSlice}}</span>
 81           </div>
 82         </div>
 83       </div>
 84     </div>
 85   </div>
 86 </template>
 87 
 88 <script>
 89 export default {
 90   name: "Home",
 91   components: {
 92     // HelloWorld
 93   },
 94   mounted() {
 95     this.renderNav();
 96     this.$nextTick(() => {
 97       // console.log(document.querySelector(".item-0"));
 98       if (this.list.length > 0) {
 99         this.clickHandler("0");
100       }
101     });
102 
103     // setTimeout(() => {}, 0);
104   },
105 
106   computed: {
107     list() {
108       // this.renderNav();
109       return this.$store.state.todo.list;
110     },
111   },
112   data() {
113     return {
114       form: {
115         title: "",
116         date: "",
117         content: "",
118       },
119       TITLE: "标题",
120       DATE: "日期",
121       CONTENT: "事件",
122       FORMSTATUS: {
123         ADD: "ADD",
124         EDIT: "EDIT",
125         SUBMIT: "SUBMIT",
126       },
127       arr: [],
128       currTarget: "",
129       lastIndex: "",
130       status: "ADD", // ADD EDIT SUBMIT
131     };
132   },
133   filters: {
134     doSlice: function (value) {
135       let newVal = value.length > 5 ? value.slice(0, 5) + "..." : value;
136       return newVal;
137     },
138   },
139   methods: {
140     submit() {
141       let objKey;
142       let flag = Object.keys(this.form).some((key) => {
143         if (this.form[key] === "") {
144           objKey = key;
145           return true;
146         }
147       });
148       if (flag) {
149         alert(`${this[objKey.toUpperCase()]} 不能为空`);
150         return false;
151       }
152       let body = this.$clone(this.form);
153       this.$store.commit("todo/pushStuff", body);
154       this.arr.push(body);
155 
156       // 等到DOM渲染完成后调用的钩子函数
157       this.$nextTick(() => {
158         this.clickHandler(this.arr.length - 1);
159       });
160       this.changeFormStatus(this.FORMSTATUS.SUBMIT);
161     },
162     reset() {
163       Object.keys(this.form).forEach((key) => {
164         this.form[key] = "";
165       });
166     },
167     renderNav() {
168       this.arr = this.$clone(this.list);
169       if (this.arr.length > 0) {
170         this.status = true;
171         let temp = this.$clone(this.list[0]);
172         this.form = temp;
173       }
174     },
175     clickHandler(index) {
176       // console.log(index);
177       // console.log(this.arr);
178       // this.$store.commit("todo/deleteStuff");
179       if (this.status === "ADD" || this.status === "EDIT") {
180         this.changeFormStatus(this.FORMSTATUS.SUBMIT);
181       }
182       this.changeClassByClass(index);
183       this.lastIndex = index;
184       let temp = this.$clone(this.list[index]);
185       this.form = temp;
186     },
187     changeClassByClass(index) {
188       if (this.lastIndex === "") {
189         let currClass = document.querySelector(".item-" + index);
190         currClass.className = currClass.className + " active";
191       } else {
192         // let lastClass = document.querySelector(".item-" + this.lastIndex);
193         // lastClass.className = lastClass.className.replace(/active/g, " ");
194         this.clearActiveClass(this.lastIndex);
195         let currClass = document.querySelector(".item-" + index);
196         currClass.className = currClass.className + " active";
197       }
198     },
199     changeBtnByClass() {
200       document.querySelector;
201     },
202     newItem() {
203       this.reset();
204       // this.status = true;
205       this.changeFormStatus(this.FORMSTATUS.ADD);
206       this.clearActiveClass(this.lastIndex);
207     },
208     clearActiveClass(index) {
209       let dom = document.querySelector(".item-" + index);
210       dom.className = dom.className.replace(/active/g, " ");
211     },
212     modifyItem() {
213       if (this.arr.length > 0) {
214         this.changeFormStatus(this.FORMSTATUS.EDIT);
215       } else {
216         return;
217       }
218     },
219     deleteItem() {
220       // console.log();
221       let operation = {
222         index: this.lastIndex,
223         num: 1,
224       };
225       this.$store.commit("todo/deleteStuff", operation);
226       this.arr.splice(operation.index, operation.num);
227       this.reset();
228       // if(this.){}
229       this.setSelectedAfterDelete();
230     },
231     cancle() {
232       // this.status = "EDITING";
233       this.changeFormStatus(this.FORMSTATUS.SUBMIT);
234     },
235     changeFormStatus(status) {
236       this.status = status;
237     },
238     setSelectedAfterDelete() {
239       let lastIndex = parseInt(this.lastIndex);
240       let length = this.arr.length;
241 
242       // lastIndex是 被删掉的最后一个数据的索引
243       if (lastIndex == length && length > 0) {
244         // this.lastIndex = lastIndex - 1;
245         this.clickHandler(lastIndex - 1);
246       } else if (length > 0) {
247         this.clickHandler(this.lastIndex);
248       } else {
249         return;
250       }
251     },
252     save() {
253       let body = this.$clone(this.form);
254       this.$set(this.arr, this.lastIndex, body);
255       this.changeFormStatus(this.FORMSTATUS.SUBMIT);
256     },
257   },
258 };
259 </script>
260 <style lang="less" scoped>
261 * {
262   padding: 0;
263   margin: 0;
264 }
265 .container {
266   padding: 15px;
267   min-height: 300px;
268   display: flex;
269   justify-content: center;
270   align-items: stretch;
271   .lt-form {
272     height: 200px;
273     margin: 0;
274      500px;
275     height: 100%;
276     border: 1px solid;
277     padding: 15px;
278     margin: 0 15px;
279     .form {
280       .title-label,
281       .date-label {
282          50%;
283         display: inline-block;
284         text-align: left;
285         label {
286           display: inline-block;
287            15%;
288         }
289         input {
290            80%;
291           font-size: 16px;
292           // margin-right: -15px;
293         }
294       }
295       .content-label {
296         text-align: left;
297         display: block;
298          100%;
299         label {
300           display: inline-block;
301         }
302         textarea {
303           font-size: 16px;
304           resize: none;
305            100%;
306         }
307       }
308       .btns {
309         display: flex;
310         justify-content: space-between;
311         .btn-submit,
312         .btn-reset,
313         .btn-delete,
314         .btn-new,
315         .btn-edit,
316         .btn-cancle,
317         .btn-save {
318           //  50%;
319           flex: 1;
320           // display: inline-block;
321           // text-align: start;
322           input {
323              50%;
324             // line-height: 10px;
325             padding: 5px;
326             font-size: 16px;
327             // background: rgb(123, 321, 313);
328             // border-radius: 5px;
329             margin: 10px 0;
330           }
331         }
332         // .btn.btn-submit,
333         // .btn.btn-edit {
334         //   text-align: start;
335         // }
336         // .btn-new {
337         //   text-align: center;
338         // }
339         // .btn.btn.btn-reset,
340         // .btn.btn-delete {
341         //   text-align: end;
342         // }
343       }
344     }
345   }
346   .rt-part {
347     display: flex;
348     flex-direction: column;
349     .rt-nav {
350        100%;
351       height: 200px;
352       min-height: 230px;
353       border: 1px solid;
354       margin: 0 15px;
355       // position: relative;
356       .rt-nav-block {
357         flex: 1;
358         height: 100%;
359         overflow: auto;
360         .nav-item {
361           display: flex;
362           align-items: center;
363           justify-content: space-around;
364           // position: relative;
365           flex-wrap: nowrap;
366           span {
367             flex: 1;
368             overflow: hidden;
369           }
370         }
371       }
372     }
373   }
374 }
375 .active {
376   background: #4f4f4f;
377   color: white;
378 }
379 </style>

/store/todolist/todo.js:

 1 export default {
 2     namespaced: true,
 3     state: {
 4         list: [{
 5             title: "测试数据1",
 6             date: "2020-02-02",
 7             content: "测试数据1测试数据1测试数据1"
 8         }],
 9         unfinishedList: [],
10         finishedList: [],
11     },
12     mutations: {
13         pushStuff(state, value) {
14             state.list.push(value);
15         },
16         concatStaff(state, arr) {
17             state.list = [].concat(arr);
18         },
19         deleteStuff(state, opertation) {
20             // console.log(arguments);
21             state.list.splice(opertation.index, opertation.num);
22         }
23     },
24     actions: {},
25     modules: {}
26 }

  /js/utils.js:

1 export default {
2     clone: function (body) {
3         return JSON.parse(JSON.stringify(body));
4     },
5 }

效果图:超过5个字符则只截取前5个字符加上 '...' 展示。

原文地址:https://www.cnblogs.com/iwannadoflex/p/13471828.html