-Vue.js開(kāi)發(fā)移動(dòng)端經(jīng)驗(yàn)總結(jié)
---好文推薦,工作中用的上的前端技能---
原文地址:blog.csdn.net/weixin_44705688/article/details/103961664
移動(dòng)端適配
相對(duì)于PC端來(lái)說(shuō),移動(dòng)端設(shè)備分辨率百花齊放,千奇百怪,對(duì)于每一個(gè)開(kāi)發(fā)者來(lái)說(shuō),移動(dòng)端適配是我們進(jìn)行移動(dòng)端開(kāi)發(fā)第一個(gè)需要面對(duì)的問(wèn)題。
在移動(dòng)端我們經(jīng)常可以在head標(biāo)簽中看到這段代碼:
<meta?name='viewport'?content='width=device-width,initial-scale=1,user-scale=no'?/>
通過(guò)meta標(biāo)簽對(duì)viewport的設(shè)置,定義了頁(yè)面的縮放比例;要了解這些參數(shù)的意義,我們需要先知道幾個(gè)視口寬度的意義。
layoutviewport布局寬度
就是網(wǎng)頁(yè)的寬度visualviewport可視寬度,就是瀏覽器窗口的寬度,這個(gè)值決定了我們手機(jī)一屏能看到的內(nèi)容;
visualviewport和layoutviewport的大小關(guān)系,決定了是否會(huì)出現(xiàn)滾動(dòng)條,當(dāng)visualviewport更大或者剛好等于layoutviewport時(shí)是不會(huì)出現(xiàn)滾動(dòng)條的。idealviewport為瀏覽器定義的可完美適配移動(dòng)端的viewport,固定不變,可以認(rèn)為是設(shè)備視口寬度device-width。
meta
meta的設(shè)置其實(shí)就是對(duì)layoutviewport和visualviewport進(jìn)行設(shè)置。width=device-width表示頁(yè)面寬度layoutviewport與設(shè)備視口寬度idealviewport一致
initial-scale=1
表示頁(yè)面寬度和網(wǎng)頁(yè)寬度與設(shè)備視口寬度的初始縮放比例,visualviewport由這個(gè)比例決定,但是對(duì)于layoutviewport來(lái)說(shuō),它同時(shí)受到兩個(gè)屬性的影響,然后取其中較大的那個(gè)值。
user-scale=no禁止縮放
所以現(xiàn)在我們知道,這段在移動(dòng)端常見(jiàn)的代碼的意思,即將visualviewport和layoutviewport設(shè)置為idealviewport的值;這樣我們?cè)谝苿?dòng)端就不會(huì)出現(xiàn)滾動(dòng)條,網(wǎng)頁(yè)內(nèi)容可以比較好的展示出來(lái),在這個(gè)前提下我們?cè)倏紤]頁(yè)面的適配問(wèn)題。UI出圖的時(shí)候一般是有一個(gè)固定的寬度的,而我們實(shí)際的移動(dòng)端設(shè)備的寬度卻都不太一樣,但是如果頁(yè)面元素的縮放比例和頁(yè)面寬度的縮放比例一致,在不同尺寸的設(shè)備下我們網(wǎng)頁(yè)的效果也將會(huì)是一致的。使用相對(duì)單位
rem
rem 是相對(duì)于根元素 html 的 font-size 來(lái)做計(jì)算。通常在頁(yè)面初始化時(shí)加載時(shí)通過(guò)對(duì)document.documentElement.style.fontSize 設(shè)置來(lái)實(shí)現(xiàn)。一般我們將根元素html的font-size設(shè)置為寬度的1/10,不同設(shè)備的寬度不同,但是同樣數(shù)值的rem比例與設(shè)備的寬度比例是一致的。
document.documentElement.style.fontSize?=?document.documentElement.clientWidth?/?10?+?'px';
在實(shí)際項(xiàng)目中我們無(wú)須在開(kāi)發(fā)中自己進(jìn)行轉(zhuǎn)換,可以使用pxtorem在輸出的時(shí)候?qū)x轉(zhuǎn)換為rem。
視口單位
將視口寬度window.innerWidth和視口高度window.innerHeight(即layoutviewport)等分為 100 份。
vw : 1vw 為視口寬度的 1% vh : 1vh 為視口高度的 1% vmin : vw 和 vh 中的較小值 vmax : 選取 vw 和 vh 中的較大值和rem相比較,視口單位不需要使用js對(duì)根元素進(jìn)行設(shè)置,兼容性稍差,但是大部分設(shè)備都已經(jīng)支持了,同樣的無(wú)須再開(kāi)發(fā)時(shí)進(jìn)行單位換算,直接使用相關(guān)的插件postcss-px-to-viewport在輸出的時(shí)候進(jìn)行轉(zhuǎn)換。修改viewport
之前我們提到了layoutviewport布局寬度實(shí)際上不是一個(gè)固定值,而是通過(guò)meta設(shè)置屬性,通過(guò)idealviewport計(jì)算出來(lái)的值,我們可以通過(guò)控制meta的屬性來(lái)將layoutviewport固定為某一個(gè)值。一般設(shè)計(jì)圖的寬度為750px,現(xiàn)在我們的目標(biāo)就是將layoutviewport設(shè)置為750px;
layoutviewport受到兩個(gè)屬性的影響,width屬性我們之間設(shè)置為750,initial-scale縮放比例應(yīng)該為idealviewport的寬度/750;當(dāng)我們未改變meta標(biāo)簽屬性的時(shí)候,layoutviewport的值其實(shí)就是idealviewport的值,所以可以通過(guò)document.body.clientWidth或者window.innerWidth來(lái)獲取。
;(function?()?{
????const?width?=?document.body.clientWidth?||?window.innerWidth
????const?scale?=?width?/?750;
????const?content?=`width=750,?initial-scale=${scale},?minimum-scale=${scale},?maximum-scale=${scale},?viewport-fit=cover`
????;
????document.querySelector('meta[name="viewport"]').content?=?content
})()
設(shè)置完成之后,layoutviewport在不同的設(shè)備中會(huì)始終保持為750px,我們開(kāi)發(fā)時(shí)可以直接使用設(shè)計(jì)稿尺寸。布局樣式
布局的方式可以是各種各樣的,但是出于兼容性的考慮,有些樣式我們最好避免使用,難以解決的問(wèn)題,那就不去面對(duì)。需要謹(jǐn)慎對(duì)待的fixed
position:fixed在日常的頁(yè)面布局中非常常用,在許多布局中起到了關(guān)鍵的作用。它的作用是:position:fixed的元素將相對(duì)于屏幕視口(viewport)的位置來(lái)指定其位置。并且元素的位置在屏幕滾動(dòng)時(shí)不會(huì)改變。但是,在許多特定的場(chǎng)合,position:fixed的表現(xiàn)與我們想象的大相徑庭。
iOS彈出鍵盤(pán);軟鍵盤(pán)喚起后,頁(yè)面的 fixed元素將失效(iOS認(rèn)為用戶更希望的是元素隨著滾動(dòng)而移動(dòng),也就是變成了 absolute定位),既然變成了absolute,所以當(dāng)頁(yè)面超過(guò)一屏且滾動(dòng)時(shí),失效的 fixed 元素就會(huì)跟隨滾動(dòng)了。
當(dāng)元素祖先的 transform 屬性非 none時(shí),定位容器由視口改為該祖先。說(shuō)的簡(jiǎn)單點(diǎn),就是position:fixed的元素會(huì)相對(duì)于最近的并且應(yīng)用了transform的祖先元素定位,而不是窗口。導(dǎo)致這個(gè)現(xiàn)象的原因是使用了transform的元素將創(chuàng)建一個(gè)新的堆疊上下文。
堆疊上下文(Stacking Context):
堆疊上下文是 HTML 元素的三維概念,這些 HTML 元素在一條假想的相對(duì)于面向(電腦屏幕的)視窗或者網(wǎng)頁(yè)的用戶的z 軸上延伸,HTML元素依據(jù)其自身屬性按照優(yōu)先級(jí)順序占用層疊上下文的空間。
順序:
總之堆疊上下文會(huì)對(duì)定位關(guān)系產(chǎn)生影響。想要進(jìn)一步可以查看不受控制的position:fixed。
鍵盤(pán)彈出與使用transform屬性的情況在移動(dòng)端是很常見(jiàn)的,所以需要謹(jǐn)慎使用position:fixed。
推薦使用flex
flex,即彈性布局,移動(dòng)端兼容性較好,能夠滿足大部分布局需求。現(xiàn)在我們使用flex來(lái)實(shí)現(xiàn)h5中常見(jiàn)的頂部標(biāo)題欄+中部滾動(dòng)內(nèi)容+底部導(dǎo)航欄的布局 頁(yè)面跳轉(zhuǎn)
轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
在vue中我們通過(guò)vue-router來(lái)管理路由,每個(gè)路由跳轉(zhuǎn)類(lèi)似與在不同的頁(yè)面之間進(jìn)行切換,從用戶友好的角度來(lái)說(shuō),每次切換頁(yè)面的時(shí)候最好添加一個(gè)轉(zhuǎn)場(chǎng)效果。如果轉(zhuǎn)場(chǎng)動(dòng)畫(huà)不區(qū)分路由是打開(kāi)新頁(yè)面、還是返回之前頁(yè)面我們只需要在外使用添加一個(gè)動(dòng)畫(huà)效果即可;
但是一般打開(kāi)和返回是應(yīng)用不同的動(dòng)畫(huà)效果的,所以我們需要在切換路由的時(shí)候區(qū)分路由是前進(jìn)還是后退。為了區(qū)分路由的動(dòng)作,我們?cè)诼酚晌募性O(shè)置meta為數(shù)字,meta表示其路由的深度,然后監(jiān)聽(tīng)$route,根據(jù)to、from meta值的大小設(shè)置不同的跳轉(zhuǎn)動(dòng)畫(huà)。如果應(yīng)用到多種跳轉(zhuǎn)動(dòng)畫(huà),可以根據(jù)詳情,具體情況具體應(yīng)用。
<template>
??<transition?:name="transitionName">
????<router-view>router-view>
??transition>
template>
export?default?{
??name:?'app',
??data?()?{
????return?{
??????transitionName:?'fade'
????}
??},
??watch:?{
????'$route'?(to,?from)?{
??????let?toDepth?=?to.meta
??????let?fromDepth?=?from.meta
??????if?(fromDepth?>?toDepth)?{
????????this.transitionName?=?'fade-left'
??????}?else?if?(fromDepth?????????this.transitionName?=?'fade-right'
??????}?else?{
????????this.transitionName?=?'fade'
??????}
????}
??}
}
登錄跳轉(zhuǎn)
vue-navigation
雖然這樣能夠?qū)崿F(xiàn)跳轉(zhuǎn)效果,但是需要在編寫(xiě)router時(shí)添加設(shè)置,比較麻煩;我們可以使用開(kāi)源項(xiàng)目vue-navigation來(lái)實(shí)現(xiàn),更加方便,無(wú)須對(duì)router進(jìn)行多余的設(shè)置。
npm?i?-S?vue-navigation
安裝,在main.js中導(dǎo)入:
import?Navigation?from?'vue-navigation'
Vue.use(Navigation,?{router})?//?router為路由文件
在App.vue中設(shè)置:
this.$navigation.on('forward',?(to,?from)?=>?{
????this.transitionName?=?'fade-right'
?})
?this.$navigation.on('back',?(to,?from)?=>?{
????this.transitionName?=?'fade-left'
?})
?this.$navigation.on('replace',?(to,?from)?=>?{
????this.transitionName?=?'fade'
?})
vue-navigation插件還有一個(gè)重要的功能就是保存頁(yè)面狀態(tài),與keep-alive相似,但是keep-alive保存狀態(tài)無(wú)法識(shí)別路由的前進(jìn)后退,而實(shí)際應(yīng)用中,我們的需求是返回頁(yè)面時(shí),希望頁(yè)面狀態(tài)保存,當(dāng)進(jìn)入頁(yè)面時(shí)希望獲取新的數(shù)據(jù),使用vue-navigation可以很好的實(shí)現(xiàn)這個(gè)效果。
具體使用可以查看vue-navigation有詳細(xì)使用說(shuō)明與案例。另外也可以嘗試vue-page-stack,兩個(gè)項(xiàng)目都能實(shí)現(xiàn)我們需要的效果,vue-page-stack借鑒了vue-navigation,也實(shí)現(xiàn)了更多的功能,并且最近也一直在更新。PS: 這里的動(dòng)畫(huà)效果引用自animate.scss;
底部導(dǎo)航欄
之前我們已經(jīng)實(shí)現(xiàn)了底部導(dǎo)航欄的基本樣式,這里我們?cè)僮鲆恍┱f(shuō)明。當(dāng)頁(yè)面路由路徑與router-link的路由匹配時(shí),router-link將會(huì)被設(shè)置為激活狀態(tài),我們可以通過(guò)設(shè)置active-class來(lái)設(shè)置路徑激活時(shí)應(yīng)用的類(lèi)名,默認(rèn)為router-link-active,而激活的類(lèi)名還有一個(gè)router-link-exact-active,這個(gè)類(lèi)名是由exact-active-class來(lái)設(shè)置的,同樣是設(shè)置路徑激活時(shí)應(yīng)用的類(lèi)名;active-class與exact-active-class其實(shí)是由路由的匹配方式?jīng)Q定的。
一般路由的匹配方式是包含匹配。舉個(gè)例子,如果當(dāng)前的路徑是 /a 開(kāi)頭的,那么 也會(huì)被設(shè)置 CSS 類(lèi)名。按照這個(gè)規(guī)則,每個(gè)路由都會(huì)激活 ,而使用exact屬性可以使用“精確匹配模式”。精確匹配只有當(dāng)路由完全相同的時(shí)候才會(huì)被激活。路由守衛(wèi)
移動(dòng)端的路由守衛(wèi)一般不會(huì)太復(fù)雜,主要是登錄權(quán)限的判斷,我們?cè)O(shè)置一個(gè)路由白名單,將所有不需要登錄權(quán)限的路由放入其中;對(duì)于需要登錄的路由做判斷,沒(méi)有登錄就跳轉(zhuǎn)登錄頁(yè)面,要求用戶進(jìn)行登錄后在訪問(wèn),如果登錄后需要返回原有路由就把目標(biāo)頁(yè)面的路由作為參數(shù)傳遞給登錄頁(yè)面,再在登錄后進(jìn)行判斷,如果存在目標(biāo)頁(yè)面參數(shù)就跳轉(zhuǎn)目標(biāo)頁(yè)面,沒(méi)有就跳轉(zhuǎn)首頁(yè)。
如果你的應(yīng)用涉及到權(quán)限,那需要標(biāo)注每個(gè)路由需要的權(quán)限,在meta中設(shè)置roles,roles是數(shù)組來(lái)保存需要的權(quán)限;從后臺(tái)的接口中獲取用戶擁有的權(quán)限和roles進(jìn)行對(duì)比就可以判斷是否具有相關(guān)權(quán)限了。
const?whiteList?=?['/login']
router.beforeEach((to,?from,?next)?=>?{
??const?hasToken?=?store.getters.auth
??if?(hasToken)?{
????if?(to.path?===?'/login')?{
??????next({?path:?'/'?})
????}?else?{
??????const?needRoles?=?to.meta?&&?to.meta.roles?
??????&&?to.meta.roles.length?>?0
??????if?(needRoles)?{
????????const?roles=store.state.user.roles;
????????const?hasRoles?=?roles.some((role)=>{
?????????return?to.meta.roles.includes(role)
????????})
????????if?(hasRoles)?{
??????????next()
????????}?else?{
??????????next('/403')
????????}
??????}?else?{
????????next()
??????}
????}
??}?else?{
????if?(whiteList.includes(to.path))?{
??????next()
????}?else?{
??????next('/login')
????}
??}
})
組件自動(dòng)加載
在我們的項(xiàng)目中,往往會(huì)使用的許多組件,一般使用頻率比較高的組件為了避免重復(fù)導(dǎo)入的繁瑣一般是作為全局組件在項(xiàng)目中使用的。而注冊(cè)全局組件我們首先需要引入組件,然后使用Vue.component進(jìn)行注冊(cè);這是一個(gè)重復(fù)的工作,我們每次創(chuàng)建組件都會(huì)進(jìn)行,如果我們的項(xiàng)目是使用webpack構(gòu)建(vue-cli也是使用webpack),我們就可以通過(guò)require.context自動(dòng)將組件注冊(cè)到全局。創(chuàng)建components/index.js文件:
export?default?function?registerComponent?(Vue)?{
??/**
???*?參數(shù)說(shuō)明:
???*?1.?其組件目錄的相對(duì)路徑
???*?2.?是否查詢其子目錄
???*?3.?匹配基礎(chǔ)組件文件名的正則表達(dá)式
???**/
??const?modules?=?require.context('./',?false,?/\w+.vue$/)
??modules.keys().forEach(fileName?=>?{
????//?獲取組件配置
????const?component?=?modules(fileName)
????//?獲取組件名稱(chēng),去除文件名開(kāi)頭的?`./`?和結(jié)尾的擴(kuò)展名
????const?name?=?fileName.replace(/^\.\/(.*)\.\w+$/,?'$1')
????//?注冊(cè)全局組件
????//?如果這個(gè)組件選項(xiàng)是通過(guò)?`export?default`?導(dǎo)出的,
????//?那么就會(huì)優(yōu)先使用?`.default`,
????//?否則回退到使用模塊的根。
????Vue.component(name,?component.default?||?component)
??})
}
之后在main.js中導(dǎo)入注冊(cè)模塊進(jìn)行注冊(cè),使用require.context我們也可以實(shí)現(xiàn)vue插件和全局filter的導(dǎo)入。
import?registerComponent?from?'./components'
registerComponent(Vue)
通過(guò)v-model綁定數(shù)據(jù)
v-model是語(yǔ)法糖,它的本質(zhì)是對(duì)組件事件進(jìn)行監(jiān)聽(tīng)和數(shù)據(jù)進(jìn)行更新,是props和on監(jiān)聽(tīng)事件的縮寫(xiě),v-model默認(rèn)傳遞value,監(jiān)聽(tīng)input事件。
現(xiàn)在我們使用v?model來(lái)實(shí)現(xiàn)下數(shù)字輸入框,這個(gè)輸入框只能輸入數(shù)字,在組件中我們只需要定義value來(lái)接受傳值,然后在輸入值滿足我們輸入條件(輸入為數(shù)字)的時(shí)候使用on監(jiān)聽(tīng)事件的縮寫(xiě),v-model默認(rèn)傳遞value,監(jiān)聽(tīng)input事件。
現(xiàn)在我們使用v-model來(lái)實(shí)現(xiàn)下數(shù)字輸入框,這個(gè)輸入框只能輸入數(shù)字,在組件中我們只需要定義value來(lái)接受傳值,然后在輸入值滿足我們輸入條件(輸入為數(shù)字)的時(shí)候使用on監(jiān)聽(tīng)事件的縮寫(xiě),v?model默認(rèn)傳遞value,監(jiān)聽(tīng)input事件。
現(xiàn)在我們使用v?model來(lái)實(shí)現(xiàn)下數(shù)字輸入框,這個(gè)輸入框只能輸入數(shù)字,在組件中我們只需要定義value來(lái)接受傳值,然后在輸入值滿足我們輸入條件(輸入為數(shù)字)的時(shí)候使用emit觸發(fā)input事件。
<template>
??<div>
????<input?type="text"?:value="value"?@input="onInput">
??div>
template>
export?default?{
??name:?'NumberInput',
??props:?{
????value:?String
??},
??methods:?{
????onInput?(event)?{
??????if?(/^\d+$/.test(event.target.value))?{
????????this.$emit('input',?event.target.value)
??????}?else?{
????????event.target.value?=?this.value
??????}
????}
??}
}
使用的時(shí)候,我們只需要使用v-model綁定值就可以了。v-model默認(rèn)會(huì)利用名為value的prop和名為input的事件,但是很多時(shí)候我們想使用不同的prop和監(jiān)聽(tīng)不同的事件,我們可以使用model選項(xiàng)進(jìn)行修改。
Vue.component('my-checkbox',?{
??model:?{
????prop:?'checked',
????event:?'change'
??},
??props:?{
????//?this?allows?using?the?`value`?prop?for?a?different?purpose
????value:?String,
????//?use?`checked`?as?the?prop?which?take?the?place?of?`value`
????checked:?{
??????type:?Number,
??????default:?0
????}
??},
??//?...
})
<my-checkbox?v-model="foo"?value="some?value">my-checkbox>
//上述代碼相當(dāng)于:
<my-checkbox
??:checked="foo"
??@change="val?=>?{?foo?=?val?}"
??value="some?value">
my-checkbox>
通過(guò)插件的方式來(lái)使用組件
在很多第三方組件庫(kù)中,我們經(jīng)常看到直接使用插件的方式調(diào)用組件的方式,比如VantUI的Dialog彈出框組件,我們不但可以使用組件的方式進(jìn)行使用,也可以通過(guò)插件的形式進(jìn)行調(diào)用。
this.$dialog.alert({
??message:?'彈窗內(nèi)容'
});
將組件作為插件使用的原理其實(shí)并不復(fù)雜,就是使用手動(dòng)掛載Vue組件實(shí)例。
import?Vue?from?'vue';
export?default?function?create(Component,?props)?{
????//?先創(chuàng)建實(shí)例
????const?vm?=?new?Vue({
????????render(h)?{
????????????//?h就是createElement,它返回VNode
????????????return?h(Component,?{props})
????????}
????}).$mount();
????//?手動(dòng)掛載
????document.body.appendChild(vm.$el);
????//?銷(xiāo)毀方法
????const?comp?=?vm.$children[0];
????comp.remove?=?function()?{
????????document.body.removeChild(vm.$el);
????????vm.$destroy();
????}
????return?comp;
}
調(diào)用create傳入組件和props參數(shù)就可以獲取組件的實(shí)例,通過(guò)組件實(shí)例我們就可以調(diào)用組件的各種功能了。
<template>
??<div?class="loading-wrapper"?v-show="visible">
????加載中
??div>
template>
export?default?{
??name:?'Loading',
??data?()?{
????return?{
??????visible:?false
????}
??},
??methods:?{
????show?()?{
??????this.visible?=?true
????},
????hide?()?{
??????this.visible?=?false
????}
??}
}
<style?lang="css"?scoped>
.loading-wrapper?{
??position:?absolute;
??top:?0;
??bottom:?0;
??width:?100%;
??background-color:?rgba(0,?0,?0,?.4);
??z-index:?999;
}
style>
const?loading?=?create(Loading,?{})
loading.show()?//?顯示
loading.hide()?//?關(guān)閉
第三方組件
移動(dòng)端各種組件、插件已經(jīng)相對(duì)完善,在項(xiàng)目開(kāi)發(fā)中重復(fù)造輪子是一件很不明智的事情;開(kāi)發(fā)項(xiàng)目時(shí)我們可以借助第三方組件、插件提高我們的開(kāi)發(fā)效率。
常用組件庫(kù)
VantUI是有贊開(kāi)源的一套輕量、可靠的移動(dòng)端Vue組件庫(kù);支持按需引入、主題定制、SSR,除了常用組件外,針對(duì)電商場(chǎng)景還有專(zhuān)門(mén)的業(yè)務(wù)組件,如果是開(kāi)發(fā)電商項(xiàng)目的話,推薦使用。官方文檔關(guān)于主題定制是在webpack.config.js中進(jìn)行設(shè)置的:
//?webpack.config.js
module.exports?=?{
??rules:?[
????{
??????test:?/\.less$/,
??????use:?[
????????//?...其他?loader?配置
????????{
??????????loader:?'less-loader',
??????????options:?{
????????????modifyVars:?{
??????????????//?直接覆蓋變量
??????????????'text-color':?'#111',
??????????????'border-color':?'#eee'
??????????????//?或者可以通過(guò)?less?文件覆蓋(文件路徑為絕對(duì)路徑)
??????????????'hack':?`true;?@import?"your-less-file-path.less";`
????????????}
??????????}
????????}
??????]
????}
??]
};
但我們的項(xiàng)目可能是使用vue-cli構(gòu)建,這時(shí)我們需要在vue.config.js中進(jìn)行設(shè)置:
module.exports?=?{
??css:?{
????loaderOptions:?{
??????less:?{
????????modifyVars:?{
??????????'hack':?`true;?@import?"~@/assets/less/vars.less";`
????????}
??????}
????}
??}
}
另外vux、mint-ui也是很好的選擇。
常用插件
better-scroll是一個(gè)為移動(dòng)端各種滾動(dòng)場(chǎng)景提供絲滑的滾動(dòng)效果的插件,如果在vue中使用可以參考作者的文章當(dāng) better-scroll 遇見(jiàn) Vue。
swiper是一個(gè)輪播圖插件,如果是在vue中使用可以直接使用vue-awesome-swiper,vue-awesome-swiper基于Swiper4,并且支持SSR。
點(diǎn)個(gè)『在看』支持下?
