uniapp地区选择三级联动选择器可搜索

AI编写自己修改

三级联动方式


image.png

搜索方式


image.png
<template>
  <view class="area-picker">
    <!-- 触发按钮 -->
 <!--   <view class="trigger" @click="open">
      <text>{{ selectedArea || '请选择地区' }}</text>
      <text class="icon">▼</text>
    </view> -->
    
    <view class="" @click="showPicker = true">
        {{ selectedArea  ? selectedArea :'请选择省市区'}}
        <i class="iconfont icon-Downarrow-R" @click="open"></i>
    </view>

    <!-- 地区选择弹窗 -->
    <u-popup
      v-model="showPicker"
      mode="bottom"
      round
      @close="showPicker = false"
    >
      <view class="picker-header">
        <text class="title">选择地区</text>
        <text class="close" @click="showPicker = false">×</text>
      </view>

      <!-- 搜索框 -->
      <view class="search-box">
        <u-search
          v-model="searchKeyword"
          placeholder="搜索省/市/区"
          @search="onSearch"
        />
      </view>

      <!-- 三级联动选择器 -->
      <view v-if="!searchKeyword" class="picker-container">
        <picker-view
          :value="pickerValue"
          @change="onPickerChange"
          class="picker-view"
          indicator-style="height: 44px;"
        >
          <!-- 省份列 -->
          <picker-view-column>
            <view
              v-for="(province, index) in provinces"
              :key="province.code"
              class="picker-item"
            >
              {{ province.name }}
            </view>
          </picker-view-column>

          <!-- 城市列 -->
          <picker-view-column>
            <view
              v-for="(city, index) in currentCities"
              :key="city.code"
              class="picker-item"
            >
              {{ city.name }}
            </view>
          </picker-view-column>

          <!-- 区县列 -->
          <picker-view-column>
            <view
              v-for="(district, index) in currentDistricts"
              :key="district.code"
              class="picker-item"
            >
              {{ district.name }}
            </view>
          </picker-view-column>
        </picker-view>
      </view>

      <!-- 搜索结果列表 -->
      <view v-else class="search-results">
        <scroll-view scroll-y class="results-scroll">
          <view
            v-for="(item, index) in filteredAreas"
            :key="item.code"
            class="result-item"
            @click="selectSearchResult(item)"
          >
            <text class="result-text">{{ item.fullPath }}</text>
          </view>
          <view v-if="filteredAreas.length === 0" class="empty">
            未找到相关地区
          </view>
        </scroll-view>
      </view>

      <!-- 确认按钮 -->
      <view class="actions">
        <u-button type="default" @click="showPicker = false">取消</u-button>
        <u-button type="primary" @click="confirmSelection">确认</u-button>
      </view>
    </u-popup>
  </view>
</template>

<script>
// 这里需要导入地区数据,实际项目中可以从接口获取或使用静态数据
import areaData from './area-data.js'
import {
    getRegionsList,
    getProvinceApi,
    getProvinceList,
    getMerchMcc,
    getAreaApi,
    getCityApi,
    getKeywords
} from '_a/home';

export default {
  name: 'AreaPicker',
  props: {
    value: {
      type: String,
      default: ''
    },
    sourceType: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      showPicker: false,
      searchKeyword: '',
      pickerValue: [0, 0, 0],
      lastPickerValue: [0, 0, 0],
      provinces: [],
      cities: [],
      districts: [],
      searchResults: [],
      selectedArea: '',
      allAreas: ''
    }
  },
  computed: {
    currentCities() {
      // if (this.provinces.length === 0) return []
      // const provinceIndex = this.pickerValue[0]
      // return this.provinces[provinceIndex]?.children || []
      // this.cities = await this.getCityApiData(this.provinces[this.pickerValue[0]].code, this.channelCode);
      // console.log('触发城市列表...')
      // console.log(this.cities)
      return this.cities;
    },
    currentDistricts() {
      // if (this.currentCities.length === 0) return []
      // const cityIndex = this.pickerValue[1]
      // return this.currentCities[cityIndex]?.children || []
      return this.districts;
    },
    // 根据搜索关键词过滤地区
    filteredAreas() {
      if (!this.searchKeyword.trim()) {
        return this.allAreas
      }
      const kw = this.searchKeyword.toLowerCase()
      return this.allAreas.filter(item => {
        return (
          item.fullPath.toLowerCase().includes(kw) ||
          item.name.toLowerCase().includes(kw)
        )
      })
    }
  },
  mounted() {
    this.initAreaData()
    if (this.value) {
      this.selectedArea = this.value
    }
  },
  methods: {
    async getProvinceApiData(channelCode) {
        let res = await getProvinceApi({channelCode});
        // this.provinceList = res.data;
        return res.data
    },
    async getCityApiData(code,channelCode) {
        let res = await getCityApi({
            code,channelCode
        });
        // this.provinceList = res.data;
        return res.data
    },
    async getAreaApiData(code,channelCode) {
        let res = await getAreaApi({
            code,channelCode
        });
        // this.provinceList = res.data;
        return res.data;
    },
    async loadFlattenedRegions(channelCode) {
        this.loading = true;
        try {
            // 替换为你的实际接口地址
            // const res = await this.$http.get('/api/v1/regions/flattened');
            const res = await getRegionsList({channelCode});
            this.allAreas = res.data || [];
            console.log('加载扁平话内容... code' + channelCode)
        } catch (error) {
            console.error('加载地区数据失败:', error);
            uni.showToast({ title: '地区数据加载失败', icon: 'none' });
        } finally {
            this.loading = false;
        }
    },
      
    open() {
        this.showPicker = true
        console.log('打开')
    },
      
    // 初始化地区数据
    async initAreaData() {
      console.log('初始化的省市区数据')
      console.log(this.sourceType)
      // 这里使用模拟数据,实际项目中可以替换为真实数据
      // this.provinces = areaData
      
      this.provinces = await this.getProvinceApiData(this.sourceType);
      this.cities = await this.getCityApiData(this.provinces[0].code, this.sourceType);
      this.districts = await this.getAreaApiData(this.cities[0].code, this.sourceType);
      
      
      this.loadFlattenedRegions(this.sourceType)
    },

    // 选择器变化
    async onPickerChange(e) {
      // console.log('选择器变化对象')
      // console.log(e)
      this.pickerValue = e.detail.value
      const newValue = e.detail.value
      
      const changedColumn = this.getChangedColumn(this.lastPickerValue, newValue)
      // 当省份变化时,重置城市和区县
      if (changedColumn == 0) {
        // this.pickerValue = [this.pickerValue[0], 0, 0]
        
        this.cities = await this.getCityApiData(this.provinces[newValue[0]].code, this.sourceType);
        this.districts = await this.getAreaApiData(this.cities[newValue[1]].code, this.sourceType);
        // console.log('触发城市列表...')
        console.log(this.cities)
        console.log(this.districts)
      }
      // 当城市变化时,重置区县
      else if (changedColumn == 1) {
        // this.pickerValue = [this.pickerValue[0], this.pickerValue[1], 0]
        
        this.districts = await this.getAreaApiData(this.cities[newValue[1]].code, this.sourceType);
        // console.log('触发区域列表...')
        console.log(this.districts)
      }
      
      this.lastPickerValue = [...newValue]
      
      await this.$forceUpdate();
    },
    getChangedColumn(oldValue, newValue) {
      for (let i = 0; i < newValue.length; i++) {
        if (oldValue[i] !== newValue[i]) {
          return i
        }
      }
      return -1 // 没有变化
    },
    // 选择搜索结果
    selectSearchResult(item) {
      this.selectedArea = item.fullPath
      this.searchKeyword = ''
      this.showPicker = false
      this.selectedCodes = item.codes
      this.$emit('input', item.fullName)
      
      let codeList = item.fullCode.split('-')
      let pathList = item.fullPath.split('-')
      this.$emit('change', {
        fullCode: item.fullCode,
        fullPath: item.fullPath,
        mercPro: codeList[0],
        mercCity: codeList[1],
        mercArea: codeList[2],
        mercProName: pathList[0],
        mercCityName: pathList[1],
        mercAreaName: pathList[2]
      })
      
    },

    // 确认选择
    confirmSelection() {
      const province = this.provinces[this.pickerValue[0]]
      const city = this.currentCities[this.pickerValue[1]]
      const district = this.currentDistricts[this.pickerValue[2]]

      if (province && city && district) {
        this.selectedArea = `${province.name}-${city.name}-${district.name}`
        this.selectedCodes = [province.code, city.code, district.code]
  //       console.log('省市区')
        // console.log(province)
        // console.log('城市')
        // console.log(city)
        // console.log('区域')
        // console.log(district)
        
        // this.$emit('input', this.selectedArea)
        // this.$emit('change', {
        //   area: this.selectedArea,
        //   codes: this.selectedCodes,
        //   province: province.name,
        //   city: city.name,
        //   district: district.name
        // })
        this.$emit('change', {
            fullCode: this.selectedCodes,
            fullPath: this.selectedArea,
            mercPro: province.code,
            mercCity: city.code,
            mercArea: district.code,
            mercProName: province.name,
            mercCityName: city.name,
            mercAreaName: district.name
        })
      }

      this.showPicker = false
    }
  }
}
</script>

<style scoped>
.area-picker {
}

.trigger {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 24rpx;
  background: #f7f8fa;
  border-radius: 8rpx;
  border: 1px solid #ebedf0;
}

.trigger .icon {
  color: #969799;
  font-size: 24rpx;
}

.picker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 32rpx;
  border-bottom: 1px solid #ebedf0;
}

.picker-header .title {
  font-size: 32rpx;
  font-weight: 500;
}

.picker-header .close {
  font-size: 40rpx;
  color: #969799;
}

.search-box {
  padding: 0 32rpx;
}

.picker-container {
  height: 400rpx;
}

.picker-view {
  height: 100%;
}

.picker-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 88rpx;
  font-size: 28rpx;
}

.search-results {
  height: 400rpx;
}

.results-scroll {
  height: 100%;
}

.result-item {
  padding: 24rpx 32rpx;
  border-bottom: 1px solid #f5f5f5;
}

.result-item:active {
  background-color: #f2f3f5;
}

.result-text {
  font-size: 28rpx;
}

.empty {
  text-align: center;
  padding: 80rpx;
  color: #969799;
  font-size: 28rpx;
}

.actions {
  display: flex;
  padding: 32rpx;
  gap: 16rpx;
}

.actions van-button {
  flex: 1;
}
</style>

后端接口说明:
三级联动方式:省市区,分为三个接口分别获取,根据上一个地区编码获取下一个区域。

搜索方式:将省市区通过一个接口获取,每个区域都同时显示省市区完整信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容