当前位置: 首页 > news >正文

小程序省市级联组件使用

背景。uni-data-picker组件用起来不方便。调整后级联效果欠佳,会关闭弹窗需要重新选择。

  • 解决方案。让cursor使用uniapp 原生组件生成懒加载省市级联
<template><view class="picker-cascader"><view class="cascader-label"><text v-if="required" class="required-mark">*</text><text class="label-text">{{ label }}</text></view><pickermode="multiSelector":range="range":value="defaultValue":disabled="disabled || readonly"@change="handleChange"@cancel="handleCancel"@columnchange="handleColumnChange"@confirm="handleConfirm"><view class="picker-input" :data-disabled="disabled || readonly"><text v-if="displayText" class="picker-text">{{ displayText }}</text><text v-else class="picker-placeholder">{{ placeholder }}</text><text class="picker-arrow">></text></view></picker></view>
</template><script>
import { getProvinceList, getCityListByProvince, getCountyListByCity } from '@/api/regionApi.js';
import { getProvinceListMock, getCityListByProvinceMock, getCountyListByCityMock } from '@/mock/regionMock.js';export default {name: 'PickerCascader',props: {/*** 标签文本*/label: {type: String,default: '所在地区'},/*** 绑定的值,支持字符串格式 "provinceCode,cityCode,countyCode" 或对象格式 {provinceCode: "110000", cityCode: "110100", countyCode: "110101"}*/regionStr: {type: [String, Object],default: ''},/*** 占位符文本*/placeholder: {type: String,default: '请选择省市区'},/*** 是否禁用*/disabled: {type: Boolean,default: false},/*** 是否只读*/readonly: {type: Boolean,default: false},/*** 最大选择级数,支持2-3级*/maxLevel: {type: Number,default: 3,validator: function (value) {return value >= 2 && value <= 3;}},/*** 是否必填*/required: {type: Boolean,default: false}},data() {return {// picker的range数据,格式为二维数组range: [],// picker的value数据,格式为数组,表示每列选中的索引defaultValue: [0, 0, 0],// 省份数据provinces: [],// 城市数据缓存,格式为 {provinceCode: cities}cityCache: {},// 县级数据缓存,格式为 {cityCode: counties}countyCache: {},// 当前选中的编码selectedCodes: ['', '', ''],// 当前选中的文本selectedTexts: ['', '', ''],// 是否正在加载数据loading: false};},computed: {/*** 显示文本*/displayText() {const texts = this.selectedTexts.filter((text) => text);return texts.length > 0 ? texts.join(' ') : '';}},watch: {/*** 监听value 变化,更新选中值*/regionStr: {handler(newVal) {console.log('value变化', newVal);this.initFromValue(newVal);},immediate: true}},mounted() {this.initData();},methods: {/*** 初始化数据*/async initData() {try {this.loading = true;console.log('PickerCascader 开始初始化数据...');await this.loadProvinces();this.initRange();this.initFromValue(this.regionStr);console.log('PickerCascader 数据初始化完成');console.log('省份数据:', this.provinces.length, '个');console.log('range数据:', this.range);} catch (error) {console.error('初始化数据失败:', error);} finally {this.loading = false;}},/*** 加载省份数据*/async loadProvinces() {try {console.log('开始加载省份数据...');const res = await getProvinceList();if (res.code === 200 && Array.isArray(res.data)) {this.provinces = res.data;console.log('从API获取省份数据成功:', this.provinces.length, '个省份');} else {// 使用mock数据console.log('API返回异常,使用mock数据');const mockRes = getProvinceListMock();this.provinces = mockRes.data;}console.log('省份数据加载完成:', this.provinces.length, '个省份');} catch (error) {console.error('获取省份列表失败:', error);// 使用mock数据const mockRes = getProvinceListMock();this.provinces = mockRes.data;console.log('使用mock数据,省份数量:', this.provinces.length);}},/*** 初始化range数据*/initRange() {// 初始化省份列const provinceColumn =this.provinces && this.provinces.length > 0? this.provinces.map((province) => ({text: province.name,code: province.code})): [];// 初始化城市列(空数据,等待选择省份后加载)const cityColumn = [];// 初始化县级列(空数据,等待选择城市后加载)const countyColumn = [];this.range = [provinceColumn, cityColumn, countyColumn];},/*** 从value初始化选中值*/initFromValue(value) {if (!value) {this.resetSelection();return;}let provinceCode = '';let cityCode = '';let countyCode = '';if (typeof value === 'string') {const codes = value.split(',');provinceCode = codes[0] || '';cityCode = codes[1] || '';countyCode = codes[2] || '';} else if (typeof value === 'object') {provinceCode = value.provinceCode || '';cityCode = value.cityCode || '';countyCode = value.countyCode || '';}this.setSelectionByCodes(provinceCode, cityCode, countyCode);},/*** 根据编码设置选中值*/async setSelectionByCodes(provinceCode, cityCode, countyCode) {if (!provinceCode) {this.resetSelection();return;}// 查找省份索引const provinceIndex = this.provinces.findIndex((p) => p.code === provinceCode);if (provinceIndex === -1) {this.resetSelection();return;}// 设置省份选中this.value[0] = provinceIndex;this.selectedCodes[0] = provinceCode;this.selectedTexts[0] = this.provinces[provinceIndex].name;// 加载城市数据await this.loadCities(provinceCode, provinceIndex);if (cityCode && this.range[1] && this.range[1].length > 0) {// 查找城市索引const cities = this.range[1];const cityIndex = cities.findIndex((c) => c.code === cityCode);if (cityIndex !== -1) {this.value[1] = cityIndex;this.selectedCodes[1] = cityCode;this.selectedTexts[1] = cities[cityIndex].text;// 如果是三级联动,加载县级数据if (this.maxLevel === 3) {await this.loadCounties(cityCode, provinceIndex, cityIndex);if (countyCode && this.range[2] && this.range[2].length > 0) {// 查找县级索引const counties = this.range[2];const countyIndex = counties.findIndex((c) => c.code === countyCode);if (countyIndex !== -1) {this.value[2] = countyIndex;this.selectedCodes[2] = countyCode;this.selectedTexts[2] = counties[countyIndex].text;}}}}}// 强制更新this.$forceUpdate();},/*** 重置选中值*/resetSelection() {this.value = [0, 0, 0];this.selectedCodes = ['', '', ''];this.selectedTexts = ['', '', ''];},/*** 加载城市数据*/async loadCities(provinceCode, provinceIndex) {console.log('开始加载城市数据,省份编码:', provinceCode);// 检查缓存if (this.cityCache[provinceCode]) {console.log('使用缓存的城市数据:', this.cityCache[provinceCode].length, '个城市');this.range[1] = this.cityCache[provinceCode];return;}try {const res = await getCityListByProvince(provinceCode);let cities = [];if (res.code === 200 && Array.isArray(res.data)) {cities = res.data;console.log('从API获取城市数据成功:', cities.length, '个城市');} else {// 使用mock数据console.log('API返回异常,使用mock数据');const mockRes = getCityListByProvinceMock(provinceCode);cities = mockRes.data;}// 转换为picker所需格式const cityColumn =cities && cities.length > 0? cities.map((city) => ({text: city.name,code: city.code})): [];console.log('城市数据转换完成:', cityColumn.length, '个城市');// 缓存数据this.cityCache[provinceCode] = cityColumn;this.range[1] = cityColumn;// 重置后续列的选中值this.value[1] = 0;this.value[2] = 0;this.selectedCodes[1] = '';this.selectedCodes[2] = '';this.selectedTexts[1] = '';this.selectedTexts[2] = '';// 清空县级数据this.range[2] = [];console.log('城市数据加载完成,range更新为:', this.range);// 强制更新this.$forceUpdate();} catch (error) {console.error('获取城市列表失败:', error);// 使用mock数据const mockRes = getCityListByProvinceMock(provinceCode);const cities = mockRes.data;const cityColumn =cities && cities.length > 0? cities.map((city) => ({text: city.name,code: city.code})): [];this.cityCache[provinceCode] = cityColumn;this.range[1] = cityColumn;console.log('使用mock数据,城市数量:', cityColumn.length);this.$forceUpdate();}},/*** 加载县级数据*/async loadCounties(cityCode, provinceIndex, cityIndex) {console.log('开始加载县级数据,城市编码:', cityCode);// 检查缓存if (this.countyCache[cityCode]) {console.log('使用缓存的县级数据:', this.countyCache[cityCode].length, '个县区');this.range[2] = this.countyCache[cityCode];return;}try {const res = await getCountyListByCity(cityCode);let counties = [];if (res.code === 200 && Array.isArray(res.data)) {counties = res.data;console.log('从API获取县级数据成功:', counties.length, '个县区');} else {// 使用mock数据console.log('API返回异常,使用mock数据');const mockRes = getCountyListByCityMock(cityCode);counties = mockRes.data;}// 转换为picker所需格式const countyColumn =counties && counties.length > 0? counties.map((county) => ({text: county.name,code: county.code})): [];console.log('县级数据转换完成:', countyColumn.length, '个县区');// 缓存数据this.countyCache[cityCode] = countyColumn;this.range[2] = countyColumn;// 重置县级选中值this.value[2] = 0;this.selectedCodes[2] = '';this.selectedTexts[2] = '';console.log('县级数据加载完成,range更新为:', this.range);// 强制更新this.$forceUpdate();} catch (error) {console.error('获取县级列表失败:', error);// 使用mock数据const mockRes = getCountyListByCityMock(cityCode);const counties = mockRes.data;const countyColumn =counties && counties.length > 0? counties.map((county) => ({text: county.name,code: county.code})): [];this.countyCache[cityCode] = countyColumn;this.range[2] = countyColumn;console.log('使用mock数据,县级数量:', countyColumn.length);this.$forceUpdate();}},/*** 处理列变化事件*/async handleColumnChange(e) {const { column, value } = e.detail;console.log('列变化事件:', { column, value, currentRange: this.range });// 更新选中索引this.value[column] = value;if (column === 0) {// 省份变化if (this.range[0] && this.range[0][value]) {const provinceCode = this.range[0][value].code;const provinceName = this.range[0][value].text;console.log('选择省份:', { provinceCode, provinceName });this.selectedCodes[0] = provinceCode;this.selectedTexts[0] = provinceName;// 加载城市数据await this.loadCities(provinceCode, value);}// 重置后续列的选中值this.value[1] = 0;this.value[2] = 0;this.selectedCodes[1] = '';this.selectedCodes[2] = '';this.selectedTexts[1] = '';this.selectedTexts[2] = '';// 清空县级数据this.range[2] = [];} else if (column === 1) {// 城市变化if (this.range[1] && this.range[1][value]) {const cityCode = this.range[1][value].code;const cityName = this.range[1][value].text;console.log('选择城市:', { cityCode, cityName });this.selectedCodes[1] = cityCode;this.selectedTexts[1] = cityName;// 如果是三级联动,加载县级数据if (this.maxLevel === 3) {await this.loadCounties(cityCode, this.value[0], value);}}// 重置县级选中值this.value[2] = 0;this.selectedCodes[2] = '';this.selectedTexts[2] = '';} else if (column === 2) {// 县级变化if (this.range[2] && this.range[2][value]) {const countyCode = this.range[2][value].code;const countyName = this.range[2][value].text;console.log('选择县级:', { countyCode, countyName });this.selectedCodes[2] = countyCode;this.selectedTexts[2] = countyName;}}// 强制更新this.$forceUpdate();},/*** 处理选择确认事件*/handleChange(e) {const { value } = e.detail;console.log('选择确认事件:', { value, range: this.range });// 更新选中索引this.value = value;// 更新选中编码和文本for (let i = 0; i < value.length; i++) {if (this.range[i] && this.range[i][value[i]] && value[i] >= 0) {this.selectedCodes[i] = this.range[i][value[i]].code;this.selectedTexts[i] = this.range[i][value[i]].text;}}// 触发change事件const result = this.formatResult();console.log('最终结果:', result);this.$emit('change', result);},/*** 处理确认事件*/handleConfirm(e) {console.log('确认事件:', e);// 这里可以添加额外的确认逻辑},/*** 处理取消事件*/handleCancel() {this.$emit('cancel');},/*** 格式化结果*/formatResult() {const codes = this.selectedCodes.filter((code) => code);const texts = this.selectedTexts.filter((text) => text);// 根据maxLevel返回相应格式if (this.maxLevel === 2) {return codes.slice(0, 2).join(',');} else {return codes.join(',');}}}
};
</script><style scoped>
.picker-cascader {background-color: #fff;border-radius: 12rpx;padding: 30rpx;margin-bottom: 20rpx;box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}.cascader-label {display: flex;align-items: center;margin-bottom: 20rpx;
}.required-mark {color: #ff4757;font-size: 28rpx;margin-right: 8rpx;font-weight: bold;
}.label-text {font-size: 28rpx;color: #333;font-weight: 500;
}.picker-input {display: flex;align-items: center;justify-content: space-between;height: 88rpx;padding: 0 24rpx;border: 2rpx solid #e1e5e9;border-radius: 8rpx;background-color: #fff;transition: all 0.3s ease;
}.picker-input:active {border-color: #2979ff;box-shadow: 0 0 0 4rpx rgba(41, 121, 255, 0.1);
}.picker-text {font-size: 28rpx;color: #333;flex: 1;
}.picker-placeholder {font-size: 28rpx;color: #999;flex: 1;
}.picker-arrow {font-size: 24rpx;color: #999;transform: rotate(90deg);
}/* 禁用状态 */
.picker-input[data-disabled='true'] {background-color: #f8f9fa;color: #999;cursor: not-allowed;
}.picker-input[data-disabled='true'] .picker-text,
.picker-input[data-disabled='true'] .picker-placeholder {color: #999;
}
</style>
http://www.dtcms.com/a/319002.html

相关文章:

  • 【课题推荐】卡尔曼滤波,创新性的算法与应用:从非线性适用性、鲁棒抗差、自适应、金融与生物新应用等方面考虑
  • 项目构想|文生图小程序
  • idea开发工具中git如何忽略编译文件build、gradle的文件?
  • C5.1:共发射极组态
  • 【Day 18】Linux-DNS解析
  • 如何理解“信号集是位掩码,每个bit代表一个信号”这句话?
  • 怎么在本地引入字体
  • 构建在 OpenTelemetry eBPF 基础之上:详解 Grafana Beyla 2.5 新特性
  • 防火墙环境下的全网服务器数据自动化备份平台搭建:基于 rsync 的完整实施指南
  • CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南
  • Express框架
  • 【JavaEE】(9) JVM
  • ElementUI之表格
  • 企业家 IP 发展态势剖析|创客匠人
  • 计算机网络1-5:计算机网络的性能指标
  • 【python 数据加密AES-GCM + 时间戳签名方案(带时间校验)】
  • vue3 el-select 加载触发
  • tcpdump问题记录
  • 软件运行时 ffmpeg.dll 丢失怎么办?从原因排查到完美修复的完整方案
  • 【Kafka系列】第二篇| Kafka 的核心概念、架构设计、底层原理
  • 什么是 TcpCommunicationSpi
  • HTML已死,HTML万岁——重新思考DOM的底层设计理念
  • 【音视频】WebRTC C++ native 编译
  • SpringAI动态调整大模型平台
  • 数据结构----栈和队列认识
  • Spring IoC 容器核心流程(面试必懂)
  • SpringMvc的原理深度剖析及源码解读
  • crew AI笔记[1] - 简介
  • list类
  • Spring中用到了哪些设计模式