Vue果果记账-Money.vue组件

1,067 阅读2分钟

css与React版本除颜色分配外几乎一致,需要的话请参见之前的文章React果果记账-styled-components,整体框图如下,css具体代码此处不赘述。

image.png

Types.vue

用装饰器的好处 image.png

TS编译时和运行时额区别 image.png

代码中使用'-'表示支出,'+'表示收入,selected选中时高亮。

//使用TS编写
<template>
  <div>
    <ul class="types">
      <li :class="type === '-' && 'selected'"
          @click="selectType('-')">支出
<!--          如果type === '-',则class等于selected-->
      </li>
      <li :class="type === '+' && 'selected'"
          @click="selectType('+')">收入
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue';
  import {Component, Prop} from 'vue-property-decorator';

  @Component//修饰器的作用:type会自动被处理成data,selectType会自动被处理成methods
  export default class Types extends Vue {
    type = '-'; // '-'表示支出,'+'表示收入
    selectType(type: string) {
      if (type !== '-' && type !== '+') {
        throw new Error('type is unknown');
      }
      this.type = type;
    }
  }
</script>

<style lang="scss" scoped>
  .types {
    background: #fde3cc;
    ...
    > li {
      ...
      &.selected{
        background: #ffb13d;
      }
    }
  }

</style>

注意:TS代码必须加分号,可以在webstorm中设置

image.png

总结1:TypeScript 好处

  1. 类型提示:更智能的提示
  2. 编译时报错:还没运行代码就知道自己写错了
  3. 类型检查:无法点出错误的属性

总结2:TS的本质

image.png

总结3:写 Vue 组件的三种方式(单文件组件)

  1. 用 JS 对象

     export default { data, props, methods, created, ...}
    
  2. 用 TS 类 <script lang="ts">

     @Component
     export default class XXX extends Vue{
         xxx: string = 'hi';
         @Prop(Number) xxx: number|undefined;
     }
    
  3. 用 JS 类 <script lang="js">

     @Component
     export default class XXX extends Vue{
         xxx = 'hi'
     }
    

NumberPad.vue

<template>
  <div class="numberPad">
    <div class="output">{{output}}</div>
    <div class="buttons">
      <button @click="inputContent">1</button>
      <button @click="inputContent">2</button>
      <button @click="inputContent">3</button>
      <button @click="remove">删除</button>
      <button @click="inputContent">4</button>
      <button @click="inputContent">5</button>
      <button @click="inputContent">6</button>
      <button @click="clear">清空</button>
      <button @click="inputContent">7</button>
      <button @click="inputContent">8</button>
      <button @click="inputContent">9</button>
      <button @click="ok" class="ok">OK</button>
      <button @click="inputContent" class="zero">0</button>
      <button @click="inputContent">.</button>
    </div>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue';
  import {Component} from 'vue-property-decorator';

  @Component
  export default class NumberPad extends Vue {
    output: string = '0';//因为后面有输入,ts会猜到类型为string,所以这里也可以不加:string

    inputContent(event: MouseEvent) {
      const button = (event.target as HTMLButtonElement);
      const input = button.textContent!;//!相当于as string,即把null排除
      if (this.output.length === 16) { return; }//输入最高为16位
      if (this.output === '0') {
        if ('0123456789'.indexOf(input) >= 0) {//如果输入的东西在0123456789中
          this.output = input;
        } else {
          this.output += input;
        }
        return;
      }
      if (this.output.indexOf('.') >= 0 && input === '.') {return;}//防止一串数字中出现两个小数点
      this.output += input;
    }

    remove() {//删除
      if (this.output.length === 1) {
        this.output = '0';
      } else {
        this.output = this.output.slice(0, -1);
      }
    }

    clear() {//清空
      this.output = '0';
    }

    ok() {

    }
  }
</script>

<style lang="scss" scoped>
  @import "~@/assets/style/helper.scss";
  .numberPad {
    .output {
      @extend %clearFix;
      @extend %innerShadow;
      font-size: 36px;
      font-family: Consolas, monospace;
      padding: 9px 16px;
      text-align: right;
      height: 72px;
    }
    .buttons {
      @extend %clearFix;
      > button {
        width: 25%;
        height: 64px;
        float: left;
        background: transparent;
        border: none;
        &.ok {
          height: 64*2px;
          float: right;
        }
        &.zero {
          width: 25*2%;
        }
        $bg: #ffb13d;
        &:nth-child(1) {
          background: lighten($bg, 4*6%);
        }
        &:nth-child(2), &:nth-child(5) {
          background: lighten($bg, 4*5%);
        }
        &:nth-child(3), &:nth-child(6), &:nth-child(9) {
          background: lighten($bg, 4*4%);
        }
        &:nth-child(4), &:nth-child(7), &:nth-child(10) {
          background: lighten($bg, 4*3%);
        }
        &:nth-child(8), &:nth-child(11), &:nth-child(13) {
          background: lighten($bg, 4*2%);
        }
        &:nth-child(14) {
          background: lighten($bg, 4%);
        }
        &:nth-child(12) {
          background: $bg;
        }
      }
    }
  }
</style>

Notes.vue

<template>
  <div>
    <label class="notes">
      <span class="name">备注</span>
      <input type="text"
             v-model="value"
             placeholder="请输入备注">
    </label>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue';
  import {Component} from 'vue-property-decorator';

  @Component
  export default class Notes extends Vue {
    value = '';
  }
</script>

<style lang="scss" scoped>
  .notes {
    font-size: 14px;
    background: #f5f5f5;
    padding-left: 16px;
    display: flex;
    align-items: center;
    .name {
      padding-right: 16px;
    }
    input {
      height: 64px;
      flex-grow: 1;
      background: transparent;
      border: none;
      padding-right: 16px;
    }
  }
</style>

复习:v-model表单输入绑定

你可以用 v-model 指令在表单 <input><textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件。

举例:

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

Tags.vue

<template>
  <div class="tags">
    <div class="new">
      <button @click="create">新增标签</button>
    </div>
    <ul class="current">
      <li v-for="tag in dataSource" :key="tag"
          :class="{selected: selectedTags.indexOf(tag)>=0}"
          @click="toggle(tag)">{{tag}}
      </li>
    </ul>
  </div>

</template>

<script lang="ts">
  import Vue from 'vue';
  import {Component, Prop} from 'vue-property-decorator';

  @Component
  export default class Tags extends Vue {
    @Prop() readonly dataSource: string[] | undefined;
    selectedTags: string[] = [];

    toggle(tag: string) {
      const index = this.selectedTags.indexOf(tag);
      if (index >= 0) {
        this.selectedTags.splice(index, 1);
      } else {
        this.selectedTags.push(tag);
      }
    }

    create() {
      const name = window.prompt('请输入标签名');
      if (name === '') {
        window.alert('标签名不能为空');
      } else if (this.dataSource) {
        this.$emit('update:dataSource',
          [...this.dataSource, name]);
      }

    }
  }
</script>

<style lang="scss" scoped>
  .tags {
    ...
    > .current {
      display: flex;
      flex-wrap: wrap;
      > li {
        $bg: #ffb13d;
        background: #fde3cc;
        ...
        &.selected {
          background: $bg;
        }
      }
    }
    > .new {
      padding-top: 16px;
      button {
        ...
      }
    }
  }

</style>