在前几天接触新的工具、新的方法,使用不同的套件,现在终於要回到side project 最初的目标- 制作打卡系统
现在要回到前端工程师的老本行之一,刻一个前端页面,提供使用者可以上传今日 进步1%努力的证明
,顺便分享一些话、一些连结与完成的心情。
所以这个页面要包含以下功能
(必填)
(必填)
另外只有上传照片和描述今天做了什麽是必填,除此之外,其他都是选填。只有当必填的选项填完之後,打卡的按钮才会从disable状态转为可以点击的状态。
以上这个就是需求,那麽就开始吧。
<div class="container">
<div class="row">
<div class="col col-12">
<ng-container [formGroup]="checkinForm">
<nb-card
status="primary"
[nbSpinner]="isLoading"
nbSpinnerSize="giant"
nbSpinnerStatus="info"
>
<nb-card-header>打卡</nb-card-header>
<nb-card-body>
<div class="custom-input-group">
<label for="checkin-file" class="label"
>上传档案
<span class="required">* (最多5张,影片请贴连结)</span></label
>
<input
id="checkin-file"
accept="image/*"
nbInput
multiple
status="primary"
type="file"
(change)="onFileChange($event)"
/>
</div>
<div class="custom-input-group">
<label for="checkin-content" class="label"
>今天做了什麽 <span class="required">*</span></label
>
<textarea
id="checkin-content"
nbInput
fullWidth
placeholder="今天做了什麽"
formControlName="message"
></textarea>
</div>
<div class="custom-input-group">
<label for="checkin-url" class="label"
>分享连结(如果有的话)</label
>
<input
id="checkin-url"
formControlName="url"
type="text"
nbInput
fullWidth
placeholder="分享连结(如果有的话)"
/>
</div>
<div class="custom-input-group">
<label class="label" for="">今天心情如何</label>
<nb-button-group>
<button
nbButton
*ngFor="let emoji of emojiList"
(click)="setEmoji(emoji)"
>
{{ emoji }}
</button>
</nb-button-group>
</div>
</nb-card-body>
<nb-card-footer>
<button
nbButton
status="primary"
[disabled]="checkinForm.invalid"
(click)="checkin()"
>
打卡
</button>
</nb-card-footer>
</nb-card>
</ng-container>
</div>
</div>
</div>
基本上的原则就是, 有人造轮子就绝对不自己造轮子
,能不自己做就不自己做,将安装的套件淋漓尽致地套用。
排版的部分就使用 bootstrap
的格线系统,像是 container
、 row
、 col
等 class,将版面排好。
而元件的部分,就使用 nebular
所以提供的各式各样元件,只要是 nb-*
开头的,都是nebular提供的。像是卡片元件 nb-card
、读取元件 nbSpinner
、按钮元件 nbButton
、输入框元件 nbInput
另外表单使用 angular 内建的 reactiveForm
来控制,所以可以看到 formGroup
、 formControl
等绑定表单元件的属性,另外在打卡的按钮,使用diable的条件,当表单内容条件没有满足的时候,将会将按钮disable,不让使用者点
<button nbButton status="primary" [disabled]="checkinForm.invalid" (click)="checkin()">
打卡
</button>
样式基本上套件都写好了,就只有针对自己需要的地方,做个小小的微调而已
.custom-input-group {
margin-bottom: 1rem;
}
.label {
margin-bottom: 0.75rem;
display: block;
}
.checkin-checkbox {
margin-left: 10px;
}
.required {
color: #de1e18;
}
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NbToastrService } from '@nebular/theme';
import { CheckinService } from '../../../services/checkin.service';
import { emojiList } from '../../../data/emoji';
import { UserService } from '../../../services/user.service';
@Component({
selector: 'challenge90days-checkin',
templateUrl: './checkin.component.html',
styleUrls: ['./checkin.component.scss'],
})
export class CheckinComponent implements OnInit {
userInfo$ = this.userService.userInfo$;
emojiList = emojiList;
checkinForm: FormGroup;
isLoading = false;
constructor(
private fb: FormBuilder,
private checkinService: CheckinService,
private toastrService: NbToastrService,
private userService: UserService,
) {}
ngOnInit(): void {
this.initForm();
}
initForm(): void {
this.checkinForm = this.fb.group({
user: [''],
message: ['', Validators.required],
url: [''],
imgFile: [null, Validators.required],
emoji: [''],
isCheckinTomorrow: [false],
});
}
// 验证选择的档案
onFileChange(event): void {
if (event?.target?.files?.length > 5) {
this.toastrService.danger('错误', '图片超过5张,请重新选择');
const input = document.getElementById('checkin-file') as HTMLInputElement;
input.value = '';
return;
}
if (event.target.files && event.target.files.length) {
this.checkinForm.get('imgFile').patchValue( event.target.files);
}
}
checkin(): void {
this.toastrService.warning('上传中', '请等待图片上传完成,请勿关闭视窗');
this.isLoading = true;
this.checkinService.addCheckin(this.checkinForm.value).subscribe((e) => {
this.toastrService.success('成功', '恭喜,又完成一天罗');
this.isLoading = false;
this.resetFrom();
});
}
setEmoji(emoji: string): void {
this.checkinForm.get('emoji').patchValue(emoji);
}
resetFrom() {
this.initForm();
const input = document.getElementById('checkin-file') as HTMLInputElement;
input.value = '';
}
}
基本上最重要的就是如何建立 响应式的表单
initForm(): void {
this.checkinForm = this.fb.group({
user: [''],
message: ['', Validators.required],
url: [''],
imgFile: [null, Validators.required],
emoji: [''],
});
}
建立表单的栏位,并且设定初始值,如果是必填的栏位,加上 Validators.required
让 reactive form 自动去验证。建立完表单之後,就可以绑定到页面上
再来就是页面送出资料的时候
checkin(): void {
this.toastrService.warning('上传中', '请等待图片上传完成,请勿关闭视窗');
this.isLoading = true;
this.checkinService.addCheckin(this.checkinForm.value).subscribe((e) => {
this.toastrService.success('成功', '恭喜,又完成一天罗');
this.isLoading = false;
this.resetFrom();
});
}
因为资料送出去就会变成非同步的动作,所以要显示loading的画面,并且给使用者回馈,现在正在做什麽,请稍等一下,让使用者不会焦虑,不知道是坏掉了还是没有按到。
接下来将表单的资料送到服务 checkinService
,给服务去处理核心的逻辑,这个就留到下一篇再解释。
当成功送出之後,告诉使用者成功了,并且将表单重置。
好了,这样就完成页面的设计罗
<<: Day16-205. Isomorphic Strings
教材网址 https://coding104.blogspot.com/2021/06/java-i...
范围链(Scope Chain) Def: 当函式本身使用的变数不存在 就会向外层寻找 (这里的外层...
watch监听器 监听data里面的值,当值有变化时,就会触发事件。 watch监听一个变数: &l...
我们把昨天的写入档案功能抽成一个独立静态方法,减少Action负责的逻辑,在其他地方若也用到写度档案...
逻辑运算子(Logical Operators) 常用来判断多个条件并回传结果,有 &&am...