城市选择组件
架构
bash
├── src
│ ├── components
│ │ ├── chooseCity
│ │ │ ├── lib (引入的第三方库)
│ │ │ │ ├── city.ts
│ │ │ │ └── province.json
│ │ │ ├── src (源码)
│ │ │ │ ├── index.vue
│ │ │ ├── index.tsindex.ts
ts
import { type App } from "vue";
import chooseCity from "./src/index.vue";
// 让这个组件可以通过use的形式使用
export default {
install(app: App) {
app.component("YJ-choose-city", chooseCity);
},
};hooks/chooseCity/useCity.ts
ts
import { ElMessage } from "element-plus";
import { ref, watch, onMounted } from "vue";
import cityList from "@/components/chooseCity/lib/city.ts";
import provinceList from "@/components/chooseCity/lib/province.json";
export const useCity = () => {
// 是否显示
const visible = ref(false);
// 最后的结果
const result = ref([]);
// 选择的值
const selectvalue = ref([]);
// 筛选的列表
const options = ref([]);
// 选择字母还是城市 1 字母 2 省份
const choosetype = ref("1");
// 字母城市数据
const cities = ref(cityList);
// 省份城市数据
const provinceinit = ref(provinceList);
const province = ref(provinceList);
// 选择的字母
const selectLetters = ref("A");
// 省份选择的字母
const selectProvinceLetters = ref("A");
// 观察 选择的类型是字母还是省份
watch(
() => {
return choosetype.value;
},
(newVal, oldVal) => {
if (newVal != oldVal) {
selectLetters.value = "A";
selectProvinceLetters.value = "A";
}
},
);
// 观察结果的变化
watch(
() => {
return result.value;
},
(newVal, oldVal) => {
const cityall: any = cities.value;
if (choosetype.value == "1") {
// 一上来先都清空
for (let attr in cityall.cities) {
for (let i = 0; i < cityall.cities[attr].length; i++) {
cityall.cities[attr][i].active = false;
}
}
// 获取到结果
newVal.forEach((item, index) => {
for (let attr in cityall.cities) {
for (let i = 0; i < cityall.cities[attr].length; i++) {
// 如果匹配上,则赋值
if (cityall.cities[attr][i].name == item) {
cityall.cities[attr][i].active = true;
}
}
}
});
} else {
const provinceall: any = province.value;
for (let attr in provinceall) {
for (let i = 0; i < provinceall[attr].length; i++) {
for (let m = 0; m < provinceall[attr][i].data.length; m++) {
provinceall[attr][i].data[m].active = false;
}
}
newVal.forEach((item, index) => {
for (let attr in provinceall) {
for (let i = 0; i < provinceall[attr].length; i++) {
// 如果匹配上,则赋值
for (let m = 0; m < provinceall[attr][i].data.length; m++) {
if (provinceall[attr][i].data[m].name == item) {
provinceall[attr][i].data[m].active = true;
}
}
}
}
});
}
}
},
{ deep: true },
);
// 方法开始
// 获取 最开始的
const getoptions = () => {
const provinceall: any = province.value;
let result: any = [];
Object.values(provinceall).forEach((item: any, index) => {
item.forEach((value: any, index2: any) => {
value.data.forEach((content: any, index3: any) => {
result.push(content);
});
});
});
options.value = result;
};
// 过滤省份
const filterprovince = () => {
let result = JSON.parse(JSON.stringify(provinceinit.value));
Object.values(result).forEach((item: any, indx) => {
item.forEach((value: any, index: any) => {
value.data.forEach((content: any, index2: any) => {
let obj = {
active: false,
name: "",
};
obj.name = content;
obj.active = false;
value.data[index2] = obj;
});
});
});
province.value = result;
};
// 改变省份
const changeprovincecity = (content: string) => {
const resultAll = JSON.parse(JSON.stringify(result.value));
if (resultAll.length == 5) {
ElMessage.warning("最多只能选择五个城市");
return false;
}
if (resultAll.includes(content)) {
ElMessage.warning("该城市已经选择过了");
return false;
} else {
resultAll.push(content);
}
result.value = resultAll;
};
// 改变省份字母
const changeprovinceletters = (content: string) => {
selectProvinceLetters.value = content;
let el = document.getElementById("province" + content);
if (el) {
el.scrollIntoView();
}
};
// 改变字母
const changeletters = (content: string) => {
selectLetters.value = content;
let el = document.getElementById(content);
if (el) {
el.scrollIntoView();
}
};
// 清除所有
const cleanAll = () => {
result.value = [];
};
// 选择城市
const changecity = (content: any) => {
const resultAll = JSON.parse(JSON.stringify(result.value));
if (resultAll.length == 5) {
ElMessage.warning("最多只能选择五个城市");
return false;
}
if (resultAll.includes(content.name)) {
ElMessage.warning("该城市已经选择过了");
return false;
} else {
resultAll.push(content.name);
}
result.value = resultAll;
};
// 挂载
onMounted(() => {
filterprovince();
getoptions();
});
return {
visible,
result,
selectvalue,
options,
choosetype,
selectLetters,
selectProvinceLetters,
getoptions,
filterprovince,
changeprovincecity,
changeprovinceletters,
changeletters,
cleanAll,
changecity,
cities: cities,
province: province,
};
};components/chooseCity/index.vue
vue
<template>
<el-popover placement="bottom-start" width="600" :visible="visible">
<template #reference>
<!--最外边显示-->
<div class="result" @click="visible = !visible">
<div v-if="result.length == 0">请选择城市</div>
<div v-else>{{ result.join(",") }}</div>
</div>
<!--最外边结束-->
</template>
<template #default>
<!--内容开始-->
<div class="container">
<el-row>
<el-col :span="8">
<el-radio-group v-model="choosetype">
<el-radio-button value="1">按照城市</el-radio-button>
<el-radio-button value="2">按照省份</el-radio-button>
</el-radio-group>
</el-col>
<el-col :span="13">
<el-select
v-model="result"
multiple
placeholder="请选择"
filterable
style="width: 320px"
>
<el-option
v-for="item in options"
:key="item.name"
:label="item.name"
:value="item.name"
>
</el-option>
</el-select>
</el-col>
<el-col :span="2" style="margin-right: 10px">
<el-button type="primary" @click="cleanAll">清空</el-button>
</el-col>
</el-row>
<!--循环拼音字母-->
<div class="letters" v-if="choosetype == '1'">
<div
v-for="(content, index) in Object.keys(cities.cities)"
:key="index"
:class="[
selectLetters == content ? 'letteritems active' : 'letteritems',
]"
@click="changeletters(content)"
>
{{ content }}
</div>
</div>
<!--循环拼音字母-->
<!--按照拼音首字母循环城市-->
<div class="cityAll" v-if="choosetype == '1'">
<div v-for="(content, attr) in cities.cities" :key="attr">
<el-row :id="attr">
<!--左侧字母-->
<el-col :span="2" class="cityAll_letters"> {{ attr }}</el-col>
<!--左侧字母-->
<!--右侧数据-->
<el-col :span="22">
<div
v-for="(item, index) in content"
:key="index"
:class="[
item.active ? 'cityall_items active' : 'cityall_items',
]"
@click="changecity(item)"
>
{{ item.name }}
</div>
</el-col>
<!--右侧数据-->
</el-row>
</div>
</div>
<!--按照拼音首字母循环城市-->
<!--循环省市字母-->
<div class="letters" v-if="choosetype == '2'">
<div
v-for="(content, index) in Object.keys(province)"
:key="index"
:class="[
selectProvinceLetters == content
? 'letteritems provinceletters active'
: 'letteritems provinceletters',
]"
@click="changeprovinceletters(content)"
>
{{ content }}
</div>
</div>
<!--循环省市字母-->
<!--按照省份循环城市-->
<div class="cityAll" v-if="choosetype == '2'">
<div v-for="(content, index) in Object.values(province)" :key="index">
<div v-for="(item, index2) in content" :key="index2">
<el-row :id="'province' + item.id">
<!--左侧字母-->
<el-col :span="3" class="cityAll_letters">
{{ item.name }}</el-col
>
<!--左侧字母-->
<!--右侧数据-->
<el-col :span="21">
<div
v-for="(value, index3) in item.data"
:key="index3"
:class="[
value.active ? 'cityall_items active' : 'cityall_items',
]"
@click="changeprovincecity(value.name)"
>
{{ value.name }}
</div>
</el-col>
<!--右侧数据-->
</el-row>
</div>
</div>
</div>
<!--按照省份循环城市-->
</div>
<!--内容结束-->
</template>
</el-popover>
</template>
<script setup lang="ts">
import { useCity } from "@/hooks/chooseCity/useCity";
const {
visible,
result,
selectvalue,
options,
choosetype,
selectLetters,
selectProvinceLetters,
getoptions,
filterprovince,
changeprovincecity,
changeprovinceletters,
changeletters,
cleanAll,
changecity,
cities,
province,
} = useCity();
</script>
<style scoped lang="scss">
.result {
width: 220px;
height: 40px;
border: 1px solid #ccc;
cursor: pointer;
text-align: center;
line-height: 35px;
margin: 0 auto;
margin-top: 30px;
}
.container {
.letters {
margin-bottom: 20px;
overflow: hidden;
.letteritems {
font-size: 14px;
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
border: 1px solid #ccc;
float: left;
margin-top: 20px;
margin-bottom: 0px;
margin-right: 10px;
cursor: pointer;
&.provinceletters {
width: 50px;
height: 30px;
}
&.active {
background: #409eff;
color: white;
}
}
}
.cityAll {
width: 580px;
height: 300px;
overflow-y: auto;
.cityAll_letters {
font-weight: 700;
font-size: 16px;
}
.cityall_items {
font-size: 15px;
float: left;
margin-right: 10px;
margin-bottom: 10px;
cursor: pointer;
&.active {
color: red;
}
}
}
}
</style>