使用ionic2开发一个登录功能
服务的采用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 |
注册 |
系统目录规划说明:
详细开发过程
1. 新建登录页面
定位到src\pages目录下,运行命令:
ionic g page login
2. 添加一个Tab页显示登录页
在ionic2.1下面变得容易了,在tabs.ts里面引入并且制定tab,然后在前台增加这个tab即可:
<ion-tab [root]="tab4Root" tabTitle="me" tabIcon="person"></ion-tab>
图标可以采用系统也可以自定义。
然后运行项目:
ionic serve
结果发现点击me不会出现新建的页面?
打开app下面的app.module.ts发现系统所有页面都需要在这里配置一遍才能使用。
这里说明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': ['admin@163.com', [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': ['admin@163.com', [Validators.required, Validators.pattern('^([a-zA-Z0-9_.]*)((@[a-zA-Z0-9_.]*)\.([a-zA-Z]{2}|[a-zA-Z]{3}))$')]],// 第一个参数是默认值
'LoginID': ['admin@163.com2', [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





浙公网安备 33010602011771号