1.项目结构

Vuex + localStorage + html实现简易todolist-LMLPHP

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,引入的模块;

Vuex + localStorage + html实现简易todolist-LMLPHP

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

Vuex + localStorage + html实现简易todolist-LMLPHP

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

 <template>
<div class="home">
<ToDoList></ToDoList>
</div>
</template> <script>
import ToDoList from "@/views/todolist/ToDoList";
export default {
name: "Home",
components: {
ToDoList,
},
mounted() {
},
data() {
return {};
},
methods: {},
};
</script>
<style lang="less" scoped>
</style>

ToDoList.vue

 <template>
<div class="container">
<div class="lt-form">
<form class="form">
<div class="title-label">
<label for="title">{{TITLE}}</label>
<input
:disabled="status!=='SUBMIT'?false:true"
autocomplete="off"
type="text"
id="title"
v-model="form.title"
data-rule="title:required"
/>
</div> <div class="date-label">
<label for="date">{{DATE}}</label>
<input
:disabled="status!=='SUBMIT'?false:true"
autocomplete="off"
type="date"
id="date"
v-model="form.date"
/>
</div> <div class="content-label">
<label for="content">{{CONTENT}}</label>
<textarea
:disabled="status!=='SUBMIT'?false:true"
id="content"
rows="5"
v-model="form.content"
></textarea>
</div>
<div class="btns" v-if="status==='ADD'">
<div class="btn btn-submit">
<input type="button" value="提交" @click="submit" />
</div>
<div class="btn btn-reset">
<input type="button" value="清空" @click="reset" />
</div>
<div class="btn btn-cancle">
<input type="button" value="取消" @click="cancle" />
</div>
</div>
<div class="btns" v-else-if="status==='EDIT'">
<div class="btn btn-save">
<input type="button" value="保存" @click="save" />
</div>
<div class="btn btn-cancle">
<input type="button" value="取消" @click="cancle" />
</div>
</div>
<div class="btns" v-else>
<div class="btn btn-new">
<input type="button" value="新增" @click="newItem" />
</div>
<div class="btn btn-delete">
<input type="button" value="删除" @click="deleteItem" />
</div>
<div class="btn btn-edit">
<input type="button" value="修改" @click="modifyItem" />
</div>
</div>
</form>
</div>
<div class="rt-part">
<div class="rt-nav">
<div class="rt-nav-block">
<div
:class="'nav-item item-'+index "
@click="clickHandler(index,item)"
v-for="(item,index) in arr "
:key="index"
>
<span>{{item.title|doSlice}}</span>
<span>{{item.date}}</span>
<span>{{item.content|doSlice}}</span>
</div>
</div>
</div>
</div>
</div>
</template> <script>
export default {
name: "Home",
components: {
// HelloWorld
},
mounted() {
this.renderNav();
this.$nextTick(() => {
// console.log(document.querySelector(".item-0"));
if (this.list.length > 0) {
this.clickHandler("0");
}
}); // setTimeout(() => {}, 0);
}, computed: {
list() {
// this.renderNav();
return this.$store.state.todo.list;
},
},
data() {
return {
form: {
title: "",
date: "",
content: "",
},
TITLE: "标题",
DATE: "日期",
CONTENT: "事件",
FORMSTATUS: {
ADD: "ADD",
EDIT: "EDIT",
SUBMIT: "SUBMIT",
},
arr: [],
currTarget: "",
lastIndex: "",
status: "ADD", // ADD EDIT SUBMIT
};
},
filters: {
doSlice: function (value) {
let newVal = value.length > 5 ? value.slice(0, 5) + "..." : value;
return newVal;
},
},
methods: {
submit() {
let objKey;
let flag = Object.keys(this.form).some((key) => {
if (this.form[key] === "") {
objKey = key;
return true;
}
});
if (flag) {
alert(`${this[objKey.toUpperCase()]} 不能为空`);
return false;
}
let body = this.$clone(this.form);
this.$store.commit("todo/pushStuff", body);
this.arr.push(body); // 等到DOM渲染完成后调用的钩子函数
this.$nextTick(() => {
this.clickHandler(this.arr.length - 1);
});
this.changeFormStatus(this.FORMSTATUS.SUBMIT);
},
reset() {
Object.keys(this.form).forEach((key) => {
this.form[key] = "";
});
},
renderNav() {
this.arr = this.$clone(this.list);
if (this.arr.length > 0) {
this.status = true;
let temp = this.$clone(this.list[0]);
this.form = temp;
}
},
clickHandler(index) {
// console.log(index);
// console.log(this.arr);
// this.$store.commit("todo/deleteStuff");
if (this.status === "ADD" || this.status === "EDIT") {
this.changeFormStatus(this.FORMSTATUS.SUBMIT);
}
this.changeClassByClass(index);
this.lastIndex = index;
let temp = this.$clone(this.list[index]);
this.form = temp;
},
changeClassByClass(index) {
if (this.lastIndex === "") {
let currClass = document.querySelector(".item-" + index);
currClass.className = currClass.className + " active";
} else {
// let lastClass = document.querySelector(".item-" + this.lastIndex);
// lastClass.className = lastClass.className.replace(/active/g, " ");
this.clearActiveClass(this.lastIndex);
let currClass = document.querySelector(".item-" + index);
currClass.className = currClass.className + " active";
}
},
changeBtnByClass() {
document.querySelector;
},
newItem() {
this.reset();
// this.status = true;
this.changeFormStatus(this.FORMSTATUS.ADD);
this.clearActiveClass(this.lastIndex);
},
clearActiveClass(index) {
let dom = document.querySelector(".item-" + index);
dom.className = dom.className.replace(/active/g, " ");
},
modifyItem() {
if (this.arr.length > 0) {
this.changeFormStatus(this.FORMSTATUS.EDIT);
} else {
return;
}
},
deleteItem() {
// console.log();
let operation = {
index: this.lastIndex,
num: 1,
};
this.$store.commit("todo/deleteStuff", operation);
this.arr.splice(operation.index, operation.num);
this.reset();
// if(this.){}
this.setSelectedAfterDelete();
},
cancle() {
// this.status = "EDITING";
this.changeFormStatus(this.FORMSTATUS.SUBMIT);
},
changeFormStatus(status) {
this.status = status;
},
setSelectedAfterDelete() {
let lastIndex = parseInt(this.lastIndex);
let length = this.arr.length; // lastIndex是 被删掉的最后一个数据的索引
if (lastIndex == length && length > 0) {
// this.lastIndex = lastIndex - 1;
this.clickHandler(lastIndex - 1);
} else if (length > 0) {
this.clickHandler(this.lastIndex);
} else {
return;
}
},
save() {
let body = this.$clone(this.form);
this.$set(this.arr, this.lastIndex, body);
this.changeFormStatus(this.FORMSTATUS.SUBMIT);
},
},
};
</script>
<style lang="less" scoped>
* {
padding: 0;
margin: 0;
}
.container {
padding: 15px;
min-height: 300px;
display: flex;
justify-content: center;
align-items: stretch;
.lt-form {
height: 200px;
margin: 0;
width: 500px;
height: 100%;
border: 1px solid;
padding: 15px;
margin: 0 15px;
.form {
.title-label,
.date-label {
width: 50%;
display: inline-block;
text-align: left;
label {
display: inline-block;
width: 15%;
}
input {
width: 80%;
font-size: 16px;
// margin-right: -15px;
}
}
.content-label {
text-align: left;
display: block;
width: 100%;
label {
display: inline-block;
}
textarea {
font-size: 16px;
resize: none;
width: 100%;
}
}
.btns {
display: flex;
justify-content: space-between;
.btn-submit,
.btn-reset,
.btn-delete,
.btn-new,
.btn-edit,
.btn-cancle,
.btn-save {
// width: 50%;
flex: 1;
// display: inline-block;
// text-align: start;
input {
width: 50%;
// line-height: 10px;
padding: 5px;
font-size: 16px;
// background: rgb(123, 321, 313);
// border-radius: 5px;
margin: 10px 0;
}
}
// .btn.btn-submit,
// .btn.btn-edit {
// text-align: start;
// }
// .btn-new {
// text-align: center;
// }
// .btn.btn.btn-reset,
// .btn.btn-delete {
// text-align: end;
// }
}
}
}
.rt-part {
display: flex;
flex-direction: column;
.rt-nav {
width: 100%;
height: 200px;
min-height: 230px;
border: 1px solid;
margin: 0 15px;
// position: relative;
.rt-nav-block {
flex: 1;
height: 100%;
overflow: auto;
.nav-item {
display: flex;
align-items: center;
justify-content: space-around;
// position: relative;
flex-wrap: nowrap;
span {
flex: 1;
overflow: hidden;
}
}
}
}
}
}
.active {
background: #4f4f4f;
color: white;
}
</style>

/store/todolist/todo.js:

 export default {
namespaced: true,
state: {
list: [{
title: "测试数据1",
date: "2020-02-02",
content: "测试数据1测试数据1测试数据1"
}],
unfinishedList: [],
finishedList: [],
},
mutations: {
pushStuff(state, value) {
state.list.push(value);
},
concatStaff(state, arr) {
state.list = [].concat(arr);
},
deleteStuff(state, opertation) {
// console.log(arguments);
state.list.splice(opertation.index, opertation.num);
}
},
actions: {},
modules: {}
}

  /js/utils.js:

 export default {
clone: function (body) {
return JSON.parse(JSON.stringify(body));
},
}

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

Vuex + localStorage + html实现简易todolist-LMLPHP

05-22 04:40
查看更多