生活少不了趣味,趣味活在人间。每一天都是快乐的,happy every day.
前言
各位掘友,又是新的一周,你们周末过得很happy吧!周末,小编利用闲暇的时间,开源一个趣味生活小型vue项目。整个小项目做下来之后还真收获不少呢。
- vue-cli2脚手架
- vue、vue-router(项目较小不使用vuex,但为后期做了些铺垫)
- axios
- fastclick
- better-scroll
- stylus
- 阿里巴巴矢量图标库-提供一些图标icon
- vsCode
- 数据来源:阿凡达云数据平台
你将学到:
- 封装axios
- 跨域
- 组件化编程
趣味生活项目实现过程
项目预期效果,小编带大家瞅瞅!顺便,随手点赞支持下作者💓
实现思路
- 阶段一:实现【我的】及【tabBar】部分
- 阶段二:实现【趣图】部分 & axios封装 & 跨域
- 阶段三:实现【推荐】部分 & 跨域
阶段一
在阶段一中,主要难点设计是:在于tabBar和headerBar部分,因为你要考虑怎么让两个导航栏同时在一个页面出现呐而且还不会影响icon的颜色问题。
所以,此时你就需要想到使用 组件化 来解决 tabBar和headerBar共处问题。过程如下:
首先,将tabBar单独拿出来做成一个组件,如果你觉得tabBar需要更灵活些,你可以在父组件中动态绑定数据传值处理。具体tab.vue组件设计如下:
<template> <div class="tab"> <router-link to="/recommend" tag="div" class="tab-item"> <div class="icon"></div> <div class="tab-link">推荐</div> </router-link> <router-link to="/img" tag="div" class="tab-item"> <div class="icon"></div> <div class="tab-link">趣图</div> </router-link> <router-link to="/mine" tag="div" class="tab-item"> <div class="icon"></div> <div class="tab-link">我的</div> </router-link> </div></template><script>export default {}</script><style lang="stylus" scoped>@import '../assets/css/function.styl'.tab display flex position absolute z-index 150 bottom 0 left 0 width 100% background-color #fff border-1px(#F5F5F5) font-size 12px height px2rem(140px) &-item flex 1 text-align center .icon margin-top px2rem(15px) .tab-link padding-bottom 5px color #000 &.router-link-active .tab-link color #1E90FF .icon color #1E90FF</style>
在父组件中使用如下:
<template> <div id="app"> <!-- tabBar --> <v-tab></v-tab> <router-view/> </div></template><script>import tab from '@/components/tab'export default { name: 'App', components: { 'v-tab': tab }}</script><style></style>
by the way,为啥tab.vue放在App.vue中呢?这里当然是为了让每个页面都可以通过点击相应的tabBar进行跳转啦。
我的部分
各位,这里需要注意啦!,在mine.vue中也有一个headerBar,该栏是是什么用途呐?如下图:
在【我的】-的headerBar其作用就是为了,扩展在【我的】页面中的子页面跳转,比如【设置】、【消息】等功能模块。headerTab设计具体如下:
<template> <div class="mHeader"> <div class="mHeader-icon" @click="leftEvent"> <slot name="left-icon"></slot> </div> <div class="mHeader-cont"> <slot name="content"></slot> </div> <div class="mHeader-icon"> <slot name="right-icon"></slot> </div> </div></template><script>export default { name: 'hd', data () { return { } }, methods: { leftEvent () { this.$store.dispatch('setShowSidebar', true) } }}</script><style lang="stylus">@import "../assets/css/function" .mHeader height px2rem(88px) line-height px2rem(88px) display flex align-items center justify-content space-between color #746ca8 font-size px2rem(30px) &-icon flex 0 0 px2rem(88px) margin-top px2rem(6px) cursor pointer .icon font-size px2rem(48px) .left margin-left px2rem(20px)</style>
此外,需要再提到的一点就是:<slot name="left-icon"></slot>的具名使用,和不具名使用,当你组件中只有一块内容需要父组件来填充时,你就是用不具名slot,当组件中需要填充多块内容时,你就使用具名slot。案例如下:
父组件具名使用slot:
<!-- 头部 --> <v-header> <i class="icon" slot="left-icon"></i> <span slot="content">我的音乐</span> <router-link to="/user" slot="right-icon"> <i class="icon"></i> </router-link> </v-header>
封装的header组件的slot:
<template> <div class="header"> <div class="header-icon" @click="leftEvent"> <slot name="left-icon"></slot> </div> <div class="header-cont"> <slot name="content"></slot> </div> <div class="header-icon"> <slot name="right-icon"></slot> </div> </div></template>
阶段二
在阶段二中主要有两个重点一个技巧内容,其一就是: axios封装;其二就是:跨域数据请求;其三就是:借助better-scroll下拉页面加载新数据实现的技巧
axios封装
为什么做axios封装技术封装,不用问,问就是为了开发方便,以及统一化管理数据请求接口,而且方便后期维护项目。
那么如何来封装axios呐?很简单的!
第一步,在项目的src下新建一个api文件夹,并且新建一个index.js文件;第二步,导入Vue和axios;第三步,new 一个 Vue实例;第四步配置axios具体配置如下:
// axios 配置axios.defaults.timeout = 10000// 请求不能超时10S// axios.defaults.baseURL = '/api'axios.defaults.headers.post['Content-Type'] = 'application/json';// 判断返回状态, 响应拦截axios.interceptors.response.use((res) => { // 请求不成功时 if (res.status !== 200) { alert('网络异常') // Promise 有两个参数 reject, resolve return Promise.reject(res) } return res}, (error) => { alert('服务器开小差了') return Promise.reject(error)})// 请求拦截 - 暂时不写// ...
第五步,导出你需要请求的数据api方法,具体逻辑如下:
export function fetchGet (url, param) { return new Promise((resolve, reject) => { axios.get(url, { params: param }) .then(response => { resolve(response.data) }, err => { reject(err) }) .catch((error) => { reject(error) }) })}export default { // 按更新时间查询笑话 GetJokeByTime (params) { return fetchGet ('/api/Joke/QueryJokeByTime', params) }, // 最新笑话 NewJoke (params) { return fetchGet ('/api/Joke/NewstJoke', params) }, // 按更新时间查询趣图 GetImgByTime (params) { return fetchGet ('/api/Joke/QueryImgByTime', params) }, // 最新趣图 NewImg (params) { return fetchGet ('/api/Joke/NewstImg', params) }, HeadLine (params) { return fetchGet ('/api//TouTiao/Query', params) },}
以上便完成了axios封装,下面说下在组件中封装后的使用
在你需要使用axios封装的api的组件中,导入,并直接通过api.某方法直接调用,具体如下:
<script>import api from '@/api...此处省略一万行
methods: { _getNewJoke () { const params = { key: '991792145f62460bac35b1c92ee50cdb', page: this.page, rows: 20 } api.NewJoke(params) .then((res) => { console.log(res) if (res.error_code === 0) { this.result = res } }) } }...此处省略一万行
</script>
跨域数据请求
随着前后端分离技术的越来越盛行,跨域问题也逐渐凸显了出来。跨域问题的根本原因:因为浏览器收到同源策略的限制,当前域名的js只能读取同域下的窗口属性。什么叫做同源策略?就是不同的域名, 不同端口, 不同的协议不允许共享资源的,保障浏览器安全。同源策略是针对浏览器设置的门槛。如果绕过浏览就能实现跨域,所以说早期的跨域都是打着安全路数的擦边球,都可以认为是 hack 处理
网上有很多跨域请求得文章,小编就不多介绍,此次只介绍Vue的域名代理来解决跨域问题。想更深入学习跨域知识,小编推荐几款平台:掘金、思否、简书、CSDN等。
废话不多说,来看怎么通过vue域名代理来解决跨域问题吧。首先找到你的vue项目的config文件夹,在找到该文件夹下的index.js文件,使用Ctrl+F快速搜索proxyTable,在该项进行域名代理配置具体配置如下:
proxyTable: { '/api': { target: 'http://api.avatardata.cn', changeOrigin: true, pathRewrite: { '^/api': '' } } // 第二个域名代理 // '/top': { // target: 'https://www.toutiao.com/', // changeOrigin: true, // pathRewrite: { // '^/top': '' // } // } },
其中,'/api'①和'/top'②表示你在数据请求的前缀名,target表示你需要跨域的目标地址;changeOrigin表示是否进行跨域;
①:fetchGet ('/api/Joke/QueryJokeByTime', params)
②:fetchGet ('/top/News', params)
下拉加载数据
在实现拉下加载数据之前,你需要了解:better-scroll;那么你会用到哪些方法和事件呐?你会使用到的方法:refresh(),scrollTo(),scrollToElement();你会使用到的事件:beforeScroll,scrol,scrollToEnd;至于其他方法和事件请见better-scroll官网;
那么下面我们开始来实现下拉加载数据吧
首先,二话不说就来一个组件封装掉你要使用的better-scroll,以此更方便你使用better-scroll;你可能很疑问,明明人家官方做得会很好封装了直接调过来用不就完了吗?你说的也没错,但是你仔细考虑一下,你需要更改某些效果或者做更多的逻辑判断,你把一个页面写了几千行代码,后期维护,谁愿意来看这段代码,如果你做更进一步的封装是不是节省了整个页面的代码量,维护起来也方便。better-scroll的封装如下:
<template> <div ref="wrapper"> <slot></slot> </div></template><script>import BScroll from 'better-scroll'import { debounce } from '@/common/utils'const DIRECTION_H = 'horizontal'const DIRECTION_V = 'vertical'export default { name: 'scroll', props: { /** * 1 滚动的时候会派发scroll事件,会节流。 * 2 滚动的时候实时派发scroll事件,不会节流。 * 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件 */ probeType: { type: Number, default: 1 }, /** * 点击列表是否派发click事件 */ click: { type: Boolean, default: true }, /** * 是否开启横向滚动 */ scrollX: { type: Boolean, default: false }, /** * 是否派发滚动事件 */ listenScroll: { type: Boolean, default: false }, /** * 列表的数据 */ data: { type: Array, default: null }, pullup: { type: Boolean, default: false }, pulldown: { type: Boolean, default: false }, beforeScroll: { type: Boolean, default: false }, /** * 当数据更新后,刷新scroll的延时。 */ refreshDelay: { type: Number, default: 10 }, direction: { type: String, default: DIRECTION_V } }, mounted () { setTimeout(() => { this._initScroll() }, 20) }, methods:{ _initScroll () { if (!this.$refs.wrapper){ return } this.scroll = new BScroll(this.$refs.wrapper, { click: this.click, probeType: this.probeType, eventPassthrough: this.direction === DIRECTION_V ? DIRECTION_H : DIRECTION_V }) // 监听 滑动, 并抛出其滚动距离 if (this.listenScroll) { debounce(this.scroll.on('scroll', (pos) => { // this.$emit('scrol', pos) // console.log(pos.y) if (pos.y > 100) { this.$emit('scrol') } }), 300) } // 派发 上拉 加载更多 if (this.pullup) { this.scroll.on('scrollEnd', ()=>{ if (this.scroll.y <= this.scroll.maxScrollY + 150) { this.$emit('scrollToEnd') } }) } // 派发下拉 刷新 if (this.pulldown) { this.scroll.on('touchend', (pos) => { if (pos.y > 50) { this.$emit('pulldown') } }) } // 是否 派发列表滚动 开始事件 if (this.beforeScroll) { this.scroll.on('beforeScrollStart', () => { this.$emit('beforeScroll') }) } }, disable() { // 代理better-scroll的disable方法 this.scroll && this.scroll.disable() }, enable() { // 代理better-scroll的enable方法 this.scroll && this.scroll.enable() }, refresh() { // 代理better-scroll的refresh方法 this.scroll && this.scroll.refresh() }, scrollTo() { // 代理better-scroll的scrollTo方法 this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments) }, scrollToElement() { // 代理better-scroll的scrollToElement方法 this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments) }, }, watch: { // 监听数据变化,延时XX时间后刷新better-scroll的效果,保证滚动效果正常 data () { setTimeout(() => { this.refresh() }, this.refreshDelay) } }}</script><style></style>
那么,下小编这里还是需要再谈到一下slot的使用,因为它的灵活性让程序编码确实很方便,只要你父组件需要给组件填充内容使用它特别方便。
此外,这里还使用到了 debounce的小知识,为啥呐?当然时为了防止你不断的下拉,造成多次的数据请求,因为你意图就是加载一次数据吗,而且他也会减缓服务器的压力。debounce实现逻辑如下:
export function debounce (func, delay) { let timer return function (...args) { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { func.apply(this, args) }, delay) }}
紧接着,我们来看下拉加载数据的思路: 在父组件中的使用better-scroll的封装组件上,绑定data数据源和scroll、beforeScroll方法。每当你向下拉页面超过某个高度时,利用发布-订阅者模式的长处,通过scroll事件再给父组件派发this.$emit('scrol')方法。此时,父组件中编写其对应的绑定的方法,去请求数据并更新页面的数据。具体实现如下:
<template>
...此处省略一万行 <v-scroll class="content-scroll" ref="contentScroll" :listenScroll="listenScroll" :data="result" :beforeScroll="beforeScroll" @scrol="searchMore"> <ul class="content-article-list"> <!-- loading --> <v-load class="loading-wrapper" v-show="display"></v-load> <li class="article-summary" v-for="(article, index) in result" :key="index"> <h3 class="article-title">{{article.content}}</h3> <div class="article-author">来源:{{article.hashId}}</div> <img :src="article.url" alt="" class="pic"> <!-- 尾部组件:用于点赞、分享、踩 --> </li> </ul> </v-scroll>
...此处省略一万行
<script>import scroll from '@/components/scroll'...此处省略一万行
methods: { _getNewImg () { const params = { key: '991792145f62460bac35b1c92ee50cdb', page: this.page, rows: 20 } api.NewImg(params) .then((res) => { // console.log(res) if (res.error_code === 0) { this.result = [...res.result, ...this.result] this.display = false // this._checkMore(res.result) console.log(this.result) } }) }, _checkMore (data) { if (data.length < 10) { this.display = false } }, searchMore () { this.display = true this.page++ this._getNewImg() } }, mounted () { this._getNewImg() setTimeout(() => { this.show = false }, 1500) }}</script>
最后,需要注意的时组件v-scroll中有一个watch需要注意如下:
watch: { // 监听数据变化,延时XX时间后刷新better-scroll的效果,保证滚动效果正常 data () { setTimeout(() => { this.refresh() }, this.refreshDelay) } }
其目的就是,为了解决,当数据更新时,better-scroll重新渲染。
阶段三
在阶段三中需要学习的东西比较少,可能需要讲解的知识点就只有,数据请求接口的key需要更好掉;在vue项目中怎么进行url页面跳转了;以及还有一个css3函数编写需要注意。
其一
数据请求key更换,为啥要提,因为在阿凡达云数据平台,不同的数据类型有不同的key值,你需要找到你记得key值,以及请求数据的关键路径。配置如下:
其二
怎么在vue项目中进行url跳转,确实很简单,你回一下h5项目的页面跳转js如何调用了浏览器的href。详情如下:
<button class="item-button" @click="detail(headline.url)">查看详情</button>...此处省略一万行
detail (url) { window.location.href = url },
...此处省略一万行
顺便再提一下,vue项目的页面切换直接再js中怎么跳转呐?简单,直接调用this.$router.push,具体如下:
<button class="item-button" @click="detail(headline.key)">查看详情</button>
detail (id) { this.$router.push({path: '/detail', query: {id: key}}) },
其三
css3函数编写,其意图是为了解决统一管理样式主题样式,以及减少重复性css代码做到css的封装复用。具体编写如下(此处是css的stylus写法):
px2rem($px) return ($px / 37.5px)remborder-1px($color) border-top 1px solid $color
最关键的🚀🚀
小编已经明白大家的想法了,是不是看源码呐!在此,小编放个GitHub连接,掘友们多多使用你们的小星星哦✿ヽ(°▽°)ノ✿,点赞加关注,继续我们的技术学习旅程!🎉
项目源码
结语
希望小编的Y式分享能帮助到你,传播知识,分享快乐,与你们一起共同进步~😄