使用ionic2开发一个登录功能

https://www.cnblogs.com/madyina/p/5970814.html

服务的采用Asp.net API实现,数据库用的sqlite,具体实现请看:源代码

唯一需要说明的是跨域问题

跨域代码:

复制代码
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type,Accept,Authorization" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE" />
      </customHeaders>
    </httpProtocol>
复制代码

配置允许Content-Type,Accept,Authorization三种头。

API功能列表:

http://localhost:1856/api/UserLogin

用户登录并返回用户信息

http://localhost:1856/api/UserInfo/id

根据ID查找用户信息

http://localhost:1856/api/UserReg

注册

系统目录规划说明:

clip_image001

详细开发过程

1. 新建登录页面
定位到src\pages目录下,运行命令:

ionic g page login

2. 添加一个Tab页显示登录页
在ionic2.1下面变得容易了,在tabs.ts里面引入并且制定tab,然后在前台增加这个tab即可:

clip_image002

<ion-tab [root]="tab4Root" tabTitle="me" tabIcon="person"></ion-tab>

图标可以采用系统也可以自定义。

然后运行项目:
ionic serve

结果发现点击me不会出现新建的页面?

打开app下面的app.module.ts发现系统所有页面都需要在这里配置一遍才能使用。

clip_image003

这里说明ionic2开发的第一个规则:

规则一:所有页面均需提前在app.module.ts中配置。

然后等待几秒或者重新启动一下就能看到页面。注意:我这里新建的页面名叫login但是我把页面对应的ts类名改为LoginPage了,所以在引用时用的是LoginPage。

3. 写Login的前台布局:
代码

复制代码
<ion-content padding>
    <form [formGroup]="loginForm" >
        <ion-item>
            <ion-label>Username</ion-label>
            <ion-input type="text" formControlName="LoginID"></ion-input>
        </ion-item>
        <p *ngIf="!loginForm.controls.LoginID.valid && loginForm.controls.LoginID.touched" color="danger">LoginID must is email.</p>
        <!-- <p *ngIf="loginForm.controls.LoginID.valid && loginForm.controls.LoginID.touched" secondary> LoginID is good</p>-->

        <ion-item>
            <ion-label>Password</ion-label>
            <ion-input type="password" formControlName="LoginPwd"></ion-input>
        </ion-item>
        <button ion-button full (click)="login(loginForm.value, $event)" [disabled]="!loginForm.valid">Dark</button>
        <button ion-button full (click)="signup()">Create Account</button>
    </form>
</ion-content>
复制代码

解释:

<form [formGroup]="loginForm" >:声明一个表单对象,访问此表单元素需要。

<ion-item>:代表一个条目,一般是一行

<ion-label>:文本标签

<ion-input type="text" formControlName="LoginID">:文本框标签,数据源名称LoginID

<button ion-button full (click)=:按钮标签,full表示占满当前行,后面是单击事件

(click)="login(loginForm.value, $event)":传入参数为表单数据源对象

[disabled]="!loginForm.valid":表单验证通过之后才显示

光写前台是出不了效果的,因为有表单验证数据源

4. Login后台
称之为后台不太准确,但很形象:ts控制html

因为要使用验证和表单对象所以需要引入:
import { FormBuilder, Validators } from '@angular/forms';

同时在构造函数中声明:

private formBuilder: FormBuilder,

初始化表单数据源:

复制代码
loginForm = this.formBuilder.group({

'LoginID': ['[email protected]', [Validators.required, Validators.minLength(4),emailValidator]],

'LoginPwd': ['123456', [Validators.required, Validators.minLength(4)]]

});
复制代码

第一个参数是默认值,一般为空,此处为调试时懒得输入,后面是验证规则,Email的验证我使用的自定义验证规则,把验证规则抽出到单独的文件中去了,此处是支持写正则表达式的。

到了这一步,页面应该能显示出来了。

5. 抽离验证类
在Providers下面新建一个validator.ts,引入表单资源,编写公用验证类

代码如下:

复制代码
import {FormBuilder,FormControl,Validators,AbstractControl } from '@angular/forms';

export function emailValidator(control: FormControl): { [s: string]: boolean } {
        if (!control.value.match(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)) {
                return { invalidEmail: true };
        }
}

export function nicknameValidator(control: FormControl): { [s: string]: boolean } {
        if (!control.value.match(/^[(\u4e00-\u9fa5)0-9a-zA-Z\_\s@]+$/)) {
                return { invalidNickname: true };
        }
}
复制代码

6. 抽离实体类:

Model文件夹下面新建一个UserInfoData.ts文件,新建模型类,可参考一般model的写法,当然也可以在这里初始化一些数据源什么的。

代码如下:

复制代码
export class UserInfoData {

    ID:number;
    LoginID:string;
    LoginPwd:string;
    RealName:string;
    FaceImg:string;
    Sex:string;
    UserToken:string;
    Birthday:Date;
    InDate:Date;
}
复制代码

7. 抽离本地存储类:
由于项目中用到临时数据存储,所以需要抽离出一个公用的本地存储访问服务:

StorageService.ts操作的是html5的localStorage。代码如下

复制代码
import { Injectable } from '@angular/core';

@Injectable()
export class StorageService {

    constructor() { }

    write(key: string, value: any) {
        if (value) {
            value = JSON.stringify(value);
        }
        localStorage.setItem(key, value);
    }

    read<T>(key: string): T {
        let value: string = localStorage.getItem(key);

        if (value && value != "undefined" && value != "null") {
            return <T>JSON.parse(value);
        }

        return null;
    }

    remove(key: string) {
        localStorage.removeItem(key);
    }

    clear() {
        sessionStorage.clear();
    }
}
复制代码

8. 抽离http请求类:

其实一般可以不用抽离的,但是本项目中用到API访问控制,思路是除了公开API,其他的都需要用token去访问,而这个token是和用户挂钩的,在注册的成功就生成了,调用系统功能时每个人必须带上自己的token,否则API返回401.

实现思路是:登录时返回用户信息,其中包含token存储在本地,以后调用时从本地取出,连同请求一起发给服务器。要实现4种请求

httpGetWithAuth、httpGetNoAuth、httpPostNoAuth、httpPostWithAuth

代码如下:

复制代码
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { StorageService } from "./StorageService";
import { UserInfoData } from "./../model/UserInfoData";

@Injectable()
export class HttpService {
    myInfoLocal: any;
    local: Storage;
    constructor(
        private http: Http,
        private storageService: StorageService) {
        //this.local = new Storage(LocalStorage);
    }

    public httpGetWithAuth(url: string) {
        let user = this.storageService.read<UserInfoData>('UserInfo');
        var headers = new Headers();
        headers.append('Content-Type', 'application/json');
        headers.append('Authorization', user.ID + '-' + user.UserToken);
        let options = new RequestOptions({ headers: headers });
        return this.http.get(url, options).toPromise()
            .then(res => res.json())
            .catch(err => {
                this.handleError(err);
            });
    }
    public httpGetNoAuth(url: string) {

        var headers = new Headers();
        headers.append('Content-Type', 'application/json');
        let options = new RequestOptions({ headers: headers });
        return this.http.get(url, options).toPromise()
            .then(res => res.json())
            .catch(err => {
                this.handleError(err);
            });
    }
    public httpPostNoAuth(url: string, body: any) {
        var headers = new Headers();
        headers.append('Content-Type', 'application/json');
        let options = new RequestOptions({ headers: headers });
        return this.http.post(url, body, options).toPromise()
            .then(res => res.json())
            .catch(err => {
                this.handleError(err);
            });
    }
    // public httpPostWithAuth(body: any, url: string) {

    //     return this.myInfoLocal = this.local.getJson('UserInfo')
    //         .then((result) => {
    //             var headers = new Headers();
    //             headers.append('Content-Type', 'application/json');
    //             headers.append('Authorization', result.ID + '-' + result.UserToken);
    //             let options = new RequestOptions({ headers: headers });
    //             return this.http.post(url, body, options).toPromise();
    //         });
    // }


    private handleError(error: Response) {
        console.log(error);
        return Observable.throw(error.json().error || 'Server Error');
    }
}
复制代码

这里需要说明一下:

本例中公开API是可以随意调用的,但UserInfo API是受保护的,即只有认证过的用户才能调用。

认证思路是注册时,就为每个用户分配一个token,登陆时,拿下来存储在本地,调用受保护API时,把这个token一并发送给服务端验证,这样做的好处是即使这个token泄露了,作为运营商很容易查出来,重新为他生一个即可。

9. 抽离一个数据访问

这里可以实现类似数据访问的功能,调用httpservice实现数据访问:

复制代码
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';

import { HttpService } from "./HttpService";
import { StorageService } from "./StorageService";

@Injectable()
export class UserInfoService {
    API_URL = "http://localhost:1856/api";
    constructor(
        private http: Http,
        private httpService: HttpService,
        private storageService:StorageService) { }

    login(user) {
        var url = this.API_URL + "/UserLogin";
        return this.httpService.httpPostNoAuth(url, user);
    }



    GetUserInfo(id:number) {
        var url = this.API_URL + "/UserInfo/"+id;
        return this.httpService.httpGetWithAuth(url);
    }
}
复制代码

10. 然后实现登陆功能

复制代码
import { Component } from '@angular/core';
import { NavController, ToastController } from 'ionic-angular';
import { FormBuilder, Validators } from '@angular/forms';
import 'rxjs/add/operator/toPromise';

import { UserInfoService } from "./../../providers/UserInfoService";
import { StorageService } from "./../../providers/StorageService";

import { UserInfoData } from "./../../model/UserInfoData";
import { emailValidator } from './../../providers/validator'

import { MyinfoPage } from '../myinfo/myinfo';


@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
  providers: [UserInfoService]
})
export class LoginPage {

  local: Storage;
  constructor(
    public navCtrl: NavController,
    private formBuilder: FormBuilder,
    public toastCtrl: ToastController,
    private userInfoService: UserInfoService,
    private storageService: StorageService) { }

  loginForm = this.formBuilder.group({
    //'LoginID': ['[email protected]', [Validators.required, Validators.pattern('^([a-zA-Z0-9_.]*)((@[a-zA-Z0-9_.]*)\.([a-zA-Z]{2}|[a-zA-Z]{3}))$')]],// 第一个参数是默认值
    'LoginID': ['[email protected]', [Validators.required, Validators.minLength(4), emailValidator]],// 第一个参数是默认值
    'LoginPwd': ['123456', [Validators.required, Validators.minLength(4)]]
  });

  ionViewDidLoad() {
    console.log('Hello Login Page');
  }

  login(user, _event) {
    _event.preventDefault();//该方法将通知 Web 浏览器不要执行与事件关联的默认动作
    this.userInfoService.login(user).then(data => {

      alert(JSON.stringify(data));
      if (data.Result.ID > 0)//登录成功
      {
        this.storageService.write('UserInfo', data.Result);
        //测试写缓存
        //let ss = this.storageService.read<UserInfoData>('UserInfo');
        //console.log(ss.UserToken);
        //传参
        this.navCtrl.push(MyinfoPage, { item: data.Result.ID });
      }
      else {
        let toast = this.toastCtrl.create({
          message: '用户名或密码错误.',
          duration: 3000,
          position: 'middle',
          showCloseButton: true,
          closeButtonText: '关闭'
        });
        toast.present();
      }
    });
  }

}
复制代码

这里举例说明了验证的几种做法。登陆成功存储用户信息,失败则弹出吐司提示,然后跳转到详情页,并且传一个参数过去。
11. 详情页

复制代码
export class MyinfoPage {
  id: number;// 用来接收上一个页面传递过来的参数
  user: UserInfoData;
  constructor(public navCtrl: NavController,
    navParams: NavParams,
    private userInfoService: UserInfoService,
    public actionSheetCtrl: ActionSheetController,
    private popoverCtrl: PopoverController
  ) {
    this.id = navParams.get('item');//这个是通过页面跳转传过来的值
    this.getInfo();
  }

  getInfo() {
    this.userInfoService.GetUserInfo(this.id).then(data => {
      this.user = data.Result;
      //alert(JSON.stringify(this.user));
    });
  }
复制代码

接收参数用于查询,然后界面显示出来。

暂时结束吧,这些所有框架变化都太快,永远也不会有稳定的那一天,所以暂时不想研究了。

 

开源地址:

https://git.oschina.net/shiyeping/Ionic-Client-

https://git.oschina.net/shiyeping/Ionic-Server


猜你喜欢

转载自blog.csdn.net/fangquan1980/article/details/80716539