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