您好,登錄后才能下訂單哦!
小編給大家分享一下京東優(yōu)選小程序如何實(shí)現(xiàn),相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
開(kāi)發(fā)工具
微信開(kāi)發(fā)者工具
VS Code
效果速覽
廢話不多說(shuō),咱先來(lái)搞一波圖片看看,點(diǎn)這里查看更多圖片
項(xiàng)目結(jié)構(gòu)
這個(gè)項(xiàng)目我使用的是普通的開(kāi)發(fā),把所有的數(shù)據(jù)都放在了json-server中模擬。
可能很多人會(huì)覺(jué)得很奇怪,但這是因?yàn)槲野l(fā)現(xiàn)easy mock的網(wǎng)站經(jīng)常打不開(kāi)請(qǐng)求失敗非常的不方便,所以我暫時(shí)沒(méi)有選擇mock數(shù)據(jù),后期有時(shí)間我會(huì)把數(shù)據(jù)挪到easy mock上。
|-jd_recommend 項(xiàng)目名 |-api 模擬數(shù)據(jù)接口 |-db.json 模擬的數(shù)據(jù) |-assets 資源文件 |-icons 圖標(biāo)資源 |-images 圖片資源 |-components 組件模塊 |-navigationBar 自定義導(dǎo)航欄 |-toast 自定義toast |-stepper 有贊vant步進(jìn)器組件 |-... 其他小程序所需組件 |-pages 項(xiàng)目頁(yè)面 |-about 關(guān)于頁(yè)面 |-account 我的訂單頁(yè)面 |-afterMarket 售后類型頁(yè)面 |-appointment 我的預(yù)約頁(yè)面 |-buy 填寫訂單信息頁(yè)面 |-commentDetail 評(píng)論詳情頁(yè)面 |-discount 優(yōu)惠券頁(yè)面 |-explore 發(fā)現(xiàn)頁(yè)面 |-feedback 反饋?lái)?yè)面 |-fix 售后頁(yè)面 |-goodsDetail 值得買優(yōu)惠詳情頁(yè)面 |-index 首頁(yè) |-jd 京東商品詳情頁(yè)面 |-login 登錄頁(yè)面 |-orderDetail 訂單詳情頁(yè)面 |-seller 客服頁(yè)面 |-service 退換/售后頁(yè)面 |-shopCart 購(gòu)物車頁(yè)面 |-user 個(gè)人中心頁(yè)面 |-style 公共樣式 |-comment.wxss 評(píng)論區(qū)樣式 |-goodsCard.wxss 商品卡片樣式 |-nav.wxss 導(dǎo)航欄樣式 |-orderCard.wxss 訂單卡片樣式 |-popright.wxss 篩選框樣式 |-popup.wxss 上拉菜單樣式 |-utils 公共模塊 |-util.js promise封裝接口 app.js 全局js app.json 全局json配置 app.wxss 全局wxss
自定義組件
大部分人寫小程序肯定要涉及修改navigationBar的title,微信小程序開(kāi)發(fā)內(nèi)置了這個(gè)組件,可以直接在app.json中配置。但是,自帶的navigationBar的樣子是固定的,你肯定見(jiàn)過(guò)長(zhǎng)成下面這樣的navigationBar:
相比平時(shí)常見(jiàn)的navigationBar,它左上角多了一個(gè)返回主頁(yè)的按鈕,這對(duì)于有多級(jí)頁(yè)面的小程序來(lái)說(shuō)是非常必要的,不然訪問(wèn)的層級(jí)太深用戶不知道怎么返回主頁(yè)。然而,小程序開(kāi)發(fā)自帶并沒(méi)有這個(gè)樣子的,好在可以自定義,接下來(lái)我們就來(lái)自定義一個(gè)。
navigationBar
首先,我們構(gòu)建一下頁(yè)面的結(jié)構(gòu):
<!-- components/navigationBar/index.wxml --> <view class='nav-wrap' style='height: {{height*2 + 20}}px;'> <!-- 導(dǎo)航欄 中間的標(biāo)題 --> <view class='nav-title' style='line-height: {{height*2 + 44}}px;'>{{navbarData.title}}</view> <view style='display: flex; justify-content: space-around;flex-direction: column'> <!-- 導(dǎo)航欄 左上角的返回按鈕和home按鈕 --> <!-- 其中wx:if='{{navbarData.showCapsule}}' 是控制左上角按鈕的顯示隱藏,首頁(yè)不顯示 --> <view class='nav-capsule' style='height: {{height*2 + 44}}px;' wx:if='{{navbarData.showCapsule}}'> <!-- 左上角的返回按鈕,wx:if='{{!share}}'空制返回按鈕顯示 --> <view bindtap='_navback'> <image src='../../assets/icons/back.png' mode='aspectFill' class='back-pre'></image> </view> <view class='navbar-v-line' wx:if='{{!share}}'></view> <view bindtap='_backhome'> <image src='../../assets/icons/back_home.png' mode='aspectFill' class='back-home'></image> </view> </view> </view> </view>
這就是一個(gè)很普通的頁(yè)面結(jié)構(gòu),值得注意的是,它的高度是根據(jù)獲取的設(shè)備的高度來(lái)確定的。
接下來(lái)又到了切圖仔上線的時(shí)候了(誤):
/* components/navigationBar/index.wxss */ /* 頂部要固定定位 標(biāo)題要居中 自定義按鈕和標(biāo)題要和右邊微信原生的膠囊上下對(duì)齊 */ .nav-wrap { position: fixed; width: 100%; top: 0; background: #fff; color: #000; z-index: 9999999; border-bottom: 1rpx solid #EFEFF4; } /* 標(biāo)題要居中 */ .nav-title { position: absolute; text-align: center; max-width: 400rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; top: 0; left: 0; right: 0; bottom: 0; margin: auto; font-size: 36rpx; color: #2c2b2b; /* font-weight: 600; */ } .nav-capsule { display: flex; align-items: center; margin-left: 30rpx; width: 140rpx; justify-content: space-between; height: 100%; } .navbar-v-line { width: 1px; height: 32rpx; background-color: #e5e5e5; } .back-pre, .back-home { width: 32rpx; height: 36rpx; margin-top: 4rpx; padding: 10rpx; } .nav-capsule .back-home { width: 36rpx; height: 40rpx; margin-top: 3rpx; }
// components/navigationBar/index.js const app = getApp() Component({ properties: { navbarData: { //navbarData 由父頁(yè)面?zhèn)鬟f的數(shù)據(jù),變量名字自命名 type: Object, value: {}, // observer: function (newVal, oldVal) { } } }, data: { height: '', //默認(rèn)值 默認(rèn)顯示左上角 navbarData: { showCapsule: 1 } }, attached: function () { // 定義導(dǎo)航欄的高度 方便對(duì)齊 this.setData({ height: app.globalData.height }) }, methods: { // 返回上一頁(yè)面 _navback() { wx.navigateBack() }, //返回到首頁(yè) _backhome() { wx.switchTab({ url: '/pages/index/index', }) } } })
京東優(yōu)選小程序這里的兩個(gè)按鈕都是返回首頁(yè),我在開(kāi)發(fā)的時(shí)候覺(jué)得不對(duì)勁,所以我改過(guò)來(lái)了。
在這里還去取了一下全局定義的變量,也就是獲取的設(shè)備頂部窗口的高度(不同設(shè)備窗口高度不一樣,根據(jù)這個(gè)來(lái)設(shè)置自定義導(dǎo)航欄的高度),在app.js中要定義一下:
app.js App({ onLaunch: function () { ...... wx.getSystemInfo({ success: (res) => { this.globalData.height = res.statusBarHeight } }) }, globalData: { ... height: 0 } })
記得自定組件的時(shí)候一定要在json中寫成自定義組件
// components/navigationBar/index.json { "component": true }
接下來(lái)就是調(diào)用該組件了
<navigationBar navbar-data='{{navbarData}}'></navigationBar>
別忘了在要引用頁(yè)面的json中引入該組件
"usingComponents": { "navigationBar": "../../components/navigationBar/index" }
Toast
Toast同樣也是小程序開(kāi)發(fā)已經(jīng)做好給你用的了,雖然它可以支持替換里面的圖標(biāo),但是你會(huì)發(fā)現(xiàn)很雞肋的一點(diǎn)是,如果你想顯示兩行文字你就沒(méi)辦法做到了。我在開(kāi)發(fā)過(guò)程中也搜索過(guò)相關(guān)的實(shí)現(xiàn)方法,找到了大部分是說(shuō)在要換行的文字后背加上rn就能實(shí)現(xiàn)了,但是我自己親測(cè)無(wú)效,所以實(shí)在忍不住也自己做了一個(gè)。
<!-- components/toast/index.wxml --> <!-- 距離頂部高度由業(yè)務(wù)需要?jiǎng)討B(tài)確定 --> <view class='mask' hidden="{{hide}}" style='top: {{toastData.top}}'> <image class="image" src='../../assets/icons/{{toastData.icon}}.png' mode='aspectFit'></image> <view class="info"> <view class='info1' wx:if="{{toastData.info1 != ''}}">{{toastData.info1}}</view> <view class="info2" wx:if="{{toastData.info2 != ''}}">{{toastData.info2}}</view> </view> </view>
/* components/toast/index.wxss */ .mask { width: 440rpx; height: auto; border-radius: 20rpx; position: fixed; left: 155rpx; z-index: 1000; background: rgba(0, 0, 0, 0.6); text-align: center; padding-bottom: 30rpx; } .image { z-index: 1000; width: 80rpx; height: 80rpx; padding-top: 30rpx; padding-bottom: 20rpx; } .info1, .info2 { color: #ffffff; font-size: 32rpx; } .info { display: flex; flex-direction: column; justify-content: center; align-items: center; }
// components/toast/index.js Component({ properties: { //定義組件屬性 toastData: { //用來(lái)顯示提示信息 type: Object, // 類型(必填),目前接受的類型包括:String, Number, Boolean, Object, Array, null(表示任意類型) value: { icon: 'success' } // 屬性初始值(可選),如果未指定則會(huì)根據(jù)類型選擇一個(gè) }, }, data: { hide: true }, methods: { showToast: function () { let that = this; that.setData({ hide: false }); }, hideToast: function (e) { let that = this; setTimeout(function () { that.setData({ hide: true }); }, 2000); } } })
這里給組件定義了兩個(gè)方法,是用來(lái)顯示和隱藏Toast的。這里要注意一下,調(diào)用給自定義組件定義方法要先在頁(yè)面上獲取該組件
<toast id="toast" toast-data="{{toastData}}"></toast>
Page({ data: { toastData: { // toast需要的參數(shù) icon: "success", info1: "加入購(gòu)物車成功", top: "50%" } }, onReady() { this.toast = this.selectComponent("#toast"); } })
然后在需要觸發(fā)Toast的事件中寫上這兩句:
this.toast.showToast() this.toast.hideToast()
功能實(shí)現(xiàn)
導(dǎo)航
所謂導(dǎo)航,也是很常見(jiàn)了,就是根據(jù)選擇欄目的不同,顯示不同的類別內(nèi)容。例如:
功能要求:
點(diǎn)擊導(dǎo)航欄目,顯示對(duì)應(yīng)的欄目數(shù)據(jù)。
如果欄目中沒(méi)有東西,要顯示對(duì)應(yīng)的提示信息。
實(shí)現(xiàn)它的功能并不難,直接sroll-view往上懟。個(gè)人覺(jué)得,京東優(yōu)選在這里有一點(diǎn)不足的地方就是,如果點(diǎn)擊了偏右側(cè)的導(dǎo)航欄目的話,導(dǎo)航條不會(huì)跟著右移顯示后面的項(xiàng)目,可能它的開(kāi)發(fā)者有不一樣的想法吧。
<view class="navigator"> <scroll-view scroll-x="true" class="nav" scroll-left="{{navScrollLeft}}" scroll-with-animation="{{true}}"> <block wx:for="{{navData}}" wx:for-index="id" wx:for-item="navItem" wx:key="id"> <view class="nav-item {{currentTab == id?'active':''}}" data-name="{{navItem.name}}" data-current="{{id}}" bindtap="switchNav"> {{navItem.name}} </view> </block> </scroll-view> </view>
通過(guò)js可以實(shí)現(xiàn)動(dòng)態(tài)的填放數(shù)據(jù),這里設(shè)置的current就是當(dāng)前選擇的欄目,可以根據(jù)這個(gè)改變樣式等。
switchNav(e) { const cur = e.currentTarget.dataset.current; // Number let currData = [] // console.log(cur.toString()); if (cur === 0) { currData = this.data.goods } else { this.data.goods.forEach(val => { if (val.category === cur.toString()) { currData.push(val) } }) } this.setData({ currentTab: cur, category: cur, currData }); }
如果是要實(shí)現(xiàn)點(diǎn)擊之后自動(dòng)向點(diǎn)擊的方法滑出顯示更多的內(nèi)容,可以通過(guò)動(dòng)態(tài)改變navScrollLeft的值去實(shí)現(xiàn),這里我就不細(xì)說(shuō)了,不過(guò)我在實(shí)現(xiàn)的時(shí)候還是花了一番功夫,實(shí)現(xiàn)的不是很好所以就沒(méi)有放在代碼里,如果你以后想做出這種效果的導(dǎo)航欄建議去網(wǎng)上搜一搜demo看懂了之后借過(guò)來(lái)用一用,畢竟傳說(shuō)程序猿最高的境界是復(fù)制粘貼,狗頭(誤)
上拉菜單和篩選框
這兩個(gè)比較相似,只是拉出的位置不一樣,這里我就舉一個(gè)篩選框的例子,我們先看看它長(zhǎng)啥樣:
我們先看看結(jié)構(gòu),這里我省略了中間的一些內(nèi)容:
<!-- 點(diǎn)擊篩選彈出的選擇菜單 --> <view class="float {{isRuleTrue?'isRuleShow':'isRuleHide'}}"> <view class="animation-element" animation="{{animation}}"> ...中間自己放的具體內(nèi)容... <!-- 底部的兩個(gè)按鈕 --> <view class='bottom'> <view class="animation-reset" bindtap="reset">重置</view> <view class="animation-button" bindtap="success">確定</view> </view> </view> </view>
/* 篩選彈框 */ /* 彈框的布局 */ .isRuleShow { display: block; } .isRuleHide { display: none; } .float { height: 100%; width: 100%; position: fixed; z-index: 999; top: 0; left: 0; /* 彈出后背景的顏色 */ background-color: rgba(0, 0, 0, 0.5); padding-left: 30rpx; padding-left: 30rpx; /* margin-top:80rpx; */ } .animation-element { width: 600rpx; height: 100%; padding-left: 30rpx; padding-right: 30rpx; background-color: #ffffff; border: 1px solid #f3f0f0; position: absolute; right: -550rpx; box-sizing: border-box; } .bottom { width: 600rpx; height: 110rpx; font-size: 32rpx; padding-top: 55rpx; position: absolute; bottom: 0; left: 0; right: 0; display: flex; } .animation-reset { width: 50%; height: 100%; line-height: 50%; text-align: center; padding-top: 55rpx; border-top: 1px solid #EFEFF4; } .animation-button { width: 50%; height: 100%; line-height: 50%; color: #fff; text-align: center; background-color: #ED7358; padding-top: 55rpx; }
重點(diǎn)是它的顯示和隱藏事件,需要用到animation,如果有不熟悉animation,可以去參考一些資料,或者是官方文檔。同樣,我也去掉了我實(shí)現(xiàn)其他業(yè)務(wù)的一些內(nèi)容。
showSelect() { // 顯示選擇菜單 this.setData({ isRuleTrue: true }) // 左偏移245 step表示一個(gè)動(dòng)作的開(kāi)始 this.animation.translate(-245, 0).step() this.setData({ animation: this.animation.export() }) }, success: function () { // 關(guān)閉選擇菜單 this.setData({ isRuleTrue: false, selected: true }) this.animation.translate(0, 0).step() this.setData({ animation: this.animation.export() }) },
購(gòu)物車邏輯
要實(shí)現(xiàn)這樣的效果并不困難,需要自己思路清晰,不能被繞進(jìn)去了。實(shí)現(xiàn)加入購(gòu)物車并不難,細(xì)節(jié)是購(gòu)物車圖標(biāo)右上角的數(shù)字要根據(jù)加入購(gòu)物車的數(shù)量進(jìn)行動(dòng)態(tài)的改變,還要注意如果是同一件商品就不需要添加新的,只需要修改原來(lái)的數(shù)量。
在這里我使用的是小程序的wx.setStorage()實(shí)現(xiàn)的:
<view class='bottom'> <view class="animation-reset" bindtap="addCart">加入購(gòu)物車</view> <view class="animation-button" bindtap="buy">立即購(gòu)買</view> </view>
addCart() { // 加入購(gòu)物車 this.setData({ toastData: { // toast需要的參數(shù) icon: "success", info1: "加入購(gòu)物車成功", top: "50%" } }) this.toast.showToast() this.toast.hideToast() this.hideModal() // 真正實(shí)現(xiàn)添加購(gòu)物車的部分 let cartData = wx.getStorageSync('cart') || []; let count = 0 cartData.map(val => { if (val.title === this.data.currData[0].title && val.type === this.data.choose_value) { val.num += this.data.num count++ // 標(biāo)記是否有找到相同的商品 } }) if (count === 0) { // 沒(méi)找到 添加新的商品信息進(jìn)購(gòu)物車 let data = { id: this.data.currData[0]._id, title: this.data.currData[0].title, weight: "0.78kg", type: this.data.choose_value, num: this.data.num, price: this.data.currData[0].plain_price, img: this.data.currData[0].thumb, discount: 20, select: true // 是否選中,方便后續(xù)計(jì)算總價(jià) } cartData.push(data) } // 刷新購(gòu)物車圖標(biāo)上的數(shù)量 let allNum = 0 cartData.forEach(val => { allNum += val.num }); this.setData({ allNum }) wx.setStorage({ key: 'cart', data: cartData }) },
這里你可以根據(jù)自己的開(kāi)發(fā)來(lái)決定方式,如果你使用的是云開(kāi)發(fā)的話,可以選擇把數(shù)據(jù)存進(jìn)云數(shù)據(jù)庫(kù)里。
回到頂部
這也是一個(gè)老生常談的功能,當(dāng)你滑到頁(yè)面比較后的位置的時(shí)候需要快速回頂。這里要記住,用swiper實(shí)現(xiàn)。首先是在頁(yè)面上擼一個(gè)回到頂部的圖標(biāo)出來(lái):
<!-- 滑動(dòng)一段距離后顯示返回頂部的按鈕 --> <scroll-view class="bigWrap" scroll-y="true" scroll-top="{{scrollTop}}" bindscroll="scroll" > <view class="goTop" bindtap="goTop" wx:if="{{&& floorstatus}}"> <image class="icon_goTop" src="../../assets/icons/back_to_top.png"></image> </view> </scroll-view>
{{scrollTop}}用來(lái)表示滑動(dòng)的時(shí)候距離頂部的位置。它的樣式也很簡(jiǎn)單,使用固定定位把它定在屏幕上,這里一定要注意頁(yè)面的層級(jí),不然它可能會(huì)被其他組件給遮擋掉!
/* 回到頂部 */ .goTop { position: fixed; bottom: 200rpx; right: 20rpx; width: 65rpx; height: 65rpx; border: 1px solid #DDDDDD; border-radius: 50%; background-color: #fff; text-align: center; } .icon_goTop { width: 40rpx; height: 40rpx; padding-top: 12rpx; padding-left: 2rpx; }
goTop(e) { // 回到頂部 this.setData({ scrollTop: 0 }) }
你肯定也注意到了,當(dāng)滑到了一定距離的時(shí)候它才顯示出來(lái),這就要靠swiper綁定的滾動(dòng)事件了:
scroll(e) { // 滾動(dòng)事件 // 容器滾動(dòng)時(shí)將此時(shí)的滾動(dòng)距離賦值給 this.data.scrollTop let floorstatus = false if (e.detail.scrollTop > 300) { floorstatus = true } this.setData({ floorstatus }) }
功能大致先說(shuō)這么一點(diǎn),可能在大??雌饋?lái)都是些很容易不起眼的功能,但是對(duì)應(yīng)我這個(gè)初學(xué)者來(lái)說(shuō)還是有點(diǎn)困難的,希望如果有大??戳宋业囊恍┕δ艿膶?shí)現(xiàn)之后我不會(huì)被罵死。
值得注意的一點(diǎn)
做過(guò)小程序開(kāi)發(fā)或者是vue等開(kāi)發(fā)的人一定聽(tīng)過(guò)事件冒泡這個(gè)名詞:子元素的事件觸發(fā)了父元素的事件,例如點(diǎn)擊事件。我就是那個(gè)幸運(yùn)鵝,我在開(kāi)發(fā)的時(shí)候就遇到了這個(gè)情況。
在購(gòu)物車中點(diǎn)擊商品可以跳轉(zhuǎn)商品詳情,但是我一開(kāi)始把跳轉(zhuǎn)事件綁定在了每個(gè)商品卡片上,這樣就導(dǎo)致了點(diǎn)擊修改商品數(shù)量的時(shí)候修改了數(shù)字但是也會(huì)直接跳轉(zhuǎn)商品詳情,比如下面這樣...
這就很不友好了,用戶體驗(yàn)很差,關(guān)于事件冒泡,微信小程序的解決方法是把bindtap替換成catchtap,這樣可以阻止子元素事件向上冒泡。
然而巧的是,我就是那個(gè)最幸運(yùn)的鵝,步進(jìn)器我用的是有贊Vant Weapp組件庫(kù)里的,我搜索了很多資料都沒(méi)有找到有效的解決方案,差點(diǎn)就放棄使用組件庫(kù)了,好在最后發(fā)現(xiàn)京東優(yōu)選小程序購(gòu)物車綁定的跳轉(zhuǎn)事件是在商品的圖片和標(biāo)題上。
這一點(diǎn)還是比較重要的,所以大家在開(kāi)發(fā)的時(shí)候一定要考慮事件的冒泡,這也是我把它放在最后來(lái)寫的原因。
寫在后面
最后,我想說(shuō)的是小程序開(kāi)發(fā)真的不容易,開(kāi)發(fā)一個(gè)好的小程序更是需要考慮性能和用戶體驗(yàn)的方方面面。當(dāng)我覺(jué)得自己第一個(gè)小程序差不多要完工的時(shí)候真的要跳起來(lái)唱joyful了(誤)。作為一個(gè)程序猿真的不容易,難怪是個(gè)容易掉發(fā)的群體。但好在愿意分享技術(shù)的人很多,在這次開(kāi)發(fā)的過(guò)程中我也查閱了很多的資料、社區(qū)和文檔。小程序的學(xué)習(xí)我也不會(huì)停下腳步,這個(gè)項(xiàng)目還有非常多做的不好的地方,我發(fā)出來(lái)也是希望大家和我進(jìn)行交流分享,后期我也會(huì)繼續(xù)完善優(yōu)化這個(gè)小程序項(xiàng)目。希望我的作品可以對(duì)那些初學(xué)小程序的人有所幫助。
最后附上我的github項(xiàng)目地址:https://github.com/tearill/jd_recommend
以上是“京東優(yōu)選小程序如何實(shí)現(xiàn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。