一.准备
1.axios
利用axios发送请求,拿到全部城市数据。
在项目根目录下打开任意终端,执行yarn add axios命令。
项目中需要发送请求统一统一放在src/api/index.js。
import axios from 'axios'
// 获取城市数据
// 1. 数据在哪里?https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json
// 2. 何时获取?打开城市列表的时候,做个内存中缓存
// 3. 怎么使用数据?定义计算属性,根据点击的省份城市展示
export const getCityList = async () => {
// 添加缓存,防止频繁加载列表数据
if ((window as any).cityList) {
// 缓存中已经存在数据了
return(window as any).cityList
}
const ret = await axios.get('https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json')
// 给window对象添加了一个属性cityList
if (ret.data) {
(window as any).cityList = ret.data
}
// 把数据返回
return ret.data
}
2.vueuse/core
利用vueuse/core里的onClickOutside,判断点击是否是外部组件,来帮助我们关闭弹层。
终端中执行yarn add @vueuse/core@5.3.0,这里安装指定版本,各位按需选择。
二.代码实现
1.封装
将其封装为局部组件,文件放在src/libs目录下,新建city.vue组件。
代码如下(示例):
<template>
<div class="city" ref="target">
<div class="select" @click="toggle" :class="{ active: isShow }">
<span v-if="!fullLocation" class="placeholder">请选择你所在的城市</span>
<span v-else class="value">{
{
fullLocation }}</span>
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xiajiantou"></use>
</svg>
</div>
<div class="option" v-show="isShow">
<div class="loading" v-if="loading"></div>
<template v-else>
<span
@click="changeCity(item)"
class="ellipsis"
v-for="item in cityList"
:key="item.code"
>{
{
item.name }}</span
>
</template>
</div>
</div>
</template>
<script lang="ts">
import {
ref, reactive, computed } from "vue";
import {
onClickOutside } from "@vueuse/core";
import {
getCityList } from "../api/index";
export default {
name: "City",
props: {
fullLocation: {
type: String,
default: "",
},
},
setup(props, {
emit }) {
const isShow = ref(false);
const loading = ref(false);
// 城市列表原始数据
const list = ref([]);
//选中的省市区
const changeResult = reactive({
provinceCode: "",
provinceName: "",
cityCode: "",
cityName: "",
countyCode: "",
countyName: "",
fullLocation: "",
});
//选择的城市操作
const changeCity = (city) => {
if (city.level === 0) {
// 点击的是省级单位
changeResult.provinceCode = city.code;
changeResult.provinceName = city.name;
} else if (city.level === 1) {
// 点击的是市级单位
changeResult.cityCode = city.code;
changeResult.cityName = city.name;
} else if (city.level === 2) {
// 点击的县级单位:选中最终的省市区数据,并且传递给父组件
changeResult.countyCode = city.code;
changeResult.countyName = city.name;
//组合完整的省区名称
changeResult.fullLocation = `${
changeResult.provinceName}${
changeResult.cityName}${
changeResult.countyName}`;
//关闭弹层
isShow.value = false;
//把选中的数据传递给子组件
emit("change-city", changeResult);
}
};
//通过计算属性计算当前显示的数据:省级,市级,县级
const cityList = computed(() => {
let result = list.value;
//当前点击的是省,那么就计算市集列表
if (changeResult.provinceCode && changeResult.provinceName) {
result = result.find(
(item) => item.code === changeResult.provinceCode
).areaList;
}
// 当前点击的是市,那么计算县级列表
if (changeResult.cityCode && changeResult.cityCode) {
result = result.find(
(item) => item.code === changeResult.cityCode
).areaList;
}
return result;
});
// 控制城市弹窗的显示和隐藏
const toggle = () => {
isShow.value = !isShow.value;
if (isShow.value) {
loading.value = true;
//调用接口之前,把之前的数据置空
for (const key in changeResult) {
changeResult[key] = "";
}
//弹层显示了,调用接口
getCityList().then((ret) => {
list.value = ret;
loading.value = false;
});
}
};
//控制点击区域外,隐藏弹层
const target = ref(null);
onClickOutside(target, () => {
isShow.value = false;
});
return {
isShow, toggle, target, cityList, loading, changeCity };
},
};
</script>
<style lang="scss">
.city {
display: inline-block;
position: relative;
z-index: 400;
.select {
border: 1px solid #e4e4e4;
min-height: 60px;
text-align: center;
padding: 0 20px;
width: 350px;
line-height: 60px;
cursor: pointer;
&.active {
background: #fff;
}
.placeholder {
color: #999;
font-size: 30px;
}
.value {
color: #999;
font-size: 30px;
}
svg {
font-size: 28px;
margin-left: 5px;
}
}
.option {
width: 600px;
border: 1px solid #e4e4e4;
position: absolute;
left: 0;
top: 60px;
background: #fff;
min-height: 30px;
line-height: 30px;
display: flex;
flex-wrap: wrap;
padding: 10px;
.loading {
height: 290px;
width: 100%;
background: url(https://code-1307161657.cos.ap-beijing.myqcloud.com/images%2Fload.gif)
no-repeat center;
}
span {
width: 166px;
cursor: pointer;
text-align: center;
border-radius: 4px;
padding: 0 3px;
&:hover {
background: #f5f5f5;
}
}
}
}
</style>
- 使用
在任意.vue结尾的文件中引入city.vue就可以使用。
如果有默认数据的话,将默认数据传递给子组件。
代码如下(示例):
<template>
<h1>City组件示例</h1>
<div class="demo">
<h2>常规使用</h2>
<div class="demo__component">
<h3>fullLocation:默认显示的城市; change事件可以获得选择的城市</h3>
<City :fullLocation="fullLocation" @change-city="changeCity" />
</div>
<div class="demo__actions">
<Button>隐藏代码</Button>
</div>
<div v-if="false" class="demo__code">
<pre>代码</pre>
</div>
</div>
</template>
<script>
import {
ref } from "vue";
import City from "../libs/City.vue";
import Button from "../libs/Button.vue";
export default {
components: {
City,
Button,
},
setup() {
//默认地址的获取,如果有默认地址就获取默认地址即可,
const provinceCode = ref("110000");
const cityCode = ref("119900");
const countyCode = ref("110101");
const fullLocation = ref("北京市市辖区东城区");
// 更新选中的省市区数据
const changeCity = (cityInfo) => {
provinceCode.value = cityInfo.provinceCode;
cityCode.value = cityInfo.cityCode;
countyCode.value = cityInfo.countyCode;
fullLocation.value = cityInfo.fullLocation;
};
return {
fullLocation, changeCity };
},
};
</script>
<style lang="scss">
</style>