封装组件库-城市选择器

2023, Mar 03    

实现城市选择器的封装

基于Element-plus框架的Popover弹出框组件、Layout布局组件、Select筛选组件、Radio单选框组件、Scrollbar滚动条组件组合封装为城市选择器组件。

初始配置

  • 注册路由组件chooseCity,配置为Container组件的子路由组件
  • 采用路由懒加载,箭头函数异步引入组件。
  • 注册通用组件chooseCity,为路由组件的子组件,以便组件间通信。
  • 将通用组件chooseCity注册为全局组件

按城市搜索

  • 模版
  <el-row>
    <el-col :span="8">
      <el-radio-group v-model="radioValue" size="small">
        <el-radio-button label="按城市" />
        <el-radio-button label="按省份" />
      </el-radio-group>
    </el-col>
    <el-col :span="15" :offset="1">
      <el-select  
        v-model="selectValue" 
        filterable 
        placeholder="请搜索城市" 
        size="small">
        <el-option
        v-for="item in options"
        :key="item.id"
        :label="item.name"
        :value="item.id" 
        />
       </el-select>
    </el-col>
  </el-row>
  • 字母区域 获取城市列表数据并添加响应式,cities为对象,键值为数组,数组里为对象,属性有id: number、spell、name 可以采用两种方式遍历对象: (value,key) in cities / (item,index) in Object.keys(cities)
  import city from '../lib/city'
  let cities = ref(city.cities)
  <template v-if="radioValue === '按城市'">
      <div class="city">
      <!-- <div v-for="(value,key) in cities" :key = 'key'> </div>-->
      <!-- 字母区域 -->
      <div class="city-item" v-for="(item,index) in Object.keys(cities)" :key="index"> </div>
    </div>
  </template> 
  • 城市菜单 城市菜单按首字母及对应城市展示,对cities进行遍历,拿到key值,作为首字母。 遍历每个key对应的value,拿到城市并展示
  <el-scrollbar max-height="300px">
    <template v-for="(value,key) in cities" :key="key">
      <el-row style="margin-bottom: 10px">
        <el-col :span="2">:</el-col>
        <el-col class="city-name" :span="22">
          <div class="city-name-item" v-for="item in value" :key="item.id">
            <div></div>
          </div>
        </el-col>
      </el-row>
    </template>
  </el-scrollbar>
  • 在页面展示选中城市 模版由城市result和icon组件组成,icon设置rotate旋转样式 给城市的div添加点击事件clickItem,传参为城市对象,并更新result,同时关闭弹出框,向父组件传值
  let result = ref<string>('请选择')
  let emits = defineEmits(['changeCity'])
  <template #reference>
    <div class="result">
      <div ></div>
      <div>
        <qt-icon-arrowdown :class="{'rotate':visible}"></qt-icon-arrowdown>
      </div>
    </div>
  </template>
  let clickItem = (val: City)=>{ 
    //给结果赋值
    result.value = val.name
    // 关闭弹出框 
    visible.value = !visible.value
    emits('changeCity',val)
  }
  • 实现点击字母跳转到指定城市区域 为每个首字母及城市区域的块添加id,值为key(id) 这里可以采用锚点链接的形式,将字母div替换为a标签,:href=’#${item}‘绑定href实现跳转,但会修改路由地址,不采取。 第二种方法,添加clickChat点击事件,传参为key(id),获取dom元素,.scrollIntoView() API跳转到指定id匹配的区域。
  <!-- 字母区域 -->
  <div  @click="clickChat(item)" class="city-item" v-for="(item,index) in Object.keys(cities)" :key="index"> </div>
  <!-- 锚点链接会修改路由 -->
  <!-- <a  :href='`#${item}`' @click="clickChat(item)" class="city-item" v-for="(item,index) in Object.keys(cities)" :key="index"> </a> -->
  </div>
  
  <template v-for="(value,key) in cities" :key="key">
    <el-row style="margin-bottom: 10px" :id="key">
      ... ...
    </el-row>
  </template>
  
  let clickChat = (val: string)=>{
     let el = document.getElementById(val)
     if(el) el.scrollIntoView()
  }

按省份搜索

  • 字母区域 引入省份数据,并添加响应式。provinces为对象形式,Object.keys遍历键名,并在模版展示。
  import province from '../lib/province.json'
  let provinces = ref(province)
  <div class="province">
    <div class="province-item" v-for="(item,index) in Object.keys(provinces)" :key="index">
      
    </div>
  </div>
  • 城市菜单 城市菜单按省份及对应城市展示,对provinces进行遍历,拿到value值,value值为数组,包括多个对象。 遍历每个value拿到每个对象,包括name、id、data(城市数据)。 :获取每个省份,遍历item1.data获取全部的城市数据,并展示。
  <el-scrollbar max-height="300px">
    <template v-for="(item,index) in Object.values(provinces)" :key="index">
      <template v-for="(item1,index1) in item" :key="index1">
        <el-row style="margin-bottom: 10px">
          <el-col :span="3">:</el-col>
          <el-col :span="21" class="province-name">
            <div class="province-name-item" v-for="(item2,index2) in item1.data" :key="index2">
              <div></div>
            </div>
          </el-col>
        </el-row>
      </template>
    </template>
  </el-scrollbar>
  • 在页面展示选中城市 给城市的div添加点击事件clickProvince,传参为城市名,并更新result,同时关闭弹出框,向父组件传值
  let emits = defineEmits(['changeProvince'])
  let clickProvince = (val: string)=>{
    result.value = val
    visible.value = !visible.value
    emits('changeProvince',val)
  }
  • 实现点击字母跳转到指定城市区域 为每个省份及城市区域的块添加id,值为id 添加clickChat点击事件,传参为key(id),获取dom元素,.scrollIntoView() API跳转到指定id匹配的区域。
  <el-row style="margin-bottom: 10px" :id="item1.id">
    <el-col :span="3">:</el-col>
    <el-col :span="21" class="province-name">
      <div class="province-name-item" v-for="(item2,index2) in item1.data" :key="index2">
        <div @click="clickProvince(item2)"></div>
      </div>
    </el-col>
  </el-row>

搜索框搜索和选择

  • 模版 初始时的单选框默认为空字符串。采用城市的数据作为默认展示的数据,在onmounted钩子函数里获取数据并展示。 Object.values得到二维数组,.flat拍平,为options赋值。
  let selectValue  = ref<string>('')
  let options = ref<City[]>([])
  let allCitiies = ref<City[]>([])
  
  export interface City{
    id: number,
    spell: string,
    name: string
  }
  
  <el-col :span="15" :offset="1">
    <div class="city-choose-select-position">
        <el-select  
        v-model="selectValue" 
        filterable 
        placeholder="请搜索城市" 
        size="small">
        <el-option
        v-for="item in options"
        :key="item.id"
        :label="item.name"
        :value="item.id" 
        />
         </el-select>
    </div>
  </el-col>
  onMounted(()=>{
    let values = Object.values(cities.value).flat(2)
    options.value = values
    allCitiies.value = values
  })
  • 实现拼音搜索 为添加:filter-method="filterMethod",实现自定义过滤。 对cities.value的数据进行过滤,如果输入框为'',则为options.value赋初始值。 如果当前为'按城市',城市的数据有spell数据项,则对values进行过滤,条件为item.name.includes(val) || item.spell.includes(val),实现名称或拼音搜索。 如果当前为'按省份',则对values进行过滤,条件为item.name.includes(val) ,实现名称搜索。
  let filterMethod = (val:string)=>{ 
    let values = Object.values(cities.value).flat(2)
    if(val === ''){
      options.value = values
      }else{
        if(radioValue.value === '按城市'){
        //中文和拼音一起过滤
        options.value = values.filter(item=>{
        return item.name.includes(val) || item.spell.includes(val)
        })
        }else{
          options.value = values.filter(item=>{
          return item.name.includes(val) 
          })
        }
      }
   }
  • 更新视图 为添加change事件@change="changeSelect",item.id === val 搜索框的value为城市对应id,如果匹配则触发更新,同时分发事件给父组件。
  let changeSelect = (val: Number)=>{
    let city = allCitiies.value.find(item=> item.id === val)!
    result.value = city.name
    if(radioValue.value === '按城市'){
        emits('changeCity',city)
    }else{
        emits('changeProvince',city.name)
    }
  }