小程序省市级联组件使用 - 教程

背景。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>
  <picker
  mode="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>
posted @ 2025-08-08 18:19  yjbjingcha  阅读(19)  评论(0)    收藏  举报