zoukankan      html  css  js  c++  java
  • 【心无旁骛】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'
    }
    
    
  • 相关阅读:
    Sql Sugar
    GoLang 环境部署
    Typora 自动添加序号
    C# 操作 Oracle批量执行Insert Blob
    C# 生成读取二维码
    Asp.net core 使用Serilog记录日志
    Asp.net Core 将日志输出到文件
    云原生领域的一些技术展望
    C# BeginInvoke用法记录
    C# 委托及线程
  • 原文地址:https://www.cnblogs.com/smart-girl/p/11243712.html
Copyright © 2011-2022 走看看