关于前端:旅游清单一步搭建Angular助力你的踏春计划

春天的脚步愈发邻近,置信很多小伙伴曾经开始布局本人的踏春打算了,无论是观赏名胜古迹,还是走访风土人文,你都须要提前准备一份游览清单!有了这款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应该包含:

  1. 模块Angular 定义了 NgModule,NgModule 为一个组件集申明了编译的上下文环境,它专一于某个应用领域、某个工作流或一组严密相干的能力,每个 Angular 利用都有一个根模块,通常命名为 AppModule。根模块提供了用来启动利用的疏导机制。 一个利用通常会蕴含很多功能模块。
  2. 组件每个 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的数据过滤出来,显示在地图上。 接下来咱们实现增加游览清单的性能。

  1. 增加游览清单
    表单空间咱们都用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…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理