最近在研究vue的相關知識,最好的學習方法莫過於本身開發一個SPA,這樣帶着問題來學習,進步天然飛速。因而邊查邊寫差很少花了2周寫完了一個todo-list,功能不夠完備,可是麻雀雖小,卻也是五臟俱全,基本功能是能夠知足的了。話很少說,直接來看項目吧。
以上。css
接下來就是代碼分析了。html
這一個步驟沒什麼好說的,網上教程一大堆,隨便找一個照着走就行了。
完成後,你應該有一個項目的文件夾,裏面應該有這幾個文件:
README.md、build、config、index.html、package.json、src、static
嗯,就這樣。vue
先安裝依賴,命令行到對應根目錄文件夾執行以下命令(推薦VS code,自帶命令行輸入,方便!)node
npm install
稍等片刻完成(若是太慢,推動啊淘寶鏡像的cnpm安裝)
安裝好以後,繼續安裝:jquery
npm install vuex vue-router bootstrap --save
安裝完成後,須要配置如下文件,確保可以使用。
打開:xx(項目文件夾)-src-main.js
以下:webpack
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=>h(App) //ES6語法 })
這裏配置了vue-router和bootstrap,項目中可使用了,接着咱們還須要配置vuex和jQuery。git
首先在根目錄建立一個文件夾,命名爲vuex,在裏面建立一個store.js文件,
配置以下:github
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。web
npm install jquery --save
打開xx-build-webpack-base.conf.js,在module.exports裏面添加以下代碼:vue-router
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任務編輯
我會一個個介紹功能。
在首頁裏,咱們會用bootstrap寫一個導航,經過vue-router的路由導航到不一樣的應用。
代碼以下:
<template> <div 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看看代碼是怎麼寫的。
配置完後,咱們的主頁面上只有一個導航。接着咱們配置主頁。
咱們在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>
代碼不復雜,就不解釋了。接下來就是重頭戲了。
這是這個應用的主組件,在這個組件裏還會包含兩個子組件: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組件內操做,刪除功能則在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,添加一下內容:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name = "viewport" content = "width = device-width, initial-scale = 1.0, maximum-scale = 1.0, user-scalable = 0" /> <title>todolist</title> </head> <body> <div id="app"></div> <!-- 下面是我添加的 --> <script> let html = document.documentElement; window.rem = html.getBoundingClientRect().width / 25 ; html.style.fontSize = window.rem + 'px'; </script> </body> </html>
添加meta和一段JS完成bootstrap的適應和rem方法自適應大小。
第二是localstorage實現存儲。畢竟是一個todolist應用,每次打開都要從新建任務怎麼行?
這裏我用了一個vue的localstorage插件:地址
npm安裝插件:
npm install vue-localstorage --save
store.js配置:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); import VueLocalStorage from 'vue-localstorage' Vue.use(VueLocalStorage) const STORAGE_KEY='myapp-todolist' const state={ items:JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '[]'), 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) }, toggleCheck(state,index){ state.items[index].isFinished=!state.items[index].isFinished }, 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; } } }, clearAll(state){ state.activeTask={}; }, 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')); } } const localStoragePlugin = store=>{ store.subscribe((mutation,{items})=>{ window.localStorage.setItem(STORAGE_KEY,JSON.stringify(items)) }) } export default new Vuex.Store({ state, getters, mutations, plugins:[localStoragePlugin] })
要改的解放並很少,主要是把任務數據的數組轉換成localstorage的格式。
完成了!撒花~~
這個todo-list實在是簡陋不堪,可是基礎很差的我也差很少花了2個星期才寫完,在寫這個應用的時候我是抱着誠懇的態度的,因此也確實花了不少的心血,包括學習命令行,npm、搭建項目腳手架、查找資料,中間的磨練和挫折也沒必要多說,一切的辛苦都在看着這個應用可以運行的那一刻煙消雲散了。
寫了一篇這麼長的,我儘可能用我認爲最明白清楚的語言來表達,中間確定有不少錯誤和疏漏,歡迎大神拍磚、指點。寫這篇blog的目的也就是從新梳理一下代碼的思路,我是抱着學習的態度來寫的。中間的代碼有不少重複的地方,在大神看來,無疑是有着佔篇幅的嫌疑,但也是但願能更加清楚地表述,方便新人理解。我也是剛入此門,十分能理解代碼不詳細帶來的困惑。
在起名字的時候,我沒有直接說這是一個todo-list,而說是一個應用盒子,是由於我想把它做爲一個個人表明項目,裏面會集成許多的應用,todolist、音樂、計算器等等,一方面練手,一方面攢一個拿得出手的項目,方便之後求職。
說了這麼多廢話,下面是乾貨:
todo-list源碼(個人github,歡迎star,fork)
Vue2.0 新手徹底填坑攻略(若是你還沒學過Vue,先看這個入門)
vue2.0 構建單頁應用最佳實戰(bootstrap+vue架構一個todolist)
Vue.js+Vuex:一個簡單的記事本(介紹vuex項目實戰最清楚的教程)
固然,官方文檔也是很是重要的,看着教程你能夠寫出須要的代碼,可是隻有看着文檔,你才能知道爲何要這樣寫。
vue官方文檔
vuex官方文檔
that's all~