这是一个非常有意思的项目,我们先来看看效果
这个项目所用的技术也比较有意思,它的技术栈为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-width: 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
//src\components\common\Icon\HeaderIcon.ts import { Component, Prop, Vue } from 'vue-property-decorator'; import template from './Icon.vue'; @Component({ name: 'HeaderIcon', mixins: [template], }) // 使用ts封装的src\components\common\Icon\HeaderIcon.ts组 export default class FooterIcon extends Vue { @Prop() private name!: string; @Prop() private path!: string; private data() { return { isTouched: false, }; } }
我们来看一下FooterIcon.ts
//src\components\common\Icon\FooterIcon.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封装的src\components\common\Icon\FooterIcon.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也做的封装
//src\components\common\Icon\Icon.vue <template> <section> <router-link v-if="!!path" :to="path"> <span @click="changeActivePage"> <icon :name="!isActived ? name.defaultName : name.activedName" style="width: 2rem; height:2rem"></icon> <p :class="{active: isActived}">{{tagName}}</p> </span> </router-link> <div v-else class="headerIcon"> <icon :name="name" style="width: 1.6rem; height:1.8rem"> </icon> </div> </section> </template> <style src="./style.scss" lang="scss" scoped> </style>
footer.vue中也是对footer进行了封装,感觉和封装普通的组件差别不大,不过在使用vuex,state之类的属性的时候就难起来
//src\components\Footer.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 { width: 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>
封装的骨架
//src\components\common\Skeleton\SkeletonList.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', width: '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>