js 记录下仿携程城市选择

城市选择组件

描述:由于之前需要写一个订票的页面,于是乎在网上搜索了些控件自己对比了下,仿着其中一个组件写了一个,属于重复造轮子,但是目的主要是记录自己的成长,以及方便日后查找

有图有真相,先上图看下效果

在这里插入图片描述
大概就是上图这个样子,点击input框后,弹出城市选择组件,点击其中一个具体的城市或者空表区域隐藏组件(有个小bug,点击空白区域可能不太灵敏)

直接上代码

<template>
	<div class="row main" @click="hideDialog" style="height: 100%;">
		<div class="q-pa-lg" style="margin-left: 25%;margin-right: 25%;margin-top: 10px;">
			<q-tabs style="width: 100%;">
				<!-- 目标 -->
				<q-tab slot="title" label="国内机票" style="width: 200px;" />
				<q-tab slot="title" label="国际机票" style="width: 200px;" />
				<q-tab slot="title" label="火车票" style="width: 200px;" />
			</q-tabs>
			<div id="startCityDialog" class="city" style="padding: 10px; border: solid 1px #ccccc;">
				<div id="startPanel">
					<span>出发城市:</span>
					<el-input type="text" @focus=" showStartCityDialog" v-model=" startCity" style="width: 300px;height: 30px;" />
					<div class="city-components" v-if="showStartCity" style="margin-top: 10px;">
						<ul class="filter-tabar clearfix">
							<li v-for="(item,index) in cityListKey" :class="{active:upCityListIndex==index}" @mouseover="upCityListKey(index)">{{item}}</li>
						</ul>
						<div class="city-content">
							<ul v-for="item in upCityList" class="clearfix">
								<label for="">{{item.ckey}}</label>
								<li v-for="ritem in item.cityList" @click=" selectStartCity(ritem)">{{ritem.airportName}}</li>
							</ul>
						</div>
					</div>
				</div>
				<div style="margin-top: 20px;margin-left: 0px;" id="endPanel">
					<span >到达城市:</span>
					<el-input type="text" @focus=" showEndCityDialog" v-model=" endCity" style="width: 300px;height: 30px;" />
					<div class="city-components" v-if=" showEndCity" style="margin-top: 10px;">
						<ul class="filter-tabar clearfix">
							<li v-for="(item,index) in cityListKey" :class="{active:upCityListIndex==index}" @mouseover="upCityListKey(index)">{{item}}</li>
						</ul>
						<div class="city-content">
							<ul v-for="item in upCityList" class="clearfix">
								<label for="">{{item.ckey}}</label>
								<li v-for="ritem in item.cityList" @click=" selectEndCity(ritem)">{{ritem.airportName}}</li>
							</ul>
						</div>
					</div>
				</div>
				<div style="margin-top: 20px;">
					<span>出发日期:</span>
					<el-date-picker v-model="startDate" style="width: 300px;height: 30px;" class="filter-item" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" />
				</div>
				<div style="margin-top: 20px;">
					<span >到达日期:</span>
					<el-date-picker v-model="endDate" style="width: 300px;height: 30px;" class="filter-item" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" />
				</div>

			</div>

		</div>
	</div>
</template>

<script>
	import { dataList } from 'src/utils/data'

	export default {
		name: 'internalTicket',
		data() {
			return {
				dataList,
				startCity: '',
				endCity: "",
				startDate: new Date(),
				endDate: new Date(),
				showStartCity: false,
				showEndCity: false,
				upCityListIndex: '',
				upCityList: ''

			}
		},
		computed: {
			cityListData() {
				let map = {}; // 处理过后的数据对象
				let temps = []; // 临时变量
				//这一步就获取了
				//				map:{
				//					'A':[{
				//						airportCode:'',
				//						airportName:''
				//					},{
				//						..
				//					}],
				//					'B':[{{
				//						airportCode:'',
				//						airportName:''
				//					},
				//					...
				//				}
				this.dataList.map(item => {
					if(item.airportCode) {
						let ekey = item.airportCode.charAt(0).toUpperCase(); // 根据key值的第一个字母分组,并且转换成大写
						temps = map[ekey] || []; // 如果map里面有这个key了,就取,没有就是空数组
						temps.push({
							airportCode: item.airportCode,
							airportName: item.cityName
						});
						map[ekey] = temps;
					}
				})
				let list = [];
				//获取到list {ckey: "I", cityList: Array(3)}
				for(let gkey in map) {
					list.push({
						ckey: gkey,
						cityList: map[gkey]
					})
				}
				console.log('list', list)
				list = list.sort((li1, li2) => li1.ckey.charCodeAt(0) - li2.ckey.charCodeAt(0));
				let chunk = 4;
				let result = [];
				for(var i = 0, j = list.length; i < j; i += chunk) {
					result.push(list.slice(i, i + chunk));
				}
				console.log('result', result)
				return result;
			},
			cityListKey() {
				let cityListKey = [];
				this.cityListData.map(item => {
					let ckeys = '';
					item.map(ritem => {
						ckeys += ritem.ckey;
					})
					cityListKey.push(ckeys);
				})
				console.log("cityListKey", cityListKey) // ["ABCD", "EFGH", "IJKL", "MNOP", "RSTU", "WXYZ"]
				this.upCityList = this.cityListData[0]
				return cityListKey;
			}
		},
		methods: {
			upCityListKey(index) {
				this.upCityListIndex = index;
				this.upCityList = this.cityListData[index];
			},
			hideCityDialog(event) {
				this.showStartCity = false;
			},
			showStartCityDialog() {
				this.showStartCity = true;
			},
			showEndCityDialog() {
				this.showEndCity = true;
			},
			selectStartCity(ritem) {
				this.startCity = ritem.airportName
				this.showStartCity = false;
			},
			selectEndCity(ritem) {
				this.endCity = ritem.airportName
				this.showEndCity = false;
			},
			hideDialog(event) {
				var sp1 = document.getElementById("startPanel");
				var sp2 = document.getElementById("endPanel");
				//点击的是到达城市区域
				if(!sp1.contains(event.target) && sp2.contains(event.target)) { 
					this.showStartCity  = false;
				}//点击的是出发城市区域
				else if(!sp2.contains(event.target) && sp1.contains(event.target)){
					this.showEndCity  = false;
				}else{
					this.showStartCity  = false;
					this.showEndCity  = false;
				}
			}
		}
	}
</script>

<style scoped lang="stylus">
	#app {
		/*background: #efefef;*/
	}
	
	* {
		margin: 0;
		padding: 0;
	}
	
	.city-wap {
		color: #3b4f62;
		.clearfix {
			&:after {
				content: '';
				display: block;
				clear: both;
			}
		}
		p {
			background: #fff;
			margin-bottom: 10px;
			padding: 0 12px;
		}
		.search {
			position: fixed;
			top: 0;
			box-shadow: 0 1px 3px 0 rgba(59, 79, 98, 0.1);
			width: 100%;
			height: 50px;
			input {
				line-height: 50px;
				width: 100%;
				border: none;
				box-shadow: none;
				padding: 0 10px;
				&:focus {
					outline: none;
				}
			}
		}
		.city-list {
			.block-60 {
				height: 60px;
			}
			ul {
				padding: 0 10px;
				li {
					list-style: none;
					display: inline-block;
					margin-right: 10px;
					width: 29%;
					margin-bottom: 8px;
					line-height: 35px;
					text-align: center;
					color: #333;
					border-radius: 3px;
					background: #fff;
					font-size: 14px;
					white-space: nowrap;
					overflow: hidden;
					text-overflow: ellipsis;
					padding: 0 2px;
				}
			}
		}
		.filter {
			position: fixed;
			right: 3px;
			top: 60px;
			font-size: 15px;
			div {
				margin-top: 2px;
				text-align: center;
			}
		}
		.active-key {
			position: fixed;
			width: 100px;
			height: 100px;
			line-height: 100px;
			top: 50%;
			left: 50%;
			transform: translate(-50%, -50%);
			z-index: 100;
			background: #dedede;
			color: #fff;
			border-radius: 100%;
			text-align: center;
			font-size: 40px;
		}
	}
	
	.city {
		position: relative;
		.city-components {
			opacity: 1;
			position: absolute;
			z-index: 10;
			/*background-color: #0066CC;*/
			width: 500px;
			box-shadow: 0 0 4px 0 rgba(117, 117, 117, 1);
			background-color: #ffffff;
			border-radius: 2px;
			padding: 20px 21px;
			.clearfix {
				&:after {
					content: '';
					display: block;
					clear: both;
				}
			}
			li {
				list-style: none;
			}
			ul {
				padding: 0;
				margin: 0;
			}
			.filter-tabar {
				border-bottom: 1px solid #d7d7d7;
				cursor: pointer;
				li {
					text-align: center;
					/*padding: 0 14px;*/
					float: left;
					padding-bottom: 14px;
					font-size: 14px;
					margin: 0 8px;
					margin-bottom: -1px;
					position: relative;
					&.active {
						border-bottom: 1px solid #2577e3;
						color: #2577e3
					}
				}
			}
			.city-content {
				max-height: 500px;
				overflow-y: auto;
				overflow-x: hidden;
				padding: 10px 13px 0 13px;
				label {
					display: block;
					margin-bottom: 5px !important;
					margin-left: 0 !important;
					color: #5f5f5f !important;
					margin-top: 5px;
				}
				li {
					padding: 6px 0 6px;
					float: left;
					text-align: left;
					font-size: 14px;
					min-width: 56px;
					margin-right: 24px;
					cursor: pointer;
				}
			}
		}
	}
</style>

主要包含两个ul ,第一个ul主要存放ABCD等的titlebar,第二个ul主要放城市以及城市类型,代码比较简单,很容易看懂

注意的点:

		hideDialog(event) {
				var sp1 = document.getElementById("startPanel");
				var sp2 = document.getElementById("endPanel");
				//点击的是到达城市区域
				if(!sp1.contains(event.target) && sp2.contains(event.target)) { 
					this.showStartCity  = false;
				}//点击的是出发城市区域
				else if(!sp2.contains(event.target) && sp1.contains(event.target)){
					this.showEndCity  = false;
				}else{
					this.showStartCity  = false;
					this.showEndCity  = false;
				}
			}

数据源大概是这个样子:

const dataList = [{
		"airportCode": "YIE",
		"cityInfo": "AES-阿尔山-YIE",
		"cityName": "阿尔山市",
		"airportName": "伊尔施",
		"status": 0,
		"lat": 47.3155940318,
		"lng": 119.9293992017
	}, {
		"airportCode": "AKA",
		"cityInfo": "AK-安康-AKA",
		"cityName": "安康",
		"airportName": "五里铺",
		"status": 0,
		"lat": 32.7132899844,
		"lng": 108.9462270884
	}]

数据源地址:

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
针对仿携程旅行的Vue3案例需求分,我们可以考虑以下方面: 1. 页面结构:了解携程旅行网站的整体结构和功能布局,包括首页、搜索页、详情页、订单页等。根据需求确定需要实现的页面和它们之间的关系。 2. 数据接口:携程旅行网站通常会有一些数据接口,如获取酒店列表、获取景点信息等。需要确定哪些接口是必要的,并与后端开发人员协商好接口的格式和参数。 3. 首页功能:仿携程旅行的首页通常会展示热门推荐、特价产品、城市导航等内容。需要确定如何获取这些数据,并实现相关的展示和交互逻辑。 4. 搜索功能:考虑到携程旅行网站的搜索功能,用户可以根据目的地、日期、价格等条件进行搜索。需要设计相应的搜索界面和实现搜索逻辑。 5. 详情页功能:仿携程旅行的详情页通常会展示产品的详细信息、图片轮播、用户评价等内容。需要确定如何获取产品的详细信息并展示,以及如何实现图片轮播和评价展示等功能。 6. 订单功能:用户可以在携程旅行网站上下单购买产品,需要实现相应的下单逻辑和订单管理功能。 7. 用户认证和登录功能:仿携程旅行的案例可能需要实现用户认证和登录功能,以便用户可以保存个人信息、查看订单等。 8. 响应式设计:确保页面在不同尺寸的移动设备上都能够良好地显示和交互。 通过对以上需求进行分析,可以更好地指导Vue3案例的设计和开发工作,确保最终的案例能够实现类似携程旅行网站的功能,并提供良好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值