소스 검색

城市选择优化

shish 10 달 전
부모
커밋
2bbec77654
1개의 변경된 파일533개의 추가작업 그리고 42개의 파일을 삭제
  1. 533 42
      ghsApp/src/components/plugin/simple-address.vue

+ 533 - 42
ghsApp/src/components/plugin/simple-address.vue

@@ -7,48 +7,135 @@
 		 @tap="hideMask(true)"></view>
 
 		<view class="simple-address-content simple-address--fixed" :class="[type,ani+'-content', animation ? 'content-ani' : '']">
+			<!-- 标题栏 -->
 			<view class="simple-address__header">
 				<view class="simple-address__header-btn-box" @click="pickerCancel">
 					<text class="simple-address__header-text">取消</text>
 				</view>
-				<view class="simple-address__header-btn-box" @click="pickerConfirm">
-					<text class="simple-address__header-text confirm-text">确定</text>
+				<view class="simple-address__header-title">
+					<text class="simple-address__title-text">选择城市</text>
+				</view>
+				<view class="simple-address__header-btn-box">
+					<!-- 保留空位保持布局平衡 -->
+				</view>
+			</view>
+			
+			<!-- 搜索框 -->
+			<view class="search-container">
+				<view class="search-box">
+					<text class="search-icon">🔍</text>
+					<input 
+						class="search-input" 
+						type="text" 
+						placeholder="搜索城市名称或拼音,点击即可选择"
+						v-model="searchText"
+						@input="onSearchInput"
+						@focus="searchFocused = true"
+						@blur="searchFocused = false"
+					/>
+					<view v-if="searchText" class="clear-btn" @click="clearSearch">
+						<text class="clear-icon">×</text>
+					</view>
 				</view>
 			</view>
+
 			<view class="simple-address__box">
-				<picker-view indicator-style="height: 70upx;" class="simple-address-view" :value="pickerValue" @change="pickerChange">
-
-					<picker-view-column>
-						<!-- #ifndef APP-NVUE -->
-						<view class="picker-item" v-for="(item,index) in region" :key="index">{{item.name}}</view>
-						<!-- #endif -->
-						<!-- #ifdef APP-NVUE -->
-						<!-- <text class="picker-item" v-for="(item,index) in provinceDataList" :key="index">{{item.name}}</text> -->
-						<!-- #endif -->
-					</picker-view-column>
-					<picker-view-column>
-						<!-- #ifndef APP-NVUE -->
-						<view class="picker-item" v-for="(item,index) in region[pickerValue[0]].children" :key="index">{{item.name}}</view>
-						<!-- #endif -->
-						<!-- #ifdef APP-NVUE -->
-						<!-- <text class="picker-item" v-for="(item,index) in cityDataList" :key="index">{{item.name}}</text> -->
-						<!-- #endif -->
-
-					</picker-view-column>
-					<!-- <picker-view-column> -->
-						<!-- #ifndef APP-NVUE -->
-						<!-- <view class="picker-item" v-for="(item,index) in areaDataList" :key="index">{{item.name}}</view> -->
-						<!-- #endif -->
-						<!-- #ifdef APP-NVUE -->
-						<!-- <text class="picker-item" v-for="(item,index) in areaDataList" :key="index">{{item.name}}</text> -->
-						<!-- #endif -->
-
-					<!-- </picker-view-column> -->
-
-				</picker-view>
+				<!-- 搜索结果 -->
+				<view v-if="searchText && searchResults.length > 0" class="search-results">
+					<scroll-view scroll-y class="search-scroll">
+						<view 
+							v-for="(item, index) in searchResults" 
+							:key="index"
+							class="search-result-item"
+							@click="selectSearchResult(item)"
+						>
+							<text class="result-province">{{ item.provinceName }}</text>
+							<text class="result-city">{{ item.cityName }}</text>
+						</view>
+					</scroll-view>
+				</view>
+				
+				<!-- 无搜索结果 -->
+				<view v-else-if="searchText && searchResults.length === 0" class="no-results">
+					<text class="no-results-text">未找到相关城市</text>
+				</view>
+				
+				<!-- 正常选择模式 -->
+				<view v-else class="normal-mode">
+					<!-- 最近选择 -->
+					<view v-if="recentCities.length > 0" class="recent-section">
+						<view class="section-title">
+							<text class="section-title-text">最近选择</text>
+						</view>
+						<view class="recent-cities">
+							<view 
+								v-for="(city, index) in recentCities" 
+								:key="index"
+								class="recent-city-item"
+								@click="selectRecentCity(city)"
+							>
+								<text class="recent-city-text">{{ city.label }}</text>
+							</view>
+						</view>
+					</view>
+					
+					<!-- 热门城市 -->
+					<view class="hot-section">
+						<view class="section-title">
+							<text class="section-title-text">热门城市</text>
+						</view>
+						<view class="hot-cities">
+							<view 
+								v-for="(city, index) in hotCities" 
+								:key="index"
+								class="hot-city-item"
+								:class="{ 'selected': isSelected(city) }"
+								@click="selectHotCity(city)"
+							>
+								<text class="hot-city-text">{{ city.cityName }}</text>
+							</view>
+						</view>
+					</view>
+					
+					<!-- 省份城市列表 -->
+					<view class="province-section">
+						<view class="section-title">
+							<text class="section-title-text">所有城市</text>
+						</view>
+						<scroll-view scroll-y class="province-scroll" :scroll-into-view="scrollIntoView">
+							<view v-for="(province, pIndex) in region" :key="pIndex" class="province-group" :id="'province-' + pIndex">
+								<view class="province-header" @click="toggleProvince(pIndex)">
+									<text class="province-name">{{ province.name }}</text>
+									<text class="province-arrow" :class="{ 'expanded': expandedProvinces.includes(pIndex) }">▼</text>
+								</view>
+								<view v-if="expandedProvinces.includes(pIndex)" class="cities-container">
+									<view 
+										v-for="(city, cIndex) in province.children" 
+										:key="cIndex"
+										class="city-item"
+										:class="{ 'selected': isSelected({
+											provinceCode: province.id,
+											cityCode: city.id,
+											provinceName: province.name,
+											cityName: city.name
+										}) }"
+										@click="selectCity(pIndex, cIndex)"
+									>
+										<text class="city-name">{{ city.name }}</text>
+										<text v-if="isSelected({
+											provinceCode: province.id,
+											cityCode: city.id,
+											provinceName: province.name,
+											cityName: city.name
+										})" class="selected-icon">✓</text>
+									</view>
+								</view>
+							</view>
+						</scroll-view>
+					</view>
+				</view>
 			</view>
 		</view>
-
 	</view>
 </template>
 <script>
@@ -104,7 +191,25 @@ export default {
 			provinceDataList: [],
 			cityDataList: [],
 			areaDataList: [],
-			region: []
+			region: [],
+			// 新增数据
+			searchText: '',
+			searchResults: [],
+			searchFocused: false,
+			selectedCity: null,
+			expandedProvinces: [],
+			scrollIntoView: '',
+			recentCities: [],
+			hotCities: [
+				{ provinceCode: '110000', cityCode: '110100', provinceName: '北京市', cityName: '北京市', label: '北京市' },
+				{ provinceCode: '310000', cityCode: '310100', provinceName: '上海市', cityName: '上海市', label: '上海市' },
+				{ provinceCode: '440000', cityCode: '440100', provinceName: '广东省', cityName: '广州市', label: '广东省 广州市' },
+				{ provinceCode: '440000', cityCode: '440300', provinceName: '广东省', cityName: '深圳市', label: '广东省 深圳市' },
+				{ provinceCode: '330000', cityCode: '330100', provinceName: '浙江省', cityName: '杭州市', label: '浙江省 杭州市' },
+				{ provinceCode: '320000', cityCode: '320100', provinceName: '江苏省', cityName: '南京市', label: '江苏省 南京市' },
+				{ provinceCode: '510000', cityCode: '510100', provinceName: '四川省', cityName: '成都市', label: '四川省 成都市' },
+				{ provinceCode: '420000', cityCode: '420100', provinceName: '湖北省', cityName: '武汉市', label: '湖北省 武汉市' }
+			]
 		}
 	},
 	watch: {
@@ -121,6 +226,7 @@ export default {
 	},
 	created() {
 		this.sInit()
+		this.loadRecentCities()
 	},
 	methods: {
 		_regionTree() {
@@ -134,6 +240,142 @@ export default {
 		// 	this.provinceDataList = JSON.parse(JSON.stringify(this.region))
 		// 	this.cityDataList = []
 		},
+		// 加载最近选择的城市
+		loadRecentCities() {
+			try {
+				const recentData = uni.getStorageSync('recent_cities')
+				if (recentData) {
+					this.recentCities = JSON.parse(recentData)
+				}
+			} catch (e) {
+				console.log('加载最近城市失败:', e)
+			}
+		},
+		// 保存最近选择的城市
+		saveRecentCity(city) {
+			try {
+				// 移除重复项
+				this.recentCities = this.recentCities.filter(item => 
+					!(item.provinceCode === city.provinceCode && item.cityCode === city.cityCode)
+				)
+				// 添加到开头
+				this.recentCities.unshift(city)
+				// 最多保存8个
+				if (this.recentCities.length > 8) {
+					this.recentCities = this.recentCities.slice(0, 8)
+				}
+				uni.setStorageSync('recent_cities', JSON.stringify(this.recentCities))
+			} catch (e) {
+				console.log('保存最近城市失败:', e)
+			}
+		},
+		// 搜索输入处理
+		onSearchInput(e) {
+			this.searchText = e.detail.value
+			this.doSearch()
+		},
+		// 执行搜索
+		doSearch() {
+			if (!this.searchText) {
+				this.searchResults = []
+				return
+			}
+			
+			const searchTerm = this.searchText.toLowerCase()
+			this.searchResults = []
+			
+			this.region.forEach(province => {
+				province.children && province.children.forEach(city => {
+					// 搜索城市名称和省份名称
+					if (city.name.toLowerCase().includes(searchTerm) || 
+						province.name.toLowerCase().includes(searchTerm) ||
+						this.getPinyin(city.name).includes(searchTerm) ||
+						this.getPinyin(province.name).includes(searchTerm)) {
+						this.searchResults.push({
+							provinceCode: province.id,
+							cityCode: city.id,
+							provinceName: province.name,
+							cityName: city.name,
+							label: `${province.name} ${city.name}`
+						})
+					}
+				})
+			})
+		},
+		// 简单的拼音支持
+		getPinyin(text) {
+			// 这里可以引入更完整的拼音库,暂时简单处理
+			const pinyinMap = {
+				'北京': 'beijing', '上海': 'shanghai', '广州': 'guangzhou', 
+				'深圳': 'shenzhen', '杭州': 'hangzhou', '南京': 'nanjing',
+				'成都': 'chengdu', '武汉': 'wuhan', '西安': 'xian',
+				'重庆': 'chongqing', '天津': 'tianjin', '苏州': 'suzhou'
+			}
+			return pinyinMap[text] || text.toLowerCase()
+		},
+		// 清空搜索
+		clearSearch() {
+			this.searchText = ''
+			this.searchResults = []
+		},
+		// 选择搜索结果
+		selectSearchResult(city) {
+			this.selectedCity = city
+			this.saveRecentCity(city)
+			this.clearSearch()
+			// 自动确认并关闭
+			this._$emit('onConfirm')
+			this.close()
+		},
+		// 选择最近城市
+		selectRecentCity(city) {
+			this.selectedCity = city
+			this.saveRecentCity(city)
+			// 自动确认并关闭
+			this._$emit('onConfirm')
+			this.close()
+		},
+		// 选择热门城市
+		selectHotCity(city) {
+			this.selectedCity = city
+			this.saveRecentCity(city)
+			// 自动确认并关闭
+			this._$emit('onConfirm')
+			this.close()
+		},
+		// 切换省份展开状态
+		toggleProvince(index) {
+			const expandIndex = this.expandedProvinces.indexOf(index)
+			if (expandIndex > -1) {
+				this.expandedProvinces.splice(expandIndex, 1)
+			} else {
+				this.expandedProvinces.push(index)
+			}
+		},
+		// 选择城市
+		selectCity(provinceIndex, cityIndex) {
+			const province = this.region[provinceIndex]
+			const city = province.children[cityIndex]
+			const cityData = {
+				provinceCode: province.id,
+				cityCode: city.id,
+				provinceName: province.name,
+				cityName: city.name,
+				label: `${province.name} ${city.name}`
+			}
+			this.selectedCity = cityData
+			this.saveRecentCity(cityData)
+			this.pickerValue = [provinceIndex, cityIndex]
+			// 自动确认并关闭
+			this._$emit('onConfirm')
+			this.close()
+		},
+		// 判断是否选中
+		isSelected(city) {
+			if (!this.selectedCity) return false
+			return this.selectedCity.provinceCode === city.provinceCode && 
+				   this.selectedCity.cityCode === city.cityCode
+		},
 		handPickValueDefault() {
 			// if (this.pickerValueDefault !== [0, 0]) {
 			// 	if (this.pickerValueDefault[0] > provinceData.length - 1) {
@@ -152,14 +394,28 @@ export default {
 			this._$emit('onChange')
 		},
 		_$emit(emitName) {
-			let pickObj = {
-				label: this._getLabel(),
-				value: this.pickerValue,
-				provinceName: this.region[this.pickerValue[0]].name,
-				cityName: this.region[this.pickerValue[0]].children[this.pickerValue[1]].name,
-				provinceCode: this._getProvinceCode(),
-				cityCode: this._getCityCode()
-				// areaCode: this._getAreaCode(),
+			let pickObj = {}
+			
+			if (this.selectedCity) {
+				// 使用新的选择数据
+				pickObj = {
+					label: this.selectedCity.label,
+					value: this.pickerValue,
+					provinceName: this.selectedCity.provinceName,
+					cityName: this.selectedCity.cityName,
+					provinceCode: this.selectedCity.provinceCode,
+					cityCode: this.selectedCity.cityCode
+				}
+			} else if (this.region.length > 0 && this.region[this.pickerValue[0]] && this.region[this.pickerValue[0]].children[this.pickerValue[1]]) {
+				// 兼容原来的picker选择方式
+				pickObj = {
+					label: this._getLabel(),
+					value: this.pickerValue,
+					provinceName: this.region[this.pickerValue[0]].name,
+					cityName: this.region[this.pickerValue[0]].children[this.pickerValue[1]].name,
+					provinceCode: this._getProvinceCode(),
+					cityCode: this._getCityCode()
+				}
 			}
 			console.log(pickObj)
 			this.$emit(emitName, pickObj)
@@ -295,9 +551,22 @@ export default {
 		flex-direction: row;
 		flex-wrap: nowrap;
 		justify-content: space-between;
+		align-items: center;
 		border-bottom-color: #f2f2f2;
 		border-bottom-style: solid;
 		border-bottom-width: 1upx;
+		padding: 0 20upx;
+	}
+	
+	.simple-address__header-title {
+		flex: 1;
+		text-align: center;
+	}
+	
+	.simple-address__title-text {
+		font-size: 32upx;
+		font-weight: 500;
+		color: #333;
 	}
 
 	.simple-address--fixed-top {
@@ -358,4 +627,226 @@ export default {
 		text-overflow: ellipsis;
 		font-size: 28upx;
 	}
+
+	/* 新增样式 */
+	.search-container {
+		padding: 20upx;
+		background-color: #fff;
+		border-bottom: 1upx solid #f2f2f2;
+	}
+
+	.search-box {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+		background-color: #f8f8f8;
+		border-radius: 30upx;
+		padding: 0 20upx;
+		height: 60upx;
+	}
+
+	.search-icon {
+		font-size: 28upx;
+		color: #999;
+		margin-right: 10upx;
+	}
+
+	.search-input {
+		flex: 1;
+		font-size: 28upx;
+		color: #333;
+		background-color: transparent;
+		border: none;
+		outline: none;
+	}
+
+	.clear-btn {
+		padding: 10upx;
+		margin-left: 10upx;
+	}
+
+	.clear-icon {
+		font-size: 32upx;
+		color: #999;
+		font-weight: bold;
+	}
+
+	.search-results {
+		height: 500upx;
+	}
+
+	.search-scroll {
+		height: 100%;
+	}
+
+	.search-result-item {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+		padding: 20upx 30upx;
+		border-bottom: 1upx solid #f5f5f5;
+	}
+
+	.search-result-item:active {
+		background-color: #f0f0f0;
+	}
+
+	.result-province {
+		font-size: 24upx;
+		color: #999;
+		margin-right: 10upx;
+	}
+
+	.result-city {
+		font-size: 28upx;
+		color: #333;
+	}
+
+	.no-results {
+		padding: 100upx 0;
+		text-align: center;
+	}
+
+	.no-results-text {
+		font-size: 28upx;
+		color: #999;
+	}
+
+	.normal-mode {
+		height: 500upx;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.section-title {
+		padding: 20upx 30upx 10upx;
+		background-color: #fafafa;
+	}
+
+	.section-title-text {
+		font-size: 24upx;
+		color: #666;
+		font-weight: 500;
+	}
+
+	.recent-section, .hot-section {
+		border-bottom: 1upx solid #f2f2f2;
+	}
+
+	.recent-cities, .hot-cities {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-wrap: wrap;
+		padding: 20upx 30upx;
+	}
+
+	.recent-city-item, .hot-city-item {
+		background-color: #f8f8f8;
+		border-radius: 30upx;
+		padding: 10upx 20upx;
+		margin: 0 10upx 10upx 0;
+	}
+
+	.recent-city-item:active, .hot-city-item:active {
+		background-color: #e0e0e0;
+	}
+
+	.hot-city-item.selected {
+		background-color: $mainColor;
+	}
+
+	.hot-city-item.selected .hot-city-text {
+		color: #fff;
+	}
+
+	.recent-city-text, .hot-city-text {
+		font-size: 26upx;
+		color: #333;
+	}
+
+	.province-section {
+		flex: 1;
+		overflow: hidden;
+	}
+
+	.province-scroll {
+		height: 100%;
+	}
+
+	.province-group {
+		border-bottom: 1upx solid #f2f2f2;
+	}
+
+	.province-header {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+		justify-content: space-between;
+		padding: 20upx 30upx;
+		background-color: #fafafa;
+	}
+
+	.province-header:active {
+		background-color: #f0f0f0;
+	}
+
+	.province-name {
+		font-size: 28upx;
+		color: #333;
+		font-weight: 500;
+	}
+
+	.province-arrow {
+		font-size: 20upx;
+		color: #999;
+		transition: transform 0.3s;
+	}
+
+	.province-arrow.expanded {
+		transform: rotate(180deg);
+	}
+
+	.cities-container {
+		background-color: #fff;
+	}
+
+	.city-item {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+		justify-content: space-between;
+		padding: 15upx 50upx;
+		border-bottom: 1upx solid #f8f8f8;
+	}
+
+	.city-item:active {
+		background-color: #f0f0f0;
+	}
+
+	.city-item.selected {
+		background-color: rgba($mainColor, 0.1);
+	}
+
+	.city-name {
+		font-size: 28upx;
+		color: #333;
+	}
+
+	.city-item.selected .city-name {
+		color: $mainColor;
+	}
+
+	.selected-icon {
+		font-size: 32upx;
+		color: $mainColor;
+		font-weight: bold;
+	}
 </style>