这个百度贴吧的项目是 vue + koa + sequelize 的项目。javascript
因为没有百度贴吧API接口,因此本身写了后端css
前端:vue + vuex + axios + better-scroll +iview + stylus (还有零散的依赖,完成某些小模块的,如 moment 完成项目内时间的需求)html
前端依赖项前端
"dependencies": { "axios": "^0.19.0", "better-scroll": "^1.15.2", "iview": "^3.4.2", "js-cookie": "^2.2.0", "jsonwebtoken": "^8.5.1", "moment": "^2.24.0", "vue": "^2.5.2", "vue-photo-preview": "^1.1.3", "vue-router": "^3.0.1", "vuex": "^3.1.1" },
后端:koa + koa-router + mysql2 + sequelize (这些是主要的,还有不少的中间件)vue
后端依赖项java
"dependencies": { "bcrypt": "^3.0.6", "env2": "^2.2.2", "jsonwebtoken": "^8.5.1", "koa": "^2.7.0", "koa-body": "^4.1.0", "koa-bodyparser": "^4.2.1", "koa-jwt": "^3.5.1", "koa-router": "^7.4.0", "koa-session": "^5.12.0", "koa-static": "^5.0.0", "koa2-cors": "^2.0.6", "mysql2": "^1.6.5", "sequelize": "^5.8.12" },
前端代码初始化是用来vue-cli的脚手架。mysql
图标是来自阿里巴巴矢量图标库,找到的图标尽可能和百度贴吧一致ios
前端的UI框架选择了iview(选择有些失误,由于百度贴吧的项目是移动端的,应该采用像vant这类优秀的移动端UI框架会更合适)git
准备工做是添加一些路径项 在build目录下的webpakc.base.conf.js文件中的alias选项中添加一些接下来项目中经常使用的路径github
这是成品效果
采用了vue-router 中 router-link to属性配置的效果
点击的该项会多出两个class属性
将linkActiveClass: 'active'
添加至router 配置中会将router-link-active 替换成active
这时就也可自定义active的样式了
大致分为5个部分,和零散的几个页面,5个部分中其中4个(除info)分别对应4个tabbar选项,将页面划分开来。
info中有三个page,分别是userInfo,baInfo,tieInfo
路由将5个部分分开定义,最后汇总
由于该项目的登陆认证对页面变化很大,因此在路由的beforeEach钩子函数中进行登陆状态的判断,对必须登陆才能访问的页面进行强制的登陆判断,不经过则跳转到登陆页。
router.beforeEach((to, from, next) => { // 必定须要登陆的url 没有认证信息 进行登陆 if (routerLoginRole.some(route => to.path === route) && !Cookies.get('username')) { next('/login') return } }
必须登陆的页面以下
export const routerLoginRole = [ '/release', '/message', '/user', '/focuslist', '/fanslist', '/focusbalist', '/tielist', '/setting', '/userhome', '/useredit', '/browsehistory', '/collection', '/release', '/like' ]
同时,不一样的页面 tabbar显示和消失状态也不一样,将tabbar的显示状态放入vuex 中,在beforeEach钩子函数中统一管理
if (TabbarRoutes.some(route => (to.path.indexOf(route) === 0))) { store.dispatch('hiddenTabbar') } else { store.dispatch('showTabbar') }
须要隐藏tabbar的页面以下
export const TabbarRoutes = [ '/login', '/register', '/search', '/focuslist', '/fanslist', '/focusbalist', '/tielist', '/setting', '/userhome', '/useredit', '/userinfo/', '/tieinfo/', '/bainfo/', '/browsehistory', '/release', '/collection', '/like' ]
import Vue from 'vue' import { Button, Input, Switch, Progress, Form, FormItem, Icon, Upload, Dropdown, DropdownMenu, DropdownItem, Message, Spin } from 'iview' import 'iview/dist/styles/iview.css' Vue.component('Button', Button) Vue.component('i-input', Input) Vue.component('i-switch', Switch) Vue.component('Progress', Progress) Vue.component('Form', Form) Vue.component('FormItem', FormItem) Vue.component('Icon', Icon) Vue.component('Upload', Upload) Vue.component('Dropdown', Dropdown) Vue.component('DropdownMenu', DropdownMenu) Vue.component('DropdownItem', DropdownItem) Vue.component('Spin', Spin) Vue.prototype.$Message = Message export default Vue
1.设置axios请求的默认路径以及运行其携带cookie信息
2.部分api接口须要登陆验证 则须要传给服务器包括token的cookie凭证
3.请求数据时的加载动画
axios.defaults.baseURL = 'http://192.168.1.4:3000/' axios.defaults.withCredentials = true axios.defaults.timeout = 5000 axios.interceptors.request.use( config => { // 给$http请求设置cookie请求头 store.dispatch('showLoading') const token = Cookies.get('username') const isTokenRight = !!(token && JsonWebToken.decode(token)) if (isTokenRight) { config.headers.common['Authorization'] = 'Bearer ' + token } return config }, error => { Message.error('网络异常,请稍后再试') store.dispatch('hiddenLoading') return Promise.reject(error) } ) axios.interceptors.response.use( response => { store.dispatch('hiddenLoading') if (response.data.statusCode !== 200) { Message.error(response.data.message) return Promise.reject(response) } return response }, error => { Message.error('网络异常,请稍后再试') store.dispatch('hiddenLoading') return Promise.reject(error) } )
效果图
分为两个阶段
其中仍是有些小问题的:
tabbar页面中首页选项的html结构
<router-link tag="span" class="tabbar-item" to="/home"> <div class="tabbar-item-icon icon-home" v-show="!this.$store.getters.isRefresh"> </div> <div class="tabbar-item-icon icon-home refresh" v-show="this.$store.getters.isRefresh" @click.stop.prevent="refresh"> </div> <span class="tabbar-item-label" v-show="!this.$store.getters.isRefresh">首页</span> <span class="tabbar-item-label refresh" v-show="this.$store.getters.isRefresh" @click.stop.prevent="refresh">刷新</span> </router-link>
其触发的刷新方法
refresh () { if (this.$store.getters.isRefresh) { this.$store.commit('updateRefreshData', true) } },
效果图
html可分为两部分 顶部的三个tab 和 下面的三个页面
顶部的tab动画效果为css的transition: all 0.3s ease
页面采用了better-scroll,并在scrollEnd钩子函数中进行相关操做。
顶部的标记滑动事件
romve (index) { let name = 'message-title-' + index this.index = index this.oldX = -index * this.$refs['message-content'].offsetWidth this.$refs.unline.style.left = this.$refs[name].offsetLeft + 'px' this.contentScroll.scrollTo(-this.$refs['message-content'].offsetWidth * index, 0, 300) },
左右滚动的初始化
initScroll () { this.$nextTick(() => { if (!this.contentScroll) { this.contentScroll = new BScroll(this.$refs['message-content'], { startX: 0, click: true, tap: true, scrollX: true, scrollY: false, momentum: false // 不让其生成滚动的滑行动画 }) } else { this.contentScroll.refresh() } this.contentScroll.on('scrollEnd', ({ x }) => { let width = this.$refs['message-content'].offsetWidth // 获取单个页面的宽度 if (x !== -width && x !== -2 * width && x !== 0) { // 避免自动滚动后继续滚动 if (Math.abs(x - this.oldX) < width / 4) { // 滚动量小于整个页面的1/4时 自动复原 this.contentScroll.scrollTo(-this.index * width, 0, 300) } else if (this.oldX > x) { // 向左滑动时 自动滚到到右边页面 this.contentScroll.scrollTo(-(++this.index) * width, 0, 300) } else { // 向右滑动时 自动滚到到左边页面 this.contentScroll.scrollTo(-(--this.index) * width, 0, 300) } this.oldX = -this.index * width // 从新计算值 this.romve(this.index) // 使顶部的标记滑动到对应的位置 } }) this.romve(0) // 顶部的标记初始化到第一个 }) }
html方面
css方面
background-size: 30px 30px background-repeat: no-repeat background-position: center center background-image: url('../../assets/icon/left.png')
要注意的问题:
各个页面的小功能写的很少,但不少模块难度问题不大,没有详细写的必要,具体可看源码。
整个项目写下来,也有很多的问题。主要仍是总体性上有欠缺,组件化不够完全。UI风格上仍是没办法和百度贴吧一致(有必定的误差)。
若是你喜欢这篇文章或者能够帮到你,给做者一点鼓励,点个赞在走吧!同时也很是但愿看到这篇文章的你能发表一点看法!