2025-10-21 22:58:47 +08:00
|
|
|
|
# 五级联动地址选择器组件
|
|
|
|
|
|
|
|
|
|
|
|
## 组件简介
|
|
|
|
|
|
|
|
|
|
|
|
`area-cascade-picker` 是一个支持省市区县街道社区的五级联动地址选择组件,适用于需要选择详细地址的场景。
|
|
|
|
|
|
|
|
|
|
|
|
## 功能特点
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 五级联动选择(省/市/区/街道/社区)
|
|
|
|
|
|
- ✅ 自动级联更新
|
|
|
|
|
|
- ✅ 支持取消和确认操作
|
|
|
|
|
|
- ✅ 底部弹出式交互
|
|
|
|
|
|
- ✅ 支持自定义标题
|
|
|
|
|
|
- ✅ 返回完整的地址信息和各级代码
|
|
|
|
|
|
|
|
|
|
|
|
## 使用方法
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 引入组件
|
|
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<button @click="openPicker">选择地址</button>
|
|
|
|
|
|
<area-cascade-picker ref="areaPicker"></area-cascade-picker>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
|
import AreaCascadePicker from '@/components/area-cascade-picker/area-cascade-picker.vue'
|
|
|
|
|
|
|
|
|
|
|
|
const areaPicker = ref(null)
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 打开选择器
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const openPicker = () => {
|
|
|
|
|
|
areaPicker.value?.open({
|
|
|
|
|
|
title: '选择地址',
|
|
|
|
|
|
maskClick: true,
|
|
|
|
|
|
success: (addressData) => {
|
|
|
|
|
|
console.log('选择的地址:', addressData)
|
|
|
|
|
|
// 处理选择结果
|
|
|
|
|
|
},
|
|
|
|
|
|
cancel: () => {
|
|
|
|
|
|
console.log('取消选择')
|
|
|
|
|
|
},
|
|
|
|
|
|
change: (addressData) => {
|
|
|
|
|
|
console.log('地址变化:', addressData)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## API 说明
|
|
|
|
|
|
|
|
|
|
|
|
### open(config)
|
|
|
|
|
|
|
|
|
|
|
|
打开地址选择器
|
|
|
|
|
|
|
|
|
|
|
|
#### 参数 config
|
|
|
|
|
|
|
|
|
|
|
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
|
|
|
|
|--------|------|------|--------|------|
|
|
|
|
|
|
| title | String | 否 | '选择地址' | 选择器标题 |
|
|
|
|
|
|
| maskClick | Boolean | 否 | false | 是否允许点击遮罩关闭 |
|
|
|
|
|
|
| success | Function | 否 | - | 确认选择的回调函数 |
|
|
|
|
|
|
| cancel | Function | 否 | - | 取消选择的回调函数 |
|
|
|
|
|
|
| change | Function | 否 | - | 选择变化的回调函数 |
|
|
|
|
|
|
| defaultValue | Object | 否 | null | 默认选中的地址(暂未实现) |
|
2025-11-10 15:27:34 +08:00
|
|
|
|
| forceRefresh | Boolean | 否 | false | 是否强制刷新数据(忽略缓存) |
|
2025-10-21 22:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
#### success 回调参数
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
{
|
|
|
|
|
|
address: "新疆维吾尔自治区/喀什地区/喀什市/学府街道/学府社区居委会",
|
|
|
|
|
|
province: {
|
|
|
|
|
|
code: "650000",
|
|
|
|
|
|
name: "新疆维吾尔自治区"
|
|
|
|
|
|
},
|
|
|
|
|
|
city: {
|
|
|
|
|
|
code: "653100",
|
|
|
|
|
|
name: "喀什地区"
|
|
|
|
|
|
},
|
|
|
|
|
|
district: {
|
|
|
|
|
|
code: "653101",
|
|
|
|
|
|
name: "喀什市"
|
|
|
|
|
|
},
|
|
|
|
|
|
street: {
|
|
|
|
|
|
code: "65310101",
|
|
|
|
|
|
name: "学府街道"
|
|
|
|
|
|
},
|
|
|
|
|
|
community: {
|
|
|
|
|
|
code: "6531010101",
|
|
|
|
|
|
name: "学府社区居委会"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### close()
|
|
|
|
|
|
|
|
|
|
|
|
关闭地址选择器
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
areaPicker.value?.close()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-11-10 15:27:34 +08:00
|
|
|
|
### clearCache()
|
|
|
|
|
|
|
|
|
|
|
|
清除地址数据缓存
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
areaPicker.value?.clearCache()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-21 22:58:47 +08:00
|
|
|
|
## 数据格式
|
|
|
|
|
|
|
|
|
|
|
|
组件使用树形结构的地址数据,格式如下:
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
code: '650000', // 行政区划代码
|
|
|
|
|
|
name: '新疆维吾尔自治区', // 名称
|
|
|
|
|
|
children: [ // 下级行政区
|
|
|
|
|
|
{
|
|
|
|
|
|
code: '653100',
|
|
|
|
|
|
name: '喀什地区',
|
|
|
|
|
|
children: [
|
|
|
|
|
|
{
|
|
|
|
|
|
code: '653101',
|
|
|
|
|
|
name: '喀什市',
|
|
|
|
|
|
children: [
|
|
|
|
|
|
{
|
|
|
|
|
|
code: '65310101',
|
|
|
|
|
|
name: '学府街道',
|
|
|
|
|
|
children: [
|
|
|
|
|
|
{
|
|
|
|
|
|
code: '6531010101',
|
|
|
|
|
|
name: '学府社区居委会'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 完整示例
|
|
|
|
|
|
|
|
|
|
|
|
### 企业注册地址选择
|
|
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<view class="form-item" @click="selectLocation">
|
|
|
|
|
|
<text class="label">企业注册地点</text>
|
|
|
|
|
|
<view class="input-content">
|
|
|
|
|
|
<text :class="{ placeholder: !formData.address }">
|
|
|
|
|
|
{{ formData.address || '请选择注册地点' }}
|
|
|
|
|
|
</text>
|
|
|
|
|
|
<uni-icons type="arrowright" size="16"></uni-icons>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<area-cascade-picker ref="areaPicker"></area-cascade-picker>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, reactive } from 'vue'
|
|
|
|
|
|
import AreaCascadePicker from '@/components/area-cascade-picker/area-cascade-picker.vue'
|
|
|
|
|
|
|
|
|
|
|
|
const areaPicker = ref(null)
|
|
|
|
|
|
const formData = reactive({
|
|
|
|
|
|
address: '',
|
|
|
|
|
|
provinceCode: '',
|
|
|
|
|
|
provinceName: '',
|
|
|
|
|
|
cityCode: '',
|
|
|
|
|
|
cityName: '',
|
|
|
|
|
|
districtCode: '',
|
|
|
|
|
|
districtName: '',
|
|
|
|
|
|
streetCode: '',
|
|
|
|
|
|
streetName: '',
|
|
|
|
|
|
communityCode: '',
|
|
|
|
|
|
communityName: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const selectLocation = () => {
|
|
|
|
|
|
areaPicker.value?.open({
|
|
|
|
|
|
title: '选择企业注册地点',
|
|
|
|
|
|
maskClick: true,
|
|
|
|
|
|
success: (addressData) => {
|
|
|
|
|
|
// 保存完整地址
|
|
|
|
|
|
formData.address = addressData.address
|
|
|
|
|
|
|
|
|
|
|
|
// 保存各级信息
|
|
|
|
|
|
formData.provinceCode = addressData.province?.code
|
|
|
|
|
|
formData.provinceName = addressData.province?.name
|
|
|
|
|
|
formData.cityCode = addressData.city?.code
|
|
|
|
|
|
formData.cityName = addressData.city?.name
|
|
|
|
|
|
formData.districtCode = addressData.district?.code
|
|
|
|
|
|
formData.districtName = addressData.district?.name
|
|
|
|
|
|
formData.streetCode = addressData.street?.code
|
|
|
|
|
|
formData.streetName = addressData.street?.name
|
|
|
|
|
|
formData.communityCode = addressData.community?.code
|
|
|
|
|
|
formData.communityName = addressData.community?.name
|
|
|
|
|
|
|
|
|
|
|
|
console.log('已选择地址:', formData)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-11-10 15:27:34 +08:00
|
|
|
|
## 性能优化
|
|
|
|
|
|
|
|
|
|
|
|
### 懒加载方案(已实现)⭐
|
|
|
|
|
|
|
|
|
|
|
|
组件已实现**懒加载**机制,大幅优化90M+地址数据的加载性能:
|
|
|
|
|
|
|
|
|
|
|
|
#### 核心优化
|
|
|
|
|
|
|
|
|
|
|
|
1. **首次加载**:只加载省份列表(< 1MB,3-5秒完成)
|
|
|
|
|
|
2. **按需加载**:用户选择省份后,再加载该省份的详细数据(2-5MB,5-10秒)
|
|
|
|
|
|
3. **智能缓存**:已加载的数据会缓存,切换省份时秒开
|
|
|
|
|
|
4. **自动降级**:如果服务器不支持分片接口,自动从完整数据中提取
|
|
|
|
|
|
|
|
|
|
|
|
#### 性能对比
|
|
|
|
|
|
|
|
|
|
|
|
| 场景 | 优化前 | 优化后(懒加载) |
|
|
|
|
|
|
|------|--------|------------------|
|
|
|
|
|
|
| 首次打开选择器 | 加载90M+(3-5分钟) | 加载省份列表(< 1MB,3-5秒) |
|
|
|
|
|
|
| 选择省份 | 无需加载 | 加载该省份数据(2-5MB,5-10秒) |
|
|
|
|
|
|
| 切换省份 | 无需加载 | 从缓存读取(< 1秒) |
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用方式
|
|
|
|
|
|
|
|
|
|
|
|
组件已自动使用懒加载模式,无需修改调用代码:
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 正常使用,自动懒加载
|
|
|
|
|
|
areaPicker.value?.open({
|
|
|
|
|
|
success: (addressData) => {
|
|
|
|
|
|
console.log('选择的地址:', addressData)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 强制刷新数据(忽略缓存)
|
|
|
|
|
|
areaPicker.value?.open({
|
|
|
|
|
|
forceRefresh: true, // 强制从服务器重新加载
|
|
|
|
|
|
success: (addressData) => {
|
|
|
|
|
|
console.log('选择的地址:', addressData)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 清除缓存
|
|
|
|
|
|
areaPicker.value?.clearCache()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 服务器分片接口(最佳方案)🚀
|
|
|
|
|
|
|
|
|
|
|
|
如果服务器可以提供分片接口,性能会进一步提升。详见:[地址数据懒加载优化方案.md](../../docs/地址数据懒加载优化方案.md)
|
|
|
|
|
|
|
|
|
|
|
|
需要的接口:
|
|
|
|
|
|
- 省份列表接口:`/address_provinces.json`(轻量级,< 1MB)
|
|
|
|
|
|
- 省份详情接口:`/address_province_{code}.json`(按需加载,每个2-5MB)
|
|
|
|
|
|
|
|
|
|
|
|
### 缓存机制
|
|
|
|
|
|
|
|
|
|
|
|
1. **自动缓存**:已加载的数据会自动缓存到 IndexedDB(H5)或 uni.storage(小程序)
|
|
|
|
|
|
2. **缓存有效期**:默认7天,过期后自动重新加载
|
|
|
|
|
|
3. **离线支持**:网络失败时自动使用缓存数据
|
|
|
|
|
|
4. **存储方案**:优先使用 IndexedDB,自动降级到 uni.storage
|
|
|
|
|
|
|
2025-10-21 22:58:47 +08:00
|
|
|
|
## 注意事项
|
|
|
|
|
|
|
2025-11-10 15:27:34 +08:00
|
|
|
|
1. **数据来源**:当前从远程JSON文件加载,生产环境建议接入后端API
|
2025-10-21 22:58:47 +08:00
|
|
|
|
2. **数据更新**:如需接入后端API,修改 `loadAreaData` 方法即可
|
2025-11-10 15:27:34 +08:00
|
|
|
|
3. **性能优化**:已集成缓存机制,首次加载后速度大幅提升
|
2025-10-21 22:58:47 +08:00
|
|
|
|
4. **兼容性**:支持 H5、微信小程序等多端
|
2025-11-10 15:27:34 +08:00
|
|
|
|
5. **存储限制**:小程序环境有存储限制,如遇问题会自动清理旧缓存
|
2025-10-21 22:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
## 接入后端API
|
|
|
|
|
|
|
|
|
|
|
|
在组件的 `loadAreaData` 方法中取消注释并配置API:
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
async loadAreaData() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const resp = await uni.request({
|
|
|
|
|
|
url: '/app/common/area/cascade',
|
|
|
|
|
|
method: 'GET'
|
|
|
|
|
|
});
|
|
|
|
|
|
if (resp.statusCode === 200 && resp.data && resp.data.data) {
|
|
|
|
|
|
this.areaData = resp.data.data;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载地区数据失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 失败时使用模拟数据
|
|
|
|
|
|
this.areaData = this.getMockData();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 更新日志
|
|
|
|
|
|
|
2025-11-10 15:27:34 +08:00
|
|
|
|
### v1.2.0 (2025-01-XX)
|
|
|
|
|
|
- 🚀 **懒加载优化**:实现按需加载,首次加载从几分钟减少到几秒
|
|
|
|
|
|
- ✅ 首次只加载省份列表(< 1MB,3-5秒)
|
|
|
|
|
|
- ✅ 按需加载省份详情(选择省份时才加载)
|
|
|
|
|
|
- ✅ 智能缓存已加载的数据,切换省份秒开
|
|
|
|
|
|
- ✅ 支持服务器分片接口(最佳性能)
|
|
|
|
|
|
- ✅ 自动降级方案(兼容完整数据)
|
|
|
|
|
|
|
|
|
|
|
|
### v1.1.0 (2025-01-XX)
|
|
|
|
|
|
- 🚀 **性能优化**:集成智能缓存系统,优化90M+地址数据加载
|
|
|
|
|
|
- ✅ 支持 IndexedDB 和 uni.storage 双缓存方案
|
|
|
|
|
|
- ✅ 支持缓存过期管理和自动清理
|
|
|
|
|
|
- ✅ 支持强制刷新数据
|
|
|
|
|
|
- ✅ 优化首次加载体验,后续加载秒开
|
|
|
|
|
|
|
2025-10-21 22:58:47 +08:00
|
|
|
|
### v1.0.0 (2025-10-21)
|
|
|
|
|
|
- ✨ 初始版本
|
|
|
|
|
|
- ✅ 实现五级联动选择功能
|
|
|
|
|
|
- ✅ 支持省市区县街道社区选择
|
|
|
|
|
|
- ✅ 提供完整的地址信息返回
|
|
|
|
|
|
|