123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- <template>
- <view class="city-picker-container">
- <view class="picker-content">
- <view class="header">
- <text class="title">城市地区</text>
- </view>
- <view class="hot-cities-section">
- <view class="sub-title">热门城市</view>
- <view class="hot-cities-grid">
- <view
- class="hot-city-item"
- v-for="city in hotCities"
- :key="city"
- @click="handleHotCityClick(city)"
- >
- {{ city }}
- </view>
- </view>
- </view>
- <view class="divider"></view>
- <!-- 选择器 -->
- <view class="picker-wrapper">
- <view class="picker-header">
- <text>省份</text>
- <text>城市</text>
- <text>区县</text>
- </view>
- <picker-view :value="pickerValue" @change="handlePickerChange" class="picker-view">
- <!-- 省份列 -->
- <picker-view-column>
- <view
- class="picker-item"
- :class="{ 'selected-item': pickerValue[0] === index }"
- v-for="(province, index) in provinces"
- :key="province.name"
- >
- {{ province.name }}
- </view>
- </picker-view-column>
- <!-- 城市列 -->
- <picker-view-column>
- <view
- class="picker-item"
- :class="{ 'selected-item': pickerValue[1] === index }"
- v-for="(city, index) in cities"
- :key="city.name"
- >
- {{ city.name }}
- </view>
- </picker-view-column>
- <!-- 区县列 -->
- <picker-view-column>
- <view
- class="picker-item"
- :class="{ 'selected-item': pickerValue[2] === index }"
- v-for="(area, index) in areas"
- :key="area.name"
- >
- {{ area.name }}
- </view>
- </picker-view-column>
- </picker-view>
- </view>
- <!-- 确认按钮 -->
- <view class="footer">
- <button class="confirm-btn" @click="handleConfirm">确定</button>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, watch, computed } from 'vue';
- import { cityData, hotCities } from './city-data.js';
- const emit = defineEmits(['update:show', 'confirm']);
- // --- Data ---
- const provinces = ref(cityData);
- const cities = ref([]);
- const areas = ref([]);
- const pickerValue = ref([0, 0, 0]);
- const selectedProvince = computed(() => provinces.value[pickerValue.value[0]] || {});
- const selectedCity = computed(() => cities.value[pickerValue.value[1]] || {});
- const selectedArea = computed(() => areas.value[pickerValue.value[2]] || {});
- // 确认选择
- const handleConfirm = () => {
- const result = {
- province: selectedProvince.value.name,
- city: selectedCity.value.name,
- area: selectedArea.value.name,
- };
- emit('confirm', result);
- };
- // picker-view 滚动时触发
- const handlePickerChange = (e) => {
- const newPickerValue = e.detail.value;
- const [provinceIndex, cityIndex, areaIndex] = newPickerValue;
- const oldProvinceIndex = pickerValue.value[0];
- const oldCityIndex = pickerValue.value[1];
- // 如果省份改变了
- if (provinceIndex !== oldProvinceIndex) {
- // 更新城市列表和地区列表,并将它们的索引重置为0
- cities.value = provinces.value[provinceIndex].children;
- areas.value = cities.value[0]?.children || [];
- pickerValue.value = [provinceIndex, 0, 0];
- }
- // 如果城市改变了
- else if (cityIndex !== oldCityIndex) {
- // 更新地区列表,并将地区索引重置为0
- areas.value = cities.value[cityIndex]?.children || [];
- pickerValue.value = [provinceIndex, cityIndex, 0];
- }
- // 如果只是地区改变
- else {
- pickerValue.value = newPickerValue;
- }
- };
- // 点击热门城市
- // 新方法:遍历cityData找到对应的省市区并更新pickerValue
- const handleHotCityClick = (cityName) => {
- let provinceIndex = -1;
- let cityIndex = -1;
- let found = false;
- // 遍历省份
- for (let i = 0; i < provinces.value.length; i++) {
- const province = provinces.value[i];
- // 遍历城市
- if (province.children && province.children.length > 0) {
- for (let j = 0; j < province.children.length; j++) {
- const city = province.children[j];
- // 检查城市名是否匹配(使用 startsWith 来兼容 "杭州" 和 "杭州市")
- if (city.name.startsWith(cityName)) {
- provinceIndex = i;
- cityIndex = j;
- found = true;
- break; // 找到城市,跳出内层循环
- }
- }
- }
- if (found) {
- break; // 找到城市,跳出外层循环
- }
- }
- // 如果找到了对应的城市
- if (found) {
- // 1. 更新城市列表,使其为选中省份的城市列表
- cities.value = provinces.value[provinceIndex].children;
-
- // 2. 更新区县列表,使其为选中城市的区县列表
- // 使用可选链 ?. 防止选中城市没有区县数据(如海南省直辖县)
- areas.value = cities.value[cityIndex]?.children || [];
-
- // 3. 更新 pickerValue,这会驱动 picker-view 滚动到指定位置
- // 区县默认选择第一个 (索引为0)
- pickerValue.value = [provinceIndex, cityIndex, 0];
- } else {
- // 如果在数据中找不到该热门城市,可以给一个提示
- uni.showToast({
- title: `未在数据源中找到 ${cityName}`,
- icon: 'none'
- });
- }
- };
- // 初始化数据
- const initialize = () => {
- const [pIndex, cIndex] = pickerValue.value;
- cities.value = provinces.value[pIndex]?.children || [];
- areas.value = cities.value[cIndex]?.children || [];
- };
- // 初始化
- initialize();
- defineExpose({
- initialize
- })
- </script>
- <style lang="scss" scoped>
- .city-picker-container {
- .picker-content {
- width: 100%;
- background-color: #ffffff;
- border-radius: 0 0 24rpx 24rpx;
- display: flex;
- flex-direction: column;
- }
- }
- .header {
- padding: 36rpx 0 0 28rpx;
- position: relative;
- .title {
- font-family: PingFang-SC, PingFang-SC;
- font-weight: bold;
- font-size: 36rpx;
- color: #252525;
- line-height: 36rpx;
- }
- }
- .hot-cities-section {
- padding: 0 28rpx;
- margin-top: 40rpx;
- .sub-title {
- font-family: PingFangSC, PingFang SC;
- font-weight: 400;
- font-size: 24rpx;
- color: #989998;
- line-height: 24rpx;
- }
- .hot-cities-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 20rpx;
- margin: 24rpx 0;
- }
- .hot-city-item {
- border-radius: 6rpx;
- border: 1rpx solid #E5E7EB;
- padding: 13rpx 0;
- font-family: PingFangSC, PingFang SC;
- font-weight: 400;
- font-size: 24rpx;
- color: #252525;
- line-height: 33rpx;
- text-align: center;
- }
- }
- .divider {
- height: 14rpx;
- background: #F7F7F7;
- }
- .picker-wrapper {
- .picker-header {
- display: flex;
- justify-content: space-around;
- padding: 20rpx 0;
- font-family: PingFang-SC, PingFang-SC;
- font-weight: bold;
- font-size: 28rpx;
- color: #252525;
- line-height: 36rpx;
- }
- }
- .picker-view {
- width: 100%;
- height: 380rpx;
- }
- .picker-item {
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 30rpx;
- color: #A4A4A4;
- transition: all 0.2s;
-
- &.selected-item {
- font-size: 32rpx;
- color: #252525;
- }
- }
- .footer {
- padding: 64rpx 147rpx 40rpx;
- .confirm-btn {
- width: 100%;
- height: 80rpx;
- line-height: 88rpx;
- background: #B7F358;
- border-radius: 45rpx;
- font-family: PingFang-SC, PingFang-SC;
- font-weight: bold;
- font-size: 28rpx;
- color: #252525;
- line-height: 80rpx;
- letter-spacing: 2rpx;
- border: none;
- &:after {
- border: none;
- }
- }
- }
- </style>
|