返回
AddressSelector: Typescript版本VUE+组件封装+最简单的策略模式
前端
2023-12-31 02:42:23
去年,我曾在一个公司项目中做过类似的功能,但由于需求中仅使用了一次,就没有进行封装。最近,我正在对该项目进行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+组件封装和最简单的策略模式来实现地址选择。
- 我们详细分析了需求,注册了组件,创建了子组件,使用了策略模式,编写了测试用例,最后总结了要点。
- 希望本文能够帮助您快速开发出满足需求的地址选择器组件。