省市区镇村五级联动

方法

bash 复制代码
<template>
  <uni-popup ref="popupRef" type="bottom" @change="popChange" :safe-area="false">
    <view class="zq-address-picker">
      <view class="zq-address-picker__close">
        <uni-icons class="absolute" :size="20" type="closeempty" @click="closePopup" />
      </view>
      <view class="zq-address-picker__title">请选择所在地区</view>

      <!-- 已选择区域 -->
      <uni-list v-for="(item, index) in activeItems" :key="item.code" :border="false">
        <uni-list-item show-arrow clickable @click="selectLevel(item)">
          <template #header>
            <view style="display: flex; align-items: center">
              <view
                style="
                  width: 11px;
                  height: 11px;
                  background-color: #fe4246;
                  border-radius: 50%;
                  margin-right: 7px;
                  position: relative;
                "
              >
                <view
                  :style="{
                    width: '1px',
                    height: '50px',
                    'background-color': '#fe4246',
                    position: 'absolute',
                    left: '5px',
                    top: index < 1 ? 0 : index < activeItems.length - 1 ? '-20px' : '-50px'
                  }"
                >
                </view>
              </view>
              <view :class="activeLevel === item.level - 1 || !item.name ? 'zq-address-picker__select--active' : ''">
                {{ item.name || selectTitle }}
              </view>
            </view>
          </template>
        </uni-list-item>
      </uni-list>

      <view class="zq-address-picker__list">
        <view class="zq-address-picker__list-title">{{ selectTitle }}</view>
        <view class="zq-address-picker__list-content" @click="reGetList">
          <uni-load-more
            v-if="loadingStatus !== 'more'"
            :status="loadingStatus"
            :content-text="{ contentnomore: '加载失败,请点击重试' }"
          />

          <!-- 省市区选择 -->
          <uni-list :border="false">
            <uni-list-item
              v-for="(item, index) in currentList"
              :key="item.code"
              :title="item.name"
              :border="false"
              clickable
              @click="selectHandle(item, index)"
            >
              <template #footer>
                <uni-icons
                  v-if="activeItems[item.level] ? activeItems[item.level].code === item.code : false"
                  type="checkmarkempty"
                  class="zq-address-picker__list-tab--active"
                />
              </template>
            </uni-list-item>
          </uni-list>
        </view>
      </view>
    </view>
  </uni-popup>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue'
import UserApi from '@/api/userApi'
import { children } from '@/uni_modules/lime-painter/components/common/relation'
// Props 定义
const props = defineProps({
  value: Boolean,
  selected: {
    type: Array,
    default: () => []
  }
})

// Emits 定义
const emit = defineEmits(['input', 'confirm'])

// 数据定义
const loading = ref(true)
const loadingStatus = ref('loading')
const country = ref([])
const list = ref([])
const activeLevel = ref(-1)
const activeIndex = ref(-1)
const activeItems = ref([])

// 模板 ref
const popupRef = ref(null)
let isClosing = false
// 关闭弹窗
const closePopup = () => {
  if (isClosing) return
  isClosing = true
  popupRef.value?.close()
  emit('confirm', activeItems.value)
  nextTick(() => {
    isClosing = false
  })
}

// 弹窗状态变化
const popChange = (e) => {
  const status = e.show
  if (activeLevel.value !== 4 && !status) {
    activeLevel.value = -1
    activeIndex.value = -1
    activeItems.value = []
  }
  emit('input', status)
}

// 已选择项切换
const selectLevel = (item) => {
  if (item.code) {
    activeLevel.value = item.level - 1
  }
}

// 选择处理
const selectHandle = (item, index) => {
  // 判断点击的级别 0:省 1:市 2:区 3:镇
  // UserApi.getdistrictcity({ code: item.code })
  // 选择省市区
  activeLevel.value = item.level
  activeIndex.value = index
  // // 直接通过索引赋值(Vue3 响应式支持)
  activeItems.value[item.level] = item
  activeItems.value.splice(item.level + 1)

  if (item.level == 0) {
    // 获取市
    UserApi.getdistrictcity({ provinceCode: item.code }).then((res) => {
      eachList(res.data, 1, 'cityCode', 'cityName', { provinceCode: item.code })
      item.children = res.data
      activeItems.value[item.level + 1] = {}
    })
  } else if (item.level == 1) {
    // 获取区
    UserApi.getdistrictcountry({ cityCode: item.code, provinceCode: item.provinceCode }).then((res) => {
      eachList(res.data, 2, 'countryCode', 'countryName', { cityCode: item.code, provinceCode: item.provinceCode })
      item.children = res.data
      activeItems.value[item.level + 1] = {}
    })
  } else if (item.level == 2) {
    // 获取镇
    UserApi.getdistricttown({
      countryCode: item.code,
      cityCode: item.cityCode,
      provinceCode: item.provinceCode
    }).then((res) => {
      eachList(res.data, 3, 'townCode', 'townName', {
        countryCode: item.code,
        cityCode: item.cityCode,
        provinceCode: item.provinceCode
      })
      item.children = res.data
      activeItems.value[item.level + 1] = {}
    })
  } else if (item.level == 3) {
    // 获取村
    UserApi.getdistrictvillage({
      townCode: item.code,
      countryCode: item.countryCode,
      cityCode: item.cityCode,
      provinceCode: item.provinceCode
    }).then((res) => {
      eachList(res.data, 4, 'villageCode', 'villageName', {
        townCode: item.code,
        countryCode: item.countryCode,
        cityCode: item.cityCode,
        provinceCode: item.provinceCode
      })
      item.children = res.data
      activeItems.value[item.level + 1] = {}
    })
  } else if (item.level == 4) {
    emit('confirm', activeItems.value)
  }
}

const eachList = (arr, level, code, name, obj) => {
  arr.forEach((v) => {
    v.level = level
    v.code = v[code]
    v.name = v[name]
    Object.assign(v, obj)
    if (v.children) {
      eachList(v.children, v.level + 1)
    }
  })
}

// 获取中国省市区数据
const getCityData = () => {
  if (!list.value.length) {
    loadingStatus.value = 'loading'
    UserApi.getdistrictprovince().then((res) => {
      if (Array.isArray(res.data)) {
        eachList(res.data, 0, 'provinceCode', 'provinceName')
        list.value = res.data
        if (props.selected.length) {
          console.log(7777)

          const active = []
          if (props.selected.length !== 1) {
            active[0] = list.value.find((v) => v.name === props.selected[1])
            active[1] = active[0]?.children.find((v) => v.name === props.selected[2])
            active[2] = active[1]?.children.find((v) => v.name === props.selected[3])
            active[3] = active[2]?.children.find((v) => v.name === props.selected[4])
            activeItems.value = active
            activeLevel.value = 4
          } else {
            activeLevel.value = -1
          }
        }

        loadingStatus.value = 'more'
      } else {
        loadingStatus.value = 'noMore'
      }
    })
  } else {
    loadingStatus.value = 'more'
  }
}

// 重新获取列表
const reGetList = () => {
  if (loadingStatus.value === 'noMore') {
    getList()
  }
}

const getList = () => {
  getCityData()
}

// 计算属性
const currentList = computed(() => {
  if (activeLevel.value !== -1) {
    return activeItems.value[activeLevel.value]?.children || []
  }
  return list.value
})

const selectTitle = computed(() => {
  const titles = {
    '-1': '请选择省份/地区',
    0: '请选择城市',
    1: '请选择区/县',
    2: '请选择街道/镇',
    3: '请选择社区/村庄'
  }
  return titles[activeLevel.value]
})

const showPick = () => {
  activeItems.value = []
  popupRef.value?.open()
  activeItems.value = props.selected || []
  getList()
}
const closePick = () => {
  closePopup()
}
defineExpose({
  showPick,
  closePick
})
</script>
<style lang="scss" scoped>
/* 样式保持不变 */

.zq-address-picker {
  border-top-left-radius: 20px;
  border-top-right-radius: 20px;
  background-color: white;
  padding-left: 8px;
  padding-right: 8px;
  padding-top: 16px;
  font-size: 16px;
  &__close {
    text-align: right;
    margin-right: 42px;
    margin-top: 5px;
    position: absolute;
    left: 0;
    right: 0;
    color: red;
  }

  &__title {
    font-weight: 500;
    font-size: 20px;
    color: #313440;
    line-height: 28px;
    text-align: center;
    font-style: normal;
  }

  &__select {
    &--active {
      color: $uni-color-primary;
    }
  }

  &__list {
    max-height: 48vh;
    overflow-y: auto;
    margin-top: 12px;

    &-tab {
      display: flex;
      margin-bottom: 20px;
      justify-content: space-between;
      padding-left: 16px;
      padding-right: 16px;

      &--hide {
        display: none;
      }

      &--active {
        color: $uni-color-primary !important;
      }
    }

    &-title {
      font-weight: bold;
      margin-bottom: 16px;
    }

    .uni-load-more {
      padding: 20px 0;
    }
  }

  &__quick {
    display: flex;
    flex-wrap: wrap;
    font-size: 14px;
    gap: 12px;
    margin-bottom: 14px;

    &-label {
      padding: 5px 12px;
      display: block;
      border-radius: 99px;
      background-color: #f4f3f3;
    }

    &-radio {
      display: none;
    }

    &--active {
      background-color: transparentize($uni-color-primary, 0.94);
      color: $uni-color-primary;
    }
  }
}
</style>

使用

bash 复制代码
    <pick @confirm="confirm" :selected="selectCity" ref="pickRef"></pick>

预览

相关推荐
weixin_4434785122 分钟前
Flutter第三方常用组件包之路由管理
前端·javascript·flutter
武藤一雄25 分钟前
C# 异步回调与等待机制
前端·microsoft·设计模式·微软·c#·.netcore
啥都不懂的小小白32 分钟前
前端CSS入门详解
前端·css
林恒smileZAZ1 小时前
前端大屏适配方案:rem、vw/vh、scale 到底选哪个?
开发语言·前端·css·css3
QQ5110082851 小时前
基于区块链的个人医疗咨询挂号信息系统vue
前端·vue.js·区块链
程序员小寒3 小时前
JavaScript设计模式(八):命令模式实现与应用
前端·javascript·设计模式·ecmascript·命令模式
wgod3 小时前
new AbortController()
前端
UXbot3 小时前
UXbot 是什么?一句指令生成完整应用的 AI 工具
前端·ai·交互·个人开发·ai编程·原型模式·ux
棒棒的唐3 小时前
WSL2用npm安装的openclaw,无法正常使用openclaw gateway start启动服务的问题
前端·npm·gateway
哔哩哔哩技术4 小时前
使用Compose Navigation3进行屏幕适配
前端