css与React版本除颜色分配外几乎一致,需要的话请参见之前的文章React果果记账-styled-components,整体框图如下,css具体代码此处不赘述。
Types.vue
用装饰器的好处
TS编译时和运行时额区别
代码中使用'-'表示支出,'+'表示收入,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中设置
总结1:TypeScript 好处
- 类型提示:更智能的提示
- 编译时报错:还没运行代码就知道自己写错了
- 类型检查:无法点出错误的属性
总结2:TS的本质
总结3:写 Vue 组件的三种方式(单文件组件)
-
用 JS 对象
export default { data, props, methods, created, ...}
-
用 TS 类
<script lang="ts">
@Component export default class XXX extends Vue{ xxx: string = 'hi'; @Prop(Number) xxx: number|undefined; }
-
用 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
会忽略所有表单元素的 value
、checked
、selected
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>