使用 Angular8 和百度地图 api 开发《旅游清单》

用户头像
徐小夕
关注
发布于: 2020 年 12 月 14 日
使用Angular8和百度地图api开发《旅游清单》





前言:

本文的目的是通过一步步实现一个旅游清单项目,让大家快速入门Angular8以及百度地图API。我们将收获:

  • Angular8基本用法,架构

  • 使用百度地图API实现自己的地图应用

  • 解决调用百度地图API时的跨域问题

  • 对localStorage进行基础封装,进行数据持久化

  • material UI的使用

项目简介

《旅游清单》项目的背景主要是为了让笔者更好的掌握angular8,因为之前做的项目主要是使用vue和react,作为一名合格的coder,必须博学而专一,也是因为笔者早年大学时期想要做的一个想法,可以有这样一个程序,记录自己的路途,见闻和感想。项目的首页展示的是已去过的旅游地点和路线,地图路线是通过调用百度地图api实现的,当然提供这样的api很多,大家可以根据自己的喜好去使用。其次我们可以在首页添加未来的旅游规划和预算,方便后面使用。我的大陆页面主要展示的你去过的和即将要去的路线,可以进行相关操作。

项目地址:

基于angular8和百度地图API开发旅游清单项目

《旅游清单》项目架构





其中components为组件存放区,config为公共配置区,home/newMap为页面区,mock为模拟数据区,service为应用所需服务区,如http服务,存储服务,custom.modules文件为第三方组件安置区。

效果预览





添加旅游规划之后:







1.开始

  1. 首先假定你已经安装了node,没有安装请移步node官网进行安装。安装脚手架:

npm install -g @angular/cli
复制代码
  1. 创建工作空间和初始应用

ng new my-app
复制代码
  1. 安装material UI

npm install @angular/material @angular/cdk @angular/animations
复制代码
  1. 根据以上架构,建立对应目录文件

  2. 启动服务

cd my-app
ng serve --open
复制代码

这里cli会自动打开浏览器4200端口,并出现默认页面。

2.引入百度地图API

官方会提供不同地图功能的api地址,以下是该项目使用的地址:

<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你的ak"></script>
<script type="text/javascript" src="http://api.map.baidu.com/library/CurveLine/1.5/src/CurveLine.min.js"></script>
复制代码

如果没有ak,请移步百度地图官网申请,步骤也很简单。

至此,项目的基本准备工作已经做好了,下面让我们先聊一聊angular。

3.angular基本语法和架构

1.基本语法

和vue类似,ng的基本语法如下:

  1. 模版语法

  2. 数据指令

  3. 属性绑定

  4. 事件绑定

案例如下:

<h1>{{title}}</h1>
<h2 [title]="mytitle">My favorite hero is: {{ mytitle }}</h2>
<p>Heroes:</p>
<ul>
<li *ngFor="let item of list">
{{ hero }}
</li>
</ul>
<button (click)="onclickBtn">单机</button>
复制代码

以上代码可以知道,我们用{{}}插入数据,用[属性名]绑定属性,*ngFor为循环指令,类似的*ngIf为条件判断,事件绑定用(click),我们看看组件的ts文件对应的写法:

import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './index.html',
styleUrls: ['./index.scss']
})
export class AppComponent {
mytitle = 'Xujiang';
list = [
'xujaing',
'zhangjiang',
'xakeng'
];
onclickBtn() {
console.log('你好')
}
}
复制代码

2.基本架构

采用angular官方提供的架构图:





我们知道,一个完整的angular应该包括:



  1. 模块

  2. 组件

import { Component, OnInit } from '@angular/core';
import { LocationService } from '../../service/list';
@Component({
selector: 'app-bar',
templateUrl: './index.html',
styleUrls: ['./index.scss']
})
export class AppBar implements OnInit {
items;
constructor(private locationService: LocationService) {
this.items = this.locationService.getItems();
}
ngOnInit() {
}
}
复制代码
  1. 服务与依赖注入

```
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class Storage {}
```
复制代码
  1. 路由

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home';
import { NewMapComponent } from './newMap';
// 路由不能以‘/’开始
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'newMap', component: NewMapComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
复制代码

4. 百度地图api及跨域问题解决

我们进入百度地图官网后,去控制台创建一个应用,此时会生成对应的应用ak,如下:





本地调试时将referer写成*即可,但是我们用ng的http或者fetch去请求api接口时仍会出现跨域,在网上搜集了各种资料,都没有达到效果,我们这里使用jquery的$.getScript(url),结合jsonp回调,即可解决该问题。



所以先安装以下jquery:

npm install jquery
复制代码

解决方案如下:

1.封装http服务:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AK, BASE_URL } from '../config';
import * as $ from "jquery";
@Injectable({
providedIn: 'root'
})
export class Http {
constructor(
private http: HttpClient
) {
}
params(data = {}) {
let obj = {...data, ak: AK, output: 'json' };
let paramsStr = '?';
for(let v in obj) {
paramsStr += `${v}=${obj[v]}&`
};
return paramsStr.substr(0, paramsStr.length -1);
}
get(url, params) {
return this.http.get(`${BASE_URL}${url}${this.params(params)}`)
}
getCors(url, params) {
return new Promise((resolve, reject) => {
$.getScript(`${BASE_URL}${url}${this.params(params)}`, (res, status) => {
if(status === 'success') {
resolve(status)
} else {
reject(status)
}
});
})
}
}
复制代码

定义jsonp回调和接收数据变量:

let locationData = null;
window['cb'] = function(data) {
locationData = data && data.results;
}
复制代码

使用:

async searchLocation(v) {
return await this.http.getCors('/place/v2/search',
{ region:v, query: v, callback: 'cb' });
}
复制代码

至此,应用几个主要的突破点已经解决好了,接下来我们来开发项目的核心页面和组件。

  1. 按需引入materialUI组件:

// custom.module.ts
import { NgModule } from '@angular/core';
import { MatButtonModule, MatTooltipModule, MatBadgeModule } from '@angular/material';
@NgModule({
imports: [MatButtonModule, MatTooltipModule, MatBadgeModule],
exports: [MatButtonModule, MatTooltipModule, MatBadgeModule],
})
export class CustomMaterialModule { }
复制代码

custom.module.ts为根目录下的文件,这里我用来做存储第三方组件的位置,定义好之后在app.module.ts中引入:

// material组件库
import { CustomMaterialModule } from './custom.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
ReactiveFormsModule,
AppRoutingModule,
HttpClientModule,
CustomMaterialModule,
],
providers: [],
bootstrap: [AppComponent]
})
复制代码

BrowserAnimationsModule主要是angular为组件提供一些动效支持的模块。接下来我们看看入口页面:

// app.component.html
<div class="app-wrap">
<app-bar></app-bar>
<main class="main">
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>
</div>
复制代码

app-bar,app-footer为我们定义好的页头页尾组件,如下:

// app-bar.html
<nav class="nav-bar">
<div class="logo">旅游导图+</div>
<a [routerLink]="['/']">首页</a>
<a [routerLink]="['/newMap']"><span [matBadge]="items.length" matBadgeOverlap="false" matBadgeColor="warn">我的大陆</span></a>
</nav>
// app-bar.ts
import { Component, OnInit } from '@angular/core';
import { LocationService } from '../../service/list';
@Component({
selector: 'app-bar',
templateUrl: './index.html',
styleUrls: ['./index.scss']
})
export class AppBar implements OnInit {
items;
constructor(private locationService: LocationService) {
this.items = this.locationService.getItems();
}
ngOnInit() {
}
}
// footer.html
<footer class="footer">@开发者:{{ name }}</footer>
// footer.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-footer',
templateUrl: './index.html',
styleUrls: ['./index.scss']
})
export class AppFooter implements OnInit {
name = '猪先森';
constructor() {
}
ngOnInit() {
}
}
复制代码

scss在这里就不引入了,因为比较简单,如果需要大家可以去我的github上现在完整项目基于angular8和百度地图API开发旅游清单项目来学习。

其次,页面头部组件用到了LocationService,我们来看看这个service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Storage } from './storage';
@Injectable({
providedIn: 'root'
})
export class LocationService {
items = [
{
name: '北京',
desc: '北京好,风景真的不错!',
price: '2000',
date: '2018-12-29',
hasDone: true,
location: {
lat: 39.910924,
lng: 116.413387
}
},
{
name: '苏州',
desc: '苏州好,去了还想去,不错!',
price: '2000',
hasDone: true,
date: '2018-12-29',
location: {
lat: 31.303565,
lng: 120.592412
}
},
{
name: '上海',
desc: '上海好,去了还想去,不错!',
price: '2000',
hasDone: true,
date: '2018-12-29',
location: {
lat: 31.235929,
lng: 121.48054
}
},
{
name: '武汉',
desc: '武汉好,去了还想去,不错!',
price: '2000',
hasDone: true,
date: '2018-12-29',
location: {
lat: 30.598467,
lng: 114.311586
}
}
];
constructor(
private http: HttpClient,
private store: Storage
) {
if(store.get('list')) {
this.items = store.get('list');
}
}
addToList(location) {
this.items.push(location);
this.store.set('list', this.items);
}
getItems() {
return this.items;
}
clearList() {
this.items = [];
return this.items;
}
}
复制代码

该服务主要提供访问列表,添加旅游清单,清除清单的功能,我们利用@Injectable({providedIn: 'root'})将服务注入根组件以便共享服务。其次我们使用自己封装的Storage服务来进行持久化数据存储,storage服务如下:

import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class Storage {
get(k) {
return JSON.parse(localStorage.getItem(k))
}
set(k, v) {
localStorage.setItem(k, JSON.stringify(v))
}
remove(k) {
localStorage.removeItem(k)
}
}
复制代码

实现起来比较简单,这里就不多说明了。接下来我们看看首页核心功能的实现:

  1. 百度地图初始化路线图:





代码如下:



import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Input } from '@angular/core';
import { Http } from '../service/http';
import { FormBuilder } from '@angular/forms';
import { LocationService } from '../service/list';
@Component({
selector: 'app-home',
templateUrl: './index.html',
styleUrls: ['./index.scss']
})
export class HomeComponent implements OnInit {
hasDoneList;
constructor(
private locationService: LocationService,
private http: Http,
) {
this.hasDoneList = this.locationService.getItems();
}
ngOnInit() {
let map = new BMap.Map("js_hover_map");
// 创建地图实例
map.centerAndZoom(new BMap.Point(118.454, 32.955), 6);
map.enableScrollWheelZoom();
let hasDoneLocations = [];
this.locationService.getItems().forEach(item => {
item.hasDone && hasDoneLocations.push(new BMap.Point(item.location.lng,item.location.lat))
})
let curve = new BMapLib.CurveLine(hasDoneLocations, {strokeColor:"red", strokeWeight:4, strokeOpacity:0.5}); //创建弧线对象
map.addOverlay(curve); //添加到地图中
curve.enableEditing(); //开启编辑功能
}
}
复制代码

我们在ngOninit生命周期里,初始化地图数据,根据前面我们定义的list server,把hasDone为true的数据过滤出来,显示在地图上。接下来我们实现添加旅游清单的功能。2. 添加旅游清单





表单空间我们都用h5原生控件,我们使用angular提供的form模块,具体代码如下:



import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Input } from '@angular/core';
import { Http } from '../service/http';
import { FormBuilder } from '@angular/forms';
import { LocationService } from '../service/list';
// 获取跨域数据的回调
let locationData = null;
window['cb'] = function(data) {
locationData = data && data.results;
}
@Component({
selector: 'app-home',
templateUrl: './index.html',
styleUrls: ['./index.scss']
})
export class HomeComponent implements OnInit {
hasDoneList;
checkoutForm;
constructor(
private formBuilder: FormBuilder,
private locationService: LocationService,
private http: Http,
) {
this.hasDoneList = this.locationService.getItems();
this.checkoutForm = this.formBuilder.group({
name: '',
price: '',
date: ''
});
}
ngOnInit() {
...
}
async searchLocation(v) {
return await this.http.getCors('/place/v2/search',
{ region:v, query: v, callback: 'cb' });
}
onSubmit(customerData) {
if(customerData.name) {
this.searchLocation(customerData.name).then(data => {
this.locationService.addToList({...customerData, location: locationData[0].location, hasDone: false})
});
} else {
alert('请填写旅游地点!');
return
}
this.checkoutForm.reset();
}
onReset() {
this.checkoutForm.reset();
}
}
// html
<div class="home-wrap">
<section class="content">
<div class="has-done">
<div class="title">我已去过:</div>
<div class="visit-list">
<button
*ngFor="let item of hasDoneList"
class="has-btn"
mat-raised-button
[matTooltip]="item.desc"
aria-label="按钮当聚焦或者经过时展示工具提示框">
{{ item.name }}
</button>
</div>
</div>
<div class="has-done">
<div class="title">未来规划:</div>
<div class="future-list">
<form [formGroup]="checkoutForm">
<div class="form-control">
<label>地点:</label>
<input type="text" formControlName="name">
</div>
<div class="form-control">
<label>预算:</label>
<input type="number" formControlName="price">
</div>
<div class="form-control">
<label>日期:</label>
<input type="date" formControlName="date">
</div>
<div class="form-control">
<button mat-raised-button color="primary" class="submit-btn" type="submit" (click)="onSubmit(checkoutForm.value)">提交</button>
<button mat-raised-button color="accent" class="reset-btn" (click)="onReset()">重置</button>
</div>
</form>
</div>
</div>
</section>
<section class="map-wrap" id="js_hover_map"></section>
</div>
复制代码

我们使用angular提供的FormBuilder来处理表单数据,这里需要注意,我们在提交表单的时候,需要先调用百度地图的api去生成经纬度数据,之后一起添加到清单,这样做的目的是要想画路线图,我们需要给百度地图api提供经纬度数据。还有一点,由于访问涉及到跨域,我们要定义jsonp的回调,来拿到数据,如下:

let locationData = null;
window['cb'] = function(data) {
locationData = data && data.results;
}
复制代码

locationService的addToList方法会将数据添加到清单,并存储到storage中。如果想了解完整代码,欢迎在我的github上查看。

接下来看看我的大陆页面,其实涉及的难点不是很多,主要是根据hasDone为true或false去显示不同的样式。





代码如下:

// html
<div class="detail">
<h1>新大陆</h1>
<div class="position-list">
<div class="position-item" *ngFor="let item of list">
<span class="is-new" *ngIf="!item.hasDone">新</span>
<span class="title">{{item.name}}</span>
<span class="date">{{item.date}}</span>
<span class="desc">{{item.desc}}</span>
<span class="price">预算:{{item.price}}</span>
</div>
</div>
</div>
// ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Input } from '@angular/core';
import { LocationService } from '../service/list';
@Component({
selector: 'app-new-map',
templateUrl: './index.html',
styleUrls: ['./index.scss']
})
export class NewMapComponent implements OnInit {
@Input() product; // 指定product值从父组件中传递
list;
constructor(
private route: ActivatedRoute,
private locationService: LocationService
) {
this.list = locationService.getItems();
}
editItem(item) {
}
ngOnInit() {
this.route.paramMap.subscribe(params => {
// this.product = products[+params.get('productId')];
});
}
}
复制代码

总结

该项目是基于angular8的实战入门项目,涉及到部分高级技巧以及百度地图,jsonp跨域的知识,大家有不懂的可以相互交流,我也会定期分享一些企业中常用的核心技术。

未完善的部分: 添加清单时,如果添了不符合规范的地址或者百度地图查不到的地址,因该出现错误提示,这块会在后期优化。

好啦,文章篇幅比较多,大致项目基本完成,如果想查看实际项目效果,请移步基于angular8和百度地图API开发旅游清单项目

最后,更多技术优质文章,技术资料,欢迎微信搜索 趣谈前端 .



发布于: 2020 年 12 月 14 日阅读数: 57
用户头像

徐小夕

关注

前端架构师,致力于前端工程化,可视化 2020.08.08 加入

公众号《趣谈前端》.喜欢coding, 开源, 擅长领域react, vue, nodejs, 小程序, 小游戏, 热爱旅游, 健身, 微信Mr_xuxiaoxi

评论

发布
暂无评论
使用Angular8和百度地图api开发《旅游清单》