前言:如果想最大化吸取本文經驗,須有小程序開發基礎,本文細節比較多(多看注釋的提醒內容),請耐心理解,多動手嘗試,收獲會更加豐富
1.定位系統使用場景及概述
如美團外賣小程序

點定位

點搜索

顯而易見,隨便一個電商小程序都需要用到定位服務,那么今天我們做一個類似的定位模塊
定位模塊總覽
外部頁面

內部頁面(下文說的內外部頁面就是指這兩個)

好了接下來我們開始動手
2.定位外部模塊樣式
效果

代碼
//wxml
<view bindtap="getLocation" class="location">
<image src="../../img/location.png"></image>
<view>{{location}}</view>
</view>
//wxss
.location{
font-size: 17px;
width: 100%;
background:rgb(196, 228, 123);
display: flex;
/* 對於兩個塊元素 */
/* 垂直居中 */
align-items: center;
/* 水平居中 */
justify-content: center;
}
.location image{
width: 23px;
height: 23px;
}
先不用管上面的{{location}},它是我們之后要從全局變量傳過來的位置信息,定位符號是用image圖片放進去的,我們用flex布局讓圖片和文字在同一行居中顯示(見注釋)
3.定位模塊內部樣式
效果

代碼(分五個小模塊,見注釋)
//wxml
//搜索模塊
<view class="header">
<view class="search">
<image src="../../img/sousuo.png"></image>
</view>
<view class="input">
<input type="text" placeholder=" 請輸入你想要的內容" placeholder-class="placeholder" bindinput="bindInput" bindfocus="bindFocus" auto-focus="{{autoFocus}}"></input>
</view>
</view>
//定位模塊
<view class="dw">
<button size="mini" bindtap="getCity">
<image src='../../img/location.png'></image>
<text>定位</text>
</button>
</view>
//當前位置模塊
<view >當前所在位置</view>
<button size="mini" bindtap="nowCity" class='nowcity'>{{city}}</button>
//熱門城市模塊
<view class="hotcity">熱門城市</view>
<view wx:for="{{hotcity}}" wx:key='index' class="hotcity1">
<!-- 用了view循環之后要把view設置為inline元素,不然5個view會分成5行顯示 -->
<button size="mini" bindtap="hotCity" data-hotcityindex='{{index}}'>{{item.cityName}}</button>
</view>
//地圖模塊
<view class="map">
<map longitude="{{longitude}}" latitude="{{latitude}}" scale="14"></map>
</view>
由於我的搜索框是用了自定義組件里面的搜索組件,我是在組件的基礎上改出來的,原組件是這樣的
我們需要把搜索圖標隱藏,我們直接設置它的透明度為0,然后把我們的定位文字跟圖標通過定位直接定位到搜索框的左邊,所以樣式的代碼如下(代碼太多不好找的話可以Ctrl+F直接搜索)
//wxss
.dw{
color:rgb(0, 0, 0);
position: absolute;
top: 14px;
left: -2px;
}
.dw button{
background: white;
padding-right: 0;
display: flex;
align-items: center;
font-weight: 600 !important;
}
.nowcity{
font-weight: normal;
}
.dw image{
width: 23px;
height: 23px;
}
page{
padding: 10px;
}
.hotcity1 button{
margin: 10px;
margin-bottom: 0;
font-weight: 500 !important;
border-radius: 10px !important;
}
.hotcity{
margin-top: 6px;
}
.map_container{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.header{
display: flex;
}
.search{
flex:1;
height: 40px;
text-align: center;
background: #fff;
}
.input{
flex:9;
height: 40px;
background: #fff;
}
.input input{
background: #f1f1f1;
height: 30px;
margin-top: 5px;
margin-bottom: 5px;
margin-right: 8px;
border-radius: 10px;
}
.search image{
width: 70%;
height: 25px;
padding-top: 9px;
padding-left: 5px;
}
.placeholder{
font-size: 14px;
}
.search image{
opacity: 0;
}
.input{
flex:4;
}
.input input{
position: relative;
right: 0px;
}
.hotcity1{
display: inline;
}
.map{
position: relative;
}
map{
border:5px solid green;
text-align: center;
margin: 10px auto;
position: relative;
right: 10px;
width: 90%;
height: 150px;
}
然后我們的搜索里面點擊搜索還會跳轉到新的搜索頁面,效果如下

這里我們可以直接復用上面的搜索組件,樣式代碼就不再貼出來了,這個模塊要將搜索自動匹配的地點名稱用循環的方式顯示出來,代碼如下
//wxml
<import src="../templates/search/search" />
<template is="search"></template>
<view bindtouchstart="bindSearch" data-keywords="{{i.name}}"
class="text_box" wx:for="{{tips}}" wx:for-item="i" wx:key='index'>
{{i.name}}
</view>
//wxss
@import '../templates/search/search.wxss';
.text_box{
margin: 10px 25px;
border-bottom:1px solid #c3c3c3;
padding-bottom:10px
}
4.外部跳轉
當我們點擊外部的位置信息,就跳轉到內部的定位模塊,剛剛我們在上面給外部的標簽設置了觸摸事件getLocation,接下來只要到js里面設置點擊跳轉(navigateTo)就可以了,但由於我們的位置信息是用全局變量賦值的,所以我們要在app.js設置一個全局變量,代碼如下
//app.js
App({
globalData: {
city:'暫未定位',
userInfo:'無'
},
)}
//外部js
// 引入app.js
const app=getApp()
const appG=app.globalData
data: {
//這里要初始化location,並將全局變量賦值給它
aboutList:'',
location:appG.city
},
Page({
//定義觸摸事件
getLocation(){
wx.navigateTo({
//跳轉到內部定位頁面
url: '../location/location',
})
},
)}
5.點擊定位
做這個功能之前我們需要先考慮用什么地圖接口,常用的有百度地圖,騰訊地圖,高德地圖,本文選用高德地圖接口作為演示,搜索https://lbs.amap.com/,注冊,進入控制台,創建新應用,
再添加key

這個key就像我們小程序調用接口時的驗證碼,有了它我們才能從高德調取位置的數據,然后我們點擊key后面的設置,再點擊微信小程序SDK

進去之后點這兩個,下載amap-wx.js 文件,然后在你的小程序目錄里面創建一個libs文件,把這個amap-wx.js扔進去

接下來我們來到內部定位頁面的js文件,因為這里要對全局變量進行修改來達到修改頁面數據的效果,所以也要引入app.js,並把全局變量初始化到data里面,除此之外我們要引入高德的文件來實現高德接口的調用,在data里面我們這里順便把等會要用到的熱門城市等數據一並初始化了
const app=getApp()
const appG=app.globalData
//key里面填高德控制台里面給你的key
const myAmapFun = new amapFile.AMapWX({key:'xxxxxxxxxx'});
data: {
city:appG.city,
hotcity:[
{'cityName':'北京市',longitude:'116.395645038',latitude:'39.9299857781'},
{'cityName':'上海市',longitude:'121.487899486',latitude:'31.24916171'},
{'cityName':'廣州市',longitude:'113.307649675',latitude:'23.1200491021'},
{'cityName':'深圳市',longitude:'114.025973657',latitude:'22.5460535462'},
{'cityName':'武漢市',longitude:'114.316200103',latitude:'30.5810841269'},
],
tips: {},//搜索自動匹配的內容
longitude:'116.4',//經度(初始值在北京)
latitude:'39.9'//緯度(初始值在北京)
}
然后我們給定位按鈕設置點擊事件getCity,這里用到高德地圖里面的獲取地址描述數據方法,教程可以參考剛剛高德控制台微信SDK里面的教程(下面搜索自動匹配提示的教程也一樣)

此外我們我們還要在小程序后台給高德的接口添加域名,操作步驟為
登錄微信公眾平台,“設置“–>"開發設置"設置request合法域名,將https://restapi.amap.com 中添加進去,這樣我們才能請求到高德的數據
代碼
getCity(){
myAmapFun.getRegeo({
success: data=>{
// that.setData({
// city:data[0].desc.slice(0,2)
// })
appG.city=data[0].desc
wx.getLocation({
success:res=>{
this.setData({
latitude:res.latitude,
longitude:res.longitude
})
wx.setStorageSync('city', appG.city)
wx.setStorageSync('latitude', res.latitude)
wx.setStorageSync('longitude', res.longitude)
}
})
},
fail: function(info){
//失敗回調
console.log(info)
}
})
},
getRegeo方法的成功回調函數里的參數包含了定位后的位置信息(可以自己輸出一下),我們把它賦值給全局變量,然后再用setData再次把全局變量appG.city賦值給data里面的city(因為appG.city已經改變了,要重新賦值頁面才會更新),除此之外我們還要把獲取到的位置信息同步緩存起來,下次進入頁面的時候在onLoad里面先判斷有沒有緩存的數據,如果有就直接使用緩存的數據,沒有就用默認值,代碼如下
onLoad: function (options) {
// 進頁面先看有無緩存數據,如果沒有再讀默認值,onLoad里面可以取到this.data
const latitude=wx.getStorageSync('latitude')
const longitude=wx.getStorageSync('longitude')
const city=wx.getStorageSync('city')
//用了三目運算符,不習慣也可以使用if
latitude&&longitude&&city?
this.setData({
latitude:latitude,
longitude:longitude
}):false
},
6.未定位時彈出定位框
給當前位置標簽添加點擊事件,判斷當位置信息為初始值暫未定位時,彈出是否定位的選擇框,當用戶點擊確定時,執行一次getCity函數即可,效果如下

代碼
nowCity(){
if(this.data.city!='暫未定位'){
wx.switchTab({
url: '../about/about',
})
}else{
wx.showModal({
title: '暫未定位',
content: '現在要進行定位嗎',
success: (res)=>{
if (res.confirm) {
this.getCity()
} else if (res.cancel) {
return false
}
}
})
}
},
7.熱門城市點擊跳轉,更新數據

當我們點擊熱門城市里面的按鈕時,跳轉到外部頁面,並且把對應熱門城市名稱更新到全局的city來傳到外部頁面顯示,同時還要更新全局中的經緯度數據,對於經緯度只要更新緩存即可,下一次進入內部定位頁面時再判斷緩存中有無定位數據,如果有就直接用,city數據是更新+緩存,代碼如下
hotCity(e){
const index=e.currentTarget.dataset.hotcityindex
//更新
appG.city=this.data.hotcity[index].cityName
//緩存
wx.setStorageSync('city', appG.city)
wx.setStorageSync('latitude', this.data.hotcity[index].latitude)
wx.setStorageSync('longitude', this.data.hotcity[index].longitude)
//跳轉
wx.reLaunch({
url: '../about/about',
success:()=>{
// 不要把數據的更新寫在這里,要在跳轉之前就寫好,因為這個回調函數是在跳轉的頁面所有函數
// 執行完之后才執行的,會導致數據兩次跳轉次才更新
}
})
},
上述代碼中注意要在熱門城市的循環標簽用data-hotcityindex="{{index}}"把下標傳到js中,再在js中用e.currentTarget.dataset.hotcityindex去取出來用,這個下標用來對應熱門城市數組的每一個對象,這樣我們就可以用this.data.hotcity[index].cityName來獲取被點擊的城市的名稱,再把它更新到appG.city中,注意跳轉的時候不能用wx.switchTab,因為從外部頁面進來的時候已經打開了外部頁面,那么用wx.switchTab的時候只會執行外部頁面的onShow函數,而不會執行onLoad,會導致頁面數據無法更新
8.搜索跳轉和輸入自動匹配地名
搜索跳轉新頁面(給內部定位頁面設置聚焦事件)
bindFocus(e){
wx.navigateTo({
url: '../locationSearch/locationSearch',
})
},
注意內部頁面的搜索框不是自動聚焦的,而跳轉到新的搜索頁面的搜索框是會自動聚焦的,這一點我們可以通過在搜索組件的input標簽添加auto-focus="{{autoFocus}}",再控制autoFocus的值來控制是否自動聚焦,代碼如下
<template is="search" data="{{autoFocus}}"></template>
注意data="{{xxx}}"是自定義組件特有的傳參方式,可以把js里面的值傳到組件中使用不過我們得先在搜索頁面的js的data中給autoFocus賦值,這里順便把保存自動匹配的地名的值tips也初始化了
data: {
autoFocus:true,
tips:{}
},
接下來我們寫輸入自動匹配地名,同樣在搜索頁面的js引入全局變量和高德js文件
const amapFile = require('../../libs/amap-wx.js');
const app=getApp()
const appG=app.globalData
const myAmapFun = new amapFile.AMapWX({key:'0c2c8f2007702caa7e0498d6ad072f83'});
然后我們來監聽用戶的輸入行為,設置為bindInput函數
<input type="text" placeholder=" 請輸入你想要的內容" placeholder-class="placeholder"
bindinput="bindInput" bindfocus="bindFocus" auto-focus="{{autoFocus}}"></input>
搜索頁面的js中定義bindInput
bindInput(e){
myAmapFun.getInputtips({
// keywords為必填,不然無法獲得tips,也就不會進入success函數
keywords:e.detail.value,
success:data=>{
this.setData({
tips:data.tips
})
}
})
},
上面的getInputtips就是我們第5點中講到的微信小程序SDK中的獲取提示詞里面的方法,可以自己去看高德的教程,此處不再贅述,上面的keywords的值就是用戶輸入的內容,接口會根據這個去尋找對應匹配的地名並返回在success函數的參數中,我們只需要在成功回調函數中更新tips就可以了,那么此時假如我們輸入武漢,效果如下

那么當我們點擊自動匹配的地名的時候,需要返回到外部頁面,並且更新數據,更新緩存,思路和上面的跳轉方法是一樣的,代碼如下
bindSearch(e){
const location=this.data.tips[e.currentTarget.dataset.searchindex]
wx.setStorageSync('city', location.name)
if(location.location.length!=0){
const longitude=location.location.split(',')[0]
const latitude=location.location.split(',')[1]
wx.setStorageSync('latitude', latitude)
wx.setStorageSync('longitude', longitude)
wx.reLaunch({
url: '../about/about',
success:()=>{
appG.city=e.currentTarget.dataset.keywords
}
})
}else{
wx.reLaunch({
url: '../about/about',
success:()=>{
appG.city=e.currentTarget.dataset.keywords
setTimeout(()=>{wx.showToast({
title: '暫無經緯度數據',
// 提示延遲時間,不能用字符串
duration:2000,
icon:'none'
})
},500);
}
})
}
由於不是每一個自動匹配的地點都有經緯度,所以我們對沒有經緯度的地名做業務退步處理,僅提醒暫無經緯度的數據,(有時候業務退一小步,技術就有一大步的發揮空間–《大型網站技術架構》——李智慧),細心的你們肯定注意到上面用了定時器,因為如果不使用定時器,這個彈框是不會顯示出來的,這個函數在頁面加載完成之前就已經執行了,所以我們給他來一個定時器作為異步函數延遲執行,才能有彈框
9.對整個模塊的優化和思考
對上述代碼,筆者開發完之后發現了如下問題:
代碼冗余嚴重,主要表現在多次使用緩存的api,重復量的地方很多
整個模塊內部互相調用復雜,高耦合,低拓展
某些地方把簡單的邏輯復雜化了,代碼不夠整潔
對於上述問題,筆者有如下思考:
通過封裝緩存api來減少不必要的代碼量,提高代碼整潔度
對整個模塊重新構思,提高可拓展性和可復用性(由於筆者水平有限,暫時擱置)
模塊從開發開始到開發完成需要不斷的演化和改進,這個過程才是讓開發者成長的關鍵
本文為筆者原創文章,轉載請注明出處
