摘要:簡介這是一個(gè)完整的已經(jīng)線上運(yùn)行的天氣應(yīng)用小程序,點(diǎn)擊可查看源碼,可隨意。但是對于一款簡單的天氣應(yīng)用小程序來說也夠了。
更新說明:
I、氣象數(shù)據(jù)由百度地圖開放平臺)修改為了和風(fēng)天氣,需要注冊賬號獲取 key;
II、d0e51c8 版本之后為小程序云開發(fā)版本,若未開通云開發(fā)功能,為不影響小程序正常運(yùn)行,可以將版本號回退到 git reset d0e51c8 --hard,或,將云開發(fā)相關(guān)代碼注釋掉。具體可查看這里。
簡介這是一個(gè)完整的已經(jīng)線上運(yùn)行的天氣應(yīng)用小程序,點(diǎn)擊可查看源碼,可隨意 star。也可以掃描下方的小程序碼直接體驗(yàn)。
新版首頁(可選擇內(nèi)置背景)
效果圖:
地理編碼、天氣數(shù)據(jù)均來自百度地圖開放平臺。個(gè)人開發(fā)完全免費(fèi),有對應(yīng)的小程序 sdk,加入即可,但是返回的天氣數(shù)據(jù)較少。
運(yùn)行前準(zhǔn)備利益相關(guān)注冊微信小程序,獲取 appid
注冊百度地圖開放平臺開發(fā)者,創(chuàng)建應(yīng)用,獲取 ak(其他配置自行查看)
在 app.js 中替換 globalData 中的 ak 為自己的 ak
Run
無
天氣數(shù)據(jù)獲取因?yàn)橹皇且粋€(gè)個(gè)人版DEMO(完整版),開發(fā)前就決定選擇免費(fèi)的天氣數(shù)據(jù)(個(gè)人開發(fā)免費(fèi)),懶得去尋找其他的天氣數(shù)據(jù),懶得去注冊賬號,就直接選擇了百度地圖開放平臺的天氣數(shù)據(jù),正好也提供了小程序?qū)?yīng)的 sdk,但是可能相比于其他的天氣 API,百度返回的數(shù)據(jù)偏少:當(dāng)天 pm2.5、當(dāng)天和未來三天數(shù)據(jù)、當(dāng)天生活指數(shù),其他的就沒有了。但是對于一款簡單的天氣應(yīng)用小程序來說也夠了。
地理編碼獲取天氣數(shù)據(jù)默認(rèn)返回當(dāng)前城市的天氣數(shù)據(jù),如果要獲取其他的城市的天氣數(shù)據(jù),需要傳入經(jīng)緯度。為了獲取其他城市的經(jīng)緯度,這里使用的地圖的地理編碼接口,輸入城市名,輸出經(jīng)緯度,然后調(diào)用獲取天氣數(shù)據(jù) API 即可。
具體實(shí)現(xiàn)該應(yīng)用只有五個(gè)個(gè)頁面:首頁、城市選擇頁、設(shè)置頁、關(guān)于頁、系統(tǒng)信息頁(展示頁)。如下:
首頁首頁最終的顯示效果是這個(gè)樣子:
從上到下依次是:其他城市天氣搜索、當(dāng)前城市數(shù)據(jù)展示、當(dāng)天和未來三天天氣數(shù)據(jù)展示、當(dāng)天生活指數(shù)展示、footer。下拉刷新會刷新當(dāng)前地區(qū)的天氣數(shù)據(jù)。其中,頂部城市天氣搜索和生活指數(shù)可以在設(shè)置中隱藏。屏幕右下角是一個(gè)可以移動的懸浮球(片??)菜單,點(diǎn)擊后會彈出城市選擇、設(shè)置、關(guān)于頁面的入口。背景色默認(rèn)是 #40a7e7 純色,可在設(shè)置中更換背景圖,未來三天天氣預(yù)報(bào)和生活指數(shù)分別添加了透明的黑色背景。設(shè)計(jì)稿?沒有的,純?nèi)庋壅{(diào)試,直到自己看著舒服。
主頁面先定義一個(gè)方法獲取當(dāng)前地區(qū)的天氣數(shù)據(jù):
init(params) { let that = this let BMap = new bmap.BMapWX({ ak: globalData.ak, }) BMap.weather({ location: params.location, fail: that.fail, success: that.success, }) },
ak 請?zhí)鎿Q為自己的 ak,因?yàn)樾枰@取用戶的地理位置,所以在 fail 的回調(diào)中需要處理用戶拒絕獲取地理位置的邏輯,這里處理為:提示打開地理位置授權(quán),3000ms 后 wx.openSetting() 跳轉(zhuǎn)到小程序設(shè)置頁,如下:
fail (res) { wx.stopPullDownRefresh() let errMsg = res.errMsg || "" // 拒絕授權(quán)地理位置權(quán)限 if (errMsg.indexOf("deny") !== -1 || errMsg.indexOf("denied") !== -1) { wx.showToast({ title: "需要開啟地理位置權(quán)限", icon: "none", duration: 3000, success (res) { let timer = setTimeout(() => { clearTimeout(timer) wx.openSetting({}) }, 3000) }, }) } else { wx.showToast({ title: "網(wǎng)絡(luò)不給力,請稍后再試", icon: "none", }) } },
獲取到用戶的地理位置后,執(zhí)行 success:
success (data) { wx.stopPullDownRefresh() let now = new Date() // 存下來源數(shù)據(jù) data.updateTime = now.getTime() data.updateTimeFormat = utils.formatDate(now, "MM-dd hh:mm") let results = data.originalData.results[0] || {} data.pm = this.calcPM(results["pm25"]) // 當(dāng)天實(shí)時(shí)溫度 data.temperature = `${results.weather_data[0].date.match(/d+/g)[2]}` wx.setStorage({ key: "cityDatas", data: data, }) this.setData({ cityDatas: data, }) },
看一下返回的天氣數(shù)據(jù)格式:
{ "error": 0, "status": "success", "date": "2018-06-29", "results": [ { "currentCity": "北京市", "pm25": "55", "index": [ { "des": "天氣炎熱,建議著短衫、短裙、短褲、薄型T恤衫等清涼夏季服裝。", "zs": "炎熱", "tipt": "穿衣指數(shù)", "title": "穿衣" }, { "des": "較適宜洗車,未來一天無雨,風(fēng)力較小,擦洗一新的汽車至少能保持一天。", "zs": "較適宜", "tipt": "洗車指數(shù)", "title": "洗車" }, { "des": "各項(xiàng)氣象條件適宜,發(fā)生感冒機(jī)率較低。但請避免長期處于空調(diào)房間中,以防感冒。", "zs": "少發(fā)", "tipt": "感冒指數(shù)", "title": "感冒" }, { "des": "天氣較好,無雨水困擾,但考慮氣溫很高,請注意適當(dāng)減少運(yùn)動時(shí)間并降低運(yùn)動強(qiáng)度,運(yùn)動后及時(shí)補(bǔ)充水分。", "zs": "較不宜", "tipt": "運(yùn)動指數(shù)", "title": "運(yùn)動" }, { "des": "屬中等強(qiáng)度紫外線輻射天氣,外出時(shí)建議涂擦SPF高于15、PA+的防曬護(hù)膚品,戴帽子、太陽鏡。", "zs": "中等", "tipt": "紫外線強(qiáng)度指數(shù)", "title": "紫外線強(qiáng)度" } ], "weather_data": [ { "date": "周五 06月29日 (實(shí)時(shí):34℃)", "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png", "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/qing.png", "weather": "多云轉(zhuǎn)晴", "wind": "東南風(fēng)微風(fēng)", "temperature": "38 ~ 25℃" }, { "date": "周六", "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png", "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png", "weather": "多云", "wind": "東南風(fēng)微風(fēng)", "temperature": "36 ~ 23℃" }, { "date": "周日", "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/qing.png", "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/qing.png", "weather": "晴", "wind": "東南風(fēng)微風(fēng)", "temperature": "35 ~ 23℃" }, { "date": "周一", "dayPictureUrl": "http://api.map.baidu.com/images/weather/day/qing.png", "nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png", "weather": "晴轉(zhuǎn)多云", "wind": "南風(fēng)微風(fēng)", "temperature": "35 ~ 25℃" } ] } ] }
success 里緩存了最新一次獲取的天氣數(shù)據(jù)+更新的時(shí)間 cityDatas,小程序的模板里無法使用方法,所以數(shù)據(jù)需要在 js 里面先格式化。calcPM 用來計(jì)算當(dāng)前 pm2.5 的質(zhì)量,返回“優(yōu)良差”類似字樣,范圍標(biāo)準(zhǔn)可自行搜索。當(dāng)天的實(shí)時(shí)溫度并沒有給出獨(dú)立的字段,而是混在了 wearther_data[0] 的 data 字段里:"date": "周五 06月29日 (實(shí)時(shí):34℃)",需要自行提取。返回的天氣 icon 和色調(diào)不搭,就沒有使用。其他的數(shù)據(jù)按照按照我們要顯示的格式直接填充即可。
城市天氣搜索獲取天氣數(shù)據(jù)傳參為經(jīng)緯度,所以搜索城市天氣時(shí),需先將城市轉(zhuǎn)換為對應(yīng)的經(jīng)緯度,然后調(diào)用獲取天氣數(shù)據(jù) API 即可。獲取經(jīng)緯度的 API 為:
https://api.map.baidu.com/geocoder/v2/?address=${address}&output=json&ak=${yourak}
返回的數(shù)據(jù)格式為:
{ "status":0, "result":{ "location":{ "lng":117.21081309155257, "lat":39.143929903310074 }, "precise":0, "confidence":12, "level":"城市" } }
然后直接調(diào)用獲取天氣 API 即可。具體代碼如下:
geocoder (address, success) { let that = this wx.request({ url: getApp().setGeocoderUrl(address), success (res) { let data = res.data || {} if (!data.status) { let location = (data.result || {}).location || {} // location = {lng, lat} success && success(location) } else { wx.showToast({ title: data.msg || "網(wǎng)絡(luò)不給力,請稍后再試", icon: "none", }) } }, fail (res) { wx.showToast({ title: res.errMsg || "網(wǎng)絡(luò)不給力,請稍后再試", icon: "none", }) }, complete () { that.setData({ searchText: "", }) }, }) }, search (val) { // 動畫 if (val === "520" || val === "521") { this.setData({ searchText: "", }) this.dance() return } wx.pageScrollTo({ scrollTop: 0, duration: 300, }) if (val) { let that = this this.geocoder(val, (loc) => { that.init({ location: `${loc.lng},${loc.lat}` }) }) } },搜索動畫彩蛋
在搜索框里搜索 520 或 521,會出現(xiàn)從頂部下小心心的動畫,如下:
這里實(shí)現(xiàn)比較簡單。
創(chuàng)建了一個(gè) heartbeat 的組件。wxml 結(jié)構(gòu)是遍歷數(shù)組,創(chuàng)建多個(gè)大小、位置隨機(jī)的圖片:
然后使用的是小程序提供的 wx.createAnimation,動畫的使用比較簡單,創(chuàng)建動畫,然后賦予 animation 屬性即可,比較簡單,但是也有局限性,比如,沒有直接的動畫結(jié)束后的回調(diào),但是可以使用 setTimeout 來實(shí)現(xiàn)等。這里會用到可用窗口寬高,因?yàn)槎嗵幱玫搅嗽搮?shù),所以在 app.js 里面異步獲取了先。
動畫代碼如下:
dance (callback) { let windowWidth = this.data.windowWidth let windowHeight = this.data.windowHeight let duration = this.data.duration let animations = [] let lefts = [] let tops = [] let widths = [] let obj = {} for (let i = 0; i < this.data.arr.length; i++) { lefts.push(Math.random() * windowWidth) tops.push(-140) widths.push(Math.random() * 50 + 40) let animation = wx.createAnimation({ duration: Math.random() * (duration - 1000) + 1000 }) animation.top(windowHeight).left(Math.random() * windowWidth).rotate(Math.random() * 960).step() animations.push(animation.export()) } this.setData({ lefts, tops, widths, }) let that = this let timer = setTimeout(() => { that.setData({ animations, }) clearTimeout(timer) }, 200) let end = setTimeout(() => { callback && callback() clearTimeout(end) }, duration) }, },
首頁搜索特定關(guān)鍵詞后,調(diào)用組件 dance 方法即觸發(fā)小心心動畫。
懸浮球菜單屏幕右下角的懸浮球提供了三個(gè)頁面的入口:城市選擇頁、設(shè)置頁、關(guān)于頁。菜單彈出、收回會有動畫。
這里的動畫分為彈出和收起,兩者寫起來基本上一樣的,只是動畫的參數(shù)不一樣。這里貼出彈出的動畫:
// wxml// js popp() { let animationMain = wx.createAnimation({ duration: 200, timingFunction: "ease-out" }) let animationOne = wx.createAnimation({ duration: 200, timingFunction: "ease-out" }) let animationTwo = wx.createAnimation({ duration: 200, timingFunction: "ease-out" }) let animationThree = wx.createAnimation({ duration: 200, timingFunction: "ease-out" }) animationMain.rotateZ(180).step() animationOne.translate(-50, -60).rotateZ(360).opacity(1).step() animationTwo.translate(-90, 0).rotateZ(360).opacity(1).step() animationThree.translate(-50, 60).rotateZ(360).opacity(1).step() this.setData({ animationMain: animationMain.export(), animationOne: animationOne.export(), animationTwo: animationTwo.export(), animationThree: animationThree.export(), }) },
懸浮菜單是可以在屏幕上隨意滑動的,方法也很簡單,監(jiān)聽 touchmove 事件即可,因?yàn)椴藛握归_方向是在左邊,所以懸浮菜單能往左邊移動的最遠(yuǎn)距離要有一段間隔,否則展開的菜單就進(jìn)入左邊屏幕了,移動到上方同樣邏輯(后期可以改成菜單展開方向隨移動而改變,而不是一味在左邊展開)。
代碼如下:
menuMainMove (e) { // 如果已經(jīng)彈出來了,需要先收回去,否則會受 top、left 會影響 if (this.data.hasPopped) { this.takeback() this.setData({ hasPopped: false, }) } let windowWidth = SYSTEMINFO.windowWidth let windowHeight = SYSTEMINFO.windowHeight let touches = e.touches[0] let clientX = touches.clientX let clientY = touches.clientY // 邊界判斷 if (clientX > windowWidth - 40) { clientX = windowWidth - 40 } if (clientX <= 90) { clientX = 90 } if (clientY > windowHeight - 40 - 60) { clientY = windowHeight - 40 - 60 } if (clientY <= 60) { clientY = 60 } let pos = { left: clientX, top: clientY, } this.setData({ pos, }) },
至于一些樣式、邏輯上的細(xì)節(jié),這里不再贅述,具體可查看源碼。
城市選擇頁城市選擇頁面就是一個(gè)城市列表,如下:
點(diǎn)擊相應(yīng)的城市,跳轉(zhuǎn)到首頁獲取所選城市的天氣數(shù)據(jù)。這里的城市數(shù)據(jù)是這樣的格式無序的列表:
{ "letter": "B", "name": "北京市" }
因?yàn)樾枰凑兆帜概帕羞M(jìn)行排序,所以需要先排序再遍歷(城市數(shù)據(jù)是之前用過的數(shù)據(jù),沒有排序就直接粘過來了)。代碼如下:
// 按照字母順序生成需要的數(shù)據(jù)格式 getSortedAreaObj(areas) { // let areas = staticData.areas areas = areas.sort((a, b) => { if (a.letter > b.letter) { return 1 } if (a.letter < b.letter) { return -1 } return 0 }) let obj = {} for (let i = 0, len = areas.length; i < len; i++) { let item = areas[i] delete item.districts let letter = item.letter if (!obj[letter]) { obj[letter] = [] } obj[letter].push(item) } // 返回一個(gè)對象,直接用 wx:for 來遍歷對象,index 為 key,item 為 value,item 是一個(gè)數(shù)組 return obj },
點(diǎn)擊城市后,需要通知首頁“我已經(jīng)切換城市了,麻煩獲取下這個(gè)城市的數(shù)據(jù)謝謝”,這里使用的是使用 getCurrentPages 獲取頁面堆棧,修改首頁數(shù)據(jù)的方式。代碼如下:
choose(e) { let item = e.currentTarget.dataset.item let name = item.name let pages = getCurrentPages() let len = pages.length let indexPage = pages[len - 2] indexPage.setData({ // 是否切換了城市 cityChanged: true, // 需要查詢的城市 searchCity: name, }) wx.navigateBack({}) },關(guān)于頁
關(guān)于頁是一個(gè)展示頁,沒有多少交互,使用到的 API 只有復(fù)制到剪切板 wx.setClipboardData。“微信快速聯(lián)系”使用的是小程序提供的聯(lián)系客服的方式,將 button 絕對定位隱藏到點(diǎn)擊區(qū)域的下方即可。有精力的話,可以自己搭建服務(wù),將小程序的消息 push 到自己的服務(wù)上去。
設(shè)置頁設(shè)置頁的功能看著有點(diǎn)多,其實(shí)并不多,只是一堆 API 的調(diào)用。這個(gè)頁面分了自定義、檢查更新、小工具、清除數(shù)據(jù)三個(gè)部分。各個(gè)設(shè)置參數(shù)保存在 storage 中。一個(gè)一個(gè)來說。
1. 自定義自定義首頁背景
自定義背景是將選取的圖片(wx.chooseImage)保存(wx.saveFile)到本地,然后首頁獲取(wx.getSavedFileList)保存的圖片,在首頁展示出來即可。長按刪除,則是獲取(wx.getSavedFileList)保存的圖片,然后 wx.removeSavedFile 掉即可。現(xiàn)在設(shè)置的是本地只保存一張圖片,所以重新設(shè)置其他背景時(shí),會刪除上一張背景圖,然后重新保存新背景圖。
實(shí)現(xiàn)如下:
defaultBcg () { this.removeBcg(() => { wx.showToast({ title: "恢復(fù)默認(rèn)背景", duration: 1500, }) }) }, removeBcg (callback) { wx.getSavedFileList({ success: function (res) { let fileList = res.fileList let len = fileList.length if (len > 0) { for (let i = 0; i < len; i++) (function (path) { wx.removeSavedFile({ filePath: path, complete: function (res) { if (i === len - 1) { callback && callback() } } }) })(fileList[i].filePath) } else { callback && callback() } }, fail: function () { wx.showToast({ title: "出錯(cuò)了,請稍后再試", icon: "none", }) }, }) }, customBcg () { let that = this wx.chooseImage({ success: function (res) { that.removeBcg(() => { wx.saveFile({ tempFilePath: res.tempFilePaths[0], success: function (res) { wx.navigateBack({}) }, }) }) }, fail: function (res) { let errMsg = res.errMsg // 如果是取消操作,不提示 if (errMsg.indexOf("cancel") === -1) { wx.showToast({ title: "發(fā)生錯(cuò)誤,請稍后再試", icon: "none", }) } }, }) },
打開頂部城市天氣快捷搜索
該操作只是將首頁的頂部搜索 wx:if 掉而已。switch 組件的樣式可以通過修改默認(rèn)的類來修改,調(diào)一個(gè)自己滿意的即可:
.wx-switch-input{width:84rpx !important;height:43rpx !important;} .wx-switch-input::before{width:82rpx !important;height: 38rpx !important;} .wx-switch-input::after{width: 38rpx !important;height: 38rpx !important;}
顯示生活指數(shù)信息
同樣 wx:if 掉。
檢查更新
檢查更新默認(rèn)關(guān)閉。小程序的更新是在冷啟動時(shí)去檢查,如果有新版本會異步下載,再次冷啟動時(shí)會加載新版本。這里使用 wx.getUpdateManager,因?yàn)樵?API 基礎(chǔ)庫支持最低版本是 1.9.90,基礎(chǔ)庫版本低的會提示不支持,顯示的文案也會相應(yīng)修改。
小工具
1)NFC
使用 wx.getHCEState。
2)屏幕亮度
獲取屏幕亮度、設(shè)置屏幕亮度、保持常亮使用的 API 分別是 wx.getScreenBrightness、wx.setScreenBrightness、wx.setKeepScreenOn。完整實(shí)現(xiàn)可查看源碼。
3)系統(tǒng)信息
系統(tǒng)信息會跳轉(zhuǎn)到新頁面。
清除數(shù)據(jù)
1)首頁懸浮球復(fù)位
首頁懸浮球的位置信息是保存本地的變量 pos,復(fù)位位置,清除 pos 即可。
2)恢復(fù)初始化設(shè)置
設(shè)置信息是保存本地的變量 setting,復(fù)位位置,清除 setting 即可。
3)清除所有本地?cái)?shù)據(jù)
wx.clearStorage 即可。
Tip: 恢復(fù)初始化設(shè)置、清除所有本地?cái)?shù)據(jù)并沒有刪除設(shè)置的背景圖(如果有設(shè)置的話),這個(gè)后續(xù)可以加上。
其他其他代碼細(xì)節(jié),不再贅述,具體可查看源碼。
更新日志:
2018.07.04
城市選擇頁面添加城市列表搜索過濾功能
2018.07.05
openSetting API 廢棄兼容處理(SDKVersion >= 2.0.7 使用 button,引導(dǎo)用戶主動打開小程序設(shè)置頁面),如下:
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96021.html
摘要:利益相關(guān)無說明該小程序代碼已開源,點(diǎn)擊可查看源碼,可隨意。也可以先掃描下方的小程序碼直接體驗(yàn)。寫在前面前段時(shí)間寫了一個(gè)簡單的小程序,源碼在這里,具體實(shí)現(xiàn)相關(guān)可查看這篇文章兩天擼一個(gè)天氣應(yīng)用微信小程序。 利益相關(guān) 無 說明 該小程序代碼已開源,點(diǎn)擊可查看源碼,可隨意 star。也可以先掃描下方的小程序碼直接體驗(yàn)。 showImg(https://segmentfault.com/img/...
摘要:微信應(yīng)用號小程序資源匯總。每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補(bǔ)充。 wechat-weapp-resource 微信應(yīng)用號(小程序)資源匯總。 每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補(bǔ)充。 showImg(https://segmentfault.com/img/remote/1460000...
摘要:微信應(yīng)用號小程序資源匯總。每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補(bǔ)充。 wechat-weapp-resource 微信應(yīng)用號(小程序)資源匯總。 每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補(bǔ)充。 showImg(https://segmentfault.com/img/remote/1460000...
摘要:微信官方?jīng)]有給出來處理異步操作,而官方異步的又非常多,這使得多異步編程會層層回調(diào),代碼一復(fù)雜,回調(diào)起來就想砸電腦。是一個(gè)轉(zhuǎn)換微信小程序異步為的一個(gè)工具庫優(yōu)點(diǎn)避免小程序異步編程多次回調(diào)帶來的過多回調(diào)導(dǎo)致邏輯不清晰,篇幅過長等問題。 把微信小程序異步API轉(zhuǎn)化為Promise。用Promise處理異步操作有多方便,誰用誰知道。微信官方?jīng)]有給出Promise API來處理異步操作,而官方AP...
閱讀 1772·2021-11-15 11:37
閱讀 3044·2021-11-04 16:05
閱讀 1910·2021-10-27 14:18
閱讀 2742·2021-08-12 13:30
閱讀 2486·2019-08-29 14:18
閱讀 2076·2019-08-29 13:07
閱讀 2004·2019-08-27 10:54
閱讀 2714·2019-08-26 12:15