【项目】记账App(七)----统计页面

382 阅读2分钟

一、封装Tags组件

(一)背景

发现记账页面和统计页面的这三个地方其实是一样的,所以封装成一个Tags组件

(二)src/component/Tags.vue

<template>
  <ul class="tabs" :class="{[classPrefix+'-tabs']: classPrefix}">
    <li v-for="item in dataSource" :key="item.value" class="tabs-item"
        :class="{ 
        [classPrefix + '-tabs-item']: classPrefix,
        selected: item.value === value 
        }"
        @click="select(item)">{{item.text}}
    </li>
  </ul>
</template>

<script lang="ts">
  import Vue from 'vue';
  import {Component, Prop} from 'vue-property-decorator';
  type DataSourceItem = { text: string;value: string }
  @Component
  export default class Tabs extends Vue {
    //外部数据dataSource用于接受到底有几个块,每个块的名字和符号是啥
    @Prop({required: true, type: Array}) dataSource!: DataSourceItem[];
    //外部数据value用于表示选中了哪个块(的符号)
    @Prop(String) readonly value!: string;
    //外部数据 classPrefix用于给加上class的前缀。父组件传值后就会成为唯一的选择器
    @Prop(String) classPrefix?: string;

    liClass(item: DataSourceItem) {
      return {
       
        [this.classPrefix + '-tabs-item']: this.classPrefix,
      
        selected: item.value === this.value
      };
    }

    //若是点击了一个块,就会把这个块的符号给父组件,父组件会让这个符号成为新的value。selected: item.value === value因此这个块就会有class:selected了
    select(item: DataSourceItem) {
      this.$emit('update:value', item.value);
    }
  }
</script>

<style lang="scss" scoped>
  @import "~@/assets/style/helper.scss";

  .tabs {
    background: $color-three;
    display: flex;
    font-size: 24px;

    > li {
      /*border: 1px solid black;*/
      width: 50%;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 12px 0;
      position: relative;

      &.selected::after {
        /*不可以用border,不然消失的时候整个会动,那就加一个伪元素,让伪元素绝对定位在它下面就行了*/
        /*border-bottom: 4px solid;*/
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 4px;
        background: #333333;
      }}}

</style>

(三)统计页面就可以使用Tags组件了

<template>
  <Layout>
    <Tabs class-prefix="type" :data-source="recordTypeList" :value.sync="type"/>
    <Tabs class-prefix="interval" :data-source="intervalList" :value.sync="interval"/>
    <div>
      type: {{type}}
      <br/>
      interval: {{interval}}
    </div>
  </Layout>
</template>

<style scoped lang="scss">
  @import "~@/assets/style/helper.scss";
  ::v-deep .type-tabs-item {
    background: white;
    &.selected {
      background: $color-three;
      &::after {
        display: none;
      }
    }
  }
  ::v-deep .interval-tabs-item {
    height: 48px;
  }
</style>

<script lang="ts">
  import Vue from 'vue';
  import {Component} from 'vue-property-decorator';
  import Tabs from '@/components/Tabs.vue';
  import intervalList from '@/constants/intervalList';
  import recordTypeList from '@/constants/recordTypeList';
  @Component({
    components: {Tabs},
  })
  export default class Statistics extends Vue {
    type = '-';
    interval = 'day';
    intervalList = intervalList;
    recordTypeList = recordTypeList;
  }
</script>

新建src/constants/intervalList.ts和recoreType.ts来当做dataSource

intervalList.ts

export default Object.freeze([
  {text: '按天', value: 'day'},
  {text: '按周', value: 'week'},
  {text: '按月', value: 'month'},
]);

recoreType.ts

export default Object.freeze([
  {text: '支出', value: '-'},
  {text: '收入', value: '+'},
]);

二、统计页面的数据结构

  1. 我们要用的是数据是recordList里的每个record

2. 根据types分为支出或者收入 3. 我们想的得到的是个森林
所以最终确定数据结构是一个哈希表数组!还得是个计算属性!(因为根据recordList和types得来的)

[
   {title:3.3, items:[record4,record5,record6], total: 100},
   {title:3.2, items:[record3], total: 100},
   {title:3.1, items:[record1,record2], : 100},
]
  • 为什么是数组?遍历这个数组的时候会按照顺序依次遍历,而对象可能会乱遍历。
  • 所以我们按照从最新的时间开始到以前的时间(倒序)这个顺序把这个数组写好,到时候按照顺序遍历
  get groupedList() {
      //一、获得数据recordList
      const {recordList} = this;
  
      
      //二、从recordList中选出一个只有支出或者只有收入,并且要按照时间倒序排序的新数组newList
      const newList = clone(recordList)
        .filter(r => r.type === this.type)
        .sort((a, b) => dayjs(b.createdAt).valueOf() - dayjs(a.createdAt).valueOf());
      
      if (newList.length === 0) {return [];}  
      
      //三、定义森林的类型是个数组,数组的每个元素都是个对象,每个对象有title和items和total属性  
      type Result = { title: string, items: RecordItem[], total?: number }[]
      
      //四、首先result中就只有最新的一笔账的对象
      const result: Result = [{title: dayjs(newList[0].createdAt).format('YYYY-MM-DD'), items: [newList[0]]}];
      //其次,从第二个record开始遍历newList中每一个元素
      for (let i = 1; i < newList.length; i++) {
        //当前的record
        const current = newList[i];
        //result中最后一个对象
        const last = result[result.length - 1
        
        //因为newList中已经按照时间倒序排好了。比较当前的record和result中最后一个对象的时间
        
        //如果一样,那就把record加入result中最后一个对象的items中
        if (dayjs(last.title).isSame(dayjs(current.createdAt), 'day')) {
          last.items.push(current);
        //如果不一样,说明result没有这个时间的对象,那就新建一个这个时间的对象,并且这个对象的items里就暂时是这个record一个。
        } else {
          result.push({title: dayjs(current.createdAt).format('YYYY-MM-DD'), items: [current]});
        }
      }
      //五、算总和
      result.map(group => {
        group.total = group.items.reduce((sum, item) => {
          console.log(sum);
          console.log(item);
          return sum + item.amount;
        }, 0);
      });
      return result;
    }

三、完整代码:src/views/Statistics.vue

<template>
  <Layout>
    <Tabs class-prefix="type" :data-source="recordTypeList" :value.sync="type"/>
    <ol>
      <li v-for="(group, index) in groupedList" :key="index">
        <h3 class="title">{{beautify(group.title)}} <span>¥{{group.total}}</span></h3>
        <ol>
          <li v-for="item in group.items" :key="item.id"
              class="record"
          >
            <span>{{tagString(item.tags)}}</span>
            <span class="notes">{{item.notes}}</span>
            <span>¥{{item.amount}} </span>
          </li>
        </ol>
      </li>
    </ol>
  </Layout>
</template>
<script lang="ts">
  import Vue from 'vue';
  import {Component} from 'vue-property-decorator';
  import Tabs from '@/components/Tabs.vue';
  import recordTypeList from '@/constants/recordTypeList';
  import dayjs from 'dayjs';
  import clone from '@/lib/clone';
  @Component({
    components: {Tabs},
  })
  export default class Statistics extends Vue {
    tagString(tags: Tag[]) {
      if(tags.length === 0) {return '无'}
        else{
        return tags.map(item=>item.name).join(' ')
      }
    }
    beautify(string: string) {
      const day = dayjs(string);
      const now = dayjs();
      if (day.isSame(now, 'day')) {
        return '今天';
      } else if (day.isSame(now.subtract(1, 'day'), 'day')) {
        console.log('hi');
        return '昨天';
      } else if (day.isSame(now.subtract(2, 'day'), 'day')) {
        return '前天';
      } else if (day.isSame(now, 'year')) {
        return day.format('M月D日');
      } else {
        return day.format('YYYY年M月D日');
      }
    }
    get recordList() {
      return (this.$store.state as RootState).recordList;
    }
    get groupedList() {
      const {recordList} = this;
      if (recordList.length === 0) {return [];}
      const newList = clone(recordList)
        .filter(r => r.type === this.type)
        .sort((a, b) => dayjs(b.createdAt).valueOf() - dayjs(a.createdAt).valueOf());
      type Result = { title: string; total?: number;items: RecordItem[] }[]
      const result: Result = [{title: dayjs(newList[0].createdAt).format('YYYY-MM-DD'), items: [newList[0]]}];
      for (let i = 1; i < newList.length; i++) {
        const current = newList[i];
        const last = result[result.length - 1];
        if (dayjs(last.title).isSame(dayjs(current.createdAt), 'day')) {
          last.items.push(current);
        } else {
          result.push({title: dayjs(current.createdAt).format('YYYY-MM-DD'), items: [current]});
        }
      }
      result.map(group => {
        group.total = group.items.reduce((sum, item) => {
          console.log(sum);
          console.log(item);
          return sum + item.amount;
        }, 0);
      });
      return result;
    }
    beforeCreate() {
      this.$store.commit('fetchRecords');
    }
    type = '-';
    recordTypeList = recordTypeList;
  }
</script>

<style scoped lang="scss">
  @import "~@/assets/style/helper.scss";
  ::v-deep .type-tabs-item {
    background: $color-five;
    &.selected {
      background: $color-three;
      &::after {
        display: none;
      }
    }
  }

  %item {
    padding: 8px 16px;
    line-height: 24px;
    display: flex;
    justify-content: space-between;
    align-content: center;
  }
  .title {
    @extend %item;
    background: $color-four;
  }
  .record {
    background: white;
    @extend %item;
  }
  .notes {
    margin-right: auto;
    margin-left: 16px;
    color: #999;
  }

</style>