這個百度貼吧的項目是 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-cli3的腳手架。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選項,將頁面劃分開來。
路由將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
複製代碼
對axios設置了攔截器,功能有三:
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)
}
},
複製代碼
效果圖
頂部的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) // 頂部的標記初始化到第一個
})
}
複製代碼
background-size: 30px 30px
background-repeat: no-repeat
background-position: center center
background-image: url('../../assets/icon/left.png')
複製代碼
要注意的問題:
各個頁面的小功能寫的很少,但不少模塊難度問題不大,沒有詳細寫的必要,具體可看源碼。
整個項目寫下來,也有很多的問題。主要仍是總體性上有欠缺,組件化不夠完全。UI風格上仍是沒辦法和百度貼吧一致(有必定的誤差)。
若是你喜歡這篇文章或者能夠幫到你,給做者一點鼓勵,點個贊在走吧!同時也很是但願看到這篇文章的你能發表一點看法!