1.项目结构
2.Vuex,什么是Vuex?
官方文档上的介绍是:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
我的理解是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个字符加上 '...' 展示。