一. 表单的基础知识
<form>和</form>之间包裹起来的内容就是表单。其中,内部的<input>啊,<button>啊,<textarea>这些,都叫做表单控件。
其中<button>的默认type为submit,这个意味着你点击这个button就会自动帮你提交,你如果强行绑定(click)事件就会引发错误。
相似的type还有reset, 这个也不能绑定事件。
二. ng-zorro表单
这里我使用的框架是ng-zorro,模板代码如下:
<div class="login-phone modal-content">
<div class="modal-wrap">
<form nz-form class="login-form" [formGroup]="formModal" (ngSubmit)="onSubmit()">
<nz-form-item>
<nz-form-control nzHasFeedback nzErrorTip="请输入正确的手机号">
<nz-input-group nzPrefixIcon="mobile">
<input type="tel" nz-input placeholder="请输入手机号" formControlName="phone">
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control nzHasFeedback nzErrorTip="请输入正确的密码">
<nz-input-group nzPrefixIcon="lock">
<input type="password" nz-input placeholder="请输入密码" formControlName="password">
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control>
<div class="tools">
<label nz-checkbox class="remember" formControlName="remember">
<span>记住密码</span>
</label>
</div>
<button nz-button class="login-form-button" [disabled]="!formModal.valid" nzType="primary" nzBlock>登陆</button>
</nz-form-control>
</nz-form-item>
</form>
</div>
</div>
注意到三点:
- 我们通过绑定[formGroup]属性确定了表单模型变量。
- 通过(ngOnSubmit)绑定了提交的事件。
- 每一个控件通过formControlName确定了表单控件的引用。
三. 表单逻辑
其实在登录组件背后的逻辑就是表单的各种操作,比如在初始化的时候设置初值:
constructor(private fb: FormBuilder) {
this.setFormModal({phone: '', password: '', remember: false});
}
setFormModal({phone, password, remember}) {
this.formModal = this.fb.group({
phone: [phone, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
password: [password, [Validators.required, Validators.minLength(6)]],
remember: [remember]
});
}
然后当你点击按钮,出发onSubmit事件的时候把LoginParams发送出去:
onSubmit(): void {
if (this.formModal.valid) {
this.onLogin.emit(this.formModal.value);
}
}
四. 调用接口登录
调用接口,老生长谈了。创建一个新的services.ts文件:
login(loginParam: LoginParams): Observable<User> {
const usernamePasswordPair = {phone: loginParam.phone, password: loginParam.password};
const param = new HttpParams({fromString: queryString.stringify(usernamePasswordPair)});
return this.http.get(this.urlPrefix + 'login/cellphone', {params: param}).pipe(
map(user => user as User)
);
}
之前我们提到把登录的参数交给父组件处理了,接下来我们来看看父组件中是怎么处理的:
父组件中主要做这么几件事情:
- 接收接口返回的数据,成功与否可以用messageService来替换console.log和console.error。注意成功了还得把模态框关了。
- 把userId保存在localStorage中,用来记住用户的登录状态;同时,利用状态管理插件改变userId这个状态,这样就能通知所有用来显示用户信息的组件渲染新的DOM。
- 还可以把用户名和密码加密后保存在localStorage中,不过不推荐(原因见下)
onChangeModalType(type) {
this.memberBatchServices.controlModal(true, type);
}
onLogin(loginParam: LoginParams) {
this.memberService.login(loginParam).subscribe(res => {
this.user = res;
console.log(this.user);
this.memberBatchServices.controlModal(false);
this.alertMessage('success', '登陆成功!');
this.storageService.setStorage({
key: 'wyUserId',
value: this.user.profile.userId.toString()
});
this.store$.dispatch(SetUserId({userId: this.user.profile.userId.toString()}));
if (loginParam.remember) {
this.storageService.setStorage({
key: 'wyRememberLogin',
value: JSON.stringify(codeJson(loginParam))
});
} else {
this.storageService.removeStorage('wyRememberLogin');
}
}, this.handleErrorMessage);
}
private alertMessage(type: string, msg: string) {
this.messageService.create(type, msg);
}
五. 记住用户登录状态
我们在登陆以后userId就直接保存在我们的localStorage中了。如果不logout的话不会清除这个。
之前也提到过,我们用这个userId是用来记住用户登录状态的。原理如下:
在页面初始化之前,通过userId来调用一个新的接口,这个接口返回和login一样的用户数据,这样我们就能把登陆后的数据渲染到页面上了。
constructor(
// 。。。
private storageService: StorageService,
) {
// 从localStorage中获取信息
const userId = this.storageService.getStorage('wyUserId');
if (userId) {
this.store$.dispatch(SetUserId({userId}));
this.memberService.getUserDetail(userId).subscribe(user => this.user = user);
}
}
这里也展示一下StorageService把,他主要是用来封装SessionStorage和LocalStorage两个操作的(原理是用[]+字符串访问元素):
@Injectable({
providedIn: ServicesModule
})
export class StorageService {
constructor(@Inject(WINDOW) private win: Window) {
}
getStorage(key: string, type = 'local'): string {
return this.win[type + 'Storage'].getItem(key);
}
setStorage(params: AnyJson | AnyJson[], type = 'local') {
const kv = Array.isArray(params) ? params : [params];
for (const { key, value } of kv) {
this.win[type + 'Storage'].setItem(key, value.toString());
}
}
removeStorage(params: string | string[], type = 'local') {
const kv = Array.isArray(params) ? params : [params];
for (const key of kv) {
this.win[type + 'Storage'].removeItem(key);
}
}
}
六. 保存用户登录用户名和密码
我们之前的登录选项中有记住用户名和密码这个选项,当我们勾选了这个选项之后,下次点击登录按钮就会从localStorage中取出保存的用户名和密码。
不过,这种做法相当不安全,这里我们也是通过了base64的加密算法来进行加密,不过依然不够安全:
// 这段代码是从上面摘抄来的。。。
if (loginParam.remember) {
this.storageService.setStorage({
key: 'wyRememberLogin',
value: JSON.stringify(codeJson(loginParam))
});
} else {
this.storageService.removeStorage('wyRememberLogin');
}
一个比较推荐的做法是,不用记住密码这种选项,而让浏览器自动记住你的登录信息。
七. 登出做什么
- 相关变量,全部设置为null。
- 利用messageService提示用户已经退出成功。
- 删除localStorage中的相关信息。
onLogout() {
this.memberService.logout().subscribe(() => {
this.alertMessage('success', '退出成功!');
this.user = null;
this.store$.dispatch(SetUserId({userId: null}));
this.storageService.removeStorage('wyUserId');
}, this.handleErrorMessage);
}