vue | 基於vue的城市選擇器和搜索城市對應的小區


城市選擇器應該是比較常用的一個組件,用戶可以去選擇自己的城市,選擇城市后返回,又根據自己選擇的城市搜索小區。

功能展示

 這是選擇結果

這是選擇城市

這是搜索小區

這是搜索小區接口,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,鼓勵我繼續努力,謝謝!

 代碼地址:https://github.com/dirkhe1051931999/writeBlog


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM