最近快畢業了嗚嗚嗚,準備找工作,但是缺乏項目經驗,于是就在B站找相關的課程,學完之后便根據老師穩定的教導,以及自己穩定的心態,做了一個類似于蘑菇街的電商APP。(后端數據接口由老師提供,老師叫coderwhy,前端講得真的很不錯)。
蘑菇街作為中國最大女性購物社區,其APP的設計水平也毋庸置疑的
(1)準備工作
在閱讀大神的博客時有人問里面使用的美工素材怎么得到的,其實很簡單,下載一個APP,把APK格式修改成rar后解壓,你會在目錄下看到所有的素材。
(2)開始工作 項目目錄:
由于是自己的練手之作,所以,莫得啟動頁面
下面是首頁:
詳情頁
分類頁
個人頁面:
一. FeatureView獨立組件封裝FeatureViewdiv>a>img
二. TabControl獨立組件的封裝props -> titles
div>根據titles v-for遍歷 div -> span{{title}}
css相關
選中哪一個tab, 哪一個tab的文字顏色變色, 下面border-bottomcurrentIndex
:key="(item,index)" class="tab-control-item"
:class="{active: index===currentIndex}"
@click="itemClick(index)">
{{item}}
?
export default {
name: 'TabControl',
props:{
titles:{
type:Array,
default(){
return [];
}
}
},
data() {
return {
currentIndex:0,
}
},
methods:{
itemClick(index){
this.currentIndex = index;
this.$emit('tabClick', index);
}
}
}
?
.tab-control{
display: flex;
text-align: center;
font-size: 15px;
height: 40px;
line-height: 40px;
background-color: #fff;
}
.tab-control-item{
flex: 1;
?
}
.tab-control-item span{
padding: 5px;
}
.active{
color: #ff5777;
}
.active span{
border-bottom: 3px solid var(--color-tint);
}
?
三. 首頁商品數據的請求
3.1. 設計數據結構, 用于保存數據
goods: {
pop: page/list
new: page/list
sell: page/list
}
3.2. 發送數據請求在home.js中封裝getHomeGoods(type, page)
在Home.vue中, 又在methods中getHomeGoods(type)
調用getHomeGoods('pop')/getHomeGoods('new')/getHomeGoods('sell')page: 動態的獲取對應的page
獲取到數據: resthis.goods[type].list.push(...res.data.list)
this.goods[type].page += 1
goods: {
pop: page1:/list[30]
new: page1/list[30]
sell: page1/list[30]
}
四. 對商品數據進行展示
4.1. 封裝GoodsList.vue組件props: goods -> list[30]
v-for goods -> GoodsListItem[30]
GoodListItem(組件) -> GoodsItem(數據)
4.2. 封裝GoodsListItem.vue組件props: goodsItem
goodsItem 取出數據, 并且使用正確的div/span/img基本標簽進行展示
五. 對滾動進行重構: Better-Scroll
5.1. 在index.html中使用Better-Scrollconst bscroll = new BScroll(el, { })
注意: wrapper -> content -> 很多內容
1.監聽滾動probeType: 0/1/2(手指滾動)/3(只要是滾動)
bscroll .on('scroll', (position) => {})
2.上拉加載pullUpLoad: true
bscroll .on('pullingUp', () => {})
3.click: falsebutton可以監聽點擊
div不可以
5.2. 在Vue項目中使用Better-Scroll在Profile.vue中簡單的演示
對Better-Scroll進行封裝: Scroll.vue
Home.vue和Scroll.vue之間進行通信Home.vue將probeType設置為3
Scroll.vue需要通過$emit, 實時將事件發送到Home.vue
六. 回到頂部BackTop
6.1. 對BackTop.vue組件的封裝
6.2. 如何監聽組件的點擊直接監聽back-top的點擊, 但是可以直接監聽?不可以, 必須添加修飾.native
回到頂部scroll對象, scroll.scrollTo(x, y, time)
this.$refs.scroll.scrollTo(0, 0, 500)
6.3. BackTop組件的顯示和隱藏isShowBackTop: false
監聽滾動, 拿到滾動的位置:-position.y > 1000 -> isShowBackTop: true
isShowBackTop = -position.y > 1000
七. 解決首頁中可滾動區域的問題Better-Scroll在決定有多少區域可以滾動時, 是根據scrollerHeight屬性決定scrollerHeight屬性是根據放Better-Scroll的content中的子組件的高度
但是我們的首頁中, 剛開始在計算scrollerHeight屬性時, 是沒有將圖片計算在內的
所以, 計算出來的告訴是錯誤的(1300+)
后來圖片加載進來之后有了新的高度, 但是scrollerHeight屬性并沒有進行更新.
所以滾動出現了問題
如何解決這個問題了?監聽每一張圖片是否加載完成, 只要有一張圖片加載完成了, 執行一次refresh()
如何監聽圖片加載完成了?原生的js監聽圖片: img.onload = function() {}
Vue中監聽: @load='方法'
調用scroll的refresh()
如何將GoodsListItem.vue中的事件傳入到Home.vue中因為涉及到非父子組件的通信, 所以這里我們選擇了事件總線bus ->總線
Vue.prototype.$bus = new Vue()
this.bus.emit('事件名稱', 參數)
this.bus.on('事件名稱', 回調函數(參數))
問題一: refresh找不到的問題第一: 在Scroll.vue中, 調用this.scroll的方法之前, 判斷this.scroll對象是否有值
第二: 在mounted生命周期函數中使用 this.$refs.scroll而不是created中
問題二: 對于refresh非常頻繁的問題, 進行防抖操作防抖debounce/節流throttle(課下研究一下)
防抖函數起作用的過程:如果我們直接執行refresh, 那么refresh函數會被執行30次.
可以將refresh函數傳入到debounce函數中, 生成一個新的函數.
之后在調用非常頻繁的時候, 就使用新生成的函數.
而新生成的函數, 并不會非常頻繁的調用, 如果下一次執行來的非常快, 那么會將上一次取消掉
debounce(func, delay) {
let timer = null
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
},
八. 上拉加載更多的功能
loadMore(){
this.getHomeGoods(this.currentType);
this.$refs.scroll.refresh();
}
九. tabControl的吸頂效果
9.1. 獲取到tabControl的offsetTop必須知道滾動到多少時, 開始有吸頂效果, 這個時候就需要獲取tabControl的offsetTop
但是, 如果直接在mounted中獲取tabControl的offsetTop, 那么值是不正確.
如何獲取正確的值了?監聽HomeSwiper中img的加載完成.
加載完成后, 發出事件, 在Home.vue中, 獲取正確的值.
補充:為了不讓HomeSwiper多次發出事件,
可以使用isLoad的變量進行狀態的記錄.
注意: 這里不進行多次調用和debounce的區別
9.2. 監聽滾動, 動態的改變tabControl的樣式問題:動態的改變tabControl的樣式時, 會出現兩個問題:問題一: 下面的商品內容, 會突然上移
問題二: tabControl雖然設置了fixed, 但是也隨著Better-Scroll一起滾出去了.
其他方案來解決停留問題.在最上面, 多復制了一份PlaceHolderTabControl組件對象, 利用它來實現停留效果.
當用戶滾動到一定位置時, PlaceHolderTabControl顯示出來.
當用戶滾動沒有達到一定位置時, PlaceHolderTabControl隱藏起來.
十. 讓Home保持原來的狀態
10.1. 讓Home不要隨意銷毀掉keep-alive
10.2. 讓Home中的內容保持原來的位置離開時, 保存一個位置信息saveY.
進來時, 將位置設置為原來保存的位置saveY信息即可.注意: 最好回來時, 進行一次refresh()
非父子組件通信:
我們在用Vue進行前端開發的時候,往往會遇到有很多個組件內,他們都有類似的data,類似的方法。這些大量重復的代碼,如果正常編寫出來,代碼既不美觀也不優雅,而且看起來也相當復雜。所以vue官方提供了一個極其好用的方式來解決這個問題
那就是mixin
先來看看官方的介紹混入 (mixin) 提供了一種非常靈活的方式,來分發 Vue 組件中的可復用功能。一個混入對象可以包含任意組件選項。當組件使用混入對象時,所有混入對象的選項將被“混合”進入該組件本身的選項。
在Java開發中 如果我們遇到兩個類有大量相似代碼的時候,我們通常會定義一個父類,來講這些重復代碼寫在一起,然后再讓這兩個類來繼承父類的代碼和方法。
class Animal{
run(){}
}
class Person extends Animal{
//run(){}
}
class Dog extends Animal{
//run(){}
}
而在Vue中,每個組件export出來的是對象,所以不能像類那樣繼承,于是Vue提供了類似于類的繼承的方法 mixin
使用方法,在這里貼上自己項目的部分代碼。
定義一個mixin.js 文件
import {debounce} from './utils';
?
export const itemListenerMixin = {
data(){
return {
itemImgListener: null,
}
},
methods:{
?
},
mounted(){
let newRefresh = debounce(this.$refs.scroll.refresh, 100)
?
this.itemImgListener = () => {
newRefresh()
}
?
this.$bus.$on('itemImgLoad', this.itemImgListener)
console.log("我是混入的東西")
}
}
mixin 里就跟一個正常的Vue的組件沒有任何的區別,可以定義data,methods,生命周期函數等等。跟Java里面的父類和子類完全一樣。只是調用的方法不一樣而已。
兩個調用mixin.js的組件
Detail.vue
import {itemListenerMixin} from "common/mixin";
?
mixins: [itemListenerMixin],
//其余代碼均省略
Home.vue
import {itemListenerMixin} from "common/mixin";
?
mixins: [itemListenerMixin],
//其余代碼均省略
只需要這樣一小段代碼,就可以調用到mixin.js 內定義的組件了。
而且在兩個組件內,作用完全一樣
當我們在組件上應用Mixin的時候,有可能組件與Mixin中都定義了相同的生命周期鉤子,這時候鉤子的執行順序的問題凸顯了出來。默認Mixin上會首先被注冊,組件上的接著注冊,這樣我們就可以在組件中按需要重寫Mixin中的語句。組件擁有最終發言權。當發生沖突并且這個組件就不得不“決定”哪個勝出的時候,這一點就顯得特別重要,否則,所有的東西都被放在一個數組當中執行,Mixin將要被先推入數組,其次才是組件。
const myMixin = {
mounted() {
console.log('mixin!')
}
}
?
new Vue({
el: '#app',
mixins: [myMixin],
mounted() {
console.log('Vue instance!')
}
});
?
//Output in console
> mixin!
> Vue instance!
//mixin
const myMixin = {
methods: {
sayHello: function() {
console.log('mixin!')
}
},
mounted() {
this.sayHello()
}
}
?
//vue instance or component
new Vue({
el: '#app',
mixins: [myMixin],
methods: {
sayHello: function() {
console.log('Vue instance!')
}
},
mounted() {
this.sayHello()
}
})
?
// Output in console
> Vue instance!
> Vue instance!
我們可以看到,當他們之間沒有發生同名沖突的時候,兩個都正常打印了。而當他們發生沖突之后。你可以看到這里打印了兩個Vue instance。這是因為第一個函數被調用之后,并沒有被銷毀,而是被重寫了。然后被調用了兩次
當組件和混入對象含有同名選項時,這些選項將以恰當的方式混合。
選項合并數據對象(data)在內部會進行遞歸合并,在和組件的數據發生沖突時以組件數據優先。
同名鉤子函數(created,mounted...)將混合為一個數組,因此都將被調用。另外,混入對象的鉤子將在組件自身鉤子之前調用。
值為對象的選項(methods, components 和 directives)將被混合為同一個對象。兩個對象鍵名沖突時,取組件對象的鍵值對。
需要注意的是謹慎使用全局混入對象,因為會影響到每個單獨創建的 Vue 實例 (包括第三方模板)。大多數情況下,只應當應用于自定義選項。也可以將其用作 Plugins 以避免產生重復應用
所以Vue對mixin 設定了 自定義選項合并策略自定義選項將使用默認策略,即簡單地覆蓋已有值。如果想讓自定義選項以自定義邏輯合并,可以向 Vue.config.optionMergeStrategies 添加一個函數:
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回合并后的值
}
對于多數值為對象的選項,可以使用與 methods 相同的合并策略:
var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.methods