425 lines
11 KiB
Vue
425 lines
11 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="city-select-container">
|
|||
|
|
<!-- 顶部导航栏 -->
|
|||
|
|
<!-- <view class="nav-bar">
|
|||
|
|
<view class="nav-left" @click="navBack">
|
|||
|
|
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
|||
|
|
</view>
|
|||
|
|
<view class="nav-title">选择城市</view>
|
|||
|
|
<view class="nav-right"></view>
|
|||
|
|
</view> -->
|
|||
|
|
|
|||
|
|
<!-- 搜索框 -->
|
|||
|
|
<view class="search-box">
|
|||
|
|
<uni-icons class="search-icon" type="search" size="20" color="#999"></uni-icons>
|
|||
|
|
<input
|
|||
|
|
class="search-input"
|
|||
|
|
placeholder="搜索城市名/拼音"
|
|||
|
|
placeholder-style="color: #999"
|
|||
|
|
v-model="searchText"
|
|||
|
|
@input="handleSearch"
|
|||
|
|
>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 定位城市 -->
|
|||
|
|
<!-- <view class="location-section">
|
|||
|
|
<view class="section-title">定位城市</view>
|
|||
|
|
<view class="location-city" :class="{ active: selectedCity.code === locationCity.code }" @click="selectCity(locationCity)">
|
|||
|
|
{{ locationCity.name }}
|
|||
|
|
</view>
|
|||
|
|
</view> -->
|
|||
|
|
|
|||
|
|
<!-- 热门城市 -->
|
|||
|
|
<!-- <view class="hot-section">
|
|||
|
|
<view class="section-title">热门城市</view>
|
|||
|
|
<view class="hot-cities">
|
|||
|
|
<view
|
|||
|
|
class="city-item"
|
|||
|
|
:class="{ active: selectedCity.code === item.code }"
|
|||
|
|
v-for="item in hotCities"
|
|||
|
|
:key="item.code"
|
|||
|
|
@click="selectCity(item)"
|
|||
|
|
>
|
|||
|
|
{{ item.name }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view> -->
|
|||
|
|
|
|||
|
|
<!-- 城市数量统计 -->
|
|||
|
|
<!-- <view class="city-count" v-if="allCities.length > 0">
|
|||
|
|
共找到 {{ allCities.length }} 个城市
|
|||
|
|
</view> -->
|
|||
|
|
|
|||
|
|
<!-- 城市列表 -->
|
|||
|
|
<view class="city-list-section">
|
|||
|
|
<scroll-view
|
|||
|
|
class="city-list-content"
|
|||
|
|
scroll-y
|
|||
|
|
:scroll-into-view="currentScrollId"
|
|||
|
|
:scroll-with-animation="true"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
class="city-group"
|
|||
|
|
v-for="group in cityGroups"
|
|||
|
|
:key="group.letter"
|
|||
|
|
:id="`city-group-${group.letter}`"
|
|||
|
|
>
|
|||
|
|
<view class="group-title">{{ group.letter }}</view>
|
|||
|
|
<view
|
|||
|
|
class="city-item"
|
|||
|
|
:class="{ active: selectedCity.code === item.code }"
|
|||
|
|
v-for="item in group.cities"
|
|||
|
|
:key="item.code"
|
|||
|
|
@click="selectCity(item)"
|
|||
|
|
>
|
|||
|
|
{{ item.name }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
|
|||
|
|
<!-- 右侧字母索引 -->
|
|||
|
|
<view class="letter-index">
|
|||
|
|
<view
|
|||
|
|
class="letter-item"
|
|||
|
|
v-for="letter in letters"
|
|||
|
|
:key="letter"
|
|||
|
|
@click="scrollToLetter(letter)"
|
|||
|
|
:class="{ active: currentLetter === letter }"
|
|||
|
|
>
|
|||
|
|
{{ letter }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, reactive, onMounted, computed, inject } from 'vue';
|
|||
|
|
const { $api, navTo } = inject('globalFunction');
|
|||
|
|
|
|||
|
|
// 搜索文本
|
|||
|
|
const searchText = ref('');
|
|||
|
|
// 定位城市
|
|||
|
|
const locationCity = ref({ code: '110100', name: '北京' });
|
|||
|
|
// 选中的城市
|
|||
|
|
const selectedCity = ref({ code: '', name: '' });
|
|||
|
|
// 当前显示的字母
|
|||
|
|
const currentLetter = ref('');
|
|||
|
|
// 当前滚动到的城市组ID
|
|||
|
|
const currentScrollId = ref('');
|
|||
|
|
// 城市数据
|
|||
|
|
const allCities = ref([]);
|
|||
|
|
// 字母列表
|
|||
|
|
const letters = ref(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']);
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 按字母分组的城市
|
|||
|
|
const cityGroups = computed(() => {
|
|||
|
|
const groups = {};
|
|||
|
|
const filteredCities = allCities.value.filter(city => {
|
|||
|
|
if (!searchText.value) return true;
|
|||
|
|
return city.name.includes(searchText.value) || city.pinyin?.includes(searchText.value.toUpperCase());
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('过滤后用于分组的城市:', filteredCities);
|
|||
|
|
|
|||
|
|
// 初始化字母分组
|
|||
|
|
letters.value.forEach(letter => {
|
|||
|
|
groups[letter] = { letter, cities: [] };
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 将城市分配到对应字母组
|
|||
|
|
filteredCities.forEach(city => {
|
|||
|
|
const firstLetter = city.pinyin || '#';
|
|||
|
|
// 如果字母不在预定义列表中,将其添加到分组中
|
|||
|
|
if (!groups[firstLetter]) {
|
|||
|
|
groups[firstLetter] = { letter: firstLetter, cities: [] };
|
|||
|
|
}
|
|||
|
|
groups[firstLetter].cities.push(city);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const result = Object.values(groups).filter(group => group.cities.length > 0);
|
|||
|
|
|
|||
|
|
// 对城市组进行排序
|
|||
|
|
result.sort((a, b) => {
|
|||
|
|
// '#' 符号应该排在最前面
|
|||
|
|
if (a.letter === '#') return -1;
|
|||
|
|
if (b.letter === '#') return 1;
|
|||
|
|
// 其他字母按字母顺序排序
|
|||
|
|
return a.letter.localeCompare(b.letter);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('最终分组结果:', result);
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取城市数据
|
|||
|
|
const getCityData = async () => {
|
|||
|
|
try {
|
|||
|
|
// 直接获取所有城市数据,新接口已经返回了所有层级的数据
|
|||
|
|
const res = await $api.createRequest('/cms/dict/sysarea/listCity', {});
|
|||
|
|
console.log('城市数据接口返回:', res);
|
|||
|
|
if (res.code === 200 && res.data) {
|
|||
|
|
console.log('原始城市数据:', res.data);
|
|||
|
|
// 显示接口返回的所有城市数据
|
|||
|
|
const filteredCities = res.data;
|
|||
|
|
console.log('过滤后城市数据:', filteredCities);
|
|||
|
|
|
|||
|
|
// 直接使用后端返回的zm字段作为拼音首字母,并转换为大写
|
|||
|
|
allCities.value = filteredCities.map(city => ({
|
|||
|
|
...city,
|
|||
|
|
pinyin: (city.zm || '#').toUpperCase()
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
console.log('使用后端zm字段的城市数据:', allCities.value);
|
|||
|
|
|
|||
|
|
// 按拼音首字母排序
|
|||
|
|
allCities.value.sort((a, b) => {
|
|||
|
|
// 首先按拼音首字母排序
|
|||
|
|
if (a.pinyin !== b.pinyin) {
|
|||
|
|
// '#' 符号应该排在最前面
|
|||
|
|
if (a.pinyin === '#') return -1;
|
|||
|
|
if (b.pinyin === '#') return 1;
|
|||
|
|
return a.pinyin.localeCompare(b.pinyin);
|
|||
|
|
}
|
|||
|
|
// 首字母相同时,按城市名称排序
|
|||
|
|
return a.name.localeCompare(b.name);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取城市数据失败:', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取拼音首字母(使用更准确的映射表)
|
|||
|
|
const getPinyinFirstLetter = (name) => {
|
|||
|
|
const firstChar = name.charAt(0);
|
|||
|
|
|
|||
|
|
// 查找对应的拼音首字母
|
|||
|
|
for (const [letter, chars] of Object.entries(pinyinMap)) {
|
|||
|
|
if (chars.includes(firstChar)) {
|
|||
|
|
return letter;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有找到,返回#
|
|||
|
|
return '#';
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 处理搜索
|
|||
|
|
const handleSearch = () => {
|
|||
|
|
// 搜索逻辑已在cityGroups计算属性中处理
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 选择城市
|
|||
|
|
const selectCity = (city) => {
|
|||
|
|
selectedCity.value = city;
|
|||
|
|
// 返回上一页并传递选择的城市
|
|||
|
|
uni.navigateBack({
|
|||
|
|
delta: 1,
|
|||
|
|
success: () => {
|
|||
|
|
// 发送事件通知首页选择了城市
|
|||
|
|
uni.$emit('citySelected', city);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 滚动到指定字母
|
|||
|
|
const scrollToLetter = (letter) => {
|
|||
|
|
currentLetter.value = letter;
|
|||
|
|
// 更新滚动ID,触发scroll-view滚动
|
|||
|
|
currentScrollId.value = `city-group-${letter}`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 返回上一页
|
|||
|
|
const navBack = () => {
|
|||
|
|
uni.navigateBack({ delta: 1 });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 组件挂载时获取城市数据
|
|||
|
|
onMounted(() => {
|
|||
|
|
getCityData();
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.city-select-container {
|
|||
|
|
background-color: #f5f5f5;
|
|||
|
|
min-height: 100vh;
|
|||
|
|
padding-top: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 导航栏 */
|
|||
|
|
.nav-bar {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
height: 44px;
|
|||
|
|
padding: 0 16px;
|
|||
|
|
background-color: #fff;
|
|||
|
|
border-bottom: 1px solid #e5e5e5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.nav-left,
|
|||
|
|
.nav-right {
|
|||
|
|
width: 40px;
|
|||
|
|
height: 40px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.nav-title {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 搜索框 */
|
|||
|
|
.search-box {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
margin: 12px 16px;
|
|||
|
|
padding:8px 16px;
|
|||
|
|
background-color: #fff;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.search-icon {
|
|||
|
|
margin-right: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.search-input {
|
|||
|
|
flex: 1;
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #333;
|
|||
|
|
border: none;
|
|||
|
|
outline: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 定位城市 */
|
|||
|
|
.location-section {
|
|||
|
|
padding: 16px;
|
|||
|
|
background-color: #fff;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #666;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.location-city {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 8px 20px;
|
|||
|
|
background-color: #f0f9ff;
|
|||
|
|
color: #007aff;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.location-city.active {
|
|||
|
|
background-color: #007aff;
|
|||
|
|
color: #fff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 热门城市 */
|
|||
|
|
.hot-section {
|
|||
|
|
padding: 16px;
|
|||
|
|
background-color: #fff;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.city-count {
|
|||
|
|
padding: 0 16px 12px;
|
|||
|
|
background-color: #fff;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #999;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.hot-cities {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.city-item {
|
|||
|
|
padding: 8px 20px;
|
|||
|
|
background-color: #f5f5f5;
|
|||
|
|
color: #333;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.city-item.active {
|
|||
|
|
background-color: #007aff;
|
|||
|
|
color: #fff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 城市列表 */
|
|||
|
|
.city-list-section {
|
|||
|
|
display: flex;
|
|||
|
|
background-color: #fff;
|
|||
|
|
height: calc(100vh - 180px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.city-list-content {
|
|||
|
|
flex: 1;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.city-group {
|
|||
|
|
padding: 0 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.group-title {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #666;
|
|||
|
|
margin: 12px 0 8px 0;
|
|||
|
|
padding-left: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.city-list-content .city-item {
|
|||
|
|
display: block;
|
|||
|
|
padding: 12px 4px;
|
|||
|
|
margin-bottom: 0;
|
|||
|
|
background-color: transparent;
|
|||
|
|
border-radius: 0;
|
|||
|
|
border-bottom: 1px solid #f0f0f0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.city-list-content .city-item.active {
|
|||
|
|
background-color: transparent;
|
|||
|
|
color: #007aff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 右侧字母索引 */
|
|||
|
|
.letter-index {
|
|||
|
|
width: 30px;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
background-color: #f5f5f5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.letter-item {
|
|||
|
|
width: 24px;
|
|||
|
|
height: 20px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #666;
|
|||
|
|
margin: 2px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.letter-item.active {
|
|||
|
|
color: #007aff;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
</style>
|