脚本宝典收集整理的这篇文章主要介绍了用Vue搭建一个应用盒子(一):todo-list,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
最近在研究vue的相关知识,最好的学习方法莫过于自己开发一个SPA,这样带着问题来学习,进步自然飞速。于是边查边写差不多花了2周写完了一个todo-list,功能不够完备,但是麻雀虽小,却也是五脏俱全,基本功能是可以满足的了。话不多说,直接来看项目吧。
技术栈
- vue全家桶(vue、vuex、vue-router)
- webpack实现打包和热加载
- ES6
- ui框架用的是bootstrap
- rem方法完成适配移动端
- localstorage实现数据的保存
- node和npm(真是零基础啊,npm都是现学现卖的...)
以上。
接下来就是代码分析了。
一、用vue-cli配置一个项目
这一个步骤没什么好说的,网上教程一大堆,随便找一个照着走就好了。
完成后,你应该有一个项目的文件夹,里面应该有这几个文件:
README.md、build、config、index.htML、package.json、src、static
嗯,就这样。
二、安装相关依赖和一堆玩意。
配置vue-router和bootstrap
先安装依赖,命令行到对应根目录文件夹执行如下命令(推荐VS code,自带命令行输入,方便!)
npm install
稍等片刻完成(如果太慢,推进啊淘宝镜像的cnpm安装)
安装好之后,继续安装:
npm install vuex vue-router bootstrap --save
安装完成后,需要配置以下文件,确保能够使用。
打开:xx(项目文件夹)-src-main.js
如下:
import Vue From 'vue'
import VueRouter from 'vue-router'
import App from './App'
import 'bootstrap/dist/css/bootstrap.css'
Vue.use(VueRouter)
const routes=[
{
path:'/',
component:Home
},
{
path:'/todolist',
component:todolist
}
];
const router=new VueRouter({routes});
/* eslint-disable no-new */
const app=new Vue({
router,
el:'#app',
render:@H_191_126@h=>h(App) //ES6语法
})
这里配置了vue-router和bootstrap,项目中可以使用了,接着我们还需要配置vuex和jQuery。
配置vuex和jQuery
首先在根目录创建一个文件夹,命名为vuex,在里面创建一个Store.js文件,
配置如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state={
}
const getters={
}
const mutations={
}
export default new Vuex.Store({
state,
getters,
mutations
})
const是ES6的语法,这里getters,state,mutations都不急着用,先配置好。
配置好store.js,回到main.js继续配置。
增加一些内容:
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from './vuex/store'
import App from './App'
import Home from './components/Home.vue'
import todolist from './components/todolist.vue'
import 'bootstrap/dist/css/bootstrap.css'
Vue.use(VueRouter)
const routes=[
{
path:'/',
component:Home
},
{
path:'/todolist',
component:todolist
}
];
const router=new VueRouter({routes});
/* eslint-disable no-new */
const app=new Vue({
router,
store,
el:'#app',
render:h=>h(App) //ES6语法
})
好了,vuex就配置完了。接着我们配置jquery,因为bootstrap依赖JQuery,所以这里也必须放上去。
老规矩,先用npm安装JQuery。
npm install jquery --save
打开xx-build-webpack-base.conf.js,在module.exports里面添加如下代码:
plugins:[
new webpack.optimize.COMmonsChunkPlugin('common.js'),
new webpack.PRovidePlugin({
jQuery: "jquery",
$: "jquery"
})
]
打开main.js配置JQuery和bootstrap的动效。
添加一点内容:
import $ from 'jquery'
import 'bootstrap/dist/js/bootstrap.min.js'
OK,至此,所有的前期配置就完成了,可以开始正式的代码书写了。
三、组件结构和实现
Vue最碉堡的地方就是它的组件式开发,所以这个思想是我们在写代码式要时刻注意的,如何合理的划分自己的组件,是一件很需要思考的事,接下来我将详细介绍我的组件内容和实现的功能。
下面是我的组件结构:
在src文件夹里,有一个主组件:app.vue,有一个组件文件夹:conponents,在这里面我放了4个组件,如下:
Home.vue ———— 首页
todolist.vue ———— todolist 应用主页面
sidebar.vue ———— todolist任务列表
editor.vue ———— todolist任务编辑
我会一个个介绍功能。
app.vue
在首页里,我们会用bootstrap写一个导航,通过vue-router的路由导航到不同的应用。
代码如下:
<template>
<div @H_201_406@id="app">
<!--nav start-->
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"><i class="glyphicon glyphicon-home"></i></a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><router-link to="/todolist">Todo List</router-link></li>
<li><a href="#">开发中...</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!--nav end-->
<!--content-->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
data(){
return{
}
}
}
</script>
<style>
</style>
首页的上部分是一个导航,导航的UI和样式用的是bootstrap,导航用路由实现链接到不同的应用,要注意的是,不同的应用我们用不同的组件封装,比如这个待办事项的应用,我们用的是todolist.vue。还要注意的是,这些组件的注册和路由链接都需要在main.js中配置。不要忘记了。
返回查看main.js看看代码是怎么写的。
配置完后,我们的主页面上只有一个导航。接着我们配置主页。
Home.vue
我们在xx-src-components文件夹里创建一个新的组件:Home.vue。这个组件是我们的首页内容,这里我放了一张图,和一句话:欢迎!这里有你需要的App。这里同样用到了bootstrap的栅格系统,这样就可以兼容移动端了。
看代码:
<template>
<div class="Home">
<div class="container">
<div class="col-sm-8">
<div class="jumbotron">
<img src="../assets/home-l-img.jpg">
</div>
</div>
<div class="col-sm-4">
<div class="jumbotron">
<h2>欢迎你!</h2>
<p>这里有你需要的app</p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
data () {
return {
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.col-sm-8 .jumbotron{
padding: 0;
}
.jumbotron img{
width: 100%;
}
</style>
代码不复杂,就不解释了。接下来就是重头戏了。
todolist.vue
这是这个应用的主组件,在这个组件里还会包含两个子组件:sidebar.vue和editor.vue,所以在这个组件里,我们要实现的是一个新建任务的功能。新建的任务会显示在左边的任务列表sidebar.vue组件里,然后点击某个任务的编辑按钮,我们可以在右边的任务编辑editor.vue组件里修改。这就是这个应用的架构思路。下面来看看代码:
<template>
<div class="todolist">
<div class="container addInput">
<input type="text" v-model="taskText" class="form-control col-sm-12" placeholder="新建一个任务" v-on:keyup.enter="addTask">
</div>
<div class="container">
<div class="col-sm-6">
<sidebar></sidebar>
</div>
<div class="col-sm-6">
<editor></editor>
</div>
</div>
</div>
</template>
<script>
import sidebar from './sidebar.vue'
import editor from './editor.vue'
export default {
name: 'todolist',
data(){
return{
taskText:''
}
},
components: {
sidebar,
editor
},
methods:{
addTask(){
if(this.taskText==''){
alert('请输入具体任务内容!')
}else{
this.$store.commit('addTask',this.taskText);
this.taskText=''
}
}
}
}
</script>
<style scoped>
.addInput {
margin-bottom: .75rem;
}
</style>
代码量不算大,除了一个输入框之外就是两个组件的标签<sidebar></sidebar>、<editor></editor>
了。这里会有一个commit(),它里面引号的内容是一个函数,这个函数在store.js里面的mutations里编写。
要注意的点:
1.两个子组件要在父组件里面注册引入才能使用。
2.这里开始涉及到了Vuex的功能,简单说明一下,我们在输入任务后,这个任务的相关数据会被保存到状态管理store里面,然后通过mutations的操作,把输入的内容保存在子组件sidebar里。
显然设置完这里还是无法使用新建任务的,我们还需要两步操作。
第一步:设置sidebar.vue:
这个子组件是完成任务列表的渲染。看代码:
<template>
<div class="sidebar">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="text-left">
任务列表
<i class="glyphicon glyphicon-refresh pull-right"></i>
</h4>
</div>
<div class="panel-body sidebar-context">
<div>
<div v-for="(item,index) in items" class="panel panel-default">
<div class="panel-heading">
<h4>
<input type="checkbox">
{{item.task}}
<i class="glyphicon glyphicon-remove pull-right"></i>
<i class="glyphicon glyphicon-edit pull-right"></i>
</h4>
</div>
<div class="panel-body">
<p>
{{item.setTime}}<br>
{{item.details}}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'sidebar',
data() {
return {
}
},
computed:{
items(){
return this.$store.getters.items;
}
},
methods:{
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
ul {
list-style: none;
}
.sidebar-context{
max-height: 8rem;
overflow: scroll;
}
i.glyphicon{
font-size: .275rem;
cursor: pointer;
margin-left: .25rem;
}
</style>
可以仔细考虑一下代码的书写,在<script>
里设置computed,否则保存在store.js的值无法输出到子组件sidebar.vue里。
第二步,设置store.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state={
items:[]
}
const getters={
items:state=>state.items
}
const mutations={
addTask(state,task){
var myDate=new Date();
var y=myDate.getFullYear();
var m,
mm=myDate.getMonth()+1;
if(mm<10){
m="0"+mm;
}else{
m=mm;
}
var d,
dd=myDate.getDate();
if(dd<10){
d="0"+dd;
}else{
d=dd;
}
var currentTime=y+m+d;
state.items.push({
task,
isFinished:false,
details:"this is a new task",
setTime:currentTime
})
}
}
export default new Vuex.Store({
state,
getters,
mutations
})
这一段代码,我们会把输入的值作为一项新建任务的task值保存,并渲染到sidebar上,当然,作为一项待办任务,只有名称是不够的,所以我们为它添加了默认的描述details和默认的完成时间setTime,setTime是当前日期。
看到commit相关的函数了吗?
OK,至此,这个todolist应用最基本的新建任务就完成了。
editor.vue
接着,我们完成编辑、删除、标记已完成任务的功能。编辑功能在editor.vue组件內操作,删除功能则在sidebar.vue里面完成即可。让我们先编辑sidebar.vue。
下面这一段代码我会一次性增加删除编辑和编辑已完成的功能,你可以先敲出来,思考一下原理,也可以跳过这一段,跟着后面的步骤一步步添加功能。
<template>
<div class="sidebar">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="text-left">
任务列表
<i class="glyphicon glyphicon-refresh pull-right" v-on:click="reList"></i>
</h4>
</div>
<div class="panel-body sidebar-context">
<div>
<div v-for="(item,index) in items" class="panel panel-default">
<div class="panel-heading">
<h4>
<input @click="itemCheck(index)" type="checkbox">
{{item.task}}
<i class="glyphicon glyphicon-remove pull-right" v-on:click="deleteTask(index)"></I>
<i class="glyphicon glyphicon-edit pull-right" v-on:click="clickTask(item)"></i>
</h4>
</div>
<div class="panel-body">
<p>
{{item.setTime}}<br>
{{item.details}}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'sidebar',
data() {
return {
}
},
computed:{
items(){
return this.$store.getters.items;
},
activeTask(){
return this.$store.getters.activeTask;
}
},
methods:{
deleteTask(index){
this.$store.commit('deleteTask',index);
},
itemCheck(index){
this.$store.commit('toggleCheck',index);
},
clickTask(item){
this.$store.commit('setActivetask',item);
},
reList(){
this.$store.commit('reList')
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
ul {
list-style: none;
}
.sidebar-context{
max-height: 8rem;
overflow: scroll;
}
i.glyphicon{
font-size: .275rem;
cursor: pointer;
margin-left: .25rem;
}
</style>
在这里,我一次性把编辑、删除、标记已完成任务的功能三个功能都加上了(偷懒...)我会一个个来解释。
1.删除
看名字能看的出来,我在一个删除图标(还是bootstrap啦~)上绑定了一个点击事件deleteTask,里面的内容是执行store.js里一个deleteTask的函数,同时传入一个index的参数。而在相关的store.js里,我们在mutations里添加deleteTask函数相关的代码:
deleteTask(state,index){
state.items.splice(index,1)
}
删除对应的数组数据。也就是删除指定index的相关数据。任务就搞定了。
2.编辑
这是一个难点。涉及到数据在兄弟组件之间的交换,我们仍然使用vuex。
先说说思路,我们点击sidebar组件里的编辑按钮(删除旁边的按钮),然后把这个任务设置为活动任务,然后显示在右边的编辑组件editor上。在editor上编辑完成后,点击完成按钮,修改的任务被更新到左边的任务列表sidebar上。
好了,看上面sidebar.vue的代码,我们先要完成设置活动任务(activeTask):
computed里面需要设置方法:
activeTask(){
return this.$store.getters.activeTask;
}
然后在编辑按钮上绑定点击事件,把当前的任务设置为活动任务:
clickTask(item){
this.$store.commit('setActivetask',item);
}
sidebar.vue里的内容就编辑完了。接着我们需要在components文件夹创建一个组件editor.vue(忘记前面有没有创建了...),开始写这一段的代码:
这里需要提前声明的是,下面这一段的代码是我最没把握的代码,因为我不确定这一段是不是有效率的,但是它肯定是能跑起来的。
<template>
<div class="sidebar">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="text-left">任务编辑</h3>
</div>
<div class="panel-body">
<p>任务名称</p>
<input type="text" class="form-control task-input" placeholder="任务名称" v-bind:value="task" v-on:input="saveTask">
<p>任务详情</p>
<textarea class="form-control details-input" rows="3" placeholder="任务详情" v-bind:value="details" v-on:input="saveDetails"></textarea>
<p>任务期限</p>
<input type="text" class="form-control settime-input" placeholder="格式:20170606" v-bind:value="setTime" v-on:input="saveSettime">
<h3><i class="glyphicon glyphicon-ok pull-right" v-on:click="save"></i></h3>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'editor',
data() {
return {
}
},
computed:{
items(){
return this.$store.getters.items;
},
task(){
this.taskInput=this.$store.getters.activeTask.task;
return this.$store.getters.activeTask.task;
},
details(){
this.detailsInput=this.$store.getters.activeTask.details;
return this.$store.getters.activeTask.details;
},
setTime(){
this.settimeInput=this.$store.getters.activeTask.setTime;
return this.$store.getters.activeTask.setTime;
}
},
methods:{
saveTask(e){
this.taskInput=e.target.value;
},
saveDetails(e){
this.detailsInput=e.target.value;
},
saveSettime(e){
this.settimeInput=e.target.value;
},
save(){
this.$store.commit('editTask',this.taskInput);
this.$store.commit('editDetails',this.detailsInput);
this.$store.commit('editSettime',this.settimeInput);
this.$store.commit('clearAll');
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
input,textarea{
margin-bottom: .3rem;
}
i{
cursor: pointer;
margin-right: .2rem;
}
</style>
说明如下:
1.computed里的方法分别对应的是items任务数组(包含所有建立的任务数据)、task任务名称数据、details任务描述数据、setTime任务时间数据。这几个方法,除了第一个,其他的功能都是在对应的input表单显示活动任务(activeTask)的值。
2.methods里的方法,前三个都是在对应的input输入新值时触发事件,它会把新输入的值分别保存在一个地方。(比如task值就会保存在this.taskInput,taskInput是类名为task-input的input,其他两个以此类推)第四个save(),会把前面保存的三个值赋给活动任务。这也是我不敢确定的地方,因为代码这样写,有多少个不同的值就要用多少个函数,很不环保。
这些commit里的函数都放在store.js的mutations里,下面再说。
以上就把editor.vue的代码编辑完了,接着编辑store.js。
我们添加一下内容:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state={
items:[],
activeTask:{}
}
const getters={
items:state=>state.items,
activeTask:state=>state.activeTask
}
const mutations={
addTask(state,task){
var myDate=new Date();
var y=myDate.getFullYear();
var m,
mm=myDate.getMonth()+1;
if(mm<10){
m="0"+mm;
}else{
m=mm;
}
var d,
dd=myDate.getDate();
if(dd<10){
d="0"+dd;
}else{
d=dd;
}
var currentTime=y+m+d;
state.items.push({
task,
isFinished:false,
details:"this is a new task",
setTime:currentTime
})
},
deleteTask(state,index){
state.items.splice(index,1)
},
setActivetask(state,item){
state.activeTask=item
},
editTask(state,task){
state.activeTask.task=task;
for(let i in state.items){
if(i==state.activeTask){
i.task=task;
}
}
},
editDetails(state,details){
state.activeTask.details=details;
for(let i in state.items){
if(i==state.activeTask){
i.details=details;
}
}
},
editSettime(state,settime){
state.activeTask.setTime=settime;
for(let i in state.items){
if(i==state.activeTask){
i.setTime=settime;
}
}
}
}
export default new Vuex.Store({
state,
getters,
mutations
})
ok,主要是在state和getters里添加了活动任务,mutations里添加了前面提到的相关函数,对比一下看看有没有遗漏。
到这里,这个todolist最核心的任务就完成了。但是我们还需要一些细节:
1.修改任务后,把右边的活动任务清空,回复初始状态。
2.修改任务的完成时间后,需要知道哪些任务更加紧急,需要让任务列表重新排序。
3.修改任务完成状态。
我们来依次完成:
1.这个比较简单,我们只要修改完后点击完成按钮时触发一个设置活动任务(activeTask)为空的方法就好了。
在editor.vue组件的save()方法添加:
this.$store.commit('clearAll');
在store.js的mutations添加:
clearAll(state){
state.activeTask={};
},
done!
2.我们在左边的任务列表上放一个刷新按钮,点击时,会根据任务数据的setTime属性重新排序,时间近的在前面,时间远的在后面。
sidebar.vue的methods添加:
reList(){
this.$store.commit('reList')
}
在store.js的mutations添加:
reList(state){
function compare(propertyName){
return function(obj1,obj2){
var value1=obj1[propertyName];
var value2=obj2[propertyName];
if(value2<value1){
return 1;
}else if(value2>value1){
return -1;
}else{
return 0;
}
}
}
state.items.sort(compare('setTime'));
}
这里用到了一个基础的比较大小重新排序的功能。done!
3.这个也不复杂,我们在任务列表组件sidebar.vue组件的多选框上添加点击事件,methods里添加:
itemCheck(index){
this.$store.commit('toggleCheck',index);
},
在store.js的mutations添加:
toggleCheck(state,index){
state.items[index].isFinished=!state.items[index].isFinished
},
因为在创建任务时,我们会添加一个isFinished的属性,默认为false,通过以上方法,我们就可以切换isFinished的值,也就是切换任务的是否完成状态。搞定。
到这里,这个todo-list基本就算完成了。
接下来还有一些需要调整的,首先是移动端的适配,我们要先打开根目录的index.html,添加一下内容: