【Vue3】vue3 + ts 封装城市选择组件

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: 【Vue3】vue3 + ts 封装城市选择组件

城市选择-基本功能

能够封装城市选择组件,并且完成基础的显示隐藏的交互功能

(1)封装通用组件src/components/city/index.vue

<script lang="ts" setup name="City"></script>
<template>
  <div class="city">
    <div class="select">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option">
      <span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
    </div>
  </div>
</template>
<style scoped lang="less">
.city {
  display: inline-block;
  position: relative;
  z-index: 400;
  .select {
    border: 1px solid #e4e4e4;
    height: 30px;
    padding: 0 5px;
    line-height: 28px;
    cursor: pointer;
    &.active {
      background: #fff;
    }
    .placeholder {
      color: #999;
    }
    .value {
      color: #666;
      font-size: 12px;
    }
    i {
      font-size: 12px;
      margin-left: 5px;
    }
  }
  .option {
    width: 542px;
    border: 1px solid #e4e4e4;
    position: absolute;
    left: 0;
    top: 29px;
    background: #fff;
    min-height: 30px;
    line-height: 30px;
    display: flex;
    flex-wrap: wrap;
    padding: 10px;
    > span {
      width: 130px;
      text-align: center;
      cursor: pointer;
      border-radius: 4px;
      padding: 0 3px;
      &:hover {
        background: #f5f5f5;
      }
    }
  }
}
</style>

全局注册

import XtxCity from '@/components/city/index.vue'
export default {
  install(app: App) {
    app.component('City', City)
  },
}

提供类型 src/global.d.ts

import XtxCity from '@/components/city/index.vue'
declare module 'vue' {
    City: typeof City
  }
}

(2)在商品详情组件中渲染city组件 src/views/goods/components/goods-name.vue

<dl>
  <dt>配送</dt>
  <dd>至 <City></City></dd>
</dl>

(3)控制城市的显示和隐藏

  1. 点击的时候切换省市区的样式
  2. 切换城市弹层的显示或隐藏
<script lang="ts" setup name="City">
+ import { ref } from 'vue'
+const active = ref(false)
+const toggle = () => {
+  active.value = !active.value
+}
</script>
<template>
  <div class="city">
+    <div class="select" @click="toggle" :class="{ active: active }">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
+    <div class="option" v-show="active">
      <span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
    </div>
  </div>
</template>

(4)点击弹层外部,关闭弹层

<script lang="ts" setup>
import { ref } from 'vue'
+import { onClickOutside } from '@vueuse/core'
const active = ref(false)
const toggle = () => {
  active.value = !active.value
}
// 点击外面,关闭弹窗
const target = ref(null)
onClickOutside(target, () => {
// 参数1:监听哪个元素,target是通过ref绑定给我们监听盒子的
// 参数2:点击了该元素外其他地方触发的函数
  active.value = false
})
</script>
<template>
  <div class="city" ref="target">
    <div class="select" @click="toggle" :class="{ active: active }">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option" v-if="active">
      <span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
    </div>
  </div>
</template>

城市选择-动态渲染

需求:完成城市数据的获取以及渲染

注意

城市数据并不是直接从接口服务器中获取的,而是从阿里云服务器上获取的数据,所以不能使用封装好的request发送请求,直接使用 原生的axios 发送请求即可。https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.jsonopen in new window

(1)定义数据类型

// 城市列表类型
export type AreaList = {
  code: string
  level: number
  name: string
  areaList: AreaList[]
}

(2)获取数据src/components/city/index.vue

需求: 从接口获取城市数据,赋值给本地数据

  1. 定义列表数据变量 const cityList = ref<AreaList[]>([])
  2. 封装调接口的方法
  3. 方法内部调用接口,把获取到的结果赋值给cityList
  4. 调用方法
<script lang="ts" setup name="City">
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
import type { AreaList } from '@/types/goods'
import axios from 'axios'
// 控制弹层的显示隐藏
const active = ref(false)
const toggle = () => {
  active.value = !active.value
}
const target = ref(null)
onClickOutside(target, (e) => {
  // console.log(e)
  // 当点击target元素的外面的时候,就会触发
  active.value = false
})
const cityList = ref<AreaList[]>([])
const getCityList = async () => {
  const res = await axios.get<AreaList[]>(
    'https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json'
  )
  cityList.value = res.data
}
getCityList()
</script>

(3)渲染数据src/components/City/index.vue

<template>
  <div class="city" ref="target">
    <div class="select" :class="{ active: active }" @click="toggle">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option" v-show="active">
      <span class="ellipsis" v-for="item in cityList" :key="item.code">
        {{ item.name }}
      </span>
    </div>
  </div>
</template>

(4)点击弹层外部,关闭弹层

<script lang="ts" setup>
import { ref } from 'vue'
+import { onClickOutside } from '@vueuse/core'
const active = ref(false)
const toggle = () => {
  active.value = !active.value
}
// 点击外面,关闭弹窗
const target = ref(null)
onClickOutside(target, () => {
// 参数1:监听哪个元素,target是通过ref绑定给我们监听盒子的
// 参数2:点击了该元素外其他地方触发的函数
  active.value = false
})
</script>
<template>
  <div class="city" ref="target">
    <div class="select" @click="toggle" :class="{ active: active }">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option" v-if="active">
      <span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
    </div>
  </div>
</template>

城市选择-交互逻辑

需求:

  1. 点击选择省市区的时候能够把省市区数据存储起来
  1. 根据点击的每一个节点的level决定他应该存储到省/城/区的哪个下面
  2. 点击的时候,需要把当前弹层城市信息替换为当前点击节点的子节点的城市信息(点击省显示市,点击市显示县)
  3. 如果点击的是区的节点,则需要关弹层
  4. 如果中途点击了关闭弹层,则需要重置城市数据

点击某个省,显示省下面的市。点击市,显示市下面的县。

根据level判断级别。

(1)给城市注册点击事件

<div class="option" v-if="active">
  <span
    class="ellipsis"
    v-for="item in cityList"
    :key="item.code"
+    @click="selectCity(item)"
  >
    {{ item.name }}
  </span>
</div>

(2)城市切换逻辑

// 选择城市
const changeResult = ref({
  provinceCode: '',
  provinceName: '',
  cityCode: '',
  cityName: '',
  countyCode: '',
  countyName: ''
})
const selectCity = (city: AreaList) => {
  if (city.level === 0) {
    // 省
    changeResult.value.provinceName = city.name
    changeResult.value.provinceCode = city.code
    cityList.value = city.areaList
  }
  if (city.level === 1) {
    // 市
    changeResult.value.cityName = city.name
    changeResult.value.cityCode = city.code
    cityList.value = city.areaList
  }
  if (city.level === 2) {
    // 县(区)
    changeResult.value.countyName = city.name
    changeResult.value.countyCode = city.code
    // 关闭弹窗
    active.value = false
  }
}

(3)关闭时恢复城市数据

const cityList = ref<AreaList[]>([])
const cacheList = ref<AreaList[]>([])
const getCityData = async () => {
  const {data:res} = await axios.get<AreaListObj[]>('https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json')
  cityList.value = res
  cacheList.value = res
}
getCityData()
// 监听关闭弹窗的处理,恢复数据
watch(active, (value) => {
  // 当关闭active的时候,需要回复数据
  if (!value) {
    cityList.value = cacheList.value
  }
})

城市选择-完整地址处理

需求描述

  1. 默认展示的配送地址需要从父组件传递过来(登录的用户可以拿到当前用户的默认配送地址)
  2. 子组件选择完省市区需要传递给父组件,由父组件组织数据传给子组件进行展示(默认展示配送地址的数据源在父组件)

注意:完整地址需要父组件传递给子组件,将来如果登录的用户,父组件可以获取到完整的地址。

(1)父组件将城市数据传递给子组件

<script lang="ts" setup>
const userAddress = ref('江西省 九江市 不知道县')
</script>
 <City :userAddress="userAddress"></City></dd>

(2)子组件接收,并且进行展示

注意点:具体的地址和请选择配送同时只展示一个

defineProps<{
  userAddress?: string
}>()
<div class="select" @click="toggle" :class="{ active }">
  <span class="value" v-if="userAddress">{{ userAddress }}</span>
  <span class="placeholder" v-else>请选择配送地址</span>
  <i class="iconfont icon-angle-down"></i>
</div>

(3)子组件选择完城市,需要将数据传递给父组件

// 选择的城市结果类型
export type CityResult = {
  provinceCode: string
  provinceName: string
  cityCode: string
  cityName: string
  countyCode: string
  countyName: string
}
const changeResult = ref<Partial<CityResult>>({})
const emit = defineEmits<{
  (e: 'changeCity', value: CityResult): void
}>()
const selectCity = (city: AreaList) => {
  if (city.level === 0) {
    // 省
    changeResult.value.provinceName = city.name
    changeResult.value.provinceCode = city.code
    cityList.value = city.areaList
  }
  if (city.level === 1) {
    // 市
    changeResult.value.cityName = city.name
    changeResult.value.cityCode = city.code
    cityList.value = city.areaList
  }
  if (city.level === 2) {
    // 县(区)
    changeResult.value.countyName = city.name
    changeResult.value.countyCode = city.code
    // 关闭弹窗
    active.value = false
    // 子传父
    emit('changeCity', changeResult.value)
  }
}

优化代码(可选)

const changeResult = ref<Partial<CityResult>>({})
// record接受两个泛型参数,第一个为对象key的类型,第二个为对象值的类型
const cityMap: Record<number, 'province' | 'city' | 'county'> = {
  0: 'province',
  1: 'city',
  2: 'county'
}
const selectedCity = (city: AreaList) => {
  changeResult.value[`${cityMap[city["level"]]}Name`] = city.name
  changeResult.value[`${cityMap[city["level"]]}Code`] = city.code
  if (city.level === 2) {
    setIsShowCity(false)
    emits('changeCity', changeResult.value)
  } else {
    cityList.value = city.areaList
  }
}

(4)父组件接受数据并且处理

<dl>
  <dt>配送</dt>
  <dd>
    <XtxCity
      @changeCity="changeCity"
      :fullPath="fullPath"
    ></XtxCity>
  </dd>
</dl>
const userAddress = ref('江西省 九江市 不知道县')
const changeCity = (changeResult: CityResult) => {
  userAddress.value =
    changeResult.provinceName +
    ' ' +
    changeResult.cityName +
    ' ' +
    changeResult.countyName
}
', changeResult.value)
  } else {
    cityList.value = city.areaList
  }
}

(4)父组件接受数据并且处理

<dl>
  <dt>配送</dt>
  <dd>
    <XtxCity
      @changeCity="changeCity"
      :fullPath="fullPath"
    ></XtxCity>
  </dd>
</dl>
const userAddress = ref('江西省 九江市 不知道县')
const changeCity = (changeResult: CityResult) => {
  userAddress.value =
    changeResult.provinceName +
    ' ' +
    changeResult.cityName +
    ' ' +
    changeResult.countyName
}
相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
目录
相关文章
|
19小时前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
19小时前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
18小时前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
4天前
|
JavaScript API
vue3知识点:ref函数
vue3知识点:ref函数
13 2
|
4天前
|
JavaScript 前端开发 API
vue3知识点:Vue3.0中的响应式原理和 vue2.x的响应式
vue3知识点:Vue3.0中的响应式原理和 vue2.x的响应式
10 0
|
1天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
4天前
|
JavaScript
Vue 指令速查表
【10月更文挑战第12天】Vue 指令速查表
|
1天前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
20 9
|
19小时前
|
JavaScript 前端开发 UED
vue 提高 tree shaking 的效果
【10月更文挑战第23天】提高 Vue 中 Tree shaking 的效果需要综合考虑多个因素,包括模块的导出和引用方式、打包工具配置、代码结构等。通过不断地优化和调整,可以最大限度地发挥 Tree shaking 的优势,为 Vue 项目带来更好的性能和用户体验。
|
4天前
|
JavaScript 前端开发 开发者
Vue 的优缺点
【10月更文挑战第16天】Vue 具有众多优点,使其成为前端开发中备受青睐的框架之一。尽管它也存在一些局限性,但通过合理的应用和技术选型,这些问题可以得到一定程度的解决。在实际项目中,开发者可以根据项目的需求和特点,权衡 Vue 的优缺点,选择最适合的技术方案。同时,随着 Vue 不断的发展和完善,相信它将在前端开发领域继续发挥重要作用。
13 6