1. 程式人生 > >vue.js移動端app

vue.js移動端app

什麼是vue-cli?

官方的解釋是:A simple CLI for scaffolding Vue.js projects,
簡單翻譯一下,就是: 用簡單的命令列來生成vue.js專案腳手架。

<!-- 全域性安裝vue-cli -->

npm install -g vue-cli

vue-cli預先定義了5個模板,根據你使用的打包工具的不同選擇不同的模板,通常我們用的都是第一個webpack模板。每個模板都預先寫好了很多依賴和基礎配置,可以直接在此基礎上進行開發,非常方便。

  1. webpack

  2. webpack-simple

  3. browserify

  4. browserify-simple

  5. simple

安裝vue-cli後,就可以下載我們要的模板了。

用法:vue init template-name project-name

這時會有很多提示,詢問你要安裝vue2還是vue1,是否要安裝mocha,eslint等東西,根據你自己的需要安裝即可。安裝好後,會提示你怎麼開始,根據提示輸入命令就可以啟動了。



為了適配各種螢幕,首先把淘寶的flexible引進來,在main.js裡面

import './base/js/base.js'

其次,把樣式重置也引入進來, App.vue的style標籤裡面

@import './base/css/normalize.scss';
這裡我有一個問題沒有解決,就是開發中我用scss來寫mixin,由於很多頁面都會用到,所以我希望在App.vue裡面引入mixin.scss,好讓所以的vue元件都能用,但實際上這樣並不起作用;後來又嘗試寫到main.js裡面,不過也沒用。目前只能是哪個元件需要用到mixin,就import進來,不過確實有點麻煩。

接著,引入字型圖示, 在App.vue的style標籤裡面

@import url('//at.alicdn.com/t/font_nfzwlroyg2vuz0k9.css')

基本配置完成後,接下來分析一下路由怎麼寫:

為了達到上圖的效果,我們需要2個基本的元件,一個是購物車,一個是home頁面。購物車比較簡單,就一個頁面,主要看Home頁面。

home元件又分成4個元件,一個是底部的導航,還有三個是上面的首頁,搜尋和個人中心。
也即是說為了達到圖片上的效果,目前我們需要總共6個元件。

分別是:

1. 購物車 
2. home

    2.1 首頁
    2.2 搜尋
    2.3 個人中心
    2.4 底部導航

因此,新建6個.vue檔案。為了儘快把路由編寫出來,我喜歡隨便填充一點內容(主要是為了知道在哪個頁面),比如:

<div>首頁首頁首頁</div>
<div>首頁首頁首頁</div> <div>首頁首頁首頁</div> <div>首頁首頁首頁</div> <div>首頁首頁首頁</div>

接著編寫路由:

使用路由首先要引入Vue-router並use,並將配置好路由的vue-router例項掛載到new出來的Vue例項上,不過vue-cli已將幫我們配置好了,只需要在其基礎上繼續開發就行了。

找到編寫路由的index.js檔案:
首先引入6個元件:

import xxx from 'xxx/xxx'

import car from '@/components/car'

你可能經常看到@這樣的東西,這其實是webpack配置的別名。開啟build資料夾下面的webpack.base.conf.js。

你也可以自己再加別名,比如

alias: {
    ~': resolve('src/component')
}

當webpack在import或者require語句中遇到~時,就會將其解析為對應的路徑。使用別名可以使得路徑更為清晰,也可以減少一些重複的程式碼。

對比一下:

import car from '../../component/car.vue'
import car from '~/car.vue'

不過,使用別名的壞處就是,編輯器沒法智慧的提示檔案所在路徑了。

當頁面多了以後,打包後的檔案會變得很大,大於1M也是很正常的。因此,首屏開啟也會變慢,畢竟一下子要載入以M為單位的js檔案。想要減少檔案的大小,可以把Vue等公共庫提取到vendor,從而利用瀏覽器的快取效果。同時,也可以讓路由按需載入,當需要用到的時候,才去載入對應的元件,利用webpakc的非同步載入可以解決:

const Car = r => require.ensure([], () => r(require('@/components/car')), 'car')

也可以像下面這麼寫:

const Car = resolve => require(['@/components/car'], resolve)

Vue2.3+的版本提供了更高階的非同步元件寫法,想了解的可以去官網看一下,這裡用的還是舊的用法。

對著上面的結構圖,路由的結構其實大概已經瞭解了

{
    path:'',
    redirect:"/home"
},  
{
    path:'/home', component:Main, children:[ { path:'', redirect:"index" }, { path:'index', component:Index }, { path:'search', component:Search }, { path:'vip', component:Vip } ] }, { path:'/car', component:Car, }

這裡我們用了2個重定向,當路由為空時,會重定向到/home,而當home為空時,又會重定向到index,所以你只需要在瀏覽器輸入http://localhost:8088 ,就會自動跳轉到home下的首頁

開始編寫home元件:

可以發現home元件由上下2部分組成,底部是固定的導航,上面的部分是動態切換的頁面。因此home元件的template寫出來應該是這樣的:



<template>
   <div>
       <router-view></router-view> <foot-nav></foot-nav> </div> </template> <script> import footNav from '../components/foot-nav.vue' export default { components:{ footNav } } </script>

foot導航元件相對來說也比較簡單,無非就是一個固定在底部的列表,每個列表都寫好了對應的路由,點選每一個就會切換對應的頁面。如果路由層級比較深,寫起來可能會很長,如to="test1/test2/test3" ,考慮在配置路由的js中,給每個路由新增name。這樣,在router-link中就只需要傳遞對應的name就可以了。

<template>
    <div class="foot-nav-containner"> <ul class="bottom-nav"> <router-link tag="li" :to='{name:"index"}' class="bottom-nav__li iconfont icon-shouye bottom-nav__li--home"></router-link> <router-link tag="li" :to='{name:"search"}' class="bottom-nav__li iconfont icon-ss bottom-nav__li--search"></router-link> <router-link tag="li" :to='{name:"car"}' class="bottom-nav__li iconfont icon-shoppingcart bottom-nav__li--car"></router-link> <router-link tag="li" :to='{name:"vip"}' class="bottom-nav__li iconfont icon-gerenzhongxinxia bottom-nav__li--vip"></router-link> </ul> </div> </template>

index元件

index元件由輪播圖以及三個排行榜組成。3個排行榜除了資料和名字不同個以外,其他的都一樣。所以,我們總共需要2個元件就可以。大致如下:

<template>
    <div id="container"> <輪播圖></輪播圖> <排行榜 :型別=1></排行榜> <排行榜 :型別=2></排行榜> <排行榜 :型別=3></排行榜> </div> </template>

先來看輪播圖:

輪播圖我們用的是vue-awesome-swiper外掛,使用方式同swiper基本一致,更多資訊請github搜尋。

在main.js中引入外掛並使用:

import VueAwesomeSwiper from 'vue-awesome-swiper'

Vue.use(VueAwesomeSwiper);

由於可能不止一個頁面會用到輪播圖,所以我們可以把輪播圖提取出來。

新建一個swiper.vue檔案
<template>
    <swiper  class="swiper-box"> <swiper-slide class="swiper-item"></swiper-slide> <swiper-slide class="swiper-item"></swiper-slide> <swiper-slide class="swiper-item"></swiper-slide> <swiper-slide class="swiper-item"></swiper-slide> <div class="swiper-pagination" slot="pagination"></div> </swiper> </template>
<script>
export default {
    data(){
        return{
             swiperOption: {
                pagination: '.swiper-pagination', direction: 'horizontal', } } }, }; </script>
<style lang="scss" scoped>
@import '../base/css/base.scss'; .swiper-box { width: 100%; height: 100%; margin: 0 auto; .swiper-item { height: 5rem; background: url() no-repeat center/cover; /* 使用Mixin來處理2x,3x圖 */ &:nth-of-type(1){ @include dpr-img("../assets/","vue"); } &:nth-of-type(2){ @include dpr-img("../assets/","swiper1"); } &:nth-of-type(3){ @include dpr-img("../assets/","swiper2"); } &:nth-of-type(4){ @include dpr-img("../assets/","swiper3"); } } } </style>

樣式方面就忽略了,要作為一個元件,上面的寫法還存在問題,主要體現在:

問題1:輪播圖的配置引數寫在元件data裡面。

假如有2個頁面需要用到這個元件,1個元件需要自動輪播,一個組價不需要自動輪播,這樣的話,你可能會考慮對某個頁面做單獨處理,比如做一個if判斷之類的。但是,假如有很多頁面需要輪播圖,而且不同的地方很多,比如你想對a頁面輪播圖滑動到下一張後alert(1),對b頁面alert(2)等等等等,那該如何做呢?總不能一個一個判斷吧,所以正確的方法應該是把配置引數通過prop接受父元件傳遞過來的引數

<script>
export default {
    data(){
        return{
            
        }       
    },
    props:{
        swiperOption:{ type:Object } } }; </script>

在父元件裡面import元件並傳遞引數

<template>
    <div id="container"> <swiperComponent :swiperOption="swiperOption"></swiperComponent> </div> </template> <script> import swiperComponent from './swiper.vue' export default { data() { return { swiperOption: { pagination: '.swiper-pagination', direction: 'horizontal', }, } }, components:{ swiperComponent, } } </script>

如此一來,當哪個頁面需要用到輪播圖,就在哪個頁面寫好引數,並通過v-bind傳遞需要的引數。

問題2:輪播圖數量固定。

不可能每個頁面都是4個輪播圖,而應該某個引數(一個數組)的長度來決定。父元件在通過ajax請求後獲得該陣列,並通過prop傳遞給swiper元件。

<template>
    <swiper  class="swiper-box"> <swiper-slide class="swiper-item" v-for="(v,i) in swiperList "></swiper-slide> <div class="swiper-pagination" slot="pagination"></div> </swiper> </template> props:{ swiperList:{ type:Array, default:[] } }
假如你是用的img標籤,則 :src="v.img";

假如你是用background,則 :style="{backgroundImage:v.img}"

這樣,我們的swiper元件基本已經解耦了。

排行榜

新建一個電影排行榜組film.vue檔案
排行榜元件結構如下:
(樣式基本人人會寫,不再多說)

<template>
     <div class="film"> <h3 class="film__type"> <span>{{type}}</span> <router-link :to='{path:"/classify/"+url}'><span class="more"><em>更多</em><em class="iconfont icon-more"></em></span></router-link> </h3> <div class="film__list" :ref="el" :data-request="url"> <ul class="clearfix"> <router-link tag="li" v-for="(v,i) in array" :key="v.id" :to='{path:"/film-detail/"+v.id}'> <div class="film__list__img"><img v-lazy="v.images.small" alt=""></div> <div class="film__list__detail"> <h4 class="film__list__title">{{v.title}}</h4> <p class="film__list__rank">評分:{{v.rating.average}}</p> <p class="film__list__rank"> <span :class="{rankColor:v.rating.average>((i-0.5)*2)}" class="iconfont icon-rank" v-for="i in 5"></span> </p> </div> </router-link> </ul> <Loading v-show="!array[0]" class="loading-center"></Loading> </div> </div> </template>

為了獲取真實的資料,我們需要:

  • 豆瓣的api

很多頁面都會用到豆瓣的api地址,所以可以把相同的部分提取到一個檔案

找一個地方,新建檔案api.js

const api="https://api.douban.com/v2/movie/";
export default api;
  • 傳送請求

選一個你熟悉的ajax庫,這裡用的是axios

在main.js裡面引入axions庫並use:

import axios from 'axios'
Vue.use(axios);
Vue.prototype.$ajax=axios;

我們把他掛載到vue的原型上,以便可以在所有地方通過this.$ajax上使用,不過你也可以不這麼做,隨個人喜歡。

  • 解決豆瓣api跨域問題

如果你就這樣發請求到豆瓣,是獲取不到資料到,會提示你跨域,這也是前後端分離專案常見的問題。解決的方法有2個

1個是通過webpack的dev-server配置proxy, 不過有一個問題,就是隻在開發階段可以使用,也即是當你開發完成後,npm run build生成打包後的檔案,想再去伺服器看效果就不行了。

2 是我現在用的,通過設定chrome來跨域,具體設定方法請參考
這篇文章。設定完後,以後所以的專案都可以使用,無需對每個專案再單獨配置proxy代理了。

搞定前提條件後,接下來的無非是在create或者mounted生命週期傳送一個請求,請求成功後把資料賦值給v-for繫結的data了。不過還有問題,就是滾動條的問題。假如你不做任何處理,那麼當獲取資料成功後,會渲染20個li(根據後臺返回的長度)。很明顯,ul的長度肯定超過了整個螢幕的寬度,所以X軸會一直拉長,底下會出現滾動條,你可以拖動到螢幕之外。但是,我們希望的是螢幕的寬度保持不變,列表超過螢幕時隱藏元素,由我們手動去滑動。

那麼ul的長度為多少了?假如你隨便寫一個很長的長度,那麼滑動到後面就全是空白了。如果太短了,就滑動不了。因此ul長度由後臺返回的數量*(li的寬頻+li的padding-right,這裡的加法取決於你的LI的css結構)決定。因此,獲取完資料後,在nextTick時需要給UL的寬度重新賦值。為了方便獲取元素的樣式,可以在util.js寫一個方法

export default function(el,style){ return parseInt(window.getComputedStyle(el, false)[style]) }

在methods裡寫一方法計算ul應有的寬度,el即ul元素,可以通過繫結ref來獲取。

freshWidth(el){
    var width=getStyle(el.children[0],"width");
    var padding=getStyle(el.children[0],"padding-right"); el.style.width=el.children.length*(width+padding+2)+"px"; }

為了能夠拖動,有2種解決方式:

第一種比較簡單,就是給Ul的外層div1設定固定長寬overflow:auto,當ul超出時,div就會出現滾動條,這樣就以拖動了。不過會出現滾動條,很礙眼。因此,給div1外面再套一層div2,並設定div2的高度低於div1,比如最為層的div2高度80px,div1高度100px,並設定div2的overflow:hidden。如此一來,就可以隱藏滾動條,並且可以拖動了。

第二種是使用better-scroll庫, 使用better-scroll需要2層的結構

<div id="containner">
    <ul id="scroller"></ul> </div> container層為初始化的元素,需要設定overflow:hidden;初始化後,第一個子元素ul就可滾動了。 引入better-scroll 1.在mounted生命週期階段new BScroll({})並傳引數初始化; 2.傳送請求獲取資料, <!-- 初始化的元素以及請求的引數我們也都通過prop接受父元件傳遞過來 --> 3.將後臺資料賦值給array陣列 4.呼叫nextTick方法並在回撥函式中重新計算ul的長度後,refresh scroller
<!-- 父元件 -->
 <filmComponent 
    :el="filmType.topFilmData.scroller" :url="filmType.topFilmData.url" :type="filmType.topFilmData.type"> </filmComponent> <script> export default { data() { return { filmType:{ topFilmData:{ scroller:"scroll-top250", url:"top250", type:"top250" } } } }, components:{ filmComponent } } </script>
<!-- 子元件 -->
   <script>
import BScroll from 'better-scroll' import getStyle from '../base/js/util.js' import Loading from './loading.vue' import api from "../base/js/api.js" export default { data () { return { scroller:null,<!-- 存放scroll元素 --> array:[],<!-- 存放後臺返回的陣列 --> }; }, components:{ Loading }, props:["el","url","type"], mounted(){ const el = this.$refs[this.el]; this.scroller=this.initScroll(el); const {request}=el.dataset; this.$ajax.get(`${api}${request}?start=${Math.floor(Math.random()*10)}`) .then((res)=>{ this.array=res.data.subjects; this.$nextTick(()=>{ this.freshWidth(el.children[0]); this.scroller.refresh(); }) }) }, methods:{ initScroll(el){ return new BScroll(el,{ click:true, probeType:3, scrollX:true, scrollY:false }) }, freshWidth(el){ var width=getStyle(el.children[0],"width"); var padding=getStyle(el.children[0],"padding-right"); el.style.width=el.children.length*(width+padding+2)+"px"; }, } }; </script>

我們總共有3個排行榜,那麼只需要在父元件再寫2個標籤並在data中寫好引數,傳給子元件就可以了

<filmComponent 
    :el="filmType.topFilmData.scroller" 
    :url="filmType.topFilmData.url" :type="filmType.topFilmData.type"> </filmComponent> <filmComponent :el="filmType.topFilmData.scroller" :url="filmType.topFilmData.url" :type="filmType.topFilmData.type"> </filmComponent>

這樣基本就就完成了,再稍微優化下,我們有3個榜單,假設每個榜單都載入20個數據,那麼獲取完資料後就會有3*20總共有60張圖片的請求。常用的優化方式就是等圖片進入可視區之後再去載入圖片,在之前給一張loading或者其他圖片作為站位。

使用vue-lazyload來達到這個效果,相關配置引數請自行github搜尋vue-lazyload

在main.js裡面

import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload, {
   preLoad: 1.3,
   loading: require('@/assets/head.jpg'),<!-- 站點陣圖 -->
  attempt: 1
})

使用的方法非常簡單:

原本我們是這麼寫的

<div><img :src="v.images.small" alt=""></div>

改成下面這樣就行了

<img v-lazy="v.images.small" alt=""></div>

寫到這裡,基本配置以及首頁就差不多完成了,相關程式碼已長傳到 https://github.com/linrunzheng/vueApp