ng2路由

路由要解决的核心问题是通过建立URL和页面的对应关系,使得不同的页面可以用不同的URL表示。在angular中,页面由组件构成,因此URL和页面的对应关系实质上就是URL和组件的对应关系。
建立URL和组件的对应关系可通过路由配置来指定。路由配置包含了多个配置项。最简单的一个配置项包含pathcomponent两个属性,path属性将被angular用来生成一个URL,而component属性则指定了该URL所对应的组件。
在定义了路由配置后,angular路由将以其为依据,来管理应用中的各个组件。

  • 首先,当用户在浏览器上输入URL后,angular将获取该URL并将其解析生成一个UrlTree实例。
  • 其次,在路由配置中寻找并激活与UrlTree实例匹配的配置项。
  • 再次,为配置项中指定的组件创建实例。
  • 最后,将该组件渲染于路由组件的模板中<router-outlet>指令所在位置。

基本用法

要将一个URL所对应的组件在页面中显示出来有三步:定义路由配置、创建根路由模块、添加<router-outlet>指令标签。

路由配置

路由配置是一个Routes类型的数组,数组的每一个元素即为一个路由配置项。

//app.routes.ts
import { Routes } from '@angular/router';
import { ListComponent } from './list/list.component';
import { CollectionComponent } from './collection/collection.component';
export const rootRouterConfig:Routes=[
    //...
    { path:'list',component:ListComponent }, //http://localhost:4200/list
    { path:'collection',component:CollectionComponent } //http://localhost:4200/collection
]

创建根路由模块

根路由模块包含了路由所需的各项服务,是路由工作流程得以正常执行的基础。通过调用RouterModule.forRoot()方法来创建根路由模块,并将其导入到应用的根模块AppModule中。

//app.module.ts
import { ModuleWithProviders } from '@angular/core';
import { RouterModule } from '@angular/router';
import { rootRouterConfig } from './app.routes';
let rootRouterModule:ModuleWithProviders=RouterModule.forRoot(rootRouterConfig);
@NgModule({
    imports:[rootRouterModule],
    //...
})
export class AppModule {}

根路由模块默认提供的路由策略为PathLocationStrategy。该策略要求应用必须设置一个base路径,用于作为前缀来生成和解析URL。设置base路径最简单的方式是在index.html文件中设置<base>元素的href属性。

<head>
    <base href="/">
</head>

添加RouterOutlet指令

RouterOutlet指令的作用是在组件的模板中开辟出一块区域,用于显示URL所对应的组件。angular将模板中使用了<router-outlet>标签的组件统称为路由组件。

<!--app.component.html-->
<main class="main">
    <router-outlet></router-outlet>
</main>

路由策略

路由策略决定angular将使用URL的哪一部分来和路由配置项的path属性进行匹配。angular提供了PathLocationStrategyHashLocationStrategy两种路由策略,分别表示使用URL的path部分和hash部分来进行路由配置。

http://host[:port]/[/path][?query][#hash]

HashLocationStrategy介绍

该策略的原理是利用了浏览器在处理hash部分的两个特性。

  • 浏览器向服务器发送请求时不会带上hash部分的内容。
  • 更改URL的hash部分不会向服务器重新发送请求,这使得在进行跳转的时候不会引发页面的刷新和应用的重新加载。

要使用该策略,只需要在注入路由服务时使用useHash属性进行显式指定即可。

//app.module.ts
@NgModule({
    imports:[RouterModule.forRoot(rootRouterConfig,{useHash:true})],
    //...
})
export class AppModule{}

PathLocationStrategy介绍

该策略使用URL的path部分来进行路由匹配,浏览器会将配置项对应的URL原封不动地发送给服务器。
作为angular的默认路由策略,其最大的优点在于为服务器端渲染提供了可能。要使用PathLocationStrategy路由策略,必须满足三个条件:
一,浏览器需支持H5的history.pushState()方法,正是这一方法使得RouterLink指令在跳转时即便更改了URL的path部分,却依然不会引起页面刷新。
二,需要在服务器进行设置,将应用的所有URL重定向到应用的首页。这是因为该策略所生成的URL在服务器上并不存在与其相对应的文件结构,如果不进行重定向,服务器将返回404错误。
三,需要为应用设置一个base路径,angular将以base路径为前缀来生成和解析URL。这样做的好处是服务器可以根据base路径来区分来自不同应用的请求。
设置base路径有两种方式,一种是设置<base>标签,如果将base路径变为app,那URL也将变为http://localhost:4200/app/list

<base href="/app">

第二种方式是通过向应用注入APP_BASE_HREF变量来实现。

//app.module.ts
import { Component,NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
@NgModule({
    providers:[{provide:APP_BASE_HREF,useValue:'/app'}],
})
export class AppModule {}

如果这两种方式同时使用,第二种具有更高优先级。

路由跳转

当某个事件引发了跳转时,angular会根据跳转时的参数生成一个UrlTree实例来和配置项进行匹配,如果匹配成功则显示相应的组件并将新URL更新在浏览器的地址栏上;如果匹配不成功则报错。

使用指令跳转

指令跳转通过使用RouterLink指令来完成。该指令接收一个链接参数数组,angular将根据该数组来生成UrlTree实例进行跳转。

<!--app/shared/footer.component.html-->
<nav>
    <!--http://localhost:4200/collection-->
    <a [routerLink]="['/location']"><i>收藏</i></a>
    <a [routerLink]="['/llist']"><i>通讯录</i></a>
</nav>

如果不借助RouterLink指令而以纯HTML的方式来定义超链接,则单击超链接后会使得整个页面被重新加载。
RouterLink指令可以被应用在任何HTML元素上。

<button [routerLink]="['/collection']"><i>收藏</i></button>

RouterLink被激活时,还可以通过RouterLinkActive指令为其相应的HTML元素指定CSS类。

/*footer.component.css*/
.active{
    background-color:#3DD689;
}
<!--footer.component.html-->
<nav>
    <a [routerLink]="['/collection']" routerLinkActive="active"><i>收藏</i></a>
</nav>

RouterLinkActive指令除了可以作用于routerLink所在的元素之外,还可以作用于这些元素的任意祖先元素。当该祖先元素下的任意routerLink处于激活状态时,该祖先元素都将获得routerLinkActive指定的CSS类。

<nav routerLinkActive="active">
    <a [routerLink]="['/collection']"><i>收藏</i>
</nav>

使用代码跳转

RouterLink指令仅响应click事件,如果需要响应其他事件或者需要根据运行时的数据动态决定如何跳转,则可以通过调用Router.navigateByUrl()或其兄弟方法Router.navigate()来完成。

//list.component.ts
import { Router } from '@angular/router';
export class ListComponent implements OnInit{
    constructor (private _router:Router){
        setTimeout(()=>{
            _router.navigateByUrl('/collection');
            //或者_router.navigate(['/collection']);
        },1000)
    }
}

Router.navigateByUrl()Router.navigate()方法的底层工作机制基本一致,最终都是通过调用Router.scheduleNavigation()方法来执行跳转。不同的地方在于两个方法指定跳转的目标配置项的方式。Router.navigateByUrl()方法通过一个URL字符串或UrlTree实例来指定。

navigateByUrl(url:string|UrlTree,extras:NavigationExtras={skipLocationChange:false}):Promise<boolean>{
    if(url instanceof UrlTree){
        return this.scheduleNavigation(url,extras);
    }else{
        //解析URL字符串生成相应的UrlTree实例
        const urlTree=this.urlSerializer.parse(url);
        //使用UrlTree实例进行跳转
        return this.scheduleNavigation(urlTree,extras);
    }
}

Router.navigate()方法与RouterLink指令相似,通过链接参数数组来指定。

navigate(commands:any[],extras:NavigationExtras={skipLocationChange:false}):Promise<boolean>{
    //通过链接参数数组生成UrlTree实例
    return this.scheduleNavigation(this.createUrlTree(commands,extras),extras);
}

这两个方法除了可以通过第一个参数来指定目标配置项外,还支持extras参数定义跳转的具体行为。如,如果想在不改变URL的情况下完成跳转,可通过以下代码:

_router.navigateByUrl('/collection',{skipLocationChange:true});

路由参数

Path参数

Path参数是通过解析URL的path部分来获取参数。在定义一个配置项的path属性时,可以使用/字符来对path属性进行分段,如果一个分段以:字符开头,则URL中与该分段进行匹配的部分将作为参数传递到组件中。

//app.routes.ts
export const ContactsAppRoutes:RouterConfig=[
    {path:'detail/:id',component:DetailComponent}
];

只有当URL解析出来的分段数和path属性的分段数一致时,才能匹配到该配置项。下面两个URL无法匹配到该配置项。

http://localhost:4200/detail  //分段数为1
http://localhost:4200/detail/1/segment //分段数为3

给路由参数赋值,除了可以直接在浏览器上输入URL外,还可以通过RouterLink指令或跳转方法来完成。

//angular会将链接参数数组的每一个非对象元素当成一个分段进行拼接
//因此下面的链接参数数组对应的path为`detail/1`
<a [routerLink]="['/detail',1]">
_router.navigate(['/detail',1]);
//或者直接指定path
_router.navigateByUrl('detail/1');

在组件中获取Path参数需要导入ActivatedRoute服务,该服务提供了两种方式,分别适用于不同页面间跳转和同一页面内跳转。
angular应用从一个页面跳转到另一个新的页面,实质上是从一个配置项跳转到另一个配置项。angular除了会为配置项所对应的组件创建实例外,还会为该配置项本身创建一个ActivatedRoute实例来表示该配置项已被激活。该ActivatedRoute实例包含了一个快照(即snapshot属性),记录了从当前URL中解析出来的所有path参数。

//detail.component.ts
//1.导入ActivatedRoute服务
import { ActivatedRoute } from '@angular/router';
//...
export class DetailComponent implements OnInit,OnDestroy{
    contact_id:string;
    constructor(private _activatedRoute:ActivatedRoute){
        console.log('创建DetailCompoent组件实例');
    }
    ngOnInit(){
        //2.通过快照获取Path参数
        this.contact_id=this._activatedRoute.snapshot.params['id'];
        console.log('参数id的值为:'+this.contact_id);
    }
}

在页面跳转时获取参数值的例子:

<!--detail.component.html-->
<div class="detail-contain">
    <a [routerLink]="['']" class="back-to-list">
        <i class="icon-back"></i>所有联系人
    </a>
    <a [routerLink]="['/detail',nextContactId()]" class="back-to-list">
        下一联系人
    </a>
</div>

//detail.component.ts
export class DetailComponent implements OnInits,OnDestroy{
    nextContactId(){
        return parseInt(this.contact_id)+1;
    }
}

点击下一联系人后,URL按照预期变成了http://localhost:4200/detail/2,但页面上显示的仍是原联系人的信息。这是因为angular在处理同一页面内跳转时,不会重新创建组件的实例,所以组件的构造函数和ngOnInit()方法都没有被调用到。虽然angular会将快照中参数id的值更新为2,但没有将这个更新通知到组件。为解决这个问题,ActivedRoute服务提供了一个Observable对象,允许对参数的更新进行订阅。

//detail.component.ts
export class DetailComponent implements OnInit,OnDestroy{
    contact_id:string;
    private sub:any;
    ngOnInit(){
        this.sub=this._activatedRoute.params.subscribe(params=>{
            this.contact_id=params['id'];
            console.log('参数id的值为:'this.contact_id);
            this.getById(this.contact_id);
        });
    }
    ngOnDestroy(){
        //为避免内存泄漏,在组件销毁时应该取消订阅
        this.sub.unsubscribe();
    }
}

Query参数

通过解析URL的query部分也可以获取参数值。由于URL的query部分不用于和匹配项进行匹配,因此每一个配置项可以拥有任意多个查询参数。
Query参数也可以通过RouterLink指令或跳转方法来赋值。

//http://localhost:4200/list?limit=5
<a [routerLink]="['/list']" [queryParams]="{limit:5}">

this._router.navigate(['/list'],{queryParams:{limit:5}});
this._router.navigateByUrl('/list?limit=5');

Query参数的获取,需要借助于ActivatedRoute服务提供的Observable类型对象queryParams来完成。

//list.component.ts
import { ActivateRoute } from '@angular/router';
export class ListComponent implements OnInit,OnDestroy{
    contacts:any[];
    private limit:number;
    private sub:any;
    constructor(private _activatedRoute:ActivatedRoute){}
    ngOnInit(){
        this.getContacts();
    }
    ngOnDestroy(){
        this.sub.unsubscribe();
    }
    getContacts(){
        this.sub=this._activatedRoute.queryParams.subscribe(params=>{
            this.limit=parseInt(params['limit']);
            if(this.limit){
                this.contacts.splice(this.limit);
            }
        });
    }
}

Matrix参数

页面上所有组件都可以访问Query参数的内容,如果想精准地向某一个组件传递参数,则需要使用Matrix参数。

子路由和附属Outlet

子路由

基本用法

一个组件可以被嵌入到另外一个组件中,从而建立起组件之间的多级嵌套关系。angular也允许一个路由组件被嵌入到另一个路由组件中,从而建立路由的多级嵌套关系。

//app.routes.ts
export const rootRouterConfig:Routes=[
    {path:'detail/:id',component:DetailComponent,
        children:[
            {path:'',component:Annotation}, //http://localhost:4200/detail/:id
            {path:'album',component:Album} //http://localhost:4200/detail/:id/album
        ]
    }
]
Matrix参数

Matrix参数通过在链接参数数组中插入一个对象来进行赋值。

<a [routerLink]="['/detail',this.contact_id,{after:'2017-01-01',before:'2017-01-01'},
    'album',{after:'2017-02-02',before:'2017-02-02'}]">Link</a>

angular会将该对象的属性转化为以;为分隔符的键值对,拼接到与该对象左边最近的URL分段上。

http://localhost:4200/detail/6;after=2017-01-01;before=2017-01-01/album;
    after=2017-02-02;before=2017-02-02

这中在一个URL分段内使用;分隔键值对的方式称为Matrix URI。每一个URL分段都可以拥有任意多个键值对,每个键值对只为其所在分段服务。Matrix参数的获取方式和Path参数一样,可以通过ActivatedRoute服务提供的快照和Observable对象两种方式来获取。

附属Outlet

angular允许一个路由组件包含多个Outlet,从而可以在一个路由组件中同时显示多个组件。其中,主要Outlet有且仅有一个,附属Outlet可以有任意多个,各个附属Outlet通过不同的命名加以区分。每一个Outlet均可以通过路由配置来指定其可以显示的组件,这使得angular可以灵活地对各个组件进行组合,从而满足不同场景的需求。

<!--detail.component.html-->
<div class="detail-contain">
    <router-outlet></router-outlet>
    <router-outlet name="aux"></router-outlet>
</div>

//app.routes.ts
export const rootRouterConfig:Routes=[
    { path:'detail/:id',component:DetailComponent,
      children:[
          //主要Outlet
          {path:'',component:AnnotationComponent},
          {path:'album',component:AlbumComponent},
          //附属Outlet
          {path:'annotation',component:AnnotationComponent,outlet:'aux'},
          {path:'album',component:AlbumComponent,outlet:'aux'}
       ]  
    }
];

id=1为例,下表列出了各种可能的组合及其相应的URL和链接参数数组。在链接参数数组中,如果一个元素包含了outlets属性,则表示该元素将用于为各个Outlet进行配置项匹配。

主要Outlet和附属Outlet各组合的定义及其对应URL

路由拦截

angular的路由拦截,允许在从一个配置项跳转到另外一个配置项之前执行指定的逻辑,并根据执行的结果来决定是否进行跳转。angular提供了5类路由拦截:

  • CanActivate,激活拦截
  • CanActivateChild,用于控制是否允许激活子路由配置项
  • CanDeactivate,反激活拦截
  • Resolve,数据预加载拦截
  • CanLoad,模块加载拦截

激活拦截与反激活拦截

激活拦截与反激活拦截用于控制是否可以激活或反激活目标配置项。


激活拦截与反激活拦截
CanActivate

假设需要根据用户的登录状态来决定能否访问联系人编辑页。要实现这个功能,可以通过为联系人编辑页添加一个判断登录状态的CanActivate拦截来实现。
首先,通过实现CanActivate接口创建拦截服务。该接口只包含了一个canActivate()方法,最简单的情况,当该方法返回true时,表示允许通过CanActivate拦截;当返回false时,则表示不允许通过CanActivate拦截,对目标配置项不予激活。

//can-activate-guard.ts
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class CanActivateGuard implements CanActivate{
    canActivate(){
        if(/*用户已登录*/){
            return true;
        }else{
            return false;
        }
    }
}

然后,在目标配置项中指定上述创建的服务作为其CanActivate拦截服务。

//app.routes.ts
import { CanActivateGuard } from '../service/can-activate-guard';
export const rootRouterConfig:Routes=[{
    path:'operate/id/:id',
    component:OperateComponent,
    canActivate:[CanActivateGuard]
}];

最后,将该服务注入到应用中。

//app.module.ts
import { CanActivateGuard } from '../services/can-activate-guard';
@NgModule({
    providers:[CanActivateGuard]
})
export class AppModule{}

除了返回布尔值,canActivate()方法还可以返回一个Observable对象,当该对象触发true时,表示允许通过拦截;触发false时则表示不允许通过。这个特性使得CanActivate拦截可以根据异步处理的结果来进行判断。

//can-activate-guard.ts
//...
@Injectable()
export class CanActivateGuard implements CanActivate{
    canActivate(){
        return new Observable<boolean>(observer=>{
            observer.next(true);
            observer.complete();
        })
    }
}

此外,angular还会给canActivate()方法传递两个参数:

  • ActivatedRouteSnapshot,表示所要激活的目标配置项,可以通过它访问配置项的相关信息。
  • RouterStateSnapshot,表示应用当前所处的路由状态,其包含了当前所需的所有配置项。
//can-activate-guard.ts
import { CanActivate,ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router';
@Injectable()
export class CanActivateGuard implements CanActivate{
        canActivate(route:ActivatedRouteSnapshot,state:RouterStateSnapshot){
        //获取配置项信息
        console.log(route.routeConfig);
        //RouterStateSnapshot按照路由配置中的定义,
        //将所需的配置项以树形结构方式组织起来
        console.log(state.root);
        return true;
    }
}
CanActivateChild

CanActivateChild拦截用于控制是否允许激活子路由配置项,其用法与CanActivate拦截相似。

CanDeactivate

使用CanDeactivate拦截的用法可分三步:
首先,通过实现CanDeactivate接口创建拦截服务。该接口只包含了一个canDeactivate()方法,该方法除了第一个参数为目标配置项对应组件的实例外,其余使用方式与canActivate()方法一样。

//can-deactivate-guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate,ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router';
@Injectable()
export class CanDeactivateGuard implements CanDeactivate<any>{
    canDeactivate(component:any,route:ActivatedRouteSnapshot,state:
        RouterStateSnapshot
    ){
        if(component.isModified()){
            return true;
        }else{
            return false;
        }
    }
}

然后,在目标配置项中指定该服务作为其CanDeactivate拦截服务。

//app.routes.ts
import { CanDeactivateGuard } from "../services/can-deactivate-guard";
export const rootRouterConfig:Routes=[{
    path:"operate/id/:id",
    component:Operate,
    canActivate:[CanActivaateGuard],
    canDeactivate:[CanDeactivateGuard]
}];

最后,将该服务注入到应用中。

//app.module.ts
import { CanDeactivateGuard } from "../services/can-deactivate-guard";
@NgModule({
    providers:[CanDeactivateGuard]
})
export class AppModule {}

数据预加载拦截

数据预加载拦截适用于对数据进行预加载,当确认数据加载成功后,再激活目标配置项。

数据预加载拦截

预加载过程可分四步:
首先,通过实现Resolve<T>泛型接口创建拦截服务。该服务只有一个resolve()方法,用于执行数据预加载逻辑。该方法可以直接将数据返回,在异步情况下也可以通过Observable对象触发。值得注意的是,所返回的任何数据都将存放于配置项的data参数部分,如果没有预加载到期望的数据,只能通过代码跳转的方式来达到不激活目标配置项的目的。

//resolve-guard.ts
import { Injectable } from '@angualr/core';
import { Router,Resolve,ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router';
import { ContactService } from '../services/contact.service';
@Injectable()
export class ResolveGuard implements Resolve<any>{
    contacts:{};
    constructor(private _router:Router,private _contactService:ContactService){}
    resolve(route:ActivatedRouteSnapshot,state:RouterStateSnapshot){
        //返回Observable对象
        return this._contactService.getContactById(route.params['id']).map(
            res=>{
                if(res){return res;}
                else{
                    //预加载失败,代码跳转至其他配置项
                    this._router.navigate(['/list']);
                }
            }
        );
    }
}

其次,在目标配置项中指定该服务作为其Resolve拦截服务。

//app.routes.ts
import { ResolveGuard } from "../services/resolve-guard";
export const rootRouterConfig:Routes=[{
    path:"operate/id/:id",
    component:Operate,
    canActivate:[CanActivateGuard],
    resolve:{
        contact:ResolveGuard
    }
}];

然后,将该服务注入到应用中。

//app.module.ts
import { ResolveGuard } from "../services/resolve-guard";
@NgModule({
    providers:[ResolveGuard]
})
export class AppModule {}

最后,在目标配置项所指定的组件中访问预加载的数据。

//operate.component.ts
export class OperateComponent implements OnInit{
    constructor(private _activatedRoute:ActivatedRoute){}
    ngOnInit(){
        this._activatedRoute.data.subscribe(data=>{
            console.log(data.contact);
        });
    }
}

模块的延迟加载

延迟加载实现

与根模块需要初始化各项路由服务不同,特性模块仅需要对其路由配置进行解析,因此子路由模块通过调用RouterModule.forChild()方法来创建。

//operate.module.ts
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { Routes,RouterModule } from '@angular/router';
import { OperateComponent } from '../widget/operate.component';
import { ContactService } from '../services/contact.service';
const operateRoutes:Routes=[
    {path:"id/:id",component:OperateComponent},
    {path:"isAdd/:isAdd",component:OperateComponent}
];
@NgModule({
    imports:[BrowserModule,FormsModule,RouterModule.forChild(operateRoutes)],
    declarations:[OperateComponent],
    providers:[ContactService]
})
export class OperateModule {}

此后OperateComponent组件便不再需要在根组件AppModule中导入。
最后,需要对根模块的路由配置进行修改,通过配置项的loadChildren属性来指定需要进行延迟加载的模块。

//app.routes.ts
export const rootRouterConfig:Routes=[
    //OperateComponent组件的配置项已在OperateModule模块中定义,故在此删除
    //{path:"id/:id",component:OperateComponent},
    //{path:"isAdd/:isAdd",component:OperateComponent}
    {path:'operate',loadChildren:'app/router/operate.module.ts#OperateModule'}
];

模块加载拦截

默认情况下,如果URL匹配到延迟加载的配置项,相应的特性模块便会被加载进来。如果想动态判断是否对该模块进行加载,可以使用CanLoad拦截。
CanLoad拦截的用法和CanActivate等其他拦截类似,首先需要实现CanLoad接口来创建拦截服务。由于在触发CanLoad拦截时,相应的特性模块还未被加载,因此能传递给canLoad()方法的只有延迟加载配置的信息。

//can-load-guard.ts
import { Injectable } from '@angular/core';
import { CanLoad,Route } from '@angular/router';
@Injectable()
export class CanLoadGuard implements CanLoad{
    canLoad(route:Route){
        //route参数为延迟加载配置项
        console.log(route.path);//输出operate
        if(/*允许加载*/){
            return true;
        }else{
            return false;
        }
    }
}

//app.routes.ts
import { CanLoadGuard } from '../services/can-load-guard';
export const rootRouterConfig:Routes=[{
    path:'operate',
    loadChildren:'app/router/operate.module.ts#OperateModule',
    canLoad:[CanLoadGuard]
}];

//app.module.ts
import { CanLoadGuard } from '../services/can-load-guard';
@NgModule({
    providers:[CanLoadGuard]
})
export class AppModule {}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容

  • 一:路由基础 什么是路由: 在web开发中,路由的概念由来已久,简而言之,就是利用URL的唯一性来指定特定的事物,...
    真的稻城阅读 6,017评论 2 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • Angular2中包含了一个路由框架,我们只需要定义一个个的路径和它对应的组件,然后在页面跳转时也是用Angula...
    小孩真笨阅读 461评论 0 0
  • 前言 随着用户的需求越来越多,对App的用户体验也变的要求越来越高。为了更好的应对各种需求,开发人员从软件工程的角...
    一缕殇流化隐半边冰霜阅读 87,102评论 214 1,098
  • 我们曾渴望命运的波澜,情爱的纠缠,事业的奋进,挥斥方遒的豪迈,但终究还是平静的生活更耐人寻味,能抚慰躁动的内心。—...
    断林阅读 501评论 3 3