这是一个非常有意思的项目,我们先来看看效果
这个项目所用的技术也比较有意思,它的技术栈为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>