vue.js移動端app實戰2:首頁
什麼是vue-cli?
官方的解釋是:A simple CLI for scaffolding Vue.js projects,
簡單翻譯一下,就是: 用簡單的命令行來生成vue.js項目腳手架。
<!-- 全局安裝vue-cli -->
npm install -g vue-cli
vue-cli預先定義了5個模板,根據你使用的打包工具的不同選擇不同的模板,通常我們用的都是第一個webpack模板。每個模板都預先寫好了很多依賴和基礎配置,可以直接在此基礎上進行開發,非常方便。
webpack
webpack-simple
browserify
browserify-simple
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 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,則 :stylex="{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>
寫到這裡,基本配置以及首頁就差不多完成了。
更多優質內容推薦:
有錢任性,某公司豪擲500萬幫助20左右年輕人找工作,起因是做善良的人:
http://www.ujiuye.com/zt/jyfc/?wt.bd=zdy35845tt
學IT,用周末給自己加薪!
http://www.ujiuye.com/zt/zmb/?wt.bd=zdy35845tt
IT職業教育:http://xue.ujiuye.com/


※dubbo源碼分析:超時原理以及應用場景
※分散式web架構中對session同步的常用處理方法及優缺點
※使用VS Code開發.Net Core 2.0 應用程序
※同一個表單,傳遞到不同的處理器中
TAG:IT優就業 |
※中國移動試行eSIM,支持iPhone和Apple Watch
※中國移動試行eSIM 支持iPhone和Apple Watch
※中國移動eSIM一號雙終端免費體驗:支持iPhone及Apple Watch
※《Muv-Luv Alternative:強襲邊境》移動版跳票
※Facebook公布WhatsApp登陸印度第二大移動系統KaiOS
※mophie powerstation USB-C XXL移動電源開箱圖賞
※Apple Watch&iPhone雙充移動電源
※希捷 Backup Plus Ultra Slim 圖賞:只有 9.6 毫米厚的移動硬碟
※TalkingData-2018年6月移動遊戲Benchmark
※Computex 2019:ACME Portal展示多款三屏移動工作站
※真香!中國移動eSIM支持Apple Watch了!
※中國移動eSIM一號雙終端免費體驗:終於支持iPhone及Apple Watch
※Pixel 3a 不支持 Google 的移動 VR 平台 Daydream
※深刻變革!移動端應用迎來WEB時代Google/Microsoft/Apple已全部支持PWA技術
※rpg maker vx ace教學第1期:場景設計、場所移動和特定事件
※Altova跨平台移動應用框架MobileTogether發新版
※榮耀Magic 2首發Link Turbo:實現Wi-Fi/移動網路聚合通訊
※Powerstation,mophie 出品的炫彩移動電源
※Open Garden首席執行官Paul Hainsworth:讓普通消費者從移動網路傳輸中受益
※Valve已推出移動端Steam聊天軟體Steam Chat