京東優(yōu)選小程序的實現(xiàn)代碼示例
寫在前面
過年有大把的時光,為何一直宅在家里不出家門看著電腦,這究竟是道德的淪喪還是人性的泯滅...這一切都還得從一只蝙蝠說起...
咳咳,好了不皮了,言歸正傳。微信推出的小程序可謂是輕量又強大,所以最近我也開始了小程序的學(xué)習(xí),學(xué)了挺多也看了很多文檔,但總覺得自己沒學(xué)到什么,感覺很迷茫。正所謂實踐出真知,所以我選擇了從高仿別人的小程序開始,選來選去最后選擇了京東優(yōu)選這個小程序(絕對不是因為它的界面清爽!)。
開發(fā)工具
效果速覽
廢話不多說,咱先來搞一波圖片看看,點這里查看更多圖片
項目結(jié)構(gòu)
這個項目我使用的是普通的開發(fā),把所有的數(shù)據(jù)都放在了json-server中模擬。
可能很多人會覺得很奇怪,但這是因為我發(fā)現(xiàn)easy mock的網(wǎng)站經(jīng)常打不開請求失敗非常的不方便,所以我暫時沒有選擇mock數(shù)據(jù),后期有時間我會把數(shù)據(jù)挪到easy mock上。
|-jd_recommend 項目名 |-api 模擬數(shù)據(jù)接口 |-db.json 模擬的數(shù)據(jù) |-assets 資源文件 |-icons 圖標(biāo)資源 |-images 圖片資源 |-components 組件模塊 |-navigationBar 自定義導(dǎo)航欄 |-toast 自定義toast |-stepper 有贊vant步進(jìn)器組件 |-... 其他小程序所需組件 |-pages 項目頁面 |-about 關(guān)于頁面 |-account 我的訂單頁面 |-afterMarket 售后類型頁面 |-appointment 我的預(yù)約頁面 |-buy 填寫訂單信息頁面 |-commentDetail 評論詳情頁面 |-discount 優(yōu)惠券頁面 |-explore 發(fā)現(xiàn)頁面 |-feedback 反饋頁面 |-fix 售后頁面 |-goodsDetail 值得買優(yōu)惠詳情頁面 |-index 首頁 |-jd 京東商品詳情頁面 |-login 登錄頁面 |-orderDetail 訂單詳情頁面 |-seller 客服頁面 |-service 退換/售后頁面 |-shopCart 購物車頁面 |-user 個人中心頁面 |-style 公共樣式 |-comment.wxss 評論區(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,微信小程序開發(fā)內(nèi)置了這個組件,可以直接在app.json中配置。但是,自帶的navigationBar的樣子是固定的,你肯定見過長成下面這樣的navigationBar:
相比平時常見的navigationBar,它左上角多了一個返回主頁的按鈕,這對于有多級頁面的小程序來說是非常必要的,不然訪問的層級太深用戶不知道怎么返回主頁。然而,小程序開發(fā)自帶并沒有這個樣子的,好在可以自定義,接下來我們就來自定義一個。
navigationBar
首先,我們構(gòu)建一下頁面的結(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}}' 是控制左上角按鈕的顯示隱藏,首頁不顯示 --> <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>
這就是一個很普通的頁面結(jié)構(gòu),值得注意的是,它的高度是根據(jù)獲取的設(shè)備的高度來確定的。
接下來又到了切圖仔上線的時候了(誤):
/* components/navigationBar/index.wxss */ /* 頂部要固定定位 標(biāo)題要居中 自定義按鈕和標(biāo)題要和右邊微信原生的膠囊上下對齊 */ .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 由父頁面?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)航欄的高度 方便對齊 this.setData({ height: app.globalData.height }) }, methods: { // 返回上一頁面 _navback() { wx.navigateBack() }, //返回到首頁 _backhome() { wx.switchTab({ url: '/pages/index/index', }) } } })
京東優(yōu)選小程序這里的兩個按鈕都是返回首頁,我在開發(fā)的時候覺得不對勁,所以我改過來了。
在這里還去取了一下全局定義的變量,也就是獲取的設(shè)備頂部窗口的高度(不同設(shè)備窗口高度不一樣,根據(jù)這個來設(shè)置自定義導(dǎo)航欄的高度),在app.js中要定義一下:
app.js App({ onLaunch: function () { ...... wx.getSystemInfo({ success: (res) => { this.globalData.height = res.statusBarHeight } }) }, globalData: { ... height: 0 } })
記得自定組件的時候一定要在json中寫成自定義組件
// components/navigationBar/index.json { "component": true }
接下來就是調(diào)用該組件了
<navigationBar navbar-data='{{navbarData}}'></navigationBar>
別忘了在要引用頁面的json中引入該組件
"usingComponents": { "navigationBar": "../../components/navigationBar/index" }
Toast
Toast同樣也是小程序開發(fā)已經(jīng)做好給你用的了,雖然它可以支持替換里面的圖標(biāo),但是你會發(fā)現(xiàn)很雞肋的一點是,如果你想顯示兩行文字你就沒辦法做到了。我在開發(fā)過程中也搜索過相關(guān)的實現(xiàn)方法,找到了大部分是說在要換行的文字后背加上rn就能實現(xiàn)了,但是我自己親測無效,所以實在忍不住也自己做了一個。
<!-- components/toast/index.wxml --> <!-- 距離頂部高度由業(yè)務(wù)需要動態(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: { //用來顯示提示信息 type: Object, // 類型(必填),目前接受的類型包括:String, Number, Boolean, Object, Array, null(表示任意類型) value: { icon: 'success' } // 屬性初始值(可選),如果未指定則會根據(jù)類型選擇一個 }, }, 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); } } })
這里給組件定義了兩個方法,是用來顯示和隱藏Toast的。這里要注意一下,調(diào)用給自定義組件定義方法要先在頁面上獲取該組件
<toast id="toast" toast-data="{{toastData}}"></toast>
Page({ data: { toastData: { // toast需要的參數(shù) icon: "success", info1: "加入購物車成功", top: "50%" } }, onReady() { this.toast = this.selectComponent("#toast"); } })
然后在需要觸發(fā)Toast的事件中寫上這兩句:
this.toast.showToast() this.toast.hideToast()
功能實現(xiàn)
導(dǎo)航
所謂導(dǎo)航,也是很常見了,就是根據(jù)選擇欄目的不同,顯示不同的類別內(nèi)容。例如:
功能要求:
- 點擊導(dǎo)航欄目,顯示對應(yīng)的欄目數(shù)據(jù)。
- 如果欄目中沒有東西,要顯示對應(yīng)的提示信息。
實現(xiàn)它的功能并不難,直接sroll-view往上懟。個人覺得,京東優(yōu)選在這里有一點不足的地方就是,如果點擊了偏右側(cè)的導(dǎo)航欄目的話,導(dǎo)航條不會跟著右移顯示后面的項目,可能它的開發(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>
通過js可以實現(xiàn)動態(tài)的填放數(shù)據(jù),這里設(shè)置的current就是當(dāng)前選擇的欄目,可以根據(jù)這個改變樣式等。
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 }); }
如果是要實現(xiàn)點擊之后自動向點擊的方法滑出顯示更多的內(nèi)容,可以通過動態(tài)改變navScrollLeft的值去實現(xiàn),這里我就不細(xì)說了,不過我在實現(xiàn)的時候還是花了一番功夫,實現(xiàn)的不是很好所以就沒有放在代碼里,如果你以后想做出這種效果的導(dǎo)航欄建議去網(wǎng)上搜一搜demo看懂了之后借過來用一用,畢竟傳說程序猿最高的境界是復(fù)制粘貼,狗頭(誤)
上拉菜單和篩選框
這兩個比較相似,只是拉出的位置不一樣,這里我就舉一個篩選框的例子,我們先看看它長啥樣:
我們先看看結(jié)構(gòu),這里我省略了中間的一些內(nèi)容:
<!-- 點擊篩選彈出的選擇菜單 --> <view class="float {{isRuleTrue?'isRuleShow':'isRuleHide'}}"> <view class="animation-element" animation="{{animation}}"> ...中間自己放的具體內(nèi)容... <!-- 底部的兩個按鈕 --> <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; }
重點是它的顯示和隱藏事件,需要用到animation,如果有不熟悉animation,可以去參考一些資料,或者是官方文檔。同樣,我也去掉了我實現(xiàn)其他業(yè)務(wù)的一些內(nèi)容。
showSelect() { // 顯示選擇菜單 this.setData({ isRuleTrue: true }) // 左偏移245 step表示一個動作的開始 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() }) },
購物車邏輯
要實現(xiàn)這樣的效果并不困難,需要自己思路清晰,不能被繞進(jìn)去了。實現(xiàn)加入購物車并不難,細(xì)節(jié)是購物車圖標(biāo)右上角的數(shù)字要根據(jù)加入購物車的數(shù)量進(jìn)行動態(tài)的改變,還要注意如果是同一件商品就不需要添加新的,只需要修改原來的數(shù)量。
在這里我使用的是小程序的wx.setStorage()實現(xiàn)的:
<view class='bottom'> <view class="animation-reset" bindtap="addCart">加入購物車</view> <view class="animation-button" bindtap="buy">立即購買</view> </view>
addCart() { // 加入購物車 this.setData({ toastData: { // toast需要的參數(shù) icon: "success", info1: "加入購物車成功", top: "50%" } }) this.toast.showToast() this.toast.hideToast() this.hideModal() // 真正實現(xiàn)添加購物車的部分 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) { // 沒找到 添加新的商品信息進(jìn)購物車 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ù)計算總價 } cartData.push(data) } // 刷新購物車圖標(biāo)上的數(shù)量 let allNum = 0 cartData.forEach(val => { allNum += val.num }); this.setData({ allNum }) wx.setStorage({ key: 'cart', data: cartData }) },
這里你可以根據(jù)自己的開發(fā)來決定方式,如果你使用的是云開發(fā)的話,可以選擇把數(shù)據(jù)存進(jìn)云數(shù)據(jù)庫里。
回到頂部
這也是一個老生常談的功能,當(dāng)你滑到頁面比較后的位置的時候需要快速回頂。這里要記住,用swiper實現(xiàn)。首先是在頁面上擼一個回到頂部的圖標(biāo)出來:
<!-- 滑動一段距離后顯示返回頂部的按鈕 --> <scroll-view class="bigWrap" scroll-y="true" scroll-top="{{scrollTop}}" bindscroll="scroll" style="position: absolute; left: 0; top:0; bottom: 0; right: -999rpx;"> <view class="goTop" bindtap="goTop" wx:if="{{&& floorstatus}}"> <image class="icon_goTop" src="../../assets/icons/back_to_top.png"></image> </view> </scroll-view>
{{scrollTop}}用來表示滑動的時候距離頂部的位置。它的樣式也很簡單,使用固定定位把它定在屏幕上,這里一定要注意頁面的層級,不然它可能會被其他組件給遮擋掉!
/* 回到頂部 */ .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)滑到了一定距離的時候它才顯示出來,這就要靠swiper綁定的滾動事件了:
scroll(e) { // 滾動事件 // 容器滾動時將此時的滾動距離賦值給 this.data.scrollTop let floorstatus = false if (e.detail.scrollTop > 300) { floorstatus = true } this.setData({ floorstatus }) }
功能大致先說這么一點,可能在大??雌饋矶际切┖苋菀撞黄鹧鄣墓δ?,但是對應(yīng)我這個初學(xué)者來說還是有點困難的,希望如果有大牛看了我的一些功能的實現(xiàn)之后我不會被罵死。
值得注意的一點
做過小程序開發(fā)或者是vue等開發(fā)的人一定聽過事件冒泡這個名詞:子元素的事件觸發(fā)了父元素的事件,例如點擊事件。我就是那個幸運鵝,我在開發(fā)的時候就遇到了這個情況。
在購物車中點擊商品可以跳轉(zhuǎn)商品詳情,但是我一開始把跳轉(zhuǎn)事件綁定在了每個商品卡片上,這樣就導(dǎo)致了點擊修改商品數(shù)量的時候修改了數(shù)字但是也會直接跳轉(zhuǎn)商品詳情,比如下面這樣...
這就很不友好了,用戶體驗很差,關(guān)于事件冒泡,微信小程序的解決方法是把bindtap替換成catchtap,這樣可以阻止子元素事件向上冒泡。
然而巧的是,我就是那個最幸運的鵝,步進(jìn)器我用的是有贊Vant Weapp組件庫里的,我搜索了很多資料都沒有找到有效的解決方案,差點就放棄使用組件庫了,好在最后發(fā)現(xiàn)京東優(yōu)選小程序購物車綁定的跳轉(zhuǎn)事件是在商品的圖片和標(biāo)題上。
這一點還是比較重要的,所以大家在開發(fā)的時候一定要考慮事件的冒泡,這也是我把它放在最后來寫的原因。
寫在后面
最后,我想說的是小程序開發(fā)真的不容易,開發(fā)一個好的小程序更是需要考慮性能和用戶體驗的方方面面。當(dāng)我覺得自己第一個小程序差不多要完工的時候真的要跳起來唱joyful了(誤)。作為一個程序猿真的不容易,難怪是個容易掉發(fā)的群體。但好在愿意分享技術(shù)的人很多,在這次開發(fā)的過程中我也查閱了很多的資料、社區(qū)和文檔。小程序的學(xué)習(xí)我也不會停下腳步,這個項目還有非常多做的不好的地方,我發(fā)出來也是希望大家和我進(jìn)行交流分享,后期我也會繼續(xù)完善優(yōu)化這個小程序項目。希望我的作品可以對那些初學(xué)小程序的人有所幫助。
最后附上我的github項目地址:https://github.com/tearill/jd_recommend
如果你覺得這個項目還不錯或者是對你有所幫助的話歡迎star,你點亮的每一個star將都是我前進(jìn)的動力!
到此這篇關(guān)于京東優(yōu)選小程序的實現(xiàn)代碼示例的文章就介紹到這了,更多相關(guān)京東優(yōu)選小程序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS前端知識點總結(jié)之內(nèi)置對象,日期對象和定時器相關(guān)操作
這篇文章主要介紹了JS前端知識點總結(jié)之內(nèi)置對象,日期對象和定時器相關(guān)操作,簡單總結(jié)分析了JS內(nèi)置對象,日期對象和定時器相關(guān)對象方法及使用技巧,需要的朋友可以參考下2019-07-07開發(fā) Internet Explorer 右鍵功能表(ContextMenu)
本篇介紹如何開發(fā) Internet Explorer 右鍵功能表(ContextMenu),以 0rz.tw 縮短網(wǎng)址列為范例2013-07-07JavaScript設(shè)計模式之職責(zé)鏈模式詳解
職責(zé)鏈模式的定義是:使多個對象都有機會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它為止2022-08-08js實現(xiàn)兼容性好的微軟官網(wǎng)導(dǎo)航下拉菜單效果
這篇文章主要介紹了js實現(xiàn)兼容性好的微軟官網(wǎng)導(dǎo)航下拉菜單效果,涉及JavaScript基于鼠標(biāo)事件實現(xiàn)頁面樣式變換的技巧,非常具有實用價值,需要的朋友可以參考下2015-09-09