【心无旁骛】vue-ts-daily

这是一个非常有意思的项目,我们先来看看效果

这个项目所用的技术也比较有意思,它的技术栈为vue2.5 + Typescript + vuex + vue-router
放下博主的项目地址吧,https://github.com/xiaomuzhu/vue-ts-daily
接下来我们一起看项目代码吧,也一起研究ts怎么在vue中进行使用
首先是入口文件main.ts

//main.ts
// 本质上和写js一样
import Vue from 'vue';
// 解决300ms点击延迟问题
import FastClick from 'fastclick';
// 引用图标字体组件
import VueIconFont from 'vue-icon-font-pro';
// 日历组件
import vueEventCalendar from 'vue-event-calendar-pro';
// Vue.js 2.0 组件级懒加载方案:Vue Lazy Component
import VueLazyComponent from '@xunlei/vue-lazy-component';
//骨架loading
import VueSkeletonLoading from 'vue-skeleton-loading';
// Normalize.css是一种CSS reset的替代方案
import 'normalize.css';
// 动画
import 'vue2-animate/dist/vue2-animate.min.css';
import 'vue-event-calendar-pro/dist/style.css';

import App from './App.vue';
import router from './router';
import store from './store';
import './registerServiceWorker';
import '@/assets/iconfont.js';

// 兼容毒瘤ios的300ms延迟问题
if ('addEventListener' in document) {
  document.addEventListener(
    'DOMContentLoaded',
    () => {
      (FastClick as any).attach(document.body);
    },
    false,
  );
}

Vue.use(VueLazyComponent);
Vue.use(VueSkeletonLoading);
Vue.use(vueEventCalendar, { locale: 'zh', weekStartOn: 1 });
Vue.use(VueIconFont);

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app');

App.vue引入两个组件

<template>
  <main id="app">
    <div v-if="$route.meta.main">
      <Header></Header>
        <router-view />  
      <Footer></Footer>
    </div>
    <div v-if="!$route.meta.main">
    <router-view />
    </div>
  </main>
</template>

<script lang="ts">
// 引入组件
import { Component, Prop, Vue } from 'vue-property-decorator';
// 引入头部和底部
import Header from './components/Header.vue';
import Footer from './components/Footer.vue';
@Component({
  components: {
    Header,
    Footer,
  },
})
export default class App extends Vue {}
</script>

<style lang="scss" scoped>
@import './style/mixin';
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: $font;
  display: flex;
  text-align: center;
  flex-direction: column;
  justify-content: space-between;
  max- 100vw;
  height: 100vh;
}
#nav {
  padding: 30px;
  a {
    font-weight: bold;
    color: $font;
    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
  .fade-leave-to
  /* .fade-leave-active below version 2.1.8 */
 {
  opacity: 0;
}
</style>

接下来我们看里面引入的HeaderIcon.ts

//srccomponentscommonIconHeaderIcon.ts
import { Component, Prop, Vue } from 'vue-property-decorator';
import template from './Icon.vue';

@Component({
  name: 'HeaderIcon',
  mixins: [template],
})
// 使用ts封装的srccomponentscommonIconHeaderIcon.ts组
export default class FooterIcon extends Vue {
  @Prop() private name!: string;
  @Prop() private path!: string;

  private data() {
    return {
      isTouched: false,
    };
  }
}

我们来看一下FooterIcon.ts

//srccomponentscommonIconFooterIcon.ts
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';
import { Mutation } from 'vuex-class';

import { PageInfo } from '@/store/state';
import template from './Icon.vue';

@Component({
  name: 'FooterIcon',
  mixins: [template],
})
// 使用ts封装的srccomponentscommonIconFooterIcon.ts组件
export default class FooterIcon extends Vue {
  @Prop() private name!: object;
  @Prop() private path!: string;
  @Prop() private id!: number;
  @Prop() private isActived!: boolean;
  @Prop() private tagName!: string;
  @Mutation private getActivePage!: (pageName: number) => void;
  @Mutation private changeHeaderState!: (pageName: number) => void;

  private changeActivePage() {
    const id = this.id;

    if (!this.isActived) {
      this.getActivePage(id);
      this.changeHeaderState(id);
    }
  }
}

对icon也做的封装

//srccomponentscommonIconIcon.vue
<template>
    <section>
        <router-link v-if="!!path" :to="path">
            <span @click="changeActivePage">
                        <icon :name="!isActived ? name.defaultName : name.activedName" style=" 2rem; height:2rem"></icon>
                        <p :class="{active: isActived}">{{tagName}}</p>
                    </span>
        </router-link>
        <div v-else class="headerIcon">
            <icon :name="name" style=" 1.6rem; height:1.8rem">
            </icon>
        </div>
    </section>
</template>

<style src="./style.scss" lang="scss" scoped>
</style>

footer.vue中也是对footer进行了封装,感觉和封装普通的组件差别不大,不过在使用vuex,state之类的属性的时候就难起来

//srccomponentsFooter.vue
<template>
    <footer>
        <Icon v-for="item in activePage" :key="item.id" :tagName="item.tagName" :isActived="item.isActived" :id="item.id" :name="item.name" :path="item.path" >
        </Icon>
    </footer>
</template>

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

import { PageInfo } from '@/store/state';
import Icon from './common/Icon/FooterIcon';

@Component({
  components: {
    Icon,
  },
})
export default class Footer extends Vue {
  @State private activePage!: PageInfo[];
}
</script>

<style lang="scss" scoped>
@import '../style/mixin';

footer {
   100%;
  height: 3.5rem;
  min-height: 8%;
  background-color: $grey;
  display: flex;
  align-items: center;
  justify-content: space-around;
  div {
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    align-items: center;
    font-size: 60%;
    svg {
      margin-bottom: 0.4rem;
    }
  }
}
</style>

封装的骨架

//srccomponentscommonSkeletonSkeletonList.vue
<template>
        <skeleton-loading>
            <row
                    v-for="i in num"
                    :key="i"
                    :gutter="{top: '10px', bottom: '10px'}"
            >
                <column :span="23" :gutter="10">
                        <square-skeleton 
                            :count="2"
                            :boxProperties="{
                                bottom: '15px',
                                 '250px',
                                height: '15px'
                            }"
                        >
                        </square-skeleton>
                </column>
            </row>
        </skeleton-loading>
</template>

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
@Component({})
export default class Skeleton extends Vue {
  @Prop() private num!: number;
}
</script>

<style lang="scss" scoped>
@import '../../../style/mixin';

</style>

我们看一下项目中的vuex是怎么和ts结合使用的
首先是state

//srcstorestate.ts
//ts里面也可以像平时一样写函数,只不过它要定义一下类型
//在state里面写了很多接口类型
import * as moment from 'moment';
export interface ClockLog {
  id: number;
  time?: moment.Moment;
  isFinished: boolean;
  message?: string;
}
export interface UserState {
  username: string | undefined;
  id: number | null;
  createdTime: string | undefined;
  url: string;
  isLogin: number;
  isSync: number;
}

export interface SettingState {
  checked: boolean;
  url: string;
}

export interface TimeSlotList {
  id: number;
  title: string;
}

export interface RemindState {
  id: number;
  remind: string;
  isOpen: boolean;
}

export interface RepeatingDateState {
  id: number;
  date: string;
  checked: boolean;
}

// 单个习惯的状态信息
export interface HabitList {
  id: number;
  iconName: string;
  color: string;
  mode: string;
  // 是否可用,否则是被归档了
  isActive: boolean;
  // 关于习惯的基本信息
  habitInfo: {
    // 习惯名称
    habitName: string;
    // 重复练习的日期
    RepeatingDate: RepeatingDateState[] | never[];
    // 练习的时间段
    activeTimes: number;
    timeSlotList: TimeSlotList[] | never[];
    // 提醒的时间
    remind: RemindState[] | never[];
    // 激励自己的话
    inspire: string;
  };
  // 习惯日志
  habitLog: {
    // 总共坚持练习了多少天
    totalHabitDays: number;
    // 当前连续联系了多少天
    currentConsecutiveDays: number;
    // 历史上最多连续练习多少天
    mostConsecutiveDays: number;
    // 创建日期
    createdTime: string;
    // 创建此习惯至今多少天
    totalDays: number;
    date: ClockLog[];
  };
}

export interface Card {
  src: string;
  content?: string;
}
export interface PageInfo {
  id: number;
  isActived: boolean;
  name: {
    defaultName: string;
    activedName: string;
  };
  path: string;
  tagName: string;
}

export interface HeaderInfo {
  left?: string;
  title: string;
  right?: string;
}

export interface State {
  activePage: PageInfo[];
  headerInfo: HeaderInfo;
  card: Card;
  habitList: HabitList[];
  today: {
    active: string[] | never[] | number[];
    finishedDate: moment.Moment[] | never[];
    isReceived: boolean;
  };
  setting: SettingState;
  user?: UserState;
}

// 初始状态
const state: State = {
  activePage: [
    {
      id: 0,
      isActived: true,
      name: {
        defaultName: 'today-o',
        activedName: 'today',
      },
      path: '/',
      tagName: '日常',
    },
    {
      id: 1,
      isActived: false,
      name: {
        defaultName: 'habit-o',
        activedName: 'habit',
      },
      path: '/habit',
      tagName: '习惯',
    },
    {
      id: 2,
      isActived: false,
      name: {
        defaultName: 'setting-o',
        activedName: 'setting',
      },
      path: '/setting',
      tagName: '更多',
    },
  ],
  headerInfo: {
    left: 'letter',
    title: 'TODAY',
    right: '', // filter
  },
  today: {
    active: [0],
    finishedDate: [],
    isReceived: false,
  },
  setting: {
    checked: false,
    url:
      'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4216091012,4283409120&fm=27&gp=0.jpg',
  },
  card: {
    src:
      'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR-5xlxmMc1UjkLOsMSPPX9sKgNr3XuCNHCCCwI__iXCx2zftWo',
    content: '1',
  },
  habitList: [
    {
      id: 1524822339790,
      iconName: 'taiyang',
      color: '#ffe884',
      mode: 'done',
      isActive: true,
      habitInfo: {
        // 习惯名称
        habitName: '背单词',
        // 重复练习的日期
        RepeatingDate: [
          { id: 0, date: '星期一', checked: true },
          { id: 1, date: '星期二', checked: true },
          { id: 2, date: '星期三', checked: true },
          { id: 3, date: '星期四', checked: true },
          { id: 4, date: '星期五', checked: true },
          { id: 5, date: '星期六', checked: true },
          { id: 6, date: '星期日', checked: true },
        ],
        // 练习的时间段
        activeTimes: 0,
        // 目前已存在的时间段
        timeSlotList: [
          {
            id: 0,
            title: '起床之后',
          },
          {
            id: 1,
            title: '晨间习惯',
          },
          {
            id: 2,
            title: '中午时分',
          },
          {
            id: 3,
            title: '午间习惯',
          },
          {
            id: 4,
            title: '晚间习惯',
          },
          {
            id: 5,
            title: '睡觉之前',
          },
          {
            id: 6,
            title: '任意时间',
          },
        ],
        // 提醒的时间
        remind: [{ id: 0, remind: '12:00', isOpen: false }],
        // 激励自己的话
        inspire: '坚持的路上没有捷径',
      },
      habitLog: {
        // 总共坚持练习了多少天
        totalHabitDays: 0,
        // 当前连续联系了多少天
        currentConsecutiveDays: 0,
        // 历史上最多连续练习多少天
        mostConsecutiveDays: 0,
        // 创建日期
        createdTime: '0',
        // 创建此习惯至今多少天
        totalDays: 0,
        // 坚持的日期
        date: [],
      },
    },
  ],
  user: {
    isLogin: -1,
    username: '',
    id: null,
    createdTime: '',
    isSync: -1,
    url:
      'https://is4-ssl.mzstatic.com/image/thumb/Purple71/v4/be/13/06/be1306d8-e343-2adb-2b04-9a6884300499/pr_source.jpg/1200x630bb.jpg',
  },
};

export default state;
//srcstoregetters.ts
import { GetterTree } from 'vuex';

const getters: GetterTree<any, any> = {
  syncData(state) {
    const { activePage, headerInfo, card, habitList, today, setting } = state;
    return {
      activePage,
      headerInfo,
      card,
      habitList,
      today,
      setting,
    };
  },
};

export default getters;
//srcstoremutations.ts
import moment from 'moment';

import { State, HabitList } from './state';
import config from '@/config';
import _ from '@/utils';

export default {
  // 切换活动图标的状态
  getActivePage(state: State, id: number) {
    state.activePage.map(item => {
      // 将当前活动的页脚图表点亮
      if (item.id !== id) {
        item.isActived = false;
      } else {
        item.isActived = true;
      }
    });
  },

  // 切换header上图标
  changeHeaderState(state: State, id: number) {
    const { headerInfo } = state;
    switch (id) {
      case 0:
        headerInfo.left = 'letter';
        headerInfo.right = ''; // filter
        headerInfo.title = 'TODAY';
        break;
      case 1:
        headerInfo.left = 'file';
        headerInfo.right = 'new';
        headerInfo.title = '我的习惯';
        break;
      case 2:
        headerInfo.left = '';
        headerInfo.right = ''; // skin
        headerInfo.title = '设置';
        break;
    }
  },

  // 创建习惯
  createHabit(state: State, habit: HabitList) {
    state.habitList.push(habit);
  },
  // 删除未定义好的习惯
  RemoveHabit(state: State) {
    state.habitList.pop();
  },
  // 选择执行的星期
  selectDate(state: State, payload: { habitId: number; id: number }) {
    const list = state.habitList;
    const len = list.length;
    const { RepeatingDate } = _.find(list, payload.habitId)!.habitInfo;

    (RepeatingDate as any[]).forEach(element => {
      if (element.id === payload.id) {
        element.checked = !element.checked;
      }
    });
  },
  // 切换练习的时间段
  changeTimes(state: State, payload: { habitId: number; id: number }) {
    const list = state.habitList;
    const habit = _.find(list, payload.habitId);

    habit!.habitInfo.activeTimes = payload.id;
  },
  // 选择图标背景
  selectColor(state: State, payload: { id: number; color: string }) {
    const list = state.habitList;
    const habit = _.find(list, payload.id);

    habit!.color = payload.color;
  },
  // 选择图标
  selectIcon(state: State, payload: { id: number; icon: string }) {
    const list = state.habitList;
    const habit = _.find(list, payload.id);

    habit!.iconName = payload.icon;
  },
  // 切换提醒时间
  switchRemind(state: State, payload: { habitId: number; id: number }) {
    const list = state.habitList;
    const { remind } = _.find(list, payload.habitId)!.habitInfo;
    (remind as any[]).forEach(item => {
      if (item.id === payload.id) {
        item.isOpen = !item.isOpen;
      }
    });
  },
  // 习惯名称
  changeName(state: State, payload: { id: number; value: string }) {
    const list = state.habitList;
    const habit = _.find(list, payload.id);
    habit!.habitInfo.habitName = payload.value;
  },
  // 绑定激励的话
  changInspire(state: State, payload: { id: number; value: string }) {
    const list = state.habitList;
    const habit = _.find(list, payload.id);
    habit!.habitInfo.inspire = payload.value;
  },
  // 切换习惯当前的状态
  changeMode(state: State, payload: { id: number; value: string }) {
    const list = state.habitList;
    const habit = _.find(list, payload.id);
    habit!.isActive = true;
    habit!.mode = payload.value;
  },
  // 将此习惯归档
  deleteHabit(state: State, id: number) {
    const list = state.habitList;
    const habit = _.find(list, id);
    habit!.isActive = false;
  },
  // 删除此习惯
  removeHabit(state: State, id: number) {
    const list: HabitList[] = state.habitList;
    state.habitList = list.filter(item => item.id !== id);
  },
  // 重新激活此习惯
  activateHabit(state: State, id: number) {
    const list = state.habitList;
    const habit = _.find(list, id);
    habit!.isActive = true;
  },
  // 获取需要当天执行的习惯
  changeCollapse(state: State, activeNames: number[] | never[]) {
    const today = state.today;
    today.active = activeNames;
  },
  // 未添加当日任务的习惯列表进行更新
  updateHabits(state: State, updateList: number[]) {
    const today = moment();
    const newId = _.getDaysId();
    const list = state.habitList;
    for (let index = 0; index < updateList.length; index++) {
      const id = updateList[index];
      const habit = _.find(list, id);

      habit!.habitLog.date.push({
        id: newId,
        time: today,
        isFinished: false,
        message: '',
      });
    }
  },
  // 对习惯的打卡信息进行补签
  supplementHabits(state: State, payload: { id: number; daysId: number }) {
    const list = state.habitList;
    const today = _.getMoment(payload.daysId);
    const habit = _.find(list, payload.id);
    // 储存date信息的数组
    const dateList = habit!.habitLog.date;
    if (dateList.length > 0) {
      for (let index = 0; index < dateList.length; index++) {
        const element = dateList[index];
        if (element.id > payload.daysId) {
          dateList.splice(index, 0, {
            id: payload.daysId,
            time: today,
            isFinished: true,
            message: '',
          });
          habit!.habitLog.currentConsecutiveDays = _.getCurrentMaxDays(
            dateList,
          );

          habit!.habitLog.totalHabitDays++;
          console.log(_.getMaxDays(dateList));

          habit!.habitLog.mostConsecutiveDays = _.getMaxDays(dateList);
          return;
        }
      }
    } else {
      dateList.push({
        id: payload.daysId,
        time: today,
        isFinished: true,
        message: '',
      });
    }
  },
  // 切换当前习惯是否完成
  changeFinished(state: State, payload: { id: number; daysId: number }) {
    const list = state.habitList;
    const habit = _.find(list, payload.id);
    // 储存date信息的数组
    const dateList = habit!.habitLog.date;
    const len = dateList.length;
    // 找到id相关信息
    const date = dateList.find(item => item.id === payload.daysId);
    // 切换完成状态
    date!.isFinished = !date!.isFinished;

    // 当当前信息被切换成"已完成"
    if (date!.isFinished) {
      // 当当前打卡信息属于当天的时候
      if (dateList[len - 1].id === payload.daysId) {
        habit!.habitLog.currentConsecutiveDays++;
      } else {
        habit!.habitLog.currentConsecutiveDays = _.getCurrentMaxDays(dateList);
      }
      habit!.habitLog.totalHabitDays++;
    } else {
      // 当当前打卡信息属于当天的时候
      if (dateList[len - 1].id === payload.daysId) {
        habit!.habitLog.currentConsecutiveDays--;
      } else {
        habit!.habitLog.currentConsecutiveDays = _.getCurrentMaxDays(dateList);
      }
      habit!.habitLog.totalHabitDays--;
      date!.message = '';
    }
    habit!.habitLog.mostConsecutiveDays = _.getMaxDays(dateList);
  },
  // 储存打卡日志
  saveLog(
    state: State,
    payload: { id: number; daysId: number; message: string },
  ) {
    const list = state.habitList;
    const habit = _.find(list, payload.id);
    const day = habit!.habitLog.date.find(item => item.id === payload.daysId);
    day!.message = payload.message;
  },

  // 领取卡片
  receiveCard(state: State) {
    const today = moment();
    // @ts-ignore
    state.today.finishedDate.push(today);
    state.today.isReceived = true;
  },
  // 登陆成功后执行
  loginLoading(state: State, data: any) {
    state.user!.isLogin = 0;
  },
  // 登陆成功后执行
  loginSuccess(state: State, data: any) {
    const currentState = JSON.parse(data.content);

    state.activePage = currentState.activePage;
    state.headerInfo = currentState.headerInfo;
    state.card = currentState.card;
    state.habitList = currentState.habitList;
    state.today = currentState.today;
    state.setting = currentState.setting;

    state.user!.id = data.id;
    state.user!.username = data.username;
    state.user!.url = data.url;
    state.user!.isLogin = 1;
  },
  // 退出登录
  logoutSuccess(state: State) {
    state.user!.id = null;
    state.user!.username = '';
    state.user!.url =
      'https://is4-ssl.mzstatic.com/image/thumb/Purple71/v4/be/13/06/be1306d8-e343-2adb-2b04-9a6884300499/pr_source.jpg/1200x630bb.jpg';
    state.user!.isLogin = -1;
  },
  // 是否开启整点报时
  changeHourly(state: State, checked: boolean) {
    state.setting.checked = checked;
  },
  // 是否同步成功
  sync(state: State, isSync: number) {
    state.user!.isSync = isSync;
  },
};
//srcstoreactions.ts
import { ActionTree } from 'vuex';
import axios from 'axios';

import config from '@/config';
import { login } from '@/api/user';
import { sync } from '@/api/sync';

const actions: ActionTree<any, any> = {
  // 发起登录
  async login({ state, commit }, data) {
    const res: Ajax.AjaxResponse = await login(data)
      .then(res => res.data)
      .catch((e: string) => console.error(e));
    if (res) {
      commit('loginSuccess', res);
    }
  },

  // 数据同步
  async sync({ state, commit }, data) {
    const res: Ajax.AjaxResponse = await sync(data)
      .then(res => res.data)
      .catch((e: string) => console.error(e));
    if (res) {
      commit('sync', 1);
    }
  },
};

export default actions;

接下来看一些views组件

//error.vue
<template>
  <div class="error">
      <router-link class="redirect" to="/">回到首页</router-link>
  </div>
</template>
<style lang="scss" scoped>
@import '../../style/mixin';

.error {
   100vw;
  height: 100vh;
  @include bis('../../assets/404.jpg');
  .redirect {
    display: block;
    position: relative;
    top: 70%;
    color: antiquewhite;
  }
}
</style>
//srcviewsCardCard.vue
<template>
    <div class="card">
        <!-- 新建页面的导航 -->
        <van-nav-bar @click-left="onClickLeft" @click-right="onClickRight">
            <icon name="left-arrow" slot="left" />
            <h3 v-if="title" slot="title">{{title}}</h3>
        </van-nav-bar>
        <!-- 渲染各种新建项目 -->
        <router-view />
    </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { NavBar, Popup, Field, Button, Toast, DatetimePicker } from 'vant';

import utils from '@/utils';

@Component({
  components: {
    [NavBar.name]: NavBar,
    [Popup.name]: Popup,
  },
})
export default class CardPage extends Vue {
  private title?: string;
  private data() {
    return {
      title: this.$route.name,
    };
  }

  private onClickLeft() {
    this.$router.go(-1);
  }
}
</script>

<style src="./style.scss" lang="scss" scoped>
</style>

这些写vuex的方式值得学习

//srcviewsCardReceiveCard.vue
<template>
  <div class="card">
    <!-- 说明 -->
    <section>
      <p v-if="num > 0">{{`你还需要完成${num}个习惯来获得此卡片`}}</p>
      <p v-else>您已完成全部习惯请领取卡片</p>
    </section>
    <!-- 卡片 -->
    <section>
      <Card :saying="saying" :id="id" ></Card>
    </section>
    <!-- 领取按钮 -->
    <section>
      <van-button @click="receive" :disabled="today.isReceived" :class="{ done: isDone }">领取</van-button>
    </section>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
import { Mutation, State } from 'vuex-class';
import { Button, Toast } from 'vant';
import moment from 'moment';

import { HabitList as HabitListState } from '@/store/state';
import Card from '@/components/common/Card/Card.vue';
import _ from '@/utils';

@Component({
  components: {
    [Button.name]: Button,
    Card,
  },
})
export default class Library extends Vue {
  @Mutation private receiveCard!: () => void;
  @State private habitList!: HabitListState[];
  @State
  private today!: {
    active: string[] | never[] | number[];
    finishedDate: string[] | never[];
    isReceived: boolean;
  };

  private title?: string;
  private num!: number;
  private isDone!: boolean;
  private isReceived!: boolean;
  private data() {
    return {
      saying: '自己打败自己是最可悲的失败,自己战胜自己是最可贵的胜利。',
      id: 1,
      num: 0,
      isDone: false,
      isReceived: false,
    };
  }

  private mounted() {
    const id = _.getDaysId();
    this.habitList.forEach(item => {
      item.habitLog.date.filter(ele => ele.id === id).forEach(e => {
        if (!e.isFinished) {
          this.num++;
        }
      });
    });
    if (this.num > 0) {
      this.isDone = false;
    } else {
      this.isDone = true;
    }
  }

  private receive() {
    if (!this.isDone) {
      Toast('请完成全部任务再来领取~');
    } else {
      const { length } = this.today.finishedDate;

      const today = this.today.finishedDate[length - 1];
      if (!length) {
        Toast('领取成功~');
        this.receiveCard();
        this.$router.go(-1);
        return;
      }
      const tip = moment(today).isSame(moment())
        ? '今天您已经领取过了'
        : '领取成功~';
      Toast(tip);
      this.receiveCard();
      this.$router.go(-1);
    }
  }
}
</script>

<style  lang="scss" scoped>
@import '../../../style/mixin';
.card {
  margin: 0;
  height: calc(100vh - 6rem);
   100vw;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}
p {
  @include font(0.8rem, 150%);
  color: #c0c0c0;
}
.done {
  background-color: #34ba3a;
}
</style>

接下来我们根据router.ts结合页面来看代码
router.ts中有利用懒加载的方式去加载,倒是可以看里面的一些懒加载的写法
首先看home.vue
一般使用组件或者引入vuex会引入

import { Component, Vue, Watch } from 'vue-property-decorator';

import { Mutation, State } from 'vuex-class';

子组件通过@Emit向父组件传递事件

//srccomponentscommonClockPopupClockPopup.vue

<template>
    <van-popup v-model="show" @click-overlay="handleHide" :close-on-click-overlay="false">
        <div class="clock">
            <p>打卡日志</p>
            <icon name="target" />
            <div>
                <van-field v-model="message" type="textarea" rows="5" placeholder="写出你想说的话..." />
                <van-button @click="save(message)" class="button" size="small">保存</van-button>
            </div>
        </div>
    </van-popup>
</template>

<script lang="ts">
import { Component, Vue, Watch, Emit, Prop } from 'vue-property-decorator';
import { Field, Popup, Button } from 'vant';
// 组件
@Component({
  components: {
    [Field.name]: Field,
    [Popup.name]: Popup,
    [Button.name]: Button,
  },
})

export default class ClockPopup extends Vue {
  // 这个是父子组件传参数 props的用法
  @Prop() private name?: string;
  @Prop() private show?: boolean;
  private message?: string;

  public data() {
    return {
      message: '',
    };
  }
  // 子组件向父组件传事件有趣有意思
  @Emit('save')
  private save(messages?: string) {
    this.message = '';
  }

  @Watch('show')
  private changeShow(val: boolean, oldVal: boolean) {
    this.show = val;
  }

  @Emit('hide')
  private handleHide() {}
}
</script>

<style src="./style.scss" lang="scss" scoped>
</style>

//srcviewsHomeHome.vue
<template>
  <div class="today">
    <section>
       <!-- dayComputed数据循环 -->
      <van-collapse v-for="(item, index) in dayComputed.habits" :key="item.id" @change="change" v-model="today.active">
        <van-collapse-item :name="index">
          <p slot="title">
            <!-- icon -->
            <icon name="time" />
            <!-- 中间内容 -->
            {{item.title}}</p>
          <aside v-for="ele in item.habits" :key="ele.id" @click="finish(ele.id)">
            <Circles radius="3.5rem" v-if="!!ele.habitLog.date.find(item =>(item.id === days))" :activeColor="!!ele.habitLog.date.find(item =>(item.id === days)).isFinished ? ele.color : '#fff'">
              <icon :name="ele.iconName" slot="icon" />
            </Circles>
          </aside>
        </van-collapse-item>
      </van-collapse>
    </section>
    <!-- 控制显示隐藏? -->
    <!-- 点击van-collapse的时候弹框弹出来 -->
    <ClockPopup :show="show" @save="saveLogs" @hide="hide" />
  </div>
</template>

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

import { Mutation, State } from 'vuex-class';
import { Collapse, CollapseItem } from 'vant';
import { HabitList as HabitListState, TimeSlotList } from '@/store/state';
import _ from '@/utils';

import Circles from '@/components/common/Circle/Circle.vue';
import ClockPopup from '@/components/common/ClockPopup/ClockPopup.vue';
// 从工具函数中获取
export interface NewList {
  habits: HabitListState[] | never[];
  title?: string;
  id?: number;
}
// 引入组件
@Component({
  components: {
    [Collapse.name]: Collapse,
    [CollapseItem.name]: CollapseItem,
    Circles,
    ClockPopup,
  },
})
export default class Today extends Vue {
  // v2ex用法
  @Mutation private createHabit!: (habit: HabitListState) => void;
  @Mutation
  private changeFinished!: (payload: { id: number; daysId: number }) => void;
  @Mutation private changeCollapse!: (habit: number[] | never[]) => void;
  @Mutation private updateHabits!: (updateList: number[]) => void;
  @Mutation
  private saveLog!: (
    payload: { id: number; daysId: number; message: string },
  ) => void;
  @State private habitList!: HabitListState[];
  @State private today!: object;
  private show!: boolean;
  private currentId!: number;
  private isDone!: boolean;
  // 今天距离1970年1.1的天数
  private days!: number;

  private data() {
    return {
      show: false,
      currentId: 0,
      days: _.getDaysId(),
      isDone: false,
    };
  }

  private mounted() {
    const { needUpdate } = this.dayComputed;
    console.log('..........dayComputed',needUpdate)
    const len = needUpdate.length;
    if (len) {
      this.updateHabits(needUpdate);
    }
    this.isDone = true;
  }

  private get dayComputed() {
    const habitsList = this.habitList.filter((item) => item.isActive === true);
    // 今天可用的习惯
    const current = _.dateComparison(habitsList);

    // 检测这些习惯是否产生了当日的任务,如果没有,批量创建
    const needUpdate = [];
    const timeList = new Set();
    for (let index = 0; index < current.length; index++) {
      const element = current[index]; // 单个习惯
      const { timeSlotList, activeTimes } = element.habitInfo;
      const { date } = element.habitLog;

      const len = date.length;
      // 当习惯的历史任务数组里是空那么放入待更新数组,如果最近的历史任务与今日的数字不匹配,说明没有创建今日任务,也放入更新
      if (len === 0) {
        needUpdate.push(element.id);
      } else if (date[len - 1].id !== this.days) {
        needUpdate.push(element.id);
      }
      // @ts-ignore
      const time = timeSlotList.find((e) => e.id === activeTimes);
      // time.habits.push(element);
      timeList.add(time!.title);
    }
    // 今天生效的时间段
    const list = Array.from(timeList);
    // 每一个时间段对应相应的习惯所组成的数组
    const currentList = [];
    for (let index = 0; index < list.length; index++) {
      const element = list[index];
      const newList: NewList = {
        habits: [],
      };
      for (let i = 0; i < current.length; ++i) {
        const item = current[i];
        const { timeSlotList, activeTimes } = item.habitInfo;

        const time = (timeSlotList as TimeSlotList[]).find(
          (e: TimeSlotList) => e.id === activeTimes,
        );
        if (time!.title === element) {
          if (newList.title === time!.title) {
            // @ts-ignore
            newList.habits.push(item);
          } else {
            newList.title = element;
            newList.id = index;
            // @ts-ignore
            newList.habits.push(item);
            currentList.push(newList);
          }
        }
      }
    }

    return {
      current,
      list,
      habits: currentList,
      needUpdate,
    };
  }
// 这个change是做什么啊?
  private change(activeNames: number[] | never[]) {
    this.changeCollapse(activeNames);
  }
  private finish(id: number) {
    this.currentId = id;
    // 如果已经完成那么则是取消操作,否则是标记完成的操作
    if (
      this.habitList
        .find((item) => item.id === id)!
        .habitLog.date.find((item) => item.id === this.days)!.isFinished
    ) {
      // TODO/bug 将Finished重新设置为false时不触发视图更新
      // this.changeFinished({
      //   id,
      //   daysId: this.days,
      // });
    } else {
      this.show = true;
      this.changeFinished({
        id,
        daysId: this.days,
      });
    }
  }
  // 点击保存
  private saveLogs(message: string) {
    const id = _.getDaysId();
    this.saveLog({ id: this.currentId, daysId: id, message });
    this.show = false;
  }
// 一开始show为false
  private hide() {
    this.show = false;
  }
}
</script>

<style lang="scss" scoped>
@import '../../style/mixin';
.today {
  height: calc(100vh - 7rem);
  display: flex;
  justify-content: flex-start;
  flex-direction: column;
  overflow: auto;
  .van-collapse-item {
    margin-bottom: 1rem;
    p {
      display: flex;
      justify-content: flex-start;
      align-items: center;
      @include font(1rem);
      svg {
         1.2rem;
        height: 1.2rem;
        margin-right: 0.5rem;
      }
    }
    aside {
      min-height: 5rem;
      display: inline-flex;
      margin: 0 1rem;
      div {
        border: solid 1px black;
        border-radius: 50%;
         3rem;
        display: flex;
        justify-content: center;
        align-items: center;
        overflow: hidden;
        box-shadow: 0 0 3px 3px rgba(130, 130, 130, 0.3);
        svg {
           3rem;
          height: 3rem;
        }
      }
    }
  }
}
</style>

//srcviewsCardReceiveCard.vue
<template>
  <div class="card">
    <!-- 说明 -->
    <section>
      <p v-if="num > 0">{{`你还需要完成${num}个习惯来获得此卡片`}}</p>
      <p v-else>您已完成全部习惯请领取卡片</p>
    </section>
    <!-- 卡片 -->
    <section>
      <Card :saying="saying" :id="id" ></Card>
    </section>
    <!-- 领取按钮 -->
    <section>
      <van-button @click="receive" :disabled="today.isReceived" :class="{ done: isDone }">领取</van-button>
    </section>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
import { Mutation, State } from 'vuex-class';
import { Button, Toast } from 'vant';
import moment from 'moment';

import { HabitList as HabitListState } from '@/store/state';
import Card from '@/components/common/Card/Card.vue';
import _ from '@/utils';

@Component({
  components: {
    [Button.name]: Button,
    Card,
  },
})
export default class Library extends Vue {
  @Mutation private receiveCard!: () => void;
  @State private habitList!: HabitListState[];
  @State
  private today!: {
    active: string[] | never[] | number[];
    finishedDate: string[] | never[];
    isReceived: boolean;
  };

  private title?: string;
  private num!: number;
  private isDone!: boolean;
  private isReceived!: boolean;
  private data() {
    return {
      saying: '自己打败自己是最可悲的失败,自己战胜自己是最可贵的胜利。',
      id: 1,
      num: 0,
      isDone: false,
      isReceived: false,
    };
  }

  private mounted() {
    const id = _.getDaysId();
    this.habitList.forEach(item => {
      item.habitLog.date.filter(ele => ele.id === id).forEach(e => {
        if (!e.isFinished) {
          this.num++;
        }
      });
    });
    if (this.num > 0) {
      this.isDone = false;
    } else {
      this.isDone = true;
    }
  }

  private receive() {
    if (!this.isDone) {
      Toast('请完成全部任务再来领取~');
    } else {
      const { length } = this.today.finishedDate;

      const today = this.today.finishedDate[length - 1];
      if (!length) {
        Toast('领取成功~');
        this.receiveCard();
        this.$router.go(-1);
        return;
      }
      const tip = moment(today).isSame(moment())
        ? '今天您已经领取过了'
        : '领取成功~';
      Toast(tip);
      this.receiveCard();
      this.$router.go(-1);
    }
  }
}
</script>

<style  lang="scss" scoped>
@import '../../../style/mixin';
.card {
  margin: 0;
  height: calc(100vh - 6rem);
   100vw;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}
p {
  @include font(0.8rem, 150%);
  color: #c0c0c0;
}
.done {
  background-color: #34ba3a;
}
</style>

接下来我们看habit页面

//srccomponentscommonHabitListList.vue
<template>
  <div class="habitList">
    <van-swipe-cell :right-width="65" :left-width="65" class="listSwipe">
      <aside class="edit" v-if="leftValue" slot="left" @click="$emit('click-left', id)" >{{leftValue}}</aside>
      <slot v-else slot="left" @click="$emit('click-right', id)" name="act"></slot>
      <van-cell-group class="listGroup">
        <van-cell   class="van-ellipsis listCell" :url="`/edit/calendar?id=${id}`" :value="habitLog.totalHabitDays + '天'" :style="{ background: color }" >
          <template slot="title">
            <icon :name="iconName" />
            <span>{{habitInfo.habitName}}</span>
          </template>
        </van-cell>
      </van-cell-group>
      <aside class="delete" v-if="rightValue" slot="right" @click="$emit('click-right', id)">{{rightValue}}</aside>
      <slot v-else slot="right" @click="$emit('click-right', id)" name="del"></slot>
    </van-swipe-cell>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Progress, Step, Steps, SwipeCell, Cell, CellGroup } from 'vant';
import { HabitList as HabitListState } from '@/store/state';
@Component({
  components: {
    [Progress.name]: Progress,
    [Step.name]: Step,
    [Steps.name]: Steps,
    [SwipeCell.name]: SwipeCell,
    [Cell.name]: Cell,
    [CellGroup.name]: CellGroup,
  },
})
export default class HabitList extends Vue {
  @Prop() private habitInfo!: object;
  @Prop() private habitLog!: object;
  @Prop() private iconName!: string;
  @Prop() private color!: string;
  @Prop() private id!: number;
  @Prop() private rightValue?: string;
  @Prop() private leftValue?: string;
}
</script>


<style src="./style.scss" lang="scss" scoped>
</style>
//srcviewsHabitHabit.vue
<template>
  <div class="habit">
    <van-tabs @click="changeTitle">
      <van-tab v-for="(item, index) in tabsComputed" :title="item" :key="index">
        <transition-group name="fade" tag="ul" class="list-group">
        <List v-for="item in ChangeTab" :key="item.id" @click-right="del(item.id)" @click-left="edit(item.id)" rightValue="归档" leftValue="编辑" :id="item.id" :color="item.color" :habitLog="item.habitLog" :habitInfo="item.habitInfo" :iconName="item.iconName" />
        </transition-group>
      </van-tab>
    </van-tabs>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Tab, Tabs } from 'vant';
import { State, Mutation } from 'vuex-class';

import { HabitList as HabitListState } from '@/store/state';

import List from '@/components/common/HabitList/List.vue';

@Component({
  components: {
    [Tab.name]: Tab,
    [Tabs.name]: Tabs,
    List,
  },
})
export default class Habit extends Vue {
  @State private habitList!: HabitListState[];
  @Mutation private deleteHabit!: (id: number) => void;
  @Mutation
  private changeMode!: (payload: { id: number; value: string }) => void;
  private currentTitle!: string;
  private data() {
    return {
      currentTitle: '全部',
    };
  }

  private edit(id: number) {
    this.$router.push(`/edit/habit?id=${id}`);
    this.changeMode({ id, value: 'editing' });
  }

  private del(id: number) {
    this.deleteHabit(id);
  }

  // 计算出全部tab标签
  private get tabsComputed() {
    const total: string[] = [];
    (this.habitList as any).forEach((item: any) => {
      item.habitInfo.timeSlotList.forEach((element: any) => {
        if (item.mode === 'done') {
          total.push(element.title);
        }
      });
    });
    const tabs = [...new Set(total)];
    tabs.unshift('全部');

    return tabs;
  }
  private changeTitle(index: number, title: string) {
    this.currentTitle = title;
  }
  // 切换tab后重新计算符合当前标准的列表
  private get ChangeTab() {
    const total: HabitListState[] = [];
    if (this.currentTitle !== '全部') {
      (this.habitList as HabitListState[]).forEach((item: HabitListState) => {
        if (item.isActive && item.mode === 'done') {
          const { activeTimes, timeSlotList } = item.habitInfo;
          // @ts-ignore
          const timeSolt = timeSlotList.find(
            (ele: any) => ele.id === activeTimes,
          );
          if (timeSolt!.title === this.currentTitle) {
            total.push(item);
          }
        }
      });
    } else {
      (this.habitList as HabitListState[]).forEach((item: HabitListState) => {
        if (item.isActive && item.mode === 'done') {
          total.push(item);
        }
      });
    }
    return total;
  }
}
</script>

<style lang="scss" scoped>
@import '../../style/mixin';
.habit {
   100%;
  height: calc(100vh - 7rem);
  display: flex;
  justify-content: flex-start;
  flex-direction: column;
}
</style>
//srcviewsEditRecycleRecycle.vue
<template>
  <div class="recycle">
    <List v-for="item in recycleList" :key="item.id" :id="item.id" :color="item.color" :habitLog="item.habitLog" :habitInfo="item.habitInfo" :iconName="item.iconName">
      <aside slot="del" @click="remove(item.id)" >删除</aside><aside @click="activate(item.id)" class="act" slot="act">激活</aside>
    </List>
  </div>
</template>

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

import { HabitList as HabitListState } from '@/store/state';

import List from '@/components/common/HabitList/List.vue';

@Component({
  components: {
    List,
  },
})
export default class Recycle extends Vue {
  @State private habitList!: HabitListState[];
  @Mutation private removeHabit!: (id: number) => void;
  @Mutation private activateHabit!: (id: number) => void;
  private currentTitle!: string;

  public activate(id: number) {
    this.activateHabit(id);
  }

  public remove(id: number) {
    this.removeHabit(id);
  }

  private onClick(index: number, title: string) {
    this.currentTitle = title;
  }
  // 获取被归档的列表
  private get recycleList() {
    const total: any[] = [];
    (this.habitList as any).forEach((item: HabitListState) => {
      if (!item.isActive) {
        total.push(item);
      }
    });
    return total;
  }
}
</script>

<style lang="scss" scoped>
@import '../../../style/mixin';
.recycle {
  overflow: auto;
   100%;
  height: calc(100vh - 7rem);
  display: flex;
  flex: none;
  justify-content: flex-start;
  flex-direction: column;
  aside {
    display: inline-flex;
    background-color: $warn;
    @include font(0.9rem);
    color: #fff;
     3rem;
    height: 100%;
    margin: 0;
    justify-content: center;
    align-items: center;
  }
  .act {
    background-color: $edit;
  }
}
</style>

//srcviewsNewLibraryLibrary.vue
<template>
  <div class="habit">
    <!-- 新建说明 -->
    <section>
      <p>
        您可以从习惯库中挑选一个习惯,也可以新建一个新的习惯。
      </p>
    </section>
    <!-- 新建习惯 -->
    <section>
      <h4>自定义习惯</h4>
      <router-link :to="{path:'habit',query:{id: newHabit.id}}">
        <van-cell @click="create(newHabit.id)" :value="newHabit.title">
          <icon :name="newHabit.name" slot="icon" />
        </van-cell>
      </router-link>
    </section>
    <!-- 习惯库 -->
    <section>
      <h4>从库中挑选习惯</h4>
      <van-list>
          <van-cell v-for="item in habitLibrary" :key="item.id" @click="create(item.id)" :value="item.title">
            <icon :name="item.name" slot="icon" />
          </van-cell>
      </van-list>
    </section>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { List, Cell } from 'vant';
import { Mutation } from 'vuex-class';
import moment from 'moment';

import config from '@/config';
import { HabitList as HabitListState } from '@/store/state';
@Component({
  components: {
    [List.name]: List,
    [Cell.name]: Cell,
  },
})
export default class Library extends Vue {
  private title?: string;
  private habitLibrary!: object[];
//  type Callback = () => void;
// function render(callback: Callback):string{}
  @Mutation private createHabit!: (habit: HabitListState) => void;
  private data() {
    return {
      title: this.$route.name,
      habitLibrary: (config as any).habitLibrary,
      newHabit: (config as any).newHabit,
    };
  }

  private create(id: number) {
    const timestamp = new Date().valueOf();
    const iconInfo =
      id === 0
        ? config.newHabit
        : config.habitLibrary.find(item => item.id === id);
    const habit = {
      id: timestamp,
      iconName: iconInfo!.name,
      color: '#ffe884',
      mode: 'creating',
      isActive: false,
      habitInfo: {
        // 习惯名称
        habitName: iconInfo!.title,
        // 重复练习的日期
        RepeatingDate: [
          { id: 0, date: '星期一', checked: true },
          { id: 1, date: '星期二', checked: true },
          { id: 2, date: '星期三', checked: true },
          { id: 3, date: '星期四', checked: true },
          { id: 4, date: '星期五', checked: true },
          { id: 5, date: '星期六', checked: true },
          { id: 6, date: '星期日', checked: true },
        ],
        // 练习的时间段
        activeTimes: 0,
        timeSlotList: [
          {
            id: 0,
            isActive: true,
            title: '起床之后',
          },
          {
            id: 1,
            isActive: false,
            title: '晨间习惯',
          },
          {
            id: 2,
            isActive: false,
            title: '中午时分',
          },
          {
            id: 3,
            isActive: false,
            title: '午间习惯',
          },
          {
            id: 4,
            isActive: false,
            title: '晚间习惯',
          },
          {
            id: 5,
            isActive: false,
            title: '睡觉之前',
          },
          {
            id: 6,
            isActive: false,
            title: '任意时间',
          },
        ],
        // 提醒的时间
        remind: [],
        // 激励自己的话
        inspire: '',
      },
      habitLog: {
        // 总共坚持练习了多少天
        totalHabitDays: 0,
        // 当前连续联系了多少天
        currentConsecutiveDays: 0,
        // 历史上最多连续练习多少天
        mostConsecutiveDays: 0,
        // 创建日期
        createdTime: moment(timestamp).format('YYYY-MM-DD'),
        // 创建此习惯至今多少天
        totalDays: parseInt(moment(timestamp).fromNow(true)),
        // 坚持的日期
        date: [],
      },
    };
    this.createHabit(habit);
    this.$router.push(`/new/habit?id=${id}`);
  }
}
</script>

<style src="./style.scss" lang="scss" scoped>
</style>

//srcviewsNewHabitHabit.vue
<template>
  <div class="habit" v-if="!!habitList[index]">
    <!-- 习惯图标 -->
    <section class="icon">
      <router-link v-if="!!colorComputed" :to="{path:'/edit/icon/',query:{mode}}">
        <div class="cir">
          <Circles radius="3.5rem" :activeColor="colorComputed">
            <icon :name="iconComputed" slot="icon" />
          </Circles>
        </div>
      </router-link>
    </section>
    <!-- 输入习惯名称 -->
    <section class="field">
      <van-field v-model="nameComputed" placeholder="请输入名称" />
    </section>
    <!-- 习惯设置 -->
    <section>
      <van-cell-group>
        <van-cell clickable is-link center @click="handleShow" title="习惯的重复" :value="dateComputed.value" />
        <router-link :to="{path:'/edit/times/',query:{mode: 'new'}}">
          <van-cell center title="重复的时段" :value="repeatComputed" />
        </router-link>
        <router-link :to="{path:'/edit/remind/',query:{mode: 'new'}}">
          <van-cell center title="提醒的时间" :value="`${remindComputed}个提醒`" />
        </router-link>
        <van-cell center title="激励的话">
          <input v-model="inspireComputed" placeholder="输入激励的话" />
        </van-cell>
      </van-cell-group>
      <van-popup v-model="show" position="right">
        <h2>选择重复的日期</h2>
        <p>您希望在一周里那几天执行这个习惯?</p>
        <aside>
          <DateBlock v-for="(item) in dateComputed.dates" :key="item.id" :checked="item.checked" :title="item.date" @click.native="select(item.id)" />
        </aside>
        <van-button @click="handleShow" size="large">保存</van-button>
      </van-popup>
    </section>
    <van-button @click="handleNew" class="button" size="large">{{mode === 'new' ? '新建' : '保存'}}</van-button>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Field, Cell, CellGroup, Popup, Button } from 'vant';
import { State, Mutation } from 'vuex-class';

import Circles from '@/components/common/Circle/Circle.vue';
import DateBlock from '@/components/common/DateBlock/DateBlock.vue';
import config from '@/config';
import _ from '@/utils';
import { HabitList as HabitListState } from '@/store/state';

@Component({
  components: {
    [Field.name]: Field,
    [Cell.name]: Cell,
    [CellGroup.name]: CellGroup,
    [Popup.name]: Popup,
    [Button.name]: Button,
    DateBlock,
    Circles,
  },
})
export default class Habit extends Vue {
  @State private habitList!: HabitListState[];
  @Mutation
  private selectDate!: (payload: { habitId: number; id: number }) => void;
  @Mutation
  private changeName!: (payload: { id: number; value: string }) => void;
  @Mutation
  private changInspire!: (payload: { id: number; value: string }) => void;
  @Mutation
  private changeMode!: (payload: { id: number; value: string }) => void;
  private show!: boolean;
  private value?: string;
  private name?: string;
  private habitLibrary!: object[];
  private id!: number;
  private index!: number;
  private mode!: string;
  private data() {
    const id: number = parseInt((this.$route.query.id) as string, 10);
    const mode = id > config.habitLibrary.length ? 'edit' : 'new';
    return {
      name,
      value: '',
      show: false,
      mode,
    };
  }

  // 获取当前习惯的id
  private created() {
    if (this.mode === 'edit') {
      this.id = parseInt((this.$route.query.id) as string, 10);
      // @ts-ignore
      const Index = _.findIndex(this.habitList, this.id);
      this.index = Index!;
      return;
    }
    const list = this.habitList;
    for (let index = 0; index < list.length; index++) {
      const element = list[index];
      if (element.mode === 'creating') {
        this.id = element.id;
        this.index = index;
        return;
      }
    }
    this.id = -1;
  }

  private get nameComputed() {
    const habit = this.habitList[this.index];

    return habit.habitInfo.habitName;
  }
  private set nameComputed(name) {
    this.changeName({ id: this.id, value: name });
  }
  private get inspireComputed() {
    const habit = this.habitList[this.index];
    return habit.habitInfo.inspire;
  }
  private set inspireComputed(name) {
    this.changInspire({ id: this.id, value: name });
  }

  // 计算当前图标
  private get iconComputed() {
    const habit = this.habitList[this.index];
    return habit.iconName;
  }
  // 计算重复时间段
  private get repeatComputed() {
    const { activeTimes, timeSlotList } = this.habitList[this.index].habitInfo;
    // @ts-ignore
    return timeSlotList.find((item: any) => item.id === activeTimes).title;
  }
  // 计算提醒个数
  private get remindComputed() {
    const { remind } = this.habitList[this.index].habitInfo;
    const num = (remind as any[]).filter((item) => item.open === true).length;
    return num;
  }
  // 计算当前颜色
  private get colorComputed() {
    const habit = this.habitList[this.index];
    const color = habit && habit.color ? habit.color : '#fff';

    return color;
  }
  // 通过计算属性获取当前每周哪几天需要重复训练
  private get dateComputed() {
    const dates = this.habitList[this.index].habitInfo.RepeatingDate;
    const currentDates = [];
    let value: string = '';
    for (let i = 0; i < dates.length; i++) {
      if (dates[i].checked) {
        currentDates.push(dates[i]);
        const result = _.getDate(dates[i].date);
        value += result;
      }
    }

    return {
      value,
      dates,
      currentDates,
    };
  }
  // 对话框控制
  private handleShow() {
    this.show = !this.show;
  }
  // 重复的日期选择
  private select(id: number) {
    this.selectDate({ habitId: this.id, id });
  }

  // 创建此习惯
  private handleNew() {
    this.changeMode({ id: this.id, value: 'done' });
    if (this.mode === 'edit') {
      this.$router.go(-1);
      return;
    }
    this.$router.go(-2);
  }
}
</script>

<style src="./style.scss" lang="scss" scoped>
</style>

//srcviewsEditIconSettingIconSetting.vue
<template>
    <div class="iconSetting">
        <!-- 当前图表 -->
        <section class="icon">
            <Circles class="cir" radius="3.5rem" :activeColor="colorComputed">
                <icon :name="iconComputed" slot="icon" />
            </Circles>
        </section>
        <!-- 备选图标 -->
        <section class="alternative">
            <div class="alternativeIcon" v-for="(item, index) in iconSetting" :key="index" @click="handleIcon(item)">
                <icon :name="item" />
            </div>
        </section>
        <!-- 图标背景 -->
        <section class="colorSetting">
            <div class="background" v-for="(item, index) in colorSetting" :key="index" @click="handleColor(item)">
                <div v-bind:style="{ backgroundColor: item }"></div>
            </div>
        </section>
    </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { SwipeCell, Cell, CellGroup } from 'vant';
import { State, Mutation } from 'vuex-class';

import config from '@/config';
import Circles from '@/components/common/Circle/Circle.vue';
import { HabitList as HabitListState } from '@/store/state';

@Component({
  components: {
    [SwipeCell.name]: SwipeCell,
    [Cell.name]: Cell,
    [CellGroup.name]: CellGroup,
    Circles,
  },
})
export default class IconSetting extends Vue {
  @State private habitList!: HabitListState[];
  @Mutation
  private selectColor!: (payload: { id: number; color: string }) => void;
  @Mutation
  private selectIcon!: (payload: { id: number; icon: string }) => void;
  private id!: number;
  private index!: number;

  private iconSetting!: string[];
  private colorSetting!: string[];
  private data() {
    return {
      iconSetting: (config as any).iconSetting,
      colorSetting: (config as any).colorSetting,
    };
  }
  // 获取当前习惯的id
  private created() {
    const list = this.habitList;
    for (let index = 0; index < list.length; index++) {
      const element = list[index];
      if (element.mode === 'creating' || element.mode === 'editing') {
        this.id = element.id;
        this.index = index;
        return;
      }
    }
    this.id = -1;
  }
  // 计算当前icon名称
  private get iconComputed() {
    const len = this.habitList.length;
    const iconName = this.habitList[this.index].iconName;
    return iconName;
  }
  // 计算当前背景颜色
  private get colorComputed() {
    const len = this.habitList.length;
    const { color } = this.habitList[this.index];

    return color;
  }
  private handleColor(color: string) {
    this.selectColor({ id: this.id, color });
  }
  private handleIcon(name: string) {
    this.selectIcon({ id: this.id, icon: name });
  }
}
</script>

<style src="./style.scss" lang="scss" scoped>
</style>

//srcviewsEditTimesTimes.vue
<template>
  <div class="times">
    <!-- 说明文字 -->
    <section class="panel">
        <p>大致在一天中哪个时间段执行这个习惯呢?</p>
    </section>
    <!-- 删除按钮 -->
    <section class="list">
        <van-radio-group v-model='radio' @change="change">
            <van-cell-group>
                <van-cell v-for="item in timesComputed.timeSlotList" :key="item.id">
                    <van-radio :name="item.id">{{item.title}}</van-radio>
                </van-cell>
            </van-cell-group>
        </van-radio-group>
    </section>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Radio, Cell, CellGroup, RadioGroup } from 'vant';
import { State, Mutation } from 'vuex-class';

import { HabitList as HabitListState } from '@/store/state';

@Component({
  components: {
    [Radio.name]: Radio,
    [Cell.name]: Cell,
    [CellGroup.name]: CellGroup,
    [RadioGroup.name]: RadioGroup,
  },
})
export default class Calendar extends Vue {
  @State private habitList!: HabitListState[];
  @Mutation
  private changeTimes!: (payload: { habitId: number; id: number }) => void;
  private radio!: number;
  private id!: number;
  private index!: number;
  public data() {
    return {
      radio: -1,
    };
  }

  // 加载完毕后将radio重新赋值
  public mounted() {
    this.radio = this.timesComputed.radio;

    const list = this.habitList;
    for (let index = 0; index < list.length; index++) {
      const element = list[index];
      if (element.mode === 'creating' || element.mode === 'editing') {
        this.id = element.id;
        this.index = index;
        return;
      }
    }
    this.id = -1;
  }

  // 计算当前时间段的状态
  private get timesComputed() {
    const len = this.habitList.length;
    const habit = this.habitList[len - 1];
    const activeTimes = habit.habitInfo;
    return {
      timeSlotList: habit.habitInfo!.timeSlotList,
      radio: habit.habitInfo.activeTimes,
    };
  }

  // 选择时段后触发vuex进行变动
  private change(id: number) {
    this.changeTimes({ habitId: this.id, id });
  }
}
</script>

<style src="./style.scss" lang="scss" scoped>
</style>

//srcviewsNewHabitHabit.vue
 <section>
      <van-cell-group>
        <van-cell clickable is-link center @click="handleShow" title="习惯的重复" :value="dateComputed.value" />
        <router-link :to="{path:'/edit/times/',query:{mode: 'new'}}">
          <van-cell center title="重复的时段" :value="repeatComputed" />
        </router-link>
        <router-link :to="{path:'/edit/remind/',query:{mode: 'new'}}">
          <van-cell center title="提醒的时间" :value="`${remindComputed}个提醒`" />
        </router-link>
        <van-cell center title="激励的话">
          <input v-model="inspireComputed" placeholder="输入激励的话" />
        </van-cell>
      </van-cell-group>
      <van-popup v-model="show" position="right">
        <h2>选择重复的日期</h2>
        <p>您希望在一周里那几天执行这个习惯?</p>
        <aside>
          <DateBlock v-for="(item) in dateComputed.dates" :key="item.id" :checked="item.checked" :title="item.date" @click.native="select(item.id)" />
        </aside>
        <van-button @click="handleShow" size="large">保存</van-button>
      </van-popup>
    </section>

//srcviewsEditRemindRemind.vue
<template>
  <div class="remind">
    <!-- 说明文字 -->
    <section class="panel">
      <p>您打算在那个时间设置提醒呢?</p>
    </section>
    <!-- 提醒列表 -->
    <section class="list">
      <van-cell-group v-if="!!remindComputed.length">
        <van-cell v-for="item in remindComputed" :key="item.id">
          <van-switch-cell :title="item.remind" v-model="item.isOpen" @change="change" />
        </van-cell>
      </van-cell-group>
    </section>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { SwitchCell, Cell, CellGroup, Toast } from 'vant';
import { State, Mutation } from 'vuex-class';

import { HabitList as HabitListState } from '@/store/state';
import config from '@/config';
import { Payload } from '_vuex@3.0.1@vuex';

@Component({
  components: {
    [SwitchCell.name]: SwitchCell,
    [Cell.name]: Cell,
    [CellGroup.name]: CellGroup,
  },
})
export default class Remind extends Vue {
  @State private habitList!: HabitListState[];
  @Mutation
  private switchRemind!: (payload: { habitId: number; id: number }) => void;
  private id!: number;

  // 获取当前习惯的id
  private mounted() {
    const list = this.habitList;
    for (let index = 0; index < list.length; index++) {
      const element = list[index];
      // 如果在编辑或者在新建那一定是当前习惯了
      if (element.mode === 'creating' || element.mode === 'editing') {
        this.id = element.id;
        return;
      }
    }
    this.id = -1;
  }

  // 计算属性得到remind相关数据
  private get remindComputed() {
    const len = this.habitList.length;
    const habit = this.habitList[len - 1];

    return habit.habitInfo.remind;
  }
  // 切换switch按钮的状态
  private change(id: number) {
    if (this.id < 0) {
      Toast({
        type: 'fail',
        message: '可能出错了',
      });
    } else {
      this.switchRemind({ habitId: this.id, id });
    }
  }
}
</script>

<style src="./style.scss" lang="scss" scoped>
</style>

//srcviewsSettingSetting.vue
<template>
  <div class="setting">
      <!-- 头像 -->
      <section class="avatar">
      <vue-lazy-component :timeout="1000">
        <router-link :to="{path:'/login'}">
          <div>
            <img v-if="user.url" :src="user.url" alt="头像">
            <icon v-else name="user" />
            <p v-if="user.username">{{user.username}}</p>
          </div>
        </router-link>
        <Skeleton slot="skeleton"/>
        </vue-lazy-component>
      </section>
    
    <!-- 系统设置 -->
      <section>
        <van-cell-group>
            <van-cell clickable title="数据备份" @click="globelSync" :is-link="false" />
            <van-switch-cell v-model="setting.checked"  @change="change" title="整点报时" />
        </van-cell-group>
      </section>
        <!-- 反馈与商店 -->
      <section>
        <van-cell-group>
            <van-cell title="主题商店" @click="handleToast" is-link />
            <router-link :to="{path:'/feedback'}">
              <van-cell title="给作者反馈" is-link />
            </router-link>
            <router-link :to="{path:'/update'}">
              <van-cell title="更新日志" is-link />
            </router-link>
        </van-cell-group>
      </section>
    <section>
      <van-button v-if="user.id" bottom-action @click="logout" >退出登录</van-button>
    </section>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Cell, CellGroup, SwitchCell, Toast, Button } from 'vant';
import { State, Mutation, Action, Getter } from 'vuex-class';
import { SettingState, UserState } from '@/store/state';

import Skeleton from '@/components/common/Skeleton/SkeletonCircle.vue';

@Component({
  components: {
    [Cell.name]: Cell,
    [CellGroup.name]: CellGroup,
    [SwitchCell.name]: SwitchCell,
    [Button.name]: Button,
    Skeleton,
  },
})
export default class Setting extends Vue {
  @State private setting!: SettingState;
  @State private user!: UserState;
  @Getter private syncData: any;
  @Mutation private changeHourly!: (checked: boolean) => void;
  @Mutation private logoutSuccess!: () => void;
  @Action private sync!: (data: any) => void;
  private isOpen!: boolean;
  public data() {
    return {
      isOpen: false,
      checked: false,
    };
  }

  private change(checked: boolean) {
    this.changeHourly(checked);
  }

  private globelSync() {
    if (!this.user.id) {
      Toast('请先登录');
      return;
    }
    this.sync({
      syncData: this.syncData,
      id: this.user.id,
    });

    if (this.user.isSync === 1) {
      Toast('同步成功');
    }
  }

  private logout() {
    // 先同步再退出
    this.globelSync();
    this.logoutSuccess();
    // 清楚本地缓存
    localStorage.removeItem('vuex');
  }

  private handleToast() {
    Toast('敬请期待!');
  }
}
</script>
<style lang="scss" scoped>
@import '../../style/mixin';

section {
   100%;
  text-align: left;
}

.setting {
  height: calc(100vh - 7rem);
   100%;
  display: flex;
  justify-content: space-around;
  align-items: center;
  flex-direction: column;
  .avatar {
     4.5rem;
    height: 4.5rem;
    margin-bottom: 1rem;
    img {
       100%;
      height: 100%;
      @include borderRadius(50%);
    }
  }
  svg {
     5rem;
    height: 5rem;
  }
  .van-button {
    background-color: $warn;
  }
  .van-cell {
    display: flex;
    // justify-content: flex-start;
    // flex-direction: row;
    // align-items: center;
  }
}
</style>

//srcviewsLoginLogin.vue
<template>
    <div class="login">
        <!-- 导航 -->
        <section>
            <van-nav-bar @click-left="onClickLeft">
                <icon name="left-arrow" slot="left" />
                <h3 v-if="title" slot="title">{{title}}</h3>
            </van-nav-bar>
        </section>
        <!-- 登录 -->
        <main>
            <section>
                <van-cell-group>
                    <van-field v-model="username" label="用户名" icon="clear" placeholder="请输入用户名" required @click-icon="username = ''" />
                    <van-field v-model="password" type="password" label="密码" placeholder="请输入密码" required />
                </van-cell-group>
            </section>
            <van-button @click="handleLogin" size="small" type="primary">登录</van-button>
            <van-loading  v-if="user.isLogin===0" type="spinner" color="black" />
        </main>
    </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
import { NavBar, Field, CellGroup, Button, Toast, Loading } from 'vant';
import { Mutation, State, Action } from 'vuex-class';

import { UserState } from '@/store/state';

@Component({
  components: {
    [NavBar.name]: NavBar,
    [Field.name]: Field,
    [CellGroup.name]: CellGroup,
    [Button.name]: Button,
    [Loading.name]: Loading,
  },
})
export default class Login extends Vue {
  @Action public login!: (data: { username: string; password: string }) => void;
  @Mutation private loginLoading!: () => void;
  @State private user!: UserState;
  private message?: string;
  private title!: string;
  private username!: string;
  private password!: string;
  public data() {
    return {
      username: '',
      title: this.$route.name,
      password: '',
    };
  }

  @Watch('user', { immediate: true, deep: true })
  private onUserChanged(val: UserState, oldVal: UserState) {
    if (val.isLogin === 1) {
      this.$router.go(-1);
    }
  }

  private handleLogin() {
    const { username, password } = this;
    if (!username || !password) {
      Toast('请输入完整的用户名和密码');
    } else {
      this.login({ username, password });
      this.loginLoading();
    }
  }
  private onClickLeft() {
    this.$router.go(-1);
  }
}
</script>

<style lang="scss" scoped>
@import '../../style/mixin';
.van-nav-bar {
  height: 3.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  svg {
    @include iconSize(1.4rem);
  }
}

main {
  section {
    margin-bottom: 3rem;
  }
  height: calc(50vh - 6rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
</style>

//srcviewsFeedbackFeedback.vue
<template>
    <div class="feedback">
        <!-- 导航 -->
        <section>
            <van-nav-bar @click-left="onClickLeft">
                <icon name="left-arrow" slot="left" />
                <h3 v-if="title" slot="title">{{title}}</h3>
            </van-nav-bar>
        </section>
        <!-- 输入框 -->
        <section>
            <p>留言板</p>
            <van-field v-model="message" type="textarea" placeholder="请输入留言" rows="4" autosize />
        </section>
        <!-- 确认发送 -->
        <section>
            <van-button :loading="loading" @click="send" size="small" >发送</van-button>
        </section>
        <van-cell title="给作者反馈" is-link />
    </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Field, Button, NavBar, Toast, Cell } from 'vant';
import { State, Mutation } from 'vuex-class';
import { SettingState, UserState } from '@/store/state';
import { feedback } from '@/api/feedback';

@Component({
  components: {
    [Cell.name]: Cell,
    [Field.name]: Field,
    [Button.name]: Button,
    [NavBar.name]: NavBar,
  },
})
export default class Feedback extends Vue {
  @State private user!: UserState;
  private message?: string;
  private title!: string;
  private loading!: boolean;
  public data() {
    return {
      message: '',
      title: this.$route.name,
      loading: false,
    };
  }
  private onClickLeft() {
    this.$router.go(-1);
  }
  private async send() {
    if (this.message) {
      const createTime = new Date().valueOf();
      const res = await feedback({
        content: this.message,
        createTime,
        username: this.user.username,
      })
        .then(res => res.data)
        .catch((e: string) => Toast(e));

      if (res.message) {
        Toast(res.message);
        this.$router.go(-1);
      }
    } else {
      Toast('请补充完反馈信息');
    }
  }
}
</script>

<style lang="scss" scoped>
@import '../../style/mixin';
.van-nav-bar {
  height: 3.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  svg {
    @include iconSize(1.4rem);
  }
}
</style>

//srcviewsUpdateLogUpdateLog.vue
<template>
    <div class="log">
        <!-- 导航 -->
        <section>
            <van-nav-bar @click-left="onClickLeft">
                <icon name="left-arrow" slot="left" />
                <h3 v-if="title" slot="title">{{title}}</h3>
            </van-nav-bar>
        </section>
        <!-- 日志 -->
        <section>
        <ul>
            <li>0.0.1: 初版,目前已完成90%的功能,即将可以正常使用.</li>
            <li></li>
        </ul>
        </section>
    </div>
</template>

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

@Component({
  components: {
    [NavBar.name]: NavBar,
  },
})
export default class UpdateLog extends Vue {
  private message?: string;
  private title!: string;
  private loading!: boolean;
  public data() {
    return {
      message: '',
      title: this.$route.name,
      loading: false,
    };
  }
  private onClickLeft() {
    this.$router.go(-1);
  }
}
</script>

<style lang="scss" scoped>
@import '../../style/mixin';
.van-nav-bar {
  height: 3.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  svg {
    @include iconSize(1.4rem);
  }
}
</style>

这个算是我看的第一个ts+vue的项目,因为我学过ts的语法,基本ts那部分是看的懂的,不过我看不懂业务逻辑,一些里面的内容我不明白为什么那样写。总之好好加油吧
对了这个里面学到的东西有
1.和react一样使用的是class继承的方式写组件
2.vue中使用了npm包专门用来引入组件和进行vuex部分的处理

import { Component, Vue } from 'vue-property-decorator';
import { State, Mutation } from 'vuex-class';

3.vue中即使用ts语法,也只是增加的类型修饰,逻辑部分其实同es6类似
4.我觉得vue同react相比,就是vue多了一个getter,而react是可以直接setState()进行改变数据的。
5.项目中用到了es6的装饰器语法,结合ts使用,显得技术很高端,还有父子组件传参等一系列的语法改变了。
下面是一些语法的改变,学习自博客:https://juejin.im/post/5c173a84f265da610e7ffe44
在Vue中使用TypeScript时,非常好用的一个库,使用装饰器来简化书写。
1.安装npm install --save vue-property-decorator

@Component (from vue-class-component)
@Prop
@Model
@Watch
@Emit
@Inject
@Provide
Mixins (the helper function named mixins defined at vue-class-component)

2、@Component

import {componentA,componentB} from '@/components';

export default{
    components:{
        componentA,
        componentB,
    },
    directives: {
        focus: {
            // 指令的定义
            inserted: function (el) {
                el.focus()
            }
        }
    }
}
import {Component,Vue} from 'vue-property-decorator';
import {componentA,componentB} from '@/components';

 @Component({
    components:{
        componentA,
        componentB,
    },
    directives: {
        focus: {
            // 指令的定义
            inserted: function (el) {
                el.focus()
            }
        }
    }
})
export default class YourCompoent extends Vue{
   
}

3、@Prop 父子组件之间值的传递

export default{
    props:{
        propA:String, // propA:Number
        propB:[String,Number],
        propC:{
            type:Array,
            default:()=>{
                return ['a','b']
            },
            required: true,
            validator:(value) => {
                return [
                    'a',
                    'b'
                 ].indexOf(value) !== -1
        }
    }
}
}

import {Component,Vue,Prop} from vue-property-decorator;

@Component
export default class YourComponent extends Vue {
    @Prop(String)
    propA:string;
    
    @Prop([String,Number])
    propB:string|number;
    
    @Prop({
     type: String, // type: [String , Number]
     default: 'default value', // 一般为String或Number
      //如果是对象或数组的话。默认值从一个工厂函数中返回
      // defatult: () => {
      //     return ['a','b']
      // }
     required: true,
     validator: (value) => {
        return [
          'InProcess',
          'Settled'
        ].indexOf(value) !== -1
     }
    })
    propC:string;
    
    
}

4、@Model (组件之间,checkbox)
父组件中使用 v-model="checked" 子组件

<input  type="checkbox" :checked="checked" @change="change">

js写法 (2.2.0+ 新增)

 export default {
     model:{
         prop:'checked',
         event:'change'
     },
     props:{
         checked:{
             type:Boolean
         }
     },
     methods:{
         change(e){
             this.$emit('change', e.target.checked)
         }
     }
 }
import {Vue,Component,Model,Emit} from 'vue-property-decorator';

@Component
export default class YourComponent extends Vue{

    @Model('change',{
        type:Boolean
    })
    checked!:boolean;
    
    @Emit('change')
    change(e:MouseEvent){}
    
}

5、@Watch

export default {
  watch: {
    'person': {
      handler: 'onPersonChanged',
      immediate: true,
      deep: true
    }
  },
  methods: {
    onPersonChanged(val, oldVal) { }
  }
}
import {Vue, Component, Watch} from 'vue-property-decorator';

@Component
export default class YourComponent extends Vue{
    @Watch('person', { immediate: true, deep: true })
    onPersonChanged(val: Person, oldVal: Person) { }
}

6、@Emit
由@Emit $emit 定义的函数发出它们的返回值,后跟它们的原始参数。 如果返回值是promise,则在发出之前将其解析。
如果事件的名称未通过事件参数提供,则使用函数名称。 在这种情况下,camelCase名称将转换为kebab-case。

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    addToCount(n) {
      this.count += n
      this.$emit('add-to-count', n)
    },
    resetCount() {
      this.count = 0
      this.$emit('reset')
    },
    returnValue() {
      this.$emit('return-value', 10)
    },
    promise() {
      const promise = new Promise(resolve => {
        setTimeout(() => {
          resolve(20)
        }, 0)
      })

      promise.then(value => {
        this.$emit('promise', value)
      })
    }
  }
}
import { Vue, Component, Emit } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  count = 0

  @Emit()
  addToCount(n: number) {
    this.count += n
  }

  @Emit('reset')
  resetCount() {
    this.count = 0
  }

  @Emit()
  returnValue() {
    return 10
  }

  @Emit()
  promise() {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(20)
      }, 0)
    })
  }
}

7、@Provide 提供 / @Inject 注入
注:父组件不便于向子组件传递数据,就把数据通过Provide传递下去,然后子组件通过Inject来获取

const symbol = Symbol('baz')

export const MyComponent = Vue.extend({

  inject: {
    foo: 'foo',
    bar: 'bar',
    'optional': { from: 'optional', default: 'default' },
    [symbol]: symbol
  },
  data () {
    return {
      foo: 'foo',
      baz: 'bar'
    }
  },
  provide () {
    return {
      foo: this.foo,
      bar: this.baz
    }
  }
})

import {Vue,Component,Inject,Provide} from 'vue-property-decorator';

const symbol = Symbol('baz')

@Component
export defalut class MyComponent extends Vue{
    @Inject()
    foo!: string;
    
    @Inject('bar')
    bar!: string;
    
    @Inject({
        from:'optional',
        default:'default'
    })
    optional!: string;
    
    @Inject(symbol)
    baz!: string;
    
    @Provide()
    foo = 'foo'
    
    @Provide('bar')
    baz = 'bar'
}

原文地址:https://www.cnblogs.com/smart-girl/p/11243712.html