Vue全棧開發之百度貼吧

這個百度貼吧的項目是 vue + koa + sequelize 的項目。javascript

因爲沒有百度貼吧API接口,因此本身寫了後端css

項目部分截圖(GIF)

項目的依賴以及準備工做

前端: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

有幾點要前面說一下

  1. 該項目的權限控制只採用了一個token,比較簡單,但不是很安全。有興趣能夠看看權限控制
  2. 因爲先後端分離寫的代碼,因此致使登陸認證時設置的cookie 不能在前端取到(應該是域名訪問的問題,取出來是undefined),因此只能將token信息傳給前端,讓前端本身設置cookie(權益之計,不建議採用)
  3. 沒有API,也沒寫爬蟲,因此數據都是手寫的,數據量不多,因此一些效果沒法實現。
  4. 最近看過的帖子和最近訪問的吧, 這兩個模塊的數據是採用localStorage存數據,其它的數據均來自後端
  5. 由於後端寫的不是很好,就不講後端的內容了

項目啓動

Tabbar

這是成品效果

效果

採用了vue-router 中 router-link to屬性配置的效果

點擊的該項會多出兩個class屬性

  1. router-link-exact-active (精確匹配規則,路由路徑徹底一致時會有)
  2. router-link-active (全包含匹配規則,也就是父路由也會有)

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'
]
複製代碼

抽離基本配置以及插件

  • iview組件的引入方式採用按需引入,在utils目錄下建立一個iview.js 將iview引入的代碼進行統一管理。
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

複製代碼
  • filter 全局過濾器,使用moment實現對時間的處理,數字的處理,統一進行管理

  • 對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)
  }
)
複製代碼

部分功能具體實現

首頁刷新功能

效果圖

分爲兩個階段

  1. 首先是監聽滾動的事件,滾動值超過某個值(我的設置500),觸發事件將tabbar中的首頁替換成刷新按鈕。
  2. 點擊刷新按鈕,將vuex中的refreshData屬性變爲true。而後在首頁中監聽這個屬性,從新加載數據而且觸發better-scroll的滾動事件,讓其滾動到最上方。

其中仍是有些小問題的:

  1. tabbar是採用router-link, 會在其事件冒泡階段阻止事件冒泡,進行其自身的路由跳轉事件,從而沒法觸發自定義的事件,解決辦法是在其內部處理掉點擊事件(也就是點擊事件冒泡到其渲染的元素以前處理掉點擊事件,並阻止點擊事件的冒泡)

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)
  }
},
複製代碼
  1. 要避免多餘的加載數據,因此採用了keep-alive標籤,在觸發better-scroll的scrollTo方法時,方法無效,具體緣由不知,但後面發現本身的better-scroll的版本是基礎型的,後面換成全能力滾動後,就能夠正常使用了(有知道具體緣由的朋友能夠聊一下)

實現優質的左右滾動

效果圖

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方面
    • 主要採用div,span,img,p 等標籤
  • css方面
    • 選擇器採用的class選擇器
    • 佈局主要採用 flex 佈局
    • z-index的層級上是 1-9 ,文字層級低,動畫蒙版等層級略高。
    • icon圖標的定義是採用了background 的形式
background-size: 30px 30px
background-repeat: no-repeat
background-position: center center
background-image: url('../../assets/icon/left.png')
複製代碼

要注意的問題:

  1. z-index的層級問題,子元素會繼承父元素的層級。
  2. 設置了層級,仍是會出現下層元素上浮的問題,有多是上層元素是透明的,添加背景顏色就能夠。
  3. 應減小使用js的setInterval造成的幀動畫,而應該採用css造成的補間動畫。

結語

各個頁面的小功能寫的很少,但不少模塊難度問題不大,沒有詳細寫的必要,具體可看源碼。

整個項目寫下來,也有很多的問題。主要仍是總體性上有欠缺,組件化不夠完全。UI風格上仍是沒辦法和百度貼吧一致(有必定的誤差)。

若是你喜歡這篇文章或者能夠幫到你,給做者一點鼓勵,點個贊在走吧!同時也很是但願看到這篇文章的你能發表一點看法!

前端源碼 後端源碼

相關文章
相關標籤/搜索