Fork me on GitHub
余鸢

饿了么45个页面重构(二):axios的应用及首页和city页面相关

跨域请求数据

使用axios跨域请求数据

我们的项目是前后端分离,后端只负责接口,前端负责数据展示、逻辑处理。但是前后端开发模式,有一个重要的问题,就是跨域问题。Vue-cli 创建的项目,可以直接利用 Node.js 代理服务器,实现跨域请求,我使用axios解决跨域。

axios简介

vue2.0官方推荐axios进行ajax请求。axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:

  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防止 CSRF/XSRF

安装axios

1
npm install axios

在其他文件中axios即可。

首页

home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div>
<head-top signin-up='home'>
<router-link to='/home' slot='logo' class="head_logo">ele.me</router-link>
</head-top>
<nav class="city_nav">
<div class="city_tip">
<span>当前定位城市:</span>
<span>定位不准时,请在城市列表中选择</span>
</div>
<router-link :to="'/city/' + guessCityid" class="guess_city">
<span>{{guessCity}}</span>
<span>></span>
</router-link>
</nav>
<div class="group_city_container">
<ul class="letter_classify">
<li v-for="(value, key, index) in sortGroupCity" :key="key" class="letter_classify_li">
<h4 class="ciyt_title">{{key}}
<span v-if="index == 0">(按字母排序)</span>
</h4>
<ul class="groupcity_name_container citylistul clear">
<router-link tag="li" v-for="item in value" :to="'/city/' + item.id" :key="item.id" class="ellipsis">
{{item.name}}
</router-link>
</ul>
</li>
</ul>
</div>
</div>
</template>

获取城市列表

使用axios获取城市列表数据,因为最新的vue-webpack-template改用webpack.dev.conf.js文件代替,配置需写在这个文件中,在devServer{}括号里添加before(app){}。

1
2
3
4
5
6
7
8
9
10
11
12
app.get('/api/getCities', (req, res) => {
const url = 'https://www.ele.me/restapi/shopping/v1/cities'
axios.get(url, {
headers: {
Host: 'www.ele.me',
Referer: 'https://www.ele.me/'
}}).then((response) => {
res.json(response.data)
}).catch((e) => {
console.log(e)
})
}),

我把业务组件相关的操作代码放在api文件下,使用时方便查找。创建/api/home.js文件:

1
2
3
4
5
6
7
8
import axios from 'axios'
export function getCities () {
const url = '/api/getCities'
return axios.get(url).then((res) => {
return Promise.resolve(res.data)
})
}

引入axios,调用webpack.dev.conf.js中定义的/api/getCities url

回到home.vue组件,data中定义数据groupCity,表示所有城市列表。创建getCities()方法获取城市列表数据。

1
2
3
4
5
6
7
8
methods: {
getCities () {
getCities().then((res) => {
console.log(res)
this.groupCity = res
})
},
}

获取到的数据发现并不是我想要的数据结构,需要手动将数据转化成我想要的数据结构。创建sortGroupCity()方法,将获取的数据按照A-Z字母开头排序。

1
2
3
4
5
6
7
8
9
sortGroupCity () {
let sortObj = {}
for (let i = 65; i <= 90; i++) {
if (this.groupCity[String.fromCharCode(i)]) {
sortObj[String.fromCharCode(i)] = this.groupCity[String.fromCharCode(i)]
}
}
return sortObj
}

在控制台console.log出数据:

e2

在template中动态获取城市列表数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="group_city_container">
<ul class="letter_classify">
<li v-for="(value, key, index) in sortGroupCity" :key="key" class="letter_classify_li">
<h4 class="ciyt_title">{{key}}
<span v-if="index == 0">(按字母排序)</span>
</h4>
<ul class="groupcity_name_container citylistul clear">
<router-link tag="li" v-for="item in value" :to="'/city/' + item.id" :key="item.id" class="ellipsis">
{{item.name}}
</router-link>
</ul>
</li>
</ul>
</div>

页面展示

e3

定位城市

定位城市首先就是要获取到城市id,data中定义guessCityidguessCity,分别表示当前城市id和城市名称,创建getGuess()方法获取当前城市。获取当前城市名称和id值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data () {
return {
guessCityid: '',
guessCity: ''
}
}
methods: {
getGuess () {
getGuess().then((res) => {
this.guessCity = res.name
this.guessCityId = res.id
})
}
}

在template中渲染

1
2
3
4
5
6
7
8
9
10
<nav class="city_nav">
<div class="city_tip">
<span>当前定位城市:</span>
<span>定位不准时,请在城市列表中选择</span>
</div>
<router-link :to="'/city/' + guessCityid" class="guess_city">
<span>{{guessCity}}</span>
<span>></span>
</router-link>
</nav>

红色遮盖部分就是当然城市的名称,页面展示

e4

根据guessCityid获取到的城市。由此定位城市成功。

点击刷新
1
2
3
<e-header signin-up='home'>
<span slot="logo" class="head_logo" @click="reload">clm.me</span>
</e-header>

点击span文字刷新页面,创建reload点击事件

1
2
3
reload () {
window.location.reload()
}

city页面

选择城市

点击当前城市跳转到city页面。修改city.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="city_container">
<e-header :head-title="cityName" :go-back="goBack">
<router-link to="/home" slot="changCity" class="chang_city">切换城市</router-link>
</e-header>
<form class="city_form" @submit.prevent>
<div>
<input type="search" name="city" v-model="query" :placeholder="placeholder" class="city_input input_style" required/>
</div>
<div>
<input type="submit" name="submit" class="city_submit input_style" @click="postPois" />
</div>
</form>
</div>
</template>

引入e-header组件并使用。

获取当前城市数据

同理,在webpack.dev.conf.js文件中添加代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.get('/api/getCurrentCity', (req, res) => {
const url = 'https://h5.ele.me/restapi/bgs/poi/reverse_geo_coding'
axios.get(url, {
headers: {
Host: 'h5.ele.me',
Referer: 'https://h5.ele.me/'
},
params: req.query
}).then((response) => {
res.json(response.data)
}).catch((e) => {
console.log(e)
})
}),

/api/city.js

1
2
3
4
5
6
export function getCurrentCity () {
const url = '/api/getGuess'
return axios.get(url).then((res) => {
return Promise.resolve(res.data)
})
}

city组件中,在data中定义cityIdcityName,分别表示当前城市id和当前城市名字,引入getCurrentCity()方法获取数据

city.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data() {
return {
cityId: '',
cityName: ''
}
},
methods: {
getCurrentCity () {
getCurrentCity().then((res) => {
this.cityName = res.name
this.cityId = res.city_id
})
},
}

同时在mounted中挂载getCurrentCity()方法,渲染数据

1
2
3
mounted () {
this.getCurrentCity()
}

页面展示

e5

搜索地址

在input输入框里输入要搜索的地址,触发postPois事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div>
<input type="submit" name="submit" class="city_submit input_style" @click="postPois"/>
</div>
methods: {
postPois () {
if (this.query) {
query(this.query, this.geohash).then((res) => {
this.historyTitle = false
this.placeList = res
if (res.length > 0) {
this.placeNone = false
}
})
}
},
}

搜索历史

思路:用户通过在搜索框输入关键字之后点击提交按钮都展示搜索结果列表。 当搜索结果加载完毕时,将关键字和关键字对应的链接地址存起来。当搜索页重新加载时,将用户搜索的条件显示并保存在当前页面上。

首先,要写个js方法来保存搜索条件。在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的。这儿我就用localStorage对数据进行存储操作。

我把对于localStorage相关的操作代码写在mUtils.js中。

common/js/mUtils.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export const setStore = (name, content) => {
if (!name) return
if (typeof content !== 'string') {
content = JSON.stringify(content)
}
window.localStorage.setItem(name, content)
}
export const getStore = name => {
if (!name) return
return window.localStorage.getItem(name)
}
export const removeStore = name => {
if (!name) return
window.localStorage.removeItem(name)
}

注意:一般我们会将JSON存入localStorage中,但是在localStorage会自动将localStorage转换成为字符串形式,这时我们可以使用JSON.stringify()这个方法,来将JSON转换成为JSON字符串。

数据渲染
1
<div v-if="historyTitle" class="pois_search_history">搜索历史</div>

点击提交按钮后即隐藏搜索历史头部,默认为显示搜索历史头部。

e6

获取搜索城市列表

点击提交按钮后显示搜索到的城市列表。在dom中进行遍历渲染。

1
2
3
4
5
6
7
<ul class="getpois_ul">
<li v-for="(item, index) in placeList" :key="index" @click="nextPage(index, item.geohash)">
<h4 class="pois_name ellipsis">{{item.name}}</h4>
<p class="pois_address ellipsis">{{item.address}}</p>
</li>
</ul>
<div class="search_none_place" v-if="placeNone">很抱歉!无搜索结果</div>

得到搜索城市列表数据后,将搜索的条件显示并保存在当前页面上,同时点击哪个城市地址将进入到下一页面。创建nextPage事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
methods: {
nextPage (index, geohash) {
let history = getStore('placeHistory')
let choosePlace = this.placeList[index]
if (history) {
let checkRepeat = false
this.placeHistory = JSON.parse(history)
this.placeHistory.forEach(item => {
if (item.geohash === geohash) {
checkRepeat = true
}
})
if (!checkRepeat) {
this.placeHistory.push(choosePlace)
}
} else {
this.placeHistory.push(choosePlace)
}
setStore('placeHistory', this.placeHistory)
this.$router.push({path: '/msite', query: {geohash}})
}
}

将localStorage的key设置为placeHistory,先判断是否已经有一样的历史记录,如果没有则新增,如果有则不做重复储存,判断完成后进入下一页。

e7

到此,city页面已经完成!

具体代码见:https://github.com/kakajing/vue-elmm