Skip to content

城市选择组件

架构

bash
├── src
   ├── components
   ├── chooseCity
      ├── lib (引入的第三方库)
    ├── city.ts
    └── province.json
      ├── src (源码)
   ├── index.vue
      ├── index.ts

index.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>

city.ts下载连接

province.json