封装组件库-城市选择器
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)
}
}