闲云旅游项目开发-(第四篇:机票首页/机票搜索功能(`el-autocomplete`远程搜索组件)/moment.js的使用/日期选择组件el-date-picker)
目錄
相關(guān)組件介紹
一 機(jī)票首頁(yè)布局
二?封裝搜索組件
1. 組件布局
2.目標(biāo)思路
3.步驟
3.1 基本功能
3.2 自動(dòng)補(bǔ)全
3.3獲取真正的推薦數(shù)據(jù)
3.4 添加城市代號(hào)
3.5 處理日期格式
3.6? 使用了momentjs,這是一個(gè)第三方的包,需要先下載并且導(dǎo)入。
其他功能
優(yōu)化
三 搜索功能
1.思路
2.實(shí)現(xiàn)步驟
獲取城市數(shù)據(jù)
調(diào)用城市查詢(xún)接口
下拉數(shù)據(jù)選中賦值
搜索跳轉(zhuǎn)
城市切換
往返
3.總結(jié)
四 特價(jià)機(jī)票
1. 思路
2.實(shí)現(xiàn)步驟
2.1 特價(jià)機(jī)票布局
2.2 請(qǐng)求數(shù)據(jù)接口
五 效果圖
?
相關(guān)組件介紹
1.el-autocomplete遠(yuǎn)程搜索自動(dòng)補(bǔ)全組件
組件文檔:https://element.eleme.cn/#/zh-CN/component/input#yuan-cheng-sou-suo
2.el-date-picker日期選擇組件
組件文檔:https://element.eleme.cn/#/zh-CN/component/date-picker#xuan-ze-ri
一 機(jī)票首頁(yè)布局
pages/air/index.vue的代碼如下 :
<template><section class="container"><h2 class="air-title"><span class="iconfont iconfeiji"></span><i>國(guó)內(nèi)機(jī)票</i></h2><!-- 搜索廣告欄 --><el-row type="flex" justify="space-between"><!-- 搜索表單 --><div>搜索</div><!-- banner廣告 --><div class="sale-banner"><img src="http://157.122.54.189:9093/images/pic_sale.jpeg"></div></el-row><!-- 廣告 --><el-row type="flex" class="statement"><el-col :span="8"><i class="iconfont iconweibiaoti-_huabanfuben" style="color:#409EFF;"></i><span>100%航協(xié)認(rèn)證</span></el-col><el-col :span="8"><i class="iconfont iconbaozheng" style="color:green;"></i><span>出行保證</span></el-col><el-col :span="8"><i class="iconfont icondianhua" style="color:#409EFF;"></i><span>7x24小時(shí)服務(wù)</span></el-col></el-row><h2 class="air-sale-title"><span class="iconfont icontejiajipiao"></span><i>特價(jià)機(jī)票</i></h2><!-- 特價(jià)機(jī)票 --><div class="air-sale"></div></section> </template><script> export default {} </script><style scoped lang="less"> .air-sale{border: 1px #ddd solid;padding:20px;margin-bottom:50px;.air-sale-pic{> div{width:225px;height:140px;position: relative;overflow: hidden;img{width:100%;}.layer-bar{position:absolute;bottom:0;left:0;background: rgba(0,0,0,0.5);color:#fff;height:30px;line-height: 30px;width:100%;box-sizing: border-box;padding: 0 15px;font-size: 14px;span:last-child{font-size:18px;}}}} }.air-sale-group{margin-top:20px;padding-top:8px;border-right:1px #eee solid;&:last-child{border-right:none;}.air-sale-row{font-size:12px;color:#666;margin-bottom:8px;.air-sale-price{color:orange;font-size: 20px;}} }.container{width:1000px;margin:0 auto; }.air-title{margin:15px 0;font-size:20px;font-weight: normal;color:orange;span{font-size:20px;} }.statement{margin:15px 0;border:1px #ddd solid;background:#f5f5f5;height: 58px;padding:10px 0;box-sizing:border-box;> div{text-align: center;line-height: 38px;border-right:1px #ddd solid;&:last-child{border-right: none;}*{vertical-align: middle;}i{font-size:30px;}} }.air-sale-title{margin:15px 0;font-size:20px;font-weight: normal;color:#409EFF;span{font-size:20px;} } </style>?
?
?
?
?
二?封裝搜索組件
1. 組件布局
新建機(jī)票搜索表單組件components/air/searchForm.vue,并替換以下布局代碼 :
<template><div class="search-form"><!-- 頭部tab切換 --><el-row type="flex" class="search-tab"><span v-for="(item, index) in tabs" :key="index"@click="handleSearchTab(item, index)":class="{active: index === currentTab}"><i :class="item.icon"></i>{{item.name}}</span></el-row><el-form class="search-form-content" ref="form" label-width="80px"><el-form-item label="出發(fā)城市"><!-- fetch-suggestions 返回輸入建議的方法 --><!-- select 點(diǎn)擊選中建議項(xiàng)時(shí)觸發(fā) --><el-autocomplete:fetch-suggestions="queryDepartSearch"placeholder="請(qǐng)搜索出發(fā)城市"@select="handleDepartSelect"class="el-autocomplete"></el-autocomplete></el-form-item><el-form-item label="到達(dá)城市"><el-autocomplete:fetch-suggestions="queryDestSearch"placeholder="請(qǐng)搜索到達(dá)城市"@select="handleDestSelect"class="el-autocomplete"></el-autocomplete></el-form-item><el-form-item label="出發(fā)時(shí)間"><!-- change 用戶(hù)確認(rèn)選擇日期時(shí)觸發(fā) --><el-date-picker type="date" placeholder="請(qǐng)選擇日期" style="width: 100%;"@change="handleDate"></el-date-picker></el-form-item><el-form-item label=""><el-button style="width:100%;" type="primary" icon="el-icon-search"@click="handleSubmit">搜索</el-button></el-form-item><div class="reverse"><span @click="handleReverse">換</span></div></el-form> </div> </template><script> export default {data(){return {tabs: [{icon: "iconfont icondancheng", name: "單程"},{icon: "iconfont iconshuangxiang", name: "往返"}],currentTab: 0,}},methods: {// tab切換時(shí)觸發(fā)handleSearchTab(item, index){},// 出發(fā)城市輸入框獲得焦點(diǎn)時(shí)觸發(fā)// value 是選中的值,cb是回調(diào)函數(shù),接收要展示的列表queryDepartSearch(value, cb){cb([{value: 1},{value: 2},{value: 3},]);},// 目標(biāo)城市輸入框獲得焦點(diǎn)時(shí)觸發(fā)// value 是選中的值,cb是回調(diào)函數(shù),接收要展示的列表queryDestSearch(value, cb){cb([{value: 1},{value: 2},{value: 3},]);},// 出發(fā)城市下拉選擇時(shí)觸發(fā)handleDepartSelect(item) {},// 目標(biāo)城市下拉選擇時(shí)觸發(fā)handleDestSelect(item) {},// 確認(rèn)選擇日期時(shí)觸發(fā)handleDate(value){},// 觸發(fā)和目標(biāo)城市切換時(shí)觸發(fā)handleReverse(){},// 提交表單是觸發(fā)handleSubmit(){}},mounted() {} } </script><style scoped lang="less"> .search-form{border:1px #ddd solid;border-top:none;width:360px;height:350px;box-sizing: border-box; }.search-tab{span{display: block;flex:1;text-align: center;height:48px;line-height: 42px;box-sizing: border-box;border-top:3px #eee solid;background:#eee;}.active{border-top-color: orange;background:#fff;}i{margin-right:5px;font-size: 18px;&:first-child{font-size:16px;}} }.search-form-content{padding:15px 50px 15px 15px;position: relative;.el-autocomplete{width: 100%;} }.reverse{position:absolute;top: 35px;right:15px;&:after,&:before{display: block;content: "";position: absolute;left:-35px;width:25px;height:1px;background:#ccc;}&:after{top:0;}&:before{top:60px;}span{display: block;position:absolute;top: 20px;right:0;font-size:12px;background: #999;color:#fff;width:20px;height:20px;line-height: 18px;text-align: center;border-radius: 2px;cursor: pointer;&:after,&:before{display: block;content: "";position: absolute;left:10px;width:1px;height:20px;background:#ccc;}&:after{top:-20px;}&:before{top:20px;}} } </style>創(chuàng)建完成后在pages/air/index.vue中導(dǎo)入組件
<template> <!-- 其他代碼... --><!-- 搜索廣告欄 --><el-row type="flex" justify="space-between"><!-- 搜索表單 --><SearchForm/><!-- banner廣告 --></el-row><!-- 其他代碼... --> </template> <script> import SearchForm from "@/components/air/searchForm";export default {components: {SearchForm}, } </script>2.目標(biāo)思路
通過(guò)用戶(hù)輸入數(shù)據(jù), 組合成需要的參數(shù) 點(diǎn)擊搜索, 跳轉(zhuǎn)頁(yè)面后, 傳遞到機(jī)票搜索結(jié)果頁(yè)面,再發(fā)送請(qǐng)求
需要的參數(shù)包括
-
出發(fā)地 / 出發(fā)地代碼 departCity /departCode
-
到達(dá)地 / 到達(dá)地代碼 destCity / destCode
-
出發(fā)時(shí)間 departDate
3.步驟
3.1 基本功能
-
三個(gè)輸入框綁定數(shù)據(jù)
-
點(diǎn)擊按鈕, 打印出出全部數(shù)據(jù)作為參考
3.2 自動(dòng)補(bǔ)全
先用死數(shù)據(jù)模擬搜索建議,顯示自動(dòng)補(bǔ)全的兩個(gè)步驟
- 聲明fetch-suggestion 定義獲取建議的函數(shù)
- 在函數(shù)當(dāng)中,可以接受兩個(gè)參數(shù)
-
出發(fā)地輸入框彈出搜索建議
-
到達(dá)地輸入框彈出搜索建議
?
3.3獲取真正的推薦數(shù)據(jù)
-
使用 ajax 獲取數(shù)據(jù)
這個(gè)接口如果沒(méi)有參數(shù)會(huì)獲取到100條數(shù)據(jù)
-
修改數(shù)據(jù)格式,,再進(jìn)行回調(diào)渲染
自動(dòng)補(bǔ)全搜索建議的數(shù)組里面的對(duì)象,必須有value才可以顯示出來(lái)
配合組件的要求 (每個(gè)都需要有 value)
3.4 添加城市代號(hào)
選擇一個(gè)選項(xiàng)的時(shí)候, 城市名作為 value 無(wú)需處理
但是城市代碼code必須主動(dòng)放入數(shù)據(jù)當(dāng)中
-
獲取搜索建議列表時(shí), 除了 value 值用來(lái)渲染列表需要以外, 還需要將當(dāng)前城市的 code 放進(jìn)去備用
-
form 數(shù)據(jù)有兩個(gè)屬性?xún)?chǔ)存城市代號(hào) departCode / destCode
-
每當(dāng)選中一個(gè)選項(xiàng)時(shí), 同時(shí)將城市代號(hào)更新到 form 中
-
監(jiān)聽(tīng) select 事件,函數(shù)可以接受一個(gè)固定的參數(shù), 就是我們選中的那個(gè)城市的數(shù)據(jù)對(duì)象
-
講這個(gè)數(shù)據(jù)對(duì)象里面的 sort 放入 code 城市代碼當(dāng)中即可
-
3.5 處理日期格式
-
使用日期選擇組件(基本用法,用 v-model 綁定數(shù)據(jù))
-
選了日期之后, 輸出的是一個(gè)日期對(duì)象
-
需要轉(zhuǎn)換為合適的格式
-
可以嘗試自己將日期對(duì)象改成 YYYY-MM-DD 的格式
-
餓了么也有一個(gè)功能 使用value-format指定綁定值的格式, 可以使用
-
-
時(shí)間格式轉(zhuǎn)換的時(shí)機(jī)每次用戶(hù)修改日期的時(shí)候自動(dòng)觸發(fā) (監(jiān)聽(tīng) change 事件), 并且修改用戶(hù)時(shí)間格式
3.6? 使用了momentjs,這是一個(gè)第三方的包,需要先下載并且導(dǎo)入。
下載命令:
npm install --save moment在組件中引入
import moment from "moment";傳入日期對(duì)象即可改變格式
moment(value).format("YYYY-MM-DD");其他功能
優(yōu)化
-
封裝獲取建議函數(shù),, 將獲取出發(fā)地搜索建議和獲取到達(dá)地搜索建議的函數(shù)合并
-
輸入后再激活輸入建議 :trigger-on-focus="false"
-
自動(dòng)高亮第一個(gè)選項(xiàng) : highlight-first-item ="true" (如果不生效請(qǐng)注意elementui 版本)
-
數(shù)據(jù)來(lái)自第三方, 某些城市缺失代碼, 刪除獲取不到 sort 的數(shù)據(jù)
其實(shí)可以過(guò)濾, 獲取簡(jiǎn)易列表時(shí), 將所有不帶有 sort 數(shù)據(jù)的城市去掉
-
城市名稱(chēng)不應(yīng)帶有市字, 在獲取列表的時(shí)候直接替換掉市字即可
利用 replace 方法, 將數(shù)據(jù) name 中最后一個(gè) 市字 替換成 空字符串
-
處理用戶(hù)沒(méi)有選擇城市,造成缺失代碼的問(wèn)題
-
搜索得出建議選項(xiàng)的時(shí)候,默認(rèn)將列表的第一項(xiàng) code 數(shù)據(jù)放入 form
默認(rèn)給一個(gè)值, 如果用戶(hù)自己選了, 再去覆蓋
-
-
思考:如果用戶(hù)非要寫(xiě)一個(gè)市字, 可以在以下幾個(gè)時(shí)間點(diǎn)進(jìn)行處理, 只要最終發(fā)請(qǐng)求前處理完畢沒(méi)有市字即可
-
失去焦點(diǎn)
-
跳轉(zhuǎn)頁(yè)面的時(shí)候
-
頁(yè)面跳轉(zhuǎn)后提交搜索 ajax 的時(shí)候
(建議在搜索時(shí)才處理,將這個(gè)處理隱藏起來(lái)不讓用戶(hù)感知)
-
?
三 搜索功能
1.思路
從結(jié)果出發(fā),首先來(lái)看下我看點(diǎn)擊表單后會(huì)發(fā)生什么事?
點(diǎn)擊表單的搜索按鈕后會(huì)跳轉(zhuǎn)到該頁(yè)面,并且提供了5個(gè)用于查詢(xún)的參數(shù),暫且不考慮目前有沒(méi)該頁(yè)面,我們可以先考慮如何給頁(yè)面提供這些參數(shù)。
參數(shù)列表(重要):
-
departCity 出發(fā)城市(注意沒(méi)有市字)
-
departCode 出發(fā)城市代碼
-
destCity 到達(dá)城市(注意沒(méi)有市字)
-
destCode 到達(dá)城市代碼
-
departDate 出發(fā)日期
可以從查找機(jī)票城市的接口找到城市相關(guān)數(shù)據(jù),在用戶(hù)輸入城市的同時(shí)獲得上面的數(shù)據(jù)。
接口:http://157.122.54.189:9095/airs/city
返回結(jié)果:
{ ?data: [{ ? ?code: "440100000000", ? ?created_at: "2019-04-02 08:18:16", ? ?id: 197, ? ?level: "2", ? ?name: "廣州市", ? ?parentCode: "440000000000", ? ?sort: "CAN", ? ?updated_at: 1558617184703 }]; ?total: 1 }?
2.實(shí)現(xiàn)步驟
-
獲取城市數(shù)據(jù)
在data中新增變量存儲(chǔ)表單數(shù)據(jù)?,在data中定義需要提交的是5個(gè)參數(shù)
<script> export default {data(){return {// 其他代碼...form: {departCity: "", // 出發(fā)城市departCode: "", // 出發(fā)城市代碼destCity: "", // 到達(dá)城市destCode: "", // 到達(dá)城市代碼departDate: "", // 日期字符串},}}, } </script>注意在template的表單中使用v-model雙向綁定數(shù)據(jù)到form的屬性。
比如出發(fā)城市v-model="form.departCity":
<el-autocomplete:fetch-suggestions="queryDepartSearch"placeholder="請(qǐng)搜索出發(fā)城市"@select="handleDepartSelect"class="el-autocomplete"v-model="form.departCity"></el-autocomplete>日期departDate只需要在el-date-picker組件中使用v-model="form.departDate"進(jìn)行綁定即可獲得了。
-
調(diào)用城市查詢(xún)接口
使用實(shí)時(shí)查詢(xún)的方式調(diào)用查詢(xún)城市的接口,由于出發(fā)城市和到達(dá)城市都需要調(diào)用這個(gè)查詢(xún)接口,所以把查詢(xún)操作封裝到一個(gè)獨(dú)立函數(shù)來(lái)調(diào)用。
<script> export default {// 其他代碼...methods: {// 出發(fā)城市輸入框獲得焦點(diǎn)時(shí)觸發(fā)// value 是選中的值,cb是回調(diào)函數(shù),接收要展示的列表async queryDepartSearch(value, cb){const arr = await this.querySearchAsync(value)if(arr.length > 0){// 不在下拉列表中選擇,則默認(rèn)選擇第一項(xiàng)this.form.departCity = arr[0].value;this.form.departCode = arr[0].sort;}cb(arr)},// 目標(biāo)城市輸入框獲得焦點(diǎn)時(shí)觸發(fā)// value 是選中的值,cb是回調(diào)函數(shù),接收要展示的列表async queryDestSearch(value, cb){const arr = await this.querySearchAsync(value)if(arr.length > 0){// 不在下拉列表中選擇,則默認(rèn)選擇第一項(xiàng)this.form.destCity = arr[0].value;this.form.destCode = arr[0].sort;}cb(arr)},// 查詢(xún)城市接口的方法,返回promise// queryString是查詢(xún)關(guān)鍵字querySearchAsync(queryString) {return new Promise((resolve, reject) => {// 如果關(guān)鍵字是空,則直接返回if(!queryString){return resolve([]);}this.$axios({url: `/airs/city`,params: {name: queryString}}).then(res => {const {data} = res.data;// 下拉提示列表必須要有value字段const arr = data.map(v => {return {...v,value: v.name.replace("市", "")}});resolve(arr);});});},// 其他代碼...}} </script>?
-
下拉數(shù)據(jù)選中賦值
針對(duì)城市下拉框選項(xiàng)選中的事件處理,我們應(yīng)該把選中的選項(xiàng)當(dāng)做是當(dāng)前的數(shù)據(jù)
<script>export default {// 其他代碼...methods: {// 其他代碼...// 出發(fā)城市下拉選擇時(shí)觸發(fā)// item代表當(dāng)前選中項(xiàng)handleDepartSelect(item) {this.form.departCity = item.value;this.form.departCode = item.sort;},// 目標(biāo)城市下拉選擇時(shí)觸發(fā)// item代表當(dāng)前選中項(xiàng)handleDestSelect(item) {this.form.destCity = item.value;this.form.destCode = item.sort;},// 確認(rèn)選擇日期時(shí)觸發(fā)handleDate(value){this.form.departDate = moment(value).format("YYYY-MM-DD");},// 其他代碼...}} </script>?
-
搜索跳轉(zhuǎn)
下面來(lái)實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn),需要在URL中把5個(gè)參數(shù)都到帶過(guò)去給搜索結(jié)果頁(yè)/air/flights
<script>export default {// 其他代碼...methods: {// 其他代碼...// 提交表單是觸發(fā)handleSubmit(){// 表單驗(yàn)證數(shù)據(jù)const rules = {depart: {value: this.form.departCity, message: "請(qǐng)選擇出發(fā)城市"},dest: {value: this.form.destCity, message: "請(qǐng)選擇到達(dá)城市"},departDate: {value: this.form.departDate, message: "請(qǐng)選擇出發(fā)時(shí)間"},}let valid = true; // 表單驗(yàn)證結(jié)果Object.keys(rules).forEach(v => {// 只要有一個(gè)結(jié)果不通過(guò),就停止循環(huán)if(!valid) return;const item = rules[v];// 數(shù)據(jù)字段為空if(!item.value){valid = false;this.$confirm(item.message, '提示', {confirmButtonText: '確定',showCancelButton: false,type: 'warning'})}});// 不通過(guò)驗(yàn)證,不需要往下執(zhí)行if(!valid) return;this.$router.push({path: "/air/flights",query: this.form})}}} </script>/air/flights頁(yè)面暫時(shí)還沒(méi)創(chuàng)建,不過(guò)只要能從URL中看到參數(shù)正確傳遞即可。
?
-
城市切換
這是個(gè)把出發(fā)城市和到達(dá)城市對(duì)換位置的功能,實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,把form的數(shù)據(jù)對(duì)調(diào)換就可以了
調(diào)換的事件函數(shù)
<script>export default {// 其他代碼...methods: {// 其他代碼...// 觸發(fā)和目標(biāo)城市切換時(shí)觸發(fā)handleReverse(){const { departCity, departCode, destCity, destCode} = this.form;this.form.departCity = destCity;this.form.departCode = destCode;this.form.destCity = departCity;this.form.destCode = departCode;},}} </script>?
?
-
往返
目前接口不支持往返,需要添加一個(gè)提示
// tab切換時(shí)觸發(fā) handleSearchTab(item, index){if(index === 1){this.$confirm("目前暫不支持往返,請(qǐng)使用單程選票!", '提示', {confirmButtonText: '確定',showCancelButton: false,type: 'warning'})} },?
3.總結(jié)
關(guān)鍵點(diǎn)在于如何獲取跳轉(zhuǎn)鏈接需要的5個(gè)參數(shù)!
el-autocomplete組件的使用(查看文檔)
使用momentjs進(jìn)行時(shí)間轉(zhuǎn)換
表單自定義驗(yàn)證
?
四 特價(jià)機(jī)票
1. 思路
思路: 其實(shí)就是獲取數(shù)據(jù)布局, 點(diǎn)擊的結(jié)果就是模擬一個(gè)搜索
特價(jià)機(jī)票布局 :注意鏈接跳轉(zhuǎn)時(shí)的數(shù)據(jù)拼接
請(qǐng)求數(shù)據(jù)接口 :在機(jī)票首頁(yè) created/mounted 生命周期里面獲取數(shù)據(jù) 賦值到 data sales 然后遍歷渲染頁(yè)面即可
2.實(shí)現(xiàn)步驟
2.1 特價(jià)機(jī)票布局
新增特價(jià)機(jī)票布局和模擬數(shù)據(jù)。
pages/air/index.vue
<template><section class="container"><!-- 其他代碼... --><!-- 特價(jià)機(jī)票 --><div class="air-sale"><el-row type="flex" class="air-sale-pic" justify="space-between"><el-col :span="6" v-for="(item, index) in sales" :key="index"><nuxt-link :to="`/air/flights?departCity=${item.departCity}&departCode=${item.departCode}&destCity=${item.destCity}&destCode=${item.destCode}&departDate=${item.departDate}`"><img :src="item.cover"/><el-row class="layer-bar" type="flex" justify="space-between"><span>{{item.departCity}}-{{item.destCity}}</span><span>¥699</span></el-row></nuxt-link></el-col></el-row></div></section> </template><script> // 其他代碼... export default {data(){return {sales: [{cover: "https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D220/sign=9154c841bcfd5266a32b3b169b199799/3812b31bb051f8199687c7e0d0b44aed2f73e7fe.jpg",departCity: "廣州",departCode: "CAN",departDate: "2019-06-17",destCity: "上海",destCode: "SHA",price: 760}]}},// 其他代碼... } </script>注意跳轉(zhuǎn)鏈接的nuxt-link :to="/air/flights?xxxx的參數(shù)拼接
?
2.2 請(qǐng)求數(shù)據(jù)接口
<script> import SearchForm from "@/components/air/searchForm";export default {data(){return {sales: [] // 去除模擬數(shù)據(jù)}},mounted(){this.$axios({url: `/airs/sale`}).then(res => {this.sales = res.data.data;});},// 其他代碼... } </script>五 效果圖
?
?
總結(jié)
以上是生活随笔為你收集整理的闲云旅游项目开发-(第四篇:机票首页/机票搜索功能(`el-autocomplete`远程搜索组件)/moment.js的使用/日期选择组件el-date-picker)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 这几种游戏配音教程分享给你
- 下一篇: 你上一次忍住没揍产品经理是什么时候?