react好租客项目Day02

首页模块

目标

  • 能够使用 Carousel 组件完成轮播图功能
  • 能够安装 axios,并且使用 axios 进行网络请求
  • 能够使用 Flex 组件完成 TabBar 功能
  • 能够知道轮播图与 TabBar 出现的 bug,并且解决
  • 能够安装 Sass,编写 Sass 代码
  • 能够使用 Grid 组件完成租房小组功能
  • 能够利用 H5 API 获取当前的定位信息
  • 能够使用百度地图 API 展示地图页面,获取对应城市信息

轮播图(★★★)

组件使用的基本步骤

  • 打开 antd-mobile 组件库的 Carousel 组件文档
  • 选择基本,点击 (</>) 显示源码
  • 拷贝核心代码到 Index 的组件中
  • 分析并且调整代码,让其能够在项目中运行

轮播图的移植

  • 拷贝示例代码中的内容

    • 导入组件
    1
    import { Carousel, WingBlank } from 'antd-mobile';
    • 状态
    1
    2
    3
    4
    5
    6
    state = {
    // 图片的名称
    data: ['1', '2', '3'],
    // 图片的高度
    imgHeight: 176,
    }
    • 声明周期钩子函数,修改状态,设置数据
    1
    2
    3
    4
    5
    6
    7
    8
    componentDidMount() {
    // simulate img loading
    setTimeout(() => {
    this.setState({
    data: ['AiyWuByWklrrUDlFignR', 'TekJlZRVCjLFexlOCuWn', 'IJOtIlfsYdTyaDTRVrLI'],
    });
    }, 100);
    }
    • 结构
    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
    32
    33
    34
    35
    <div className="index">
    <Carousel
    {/* 自动播放 */}
    autoplay={false}
    {/* 无限循环 */}
    infinite
    {/* 轮播图切换前的回调函数 */}
    beforeChange={(from, to) => console.log(`slide from ${from} to ${to}`)}
    {/* 轮播图切换后的回调函数 */}
    afterChange={index => console.log('slide to', index)}
    {/* 自动切换的时间 */}
    autoplayInterval='2000'
    >
    {/* 遍历状态里面的数据,创建对应的a标签和img图片标签 */}
    {this.state.data.map(val => (
    <a
    key={val}
    href="http://www.alipay.com"
    style={{ display: 'inline-block', width: '100%', height: this.state.imgHeight }}
    >
    <img
    src={`https://zos.alipayobjects.com/rmsportal/${val}.png`}
    alt=""
    style={{ width: '100%', verticalAlign: 'top' }}
    {/* 图片加载完成的时候调用 */}
    onLoad={() => {
    // fire window resize event to change height
    window.dispatchEvent(new Event('resize'));
    this.setState({ imgHeight: 'auto' });
    }}
    />
    </a>
    ))}
    </Carousel>
    </div>
  • 现在我们需要对轮播图进行定制

    • 先优化相应的结构,删除不必要的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <div className="index">
    <Carousel
    autoplay={true}
    infinite
    autoplayInterval='2000'
    >
    {this.state.data.map(val => (
    <a
    key={val}
    href="http://www.alipay.com"
    style={{ display: 'inline-block', width: '100%', height: this.state.imgHeight }}
    >
    <img
    src={`https://zos.alipayobjects.com/rmsportal/${val}.png`}
    alt=""
    style={{ width: '100%', verticalAlign: 'top' }}
    />
    </a>
    ))}
    </Carousel>
    </div>

获取轮播图的数据

  • 安装 axios: yarn add axios
  • 在 Index 组件中导入 axios
1
import axios from 'axios'
  • 在 state 中添加轮播图数据:swipers
1
2
3
4
state = {
// 轮播图状态
swipers: [],
}
  • 新建一个方法 getSwipers 用来获取轮播图数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
async getSwipers() {
// 请求数据
let {data: res} = await axios.get('http://localhost:8080/home/swiper')
// 判断返回的状态是否是成功
if(res.status!= 200){
console.error(res.description)
return
}
// 把获取到的值设置给state
this.setState({
swipers: res.body
})

}
  • 在 componentDidMount 钩子函数中调用这个方法
1
2
3
4
componentDidMount() {
// 调用请求轮播图的方法
this.getSwipers()
}
  • 使用获取到的数据渲染轮播图
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
// 渲染轮播图的逻辑代码
renderSwipers(){
return this.state.swipers.map(item => (
<a
key={item.id}
href="http://www.itcast.cn"
style={{ display: 'inline-block', width: '100%', height: 212 }}
>
<img
src={`http://localhost:8080${item.imgSrc}`}
alt=""
style={{ width: '100%', verticalAlign: 'top' }}
/>
</a>
))
}
render() {
return (
<div className="index">
<Carousel
autoplay={true}
infinite
autoplayInterval='2000'
>
{/* 调用渲染轮播图的方法 */}
{this.renderSwipers()}
</Carousel>
</div>
)
}

导航菜单(★★★)

  • 利用了 antd-moblie 的 Flex 组件进行的布局
  • 导入 nav 的图片
1
2
3
4
import nav1 from '../../images/nav-1.png'
import nav2 from '../../images/nav-2.png'
import nav3 from '../../images/nav-3.png'
import nav4 from '../../images/nav-4.png'
  • 编写页面页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Flex className="nav">
<Flex.Item>
<img src={nav1} alt=""/>
<h2>整租</h2>
</Flex.Item>
<Flex.Item>
<img src={nav2} alt=""/>
<h2>合租</h2>
</Flex.Item>
<Flex.Item>
<img src={nav3} alt=""/>
<h2>地图找房</h2>
</Flex.Item>
<Flex.Item>
<img src={nav4} alt=""/>
<h2>去出租</h2>
</Flex.Item>
</Flex>
  • 给 Flex 组件添加了类名是为了去更改相应的样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.nav {
padding: 10px 0;
}

.nav img {
width: 48px;
}

.nav h2 {
font-size: 13px;
font-weight: 400;
}
/* 通过调试工具我们看到,后续被生成的这个父元素的类名叫am-flexbox-item */
.am-flexbox-item {
text-align: center;
}

h2 {
margin: 0;
margin-top: 7px;
}

导航菜单的重构

  • 把内容封装成数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 导航菜单的数据
const navs = [{
id: 0,
img: nav1,
title: '整租',
path: '/home/list'
}, {
id: 1,
img: nav2,
title: '合租',
path: '/home/list'
}, {
id: 2,
img: nav3,
title: '地图找房',
path: '/home/map'
}, {
id: 3,
img: nav4,
title: '去出租',
path: '/home/list'
}]
  • 创建对应的方法 renderNavs,在方法中遍历 navs,一个一个设置数据,把最终的 JSX 返回
1
2
3
4
5
6
7
8
9
10
11
// 渲染导航菜单的逻辑代码
renderNavs() {
return navs.map(item => {
return (
<Flex.Item key={item.id} onClick={()=>{this.props.history.push(item.path)}}>
<img src={item.img} alt="" />
<h2>{item.title}</h2>
</Flex.Item>
)
})
}
  • 在 render 方法中调用这个函数
1
2
3
4
{/* 导航栏 */}
<Flex className="nav">
{this.renderNavs()}
</Flex>

轮播图的问题(★★★)

  • 由于我们动态加载数据,导致了轮播图不能自动去进行轮播以及高度的塌陷

  • 解决办法

    • 在 state 中添加轮播图数据是否加载完成的状态
    1
    2
    3
    4
    5
    state = {
    // 轮播图状态
    swipers: [],
    isSwiperLoaded: false
    }
    • 在轮播图数据加载完成时候,修改这个状态为 true
    1
    2
    3
    4
    5
    6
    7
    8
    9
    async getSwipers() {
    ...
    // 把获取到的值设置给state
    this.setState({
    swipers: res.body,
    isSwiperLoaded: true
    })

    }
    • 只有在录播图数据加载完成的情况下,才渲染轮播图组件

    • 给轮播图的外层包裹一个 div,给这个 div 设置高度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <div className="swiper">
    {/* 轮播图 */}
    {this.state.isSwiperLoaded ? (<Carousel
    autoplay={true}
    infinite
    autoplayInterval='2000'
    >
    {/* 调用渲染轮播图的方法 */}
    {this.renderSwipers()}
    </Carousel>) : ('')}
    </div>

TabBar 的问题(★★★)

  • 当我们通过首页菜单导航跳转到相应页面的时候,底部的 TabBar 没有进行高亮显示
  • 原因: 我们实现该功能的时候,只考虑了点击以及第一次家的 Home 组件的情况下,但是,我们没有考虑不重新加载 Home 组件时路由的切换
  • 解决:在路由发生切换的时候,再来处理 TabBar 的高亮显示
    • 添加 componentDidUpDate 钩子函数
    • 在钩子函数中判断路由地址是否切换
    • 在路由地址切换的时候,让 TabBar 对应高亮
  • 在 Home.js 里面注册钩子函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 当Home组件的内容发生更新的时候调用

componentDidUpdate(prevProps) {

// 在这里就能判断路由是否进行了切换,路由的信息保存在props属性里面

// 如果当前的路由信息不等于上一次的,那么就代表发生了路由切换

if(prevProps.location.pathname !== this.props.location.pathname){

this.setState({

selectedTab: this.props.location.pathname

})

}
}

Sass 的使用

  • 打开脚手架文档,找到添加 Sass 样式
  • 安装 Sass: yarn add node-sass
  • 创建后缀名为.scss 或者 .sass 的样式文件
  • 在组件中导入 Sass 样式

租房小组(★★★)

业务介绍

  • 需求:根据当前地理位置展示不同小组信息
  • 需要后台接口根据用户找房数据,推荐用户最感兴趣的内容(正常的逻辑是我们先获取到用户当前定位的信息,把信息发送给后台,后台根据定位信息获取对应的内容)
  • 前端只需要展示数据

数据获取

  • 在 state 中添加租房小组数据:groups
1
2
3
4
5
state = {
...
// 租房小组状态
groups: []
}
  • 新建一个方法getGroups用来获取数据,并更新 groups 状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async getGroups() {
let { data: res } = await axios.get('http://localhost:8080/home/groups', {
params: {
'area': 'AREA%7C88cff55c-aaa4-e2e0'
}
})
// 判断返回的状态是否是成功
if (res.status != 200) {
console.error(res.description)
return
}
// 把获取到的值设置给state
this.setState({
groups: res.body
})
}
  • componentDidMount钩子函数中调用该方法
1
2
3
4
5
componentDidMount() {
// 调用请求轮播图的方法
this.getSwipers()
this.getGroups()
}
  • 使用获取到的数据渲染租房小组数据

页面结构样式

  • 实现标题的结构和样式
  • 打开 Grid 宫格组件
  • 选择 基本 菜单,点击(</>) 显示源码
  • 拷贝核心代码到 Index 组件中
  • 分析调整代码

布局结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 {/* 租房小组 */}
<div className="group">
<h3 className="group-title">
租房小组 <span className="more">更多</span>
</h3>
<Grid data={this.state.groups}
{/* 列数 */}
columnNum={2}
{/* 是否强制正方形 */}
square={false}
{/* 是否有边框 */}
hasLine={false}
{/* 自定义里面的布局 */}
renderItem={item => this.renderGroups(item)} />
</div>

自定布局单独抽取成方法

1
2
3
4
5
6
7
8
9
10
11
renderGroups(item) {
return (
<Flex className="group-item" justify="around">
<div className="desc">
<p className="title">{item.title}</p>
<span className="info">{item.desc}</span>
</div>
<img src={`http://localhost:8080${item.imgSrc}`} alt="" />
</Flex>
)
}

相应的样式属性

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
.group {
background-color: #f6f5f6;
overflow: hidden;
padding: 0 10px;
.group-title {
position: relative;
margin: 15px 0px 15px 10px;
font-size: 15px;
.more {
color: #787d82;
position: absolute;
right: 0;
font-size: 14px;
font-weight: normal;
}
}
// 覆盖默认背景色
.am-grid .am-flexbox {
background-color: inherit;
.am-flexbox-item .am-grid-item-content {
padding: 0;
padding-bottom: 10px;
}
}
.group-item {
height: 75px;
.desc {
.title {
font-weight: bold;
font-size: 13px;
margin-bottom: 5px;
}
.info {
font-size: 12px;
color: #999;
}
}
img {
width: 55px;
}
}
.am-flexbox-align-stretch {
margin-bottom: 10px;
.am-grid-item {
background-color: #fff;
&:first-child {
margin-right: 10px;
}
}
}
}

最新资讯

数据获取&页面渲染

  • 在 state 中添加租房小组数据:news
1
2
3
4
5
state = {
...
// 最新资讯
news: []
}
  • 创建一个函数 getNews(), 在这个函数中利用axios来请求服务器
  • 获取到的数据判断返回的状态是否是 200,如果不是,提示用户
  • 如果状态是 200,利用this.setState() 来更新页面
  • componentDidUpdate钩子函数中调用 getNews()
1
2
3
4
5
6
7
8
9
10
11
12
async getNews() {
let { data: res } = await axios.get('http://localhost:8080/home/news?area=AREA%7C88cff55c-aaa4-e2e0')
// 判断返回的状态是否是成功
if (res.status != 200) {
console.error(res.description)
return
}
// 把获取到的值设置给state
this.setState({
news: res.body
})
}
  • 创建页面结构,渲染到页面
    • WingBlank组件 两翼留白 的效果
    • 渲染的逻辑代码比较多,抽取成一个方法,这样保证结构中的代码比较清晰
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
 {/* 最新资讯 */}
<div className="news">
<h3 className="group-title">最新资讯</h3>
<WingBlank size="md">{this.renderNews()}</WingBlank>
</div>

renderNews() {
return this.state.news.map(item => {
return (
<div className="news-item" key={item.id}>
<div className="imgwrap">
<img
className="img"
src={`http://localhost:8080${item.imgSrc}`}
alt=""
/>
</div>
<Flex className="content" direction="column" justify="between">
<h3 className="title">{item.title}</h3>
<Flex className="info" justify="between">
<span>{item.from}</span>
<span>{item.date}</span>
</Flex>
</Flex>
</div>
)
})
  • 样式属性
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 最新资讯:
.news {
padding: 10px;
background-color: #fff;
overflow: hidden;

.group-title {
margin: 10px 0 5px 10px;
font-size: 15px;
}

.news-item {
height: 120px;
padding: 15px 10px 15px 0;
border-bottom: 1px solid #e5e5e5;
}

.news-item:last-child {
border: 0;
}

.imgwrap {
float: left;
height: 90px;
width: 120px;
}

.img {
height: 90px;
width: 120px;
}

.content {
overflow: hidden;
height: 100%;
padding-left: 12px;
}

.title {
margin-bottom: 15px;
font-size: 14px;
}

.info {
width: 100%;
color: #9c9fa1;
font-size: 12px;
}

.message-title {
margin-bottom: 48px;
}
}

解决内容被TabBar压住的问题

我们在 Home.js 中找到 包裹 路由和底部导航栏的 div 盒子,给其添加 padding-bottom 属性即可

顶部导航功能(★★★)

  • 实现结构和样式
  • 添加城市选择、搜索、地图找房页面的路由跳转

相关结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 <Flex className='search-box'>
{/* 左侧白色区域 */}
<Flex className="search">
{/* 位置 */}
<div className="location" >
<span className="name">长沙</span>
<i className="iconfont icon-arrow" />
</div>

{/* 搜索表单 */}
<div className="form">
<i className="iconfont icon-seach" />
<span className="text">请输入小区或地址</span>
</div>
</Flex>
{/* 右侧地图图标 */}
<i className="iconfont icon-map" />
</Flex>

相关样式

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 顶部导航
.search-box {
position: absolute;
top: 25px;
width: 100%;
padding: 0 10px;
// 左侧白色区域
.search {
flex: 1;
height: 34px;
margin: 0 10px;
padding: 5px 5px 5px 8px;
border-radius: 3px;
background-color: #fff;
// 位置
.location {
.icon-arrow {
margin-left: 2px;
font-size: 12px;
color: #7f7f80;
}
}
// 搜索表单
.form {
border-left: solid 1px #e5e5e5;
margin-left: 12px;
line-height: 16px;
.icon-seach {
vertical-align: middle;
padding: 0 2px 0 12px;
color: #9c9fa1;
font-size: 15px;
}
.text {
padding-left: 4px;
font-size: 13px;
color: #9c9fa1;
}
}
}
// 右侧地图图标
.icon-map {
font-size: 25px;
color: #fff;
}
}

H5 中利用定理定位 API

地理位置 API 允许用户向 Web 应用程序提供他们的位置,出于隐私考虑,报告地理位置前先会请求用户许可

地理位置的 API 是通过 navigator.geolocation 对象提供,通过getCurrentPosition方法获取

获取到的地理位置跟 GPS、IP 地址、WIFI 和蓝牙的 MAC 地址、GSM/CDMS 的 ID 有关

比如:手机优先使用 GPS 定位,笔记本等最准确的是定位是 WIFI

我们所获取到的是经纬度,其实对我们来说是没有用的,所以我们需要借助百度地图、高德地图等的开放接口,来帮我们把经纬度进行换算

定位相关

百度地图 API(★★★)

  • H5 的地理位置 API 只能获取到对应经纬度信息
  • 实际开发中,会使用百度地图/高德地图来完成地理位置的相关功能
  • 租房项目中,通过百度地图 API 实现地理位置和地图找房功能
  • 我们需要去参照百度地图文档
  • 注册百度开发者账号,申请对应的 AK

使用步骤

  • 引入百度地图的 API 的 JS 文件,替换自己申请好的密钥

  • 在 index.css 中设置全局样式

  • 创建 Map 组件,配置路由,在 Map 组件中,创建地图容器元素,并设置样式

  • 创建地图实例

  • 设置中心点坐标

  • 初始化地图,同时设置展示级别

获取顶部导航城市信息

  • 查看百度地图的定位文档
  • 通过 IP 定位获取到当前城市名称
  • 调用我们服务器的接口,换取项目中的城市信息