利用vue-cli配合vue-router搭建一個完整的spa流程(一)

前言
Ⅰ. demo所用vue-router的一些基本操做。vue-router中文文檔,快速瀏覽一遍便可 http://router.vuejs.org/zh-cn/
Ⅱ. 整個demo所用到的技術棧 vueJS(2.0) vue-cli vue-resource es6
Ⅲ. 所需構建工具 nodeJS Gitjavascript

第一步:安裝

OK,正題開始,首先保證nodeJS,Git,webpack已安裝完畢。打開項目文件夾,安裝vue-cli。css


全局安裝 vue-cli
$ npm install --global vue-clihtml

建立一個基於webpack的模板
vue init webpack my-project
建立過程當中,vue-router爲必須,其餘語法檢測,單元測試等按需求安裝。
vue

建立完成後進入項目文件夾,安裝依賴
$ npm installjava

安裝到此結束,運行以下代碼,顯示爲圖片所示,則安裝成功。
$ npm run dev
node


第二步:項目文件及運行流程

Ⅰ: 項目文件

打開已經建立好的模板
webpack

如圖所示,只會用到,src,static,index.html這三個文件。首先解釋一下三個文件的做用:
Ⅰ: src 存放路由JS,模板.vue文件,入口JS,以及一個入口.vue文件
Ⅱ: static 存放靜態文件
Ⅲ: index 入口html文件css3

這裏解釋一下xxx.vue文件是什麼,官網叫其爲單文件組件,經過webpack源碼轉換,會所有轉換爲對應的文件。
說白了就是一個包裹,裏邊含有三部分 一部分模板template,一部分樣式style,一部分JSjavascript,他們封裝在一塊兒。
以下圖所示:
es6

Ⅱ: 運行流程

寫起來比較麻煩,作了一張圖,直截了當。
web

第三步:搭建基本路由框架

項目文件明瞭以後,咱們開始搭建一個簡單的SPA路由構架:
Ⅰ: 頁面中有倆個及倆個以上的分類
Ⅱ: 每一個分類中能夠點擊進入到詳情頁面
Ⅲ: URL輸入錯誤後展現404頁面
Ⅳ: 在頁面中刷新,根據URL從新獲取數據,渲染頁面

根據基礎框架建立對應的文件。

文件詳解:
Ⅰ: src中components文件夾裏新建三個xxx.vue文件,
①error.vue 此爲404頁面
②showone.vue 此爲第一個分類頁面
③showtwo.vue 此爲第二個分類頁面

Ⅱ: src中zjapp.vue這是路由入口文件

Ⅲ: static中img爲詳情頁面大圖,thumbnail爲分類頁面縮略圖

Ⅳ: 倆個JSON文件,分別表明分類一和分類二的數據來源

Ⅴ: bootstrap.css 樣式CSS

到此路由的基本框架搭建完成,後面開始代碼的填充。

第四步:主頁面代碼編寫

Ⅰ: index.html

做爲頁面入口文件,先引入Bootstrap.CSS,若是是本地文件放在static文件夾裏。可使用CDN或者npm安裝。
爲了方便後面閱讀將id="app"更改成id="index"。固然,也能夠不更改,main.js中有多個爲app的名字,避免混淆。

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>spa-vue-demo</title> <link rel="stylesheet" href="static/bootstrap.min.css" /> </head> <body> <div id="index"></div> <!-- built files will be auto injected --> </body> </html>

Ⅱ: main.js

main.js爲入口JS文件,Vue的實例在這裏書寫。el 掛載在 index.html 中 id="index" 的標籤上。

import Vue from 'vue' import App from './zjapp' import router from './router' Vue.config.productionTip = false new Vue({ el: '#index', router, template: '<App/>', components: { App } })

Vue 開始渲染時,加載 components: { App } 組件替換生成在 id="index" 內的 <App></App> 標籤,那麼{ App }來自哪裏呢?

答案在 import App from './zjapp' 這裏是ES6語法,引入zjapp.vue模塊中暴露出來的接口,後綴能夠不寫。

Vue 實例中的 router 屬性也是ES6中對象的字面量寫法,等於router:router。同理 import router from './router' 這裏引入router。

由於,router中index.js暴露接口時沒有署名,這裏也能夠改一個名字,好比:

import Vue from 'vue' import App from './zjapp' //修更名字同樣能夠。 import changeES6 from './router' Vue.config.productionTip = false new Vue({ el: '#index', //修改在這裏 router:changeES6, template: '<App/>', components: { App } })

最後,可能有人會問 Vue.config.productionTip = false 是作什麼用的,其實這裏是關閉了生產模式即部署到服務器後給出的提示。

Ⅲ: zjapp.vue

這個文件是 Vue 一開始渲染組件時的文件,首先貼出所有代碼,不少,可是會所有講解做用,含義。

<template> <div class="container"> <div class="row"> <div id="index" class="col-xs-12 col-lg-12 col-md-12" style="padding: 0;"> <transition name='animate' appear mode='out-in'> <router-view v-bind:router-data="allData" v-bind:key="change"></router-view> </transition> <transition name='btn' appear mode='out-in'> <div class="app-btn" v-show="allData.mainShow"> <button class="btn btn-success app-btn-back" v-show="back==0?false:true" v-bind:key="back" v-on:click="dosom('back')">上一頁</button> <button class="btn btn-success app-btn-next" v-show="next==0?false:true" v-bind:key="next" v-on:click="dosom('next')">下一頁</button> </div> </transition> <div class="app-loading" v-show="loading"> <img src="../static/loading/loading.gif" style="margin:0 auto;display: block;" alt="loading" /> </div> </div> </div> </div> </template> <script> import router from './router' export default{ data(){ return{ allData:{ showData:null, detailedData:{}, num:0, mainShow:true }, loading:false, change:true, back:0, next:1, } }, created(){ this.routePath(); }, watch:{ "$route"(to){ this.routePath(); } }, methods:{ buttonToggle(){ var nowNum=this.allData.num; this.back=nowNum; this.next=2-nowNum; }, dosom(str){ str=="next"?this.allData.num++:this.allData.num--; this.buttonToggle(); //當前user/當前頁面/當前頁面路由 router.push(this.$route.path.slice(0,8)+this.allData.num); }, routePath(){ if(this.$route.fullPath=="/"){ router.push("/user/0/0"); this.load(); } else if(this.$route.fullPath.length==9 || this.$route.fullPath.length==20){ this.load(); } else{ router.push("/user/error"); this.back=0; this.next=0; } }, load(){ var numData=null, listData=null; // /user/lisData/numData numData=this.$route.path.slice(8,9); listData=this.$route.path.slice(6,7); // 初始化 this.allData.num=numData; this.buttonToggle(); this.change=!this.change; if(this.$route.path.indexOf("con")>0){ //獲取list中第幾個 var typeData=this.$route.query.type; this.$nextTick(e=>{ this.$http.get("static/data-"+listData+".json").then(rea=>{ this.loading=true; setTimeout(e=>{ //vue-resource加載數據存在於data.body中 var listNum=rea.body.allData.slice(numData*6,numData*6+6); //詳細顯示頁面數據來源 this.allData.detailedData=listNum.slice(typeData,typeData+1)[0]; this.loading=false; },700); }); }); this.allData.mainShow=false; }else{ this.$nextTick(e=>{ this.loading=true; setTimeout(e=>{ this.$http.get("static/data-"+listData+".json").then(rea=>{ this.allData.showData=rea.body.allData.slice(numData*6,numData*6+6); this.loading=false; }); },700); }); this.allData.mainShow=true; } } } } </script> <style> /*切換中動畫*/ .animate-enter-active,.animate-leave-active{ transition: all 0.5s ease; } .animate-enter{ transform: translateX(-80px); opacity: 0; } .animate-leave-active{ transform: translateX(80px); opacity: 0; } /*底部按鈕簡單動畫*/ .btn-enter-active,.btn-leave-active{ transition: all 1s ease; } .btn-enter{ opacity: 0; } .btn-leave-active{ opacity: 0; } /*back,next btn-class*/ .app-btn{ overflow: hidden; width: 140px; height: 34px; position: relative; margin-top: 15px; } /*back btn-class*/ .app-btn-back{ position: absolute; top: 0; left: 0; } /*next btn-class*/ .app-btn-next{ position: absolute; bottom: 0; right: 0; } /*loading*/ .app-loading{ background-color: tan; position: fixed; height: 100%; width: 100%; left: 0; top:0; } </style>

Ⅰ: HTML部分(即template)

大致分爲三部個分

第一部分:

<transition name='animate' appear mode='out-in'> <router-view v-bind:router-data="allData" v-bind:key="change"></router-view> </transition>

第一部分爲頁面中內容路由入口,其中:
v-bind:router-data="allData" 是對模板中傳輸數據用的
v-bind:key="change" 是頁面切換動畫綁定的變值,用來使頁面被複用時觸發切換動畫

第二部分:

<transition name='btn' appear mode='out-in'> <div class="app-btn" v-show="allData.mainShow"> <button class="btn btn-success app-btn-back" v-show="back==0?false:true" v-on:click="dosom('back')">上一頁</button> <button class="btn btn-success app-btn-next" v-show="next==0?false:true" v-on:click="dosom('next')">下一頁</button> </div> </transition>

第二部分爲頁面中上一頁,下一頁按鈕部分,其中:
v-show="allData.mainShow" 是控制倆個按鈕顯示,隱藏(詳情頁隱藏)
v-show="back==0?false:true" 是控制單個按鈕顯示,隱藏(最後一頁時,下一頁按鈕隱藏)
v-on:click="dosom('back')" 是綁定的點擊事件

第三部分:

<div class="app-loading" v-show="loading"> <img src="../static/loading/loading.gif" style="margin:0 auto;display: block;" alt="loading" /> </div>

第三部分爲頁面中內容更新數據時loading畫面,其中:
v-show="loading" 是用來顯示,隱藏 loading 動畫

好了,到此主要的HTML模塊已經佈局完畢,如今開始JS功能的開發

Ⅱ: javascript

JS這裏的總體流程:watch router.path的變化,從URL中讀取數據,重新獲取數據。(由於本地JSON文件,獲取JSON後對JSON進行剪切)

import router from './router' ES6語法,引入router模塊下暴露的接口,這裏引入router實例爲後續編寫編程式導航鋪墊。

data(){ return{  allData:{  showData:null,  detailedData:{},  num:0,  mainShow:true },  loading:false,  change:true,  back:0,  next:1, } }

showData 爲當前頁面渲染數據,即router-view被替換爲showone.vue模板中的數據來源,每次點擊下一頁等操做致使router.path變化時,此數據更新對應的子模板中(showone.vue)數據也更新。(後面講到路由頁面時會解釋)

detailedData 爲詳情頁面渲染數據,即router-view被替換爲showtwo.vue模板中的數據來源,同上。

num 這個是用來每次打開或者刷新頁面時讀取當前爲第幾頁的number,由於這個數值用了不少次,故將它放到了初始化函數裏

mainShow 控制倆個按鈕(下一頁,上一頁)整體顯示,隱藏

loading 控制loading動畫的顯示,隱藏

change 頁面複用時的Key值

back 返回按鈕的number,由於按鈕的判斷爲v-show="back==0?false:true"當爲0是隱藏

next 同上


以上爲這個demo中數據的含義,下面是方法的解釋,從methods開始提及:

buttonToggle(){
    var nowNum=this.allData.num; this.back=nowNum; this.next=2-nowNum; }

這是倆個按鈕的控制函數,由於JSON數據很少,一個分類中只有2頁數據,因此 this.next=2-nowNum; 最後一頁時隱藏。

dosom(str){
    str=="next"?this.allData.num++:this.allData.num--; this.buttonToggle(); // http://localhost:8080/#/user/0/1 // http://localhost:8080/#/user/0/this.allData.num router.push(this.$route.path.slice(0,8)+this.allData.num); }

這是按鈕點擊時觸發的方法,點擊後判斷是上一頁,仍是下一頁,由於會動態的隱藏按鈕因此不用關注++或者--的上下界。隨後進行url的更改(url更改後會觸發watch,watch中執行的函數爲 routePath() ,下一個說到)

routePath(){
    if(this.$route.fullPath=="/"){ router.push("/user/0/0"); this.load(); } else if(this.$route.fullPath.length==9 || this.$route.fullPath.length==20){ this.load(); } else{ router.push("/user/error"); this.back=0; this.next=0; } }

this.$route.fullPath 返回的是所有 url 字符串,這是當前url判斷函數:

當讀取到的url爲「/」時,此爲第一次打開頁面,跳轉到首頁也就是 http://localhost:8080/#/user/0/0 而後 執行load()方法,load() 方法下一個說到。

當讀取到this.$route.fullPath.length==9 || this.$route.fullPath.length==20,其實就是 this.$route.fullPath 爲 /user/x/x 的主頁面中,或者爲 /user/x/x/con?type=x 的詳情頁面中,此時直接進行 load() 方法更新數據便可

最後其餘任何 url 都被認爲是錯誤的 http 請求,返回404頁面,固然倆個翻頁按鈕隱藏。

load(){
    var numData=null, listData=null; // /user/lisData/numData listData=this.$route.path.slice(6,7); numData=this.$route.path.slice(8,9); // 初始化num值 this.allData.num=numData; this.buttonToggle(); // 頁面複用時Key值 this.change=!this.change; if(this.$route.path.indexOf("con")>0){ // 獲取list中第幾個 var typeData=this.$route.query.type; this.$nextTick(e=>{ this.$http.get("static/data-"+listData+".json").then(rea=>{ this.loading=true; setTimeout(e=>{ //vue-resource加載數據存在於data.body中 var listNum=rea.body.allData.slice(numData*6,numData*6+6); //詳細顯示頁面數據來源 this.allData.detailedData=listNum.slice(typeData,typeData+1)[0]; this.loading=false; },700); }); }); this.allData.mainShow=false; }else{ this.$nextTick(e=>{ this.loading=true; setTimeout(e=>{ this.$http.get("static/data-"+listData+".json").then(rea=>{ this.allData.showData=rea.body.allData.slice(numData*6,numData*6+6); this.loading=false; }); },700); }); this.allData.mainShow=true; } }

這個方法的做用是交互數據,demo 有倆個分類,首頁,和第一頁,因此對應的數據也是倆個JSON。定時器的做用是模擬數據請求延時。

以上就是 methods 方法裏所有函數,下面解釋一下Vue實例裏其餘的方法。

created(){ this.routePath(); }

這是一個生命週期鉤子表明Vue實例被建立好了,建立好以後進行url解析,這是初始化的步驟,第一次打開這個demo執行的函數。官方文檔:Vue生命週期

watch:{
    "$route"(to){ this.routePath(); } }

說到 watch 了這是監控url變化時觸發的函數,說白了就是執行 router.path("/user/x/x") 
以後Vue會檢測到變化,從而進行回調函數,這裏執行 routerPath() 分析 url 是屬於哪一個頁面從而進行數據更新。

好了,javascript的編寫到此結束,主要部分仍是在 routerPath() 這個函數,再經過 url 從新獲取數據。


style 部分就不說了,簡單的css3動畫

相關文章
相關標籤/搜索