Files
ks-app-employment-service/components/area-cascade-picker/area-cascade-picker.vue
2025-11-10 15:27:34 +08:00

523 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<uni-popup
ref="popup"
type="bottom"
borderRadius="10px 10px 0 0"
background-color="#FFFFFF"
:mask-click="maskClick"
>
<view class="popup-content">
<view class="popup-header">
<view class="btn-cancel" @click="cancel">取消</view>
<view class="title">{{ title }}</view>
<view class="btn-confirm" @click="confirm">确认</view>
</view>
<view class="popup-list">
<picker-view
indicator-style="height: 84rpx;"
:value="selectedIndex"
@change="bindChange"
class="picker-view"
>
<!-- -->
<picker-view-column>
<view
v-for="(item, index) in provinceList"
:key="item.code"
class="item"
:class="{ 'item-active': selectedIndex[0] === index }"
>
<text>{{ item.name }}</text>
</view>
</picker-view-column>
<!-- -->
<picker-view-column>
<view
v-for="(item, index) in cityList"
:key="item.code"
class="item"
:class="{ 'item-active': selectedIndex[1] === index }"
>
<text>{{ item.name }}</text>
</view>
</picker-view-column>
<!-- 区县 -->
<picker-view-column>
<view
v-for="(item, index) in districtList"
:key="item.code"
class="item"
:class="{ 'item-active': selectedIndex[2] === index }"
>
<text>{{ item.name }}</text>
</view>
</picker-view-column>
<!-- 街道 -->
<picker-view-column>
<view
v-for="(item, index) in streetList"
:key="item.code"
class="item"
:class="{ 'item-active': selectedIndex[3] === index }"
>
<text>{{ item.name }}</text>
</view>
</picker-view-column>
<!-- 社区/居委会 -->
<picker-view-column>
<view
v-for="(item, index) in communityList"
:key="item.code"
class="item"
:class="{ 'item-active': selectedIndex[4] === index }"
>
<text>{{ item.name }}</text>
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</uni-popup>
</template>
<script>
import addressDataLoaderLazy from '@/utils/addressDataLoaderLazy.js';
export default {
name: 'AreaCascadePicker',
data() {
return {
maskClick: false,
title: '选择地址',
confirmCallback: null,
cancelCallback: null,
changeCallback: null,
selectedIndex: [0, 0, 0, 0, 0],
// 原始数据(懒加载模式下,只存储省份列表)
areaData: [],
// 各级列表
provinceList: [],
cityList: [],
districtList: [],
streetList: [],
communityList: [],
// 当前选中的项
selectedProvince: null,
selectedCity: null,
selectedDistrict: null,
selectedStreet: null,
selectedCommunity: null,
// 加载状态
isLoading: false,
// 懒加载模式:已加载的省份详情缓存
loadedProvinceDetails: new Map(),
};
},
methods: {
async open(newConfig = {}) {
const {
title,
success,
cancel,
change,
maskClick = false,
defaultValue = null,
forceRefresh = false, // 是否强制刷新数据
} = newConfig;
this.reset();
if (title) this.title = title;
if (typeof success === 'function') this.confirmCallback = success;
if (typeof cancel === 'function') this.cancelCallback = cancel;
if (typeof change === 'function') this.changeCallback = change;
this.maskClick = maskClick;
// 加载地区数据
this.isLoading = true;
try {
await this.loadAreaData(forceRefresh);
// 初始化列表
this.initLists();
this.$nextTick(() => {
this.$refs.popup.open();
});
} catch (error) {
console.error('打开地址选择器失败:', error);
uni.showToast({
title: '加载地址数据失败',
icon: 'none',
duration: 2000
});
} finally {
this.isLoading = false;
}
},
async loadAreaData(forceRefresh = false) {
try {
console.log('正在加载省份列表(懒加载模式)...');
// 优先尝试调用后端API获取省份列表
// const resp = await uni.request({
// url: '/app/common/area/provinces',
// method: 'GET'
// });
// if (resp.statusCode === 200 && resp.data && resp.data.data) {
// this.areaData = resp.data.data;
// return;
// }
// 使用懒加载器:只加载省份列表(数据量很小,< 1MB
const provinceList = await addressDataLoaderLazy.loadProvinceList(forceRefresh);
if (provinceList && Array.isArray(provinceList)) {
this.areaData = provinceList;
} else {
console.warn('省份列表格式不正确,使用空数据');
this.areaData = [];
}
} catch (error) {
console.error('加载省份列表失败:', error);
this.areaData = [];
throw error;
}
},
initLists() {
// 初始化省列表
this.provinceList = this.areaData;
if (this.provinceList.length > 0) {
this.selectedProvince = this.provinceList[0];
// 懒加载:首次选择第一个省份时,加载其详情
this.updateCityList();
}
},
async updateCityList() {
if (!this.selectedProvince) {
this.cityList = [];
this.districtList = [];
this.streetList = [];
this.communityList = [];
return;
}
// 检查是否已加载该省份的详情
let provinceDetail = this.loadedProvinceDetails.get(this.selectedProvince.code);
// 如果未加载,则懒加载该省份的详情
if (!provinceDetail) {
try {
console.log(`📥 懒加载省份详情: ${this.selectedProvince.name} (${this.selectedProvince.code})`);
provinceDetail = await addressDataLoaderLazy.loadProvinceDetail(
this.selectedProvince.code,
false
);
if (provinceDetail) {
// 缓存已加载的省份详情
this.loadedProvinceDetails.set(this.selectedProvince.code, provinceDetail);
// 更新省份对象添加children
Object.assign(this.selectedProvince, {
children: provinceDetail.children || []
});
}
} catch (error) {
console.error('加载省份详情失败:', error);
// 如果加载失败,检查是否有 _hasChildren 标记
if (this.selectedProvince._hasChildren) {
uni.showToast({
title: '加载城市数据失败,请重试',
icon: 'none',
duration: 2000
});
}
this.cityList = [];
this.districtList = [];
this.streetList = [];
this.communityList = [];
return;
}
} else {
// 如果已加载,直接使用缓存的详情
Object.assign(this.selectedProvince, {
children: provinceDetail.children || []
});
}
// 更新城市列表
if (!this.selectedProvince.children || this.selectedProvince.children.length === 0) {
this.cityList = [];
this.districtList = [];
this.streetList = [];
this.communityList = [];
return;
}
this.cityList = this.selectedProvince.children;
this.selectedIndex[1] = 0;
if (this.cityList.length > 0) {
this.selectedCity = this.cityList[0];
this.updateDistrictList();
}
},
updateDistrictList() {
if (!this.selectedCity || !this.selectedCity.children) {
this.districtList = [];
this.streetList = [];
this.communityList = [];
return;
}
this.districtList = this.selectedCity.children;
this.selectedIndex[2] = 0;
if (this.districtList.length > 0) {
this.selectedDistrict = this.districtList[0];
this.updateStreetList();
}
},
updateStreetList() {
if (!this.selectedDistrict || !this.selectedDistrict.children) {
this.streetList = [];
this.communityList = [];
return;
}
this.streetList = this.selectedDistrict.children;
this.selectedIndex[3] = 0;
if (this.streetList.length > 0) {
this.selectedStreet = this.streetList[0];
this.updateCommunityList();
}
},
updateCommunityList() {
if (!this.selectedStreet || !this.selectedStreet.children) {
this.communityList = [];
return;
}
this.communityList = this.selectedStreet.children;
this.selectedIndex[4] = 0;
if (this.communityList.length > 0) {
this.selectedCommunity = this.communityList[0];
}
},
async bindChange(e) {
const newIndex = e.detail.value;
// 检查哪一列发生了变化
for (let i = 0; i < 5; i++) {
if (newIndex[i] !== this.selectedIndex[i]) {
this.selectedIndex[i] = newIndex[i];
// 根据变化的列更新后续列
if (i === 0) {
// 省变化 - 需要懒加载新省份的详情
this.selectedProvince = this.provinceList[newIndex[0]];
await this.updateCityList(); // 改为异步
} else if (i === 1) {
// 市变化
this.selectedCity = this.cityList[newIndex[1]];
this.updateDistrictList();
} else if (i === 2) {
// 区县变化
this.selectedDistrict = this.districtList[newIndex[2]];
this.updateStreetList();
} else if (i === 3) {
// 街道变化
this.selectedStreet = this.streetList[newIndex[3]];
this.updateCommunityList();
} else if (i === 4) {
// 社区变化
this.selectedCommunity = this.communityList[newIndex[4]];
}
break;
}
}
if (this.changeCallback) {
this.changeCallback(this.getSelectedAddress());
}
},
getSelectedAddress() {
const parts = [];
if (this.selectedProvince) parts.push(this.selectedProvince.name);
if (this.selectedCity) parts.push(this.selectedCity.name);
if (this.selectedDistrict) parts.push(this.selectedDistrict.name);
if (this.selectedStreet) parts.push(this.selectedStreet.name);
if (this.selectedCommunity) parts.push(this.selectedCommunity.name);
return {
address: parts.join('/'),
province: this.selectedProvince,
city: this.selectedCity,
district: this.selectedDistrict,
street: this.selectedStreet,
community: this.selectedCommunity,
};
},
close() {
this.$refs.popup.close();
},
cancel() {
this.clickCallback(this.cancelCallback);
},
confirm() {
this.clickCallback(this.confirmCallback);
},
async clickCallback(callback) {
if (typeof callback !== 'function') {
this.$refs.popup.close();
return;
}
try {
const result = await callback(this.getSelectedAddress());
if (result !== false) {
this.$refs.popup.close();
}
} catch (error) {
console.error('callback 执行出错:', error);
}
},
reset() {
this.maskClick = false;
this.confirmCallback = null;
this.cancelCallback = null;
this.changeCallback = null;
this.selectedIndex = [0, 0, 0, 0, 0];
this.provinceList = [];
this.cityList = [];
this.districtList = [];
this.streetList = [];
this.communityList = [];
},
/**
* 清除地址数据缓存(供外部调用)
*/
async clearCache() {
try {
await addressDataLoaderLazy.clearCache();
// 同时清除内存缓存
this.loadedProvinceDetails.clear();
uni.showToast({
title: '缓存已清除',
icon: 'success',
duration: 2000
});
} catch (error) {
console.error('清除缓存失败:', error);
uni.showToast({
title: '清除缓存失败',
icon: 'none',
duration: 2000
});
}
}
},
};
</script>
<style lang="scss" scoped>
.popup-content {
color: #000000;
height: 60vh;
}
.popup-list {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: space-evenly;
flex: 1;
overflow: hidden;
.picker-view {
width: 100%;
height: calc(60vh - 100rpx);
margin-top: 20rpx;
.uni-picker-view-mask {
background: rgba(0, 0, 0, 0);
}
.item {
line-height: 84rpx;
height: 84rpx;
text-align: center;
font-weight: 400;
font-size: 28rpx;
color: #cccccc;
padding: 0 4rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-active {
color: #333333;
font-weight: 500;
}
.uni-picker-view-indicator:after {
border-color: #e3e3e3;
}
.uni-picker-view-indicator:before {
border-color: #e3e3e3;
}
}
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 40rpx 10rpx 40rpx;
.title {
font-weight: 500;
font-size: 36rpx;
color: #333333;
text-align: center;
}
.btn-cancel {
font-weight: 400;
font-size: 32rpx;
color: #666d7f;
line-height: 38rpx;
}
.btn-confirm {
font-weight: 400;
font-size: 32rpx;
color: #256bfa;
}
}
</style>