春天的脚步愈发邻近,置信很多小伙伴曾经开始布局本人的踏春打算了,无论是观赏名胜古迹,还是走访风土人文,你都须要提前准备一份游览清单!有了这款 Angular 游览打算利用,从地点到估算,它都能帮忙你创立本人的踏春脚印!
踏春正过后,马上追随本文,从云平台创立利用模版,本地编写《游览清单》我的项目,到一键部署上线开始,一步一步创立本人的踏春打算,让一场说走就走的旅行,从当下产生吧!
一、通过云开发平台疾速创立初始化利用
1. 创立相干利用模版请参考链接:https://developer.aliyun.com/…
2. 实现创立后就能够在 github 中查看到新增的 Angular 仓库
二、本地编写《游览清单》我的项目
1. 将利用模版克隆到本地
• 首先假设你曾经装置了 Git、node,没有装置请移步 node 官网进行装置。克隆我的项目:
git clone + 我的项目地址
• 进入我的项目文件
cd Angular
• 切换到 feature/1.0.0 分支上
git checkout feature/1.0.0
• 应用一下命令全局装置 Angular CLI:
npm install -g @angular/cli
• 装置依赖包
npm install
• 启动服务
ng serve
这里关上浏览器 4200 端口,并呈现默认页面。
2. 架构与成果预览
•《游览清单》我的项目架构
• 其中 components 为组件寄存区,config 为公共配置区,home/newMap 为页面区,mock 为模仿数据区,service 为利用所需服务区,如 http 服务,存储服务,custom.modules 文件为第三方组件安置区。
• 成果预览
增加游览布局之后:
3. 我的项目编写
• 引入地图 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>
至此,我的项目的根本筹备工作曾经做好了,上面让咱们先聊一聊 angular。
4.angular 根本语法和架构
• 根本语法
和 vue 相似,ng 的根本语法如下:* 模版语法
* 数据指令
* 属性绑定
* 事件绑定
案例如下:
<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('你好')
}
}
• 根本架构
采纳 angular 官网提供的架构图:
咱们晓得,一个残缺的 angular 应该包含:
- 模块 Angular 定义了 NgModule,NgModule 为一个组件集申明了编译的上下文环境,它专一于某个应用领域、某个工作流或一组严密相干的能力,每个 Angular 利用都有一个根模块,通常命名为 AppModule。根模块提供了用来启动利用的疏导机制。一个利用通常会蕴含很多功能模块。
- 组件每个 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() {}
}
• 服务于依赖注入
对于与特定视图无关并心愿跨组件共享的数据或逻辑,能够创立服务类。服务类的定义通常紧跟在“@Injectable()”装璜器之后。该装璜器提供的元数据能够让你的服务作为依赖被注入到客户组件中。例如:
import {Injectable} from '@angular/core';
@Injectable({providedIn: 'root'})
export class Storage {}
• 路由
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 {}
• 百度地图 api 及跨域问题解决
咱们进入百度地图官网后,去控制台创立一个利用,此时会生成对应的利用 ak,如下:
本地调试时将 referer 写成 * 即可,然而咱们用 ng 的 http 或者 fetch 去申请 api 接口时仍会呈现跨域,在网上收集了各种材料,都没有达到成果,咱们这里应用 jquery 的 $.getScript(url), 联合 jsonp 回调,即可解决该问题。
所以先装置以下 jquery:
npm install jquery
解决方案如下:
• 封装 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'});
}
至此,利用几个次要的突破点曾经解决好了,接下来咱们来开发我的项目的外围页面和组件。
• 按需引入 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() {}
}
其次,页面头部组件用到了 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)
}
}
实现起来比较简单,这里就不多阐明了。接下来咱们看看首页外围性能的实现:
• 地图初始化路线图:
代码如下:
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 的数据过滤出来,显示在地图上。接下来咱们实现增加游览清单的性能。
- 增加游览清单
表单空间咱们都用 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')];
});
}
}
大抵我的项目根本实现,如果想查看理论我的项目成果,可参考原我的项目作者的代码:
https://github.com/MrXujiang/…
三、云端一键部署上线利用
1. 上传代码
git add .
git commit -m '增加你的正文'
git push
2. 在日常环境部署
一键进行利用部署。在利用详情页面点击日常环境的「部署」按钮进行一键部署,部署状态变成绿色已部署当前能够点击拜访部署网站查看成果。
3. 配置自定义域名在线上环境上线
• 配置线上环境自定义域名。在性能开发验证实现后要在线上环境进行部署,在线上环境的「部署配置」-「自定义域名」中填写本人的域名。例如咱们增加一个二级域名 company.workbench.fun 来绑定咱们部署的前端利用。而后复制自定义域名下方的 API 网关地址对增加的二级域名进行 CNAME 配置。
• 配置 CNAME 地址。复制好 API 网关域名地址后,来到你本人的域名治理平台(此示例中的域名治理是阿里云的域名治理控制台,请去本人的域名控制台操作)。增加记录的「记录类型」抉择「CNAME」,在「主机记录」中输出你要创立的二级域名,这里咱们输出「company」,在「记录值」中粘贴咱们之前复制的 API 网关域名地址,「TTL」保留默认值或者设置一个你认为适合的值即可。
• 在线上环境部署上线。回到云开发平台的利用详情页面,依照部署的操作,点击线上环境的「部署按钮」,部署实现当前就在你自定义的域名进行了上线。CNAME 失效之后,咱们输出 company.workbench.fun(示例网址)能够关上部署的页面。至此,如何部署一个利用到线上环境,如何绑定本人的域名来拜访一个线上的利用就实现了,连忙部署本人的利用到线上环境,用本人的域名玩起来吧;)
一键创立 angular 利用模版链接:https://workbench.aliyun.com/…
参考文献:https://juejin.cn/post/684490…