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

简介: 本文的目的是通过一步步实现一个旅游清单项目,让大家快速入门Angular8以及百度地图API。我们将收获:a. Angular8基本用法,架构a. 使用百度地图API实现自己的地图应用a. 解决调用百度地图API时的跨域问题a. 对localStorage进行基础封装,进行数据持久化a. material UI的使用


前言:


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


  1. Angular8基本用法,架构


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


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


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


  1. 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. 根据以上架构,建立对应目录文件


  1. 启动服务


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. 模版语法


  1. 数据指令


  1. 属性绑定


  1. 事件绑定


案例如下:


<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. 模块


Angular 定义了 NgModule,NgModule 为一个组件集声明了编译的上下文环境,它专注于某个应用领域、某个工作流或一组紧密相关的能力,每个 Angular 应用都有一个根模块,通常命名为 AppModule。根模块提供了用来启动应用的引导机制。 一个应用通常会包含很多功能模块。


  1. 组件


每个 Angular 应用都至少有一个组件,也就是根组件,它会把组件树和页面中的 DOM 连接起来。 每个组件都会定义一个类,其中包含应用的数据和逻辑,并与一个 HTML 模板相关联,该模板定义了一个供目标环境下显示的视图 比如:


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. 服务与依赖注入


对于与特定视图无关并希望跨组件共享的数据或逻辑,可以创建服务类。 服务类的定义通常紧跟在 “@Injectable()” 装饰器之后。该装饰器提供的元数据可以让你的服务作为依赖被注入到客户组件中。例如:


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


Angular 的 Router 模块提供了一个服务,它可以让你定义在应用的各个不同状态和视图层次结构之间导航时要使用的路径。如下:


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. 添加旅游清单



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开发旅游清单项目



目录
相关文章
|
7天前
|
安全 搜索推荐 数据挖掘
虾皮店铺商品API接口的开发、运用与收益
虾皮(Shopee)作为东南亚领先的电商平台,通过开放API接口为商家和开发者提供了全面的数据支持。本文详细介绍虾皮店铺商品API的开发与运用,涵盖注册认证、API文档解读、请求参数设置、签名生成、HTTP请求发送及响应解析等步骤,并提供Python代码示例。API接口广泛应用于电商导购、价格比较、商品推荐、数据分析等场景,带来提升用户体验、增加流量、提高运营效率等收益。开发者需注意API密钥安全、请求频率控制及遵守使用规则,确保接口稳定可靠。虾皮API推动了电商行业的创新与发展。
61 31
|
1天前
|
自然语言处理 搜索推荐 数据挖掘
淘宝商品描述 API 接口的开发、应用与收益
淘宝商品描述API接口的开发与应用涵盖注册成为开发者、了解API规范、选择开发工具及语言(如Python)和实现代码调用。该接口可用于优化电商平台商品展示、同步数据、竞品分析、智能客服及个性化推荐,从而提高销售转化率、降低运营成本并拓展业务机会。通过自动化处理和数据分析,企业能更精准地满足消费者需求,提升竞争力。
21 9
|
5天前
|
监控 搜索推荐 API
京东JD商品详情原数据API接口的开发、运用与收益
京东商品详情API接口是京东开放平台的重要组成部分,通过程序化方式向第三方提供商品详细信息,涵盖名称、价格、库存等。它促进了京东生态系统的建设,提升了数据利用效率,并推动了企业和商家的数字化转型。开发者可通过注册账号、获取密钥、调用接口并解析返回结果来使用该API。应用场景包括电商平台的价格监控、竞品分析、个性化推荐系统开发、移动应用开发及数据整合与共享等。该接口不仅为企业和开发者带来商业价值提升、用户体验优化,还助力数据资产积累,未来应用前景广阔。
27 9
|
9天前
|
存储 搜索推荐 API
拼多多根据ID取商品详情原数据API接口的开发、运用与收益
拼多多作为中国电商市场的重要参与者,通过开放平台提供了丰富的API接口,其中根据ID取商品详情原数据的API接口尤为重要。该接口允许开发者通过编程方式获取商品的详细信息,为电商数据分析、竞品分析、价格监测、商品推荐等多个领域带来了丰富的应用场景和显著的收益。
38 10
|
2天前
|
存储 JSON API
小红书获取笔记详情API接口的开发、应用与收益。
小红书笔记详情API采用Python与Django框架开发,使用MySQL数据库存储数据。接口通过HTTP GET请求获取笔记详情,返回JSON格式数据,包含笔记内容、作者信息、图片链接等。该API应用于小红书APP内笔记展示和互动功能,并支持第三方平台的内容整合与数据分析,提升用户体验与活跃度,促进品牌合作推广,优化平台运营效率,为平台带来显著收益。
21 1
|
8天前
|
JSON 供应链 搜索推荐
淘宝APP分类API接口:开发、运用与收益全解析
淘宝APP作为国内领先的购物平台,拥有丰富的商品资源和庞大的用户群体。分类API接口是实现商品分类管理、查询及个性化推荐的关键工具。通过开发和使用该接口,商家可以构建分类树、进行商品查询与搜索、提供个性化推荐,从而提高销售额、增加商品曝光、提升用户体验并降低运营成本。此外,它还能帮助拓展业务范围,满足用户的多样化需求,推动电商业务的发展和创新。
28 5
|
15天前
|
存储 API 计算机视觉
自学记录HarmonyOS Next Image API 13:图像处理与传输的开发实践
在完成数字版权管理(DRM)项目后,我决定挑战HarmonyOS Next的图像处理功能,学习Image API和SendableImage API。这两个API支持图像加载、编辑、存储及跨设备发送共享。我计划开发一个简单的图像编辑与发送工具,实现图像裁剪、缩放及跨设备共享功能。通过研究,我深刻体会到HarmonyOS的强大设计,未来这些功能可应用于照片编辑、媒体共享等场景。如果你对图像处理感兴趣,不妨一起探索更多高级特性,共同进步。
71 11
|
12天前
|
JSON API 开发者
Lazada 商品评论列表 API 接口:开发、应用与收益
Lazada作为东南亚领先的电商平台,其商品评论数据蕴含丰富信息。通过开发和利用Lazada商品评论列表API接口,企业可深入挖掘这些数据,优化产品、营销和服务,提升客户体验和市场竞争力。该API基于HTTP协议,支持GET、POST等方法,开发者需注册获取API密钥,并选择合适的编程语言(如Python)进行开发。应用场景包括竞品分析、客户反馈处理及精准营销,帮助企业提升销售业绩、降低运营成本并增强品牌声誉。
30 2
|
16天前
|
供应链 搜索推荐 API
1688榜单商品详细信息API接口的开发、应用与收益
1688作为全球知名的B2B电商平台,为企业提供丰富的商品信息和交易机会。为满足企业对数据的需求,1688开发了榜单商品详细信息API接口,帮助企业批量获取商品详情,应用于信息采集、校验、同步与数据分析等领域,提升运营效率、优化库存管理、精准推荐、制定市场策略、降低采购成本并提高客户满意度。该接口通过HTTP请求调用,支持多种应用场景,助力企业在电商领域实现可持续发展。
62 4
|
15天前
|
监控 搜索推荐 API
京东按图搜索京东商品(拍立淘)API接口的开发、应用与收益
京东通过开放商品详情API接口,尤其是按图搜索(拍立淘)API,为开发者、企业和商家提供了创新空间和数据支持。该API基于图像识别技术,允许用户上传图片搜索相似商品,提升购物体验和平台竞争力。开发流程包括注册账号、获取密钥、准备图片、调用API并解析结果。应用场景涵盖电商平台优化、竞品分析、个性化推荐等,为企业带来显著收益,如增加销售额、提高利润空间和优化用户体验。未来,随着数字化转型的深入,该API的应用前景将更加广阔。
61 1