Vuex + localStorage + html实现简易todolist
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:
<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的更多相关文章
- 基于vue2.0+vuex+localStorage开发的本地记事本
本文采用vue2.0+vuex+localStorage+sass+webpack,实现一个本地存储的记事本.兼容PC端和移动端.在线预览地址:DEMO github地址:https://github ...
- Vuejs 实现简易 todoList 功能 与 组件
todoList 结合之前 Vuejs 基础与语法 使用 v-model 双向绑定 input 输入内容与数据 data 使用 @click 和 methods 关联事件 使用 v-for 进行数据循 ...
- vue 简易toDoList
vue+bootstrap简易响应式任务管理表: <!DOCTYPE html> <html> <head> <meta charset="UTF- ...
- jQuery+ localStorage 实现一个简易的计时器
原型 图片发自简书App 需求1.关闭浏览器时时间继续运行2.刷新时保持当前状态3.结束时间保存在客户端 <div class="wrapper"> <div c ...
- Vuex基础 -01 -实现简易计数器 -支持 加数/ 减数/ 奇数再加/ 异步加法(setTimeout 1000ms) -单组件演示语法
Vuex 的结构图 工程组织 Vuex的核心管理程序 store.js /* vuex的核心管理程序 */ import Vue from 'vue' import Vuex from 'vuex' ...
- vue项目用户登录状态管理,vuex+localStorage实现
安装vuex cnpm install vuex --save-dev
- react 做的简易todolist
首先要有一定的react的基础,里面的一些不做解释(包括项目文件的用法及作用) ### 1. 先安装react的插件 npm install create-react-app -g ...
- 详解vuex结合localstorage动态监听storage的变化
这篇文章主要介绍了详解vuex结合localstorage动态监听storage的变化,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 需求:不同组件间共用同一数据,当一个 ...
- 页面刷新 vuex 数据重新被初始化
1.原因 vuex里用来存储的也只是一个全局变量,当页面刷新,该全局变量自然不存在了. 2.解决 使用localStorage存储一份 (1)storage.js /** * vuex localSt ...
随机推荐
- CentOS7 64位下MySQL区分大小写
在使用centos系统时,安装完MySQL数据库,创建完表之后,发现查询表操作时,是区分大小写的, 说以说在创建表之前,需要查看一下数据库是否区分大小写: 查看办法: lower_case_table ...
- T133308 57级返校测试重测-T3-成绩单
大致题意: 给定n个学生的学号和分数, 求各个分数段的人数, 求把学号排序后的序列, 求满分的人数以及学号. 基本思路: 虽然看起来很繁琐(?),但就非常非常的简单,直接按题意做就好了. 然后有个坑, ...
- layui :iframe 与 layer 的位置问题
最近有个项目是用 Layui 搭的,但是在layer的使用上遇到了问题. 简单的说,在父页面声明layer,在子页面中的子页面用window.parent调用那个父页面的layer. 讲道理应该是和i ...
- linux : 新服务器部署项目要做的事
环境:阿里云服务器两台,一台web,一台db,系统centos7. 用户用外网访问web server ,web server 再去访问db server. 1 阿里云控制台进入系统2 SSH进入系统 ...
- IDEA 2020.1.2 idea 2020.1.3下载 安装 一键破解
IDEA 2020.1.2 idea 2020.1.3下载 安装 破解 本项目只做个人学习研究之用,不得用于商业用途!若资金允许,请点击链接购买正版,谢谢合作!学生凭学生证可免费申请正版授权!创业公司 ...
- 文件传输协议---TFTP
简介 TFTP协议全称为简单文件传输协议,是以UDP为基础的应用层协议,主要用于不同设备之间的文件传输.具有协议简单,易于实现的特点,常用于嵌入式设备开发中. 传输模式 数据的存储有不同的格式,磁盘中 ...
- 时间复杂度为O(nlogn)的排序算法
时间复杂度为O(nlogn)的排序算法(归并排序.快速排序),比时间复杂度O(n²)的排序算法更适合大规模数据排序. 归并排序 归并排序的核心思想 采用"分治思想",将要排序的数组 ...
- 官宣!AWS Athena正式可查询Apache Hudi数据集
1. 引入 Apache Hudi是一个开源的增量数据处理框架,提供了行级insert.update.upsert.delete的细粒度处理能力(Upsert表示如果数据集中存在记录就更新:否则插入) ...
- 第二章 Java基础知识(下)
2.1.分支结构(if.switch) 2.1.1.if语句 格式一: if (关系表达式) { 语句体; } 流程一: ①首先计算关系表达式的值 ②如果关系表达式的值为true就执行语句体 ③如果关 ...
- Intellij IDEA 快速查找接口实现类的快捷键
查找接口的实现类: IDEA 风格 ctrl + alt +B 在按F2查看详细文档注解 查看类或接口的继承关系: ctrl + h