DAY16-前後端合体 建立打卡页面-前端元件篇

在前几天接触新的工具、新的方法,使用不同的套件,现在终於要回到side project 最初的目标- 制作打卡系统

https://ithelp.ithome.com.tw/upload/images/20211001/20120107ihDaScHgA2.png

现在要回到前端工程师的老本行之一,刻一个前端页面,提供使用者可以上传今日 进步1%努力的证明,顺便分享一些话、一些连结与完成的心情。

所以这个页面要包含以下功能

  • 上传照片,证明完成某件事情,但是最多只能上传五张,避免firebase的空间被滥用 (必填)
  • 描述一下今了做了什麽 (必填)
  • 如果有想要分享的连结,也可以贴上来分享给大家
  • 纪录今天完成的心情,是喜、怒、哀、乐

另外只有上传照片和描述今天做了什麽是必填,除此之外,其他都是选填。只有当必填的选项填完之後,打卡的按钮才会从disable状态转为可以点击的状态。

以上这个就是需求,那麽就开始吧。

首先是html的部分

<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 的格线系统,像是 containerrowcol 等 class,将版面排好。

而元件的部分,就使用 nebular 所以提供的各式各样元件,只要是 nb-* 开头的,都是nebular提供的。像是卡片元件 nb-card 、读取元件 nbSpinner 、按钮元件 nbButton、输入框元件 nbInput

另外表单使用 angular 内建的 reactiveForm 来控制,所以可以看到 formGroupformControl等绑定表单元件的属性,另外在打卡的按钮,使用diable的条件,当表单内容条件没有满足的时候,将会将按钮disable,不让使用者点

<button nbButton status="primary" [disabled]="checkinForm.invalid" (click)="checkin()">
    打卡
</button>

再来是样式 scss 的部分

样式基本上套件都写好了,就只有针对自己需要的地方,做个小小的微调而已

.custom-input-group {
  margin-bottom: 1rem;
}

.label {
  margin-bottom: 0.75rem;
  display: block;
}

.checkin-checkbox {
  margin-left: 10px;
}

.required {
  color: #de1e18;
}

最後是逻辑 typescript 的部分

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

>>:  [Day16] 建立订单交易API_9

[Java Day28] 6.5. instanceof

教材网址 https://coding104.blogspot.com/2021/06/java-i...

JS 范围链 与 提升 DAY49

范围链(Scope Chain) Def: 当函式本身使用的变数不存在 就会向外层寻找 (这里的外层...

Vue.js 从零开始:watch

watch监听器 监听data里面的值,当值有变化时,就会触发事件。 watch监听一个变数: &l...

第28天:自订(拆成)自己的Helper辅助功能-FileHelper

我们把昨天的写入档案功能抽成一个独立静态方法,减少Action负责的逻辑,在其他地方若也用到写度档案...

[Day19] 逻辑运算子

逻辑运算子(Logical Operators) 常用来判断多个条件并回传结果,有 &&am...