返回

AddressSelector: Typescript版本VUE+组件封装+最简单的策略模式

前端

去年,我曾在一个公司项目中做过类似的功能,但由于需求中仅使用了一次,就没有进行封装。最近,我正在对该项目进行2.0版本升级,其中包括对地址选择功能的优化。

首先,我们来分析一下需求。

  • 用户需要填写五级地址,从省到自然村。
  • 地址选择器需要支持动态数据加载,即当用户选择上一级地址时,下一级地址会自动加载。
  • 地址选择器需要支持自定义样式,以便与项目整体风格保持一致。
  • 地址选择器需要支持多语言,以便支持国际化。

接下来,我们开始注册组件。

// main.ts
import Vue from 'vue'
import AddressSelector from './components/AddressSelector.vue'

Vue.component('AddressSelector', AddressSelector)

然后,我们创建子组件。

// AddressSelector.vue
<template>
  <div class="address-selector">
    <select v-model="province">
      <option v-for="item in provinces" :value="item.code">{{ item.name }}</option>
    </select>
    <select v-model="city">
      <option v-for="item in cities" :value="item.code">{{ item.name }}</option>
    </select>
    <select v-model="district">
      <option v-for="item in districts" :value="item.code">{{ item.name }}</option>
    </select>
    <select v-model="town">
      <option v-for="item in towns" :value="item.code">{{ item.name }}</option>
    </select>
    <select v-model="village">
      <option v-for="item in villages" :value="item.code">{{ item.name }}</option>
    </select>
  </div>
</template>

<script>
import { ref } from 'vue'
import { getAddressData } from '../api/address'

export default {
  setup() {
    const province = ref('')
    const city = ref('')
    const district = ref('')
    const town = ref('')
    const village = ref('')

    const provinces = ref([])
    const cities = ref([])
    const districts = ref([])
    const towns = ref([])
    const villages = ref([])

    const loadProvinces = async () => {
      const res = await getAddressData('province')
      provinces.value = res.data
    }

    const loadCities = async (provinceCode) => {
      const res = await getAddressData('city', provinceCode)
      cities.value = res.data
    }

    const loadDistricts = async (cityCode) => {
      const res = await getAddressData('district', cityCode)
      districts.value = res.data
    }

    const loadTowns = async (districtCode) => {
      const res = await getAddressData('town', districtCode)
      towns.value = res.data
    }

    const loadVillages = async (townCode) => {
      const res = await getAddressData('village', townCode)
      villages.value = res.data
    }

    loadProvinces()

    return {
      province,
      city,
      district,
      town,
      village,
      provinces,
      cities,
      districts,
      towns,
      villages,
      loadCities,
      loadDistricts,
      loadTowns,
      loadVillages
    }
  }
}
</script>

<style scoped>
.address-selector {
  display: flex;
  align-items: center;
}

.address-selector select {
  margin-right: 10px;
}
</style>

接下来,我们使用策略模式。

// Strategy.ts
interface Strategy {
  getAddressData(type: string, code?: string): Promise<any>
}

class ProvinceStrategy implements Strategy {
  async getAddressData(type: string, code?: string): Promise<any> {
    // 省级地址数据
    return await fetch('省级地址数据接口')
  }
}

class CityStrategy implements Strategy {
  async getAddressData(type: string, code?: string): Promise<any> {
    // 市级地址数据
    return await fetch('市级地址数据接口')
  }
}

class DistrictStrategy implements Strategy {
  async getAddressData(type: string, code?: string): Promise<any> {
    // 区级地址数据
    return await fetch('区级地址数据接口')
  }
}

class TownStrategy implements Strategy {
  async getAddressData(type: string, code?: string): Promise<any> {
    // 镇级地址数据
    return await fetch('镇级地址数据接口')
  }
}

class VillageStrategy implements Strategy {
  async getAddressData(type: string, code?: string): Promise<any> {
    // 村级地址数据
    return await fetch('村级地址数据接口')
  }
}

// 根据地址类型返回对应的策略
const getAddressStrategy = (type: string): Strategy => {
  switch (type) {
    case 'province':
      return new ProvinceStrategy()
    case 'city':
      return new CityStrategy()
    case 'district':
      return new DistrictStrategy()
    case 'town':
      return new TownStrategy()
    case 'village':
      return new VillageStrategy()
  }
}

最后,我们编写测试用例。

// AddressSelector.spec.ts
import { mount } from '@vue/test-utils'
import AddressSelector from '../components/AddressSelector.vue'

describe('AddressSelector', () => {
  it('should render correctly', () => {
    const wrapper = mount(AddressSelector)
    expect(wrapper.find('select').exists()).toBe(true)
    expect(wrapper.find('option').exists()).toBe(true)
  })

  it('should load provinces correctly', async () => {
    const wrapper = mount(AddressSelector)
    await wrapper.vm.loadProvinces()
    expect(wrapper.vm.provinces.length).toBeGreaterThan(0)
  })

  it('should load cities correctly', async () => {
    const wrapper = mount(AddressSelector)
    await wrapper.vm.loadProvinces()
    await wrapper.vm.loadCities(wrapper.vm.provinces[0].code)
    expect(wrapper.vm.cities.length).toBeGreaterThan(0)
  })

  it('should load districts correctly', async () => {
    const wrapper = mount(AddressSelector)
    await wrapper.vm.loadProvinces()
    await wrapper.vm.loadCities(wrapper.vm.provinces[0].code)
    await wrapper.vm.loadDistricts(wrapper.vm.cities[0].code)
    expect(wrapper.vm.districts.length).toBeGreaterThan(0)
  })

  it('should load towns correctly', async () => {
    const wrapper = mount(AddressSelector)
    await wrapper.vm.loadProvinces()
    await wrapper.vm.loadCities(wrapper.vm.provinces[0].code)
    await wrapper.vm.loadDistricts(wrapper.vm.cities[0].code)
    await wrapper.vm.loadTowns(wrapper.vm.districts[0].code)
    expect(wrapper.vm.towns.length).toBeGreaterThan(0)
  })

  it('should load villages correctly', async () => {
    const wrapper = mount(AddressSelector)
    await wrapper.vm.loadProvinces()
    await wrapper.vm.loadCities(wrapper.vm.provinces[0].code)
    await wrapper.vm.loadDistricts(wrapper.vm.cities[0].code)
    await wrapper.vm.loadTowns(wrapper.vm.districts[0].code)
    await wrapper.vm.loadVillages(wrapper.vm.towns[0].code)
    expect(wrapper.vm.villages.length).toBeGreaterThan(0)
  })
})

最后,我们总结一下要点。

  • 本文介绍了如何使用Typescript版本VUE+组件封装和最简单的策略模式来实现地址选择。
  • 我们详细分析了需求,注册了组件,创建了子组件,使用了策略模式,编写了测试用例,最后总结了要点。
  • 希望本文能够帮助您快速开发出满足需求的地址选择器组件。