城市選擇器應該是比較常用的一個組件,用戶可以去選擇自己的城市,選擇城市后返回,又根據自己選擇的城市搜索小區。
功能展示
這是選擇結果
這是選擇城市
這是搜索小區
這是搜索小區接口,key為城市名字,id是城市的id
假如切換城市
搜索接口也會相應變化,id=0997 就是指定的搜索城市id
技術棧
vue2.0+vue-router+webpack+vuex+less+better-scroll+axios
webpack
resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', 'src': resolve('src'), 'common' : resolve('src/common'), 'components': resolve('src/components'), 'base': resolve('src/base'), "api":resolve('src/api') } },
用less需要引入less和less-loader,但是不需要在webpack操作,webpack已經操作好了
技術棧介紹
所有城市是本地維護的,在city.js中
1、axios封裝
import {ajaxUrl} from "./config" import axios from 'axios' export function getSearchData(key,id){ var obj = { op:"search", key, id } return axios.get(ajaxUrl.searcUrl,{ params: obj }).then((res)=>{ return Promise.resolve(res.data); }).catch((err)=>{ return Promise.resolve(err); }) }
2、axios調用,因為目前沒有接口,所以只是模擬演示接口,但不影響邏輯
import {getSearchData} from "api/search" _getDiscList(key,id){ this.searchList=["八方城","西溪北苑北區","西溪北苑西區","西溪北苑東區","萬科城","恆大城","西溪科技園","未來科技城","智慧城","春天家園","茶張新苑","雙水磨小區","小區1","小區2","小區3","小區4","小區5","小區6","小區7"]; getSearchData(key,id).then((res)=>{ },(err)=>{}) },
3、vuex狀態管理,vuex我就不介紹了,具體可以去看官網
4、主要介紹一下state中變量的含義
import {initial} from "common/js/config" const state = { selectCity:initial.city, selectCommunity:initial.community, hasSelCityID:-1 } export default state
5、config.js
export const initial = { city:"杭州", community:"八方城" }
selectCity是選擇的城市
selectCommunity是選擇的小區
hasSelCityID是選擇城市的id,根據此id選擇對應小區
6、better-scroll
better-scroll 是之前封裝好的一個頁面滾動組件
7、vue-router
8、頁面滑動對應title也變化原理
首先需要記住變量scrollY,這是記錄頁面滾動到哪個title
data(){ return{ city:[], scrollY: -1, currentIndex:0, diff:-1, } },
初始化的時候初始化這三個變量,probetype=3是better-scroll可支持touchmove事件的參數
created(){ this.touch = {}; this.listenScroll = true; this.listenHeight = []; this.probetype = 3; },
時刻計算高度,並檢測頁面滾動到哪個位置
watch:{ city(){ setTimeout(()=>{ this._calculateHeight() },20) }, scrollY(newY){ // 滾動到中間部分 const listenHeight = this.listenHeight; // 滾動到頭部以上 if (newY>=-25) { this.currentIndex = 0; return; } for(let i=0;i<listenHeight.length-1;i++){ let height1 = listenHeight[i]; let height2 = listenHeight[i+1]; // 如果沒在下限,且在height1和height2之間 if (-newY>=height1 && -newY<=height2) { this.currentIndex = i; this.diff = height2 + newY; return; } } }, diff(newVal){ let fixedTop = (newVal>0 && newVal<TITLE_HEIGHT)?newVal-TITLE_HEIGHT:0; if (this.fixedTop === fixedTop) { return } this.fixedTop = fixedTop; this.$refs.fixed.style.transform = `translate3d(0,${fixedTop}px,0)` } }
_calculateHeight(){ this.listenHeight = []; const list = this.$refs.listGroup; let height = 0; this.listenHeight.push(height); for(let i =0;i<list.length;i++){ let item = list[i]; height +=item.clientHeight; this.listenHeight.push(height); } },
滾動指定位置
_scrollTo(index){ if (!index && index!=0) { return } // 點擊右邊字母跳到指定位置並高亮 this.scrollY = -this.listenHeight[index]-1; this.$refs.cityList.scrollToElement(this.$refs.listGroup[index],0); },
改變標題
fixedTitle(){ if (this.scrollY>0) { return "" } return this.city[this.currentIndex]?this.city[this.currentIndex].initial:"" }
點擊字右邊索引跳轉指定位置
onShortcutTouchStart(e){ let anchorIndex = getData(e.target,'index'); console.log(anchorIndex); let firstTouch = e.touches[0]; this.touch.y1 = firstTouch.pageY; this.touch.anchorIndex = anchorIndex; // this.$refs.singerlist.scrollToElement(this.$refs.listGroup[anchorIndex],0); this._scrollTo(anchorIndex) },
9、搜索組件
搜索輸入框是一個組件,組件負責監聽input的model變化,只要變化就派發事件,引用該組件的組件,只需要監聽派發的事件即可
created(){ this.$watch('query',(newQuery)=>{ this.$emit('query',newQuery) }) }
11、城市搜索,支持首字母(不區分大小寫)搜索
首先給城市加首字母
_addFirstLetter(citylist){ for(var i=0;i<citylist.length;i++){ for(var j=0;j<citylist[i].list.length;j++){ citylist[i].list[j]['firstLetter'] = citylist[i].initial; } } this._formatCityList(citylist); },
序列化數組
// 序列化數組 _formatCityList(arr){ var letterArr = {}; for (var i = 0; i < arr.length; i++) { if (!(arr[i]['initial'] in letterArr)) { letterArr[arr[i]['initial']] = []; for(var j=0;j<arr[i].list.length;j++){ letterArr[arr[i]['initial']].push(arr[i].list[j]); } }else{ for(var j=0;j<arr[i].list.length;j++){ letterArr[arr[i]['initial']].push(arr[i].list[j]); } } } this.letterList = letterArr; },
搜索
正則 var reg = new RegExp(newVal == '' ? 'xxyy' :newVal, 'ig'); ig是不區分大小寫
// 搜索 _search(newVal){ var reg = new RegExp(newVal == '' ? 'xxyy' :newVal, 'ig'); var _arr = []; for(var i in this.letterList){ for(var j = 0; j < this.letterList[i].length; j++){ if( reg.test(this.letterList[i][j][ 'name' ]) || reg.test(this.letterList[i][j][ 'firstLetter' ]) ){ _arr.push(this.letterList[i][j]); } } } this.searchList = _arr; },
因為引入的搜索框組件,所以只需要監聽input內容改變后派發的事件即可
this._search(newVal); this.queryCity = newVal;
data 搜索結果會放在searchList里面,只需要v-for即可,但是需要邊緣處理,沒有搜索結果,有一個UI上的一個展示
data(){ return{ city:[], letterList:[], searchList: [], //搜索結果 queryCity:"", placeholder:"輸入城市名稱" } },
10、每次點擊搜索城市后觸發mutation,修改state
selectItem(item){ this.afterSelect(item) }, selectSearchItem(item){ this.afterSelect(item) }, // 選擇之后的操作 afterSelect(item){ this.$router.back(); this.setCity(item.name); this.setCityId(item.zip); }, ...mapMutations({ setCity:"SET_CITY", setCityId:"SET_CITYID" })
推薦使用vuex鈎子,具體如何使用可去看官網
import {mapMutations} from "vuex"
import {mapGetters} from "vuex"
業務功能模板
1、select.vue
<template lang="html"> <!-- <transition name="slide"> --> <div> <div @click="city" class="city clearfix"> <i>所在城市</i> <em></em> <span>{{selCity}}</span> </div> <div @click="community" class="community clearfix"> <i>小區名稱</i> <em></em> <span>{{selCommunity}}</span> </div> </div> <!-- </transition> --> </template>
2、city.vue
<transition name="slide"> <div class="xin-widget-citys animated"> <SearchBox class="search" @query="query" :placeholder="placeholder"></SearchBox> <div class="currentCity" v-if="queryCity===''"> <ul> <h2>當前定位城市</h2> <li>杭州</li> </ul> </div> <Scroll :data="searchList" class="searchlist" v-if="queryCity !== ''" :class="{'bg':searchList.length === 0}"> <div> <ul v-if="searchList.length!==0"> <li class="bdb" v-for="item in searchList" @click="selectSearchItem(item)">{{item.name}}</li> </ul> <img v-else src="../../common/img/404.png" class="nomatch"/> </div> </Scroll> <CityList class="city" v-if="queryCity===''" @selectItem="selectItem"></CityList> </div> </transition>
3、search.vue
<transition name="slide"> <div class="xin-widget-citys animated"> <SearchBox class="search" @query="query" :placeholder="placeholder"></SearchBox> <Scroll :data="searchList" class="searchlist" v-if="queryCity !== ''" :class="{'bg':searchList.length === 0}"> <div> <ul v-if="searchList.length!==0"> <li v-for="item in searchList" @click="selectSearchItem(item)">{{item}}</li> </ul> <img v-else src="../../common/img/404.png" class="nomatch"/> </div> </Scroll> </div> </transition>
基礎組件模板
1、city-list.vue
<Scroll class="citylist" :data="city" ref="cityList" :listenScroll="listenScroll" @scroll="scroll" :probetype="probetype"> <div> <div v-for="(item,index) in city" class="allCity" ref="listGroup"> <h2>{{item.initial}}</h2> <ul> <li v-for="city in item.list" @click="selectItem(city)"> {{city.name}} </li> </ul> </div> </div> <div class="list-shortcut" @touchstart="onShortcutTouchStart"> <ul> <li class="starCity"></li> <li v-for="(item,index) in city" class="item" :data-index="index"> {{item.initial}} </li> </ul> </div> <div class="list-fixed" v-show="fixedTitle" ref="fixed"> <h1 class="fixed-title">{{fixedTitle}}</h1> </div> </Scroll>
2、search-box.vue
<template> <div class="search-box"> <i class="icon-search"></i> <input ref="query" class="box" :placeholder="placeholder" v-model="query"/> </div> </template>
總結
以上就是城市選擇器的大概介紹,源碼我已經放在了我的github上了,有需要可去下載,如果有幫助,麻煩給個star,鼓勵我繼續努力,謝謝!